論文中に 「VIF(分散拡大係数)」として登場する用語。
VIF(分散拡大係数) とは:多重共線性の強さを定量化。VIF > 10 は深刻、5以上で注意。1なら他変数と独立。
VIF(Variance Inflation Factor, 分散拡大係数)は、 多重共線性の強さを変数ごとに定量化する指標。 重回帰の診断で必ず計算します。
定義:$\text{VIF}_j = 1 / (1 - R_j^2)$
ここで $R_j^2$ は、 「説明変数 $x_j$ を残りの説明変数で回帰したときの $R^2$」。 もし $x_j$ が他変数で完全に予測できるなら $R_j^2 = 1$ → VIF が無限大。 完全に独立なら $R_j^2 = 0$ → VIF = 1。
目安:
使い方:
Python:from statsmodels.stats.outliers_influence import variance_inflation_factor
VIF の閾値は固い決まりではない:10 という値も慣習で、 厳しめに 3 を採用する研究者もいます。 領域ごとの慣行に従いましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # 基本パターン import pandas as pd import numpy as np from scipy import stats import matplotlib.pyplot as plt import seaborn as sns # データ読み込み df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932') # 基本統計量 df.describe() # 可視化 sns.pairplot(df[['食料費', '教育費', '住居費']]) plt.show() |
このページの上にある3つの概念マップ(関係マップ、 包含マップ、 ツリーマップ)でこの概念の位置づけが視覚的に分かります。 関連手法を辿って学習を進めましょう。
統計データ活用コンペティションのSSDSE-B-2026データは、 47都道府県の社会経済データ。 この概念を使って以下のような分析ができます:
| 機能 | Python (pandas) | Python (scipy) |
|---|---|---|
| 要約統計 | df.describe() | stats.describe() |
| 平均 | df.mean() | np.mean() |
| 標準偏差 | df.std() | np.std() |
| 相関 | df.corr() | stats.pearsonr() |
| t検定 | — | stats.ttest_ind() |
| 回帰 | — | stats.linregress() |
| 分布フィッティング | — | stats.norm.fit() |
この概念は、 他の多くの統計概念と密接に関連しています。 ジャストインタイム型学習では、 必要に応じて関連用語へジャンプしながら全体像を構築します。
| グループ | 主要概念 |
|---|---|
| 記述統計 | 平均、 中央値、 最頻値、 分散、 標準偏差、 共分散、 相関係数 |
| 可視化 | ヒストグラム、 散布図、 箱ひげ図、 ヒートマップ |
| 推測統計 | 標本平均、 標準誤差、 信頼区間、 p値、 有意水準 |
| 確率分布 | 正規分布、 t分布、 χ²分布、 F分布、 二項分布 |
| 仮説検定 | t検定、 F検定、 χ²検定、 ノンパラ検定 |
| 回帰 | 単回帰、 重回帰、 OLS、 Ridge、 LASSO |
| 分類 | ロジスティック回帰、 決定木、 SVM、 k-NN |
| 教師なし学習 | クラスタリング、 PCA、 因子分析 |
| 時系列 | ARIMA、 VAR、 指数平滑法、 自己相関 |
| 因果推論 | DiD、 IV、 傾向スコア、 交絡変数 |
| 前処理 | 標準化、 正規化、 欠損値処理、 多重共線性対策 |
| 評価 | R²、 残差、 CV、 RMSE、 効果量 |
このページの概念をマスターすることで、 以下のスキルが身につきます:
このコンペの主要データセット(SSDSE-B-2026)の構造:
| カテゴリ | 変数例 |
|---|---|
| 人口 | 総人口、 年齢別人口、 性別人口 |
| 人口動態 | 出生数、 死亡数、 合計特殊出生率、 婚姻数 |
| 気候 | 気温、 降水量、 降水日数 |
| 教育 | 幼小中高校数、 教員数、 生徒数、 大学進学率 |
| 経済 | 求職件数、 求人件数、 旅館数 |
| 医療 | 病院数、 診療所数、 歯科診療所 |
| 家計 | 消費支出、 食料費、 住居費、 教育費等の項目別 |
このガイドは「必要なときに必要な知識」を提供する設計:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # 必須ライブラリのインストール pip install pandas numpy scipy statsmodels scikit-learn matplotlib seaborn # 標準的なインポート import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from scipy import stats from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split from sklearn.metrics import r2_score, mean_squared_error # 日本語表示の設定(matplotlib) plt.rcParams['font.family'] = 'Hiragino Sans' plt.rcParams['axes.unicode_minus'] = False # データ読み込み(SSDSE は cp932 エンコーディング) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932') print(df.shape) print(df.head()) print(df.describe()) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | def quick_eda(df, target=None): """探索的データ分析の基本テンプレート""" print(f"Shape: {df.shape}") print(f"\nColumn types:\n{df.dtypes}") print(f"\nMissing values:\n{df.isnull().sum()}") print(f"\nBasic stats:\n{df.describe()}") # 数値列の可視化 numeric_cols = df.select_dtypes(include=[np.number]).columns df[numeric_cols].hist(bins=20, figsize=(15, 10)) plt.tight_layout() plt.show() # 相関ヒートマップ if len(numeric_cols) > 1: plt.figure(figsize=(12, 10)) sns.heatmap(df[numeric_cols].corr(), annot=True, fmt='.2f', cmap='RdBu_r', center=0) plt.show() # ターゲットがあれば散布図行列 if target and target in df.columns: sns.pairplot(df[numeric_cols[:5]], hue=target if df[target].dtype == 'O' else None) plt.show() |
分析結果を報告する際の標準的な構成:
p値だけでなく効果量も併記するのが現代統計の標準。 主要な指標と Cohen の解釈基準:
| 統計量 | 効果量 | 小 | 中 | 大 |
|---|---|---|---|---|
| 2群平均差 | Cohen's d | 0.2 | 0.5 | 0.8 |
| 相関 | r | 0.1 | 0.3 | 0.5 |
| 線形回帰 | R² | 0.02 | 0.13 | 0.26 |
| ANOVA | η² (eta²) | 0.01 | 0.06 | 0.14 |
| χ² | Cramér's V | 0.1 | 0.3 | 0.5 |
| ロジスティック | Odds Ratio | 1.5 | 2.5 | 4.0 |
VIF(分散拡大係数) がデータサイエンスの体系の中でどこに位置するかを、 3つの異なる視点で可視化します。 同じ情報でも見方を変えると気付きが変わります。
🌐 体系階層に未登録
中心の概念から放射状に、 前提・兄弟・発展形・応用先などの関係性を矢印で結びます。 横の繋がりを見るのに最適。 ノードをドラッグ、 ホイールでズーム、 クリックで遷移。
大きな円が小さな円を包含する Circle Packing 図。 「VIF(分散拡大係数)」は緑色でハイライト。
長方形を入れ子に分割した Treemap 図。 各分野の規模感を面積で比較。 「VIF(分散拡大係数)」は緑色でハイライト。
| マップ | 分かること | こんな時に見る |
|---|---|---|
| 🔗 関係マップ | 手法間の横の関係(前提→発展→応用) | 「次に何を学べばよい?」 学習順序の判断 |
| ⭕ 包含マップ | 分類体系の入れ子構造(上位⊃下位) | 「この手法はどんなジャンルに属する?」 |
| 🌳 ツリーマップ | 分野の規模比較(面積=ボリューム) | 「データサイエンス全体の俯瞰像」 |
💡 ジャストインタイム学習のヒント:3つの視点を行き来することで、 概念を多角的に理解できます。 包含マップやツリーマップはズーム/ドリルダウンで大分類から細部まで探索できます。
多重共線性診断と対処のトピックを網羅。
SSDSE-B-2026 の数値列で重回帰を組み、 各変数の VIF を計算します。 典型的に「総人口」「世帯数」「就業者数」など強相関の人口系変数で VIF>10 が観測されます。
| 変数(典型例) | VIF 目安 | 判定 | 対処 |
|---|---|---|---|
| 総人口 | > 50 | 深刻 | どれか1つに絞る |
| 世帯数 | > 50 | 深刻 | 主成分化 |
| 就業者数 | > 30 | 深刻 | 人口で割って比率化 |
| 平均所得 | 2〜5 | 許容範囲 | そのまま使用可 |
| 持ち家比率 | 1.5〜3 | 問題なし | 独立性が高い |
1 2 3 4 5 6 7 8 9 10 11 12 | import pandas as pd from statsmodels.stats.outliers_influence import variance_inflation_factor from statsmodels.tools.tools import add_constant df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1) num = df.select_dtypes(include='number').dropna() X = add_constant(num) vif = pd.DataFrame({ '変数': X.columns, 'VIF': [variance_inflation_factor(X.values, i) for i in range(X.shape[1])] }).query("変数 != 'const'").sort_values('VIF', ascending=False) print(vif) |
summary() の Cond.No. は重要な補完情報なので、 必ず VIF と一緒に報告しましょう。variance_inflation_factor は定数項を含むデザイン行列を期待します。 add_constant を忘れると、 各変数の VIF が異常に大きく見える(中心化なしのため)。 「VIF が全部 100 超だった」とパニックになる前に、 まず const 列があるかを確認してください。 この罠は statsmodels の Stack Overflow 質問の常連です。1 2 3 4 5 6 7 8 9 10 | import pandas as pd from statsmodels.stats.outliers_influence import variance_inflation_factor from statsmodels.tools.tools import add_constant df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1) num = df.select_dtypes(include='number').dropna() X = add_constant(num) for i, c in enumerate(X.columns): if c == 'const': continue print(f'{c}: VIF = {variance_inflation_factor(X.values, i):.2f}') |
1 2 3 4 5 6 7 8 | from sklearn.linear_model import LinearRegression X = num.values cols = num.columns for j, c in enumerate(cols): others = [k for k in range(X.shape[1]) if k != j] r2 = LinearRegression().fit(X[:, others], X[:, j]).score(X[:, others], X[:, j]) vif = 1 / (1 - r2) print(f'{c}: R²={r2:.3f}, VIF={vif:.2f}') |
1 2 3 4 5 | import numpy as np R = num.corr().values vifs = np.diag(np.linalg.inv(R)) for c, v in zip(num.columns, vifs): print(f'{c}: VIF={v:.2f}') |
1 2 3 | import statsmodels.api as sm res = sm.OLS(num['持ち家比率'], add_constant(num.drop(columns=['持ち家比率']))).fit() print(res.summary()) # 末尾に Cond. No. が出る |
| 指標 | 問題視する目安 | 特徴 |
|---|---|---|
| VIF | > 10(厳しめ > 5) | 変数ごと、 1対多 |
| 許容度 1/VIF | < 0.1 | VIF の逆数 |
| 条件数 | > 30 | 全変数構造、 多対多 |
| 最小固有値 | < 0.05 | 相関行列の縮退 |
| GVIF(カテゴリ) | ^(1/2df) > 2 | ダミー群全体 |
VIF は「説明変数同士が強く相関している(多重共線性)と、 推定の分散がどれだけ膨らむか」を示す指標。 VIF=1 で無相関、 10 を超えると要注意、 5 を超えると黄信号。 SSDSE-B-2026 で A1101(総人口)・A1301(15 歳未満)・A1303(65 歳以上)を全部入れると、 A1101 ≈ A1301 + A1302 + A1303 のため VIF が爆発する。
VIF (分散拡大係数) は「回帰」カテゴリの中核概念。 初めて触れる読者は、 まずこの「🎨 直感」セクションだけ通読し、 必要になった時点で「📐 数式」「🐍 Python」「⚠️ 落とし穴」へ戻る読み方が定着しやすいです。
直感の次は、 厳密な定義を確認します。 数式は言語の一種で、 一度書き慣れれば「言葉より速く伝えられる」便利な道具。 慣れていない方は、 各記号が何を表すかを下の「🔬 記号読み解き」で 1 つずつ確認してください。
上の数式を眺めるだけでは身につかないので、 各記号がどんな役割を担っているかを言葉で押さえます。 「数式を音読する習慣」がつくと、 論文や教科書を読むスピードが体感で 2 倍ほど上がります。
数式だけでは「実感」が湧きにくいので、 実データ data/raw/SSDSE-B-2026.csv(47 都道府県 × 16 年)で 1 度手計算してみると理解が定着します。
SSDSE-B-2026 (2023) で y=L3221、 説明変数 X={A1101, A1301, A1303} を当てると、 A1101 の VIF が約 100 以上、 A1303 が 30 程度になる(A1101 = A1301 + 64 歳人口 + A1303 という会計恒等式が原因)。 A1101 を抜くか、 比率変数(A1303/A1101)に変換することで VIF を 2-3 まで下げられる。
| 都道府県 | A1101 総人口 | A1303 65 歳以上 | L3221 消費支出 |
|---|---|---|---|
| 東京都 | 14,086,000 | 3,205,000 | 341,320 |
| 神奈川県 | 9,229,000 | 2,390,000 | 306,565 |
| 大阪府 | 8,763,000 | 2,424,000 | 271,246 |
| 愛知県 | 7,477,000 | 1,923,000 | 300,221 |
| 埼玉県 | 7,331,000 | 2,012,000 | 344,092 |
| 千葉県 | 6,257,000 | 1,756,000 | 306,943 |
上記は SSDSE-B-2026 (2023) からの抜粋。 手計算で確認した値が、 後述の Python 実装で得る値と一致することを確認すると、 「数式とコードの対応関係」がクリアに見えるようになります。
公的統計(SSDSE-B-2026)を題材に、 最小限の Python コードで VIF (分散拡大係数) を動作させます。 まずはこのまま実行してみてください。
# VIF (分散拡大係数) を SSDSE-B-2026 で実行する最小コード
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023] # 2023 年のみ抽出
print(df.shape) # (47, 112)
print(df[['Prefecture','A1101','A1303','L3221']].head())
from statsmodels.stats.outliers_influence import variance_inflation_factor
import pandas as pd
import numpy as np
X = df[['A1101','A1301','A1303']].astype(float)
X = pd.DataFrame(X.values, columns=X.columns)
vifs = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])]
for c, v in zip(X.columns, vifs):
print(f'{c}: VIF={v:.2f}')
上のコードで動かない場合は、 ①必要なパッケージがインストール済みか(pip install pandas scikit-learn scipy statsmodels matplotlib)、 ②データファイルが data/raw/SSDSE-B-2026.csv に存在するか、 ③encoding='cp932' になっているかを確認してください。
VIF (分散拡大係数) を使うときに初学者が踏みやすい失敗パターン。 1 度経験してしまえば次から避けられますが、 先に知っておくに越したことはありません。
VIF は多重共線性の代表指標ですが、 説明変数行列 X の 固有値 と 条件数 に翻訳すると、 なぜ大きい VIF が「数値的不安定」を生むのかが透けて見えます。 SSDSE-B-2026 を題材に、 設計行列の構造から VIF へ降りる道筋を辿ります。
標準化された説明変数行列を Z(n×p)とすると、 OLS 推定量の分散は次の形を取ります。
Var(β̂) = σ² (ZᵀZ)⁻¹
(ZᵀZ) を固有値分解すると ZᵀZ = U Λ Uᵀ(Λ は固有値の対角行列)。 VIF が大きくなる方向 = (ZᵀZ)⁻¹ の固有値が小さい方向 = 共線性の「同じ向き」の固有ベクトル。 つまり VIF=10 以上の変数は、 設計行列のある方向で情報がほぼゼロになっていることを意味します。
import pandas as pd, numpy as np
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1)
X = df[['人口(総数)', '一般世帯数', '15歳未満人口', '65歳以上人口']].dropna()
Z = (X - X.mean()) / X.std()
# 共分散行列の固有値
eig = np.linalg.eigvalsh(Z.T @ Z / len(Z))
cond_number = np.sqrt(eig.max() / eig.min())
print('固有値:', np.round(eig, 4))
print('条件数:', round(cond_number, 1))
# 期待出力: 固有値 [≈0.0001, 0.04, 0.6, 3.35]、 条件数 ≈ 180
# → 条件数 > 30 で深刻、 100 超で危機的
| 条件数 | 最小固有値の挙動 | 推定 VIF レンジ | 解釈 |
|---|---|---|---|
| < 10 | 十分大きい | 1–3 | 健全 |
| 10–30 | 小さくなり始める | 3–10 | 要注意 |
| 30–100 | 10⁻³ オーダー | 10–50 | 深刻 |
| > 100 | 10⁻⁵ 以下 | 100+ | 推定が崩壊 |
VIF が大きい変数群を主成分(PC)に縮約すれば、 PC 同士は直交(共線性ゼロ)。 SSDSE-B-2026 の人口変数 4 つを 2 主成分に縮約すれば、 累積寄与率 99% を保ちつつ VIF=1 を実現できます。
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
Z_pc = pca.fit_transform(Z)
print('寄与率:', pca.explained_variance_ratio_.round(3))
# 主成分回帰モデルで Var(β̂) は安定化
💡 実務メモ:VIF が示すのは「数値的不安定さ」であって「予測性能の悪さ」ではない。 予測だけが目的なら Ridge 回帰のような正則化、 解釈が目的なら主成分や変数削減、 と目的別に対処法を選びましょう。
「VIF > 10 は深刻」「VIF > 5 は注意」という閾値はどこから来たのでしょうか。 実は厳密な統計的根拠というよりは、 経験的な慣習に近いものです。 SSDSE-B-2026 で実際に検証してみると、 閾値だけに頼ることの危険性が見えてきます。
| 閾値 | 出典 | 含意 |
|---|---|---|
| VIF > 10 | Marquardt (1970) | R²ⱼ > 0.9 に対応 |
| VIF > 5 | 慣習的 | R²ⱼ > 0.8 に対応 |
| VIF > 4 | O'Brien (2007) | R²ⱼ > 0.75 に対応 |
| VIF > 1 | 完全独立しか許さない | 実務的に不可能 |
O'Brien (2007) は A Caution Regarding Rules of Thumb for Variance Inflation Factors で、 VIF=10 という閾値が「絶対的な意味を持たない」と警鐘を鳴らしました。 サンプルサイズ・モデルのフィット・他の説明変数の数を考慮した相対的判断が必要です。
同じ VIF=10 でも、 n=30 と n=3000 では信頼区間の幅がまったく違います。 大標本では VIF=20 でも実用的に問題ないケースがあり、 小標本では VIF=5 でも係数推定が破綻するケースがあります。
import pandas as pd, numpy as np
import statsmodels.api as sm
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1)
df = df.dropna(subset=['人口(総数)', '一般世帯数', '15歳未満人口', '65歳以上人口'])
n = len(df)
# 信頼区間の幅は std_err × t_critical に比例
# std_err は √VIF に比例
X = sm.add_constant(df[['一般世帯数', '15歳未満人口', '65歳以上人口']])
model = sm.OLS(df['人口(総数)'], X).fit()
ci = model.conf_int()
ci_width = ci[1] - ci[0]
print(f'n = {n}')
for var in ['一般世帯数', '15歳未満人口', '65歳以上人口']:
print(f' {var}: 95% CI 幅 = {ci_width[var]:.2f}')
# n=47 では幅が広く、 同じ VIF でも n=470 なら 1/√10 ≈ 0.32 倍
| 目的 | VIF 許容範囲 | 理由 |
|---|---|---|
| 予測精度 | VIF=∞ でも可 | 予測の MSE は VIF に影響されない |
| 変数選択 | VIF < 10 推奨 | どの変数が効くかの判定が不安定 |
| 仮説検定 | VIF < 5 推奨 | 標準誤差が膨らみ第二種の過誤増 |
| 構造方程式 | VIF < 2.5 | 構造パラメータの一意性 |
多項式項や交互作用項で VIF が爆発する場合、 元の変数を 平均で引いてから(中心化)二乗・乗算すると、 VIF を劇的に下げられます。 これは数学的に等価な変換ですが、 数値計算上は別物として振る舞います。
import pandas as pd
from statsmodels.stats.outliers_influence import variance_inflation_factor
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1)
df = df.dropna(subset=['人口(総数)'])
x = df['人口(総数)'].astype(float)
# 中心化なし:x と x²
X1 = pd.DataFrame({'x': x, 'x2': x ** 2})
print('中心化なし:')
for i, c in enumerate(X1.columns):
print(f' {c}: VIF = {variance_inflation_factor(X1.values, i):.1f}')
# 中心化あり
x_c = x - x.mean()
X2 = pd.DataFrame({'x_c': x_c, 'x_c2': x_c ** 2})
print('中心化あり:')
for i, c in enumerate(X2.columns):
print(f' {c}: VIF = {variance_inflation_factor(X2.values, i):.1f}')
# 中心化なしでは VIF >> 10、 中心化ありでは VIF ≈ 1 に
💡 実務 4 か条:(1) VIF 単独で判断せず、 標本サイズと目的を併せて評価、 (2) 多項式項は必ず中心化、 (3) ダミー変数は基準カテゴリを意識した解釈、 (4) 高 VIF でも予測目的なら Ridge で正則化、 が王道です。