共結晶構造情報のリガンドが市販かどうかを調べるには

SBDDに限らず、創薬にあたってポジコンやツール化合物があるかどうかは評価系構築の確度に関わる部分です。
ここをしっかりしてもらうことが、我々ケミストの成功の第一歩になります。

若い子「おじさん、すみません。共結晶が報告されてるリガンドを調べてもらうことってできますか?」 おじさん「いいよ。」

数分後...

おじさん「結構な数があるね。」
若い子「これって買うか作るか出来ますか?」
おじさん「うーん調べるならちょっと時間ください」

よく知られてる標的だと結構な数の共結晶が報告されてますよね。
調べるだけで半日(~1日)かかっちゃいそうです。
そんなわけでpythonスクリプトを書きました (>3日かかった)。

Contents

環境構築

miniconda がインストールされているものとします。ちなみにMacでもWinでも動作確認済みです。
今回のスクリプトは下記の仮想環境で動作確認済みです。

conda create -n bi -c conda-forge -y python=3.10 pymol-open-source=2.5 BioPython=1.81 pandas=2.0.3 requests=2.31 pypdb=2.3 biopandas=0.4.1 rdkit=2022.09.1

以降のスクリプトconda activate biで仮想環境をactivateする必要があります。
すでにinstallされてたら問題ないです。今回は使わないものも入っています。

標的のPDBリストを作る

色んな方法が報告されています。例えば, 下記のスクリプト実行してキーワード検索の結果をリストにすることができます。

from pypdb import *
q = input('keywords? ')
pdb_ids = Query(q).search()
print('the number of PDB IDs are ' + str(len(pdb_ids)))

jupyterで実行するとinputの入力を求められます。 今回は有名どころでCRBNと入力したところ、2023.9.4現在で81個のPDBがヒットしました。
他にも方法はあるので気が向いた時にでも公開します。

PDBに含まれるリガンドをリストアップして、PubChemのSupplierリンクをつける

今回は@くろたんくさんの記事を参考にさせてもらいました。
私の拙いcodingでは例外処理がめんどうで目的を達成できませんでした。感謝です。
早速ズバリ結論を書くと、上述のPDBリストを作った状態で下記を実行します。

import requests
import pubchempy as pcp
import pandas as pd

# Ligandの有無を判定してdfを作成する関数

def PDB2L(entry_id):
    ligand_list = []

    query = '''
    {
    entry(entry_id:"'''  + entry_id + '''") {
      rcsb_id
    struct {
      title
    }
        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()

    # dfの作成
    # Ligandがある場合だけrun
    if json_data['data']['entry']['nonpolymer_entities'] != None:
        # LigandがUNLやUNKではない時はrun
        if json_data['data']['entry']['nonpolymer_entities'][0]['nonpolymer_comp']['pdbx_chem_comp_descriptor'] != None:
            # 複数のリガンドをdfに代入
            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') == "InChI") and (data.get('program') == "InChI"):
                        inchi = [data.get('descriptor')]
                        type = [data.get('type')]
                        program = [data.get('program')]
                        d = {'Ligand_ID':ligand_id, 'InChI':inchi}
                        ligand_list.append(d)
                    df = pd.DataFrame(ligand_list)
                    df['Title'] = json_data['data']['entry']['struct']['title']
        return(df)

# ここからfor文

# pdb_ids = ['5UT2', '4FVR'] # for test
cols = ['Ligand_ID', 'SMILES', 'InChI', 'CID', 'MW', 'PDB_ID', 'Title', 'Supplier', 'RCSB_URL', 'Ligand_URL']
Sup_List = pd.DataFrame(columns = cols)

for pdb_id in pdb_ids:

    # Ligandがない場合の例外処理
    if judge(pdb_id) != None:
        if UNL_UNK(pdb_id) != None:

            # DataFrameの作成
            df = pd.DataFrame(entry_to_ligand(pdb_id))

            df['PDB_ID'] = pdb_id
            df['CID'] =  df['InChI'].map(lambda x : pcp.get_properties('MolecularWeight', x, 'inchi')[0]['CID']).astype(str)
            df['MW'] =  df['InChI'].map(lambda x : pcp.get_properties('MolecularWeight', x, 'inchi')[0]['MolecularWeight']).astype(float)
            df['SMILES'] =  df['InChI'].map(lambda x : pcp.get_properties('IsomericSMILES', x, 'inchi')[0]['IsomericSMILES'])
            df['Title'] =  df['PDB_ID'].map(lambda x : title(x))

            # リンクの作成
            df['Supplier'] = df['CID'].map(lambda x : 'https://pubchem.ncbi.nlm.nih.gov/compound/' + x + '#section=Chemical-Vendors&fullscreen=true')
            df['RCSB_URL'] = df['PDB_ID'].map(lambda x : 'https://www.rcsb.org/structure/' + x)
            df['Ligand_URL'] = df['Ligand_ID'].map(lambda x : 'https://www.rcsb.org/ligand/' + x)
            # df['Title'] = json_data['data']['entry']['struct']['title']

            # df結合
            Sup_List = pd.concat([Sup_List, df])
        else:
            df = pd.DataFrame(columns=cols)
            empty = []
            empty.append(pdb_id)
            df['PDB_ID'] = empty
            df['RCSB_URL'].iloc[0] =  'https://www.rcsb.org/structure/' + pdb_id
        Sup_List = pd.concat([Sup_List, df])
    else:
        df = pd.DataFrame(columns=cols)
        empty = []
        empty.append(pdb_id)
        df['PDB_ID'] = empty
        df['RCSB_URL'].iloc[0] =  'https://www.rcsb.org/structure/' + pdb_id
        Sup_List = pd.concat([Sup_List, df])

Sup_List = Sup_List.reset_index(drop=True)

添加剤やイオンなんかも結果に含まれてしまうので、必要に応じてフィルタリングが必要です。
重複も除いてあげると親切ですね。

# MW>150
Sup_List_150 =  Sup_List[Sup_List['MW']>150].reset_index(drop=True)
#remove duplicates
Sup_List_Dup = Sup_List[Sup_List['MW']>150].drop_duplicates('Ligand_ID').reset_index(drop=True)

あーやっとスッキリしました。