GraphQL-based APIによるPDBのデータ取得(Ligand 情報編)
はじめに
はじめて(PDB)https://www.rcsb.org/というタンパク質のDBを触ったが、GUIではなくAPIを利用したデータ取得に少し手間取ったのでメモ。参考は以下
今回は主に、GraphQLによる取得について書く
GraphQL-based API
GraphQLは使ったことがなかったが、JSON形式でqueryを記述することで複雑なデーター構造の所望のデータを取得できるようで、今回の需要にあっていると感じたし、そもそもこっちで取得しろよっていう雰囲気を感じた。
https://data.rcsb.org/#data-api https://data.rcsb.org/migration-guide.html#legacy-fetch-api
経緯
今回は以下のような状況でLigand情報を取得しようと試みた。
目的はLigandの分子量、ALOGPなどの各種特性を計算したいが、 そのLigandの
- ID
- 名前
- SMILES
が不明である。
ただし、
- 論文上で複数のProtein+Ligand複合体のPDB IDがわかっている。
- そのLigandとされる構造式も書かれている。
PDB IDからのLigand情報の取得
ドキュメントを読む限り、
Entry:特定のPDB構造関するデータ。
- 4文字の英数字による識別子(PDB IDで例えば、1Q2W)
- タイトル、寄託者のリスト、登録日、公開日、実験の詳細
Entity : PDBに存在する化学的な分子の内容。大きく分けて3種類ある
- polymer_entity - タンパク質、DNA、RNA
- Branched_entity - 直鎖状または分岐状の炭水化物(糖類およびオリゴ糖とか)
- nonpolymer_entity - 低分子化学物質(酵素の補酵素、Ligand、ionとか)
Entity Instance : PDBに存在するEntityのコピーでEntityと同様3種類ある
- polymer_entity_instance
- branched_entity_instance
- nonpolymer_entity_instance
Assembly : 生物学的ユニットを形成する構造要素
例えば、以下のようなものが記述されている
Chemical Component : PDBエントリーに含まれる全ての残基や低分子のデータ
例えば、以下のようなものが記述されている
- 化学記述子(SMILES & InChI)
- 化学式
- 系統的な化学名
ドキュメントを頼りに色々やったが、クエリ上で、フィルターするやり方がいまいちわからない。今回で言えば、pdbx_chem_comp_descriptor.type == 'SMILES_CANONICAL' & pdbx_chem_comp_descriptor.program == 'OpenEye OEToolkits'
この様に絞りたいが、どの様にクエリを書けばいいのだろうか・・・それに、階層構造もスキーマを見てもよくわからん
しかし幸い、サンプルにLigandのSMILESを取得するQueryが書かれていたのでそちらを参考にPythonで取得できるようにした。
import requests query = ''' { entry(entry_id:"1AZ1") { nonpolymer_entities { rcsb_nonpolymer_entity_container_identifiers { entry_id } nonpolymer_comp { chem_comp { id type } pdbx_chem_comp_descriptor { descriptor type program } } } } } ''' url = "https://data.rcsb.org/graphql?query=" + query response = requests.get(url) json_data = response.json() json_data
とすると以下のような結果が返ってくる
{'data': {'entry': {'nonpolymer_entities': [{'nonpolymer_comp': {'chem_comp': {'id': 'NAP', 'type': 'non-polymer'}, 'pdbx_chem_comp_descriptor': [{'descriptor': 'NC(=O)c1ccc[n+](c1)[C@@H]2O[C@H](CO[P]([O-])(=O)O[P@@](O)(=O)OC[C@H]3O[C@H]([C@H](O[P](O)(O)=O)[C@@H]3O)n4cnc5c(N)ncnc45)[C@@H](O)[C@H]2O', 'program': 'CACTVS', 'type': 'SMILES_CANONICAL'}, {'descriptor': 'NC(=O)c1ccc[n+](c1)[CH]2O[CH](CO[P]([O-])(=O)O[P](O)(=O)OC[CH]3O[CH]([CH](O[P](O)(O)=O)[CH]3O)n4cnc5c(N)ncnc45)[CH](O)[CH]2O', 'program': 'CACTVS', 'type': 'SMILES'}, {'descriptor': 'c1cc(c[n+](c1)[C@H]2[C@@H]([C@@H]([C@H](O2)CO[P@@](=O)([O-])O[P@](=O)(O)OC[C@@H]3[C@H]([C@H]([C@@H](O3)n4cnc5c4ncnc5N)OP(=O)(O)O)O)O)O)C(=O)N', 'program': 'OpenEye OEToolkits', 'type': 'SMILES_CANONICAL'}, {'descriptor': 'c1cc(c[n+](c1)C2C(C(C(O2)COP(=O)([O-])OP(=O)(O)OCC3C(C(C(O3)n4cnc5c4ncnc5N)OP(=O)(O)O)O)O)O)C(=O)N', 'program': 'OpenEye OEToolkits', 'type': 'SMILES'}, {'descriptor': 'InChI=1S/C21H28N7O17P3/c22-17-12-19(25-7-24-17)28(8-26-12)21-16(44-46(33,34)35)14(30)11(43-21)6-41-48(38,39)45-47(36,37)40-5-10-13(29)15(31)20(42-10)27-3-1-2-9(4-27)18(23)32/h1-4,7-8,10-11,13-16,20-21,29-31H,5-6H2,(H7-,22,23,24,25,32,33,34,35,36,37,38,39)/t10-,11-,13-,14-,15-,16-,20-,21-/m1/s1', 'program': 'InChI', 'type': 'InChI'}, {'descriptor': 'XJLXINKUBYWONI-NNYOXOHSSA-N', 'program': 'InChI', 'type': 'InChIKey'}]}, 'rcsb_nonpolymer_entity_container_identifiers': {'entry_id': '1AZ1'}}, {'nonpolymer_comp': {'chem_comp': {'id': 'ALR', 'type': 'non-polymer'}, 'pdbx_chem_comp_descriptor': [{'descriptor': 'O=C2c1c3c(ccc1)cccc3C(=O)N2CC(=O)O', 'program': 'ACDLabs', 'type': 'SMILES'}, {'descriptor': 'OC(=O)CN1C(=O)c2cccc3cccc(C1=O)c23', 'program': 'CACTVS', 'type': 'SMILES_CANONICAL'}, {'descriptor': 'OC(=O)CN1C(=O)c2cccc3cccc(C1=O)c23', 'program': 'CACTVS', 'type': 'SMILES'}, {'descriptor': 'c1cc2cccc3c2c(c1)C(=O)N(C3=O)CC(=O)O', 'program': 'OpenEye OEToolkits', 'type': 'SMILES_CANONICAL'}, {'descriptor': 'c1cc2cccc3c2c(c1)C(=O)N(C3=O)CC(=O)O', 'program': 'OpenEye OEToolkits', 'type': 'SMILES'}, {'descriptor': 'InChI=1S/C14H9NO4/c16-11(17)7-15-13(18)9-5-1-3-8-4-2-6-10(12(8)9)14(15)19/h1-6H,7H2,(H,16,17)', 'program': 'InChI', 'type': 'InChI'}, {'descriptor': 'GCUCIFQCGJIRNT-UHFFFAOYSA-N', 'program': 'InChI', 'type': 'InChIKey'}]}, 'rcsb_nonpolymer_entity_container_identifiers': {'entry_id': '1AZ1'}}]}}}
対応するLigandは複数存在することもあるようだ。 今回はひとまずすべて取得し後に構造式から判断する。
以下のような関数を作成
- PDB IDを引数とする
- 特定のSMILESも絞れていないので取得後に、
pdbx_chem_comp_descriptor.type == 'SMILES_CANONICAL' & pdbx_chem_comp_descriptor.program == 'OpenEye OEToolkits'
となるデータのみ抽出する
import requests def entry_to_ligand(entry_id): ligand_list = [] query = ''' { entry(entry_id:"''' + entry_id + '''") { nonpolymer_entities { rcsb_nonpolymer_entity_container_identifiers { entry_id } nonpolymer_comp { chem_comp { id type } pdbx_chem_comp_descriptor { descriptor type program } } } } } ''' url = "https://data.rcsb.org/graphql?query=" + query response = requests.get(url) json_data = response.json() for i in json_data.get('data').get('entry').get('nonpolymer_entities'): entry_id = i.get('rcsb_nonpolymer_entity_container_identifiers') nonpolymer_comp = i.get('nonpolymer_comp') ligand_id = nonpolymer_comp.get('chem_comp').get('id') for data in nonpolymer_comp.get('pdbx_chem_comp_descriptor'): if (data.get('type') == "SMILES_CANONICAL") and (data.get('program') == "OpenEye OEToolkits"): smiles = [data.get('descriptor')] type = [data.get('type')] program = [data.get('program')] d = {'entry':entry_id, 'ligand_id':ligand_id, 'smiles':smiles, 'type': type, 'program': program} ligand_list.append(d) return ligand_list entry_to_ligand("1AZ1") # [{'entry': {'entry_id': '1AZ1'}, # 'ligand_id': 'NAP', # 'program': ['OpenEye OEToolkits'], # 'smiles': ['c1cc(c[n+](c1)[C@H]2[C@@H]([C@@H]([C@H](O2)CO[P@@](=O)([O-])O[P@](=O)(O)OC[C@@H]3[C@H]([C@H]([C@@H](O3)n4cnc5c4ncnc5N)OP(=O)(O)O)O)O)O)C(=O)N'], # 'type': ['SMILES_CANONICAL']}, # {'entry': {'entry_id': '1AZ1'}, # 'ligand_id': 'ALR', # 'program': ['OpenEye OEToolkits'], # 'smiles': ['c1cc2cccc3c2c(c1)C(=O)N(C3=O)CC(=O)O'], # 'type': ['SMILES_CANONICAL']} # ]
DataFrameで扱いたいなら適宜pandasで
pd.DataFrame(entry_to_ligand("1AZ1")) # entry_id ligand_id smiles type program # 1AZ1 NAP [c1cc(c[n+](c1)[C@H]2[C@@H]([C@@H]([C@H](O2)CO... [SMILES_CANONICAL] [OpenEye OEToolkits] # 1AZ1 ALR [c1cc2cccc3c2c(c1)C(=O)N(C3=O)CC(=O)O] [SMILES_CANONICAL] [OpenEye OEToolkits]
あとは適宜、ProteinとLigand PDB IDを適宜流し込んであげれば欲しいデータは取得できた。(ただし、NAP, CL, URE, PO4などのようなLigandを弾きたければ適宜弾く必要はある。)
df = pd.DataFrame() for entry_id in entry_ids: tmp = pd.DataFrame(entry_to_ligand(entry_id)) df = pd.concat([df, tmp]) df.reset_index().drop('index', axis=1) # entry_id ligand_id smiles type program # 1AZ1 NAP [c1cc(c[n+](c1)[C@H]2[C@@H]([C@@H]([C@H](O2)CO... [SMILES_CANONICAL] [OpenEye OEToolkits] # 1AZ1 ALR [c1cc2cccc3c2c(c1)C(=O)N(C3=O)CC(=O)O] [SMILES_CANONICAL] [OpenEye OEToolkits] # 1DDR CL [[Cl-]] [SMILES_CANONICAL] [OpenEye OEToolkits] # 1DDR MTX [CN(Cc1cnc2c(n1)c(nc(n2)N)N)c3ccc(cc3)C(=O)N[C... [SMILES_CANONICAL] [OpenEye OEToolkits] # 1DDR URE [C(=O)(N)N] [SMILES_CANONICAL] [OpenEye OEToolkits] # ... ... ... ... ... # 2BGD CL [[Cl-]] [SMILES_CANONICAL] [OpenEye OEToolkits] # 2BGD PO4 [[O-]P(=O)([O-])[O-]] [SMILES_CANONICAL] [OpenEye OEToolkits] # 2BGD NA [[Na+]] [SMILES_CANONICAL] [OpenEye OEToolkits] # 7STD CA [[Ca+2]] [SMILES_CANONICAL] [OpenEye OEToolkits] # 7STD CRP [CC[C@]1([C@H](C1(Cl)Cl)C)C(=O)N[C@H](C)c2ccc(... [SMILES_CANONICAL] [OpenEye OEToolkits]
終わりに
正直、Protein:Ligandの組が1:1対応と思っていたのだが、いわゆる低分子だけでなく、NAPとかイオンとかその他諸々も含むので複数もあり得るのかと思った。
仕方がないので一旦すべて取得して、不要なものはルールで除去し、差雌雄的にRDKitで構造式に直して、目視で論文に書かれている構造式と照らし合わせて、目的のLigandのSMILESを得ることができた。
GraphQLでの取得はDBの構造というかKeyの単語がスキーマをみてもよくわからなかったりするのでちょっと困る。ちょっとずつおぼえていくしかないか