RDKitのPAINSフィルターで化合物をスクリーニング

化学

化合物ライブラリー中の大量の化合物から,どれをスクリーニングにかけるかはケモインフォマティクスにおける重要な課題です.本サイトでもこれまで

といった話題について扱ってきました.通常,ハイスループットスクリーニング(HTS)では決まった濃度で次々とアッセイをしていき,その後ヒット化合物について活性の濃度依存性などを見ていくことになります.

スクリーニングにあたっては,酸ハライドのような明らかに反応性の高い官能基を有する化合物は事前に除いておきます.それでも色々な標的に対するスクリーニングにおいて一貫して「ヒット」に含まれている構造が存在することが知られてきました.そのような化合物は,我々が期待するような作用機序とは異なる機構で「活性」を示すことが多く,事前に除いておくことが偽陽性を減らすことに繋がります.

今回取り上げるPAINSフィルターはそのような部分構造をリスト化したもので,賛否両論ありながら忌避構造リストとしては有名なものになります.

PAINSフィルターの元文献は以下になります.興味のある方は参照してみてください.「New Substructure Filters for Removal of Pan Assay Interference Compounds (PAINS) from Screening Libraries and for Their Exclusion in BioassaysJ. Med. Chem. 2010, 53, 2719–2740. (DOI:10.1021/jm901137j)

PAINS化合物とは

2010年のJMCでは,PerkinElmer社のAlphaScreenを用いた6つのHTSにおけるヒット化合物の数を調べています.

ヒットカウント 6 5 4 3 2 1 0 合計
化合物数 362 785 915 1220 4689 12077 73164 93212

<出典:JMC 2010, 53, 2719>

上の表のように,例えば全てのアッセイで「ヒット」と判定された化合物が362ありました.著者らはこういった化合物に存在する問題となりそうな「部分構造」を,ある基準でリスト化することとしました.このような化合物(Pan-Assay INterference compoundS)から得られた480個のリストがPAINSフィルターになります.

偽陽性化合物であることを見抜けずに,その構造を最適化してしまった場合に費やされるお金と時間は文字通り「無駄」になりますので,このようなリストの登場は歓迎されました.

またこういった偽陽性化合物が活性化合物として論文に報告され,コントロール試薬として市販されてしまうことは,今後の科学の発展にも大きな障害となります.アメリカ化学会では「The Ecstasy and Agony of Assay Interference Compounds」と題した2017年の論説で,論文投稿にあたっては

  • PAINSフィルタリングを行うこと
  • PAINS化合物については,追加実験によって化合物の真の活性を担保すること

を強く推奨しています.以前からPAINSフィルターに関しては議論がありましたが,この論説以降さらに活発になりました.

PAINSフィルターに関する議論

PAINSフィルターに関する問題点としては,

  • AlphaScreenアッセイのみを用いていること
  • たった6回のタンパク質間相互作用に対するアッセイから得られていること
  • 商用化合物データを使っていること
  • 7割近い構造はライブラリー中でごく少数回登場しただけであること

などが挙げられています.

より大規模でオープンなデータベースであるPubChemを用いてPAINSフィルターを検証したところ,フィルタリングされた化合物の多くはAlphaScreenを用いたPPIアッセイでは頻繁にヒットとはなっていないことなどが示されています.

またEli Lillyの研究者らがLillyの社内ライブラリーにてPAINSフィルターを検証したところ,フィルター構造の多くは化合物の不安定性を現しているに過ぎないと示されました.

これ以上詳細に立ち入るのは本サイトの趣旨とは外れますので,興味のある方は以下の文献や記事を参照してみてください(全て英語です).

RDKitにおけるPAINSフィルターの実装

元文献では線形表記法の1つであるSLN形式でリストが提示されていましたので,RDKitではそれをより一般的なSMARTS形式に翻訳した上で実装されています.

開発初期段階ではRDKitにおける明示的な水素原子の取り扱いにやや問題があったために,本来よりもヒットする化合物数が少なくなるといったこともあったようですが,現在では改善されています.

このあたりの話はRDKitブログの以下の記事に詳しく書かれています.興味のある方は参照してみてください(英語です).
Changes in the 2016.03 Release: the FilterCatalog
Curating the PAINS filters
RDKit learns how to filter PAINS/BRENK/ZINC/NIH via FilterCatalog #536
SMARTS記法などの線形表記法については「SMILES記法は化学構造の線形表記法」という記事で解説しています.参照してみてください.

フィルターカタログ

RDKitではPAINSフィルターはChem.rdfiltercatalogモジュールの一部として実装されています.PAINSの他にもZINCフィルターなどが実装されています.フィルターの種類は下記のコードのようにFilterCatalogs.namesでアクセス可能です.

from rdkit.Chem import FilterCatalog
FilterCatalog.FilterCatalogParams.FilterCatalogs.names
{'PAINS_A': rdkit.Chem.rdfiltercatalog.FilterCatalogs.PAINS_A,
 'PAINS_B': rdkit.Chem.rdfiltercatalog.FilterCatalogs.PAINS_B,
 'PAINS_C': rdkit.Chem.rdfiltercatalog.FilterCatalogs.PAINS_C,
 'PAINS': rdkit.Chem.rdfiltercatalog.FilterCatalogs.PAINS,
 'BRENK': rdkit.Chem.rdfiltercatalog.FilterCatalogs.BRENK,
 'NIH': rdkit.Chem.rdfiltercatalog.FilterCatalogs.NIH,
 'ZINC': rdkit.Chem.rdfiltercatalog.FilterCatalogs.ZINC,
 'ALL': rdkit.Chem.rdfiltercatalog.FilterCatalogs.ALL}

具体的にどのような構造がフィルタリング対象かを眺めるにはZINC15データベースのパターンを眺めるとよいと思います.下の画像ではPAINSフィルターの一部を取り出したものです.

ZINC Pains

フィルターカタログの具体的な使い方

フィルタリングの実行

Chem.rdfiltercatalog.FilterCatalog.HasMatch(mol)
Chem.rdfiltercatalog.FilterCatalog.GetMatches(mol)

具体的な使い方としては,まずChem.rdfiltercatalog.FilterCatalogParamsオブジェクトに先ほど見たフィルタリング要素を追加していき,望みのフィルターを作成します.コードとしては以下のようになります.

param = FilterCatalog.FilterCatalogParams()
param.AddCatalog(FilterCatalog.FilterCatalogParams.FilterCatalogs.PAINS)
filt = FilterCatalog.FilterCatalog(param)

続いて作成したフィルターに化合物を通していきます.HasMatchメソッドは該当構造の有無を,GetMatchesメソッドは該当構造全てをリストで返します.

それでは具体的に使い方を見ていきたいと思います.化合物としてはZINC15データベースから取得したある程度活性の高い化合物リストを用い,pandasのデータフレームとして読み込みます.

RDKitとpandasの併用方法については,「RDKitのPandasToolsでデータ分析を加速する」という記事で解説しています.参照してみてください.
from rdkit import rdBase, Chem
from rdkit.Chem import AllChem, Draw, PandasTools
import pandas as pd
print(rdBase.rdkitVersion) # 2018.09.1

df = PandasTools.LoadSDF('./SDF/1uM.sdf.mol')
df.affinity = df.affinity.astype(float)
len(df) # 28372

df['PAINS'] = df.ROMol.map(filt.HasMatch)
df.head(2)

ここまでのデータフレームは以下のようになっています.

PAINS df

少し分布と統計量を見てみましょう.

import seaborn as sns
sns.violinplot(x='PAINS', y='affinity', data=df)
df.affinity.describe().round(2)
df[ df.PAINS == False].affinity.describe().round(2)
df[ df.PAINS == True].affinity.describe().round(2)

PAINS Affinity

count    28372.00
mean         7.57
std          1.03
min          6.00
25%          6.71
50%          7.48
75%          8.30
max         11.00
Name: affinity, dtype: float64

count    26995.00
mean         7.58
std          1.02
min          6.00
25%          6.72
50%          7.49
75%          8.30
max         11.00
Name: affinity, dtype: float64

count    1377.00
mean        7.49
std         1.08
min         6.00
25%         6.55
50%         7.31
75%         8.19
max        10.68
Name: affinity, dtype: float64

特に大きな違いはなさそうです.続いて具体的にどのような構造がヒットしているかを見ていきます.

フィルターに一致した構造の取り扱い

FilterCatalogEntry.GetFilterMatches(mol)

GetMatchesメソッドは該当構造をChem.rdfiltercatalog.FilterCatalogEntryオブジェクトのリストで返します.

FilterCatalogEntryオブジェクトは

  • description
  • Reference
  • Scope

というプロパティを有していて,どういった構造がヒットしたのかを知ることが可能です.

具体的なヒット構造のアトム番号を得るのはややトリッキーで,GetFilterMatchesメソッドを使うことになります.下のコードでは

  1. ヒット構造に関する説明を取得する関数を定義(descriptionプロパティ)
  2. ヒット構造のアトム番号のリストを返す関数を定義
  3. 定義した関数をデータフレームにmap関数で適用
  4. ランダムに選んだ6つの化合物に対して,ヒット構造をハイライトした状態で構造描画

という手順で進めています.またZINCデータベースでは分子は立体座標を持っていますので,2次元で見やすくなるように手を加えています.

def get_description(mol):
    descriptions = []
    if filt.HasMatch(mol):
        catalog_entry_list = filt.GetMatches(mol)
        for entry in catalog_entry_list:
            descriptions.append(entry.GetProp('description'))
    return descriptions

def get_matched_atoms(mol):
    atom_pairs = []
    if filt.HasMatch(mol):
        catalog_entry_list = filt.GetMatches(mol)
        for entry in catalog_entry_list:
            pairs = [x[1] for x in entry.GetFilterMatches(mol)[0].atomPairs]
            atom_pairs.append(pairs)
    return atom_pairs

df['PAINS_desc'] = df.ROMol.map(get_description)
df['smiles'] = df.ROMol.map(Chem.MolToSmiles)
df['2D_mol'] = df.smiles.map(Chem.MolFromSmiles)
df['matched_atoms'] = df['2D_mol'].map(get_matched_atoms)

import numpy as np
random_list = list(np.random.choice(df[df.PAINS==True].index, size=6, replace=False))
Draw.MolsToGridImage(df['2D_mol'][random_list], subImgSize=(350,250),
                         highlightAtomLists=[x[0] for x in df['matched_atoms'][random_list]],
                    legends=[x[0] for x in df['PAINS_desc'][random_list]])

PAINS Sub

終わりに

今回は「RDKitのPAINSフィルターで化合物をスクリーニング」という話題について,

  • PAINSフィルターとはなにか
  • PAINSフィルターに関する議論
  • RDKitにおけるフィルターカタログの実装

という内容を扱ってきました.PAINSフィルター自体は,必ずしもアッセイ系を阻害するような高頻度ヒット化合物のリストではないかもしれませんが,化合物の安定性などを考慮する際に忌避構造リストとして1つの指標になると思います.

コメント

タイトルとURLをコピーしました