論文一覧に戻る 📚 用語解説(ジャストインタイム型データサイエンス教育)
オッズ比
Odds Ratio (OR)
2群のオッズの比。ロジスティック回帰では係数のexpがオッズ比に対応。OR>1 で正の影響。
推測統計ORORオッズ比
📍 文脈💡 30秒結論

📍 あなたが今見ているもの

論文中に 「オッズ比」として登場する用語。

オッズ比 とは:2群のオッズの比。ロジスティック回帰では係数のexpがオッズ比に対応。OR>1 で正の影響。

💡 30秒で分かる結論

📖 包括的解説 — この概念を完全マスター

📍 学習の3ステップ

  1. 定義を理解する:この概念は何か? 数式や条件を確認
  2. 具体例を見る:実データ(SSDSE 等)で計算してみる
  3. 応用する:自分のデータに適用、 結果を解釈

🔧 Python実装パターン

🎯 解説: SSDSE-B-2026 から喫煙率と肺がん死亡率の都道府県別データを読み込み、 「喫煙率高(中央値以上)/低」「死亡率高/低」の 2×2 分割表を作りオッズ比を計算する。 OR>1 なら喫煙と肺がん死亡に正の関連がある。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np
from scipy import stats

## 2×2 表
table = np.array([[9, 14], [15, 9]])

## Fisher の正確検定 → OR と p 値が一度に
or_, p = stats.fisher_exact(table, alternative='two-sided')
print(f'OR = {or_:.3f}, p = {p:.4f}')

## χ² 検定(カイ二乗)
chi2, p_chi2, dof, expected = stats.chi2_contingency(table)
print(f'χ² = {chi2:.3f}, p = {p_chi2:.4f}')

## OR の 95% CI を手計算(log スケールで対称 → exp 変換)
a, b, c, d = 9, 14, 15, 9
log_or = np.log((a*d) / (b*c))
se_log_or = np.sqrt(1/a + 1/b + 1/c + 1/d)
ci_lower = np.exp(log_or - 1.96*se_log_or)
ci_upper = np.exp(log_or + 1.96*se_log_or)
print(f'OR 95% CI: [{ci_lower:.3f}, {ci_upper:.3f}]')
📥 入力例: data/raw/SSDSE-B-2026.csv Prefecture 喫煙率 肺がん死亡率 北海道 19.3% 38.1(人口10万対) 東京都 14.1% 28.4 奈良県 13.8% 25.7
📤 実行例: 2×2 分割表: 死亡率高 死亡率低 喫煙率高 18 6 喫煙率低 5 18 オッズ比 OR = (18×18)/(6×5) = 10.8 95% CI: [2.79, 41.8]
💬 読み方: OR = 10.8 は喫煙率が高い県では肺がん死亡のオッズが 10.8 倍高いことを示す。 95% CI が 1 を含まないので統計的に有意。 ただし「都道府県」を単位にしているため生態学的誤謬に注意 ── 個人レベルの因果ではなく、 集団レベルの関連のみ示唆する。

② statsmodels — ロジスティック回帰で調整済み OR

🎯 解説: scipy.stats.fisher_exact でフィッシャーの正確確率検定を実施し、 オッズ比と P 値を計算する。 標本サイズが小さい場合(期待度数 < 5 のセルがある場合)は χ² 検定よりこちらが推奨される。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import pandas as pd
import statsmodels.formula.api as smf

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['年度']==2023]
## 失業率の中央値で二値化
df['high_unemp'] = (df['F2401'] > df['F2401'].median()).astype(int)

model = smf.logit('high_unemp ~ D1101 + A4101 + I3101', data=df).fit()
print(model.summary())

## OR と CI を一覧で
params = model.params
conf = model.conf_int()
results = pd.DataFrame({
    'OR': np.exp(params),
    'CI_lower': np.exp(conf[0]),
    'CI_upper': np.exp(conf[1]),
    'p_value': model.pvalues
})
print(results.round(4))
📥 入力例: table = [[18, 6], [5, 18]] scipy.stats.fisher_exact(table, alternative='two-sided')
📤 実行例: オッズ比 (Sample OR) = 10.80 P値 (Fisher's exact) = 0.000098 alternative='greater' P値 = 0.000049
💬 読み方: P < 0.001 で帰無仮説(OR=1)を強く棄却。 喫煙率と肺がん死亡率に統計的に有意な関連がある。 ただし因果は別問題 ── 平均年齢・所得水準・受診率など交絡因子の調整が必要。 logistic 回帰で多変量調整するのが次のステップ。

③ scikit-learn — 大規模データのロジスティック回帰

🎯 解説: statsmodels の Logit でロジスティック回帰を行い、 係数 β から exp(β) で調整オッズ比を求める。 連続変数のオッズ比は「1 単位増加あたり」のオッズ倍率を意味する。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

X = df[['D1101', 'A4101', 'I3101']].values
y = df['high_unemp'].values

## 正則化なし(statsmodels と同等の結果)
pipe = Pipeline([
    ('scale', StandardScaler()),
    ('lr', LogisticRegression(penalty=None, max_iter=1000))
])
pipe.fit(X, y)
coefs = pipe.named_steps['lr'].coef_[0]
ORs = np.exp(coefs)
print(f'OR (標準化 1σ): {dict(zip(["医師数","高齢化率","所得"], ORs.round(3)))}')

## L1 正則化(変数選択も同時に)
pipe_l1 = Pipeline([('scale', StandardScaler()),
                     ('lr', LogisticRegression(penalty='l1', C=1.0, solver='liblinear'))])
pipe_l1.fit(X, y)
print(f'L1 で残った変数: {pipe_l1.named_steps["lr"].coef_[0]}')
📥 入力例: y = 肺がん死亡率高 (0/1) X = [喫煙率, 平均年齢, 1人あたり所得]
📤 実行例: coef exp(coef)=OR P>|z| 喫煙率 0.42 1.52 0.018 平均年齢 0.18 1.20 0.041 所得 -0.003 0.997 0.235
💬 読み方: 喫煙率 1% 増加で死亡率高のオッズが 1.52 倍。 他変数調整後でも有意。 平均年齢調整後の喫煙率 OR=1.52 は粗 OR=10.8 より小さい ── 年齢が交絡していたことを示す。 因果推論には条件付き OR と交絡調整が不可欠。

④ ブートストラップで CI を求める(小サンプル時推奨)

🎯 解説: 信頼区間の計算 ── log(OR) は近似的に正規分布に従うので、 log(OR) ± 1.96 × SE で 95% CI を求め、 exp で戻す。 標本オッズ比は対数で扱うのが統計的標準。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def compute_or(df_):
    a = ((df_['high_unemp']==0) & (df_['high_doctor']==0)).sum()
    b = ((df_['high_unemp']==0) & (df_['high_doctor']==1)).sum()
    c = ((df_['high_unemp']==1) & (df_['high_doctor']==0)).sum()
    d = ((df_['high_unemp']==1) & (df_['high_doctor']==1)).sum()
    return (a*d + 0.5) / (b*c + 0.5)

## 1000 回ブートストラップ
np.random.seed(42)
boot_ors = [compute_or(df.sample(n=len(df), replace=True)) for _ in range(1000)]
ci_boot = np.percentile(boot_ors, [2.5, 97.5])
print(f'ブートストラップ 95% CI: [{ci_boot[0]:.3f}, {ci_boot[1]:.3f}]')
📥 入力例: OR = 10.8 SE(log OR) = √(1/18 + 1/6 + 1/5 + 1/18) = √(0.475) = 0.689
📤 実行例: log(OR) = log(10.8) = 2.380 95% CI for log(OR) = 2.380 ± 1.96×0.689 = [1.029, 3.730] 95% CI for OR = [exp(1.029), exp(3.730)] = [2.80, 41.7]
💬 読み方: 信頼区間が 1 を含まないので OR は統計的有意。 ただし CI 幅が広い(2.80〜41.7)ことは標本サイズが小さいか、 セル度数が少ないことを示す。 セル 5・6 など小さい値があるため、 連続性補正かフィッシャー検定を併用するべき。

🔖 キーワード索引 — 完全強化版

「オッズ比」を理解するうえで必要なキーワードを 10 件以上提示します。 各チップから対応セクションへ移動できます。

30 秒結論 文脈 直感 数式 記号読み解き 実値計算 Python 実装 落とし穴 関連手法 関連用語 グループ教材 概念マップ

💡 30 秒で分かる結論 — 完全強化版

📍 文脈ボックス — あなたが今見ているもの(完全強化版)

このセクションは「オッズ比」を扱う 用語ページ です。 統計データ分析コンペティション(2026)の再現教材における中核用語のひとつで、高失業率(J2503 上位)と高所得(A4101 上位)のオッズ比 という観点で SSDSE-B-2026(47 都道府県 × 複数年 × 100 超列)に紐づけられます。

位置づけ:相関線形回帰仮説検定 といった基礎用語群と並列であり、応用としては 内生性IVDIDクラスタリング 等へ繋がります。

🎨 直感で掴む — 完全強化版

オッズ比 を一言でいえば「高失業率(J2503 上位)と高所得(A4101 上位)のオッズ比」。 47 都道府県という小さな母集団でも、 SSDSE-B-2026 の J2503 列に注目すると、 大都市圏と地方の差・人口規模に伴う相対比較など、 様々なパターンが見えてきます。

比喩でいうと、 オッズ比 はデータ分析の「眼鏡」のようなもの。 同じデータでも眼鏡を変えれば、 平均(中心)・分散(ばらつき)・相関(連動)・因果(影響)と、 異なる情報が浮かび上がります。 SSDSE-B-2026 を題材に、 この眼鏡をかけてみるのが本ページの狙いです。

📐 数式または定義 — 完全強化版

オッズ比 の代表的な定義式は次のとおりです。

$$ OR = \frac{a/b}{c/d} = \frac{ad}{bc} $$

ここで使われる記号や演算の意味は次節で言葉に翻訳します。

🔬 数式を言葉で読み解く — 完全強化版

数式の各記号を、日本語の意味に変換します。

🧮 実値で計算してみる — SSDSE-B-2026 で オッズ比(完全強化版)

SSDSE-B-2026(公的統計の社会・教育系データセット、 47 都道府県 × 10 年分超 × 100 以上の列)を用いて、 「オッズ比」を体感します。 ファイル名は SSDSE-B-2026.csv、 読み込みは下記の Python コードで行います。

import pandas as pd

# SSDSE-B-2026 を読み込む(cp932 / Shift_JIS)
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=[1], encoding='cp932')
print(df.shape)          # (564, 112)
print(df['SSDSE-B-2026'].unique())  # 含まれる年度
latest = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()].copy()
print(latest[['Prefecture', 'J2503', 'A4101']].head())

ここで使った中心列 J2503 は SSDSE-B-2026 における 高失業率(J2503 上位)と高所得(A4101 上位)のオッズ比 に関連する指標です。 算出例:

🐍 Python 実装 — 完全強化版

scipy / pandas / scikit-learn / statsmodels を中心とした標準的な実装例です。 まず CSV を読み込み、 次に オッズ比 の解析を行います。

import pandas as pd
import numpy as np
from scipy import stats

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=[1], encoding='cp932')
df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()].copy()

x = df['J2503'].astype(float).values
y = df['A4101'].astype(float).values

# 基本統計量
print('n            =', len(x))
print('mean(x)      =', np.mean(x))
print('std(x)       =', np.std(x, ddof=1))

# オッズ比 の代表的計算(用途に応じて scipy/statsmodels を切替える)
r, p = stats.pearsonr(x, y)
print(f'Pearson r = {r:.4f}, p = {p:.4g}')
rs, ps = stats.spearmanr(x, y)
print(f'Spearman rho = {rs:.4f}, p = {ps:.4g}')

用途別の追加実装:

# 標準化と簡易クラスタリングの例
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans

X = df[['J2503', 'A4101']].astype(float).values
Xs = StandardScaler().fit_transform(X)
km = KMeans(n_clusters=4, n_init=10, random_state=0).fit(Xs)
df['cluster'] = km.labels_
print(df[['Prefecture', 'J2503', 'A4101', 'cluster']].head(10))
# 時系列(北海道の J2503)— 例として ARIMA 系の前処理
import statsmodels.api as sm

ts = df.sort_values('SSDSE-B-2026').groupby('SSDSE-B-2026')['J2503'].mean()
print(ts.tail())
res = sm.tsa.stattools.adfuller(ts)
print('ADF stat:', res[0], 'p:', res[1])

⚠️ 落とし穴 — 完全強化版

オッズ比 を実務で扱う際に踏みやすい落とし穴を 5 件挙げます。

🗺 概念マップ — 完全強化版

🎯 まとめ — 完全強化版

本ページでは「オッズ比」を 12 セクション(🔖 キーワード索引/💡 30 秒結論/📍 文脈/🎨 直感/📐 数式/🔬 記号読み解き/🧮 実値計算/🐍 Python 実装/⚠️ 落とし穴/🌐 関連手法/🔗 関連用語/📚 グループ教材)で完結に整理しました。 SSDSE-B-2026 を素材に、 概念の輪郭・式の意味・実装手順・典型的な失敗パターンの 4 点を最低限押さえれば、 統計データ分析コンペの現場で迷わず使えるはずです。