1. 要約
卒論やってるときに、「2つの特徴量間の相関係数を一定以下に抑えないといけない、でも特徴量の候補が100もある」みたいなことに直面した。たぶん技術的に何でもない基本的なことなせいか、ネットに同じようなものが見当たらなかった。とりあえず記事にしておく。内容の詳細は「2. コードの構成」以降に書いた。
1.1 特徴量間の相関が高いときの問題
統計や機械学習のモデルで分析を行うとき相関が高い特徴量の組み合わせが含まれていると、
- 分析結果の説得力が望めない
- 学習ごとに重みづけが安定しない
などの弊害(多重線形性)が生じてしまう。手っ取り早いものに「相関が高い特徴量のそれぞれの組み合わせのうち片方を削除」という手法がある。
1.2 「特徴量の数」と「相関の低さ」のトレードオフ
とはいえ、なにも考えずに特徴量を削除していくと極端に特徴量の数が減ってしまう可能性がある。例えば、相関が高い組み合わせが複数含まれているケースで、他の特徴量とよりおおくの相関をもつ特徴量を残してしまい、必要以上に特徴量を減らさないといけない可能性がある。短く言えば「『相関係数を一定以下』かつ『特徴量の数がもっとも多い組み合わせ』を見つけたい、ということ。
1.3 コードと実用例
コードの例と簡単に実用例を上げておく。
コードの一例
最初の二つの関数をつかって三つ目の関数を定義する流れになっていて、処理を実行するときは三つ目の関数を使う。3つ目の関数no_high_corr(df, threshold)の概要は以下。
関数 | 引数 | 説明 |
---|---|---|
no_high_corr( ) | df, threshold | df:特徴量のデータ、pandas.DataFrame threshold:相関係数の水準、0以上1以下 返り値:2特徴量間の相関係数がthreshold以下のときの特徴量数が最大の組み合わせ, pandas.DataFrame |
最初の二つの関数については「4. メイン」の(2.)でまとめてある。
import pandas as pd
import numpy as np
def corr_loc(corr_df,threshold):
count_sums = pd.Series(np.zeros(corr_df.shape[1]))
for i in range(corr_df.shape[1]):
count_j = 0
for j in range(corr_df.shape[1]):
if corr_df.iloc[j,i] > threshold:
count_j += 1
else:
pass
count_sums.iloc[i] = count_j
print(count_sums)
return count_sums.idxmax()
def corr_max(corr_df,threshold):
count_sums = pd.Series(np.zeros(corr_df.shape[1]))
for i in range(corr_df.shape[1]):
count_j = 0
for j in range(corr_df.shape[1]):
if corr_df.iloc[j,i] > threshold:
count_j += 1
else:
pass
count_sums.iloc[i] = count_j
print(count_sums)
return count_sums.max()
def no_high_corr(df, threshold):
corrmat = df.corr()
a = corrmat.abs()
b = corr_loc(a,threshold)
c = corr_max(a,threshold)
while c > 1:
A = a.drop(a.columns[b],axis=1)
B = A.drop(A.index[b])
a = B
b = corr_loc(B,threshold)
c = corr_max(B, threshold)
return df.loc[:,a.columns]
####実用例
sklearnのデータセット"ボストン住宅価格"の特徴量を対象にした実装例。各特徴量同士の相関係数が0.5以下、かつ特徴量数が最大になる組み合わせを出力する。
import pandas as pd
import numpy as np
from sklearn.datasets import load_boston
boston = load_boston()
df_features = pd.DataFrame(boston.data, columns = boston.feature_names)
print('==============Oridinary data==============')
print(df_features.head())
low_corr_features = no_high_corr(df_features,0.5)
print('==============Corrected data==============')
print(low_corr_features)
以下が出力。もともと13あった特徴量(Ordinary data)から6の特徴量(Corrected data)が選択された。
==============Oridinary data==============
CRIM ZN INDUS CHAS NOX RM AGE DIS RAD TAX \
0 0.00632 18.0 2.31 0.0 0.538 6.575 65.2 4.0900 1.0 296.0
1 0.02731 0.0 7.07 0.0 0.469 6.421 78.9 4.9671 2.0 242.0
2 0.02729 0.0 7.07 0.0 0.469 7.185 61.1 4.9671 2.0 242.0
3 0.03237 0.0 2.18 0.0 0.458 6.998 45.8 6.0622 3.0 222.0
4 0.06905 0.0 2.18 0.0 0.458 7.147 54.2 6.0622 3.0 222.0
PTRATIO B LSTAT
0 15.3 396.90 4.98
1 17.8 396.90 9.14
2 17.8 392.83 4.03
3 18.7 394.63 2.94
4 18.7 396.90 5.33
==============Corrected data==============
CHAS DIS RAD PTRATIO B LSTAT
0 0.0 4.0900 1.0 15.3 396.90 4.98
1 0.0 4.9671 2.0 17.8 396.90 9.14
2 0.0 4.9671 2.0 17.8 392.83 4.03
3 0.0 6.0622 3.0 18.7 394.63 2.94
4 0.0 6.0622 3.0 18.7 396.90 5.33
.. ... ... ... ... ... ...
501 0.0 2.4786 1.0 21.0 391.99 9.67
502 0.0 2.2875 1.0 21.0 396.90 9.08
503 0.0 2.1675 1.0 21.0 396.90 5.64
504 0.0 2.3889 1.0 21.0 393.45 6.48
505 0.0 2.5050 1.0 21.0 396.90 7.88
2. コードの構成
2.1 全体の流れ
相関が一定以下かつ、最大数の特徴量の組み合わせを以下の流れで取得。
- 相関行列を取得
- 各列で一定以上の相関係数をカウント
- 相関係数の数が一番多い特徴量を相関行列の行・列から削除
- 相関係数が一定以上が、全ての列で0になるまで2., 3., 4.を繰り返す
- 残った特徴量で新しいデータテーブルを出力
2.2 その他
- 基本的にPandasを中心にしてにして構成。
3. データの準備
構成にむけて、5つの特徴量(A,B,C,D,E)$\times$ 10個のデータをpd.DataFrameで用意。
demo_data = pd.DataFrame(np.random.randint(-1000, 1000,(10,5)),columns=['A','B','C','D','E'])
print(demo_data)
A B C D E
0 -644 225 8 509 -980
1 809 993 882 -144 -462
2 -501 -505 972 -657 194
3 -980 862 886 -163 -444
4 -757 -254 186 -506 -178
5 -171 -317 973 -237 760
6 831 265 461 0 214
7 814 -466 610 -668 112
8 -281 832 -753 963 306
9 578 -557 -962 3 435
4. メイン
「2.1 全体の流れ」の1.〜5.にしたがって処理していく。
(1.) 相関行列を取得
pd.DataFrame.corr()で相関行列を計算すると以下。
corrmat = demo_data.corr()
print(corrmat)
A B C D E
A 1.000000 -0.093300 -0.070089 -0.112714 0.312305
B -0.093300 1.000000 0.048836 0.559999 -0.472975
C -0.070089 0.048836 1.000000 -0.638188 -0.117121
D -0.112714 0.559999 -0.638188 1.000000 -0.153192
E 0.312305 -0.472975 -0.117121 -0.153192 1.000000
(2.) 各列で一定以上の相関係数をカウント
相関係数を各列でカウントし、最大値の列番号を返す関数(corr_loc(corr_df,threshold))と、最大値を返す関数(corr_max(corr_df,threshold))を定義する。*1~*4は共通で返り値の5と6が異なる。
関数 | 引数 | 説明 |
---|---|---|
corr_loc( ) | corr_df, threshold | corr_df:各成分の絶対値をとった相関行列、pandas.DataFrame threshold:相関係数の水準、0以上1以下 返り値:thresholdより大きい相関係数を最ももつ特徴量の列番号 |
corr_max( ) | corr_df, threshold | corr_df:各成分の絶対値をとった相関行列、pandas.DataFrame threshold:相関係数の水準、0以上1以下 返り値:thresholdより大きい相関係数を最ももつときの個数 |
# 最大値の列番号を返す関数
def corr_loc(corr_df,threshold):
# *1 i列の相関係数一定以上をカウントを記録する空のテーブル
count_sums = pd.Series(np.zeros(corr_df.shape[1]))
# *2 i列ごとに相関係数一定以上の個数をカウントするためのループ
for i in range(corr_df.shape[1]):
# *3 i列のうちj行が相関係数一定以上ならカウント、一定以下ならパスする
count_j = 0
for j in range(corr_df.shape[1]):
if corr_df.iloc[j,i] > threshold:
count_j += 1
else:
pass
# *4 i列での相関係数一定以上の個数を記録
count_sums.iloc[i] = count_j
print(count_sums)
# *5 相関係数一定以上のカウントが最大の列番号を返す
return count_sums.idxmax()
# 最大値を返す関数
def corr_max(corr_df,threshold):
# *1
count_sums = pd.Series(np.zeros(corr_df.shape[1]))
# *2
for i in range(corr_df.shape[1]):
# *3
count_j = 0
for j in range(corr_df.shape[1]):
if corr_df.iloc[j,i] > threshold:
count_j += 1
else:
pass
# *4
count_sums.iloc[i] = count_j
print(count_sums)
# *6 相関係数一定以上のカウントの最大値を返す
return count_sums.max()
番号 | 注釈 |
---|---|
*1 | i列ごとに相関係数一定以上をカウントを記録する空のテーブル |
*2 | i列ごとに相関係数一定以上の個数をカウントするためのループ |
*3 | i列のうちj行が相関係数一定以上ならカウント、一定以下ならパス |
*4 | i列の個数を記録 |
*5 | 相関係数一定以上のカウントが最大の列番号を返す |
*6 | 相関係数一定以上のカウントの最大値を返す |
(3.) 相関係数の数が一番多い特徴量を行・列から削除
& (4.) 一定以上の相関係数が無くなるまで繰り返し
pd.DataFrame.drop()をつかって、一定水準より大きい相関係数の数が一番多い特徴量を、相関行列の行・列それぞれから削除するのを繰り返す。すべての列から一定水準より大きい相関係数がなくなったらループ終了。以下の例では相関係数の絶対値が0.2を一定の水準としている。ただし、相関行列では、各列に自らとの相関($=1$)が含まれるのでループの終了条件に注意。
# *7 正・負の高い相関係数を削除したいので、相関行列の各成分を絶対値にしておく
a = corrmat.abs()
# *8 今回は相関係数の絶対値が0.2より大きいものを削除
b = corr_loc(a,0.2)
c = corr_max(a,0.2)
# *9 相関係数が一定以上の組み合わせが1個より大きいかぎり繰り返す
while c > 1:
A = a.drop(a.columns[b],axis=1)
B = A.drop(A.index[b])
a = B
b = corr_loc(B,0.2)
c = corr_max(B, 0.2)
print(a)
D E
D 1.000000 0.153192
E 0.153192 1.000000
番号 | 注釈 |
---|---|
*7 | 正・負の高い相関係数を削除したいので、相関行列の各成分を絶対値にしておく |
*8 | 今回は相関係数の絶対値が0.2より大きいものを削除 |
*9 | 相関係数が一定以上の組み合わせが1個より大きいかぎり繰り返す |
(5.) 残った特徴量で新しいデータテーブルを出力
元のデータ(demo_data)から相関係数0.2以下の組み合わせを抽出する。
# *10 列名のリスト(a.coulumns)を渡して抽出
no_high_corr = demo_data.loc[:,a.columns]
print(no_high_corr)
D E
0 509 -980
1 -144 -462
2 -657 194
3 -163 -444
4 -506 -178
5 -237 760
6 0 214
7 -668 112
8 963 306
9 3 435
番号 | 注釈 |
---|---|
*10 | 列名のリスト(a.coulumns)を渡して抽出 |