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