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と比較演算子

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( )内でのインデントは、結果に影響を与えません。

⚠️ **GitHub.com Fallback** ⚠️