グラフ中に化合物の構造式や物性値を埋め込む

# はじめに
グラフ中に化合物の構造式や物性値を埋め込む方法を紹介します。例えばグラフを見ながらディスカッションする際に、あるプロットにカーソルを合わせるだけで、そのプロットの詳細（どのような構造式なのか、どのような特徴量を持つのか）を知ることができます。いちいちパワーポイントでプロットの詳細を書き込むという煩雑な作業から解放されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057114/788b061d-1bfe-1cd8-9af0-64f0d121f2bd.png)

# 環境
- windos10
- anaconda
- python 3.9.17
- rdkit 2022.03.5
- bokeh 3.2.2
- numpy 1.22.3
- pandas 1.2.4
- xlsxwriter 3.1.2

RDKitのインストール方法は以下を参考にしました。  
https://datachemeng.com/rdkit_install_import/

# データの読み込み
以下のようなサンプル名やSMILES、物性値が書かれたcsvファイル（sample_data2.csv）を使ってグラフを描いていくことにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057114/fa674a12-bc56-0ac3-6cd3-4c2af5252459.png)

```python
# データの読み込み
import pandas as pd
df = pd.read_csv("sample_data2.csv", encoding="shift_jis")
df.head()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057114/905d19bf-6f0a-bdca-e54b-8afc8ccc3274.png)

# 構造式を付与する
以下のコードを実行することでSMILESを構造式（画像）に変換できます。

```python
# ライブラリーのインストール
from IPython.display import SVG
from rdkit.Chem import AllChem, Draw, Descriptors, PandasTools

# SMILES→構造可視化
PandasTools.AddMoleculeColumnToFrame(df, "SMILES")

# データの表示
df.head()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057114/aaf94bd6-bd7e-ce90-6436-66889dd456aa.png)

※画像は先頭2行分だけ表示

```python
# エクセルに保存（エラー回避のため欠損値は0埋めしておく）
import xlsxwriter
PandasTools.SaveXlsxFromFrame(df.fillna(0), "sample_data2_ROMol.xlsx", size=(150, 150))
```

# 可視化する

```python
# ライブラリーのインポート
from bokeh.io import output_notebook, show
from bokeh.models import ColumnDataSource, HoverTool
from bokeh.plotting import figure
output_notebook()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057114/68b4ba79-b179-fde8-858b-ec9000b42b11.png)

Label列でGroupAとGroupBとグループ分けしたデータをそれぞれ色分けして、横軸にMolWt、縦軸にNumValenceElectronsをとって各化合物をプロットしていきます。

```python
# 色分けするためにデータをそれぞれ定義
df_A = df[df["Label"] == "GroupA"]
df_B = df[df["Label"] == "GroupB"]
```

```python
# プロットするデータの定義
import base64

df_A['base64image'] = df_exp['ROMol'].map(
    lambda x: str(x)[str(x).find('src="')+5:str(x).rfind('" alt')])
A_source = ColumnDataSource(df_A[['MolWt', 'NumValenceElectrons', 'base64image']])
df_B['base64image'] = df_exp['ROMol'].map(
    lambda x: str(x)[str(x).find('src="')+5:str(x).rfind('" alt')])
B_source = ColumnDataSource(df_B[['MolWt', 'NumValenceElectrons', 'base64image']])
```

```python
TOOLS = 'hover,save,pan,box_zoom,reset,wheel_zoom'
p = figure(title='MolWt vs NumValenceElectrons', tools=TOOLS)
p.scatter(x='MolWt', y='NumValenceElectrons', source=A_source, line_color=None, fill_color='blue', size=5)
p.scatter(x='MolWt', y='NumValenceElectrons', source=B_source, line_color=None, fill_color='orange', size=5)
p.xaxis.axis_label = 'MolWt'
p.yaxis.axis_label = 'NumValenceElectrons'
p.select_one(HoverTool).tooltips = """
    <div>
        <div>
            <img src=@base64image></img>
        </div>
    </div>
    """
```

```python
# グラフの表示
show(p)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057114/116c88c9-4c3b-96bf-2717-674c8007f60f.png)

続いて先ほどのプロットに各化合物の物性値（TPSAとPBF_mean）の情報を埋め込んで表示させます。

```python
# プロットするデータの定義
df_A['base64image'] = df_A['ROMol'].map(
    lambda x: str(x)[str(x).find('src="')+5:str(x).rfind('" alt')])
A_source = ColumnDataSource(df_A[["sample", "MolWt", "NumValenceElectrons", "TPSA", "PBF_mean", "base64image"]])
df_B['base64image'] = df_B['ROMol'].map(
    lambda x: str(x)[str(x).find('src="')+5:str(x).rfind('" alt')])
B_source = ColumnDataSource(df_B[["sample", "MolWt", "NumValenceElectrons", "TPSA", "PBF_mean", "base64image"]])
```

```python
TOOLS = 'hover,save,pan,box_zoom,reset,wheel_zoom'
p = figure(title='MolWt vs NumValenceElectrons', tools=TOOLS)
p.scatter(x='MolWt', y='NumValenceElectrons', source=A_source, line_color=None, fill_color='blue', size=5)
p.scatter(x='MolWt', y='NumValenceElectrons', source=B_source, line_color=None, fill_color='orange', size=5)
p.xaxis.axis_label = 'MolWt'
p.yaxis.axis_label = 'NumValenceElectrons'
p.select_one(HoverTool).tooltips = """
    <div>
        <div>
            <span style="font-size: 17px; color: #966;">sample:</span>
            <span style="font-size: 15px; ">@sample</span>
        </div>
        <div>
            <span style="font-size: 17px; color: #966;">TPSA:</span>
            <span style="font-size: 15px; ">@TPSA</span>
        </div>
        <div>
            <span style="font-size: 17px; color: #966;">PBF_mean:</span>
            <span style="font-size: 15px; ">@PBF_mean</span>
        </div>
        <div>
            <img src=@base64image></img>
        </div>
    </div>
    """
```

```python
# グラフの表示
show(p)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057114/831bdd7d-a63a-b678-442e-cd6aebbec2dc.png)