Pandasを活用し相関の高い特徴(記述子)を除去する


# 背景
RDKitやmordred等記述子計算で生成した特徴(記述計算結果)を元に、化合物の機械学習を行う際、そのままでは相関の高い記述子の組み合わせが含まれる。この場合「多重共線性」が発生し重回帰分析等がうまくいかないため、相関の高いものを除外する必要がある。

# 環境
- Windows10
- RDkit 2018.09.1.0  
- scikit-learn 0.20.2
- Jupyter Notebook

# 今回の目的
多重共線性の理論的な説明等は他のサイトに詳しい説明があるため、ここでは相関の高いものを除外する具体的な手順に絞って記載する。
やりたいことは、記述子計算結果に対し、ある一定以上の相関を持つ記述子の組み合わせが存在しない、新たな記述子セットを抽出することである。

# 手順概要
scikit-learn等にピンポイントで今回の目的を満たすようなモジュールが見当たらなかったため、自前で作成した。
前提として、PandasのDataFrameの各列に記述子の計算結果が格納されているものとする。

アルゴリズムとしては、以下のようにシンプルなものを考えた。

1. 記述子計算結果のDataFrameに対し、corr()メソッドより、全記述子同士の相関行列を求める。
2. 相関行列の中で最も相関の高い組を1つ取り出し、しきい値を超えていれば、どちらかの記述子を除去したものを当たらな相関行列とする(ただし自身同士の組み合わせは除外する）
3. 2でしきい値を超える組み合わせがなければ終了とする。もししきい値を超える組み合わせがあれば2に戻る。

# ソースコード
手順概要で記載したアルゴリズムを実装したコードを示す。
なお、BBCPのデータをPandasのDataFrameに記述子計算結果を取り込むまでは、前回の記事 [scikit-learnによりVarianceが低い記述子を除去する](https://qiita.com/kimisyo/items/fda30f28a27dfe88ad6d)を参考にしてほしい。ここでは、BBCPの記述子計算結果が、"desc_df"というDataFrameに格納されている前提で話を進める。

```python
import math
threshold = 0.8 #しきい値

# 相関行列を求める
desc_corr = desc_df.corr()

while True:
    columns = desc_corr.columns 
    data_max = 0.0
    index_max  = None
    column_max = None
    #相関行列の中で最も相関の高い組を1つ取り出す。
    for index in columns:
        for column in columns:
            data = abs(desc_corr.loc[index, column])
            # 自身同士でなく、しきい値を超えているか。
            if index != column and not np.isnan(data) and data > threshold :
                #相関の最も高いものを記憶
                if data_max < data:
                    data_max = data
                    index_max = index 
                    column_max = column 

    if data_max == 0.0:
        # しきい値を超えるものがなければ終了
        break
    else:
        # しきい値を超えるものがある場合、column側の特徴を相関行列から除去(行、列共に)
        desc_corr.drop([column_max], axis=0, inplace=True)
        desc_corr.drop([column_max], axis=1, inplace=True)
```

# 結果
BACEのデータに対し上記を実行したところ、200記述子から58個の記述子が除去され、最終的に142個の記述子が残った。

# 検証
記述子抽出の結果を可視化で確認する。
相関の高いものを除去しなかった場合と、相関0.6以上のものを除外した場合について、
相関の高い記述子同士が近く配置されるよう、MDSを用いて可視化を行った。

相関の高いものを除去しなかった場合

```python
from sklearn import manifold
mds = manifold.MDS(n_components=2, dissimilarity="precomputed", random_state=1)

desc_all_corr = desc_df.corr()
# 相関にNANが含まれる特徴を除去
desc_all_corr_new = desc_all_corr.dropna(how='all', axis=0)
desc_all_corr_new = desc_new.dropna(how='all', axis=1)
# 相関の高いものが類似度が高くなるよう変換
desc_all_corr_new = 1 - np.abs(desc_all_corr_new)
# 可視化
pos = mds.fit_transform(desc_all_corr_new)
plt.scatter(pos[:, 0], pos[:, 1], marker = 'o')
plt.show()
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/329816/f0aa21a5-fff5-366c-7fcb-04802b5843a5.png)

相関の高いもの(0.6以上)を除去した場合

```python
desc_deleted_corr = desc_corr
#  相関にNANが含まれる特徴を除去
desc_deleted_corr_new = desc_deleted_corr.dropna(how='all', axis=0)
desc_deleted_corr_new = desc_deleted_corr_new.dropna(how='all', axis=1)
# 相関の高いものが類似度が高くなるよう変換
desc_deleted_corr_new = 1 - np.abs(desc_deleted_corr_new)
# 可視化
pos = mds.fit_transform(desc_deleted_corr_new)
plt.scatter(pos[:, 0], pos[:, 1], marker = 'o')
plt.show()
```


![image.png](https://qiita-image-store.s3.amazonaws.com/0/329816/9db584ea-8703-3150-b752-a0fb62677578.png)


グラフを見ると確かに相関性の高いものを除外したことによって、特徴間の距離が過度に近いものがなくなり、適当にばらけていることが確認できる。