6
7

More than 1 year has passed since last update.

RDKitのフィンガープリントをPandasのデータフレームに取り込んで使い倒そう

Last updated at Posted at 2021-11-05

はじめに

化学構造を用いた機械学習手法では、説明変数としてRDKitやmordred等で提供される記述子が良く用いられているが、これ以外にフィンガープリントも用いられる。

フィンガープリントについて詳しくは参考文献を参照してほしい。

フィンガープリントには多くの種類があり、RDKitやmordredの記述子と組み合わせることで予測モデルの精度向上が期待できる。また、手法によってはフィンガープリントのビットから部分構造が特定できるものがあるため、どの部分構造が予測に寄与しているのかを知ることも可能となる。

フィンガープリントを用いた化合物の類似度の計算については記事が多くあるが、計算結果を機械学習の説明変数に利用する事例は少ない(気がする)。

本記事ではRDkitで用意されている様々な種類のフィンガープリントについて、主に機械学習の説明変数として用いるためにPandasデータフレームに取り込む方法について解説する。

環境

  • RDKit 2020.09.3

前準備

本記事のソースを動かすにあたり、以下のモジュールをインポートしておく必要がある。

import numpy as np
import pandas as pd
from rdkit import rdBase, Chem
from rdkit import DataStructs
from rdkit.DataStructs import ExplicitBitVect
from rdkit.Chem import AllChem, Descriptors
from rdkit.ML.Descriptors import MoleculeDescriptors

フィンガープリントの説明

ここでは各フィンガープリントのビット数、ビットの種類、フィンガープリントを求めてpandasに取り込む方法について説明する。
なお、以下のコード例は全て前提としてmolsという変数にRDkitのMolオブジェクトのリストが格納されていることを前提としている。
また、Pandasのデータフレームのindexは指定していないので、化合物名のリスト等、適宜補ってほしい。

MACCS Keys Fingerprint

  • ビット数 167ビット
  • ビットの種類 0, 1

Pandasに取り込む方法

こんな感じ。

maccs_fps = []

for mol in mols:
    fp = AllChem.GetMACCSKeysFingerprint(mol) 
    maccs_fps.append(np.array(fp, int))

df = pd.DataFrame(data=maccs_fps)

Morgan Fingerprint

  • ビット数 不定または指定可能
  • ビットの種類 0, 1

Pandasに取り込む方法

ビット数を増やすとハッシュ化時の衝突を減らすことができる。
GetMorganFingerprint関数を用いるとビット数の衝突は生じないが、ビット数が不定となるためPandasでの取り扱いが面倒になる。
ここでは、GetMorganFingerprintAsBitVect関数を使ってビット数2048を指定した例を示す。

morgan_fps = []
for mol in mols:
    fp = AllChem.GetMorganFingerprintAsBitVect(mol, 2, 2048)
    print(np.array(fp, int))
    morgan_fps.append(np.array(fp, int))

df = pd.DataFrame(data=morgan_fps)

RDKit Fingerprint

  • ビット数 2048
  • ビットの種類 0, 1

Pandasに取り込む方法

rdkit_fps = []
for mol in mols:
    fp = AllChem.RDKFingerprint(mol)
    rdkit_fps.append(np.array(list(fp.ToBitString()), int))

df = pd.DataFrame(data=rdkit_fps)  

TopologicalTorsion Fingerprint

  • ビット数 不定
  • ビットの種類 int

Pandasに取り込む方法

ビット数は不定となっており、フィンガープリントの計算を行うまでどんなビットがでてくるか分からないという特徴がある。化合物によって得られるビットも異なってくる。
従って計算しながらビット名をキーとして保持し、後から全化合物に対し存在しないビットの値を0で補完する処理が必要となる。
取り扱いが面倒なフィンガープリントである。
以下はPandasに取り込むコードである。

from collections import defaultdict
topo_fps_tmp = []
keys = set()
for mol in mols:
    fp = AllChem.GetTopologicalTorsionFingerprint(mol)
    topo_fps_tmp.append(fp.GetNonzeroElements())
    for key in fp.GetNonzeroElements():
        keys.add(key)

topo_fps = defaultdict(list)
for fp in topo_fps_tmp:
    for key in keys:
        if key in fp:
            topo_fps[key].append(fp[key])
        else:
            topo_fps[key].append(0)

df = pd.DataFrame(data=topo_fps)

AtomPair Fingerprint(ハッシュ版)

  • ビット数 2048
  • ビットの種類 int(0/1?)

Pandasに取り込む方法

今回GetHashedAtomPairFingerprintAsBitVectというハッシュ版を使ったが、GetAtomPairFingerprint関数を用いれば、ビット数不定のものが得られると思われる。ただその場合、Morgan Fingerprintで述べたように取り扱いが面倒となる。

from collections import defaultdict
atp_fps = []

for mol in mols:
    fp = AllChem.GetHashedAtomPairFingerprintAsBitVect(mol)
    atp_fps.append(np.array(fp, int))

#print(atp_fps)    
df = pd.DataFrame(data=atp_fps)

Avalon Fingerprint

  • ビット数 512
  • ビットの種類 0/1

Pandasに取り込む方法

from rdkit.Avalon.pyAvalonTools import GetAvalonCountFP, GetAvalonFP

avalon_fps = []

for mol in mols:
    fp =GetAvalonFP(mol)
    avalon_fps.append(np.array(fp, int))

df = pd.DataFrame(data=avalon_fps)

AvalonCount Fingerprint

  • ビット数 不定
  • ビットの種類 int

Pandasに取り込む方法

存在するビットを数え上げるタイプのフィンガープリントであるためビット数が不定となり、Pandasに取り込むには TopologicalTorsion Fingerprintと同様の処理が必要となる。

from rdkit.Avalon.pyAvalonTools import GetAvalonCountFP, GetAvalonFP

from collections import defaultdict
avalon_fps_tmp = []
keys = set()
for mol in mols:
    fp = GetAvalonCountFP(mol)
    avalon_fps_tmp.append(fp.GetNonzeroElements())
    for key in fp.GetNonzeroElements():
        keys.add(key)

avalon_fps = defaultdict(list)
for fp in avalon_fps_tmp:
    for key in keys:
        if key in fp:
            avalon_fps[key].append(fp[key])
        else:
            avalon_fps[key].append(0)

df = pd.DataFrame(data=avalon_fps)

ErG Fingerprint

  • ビット数 315
  • ビットの種類 float型

細かい計算原理はわからないがビットが0/1ではなく、3.0等のfloat型になることが特徴である。

Pandasに取り込む方法

erg_fps = []
for mol in mols:
    fp = AllChem.GetErGFingerprint(mol)
    erg_fps.append(fp)

df = pd.DataFrame(data=erg_fps)    

Pandasに取り込んだデータでTanimoto係数を計算する方法

RDKkitのDataStructs.FingerprintSimilarityでフィンガープリントを計算する場合、ExplicitBitVect型の変数にしておく必要がある。
pandasに取り込んだ化合物同士の比較を行う際に、ExplicitBitVect型に変換してRDKitのTanimoto係数を計算する関数を以下に掲載する

def Tanimoto(fp1, fp2, metric=DataStructs.TanimotoSimilarity):

    ebv1 =ExplicitBitVect(len(fp1))
    ebv2 =ExplicitBitVect(len(fp2))

    for i, bit in enumerate(fp1):
        if bit > 0:
            ebv1.SetBit(i)

    for i, bit in enumerate(fp2):
        if bit > 0:
            ebv2.SetBit(i)            

    result = DataStructs.FingerprintSimilarity(ebv1, ebv2, metric=metric)

    return result

使い方はこんな感じだ。

Tanimoto(df.iloc[0, :].values, df.iloc[1, :].values)

データフレームに格納された0行目と1行目の化合物のTanimoto係数を計算している。
metric引数を変えることでTanimoto係数以外の類似度も計算できる。詳しくはRDKitのマニュアルを見てほしい。

おわりに

一旦Pandasに変換してしまえば、記述子だろうがフィンガープリントだろうが同じ扱いができるので、バシバシこれでモデルを作ってみよう。
なお、説明変数が大量にでてくるので、モデルを作成する前の段階での特徴選択が重要になるだろう。

参考

6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7