ご注文はリード化合物ですか?〜医薬化学録にわ〜

自分の勉強や備忘録などを兼ねて好き勝手なことを書いていくブログです。

小ネタ:Mordred で算出した記述子の欠損値処理

ケモインフォマティクスにおける Mordred は、化学構造から簡便に記述子を算出してくれる便利なライブラリです。

aimedchem.hatenablog.com

しかし、どんな分子でも記述子を計算してくれる訳ではなく、時々計算が出来ずにエラーを吐き出します。
エラーの種類は色々あるようですが、化学グラフの処理なんかでエラーが出るようです(メタンはエラーとなる記述子が多い)。
例えば mordred.error.Missing at 0xa1f90a510 といった具合です。
厄介なのが、エラーはそれ自体がオブジェクトとして dataframe のセルに入力されるため、 pandas 上では欠損値とみなされません(例えば dataframe.isnull() で検出できない)。そこで、コード上の処理に工夫が必要となります。

一番簡単なのは、数字以外が書かれている記述子を除いてしまうことでしょう。Pandas には select_dtype というコマンドがあり、指定した型のデータだけを抽出してくれるのですが、引数に "number" と指定することで、 int 型と float 型、つまり数字だけを選択してくれます。

import numpy as np
import pandas as pd
from rdkit import Chem
from rdkit.Chem.Draw import IPythonConsole

from mordred import Calculator, descriptors

# 記述子計算用インスタンスを用意
calc = Calculator(descriptors, ignore_3D = True)

# 計算したい化合物データを用意(mol 形式のリストを作る)
mols = [Chem.MolFromSmiles(mol) for mol in ["C", "c1ccccc1", "CCI"]]

# Pandas のデータフレームとして出力
df = calc.pandas(mols)

# 数字以外が記載されている列を落とす
df.select_dtypes("number")

select_dtype は、通常の解析においても力を発揮してくれるので、覚えておいて損はないと思います。

また、mordred は上記の設定だと、1613 記述子を計算してくれるのですが、そもそも特定の記述子だけが欲しいという場面もあります。既にモデルがある場合は、そのモデルで使っている記述子だけを計算すれば充分です。
その方法は、こちらの qiita の記事に纏まっています。

qiita.com

Qiita のコードとほぼ同じですが、下に例を記載します。

# 計算したい記述子名をリスト形式で用意
my_desc_names = ["ABC", "MW"]
my_descs = []

# 一度計算するインスタンスを作成し、実際に計算するオブジェクトを一つ一つ取り出して確認する
calc_dummy = Calculator(descriptors, ignore_3D=False)
for i, desc in enumerate(calc_dummy.descriptors):
    if desc.__str__()  in my_desc_names:
        my_descs.append(desc)
        
# 後は通常通りに計算
calc = Calculator(my_descs, ignore_3D = True)
mols = [Chem.MolFromSmiles(mol) for mol in ["C", "c1ccccc1", "CCI"]]
df = calc.pandas(mols)

では、選択した記述子を使いたいにも関わらず、欠損値が含まれている場合はどうすれば良いでしょうか?
良い解決策が思いつかなかったので、下のように力押しをしています。

# 取り敢えず numpy 形式にする
A = np.array(df)

# 力押しで全てのセルをチェック
for row_idx in list(range(A.shape[0])):
    for column_idx in list(range(A.shape[1])):
        
        # 数字でなければ float にできないので、nan になる
        if np.isnan(np.float(A[row_idx, column_idx])):
            
            # 欠損値を取り敢えず 0 で埋める
            A[row_idx, column_idx] = 0

あるいは、欠損値が含まれる記述子だけを選択してチェックをしていっても良いかもしれません。

# 欠損値が含まれている記述子を選択
lack_descriptors = [i for i in df.columns if i not in df.select_dtypes("number").columns]

# 力押しで上で選ばれた記述子のセルを全てチェック
for descriptor_name in lack_descriptors:
    for row_idx in list(range(df.shape[0])):
        
        # 欠損値を取り敢えず 0 で埋める
        df[descriptor_name][row_idx] = 0

無理に欠損値を使いたい場合は、ある程度限られてくると思うので、多少力押しになっても、実用上何とか耐えられる場合もあると思います。
いずれにしても、mordred そのもので何とかするというよりは、pandas などの力に頼った方が早いと思います。