PythonでVIF(variance inflation factor: 分散拡大係数)を算出する

# やりたいこと
重回帰モデルなどを実装する際に直面する多重共線性の問題に対して、VIFを算出することで相関のある変数を削除する方法を備忘録としてPythonで実装したいと思います。
また、よくあるVIFによる変数削除はVIFを算出した際に閾値を超えた複数変数を一括で削除していますが、この記事では閾値を超えた上位1変数を削除し、再度VIFを算出する処理を繰り返し行い、最終的に閾値を超えないことを確認する実装を行いたいと思います。

# VIFとは
説明変数間で相関係数の高い変数がある場合、それを「多重共線性がある」といいます。
多重共線性があると偏回帰係数の標準誤差が大きくなるため、統計的な信頼性が低下し要因分析などで問題が生じます。
そこで説明変数間で関係性の高い変数を削除することで、この問題を解消するというものがVIFのモチベーションです。

# VIFの算出方法
VIFの理論部分については色々な記事に記載がありますので、ここでは簡単な導入のみを記載して考え方を示したいと思います。
#### 1. 各説明変数をそれぞれ目的変数として最小二乗回帰を行う
```math
X_1 = α_2X_2 + α_3X_3 + ・・・+α_kX_k+b+e
```
bは定数、eは誤差項
#### 2. 上記の重回帰モデルから算出される決定係数を用いてVIFを求める
```math
VIF_i = \frac{1}{1-R^2_i}
```
説明変数の数だけ1,2を繰り返す
#### 3. VIFを降順に並べて閾値以上の変数を削除する
決定係数を利用することで、2変数以上の多変量の関係性を表現しています。
閾値については正確な基準値があるわけではないですが、調べていると一般的に10以上の場合は多重共線性があるという記載が多かったです。
ただしstatsmodelsのドキュメントでは5以上は高い関係性があると記載されているため、最終的には分析者の主観となってしまう部分かと思います。
[statsmodels](https://www.statsmodels.org/stable/generated/statsmodels.stats.outliers_influence.variance_inflation_factor.html)
# 一括削除しない理由について
VIFを紹介している他の記事を見ていると、VIFの閾値を超える変数が複数ある場合には、その変数群を一括で削除している記載がほとんどでした。
しかし、例えば説明変数の中で2変数間にのみ強い相関があった場合、VIFを計算するとその2変数がともに高いVIF factorとして出力されるため2変数とも削除されてしまいます。
本来は片方の変数のみを削除するべきと考えられるため、今回の実装では閾値を超える場合は上位の1変数を削除し再度計算をするループ処理としています。

# Pytnonでの実装
使用するライブラリをインポート
```python
import pandas as pd
from sklearn.datasets import fetch_california_housing
from statsmodels.stats.outliers_influence import variance_inflation_factor
```
今回はサンプルとしてSklearnのカリフォルニア住宅価格データセットを利用する
```python
california_housing = fetch_california_housing()
train_x = pd.DataFrame(california_housing.data, columns=california_housing.feature_names)
train_y = pd.Series(california_housing.target)
```
VIFの計算部分を実装
```python
#VIFを計算する
def cal_vif(df):
    vif_df = pd.DataFrame()
    vif_df["VIF_Factor"] = [variance_inflation_factor(df.values, i) for i in range(df.shape[1])]
    vif_df["features"] = df.columns
    vif_df.sort_values("VIF_Factor", inplace=True, ascending=False)
    # 変数削除の際にindex指定するため降順でindexをリセット
    vif_df.reset_index(inplace=True, drop=True)
    return vif_df

# VIF値の上位の変数から削除するループを実装
def drop_vif(df, threshold=10):
    df_copy = df.copy()
    while True:
        df_vif_cal = cal_vif(df_copy)
        max_vif = df_vif_cal["VIF_Factor"].max()
        if max_vif <= threshold:
            break
        else:
            max_vif_feature = df_vif_cal.iloc[0]["features"]
            df_vif_cal.drop(inplace=True, index=0)
            df_vif_cal.reset_index(inplace=True, drop=True)
            df_copy = df_copy[df_vif_cal["features"]]
    return df_vif_cal
```
定義した関数を実行してVIFを算出する
```python
vif_result = drop_vif(train_x, 10)
vif_result
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815993/d7e3270a-3f7d-2ee2-b184-ac6d66ef25b3.png)
これにより説明変数間で関係性が低いと考えられる変数群を算出することが可能です。
以上が多重共線性を考慮した説明変数の選定でした。
# その他
より簡単な手法として相関係数を2変数同士で計算し(相関行列)、閾値以上の場合は片方の変数を削除するやり方もあります。
変数が少ない場合はそれでも良いかもしれませんが、変数が多くなると組み合わせが膨大になってしまいます。
また、どちらの変数を削除するべきかという部分で分析者の主観が入ってしまうためVIFのほうが機械的に処理できる分迷いが減るかと思います。(いいか悪いかは別として)
ドメイン知識があるようであれば相関係数のペアで確認する方法もありかと思います。

# 参考リンク
[statsmodelsドキュメント](https://www.statsmodels.org/stable/generated/statsmodels.stats.outliers_influence.variance_inflation_factor.html)
[ごちきか](https://gochikika.ntt.com/Preprocessing/reduction.html)
[βshort Lab.](https://betashort-lab.com/%e3%83%87%e3%83%bc%e3%82%bf%e3%82%b5%e3%82%a4%e3%82%a8%e3%83%b3%e3%82%b9/%e7%b5%b1%e8%a8%88%e5%ad%a6/vif/)