8-1.全食品について、条件にあったピークを集める - nsaku/xrepo-api-tutorial GitHub Wiki

やること

・APIを組み合わせて大規模な検索をする。 ・選んだピーク情報だけ、別の配列に保持する。

前項までで、forやifを使って、大量のピークデータから、条件に合ったピークを選別する方法を学びました。 ただし、一つのサンプルについての実施例だけを見てきました。

APIの書式を見ると、サンプルのピーク情報を得るには、「食品番号」と「posかnegか」を指定すればよいです。

私たちはすでに、サンプル一覧を取得するAPIを知っているので、全食品の食品番号のリストを得ることができます。

両者を組み合わせれば、全食品について条件にあうピークを選ぶことができそうです。

(前準備)条件にあったピークを覚えておく

前項まででは、条件にあったピークが見つかった場合、それを直接画面に出力しました。 条件に合ったピークだけを、後で再利用できるように、別の配列変数に覚えさせる方法を見てみましょう。

test9.pyにコードを書いてゆきます。ここでは、MS2スペクトルでのFlavonoidSearchのスコアが0.3以上だったものを選んでみましょう。

import requests

url = "http://metabolites.in/foods/api/peaklist/05018_1/pos"

peaks = requests.get( url ).json()

peaks_fs2 = []               # -- 選んだピークを覚えておくための新たな配列

for peak in peaks:
    if( 0.3 < peak["scoreFs2"] ):
        peaks_fs2.append( peak )     # -- 条件に合ったものを配列に追加
        
print( len( peaks_fs2 ) )   # -- 配列の要素数(選ばれたピーク数)を表示

結果は以下になったと思います。

2
>>>>

プログラムの解説

選んだピークを覚えておくために、新たな配列「peaks_fs2」を準備しています。 新たな配列を作るときは、要素がない空の配列([])を代入して、

peaks_fs2 = []

と書きます。

元のデータも配列だったので、選んだピークの情報(オブジェクト)だけを、 新しい配列に付け加えてゆきます。これは、配列が持つappend()を使って、

peaks_fs2.append( peak )

と書きます。

プログラムの最後では、追加されたピークの数を知るために、len()関数を使って、 配列要素の数を書き出しています。

print ( len( peaks_fs2) )

数を数えるやり方にはこのような方法もあります。

覚えておく情報をもう少し拡張する(if, elif, else)

せっかくなので、ほかの条件に合うピークを取得することも考えてみましょう。 MS2だけでなく、MS3でFlavonoidSearchのヒットスコアが高いもの、などを選んでみましょう。

ifでは、ifの条件に合わなかった場合に、別の条件に合う場合の処理(elif)や、代わりに行う処理(else)を 記述することができます。

import requests

url = "http://metabolites.in/foods/api/peaklist/05018_1/pos"

peaks = requests.get( url ).json()

peaks_fs2 = []
peaks_fs3 = []
peaks_other = []

for peak in peaks:
    if( 0.3 < peak["scoreFs2"] ):
        peaks_fs2.append( peak )
    elif( 0.3 < peak["scoreFs3"] ):
        peaks_fs3.append( peak )
    else:
        peaks_other.append( peak )

print( len( peaks_fs2 ) )
print( len( peaks_fs3 ) )
print( len( peaks_other ) )

結果は以下のようになりました。

2
15
7943
>>>

if()の条件に合わない時ときに、elif()の条件が評価されます。それにも合わない場合、elseの処理が行われます。 elseには()の条件が必要ないことに注目してください。 elif()は、上の例では一つだけですが、複数個設けることもでき、上から順番に条件に合うまで評価されてゆきます。

結果を見るとほとんどがOtherに分類されるようですね。

元のピークの数とあっているかどうか、コードの最後に以下を追加して確認してみましょう。

print( len( peaks ) )

sum = len( peaks_fs2 ) + len( peaks_fs3) + len( peaks_other)
print( sum )

peaks_fs2、peaks_fs3、peaks_otherの合計を計算しています。

2
15
7943
7960    # -- peaksの数
7960    # -- peaks_fs2, peaks_fs3, peaks_otherの合計 = sum
>>> 

全食品で該当するピークを洗い出す

データの覚えさせ方が分かったので、全食品について条件に合うピークを見つけることを考えてみましょう。

ピーク一覧を取得するには「食品番号」が必要ですが、食品番号の一覧は、(*)で確認したように、別のAPIで取得できます。

そこで、以下のようにforを入れ子にすることで、この処理は実現できそうです。

test10.pyにコードを書いてゆきましょう。

ただし、注意点があります。

まだ実行しないでください!

import requests

url_samples = "http://metabolites.in/foods/api/samples"        

samples = requests.get( url_samples ).json()                   # 1)

for sample in samples:
    fid = sample["fid"]                                        # 2)

    url_peaks = "http://metabolites.in/foods/api/peaklist/" + fid + "/pos"   # 3)

    peaks = requests.get( url_peaks ).json()                   # 4)

    for peak in peaks:

        if( 0.3 < peak["scoreFs2"] ):
            # to do something

        if ( 0.3 < peak["scoreFs3"] ):                         # 5)
            # to do something

プログラムの解説

    1. 食品一覧を取得するAPIで、食品のデータ(samples)を取得しています。
    1. forを使って各サンプルについて、その食品番号(sample["fid"])を取得し、
    1. それを、ピーク一覧を取得するAPIのURLとして使っています。FlavonoidSearchの結果はポジティブモードにしか 付与されていないため、"pos"の部分は決め打ちしています。
    1. そのサンプルのピーク一覧の情報を取得し、forを使って各ピークを評価し、ifを使って条件に合ったピークに 対する処理を書いてゆきます(今はまだ何の処理も書いていません)
    1. MS2で条件にマッチし、かつMS3で条件にマッチするものをあるかもしれません。そのためここでは、elifではなく、 独立したifを設けることにしました。ifはこのような使い方もできます。

プログラムをテストする

さて、これで「# to do something」のところに処理を書いてプログラムを実行すれば、目的が果たせそうですが、 ちょっと気になることがあります。

今まで1食品についてプログラムを実行したときも、それなりに数秒程度の待ち時間がありました。 ピーク一覧を取得するAPIを使うと、1サンプルついて数千、数万という膨大なピーク情報を転送するので、 どうしても時間がかかってしまいます。

これを、食レポに登録されている200以上の食品について全部行うと、きっと、恐ろしく時間がかかることになります。 ですから、実際に全サンプルへの処理を行うのは、プログラムがうまく動くことをテストしてからがよさそうです。

ということで、次のような方針でテストすることを考えてみましょう。

200の食品全部を行うのではなく、5個くらいから動作を確認してみることにします。 そこで、プログラムを以下のように書き直します。

import requests

url_samples = "http://metabolites.in/foods/api/samples"        

samples = requests.get( url_samples ).json()                   

count_loop = 0                                  # 1) 追加
for sample in samples:

    count_loop = count_loop + 1                 # 2) 追加

    if( 5 < count_loop ):                       # 3) 追加
        break                                   # 4) 追加

    fid = sample["fid"]                
    
    print( str( count_loop ) + ". processing: " + fid )    # 5) 追加

    url_peaks = "http://metabolites.in/foods/api/peaklist/" + fid + "/pos"

    peaks = requests.get( url_peaks ).json() 

    for peak in peaks:

        if( 0.3 < peak["scoreFs2"] ):
            # to do something
            continue                            # 6) 追加

        if ( 0.3 < peak["scoreFs3"] ):       
            # to do something
            continue                            # 6) 追加
    1. 繰り返し処理の回数を数えるcount_loopという変数を設けました。
    1. samplesが繰り返し処理されるたびに1ずつ足して行き、
    1. 5以上になったときに、
  • 4)「break」というキーワードで、すぐ外側にある繰り返し処理(この場合はfor sample in samples:)を強制終了しています。

さらに、今どこまで処理が進んでいるかを見るために、

    1. 現在処理している食品番号を書き出すことにしました。

forなどの繰り返し処理を、条件によって制御するのは、「break」だけではありません。「continue」というキーワードは、 そこに到達したときに、あとのコードを実行せずに、次の繰り返し処理を開始します。

    1. ifで行う処理が何も書かれていないと、pythonではエラーとなってしまうので、とりあえず、 continueで何もせずに次の繰り返しに回るようにしました。

テスト実行 (強制終了のしかた)

準備がよさそうなので、実行してみたいのですが、念には念を入れておきましょう。 もし何か手違えで繰り返しが終わらない状態になったときの、対処法を確認しておきます。

  • python IDLEを使っている場合は、結果が表示されるウィンドウの右上の「×」ボタン(Windowsの場合)を押せば、 処理が強制終了されます。
  • コマンドプロンプトで実行している場合は、「Ctrlキー + Cキー」を押すことで、強制終了できます。

よろしいでしょうか?

それでは、上記のプログラムを実行してみてください。

1. processing: 01026
2. processing: 01038
3. processing: 01069
4. processing: 01080
5. processing: 01083
>>> 

無事、上記のような結果が得られたでしょうか?

おめでとうございます!

ここまでできれば、あとは実際にデータを処理するコードを書いてゆき、 テストして良さそうだったら、全食品を対象とした本番処理を行うだけです。