モダンなfingerprintの計算方法
目次
これまで馴染み深かったrdkitにおけるfingerprintの計算方法が新しくなったらしいのでメモ。
Sponsored by Google AdSense
計算方法
モジュールのインポート
import numpy as np
import rdkit
from rdkit import Chem, rdBase
from rdkit.Chem import rdFingerprintGenerator, Draw, rdMolDescriptors
from rdkit.DataStructs import ConvertToNumpyArray
rdkit.__version__
'2024.03.1'
化合物の準備
mols = tuple(
map(
Chem.MolFromSmiles,
(
"Cn1cnc2n(C)c(=O)n(C)c(=O)c12",
"CC(C)Cc1ccc(cc1)C(C)C(=O)O",
"CC(=O)Nc1ccc(O)cc1",
),
)
)
mols
(<rdkit.Chem.rdchem.Mol at 0x122b28430>,
<rdkit.Chem.rdchem.Mol at 0x122b28580>,
<rdkit.Chem.rdchem.Mol at 0x122b285f0>)
MorganFingerPrint
# 必要なパラメータ
RADIUS = 2
N_BITS = 1024
昔の方法
bit
_list_arr = []
for _mol in mols:
_arr = np.zeros(N_BITS, dtype=np.uint8)
_fp = rdMolDescriptors.GetMorganFingerprintAsBitVect(_mol, RADIUS, N_BITS)
ConvertToNumpyArray(_fp, _arr)
_list_arr.append(_fp)
fps_morgan_old = np.vstack(_list_arr)
[23:23:35] DEPRECATION WARNING: please use MorganGenerator
[23:23:35] DEPRECATION WARNING: please use MorganGenerator
[23:23:35] DEPRECATION WARNING: please use MorganGenerator
print(f"{fps_morgan_old.shape=}")
print(fps_morgan_old)
fps_morgan_old.shape=(3, 1024)
[[1 0 0 ... 0 0 0]
[0 1 0 ... 0 0 0]
[0 0 0 ... 0 0 0]]
countable
_list_arr = []
for _mol in mols:
_arr = np.zeros(N_BITS, dtype=np.uint32)
_fp = rdMolDescriptors.GetHashedMorganFingerprint(
_mol, radius=RADIUS, nBits=N_BITS
)
ConvertToNumpyArray(_fp, _arr)
_list_arr.append(_arr)
fps_countable_morgan_old = np.vstack(_list_arr)
[23:23:35] DEPRECATION WARNING: please use MorganGenerator
[23:23:35] DEPRECATION WARNING: please use MorganGenerator
[23:23:35] DEPRECATION WARNING: please use MorganGenerator
print(f"{fps_countable_morgan_old.shape=}")
print(fps_countable_morgan_old)
fps_countable_morgan_old.shape=(3, 1024)
[[1 0 0 ... 0 0 0]
[0 2 0 ... 0 0 0]
[0 0 0 ... 0 0 0]]
ちなみに、warningを表示させないこともできる。
rdBase.DisableLog("rdApp.warning")
rdMolDescriptors.GetMorganFingerprintAsBitVect(
_mol, radius=RADIUS, nBits=N_BITS
)
rdBase.EnableLog("rdApp.warning")
rdkit用のlogging.logger
もあるが、そちらでは無効化できなかった。(C++の関数のため、Pythonのlogger
では非表示にできない?)
rdkit.logger
<Logger rdkit (WARNING)>
モダンな方法
mfgen = rdFingerprintGenerator.GetMorganGenerator(radius=RADIUS, fpSize=N_BITS)
mfgen
<rdkit.Chem.rdFingerprintGenerator.FingeprintGenerator64 at 0x122b29000>
bit
# 1個ずつ計算するとき
mfgen.GetFingerprintAsNumPy(mols[0])
array([1, 0, 0, ..., 0, 0, 0], dtype=uint8)
直接np.ndarray
で得られる。
# 複数個を一度に計算するとき
fps_morgan_new = np.array(mfgen.GetFingerprints(mols, numThreads=1))
print(f"{fps_morgan_new.shape=}")
fps_morgan_new
fps_morgan_new.shape=(3, 1024)
array([[1, 0, 0, ..., 0, 0, 0],
[0, 1, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0]])
ちゃんと新旧で値は一致する。
np.allclose(fps_morgan_old, fps_morgan_new)
True
countable
# 1個ずつ計算するとき
mfgen.GetCountFingerprintAsNumPy(mols[0])
array([1, 0, 0, ..., 0, 0, 0], dtype=uint32)
# 複数個を一度に計算するとき
_list_arr = list()
for _fp in mfgen.GetCountFingerprints(
mols,
numThreads=1,
):
_arr = np.zeros(N_BITS, dtype=np.uint32)
ConvertToNumpyArray(_fp, _arr)
_list_arr.append(_arr)
fps_countable_morgan_new = np.vstack(_list_arr)
print(f"{fps_countable_morgan_new.shape=}")
fps_countable_morgan_new.shape=(3, 1024)
ちゃんと新旧で値は一致する。
np.allclose(fps_countable_morgan_old, fps_countable_morgan_new)
True
‘bitInfo’を計算する
countableであろうとなかろうとやり方は同じ。
additional_output = rdFingerprintGenerator.AdditionalOutput()
additional_output.CollectBitInfoMap()
# もしくは`additional_output.AllocateBitInfoMap()`
mfgen.GetCountFingerprintAsNumPy(mols[0], additionalOutput=additional_output)
array([1, 0, 0, ..., 0, 0, 0], dtype=uint32)
additional_output.GetBitInfoMap()
{0: ((5, 2),),
33: ((0, 0), (6, 0), (10, 0), (4, 2)),
121: ((0, 1), (6, 1), (10, 1)),
179: ((3, 2),),
234: ((9, 2),),
283: ((11, 2),),
314: ((8, 1), (12, 1)),
330: ((13, 2),),
356: ((4, 0), (7, 0), (11, 0), (13, 0)),
378: ((3, 0),),
385: ((1, 1),),
400: ((7, 2),),
416: ((13, 1),),
428: ((3, 1),),
463: ((1, 2),),
493: ((2, 2),),
504: ((11, 1),),
564: ((5, 1), (9, 1)),
650: ((8, 0), (12, 0)),
672: ((4, 1),),
771: ((7, 1),),
849: ((2, 0),),
932: ((2, 1),),
935: ((1, 0), (5, 0), (9, 0))}
複数個計算するときはたとえば以下の通り。
list_bitinfo: list[dict[int, tuple[tuple[int, int], ...]]] = list()
list_fps: list[np.ndarray] = list()
for _mol in mols:
_fp = mfgen.GetCountFingerprintAsNumPy(
_mol, additionalOutput=additional_output
)
list_fps.append(_fp)
list_bitinfo.append(additional_output.GetBitInfoMap())
fps = np.vstack(list_fps)
# ちゃんと違うものになっている
list_bitinfo[0] == list_bitinfo[1]
False
bitの可視化
Draw.DrawMorganBit(
mols[2],
bitId=tuple(additional_output.GetBitInfoMap().keys())[0],
bitInfo=additional_output.GetBitInfoMap(),
legend=f"bit: {tuple(additional_output.GetBitInfoMap().keys())[0]}",
)
Draw.DrawMorganBits(
tuple(
(mols[2], bit_id, additional_output.GetBitInfoMap())
for bit_id in additional_output.GetBitInfoMap()
),
legends=tuple(
f"bit: {bit_id}" for bit_id in additional_output.GetBitInfoMap()
),
)
コメント
今回はMorganFingerprintしか試してないが、他のFingerprintも大体似た感じ。