Butinaモジュールによる類似化合物のクラスタリング

RDKitには化合物の類似度に基づいてクラスタリングを行うモジュールが用意されています。
その中の一つに[Butinaモジュール][20]と呼ばれるクラスタリングアルゴリズムがあり、
化合物間の距離行列を用意することで類似化合物の分類を行うことが可能です。

[20]:https://www.rdkit.org/docs/source/rdkit.ML.Cluster.Butina.html

今回はMorganフィンガープリントを基に化合物間のタニモト距離を計算し、
それをButinaモジュールで解析して化合物のクラスタリングを行う方法を紹介します。

###環境
- Windows 11
- python 3.8.8
- RDKit 2021.09.4

---
###下準備
今回もAsinex社のビルディングブロックを題材として扱います。
まずは<b>[以前の記事][11]</b>中で作成したpickleファイルを読み込みます。

```python
from rdkit import DataStructs
from rdkit.Chem import AllChem, Draw
from rdkit.ML.Cluster import Butina
import numpy as np
import pandas as pd
import time
import matplotlib.pyplot as plt
import collections
#パッケージの読み込み

df = pd.read_pickle("Asinex_Building_Blocks.pkl")
#Asinex_Building_Blocks.pklを読み込み、得られたデータフレームをdfと定義した
```

読み込んだ分子群からMorganフィンガープリントを作成します。

```python
mols = df['ROMol']
morgan_fp = [AllChem.GetMorganFingerprintAsBitVect(x, 2, 2048) for x in mols]
#Morganフィンガープリント(半径2, 2048ビット)を作成し、morgan_fpに格納した。
```

---
###タニモト距離に基づく距離行列の作成
続いて先に作成したフィンガープリントから互いのタニモト距離を計算します。
ただし距離行列は三角行列のかたちで計算し、その中身をリスト内に並べるようにします。

```python
#タニモト距離の三角行列を作成
t1 = time.time()
dis_matrix = []
for i in range(1, len(morgan_fp)):
    similarities = DataStructs.BulkTanimotoSimilarity(morgan_fp[i], morgan_fp[:i], 
                                                      returnDistance = True)
    dis_matrix.extend(similarities)
t2 = time.time()
print('t2 - t1:', t2 - t1)
#タニモト距離の距離行列を作成し、中身をdis_matrixに格納した。
#t2 - t1: 48.877509355545044
```

22,525化合物の距離行列の計算には約1分の時間を要します。
要素数が大きくメモリの消費量も著しいため、以下のButinaモジュール使用後は
dis_matrixの変数を削除してしまってもいいかもしれません。

---
###Butinaモジュールによるクラスタリング
<b>Butina.ClusterData</b>関数を使用し、クラスタリングを実施します。

```python
cutoff = 0.5    #類似度0.5以上のクラスターをつくる。

t3 = time.time()
clusters = Butina.ClusterData(dis_matrix, len(morgan_fp), cutoff, isDistData = True)
t4 = time.time()
print('t4 - t3:', t4 - t3) 
#Butinaクラスタリングを実施し、結果をclustersに格納した。6490個のクラスターが作成された。
#t4 - t3: 24.776731967926025
```

この関数を使用する際は主に以下の引数を指定します。
（isDistDataについてよく理解できなかったため、[参考例][21]に倣ってTrueを指定しています。
　詳しくは[rdkit.ML.Cluster.Butinaモジュールの公式ドキュメント][22]をご覧ください。）

[21]:https://www.rdkit.org/docs_jp/RDKit_cookbook_jp.html#id8
[22]:https://www.rdkit.org/docs/source/rdkit.ML.Cluster.Butina.html

- クラスタリングに用いる距離行列
- クラスタリングを行う分子数
- どれぐらいの化合物距離でクラスタリングするか
- isDistDataはとりあえずTrueを指定する（デフォルトだとFalse）

今回はcutoff = 0.5を指定したため、タニモト距離が0.5以下のクラスターが形成されました。
この値を例えば0.2にするとタニモト距離が0.2以下の分子群がクラスタリングされます。
値を小さくすればより細分化され類似度の高い分子群を形成でき、逆に大きな値を指定すれば
類似度が下がるもののより要素数の大きなクラスターが形成されることになります。

---

<b>Butina.ClusterData</b>関数の戻り値は化合物番号が格納されたタプルのタプルとなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1144410/77eb84de-bfd1-7582-11ae-58043c793cb6.png)

格納された化合物番号を利用すれば以下のように中身の分子を取り出すことが可能です。
以下の3行を実行し、適当なクラスターの中身を図示してみましょう。
（ただし多くのクラスターは格納されている分子数が1の小さなクラスターです）

```python
#お試しで適当なクラスターの中身を覗いてみる。
num_cluster_random = np.random.randint(0, len(clusters))
cluster_random = [mols[x] for x in clusters[num_cluster_random]]
Draw.MolsToGridImage(cluster_random, molsPerRow = 5)
```

(num_cluster_random = 260の場合)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1144410/d7ef868f-ceec-9b47-47d7-feedab54458d.png)

なんとなく似た化合物が集まっていることが目視で分かるかと思います。

---
###出力結果を用いたデータ閲覧・加工の例

どんな大きさのクラスターが何個ずつあるのか図示して確認してみます。
要素数1のクラスターが大多数であることが確認できます。

```python
clusters_count = collections.Counter(len(x) for x in clusters)
#クラスターの要素数ごとに何クラスターあるのか数え、clusters_countに格納した。

#クラスターあたりの要素数をx軸、対応するクラスター数をy軸にプロットする。
left = [x for x in clusters_count.keys()]
height = [x for x in clusters_count.values()]
plt.xlabel('Number of clusters', fontsize=20)
plt.ylabel('Number of molecules', fontsize=20)
plt.bar(left, height)
plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1144410/04e91cfc-5a4b-1ad7-dd5c-eb7304ae8021.png)


---

それぞれのクラスターに何分子ずつ格納されているかも見てみましょう。

```python
#クラスター番号をx軸、格納されている分子数をy軸にプロットする。
fig = plt.figure(1, figsize=(10, 4))
plt1 = plt.subplot(111)
plt.axis([0, len(clusters), 0, len(clusters[0])+1])
plt.xlabel('Cluster index', fontsize=20)
plt.ylabel('Number of molecules', fontsize=20)
plt.tick_params(labelsize=16)
plt1.bar(range(1, len(clusters)), [len(c) for c in clusters[:len(clusters)-1]], lw=0)
plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1144410/499ce243-e893-f097-ed52-5a74a263c814.png)

---

要素数が大きなクラスターだけ集めると後々役に立つことがあるかもしれません。
（他のクラスタリング手法の教材にする等）
試しに要素数が100以上のクラスターだけ集めてみましょう。

```python
large_clusters = [x for x in clusters if len(x) >= 100]
#要素数が100以上のクラスターをピックアップした。
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1144410/04aa1015-b047-982e-2398-d9e5a9e2a456.png)

---
###おわりに
Butinaクラスタリングモジュールを用いたクラスタリングを実施いたしました。
今回Morganフィンガープリントから計算したタニモト距離を基にクラスタリングしましたが、
その他の指標による距離行列を用意すれば同様の手法でクラスタリングが可能です。

類似化合物のクラスタリングができれば、各クラスターから代表分子を選ぶことで
偏りが小さく多様な分子が含まれるライブラリーを構築することが可能です。

###参考

- [rdkit.ML.Cluster.Butina module][22]
- [TeachOpenCADD トピック５ 〜化合物クラスタリング〜][23]
- [RDKitクックブック―分子のクラスタリング][24]
- [AI創薬のための ケモインフォマティクス入門 6章: 化合物の類似性を評価してみる][25]

[23]:https://magattaca.hatenablog.com/entry/2020/04/25/161946
[24]:https://www.rdkit.org/docs_jp/RDKit_cookbook_jp.html#id8
[25]:https://github.com/Mishima-syk/py4chemoinformatics/blob/master/py4c.asciidoc#6%E7%AB%A0-%E5%8C%96%E5%90%88%E7%89%A9%E3%81%AE%E9%A1%9E%E4%BC%BC%E6%80%A7%E3%82%92%E8%A9%95%E4%BE%A1%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B

###これまでの記事のシリーズ

[RDKit入門①：まずは環境構築][1]
[RDKit入門②：1分子の読み込みと部分構造検索][2]
[RDKit入門③：複数分子の読み込み (前編)と分子群の描画][3]
[RDKit入門④：複数分子の読み込み (後編)とデータフレームの加工][4]
[RDKit入門⑤：データフレーム内の分子群に対する部分構造検索][5]
[RDKit入門⑥：Morganフィンガープリントの作成とそれを用いたタニモト係数の計算による分子類似性評価][6]
[RDKit入門⑦：反応式の取り扱い(前編)][7]
[RDKitで化学反応を扱った際の生成物群を整理し図示する方法][8]
[RDKit入門⑧：反応式の取り扱い(後編)][9]
[RDKitで化学反応を繰り返しつつ分子の重複数をカウント・記載する][10]
[RDKit入門⑨：データフレーム内の分子群に対する化学反応の適用とExcelファイルへの出力][11]
[RDKit入門⑩：データフレーム内の分子群に対する完全一致検索][12]
[指定した回数だけ化学反応を実施する関数の作成][13]
[BRICSフラグメントを利用した分子構造生成関数の簡略化][14]

[1]:https://qiita.com/Ru-PNP/items/ed5a8530611ef0d712b7
[2]:https://qiita.com/Ru-PNP/items/58d733ef327677d273fd
[3]:https://qiita.com/Ru-PNP/items/1cea34b864e993e4e1e1
[4]:https://qiita.com/Ru-PNP/items/62a108d92c1ddd522e58
[5]:https://qiita.com/Ru-PNP/items/b469ac1b907909a9d821
[6]:https://qiita.com/Ru-PNP/items/79873f0e3eab444f5bfe
[7]:https://qiita.com/Ru-PNP/items/49a080a015414553bbad
[8]:https://qiita.com/Ru-PNP/items/baebf99e269bd7fb2fb5
[9]:https://qiita.com/Ru-PNP/items/e0c1cccf4a7b0cd3f412
[10]:https://qiita.com/Ru-PNP/items/25d40044f3985a406982
[11]:https://qiita.com/Ru-PNP/items/897541250b69733f6f1e
[12]:https://qiita.com/Ru-PNP/items/6185eb2fe873e9d9a893
[13]:https://qiita.com/Ru-PNP/items/7bbea91407790e7f76a1
[14]:https://qiita.com/Ru-PNP/items/c1d02a9716c3489ab911