SelectKBestって何？ってなったときに、サクッとみれるようまとめた

分析の学習をしているとSelectKBestという関数を使っているケースに巡り、
「？？？？」ってなったので、簡単に自分でも使ってみました。
ログとしてここに書いておきます。

簡単な説明にとどめ、そして$χ^2$検定とか出てきますが、ガツガツした説明はここでは一旦省きます。
#使うデータ
[公式ドキュメント](https://scikit-learn.org/stable/modules/feature_selection.html#univariate-feature-selection)に従って、定番のload_irisを使います。
（コードは一気に書くので、後述）

# ドキュメントにまずは従う
一旦ドキュメント通りにコピペして使ってみます。

```
import numpy as np

from sklearn.datasets import load_iris
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2


X, y = load_iris(return_X_y=True)
print(X.shape)
X_new = SelectKBest(chi2, k=2).fit_transform(X, y)
X_new.shape

>>>(150, 4)
>>>(150, 2)
```
当然ながら、同じ結果になります。

なんですが、、、そもそも自分の中で$χ^2$って$χ^2$検定（二つの質的変数がどのくらい連関があるか？的な）の利用シーンしか知らず、
クロス表でCategorical×Categoricalのイメージのみでした。
そーなると「なんですか？これは？」ってなるわけですが、
とりあえずX, X_newの要素をランダムにみてみました。

```
print(X[0])
print(X_new[0])
print(X[10])
print(X_new[10])


[5.1 3.5 1.4 0.2]
[1.4 0.2]
[5.4 3.7 1.5 0.2]
[1.5 0.2]
```

#scipyでも検証
結果的に今回SelectKBestは特徴量選択のメソッドで、今回は後ろ2つが$χ^2$の値を最も最適と判断されたわけですが、本当にそうか？？？って思ったので、scipy.statsでも検証してみました。

```
print('そのままのX:', chi2_contingency(np.hstack((X, y.reshape(-1, 1))), correction=False)[:3])
X_new2 = np.array([arr[:2] for arr in X]) # 0, 1
print('0, 1番目:', chi2_contingency(np.hstack((X_new2, y.reshape(-1, 1))), correction=False)[:3])
X_new2 = np.array([arr[::2] for arr in X]) # 0, 2
print('0, 2番目:', chi2_contingency(np.hstack((X_new2, y.reshape(-1, 1))), correction=False)[:3])
X_new2 = np.array([arr[::3] for arr in X]) # 0, 3
print('0, 3番目:', chi2_contingency(np.hstack((X_new2, y.reshape(-1, 1))), correction=False)[:3])
X_new2 = np.array([arr[1:-1] for arr in X]) # 1, 2
print('1, 2番目:', chi2_contingency(np.hstack((X_new2, y.reshape(-1, 1))), correction=False)[:3])
X_new2 = np.array([arr[1::2] for arr in X]) # 1, 3
print('1, 3番目:', chi2_contingency(np.hstack((X_new2, y.reshape(-1, 1))), correction=False)[:3])
# 3, 4
print('本命:', chi2_contingency(np.hstack((X_new, y.reshape(-1, 1))), correction=False)[:3])


そのままのX: (197.72548568528768, 1.0, 596)
0, 1番目: (95.8009422151861, 1.0, 298)
0, 2番目: (108.3388708044952, 1.0, 298)
0, 3番目: (106.60836667325174, 1.0, 298)
1, 2番目: (150.73817774749693, 0.9999999999999512, 298)
1, 3番目: (137.88456015851705, 1.0, 298)
本命: (34.65598151635649, 1.0, 298)
```
なるほど、確かに！って感じで、後ろ2つで$χ^2$が一番小さいことがわかりました。

#じゃあ、これで次元削減していいの？
このSelectKBestで最適な特徴量を選択できるのであれば、PCAとかいらないじゃ〜〜ん！って思いがちですが、そうでもないです。
そもそも$χ^2$において最適な特徴量であるので、
例えばこのX_newとかをLogisticRegressionとかに当てはめると、正解率はそんなによくならないことを確かめてみました。

### そのまま
```
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=123)
model = LogisticRegression(solver='liblinear')
model.fit(X_train, y_train)
model.score(X_test, y_test)

0.9736842105263158
```
### X_new
```
X_train, X_test, y_train, y_test = train_test_split(X_new, y, random_state=123)
model = LogisticRegression(solver='liblinear')
model.fit(X_train, y_train)
model.score(X_test, y_test)

0.9210526315789473
```
### 0, 1番目
```
X_new2 = np.array([arr[:2] for arr in X])
X_train, X_test, y_train, y_test = train_test_split(X_new2, y, random_state=123)
model = LogisticRegression(solver='liblinear')
model.fit(X_train, y_train)
model.score(X_test, y_test)

0.8421052631578947
```
### 1, 3番目
```
X_new2 = np.array([arr[1::2] for arr in X])
X_train, X_test, y_train, y_test = train_test_split(X_new2, y, random_state=123)
model = LogisticRegression(solver='liblinear')
model.fit(X_train, y_train)
model.score(X_test, y_test)

0.9473684210526315
```
試合終了しました。(chi2 -> f_regressionでも同じ結果になる)

# 機械学習にはRFEとかが良さげ
このSelectKBestはどういう時に有利なのかというと、統計量の側面から見た特徴量選択に適している（そう）です。

なので、上記で見たlinear modelで力を発揮するかと言われれば、そうではなく、
機械学習において特徴量をうまく選択したい時は、
RFE(Recursive Feature Elimination)、EFS（Exhaustive Feature Selector）などを使う方が良いです。

実際に使って検証しました。

```
from sklearn.feature_selection import RFE


X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=123)
model = LogisticRegression(solver='liblinear')
model.fit(X_train, y_train)
print('そのまま', model.score(X_test, y_test))

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=123)
rfe = RFE(estimator=LogisticRegression(solver='liblinear'), n_features_to_select=2)
rfe.fit(X_train, y_train)
print('RFE features 2', rfe.score(X_test, y_test))


そのまま 0.9736842105263158
RFE features 2 0.9473684210526315
```
結果をみると、確かに機械学習において最適な特徴量選択をしてくれていますね。

# まとめ
本来はSelectKBestってなんだ？ってところから、最終的にどのように使い分けるのか？というところまで派生しました。
おさらいすると
統計的な側面で特徴量選択→ SeletKBest（など。他にもあります）
機械学習の側面で特徴量選択→RFE（など。ほかにもあります）
って感じで使い分けるのが適切かと。

何かしらの参考になれば嬉しい限りです！

#参考文献
[【入門者向け】特徴量選択の基本まとめ(scikit-learnときどきmlxtend)](https://qiita.com/FukuharaYohei/items/db88a8f4c4310afb5a0d) ⇦めちゃ参考になりました。
[https://scikit-learn.org/stable/modules/generated/sklearn.feature_selection.SelectKBest.html#sklearn.feature_selection.SelectKBest](https://scikit-learn.org/stable/modules/generated/sklearn.feature_selection.SelectKBest.html#sklearn.feature_selection.SelectKBest)
[https://scikit-learn.org/stable/modules/generated/sklearn.feature_selection.chi2.html#sklearn.feature_selection.chi2](https://scikit-learn.org/stable/modules/generated/sklearn.feature_selection.chi2.html#sklearn.feature_selection.chi2)
[https://scikit-learn.org/stable/modules/generated/sklearn.feature_selection.f_regression.html#sklearn.feature_selection.f_regression](https://scikit-learn.org/stable/modules/generated/sklearn.feature_selection.f_regression.html#sklearn.feature_selection.f_regression)
[https://scikit-learn.org/stable/modules/generated/sklearn.feature_selection.RFE.html](https://scikit-learn.org/stable/modules/generated/sklearn.feature_selection.RFE.html)