7.ピーク一覧を取得して特定のピークだけ取り出す - nsaku/xrepo-api-tutorial GitHub Wiki
JSONのデータ構造を確認する方法が分かったので、練習に、ほかのデータの例としてピークの一覧の取得をしてみます。
そしてデータの中から、特定の条件に当てはまるピークだけを選別する方法を学びます。
食レポのAPIのヘルプにあるように、ピーク一覧を取得するには、
食品番号と、極性のタイプ(ポジティブモード"pos"かネガティブモード"neg"か)を指定して、下記のURLにアクセスします。
(注意) 大きなテキストデータのため、ブラウザで表示させると時間がかかります。
http://metabolites.in/foods/api/peaklist/01026/pos
それではデータの構造を細かく見るため、いったんこのデータを、json.dump()で整形して、ファイルに保存してみましょう。
test6.pyに記述してゆきます。保存するファイル名はout6.jsonとしました。
import requests
import json
url = "http://metabolites.in/foods/api/peaklist/01026/pos"
peaks = requests.get( url ).json()
out_file = "out6.json"
fh_out = open( out_file, "w", encoding="utf-8")
json.dump( peaks, fh_out, indent=4, ensure_ascii=False)
fh_out.close()
URLや取得したjsonを収める変数名(peaks)、ファイル名が変わっただけで、これまでと全く一緒ですね。
ファイルへの書き出しの速度は、ブラウザ等に画面表示するよりも断然速いので、数秒で終了したと思います。
それでは出力したファイルを見てゆきましょう。
[
{
"id": 14048392,
"mnMId": "SE112_S010261_M90",
"mnDId": "D01",
"pid": "4",
"type": "pos",
"rt": 5.0557,
"mz": 182.98452758789062,
"intensity": 184.944,
"intensityLog": -1.12129,
"adduct": "[M+H]+",
"mzDeionized": 181.9772511359906,
"annot": "",
"skeleton": "",
"ms2": 0,
"ms3": 0,
"scoreFs2": -1.0,
"scoreFs3": -1.0,
"db_all": 0,
"db_kg": 0,
"db_kn": 0,
"db_hm": 0,
"db_lm": 0,
"db_fl": 0,
"colorCode": "",
"colorCodeFont": "",
"printString": "SE112_S010261_M90\tD01\t4\tpos\t5.0557\t182.98452758789062\t184.944\t-1.12129\t[M+H]+\t181.9772511359906\t\t\t0\t0\t-1.0\t-1.0"
},
{
"id": 14048393,
"mnMId": "SE112_S010261_M90",
"mnDId": "D01",
"pid": "5",
"type": "pos",
"rt": 5.05581,
"mz": 224.01167907714844,
...
},
...
]
データ構造はどうでしょうか?
サンプル一覧と同様に、一番外側が[ ]なので配列になっていて、その中に、"id"から"printString"までの26項目のデータを持つ オブジェクトが含まれていることが分かりました。
このデータは、食品番号「01026 こむぎ」のポジティブモードのデータなので、実際の食レポのページと見比べると、 どんな情報がどういう項目名(キー)で記載されているかが、だいたい分かると思います。
http://metabolites.in/foods/fid/01026
ピークの情報は、上記のpeaksの中に、配列として収められているはずなので、「繰り返し処理」をして、特定のデータだけ 取り出してみましょう。
新たに"test7.py"を作り、そこにコードを書いてゆきましょう。
import requests
url = "http://metabolites.in/foods/api/peaklist/01026/pos"
peaks = requests.get( url ).json()
for peak in peaks: # -- 追加
print( peak["pid"] ) # -- 追加(字下げが大事)
test6.pyで書いた、jsonを出力するためのコードは不要なので、それらを除外し、
あらたに、for
以下の2行を追加しました。
ここでの注意点は、print()の部分にインデント(字下げ)があることです。
書けたらIDLEで実行してみましょう。
全部表示されるまでに十数秒がかかるかもしれませんが、完了すると以下のようになります。
...
10277
10280
10270
10282
10283
10285
>>>
ここでは、peaksにおさめられたピークのデータを繰り返し処理し、各ピークのピークID("pid")を 全部出力しています。
for peak in peaks:
print( peak["pid"] )
for peak in peaks:
では、配列であるpeaksの中の各要素(オブジェクト)を、先頭から順に「peak」という
名前で取り出しています。取り出す変数名はpeakでなくてもなんでも構いません。「p」とか任意につけられます。
この行の最後にはコロン「:」がついており、繰り返し処理がここから始まることを示しています。
順に取り出したpeakのデータを処理しているのが2行目です。 ここでインデント(字下げ)をしていますが、これには重要な意味があります。 このインデント以降、同じ字数のインデントを持つ行は、取り出したpeakに対して行われる一連の処理のまとまりを 意味します。
ちょっと表示処理に時間がかかりますが、for
以下を改造して、次のコードを実行してみましょう。
for peak in peaks:
print( peak["pid"] )
print( str( peak["rt"] ) )
結果は以下のように、各peakのpidとrt(溶出時間)が交互に出力されます。
...
10270
104.964
10282
104.974
10283
104.974
10285
104.981
>>>
もうちょっと改変して、以下のようにわざとインデントをずらしてみましょう
for peak in peaks:
print( peak["pid"] )
print( str( peak["rt"] ) ) # -- インデントがそろっていない!
IDLEでは、実行しようとすると、「unexpected indent」という警告が出て実行できないと思います。
このように、一連の処理をする部分をプログラム言語では「ブロック」と呼びますが、 インデントでブロックを表現することが、pythonの特徴のひとつとなっています。 これにより、コードが簡潔になり、見やすくなります。
インデントは、半角スペースでもタブでも、個数も何個でもよいですが、コード中に 違うインデントが混在していると、エラーのもとになりますし、せっかくの可読性が下がってしまいます。 大多数のコードで「スペース4個」になっているそうなので、「スペース4個」をお勧めします。 IDLEでも標準でスペース4個になっています。
同じ繰り返し処理を使って、ピークの数を数えてみましょう。
forの付近を以下のように書き換えます。
count = 0
for peak in peaks:
count = count + 1
print(count) # -- インデントしていないことが大事
今度は、今までよりも短時間で処理が終了したことと思います。 IDLEでは、以下のような結果になったでしょうか?
6805
>>>
食レポの食品一覧(http://metabolites.in/foods/list)で表示されている数と一致していますね。
「peak」が何回取り出されたかを数えるために、繰り返しが行われるたびに、回数を覚えておく変数「count(初期値は0)」に 1を足しています。要素の数が1個なら、1回取り出されcountは1に、要素2個なら2回取り出されてcountは2になります。 「値を1増やす」という処理は、現在のcountの値に1を足した値を、再度count自身に設定することで実現しています。
最終的に、countの値をprintすることで、要素数が正しく表示されます。
ここでもし、初期値を1にしてしまうと、要素数が1のときにcountの値が2、要素数が2のとき値が3となってしまい、 最終的に実際の要素数よりも1多い値が表示されてしまうので、注意しましょう。 「要素が*個だったら値は~になって…」と、コードを見ながら具体的に考えると間違えがありません。
また、print()が、インデント(字下げ)をしていない元の位置に書かれていることに注目してください。
インデントを戻すことで、printは、forによる繰り返し処理のブロックが終わった後に実行されるため、 最後に1回だけ画面出力されています。
(補足)
print()の行を、インデントをしたまま実行してみましょう。
count = 0
for peak in peaks:
count = count + 1
print(count) # -- インデントしていないことが大事
同じインデントだと、繰り返しブロックの中と判断されて、要素が取り出されるごとにcountの値が表示されてしまいます。
まぁこれでも、やりたいこと(数を数える)は達成できていますが、時間もかかるしあまりスマートではないですね。
ちなみに、print(count)の前に、「インデントしていない空の行」があったとしても、同一ブロックだとみなされますので、 ご注意ください。
少しずつプログラムらしくなってきました。 forを使って、各ピークのデータを取り出せることが分かりました。
それでは次に、「特定の条件に合うピークだけを取り出す」ということをしてみましょう。
"test8.py"にコードを記述してゆきます。 これまで見た通り、画面表示には意外と時間がかかるので、最初は該当する数だけを数えるようにしてみます。
import requests
url = "http://metabolites.in/foods/api/peaklist/01026/pos"
peaks = requests.get( url ).json()
count = 0
for peak in peaks:
if( peak["adduct"] == "[M+H]+" ): # -- ここを変更
count = count + 1 # -- さらに字下げ
print( count )
上記で、判定されたアダクトイオンの種類が[M+H]+であるものだけを数えています。
結果は以下となっているはずです。
5312
>>>
if
は、( )
の中に書かれた条件に合致する場合だけ、そのブロックを処理します。
この場合、アダクトの種類が[M+H]+だった場合だけ、countに1を足して数を数えています。
( )
の中で条件判断をしている部分に注目してください。
peak["adduct"] == "[M+H]+"
上記のように、「イコール(=)」がふたつ書かれています。これはミスタイプではありません。
実は「==」は、左辺(この場合peak["adduct"])と右辺([M+H]+)の値が同じかどうかを判定する機能を持っていて、 「比較演算子」と呼ばれる特別な存在です。
他にも、以下のような演算子などが使えます。
比較演算子 | 意味 |
---|---|
== | 等しい |
!= | 等しくない |
< | (右辺は左辺)より大きい |
<= | (右辺は左辺)以上 |
> | (右辺は左辺)より小さい |
>= | (右辺は左辺)以下 |
比較演算子は、判定結果を、真(True)または偽(False)で返します。if
は、( )
内がTrueになる場合だけ、
そのブロックを実施することになります。
※文字列を比較する場合と、数値を比較する場合、あるいはオブジェクトや配列を比較する場合で、 判定結果は細かくルール化されています。期待しない判定結果になっている場合はこの辺り(特に文字列か数値か)が 原因になっている場合がありますので、そんなときは「python 比較演算子」でググって調べてみましょう。
比較演算子を色々変えて、想定した結果になるか、試してみてください。
例)
count = 0
for peak in peaks:
if( peak["mz"] < 150 ): # --「<」を使った
count = count + 1
print( str( peak["mz"] ) + "\t" + peak["annot"] ) # -- データの書き出し
print( count )
執筆時のデータでは、答えは11個になっていました。
このくらいの個数なら、画面出力にも時間がかからないと思われるので、 データの一部を書き出す処理も、ifブロックの中に入れてみました。
ifではさらに、複数の比較演算子の式を組み合わせて、複雑な条件のものを選別することができます。 組み合わせに使う演算子は、and、or、notの3種類です。
演算子 | 使い方 | 意味 |
---|---|---|
and | 式A and 式B | かつ (AとBがどちらともTrueの場合、True。それ以外なら、Falseを返す。) |
or | 式A or 式B | または (AかBのどちらかがTrueの場合、True。それ以外なら、Falseを返す。) |
not | not 式A | でない (AがTrueの場合、False。AがFalseの場合、Trueを返す。) |
これらは数式と同様に、( )を使って判断の優先順位をつけることができます。
例を見てゆきましょう。
(例1)14分より前のカラムに保持されなかったピークと、グラジエントが終わる97以降のピークを除外したい。
if( 14 < peak["rt"] and peak["rt"] < 97):
(例2)FlavonoidSearchのヒットスコアが、MS2またはMS3で0.3以上で、フラボノイド(アグリコンを含む)っぽい けれども、データベース検索結果がない(未知かもしれない)ピークを探したい。
if( ( 0.3 < peak["scoreFs2"] or 0.3 < peak["scoreFs3"] ) and
peak["db_all"] == 0 ):
※ifの( )内ではこのように、条件式を複数行にまたがって書いても問題ありません。if( )内でのインデントは、結果に影響を与えません。