8-2.全食品の処理(後半) - nsaku/xrepo-api-tutorial GitHub Wiki

やること

時間のかかりそうな処理をするときのテスト方法も分かったので、実際にデータ処理のコードを書いてゆきます。

また、時間のかかる処理の結果をファイルに書き出し、再利用できるようにします。

データを覚えておく

ここでは、MS2またはMS3スペクトルが取得できたピークについて、そのFlavonoidSearchのスコアが 0.3以上であったピークについて、MS2, MS3それぞれで該当ピークを洗い出すことを目指します。

test11.pyを作成し、前回までのコードをもう一度書きましょう。こんな感じでした。

import requests


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

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

count_loop = 0

for sample in samples:

    count_loop = count_loop + 1

    if( 5 < count_loop ):
        break
    
    fid = sample["fid"]

    print ( str( count_loop) + ". processing: " + fid )

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

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

    for peak in peaks:
        if( 0.5 < peak["scoreFs2"] ):
            # do something
            continue
        if ( 0.5 < peak["scoreFs3"] ):
            # do something
            continue

MS2、MS3それぞれでデータを保持するために、新たな配列を2つ用意して、# to do somethingと書かれた 部分に、要素を付加するコードを書きます。continueは必要ないので削除します。

そして、データを確認するために、json.dumpで整形して書き出してみましょう。そのために、import jsonも追加します。

以下のようになります。

import requests
import json                                     # -- 追加

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

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

peaks_fs2 = []                   # -- 追加
peaks_fs3 = []                   # -- 追加


count_loop = 0

for sample in samples:

    count_loop = count_loop + 1

    if( 5 < count_loop ):
        break
    
    now_fid = sample["fid"]

    print ( str( count_loop) + ". processing: " + now_fid )

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

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


    for peak in peaks:
        if( 0.5 < peak["scoreFs2"] ):
            
            peaks_fs2.append( peak )     # -- ifブロックの処理を変更
            
        if ( 0.5 < peak["scoreFs3"] ):

            peaks_fs3.append( peak )     # -- ifブロックの処理を変更

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

# --- 以降、書き出し処理。

fh_out_fs2 = open( "out11_fs2.json", "w", encoding="utf-8" )
fh_out_fs3 = open( "out11_fs3.json", "w", encoding="utf-8" )

json.dump(peaks_fs2, fh_out_fs2, indent=4, ensure_ascii=False )
json.dump(peaks_fs3, fh_out_fs3, indent=4, ensure_ascii=False )

fh_out_fs2.close()
fh_out_fs3.close()

書き出し処理は、一つずつの意味合いを復習しながら、ミスタイプとかがないことを確認して入力しましょう。

準備ができたら実行してみます。

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

MS2、MS3のFlavonoidSearch結果が0.5以上だったのは、それぞれ4ピーク、3ピークでした。

出力ファイルは無事出来上がったでしょうか? MS2のほうの結果を見てみましょう。

out11_fs2.json

[
    {
        "id": 14074405,
        "mnMId": "SE122_S010801_M90",
        "mnDId": "D01",
        "pid": "1533",
        "type": "pos",
        "rt": 38.661,
        "mz": 595.1658935546875,
        "intensity": 2278.65,
        "intensityLog": -0.0316023,
        "adduct": "[M+H]+",
        "mzDeionized": 594.1586171027875,
        "annot": "(100 names) Scutellarein 7-neohesperidoside;Elatin(flavonoid);Pelargonidin 3-gentiobioside;Kaempferol 7-neohesperidoside;Luteolin 4'-rutinoside;8-C-Glucopyranosylgenistein 4'-O-glucoside;Galein;Galeine;Chrysoerio...",
        "skeleton": "",
        "ms2": 1,
        "ms3": 0,
        "scoreFs2": 0.555556,
        "scoreFs3": -1.0,
        "db_all": 98,
        "db_kg": 12,
        "db_kn": 94,
        "db_hm": 25,
        "db_lm": 83,
        "db_fl": 85,
        "colorCode": "",
        "colorCodeFont": "",
        "printString": "SE122_S010801_M90\tD01\t1533\tpos\t38.661\t595.1658935546875\t2278.65\t-0.0316023\t[M+H]+\t594.1586171027875\t(100 names) Scutellarein 7-neohesperidoside;Elatin(flavonoid);Pelargonidin 3-gentiobioside;Kaempferol 7-neohesperidoside;Luteolin 4'-rutinoside;8-C-Glucopyranosylgenistein 4'-O-glucoside;Galein;Galeine;Chrysoerio...\t\t1\t0\t0.555556\t-1.0"
    },
    ...
]

ちゃんとscoreFs2が0.5以上のものが取れてきているようです。

しかし、結果をよく見ると、実はpeak情報には、食品番号(fid)や名前がなく、どのピークがどのサンプル由来なのかは、 分かりませんでした。実は、mnMIdというデータの「SE***_S@@@@@@_M90」という部分が、食品番号+枝番になっているのですが、 通常はそんなルールまで気づくことはできません。

これでは自分が後で見た時に不便なので、出力データを改良することを考えてみましょう。

出力データに、食品番号を追加する

ピーク情報を、食品ごとにまとめて、次のような形になるように設計してみましょう。

[
    {
        "sample_id": "010801",
        "name": "ごま いり(白ごま)",
        "peaks": [
            {
                "id": 14074405,
                "mnMId": "SE122_S010801_M90",
                "mnDId": "D01",
                "pid": "1533",
                "type": "pos",
                "rt": 38.661,
                "mz": 595.1658935546875,
                ....
            },
            ....
        ]
    },
    ....
]

ということで、コードを改良してみます。

test12.pyに書いてゆきましょう。

一番外側が[ ] なので、最初に空の配列を作るところは同じです。

import requests
import json

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

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

data_fs2 = []                                     # 1) -- 格納する変数名を変更
data_fs3 = []                                

count_loop = 0

for sample in samples:

    count_loop = count_loop + 1

    if( 5 < count_loop ):
        break
    
    now_fid = sample["fid"]

    print ( str( count_loop) + ". processing: " + now_fid )

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

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

    now_sample_fs2 = {}                           # 2) -- このサンプルのデータを格納するオブジェクトを作成
    now_sample_fs3 = {}                           

    now_sample_fs2["sample_id"] = now_fid         # 3) -- 食品番号を格納
    now_sample_fs3["sample_id"] = now_fid         

    now_sample_fs2["name"] = sample["nameJa"]     # 4) -- 食品の日本語名を格納
    now_sample_fs3["name"] = sample["nameJa"]    
    
    peaks_fs2 = []                                # 5) -- ピーク情報を格納する配列を作成
    peaks_fs3 = []                                

    for peak in peaks:
        if( 0.5 < peak["scoreFs2"] ):
            
            peaks_fs2.append( peak )
            
        if ( 0.5 < peak["scoreFs3"] ):

            peaks_fs3.append( peak )
    
    now_sample_fs2[ "peaks" ] = peaks_fs2         # 6) -- ピークの配列を格納
    now_sample_fs3[ "peaks" ] = peaks_fs3         

    data_fs2.append( now_sample_fs2 )             # 7) -- 最終的なデータ(配列)に格納
    data_fs3.append( now_sample_fs3 )        
    

fh_out_fs2 = open( "out12_fs2.json", "w", encoding="utf-8" )    # -- ファイル名変更
fh_out_fs3 = open( "out12_fs3.json", "w", encoding="utf-8" )    

json.dump(data_fs2, fh_out_fs2, indent=4, ensure_ascii=False )
json.dump(data_fs3, fh_out_fs3, indent=4, ensure_ascii=False )

fh_out_fs2.close()
fh_out_fs3.close()
    1. 最終的にまとめ上げるデータ配列名を変えました。
    1. 食品ごとの処理の冒頭に、食品のデータを格納するオブジェクトを作成しています。 オブジェクトの作成は、ここで初めて出てきました。オブジェクトを作るのは、[]ではなく{}になります。 似たような記号ですが、意味が大きく異なるので注意しましょう。
  • 3), 4) 次いで、食品の番号と名前をオブジェクトに格納します。 オブジェクトのデータを取りだすときと逆に、項目名(キー)に値を設定することで、 そのキーに値がセットされます。存在しないキーの場合は、新たにキーごと作られます。
    1. ピークの情報を、食品ごとに覚えておくため、ここで変数を作成することにします。
    1. そしてピーク情報を、サンプルのオブジェクトに「peaks」という名前で格納します。
    1. 食品ごとの繰り返しの最後で、出来上がった食品ごとのオブジェクトを、最終データの配列に追加します。

あとは、そのデータをjson.dumpで書き出しています。

さて、結果はどうなったでしょうか?

test12_fs3.json

[
    {
        "sample_id": "01026",
        "name": "こむぎ [パン類] 食パン",
        "peaks": []
    },
    {
        "sample_id": "01038",
        "name": "こむぎ [うどん・そうめん類] うどん 生",
        "peaks": []
    },
    {
        "sample_id": "01069",
        "name": "こむぎ [その他] ちくわぶ",
        "peaks": []
    },
    {
        "sample_id": "01080",
        "name": "こめ [水稲穀粒] 玄米",
        "peaks": [
            {
                "id": 14074405,
                "mnMId": "SE122_S010801_M90",
                "mnDId": "D01",
                "pid": "1533",
                "type": "pos",
                "rt": 38.661,
                "mz": 595.1658935546875,
                "intensity": 2278.65,
                "intensityLog": -0.0316023,
                "adduct": "[M+H]+",
                ...
            },
            ...
        ]
    },
    ...
]

期待通りだったでしょうか?

データ構造的にはよさそうです。ただ、該当するピークが0件であっても、上記のように出力されていました。 これはこれで、あとでMS2とMS3の結果を比較する場合などに使えるかもしれないので、このままにしておきましょう。

もし、ヒット0件のデータを格納したくない場合は、上記7)の部分で、ピークの数で場合分けをすればOKでしょう。 これまで学んだ知識で実現できます。

data_fs2.append( now_sample_fs2 ) 

を、以下のように改変します。

if( len ( now_sample_fs2 [ "peaks" ] )):
    data_fs2.append( now_sample_fs2 ) 

全食品に対する処理をする。

上記までで、繰り返し処理もうまく動作し、欲しいデータも保持できるようになりました。 それでは、全サンプルのデータを書き出してみましょう。

プログラムに手を加えるのは下記の一か所だけで済みます。

count_loop = 0

for sample in samples:

    count_loop = count_loop + 1

#    if( 5 < count_loop ):              # -- この2行をコメントアウト
#        break              

無事終了したでしょうか? 私の環境では8分弱で終わりました。

(補足)かかった時間をはかる

上記の処理時間は、コンソールでの処理の進行を、時計を見ながら測ってもよいのですが、 もっと確実に測る方法があります。現在時刻を測定する関数を使うのです。

import requests
import json
import time                # -- これを追加

start_time = time.time()

# ...メインの処理(省略)...

# -- プログラムの最後に以下を追加
end_time = time.time()
period = end_time - start_time
period = period / 60
print(str(period))

timeモジュールのtime()関数は、現在時刻を取得するものです。 なので、はじめと終わりで時刻を取得し、その差を取れば、かかった時間がわかります。

時刻の単位は秒なので、適宜60や3600で割ることで、かかった時間を分や時間で知ることができます。 割り算はスラッシュ「/」で表します。

ところで、time.time()で秒として取得される時刻とは、いつを基準にしているのでしょうか?

答えは、協定世界時(UTC)1970年1月1日午前0時0分0秒で、この時刻を0としています。 この基準時刻はUNIXエポックと呼ばれ、コンピューターの時刻の基準としてよく使われています。

ただ、数字が一秒に1ずつ増えてゆくので、当然、長い時間が経つほど大きな数字になり、 システムによっては、覚えておける桁数がオーバーフローして、予期せぬエラーが発生する可能性があります。 古いシステムだとその期日は2038年1月19日で、これは2038年問題と言われています。意外ともうすぐですね。