SMILESから分子記述子とフィンガープリントを算出して、データフレームに格納する【Python, RDKit】

2020年1月8日

RDKitで化合物データセットのSMILESから、分子記述子(descriptor)およびフィンガープリント(fingerprint)を含むデータフレームを作成する方法です。QSAR/機械学習モデルを自作しようにも、分子記述子やフィンガープリントの作成で結構つまづいたので以下にまとめます。

データフレームに格納する意義

機械学習に入れるためだけならリスト型で作成してもいいですが、データフレームにすることで以下のことがしやすくなります。

  1. 作成した記述子/フィンガープリントをベースした化合物データセットの俯瞰
  2. 欠損値対応や次元削減等のデータ前処理

また、RDKitでは記述子を算出するためにSMILESを一旦molオブジェクトに変換しますが、その際うまく変換できなかったものがあってもデータフレームのほうが対応しやすいです。

実践してみる

下準備

サンプルデータには、MoleculeNetのBBBP( blood-brain barrier penetration:化合物の血液脳関門透過性データセット)のSMIELSを使用してみます。

RDKit molオブジェクトはROMolという列に格納されるので、これをベースに記述子を作成します。

参考:化合物のデータセット一覧

import numpy as np
import pandas as pd
 
from rdkit import rdBase, Chem
from rdkit.Chem import AllChem, PandasTools, Descriptors
from rdkit.Chem.Draw import IPythonConsole
 
print('rdkit version: ',rdBase.rdkitVersion)  # rdkit version:  2019.03.4
 
# 下準備
# データセットの読み込み
df = pd.read_csv("BBBP.csv")
 
# dfのSMILES列を参照してMolオブジェクト列をデータフレームに加える
PandasTools.AddMoleculeColumnToFrame(df,'smiles')
 
# Molオブジェクトが作成できたか確認
print(df.shape)
print(df.isnull().sum())  
(2050, 4)
 
num        0
name       0
p_np       0
smiles     0
ROMol     11
dtype: int64

エラーとして「Explicit valence for atom # 1 N, 4, is greater than permitted」が出ますが、これは異常な原子価を持つ分子(イオンなど)があったのが原因です(「Nに4の原子価は許容値を超えている」と言っています)。そのような分子のROMolにはNoneが返されてしまい、ここではそのようなSMILESが11個ありました。

一つ一つ対応しても良いですが、数が少なければとりあえず除いてしまうのが手っ取り早いです。そこでisnull().sum()でROMol列における欠損値の有無を調べ、あればその行を取り除いておきます。

参考:化合物データの読み込みのトラブルシュート

# ROMolが作成できなかったものを確認
print(df[df.ROMol.isnull()])
# 欠損行の除去
df = df.dropna() 
SMILESから分子記述子とフィンガープリントを算出して、データフレームに格納する

「WARNING: not removing hydrogen atom without neighbors」と出る場合は、塩などがデータに含まれているためと思われます。RDKitではデフォルトでHが除かれた状態で保存されるため、隣と結合していないH (塩など)があるとそのようなHは除去できず、警告してくれてます。

 

分子記述子の作成

データフレーム中の各行にある対象へある関数を適用させるにはMap関数が便利です。

RDKitの「Descriptors.descList」には記述子の名称と関数(function)がリスト化されているため、少々時間がかかりますがfor関数とmap関数で一括計算して、データフレームに返せました。

for i,j in Descriptors.descList:
    df[i] = df.ROMol.map(j)
 
df.shape
# (2039, 205)
df.head()
分子記述子のdataframe作成

201列分の記述子が追加されました。

得られた変数をscikit-learnやディープラーニングフレームワークにかける場合、「ValueError: Input contains NaN, infinity or a value too large for dtype('float64’)」というエラーがでるかもしれませんが、その場合は以下のようにしたら大丈夫でした。

for i,j in Descriptors.descList:
    df[i] = df['ROMol'].map(j)
df['Ipc'] = [Descriptors.Ipc(mol, avg=True) for mol in df['ROMol']]  

記述子の一部「IPC」の値に無限のように大きいものが作成されてしまうのが原因のようです。

参考:#12 RDKit 2D descriptor である IPC の値が非常に大きい場合の対処法
参考:分子記述子一覧

フィンガープリントの作成

apply関数を使うと高速に算出できますが、フィンガープリントのリストが1列に格納されるようです。

フィンガープリントはExplicitBitVect objectという形式で保存されているので、各値を1列づつに格納する場合は少し手間がいりました。

# 下準備
df = pd.read_csv("BBBP.csv")
PandasTools.AddMoleculeColumnToFrame(df,'smiles') 
df = df.dropna()
 
# 1列にfingerprintのリストを追加する場合
df['FP'] = df.apply(lambda x: AllChem.GetMorganFingerprintAsBitVect(x.ROMol, 2, 1024), axis=1)
# fingerprintの各値を各列に格納する場合
# 個別に01をデータフレームに格納する
FP = [AllChem.GetMorganFingerprintAsBitVect(mol, 2, 1024) for mol in df.ROMol]
df_FP = pd.DataFrame(np.array(FP)) 
# フィンガープリントをもとのデータフレームに結合
df_FP.index = df.index
df = pd.concat([df, df_FP], axis=1)
1列にfingerprintのリストを追加した場合
fingerprintの各値を各列に格納する場合