投資のためのデータサイエンス

個人の投資活動に役立つデータ分析にまつわる話題を綴ります。

Pythonで学ぶ統計学(9): クロス集計表の分析(改訂版)

調査対象の個人の性別や商品の購入/非購入などの「カテゴリカル変数」が複数あってその間の関連を知りたい場合に「クロス集計表」を作成します。今、 N 個の個体を特性 A,B に関して分類してカウントした時に、特性 A_i かつ特性 B_j に分類された個体数が c_{ij} であるとした時、クロス集計表は以下のように表されます。

 \begin{array}{lccccc|c} \hline

& B_1 & B_2 & \cdots & B_t & 計 \\ \hline

A_1 & c_{11} & c_{12} & \cdots & c_{1t} & m_1 \\

A_2 & c_{21} & c_{22} & \cdots & c_{2t} & m_2 \\

\vdots & \vdots & \vdots & & \vdots & \vdots \\

A_s & c_{s1} & c_{s2} & \cdots & c_{st} & m_s \\ \hline

計 & n_1 & n_2 & \cdots & n_t & N \\ \hline

\end{array}

特性 A と特性 B が独立であるという帰無仮説の検定(独立性の検定)の統計量は以下で表されます

 \chi ^2=\sum _{i=1}^{s} \sum _{j=1}^{t} \frac{(c_{ij}-d_{ij})^2}{d_{ij}}, \ \ \ d_{ij}=\frac {m_i n_j}{N}

ここで d_{ij} は特性 A と特性 B が独立であるとした場合の期待度数です。

一般に、期待度数 d_{ij} が5より小さいときは、隣接した行や列を加えあわせて、期待度数を5以上にしてから検定を行うべきであるとされています。

また N が万のオーダーやそれ以上の大規模データの場合はほとんどのケースで独立であるという帰無仮説は棄却されます。これはデータが多くなるほど「ぴったり独立」という状態からの少しのズレでも検出してしまうからです。そこで、データのサイズに依存せずに2つの特性間の関連性を表すのが「クラメールの連関係数(クラメールのV)」で、以下で表されます。

 V = \sqrt{\frac{\chi^2}{N \times \min (s-1,t-1)}}

以下のpythonコードは、食の好みに関するデータを読み込んで、年齢のカテゴリー化を行い、年齢と食の好みのクロス集計表を表示して、独立性のχ二乗検定及びクラメールのV(修正版)を計算しています。

# クロス集計表分析
# データ出典 https://www.kaggle.com/vijayashreer/food-preferences
import pandas as pd
import numpy as np
import scipy as sp
import matplotlib.pyplot as plt
from matplotlib import rcParams
rcParams['font.family']='MS Gothic'
from scipy.stats import chi2_contingency
# クラメールのV(修正版)の計算
def cramers_corrected_stat(confusion_matrix):
    """ calculate Cramers V statistic for categorial-categorial association.
    uses correction from Bergsma and Wicher,
    Journal of the Korean Statistical Society 42 (2013): 323-328
    """
    chi2 = chi2_contingency(confusion_matrix)[0]
    n = confusion_matrix.sum().sum()
    phi2 = chi2/n
    r,k = confusion_matrix.shape
    phi2corr = max(0, phi2 - ((k-1)*(r-1))/(n-1))
    rcorr = r - ((r-1)**2)/(n-1)
    kcorr = k - ((k-1)**2)/(n-1)
    return np.sqrt(phi2corr / min( (kcorr-1), (rcorr-1)))
# データ準備(年齢はカテゴリー化)
df1 = pd.read_csv('Food_Preference.csv')
func_age = lambda x:'age0-19' if x<20 else ('age20-29' if x<30 else \
('age30-39' if x<40 else ('age40-49' if x<50 else 'age50over')))
df2 = df1.assign(AgeGrp=df1['Age'].apply(func_age))
# クロス集計表と統計量の表示
dfcrs=pd.crosstab(df2['Food'],df2['AgeGrp'])
fig=plt.figure()
ax=fig.add_subplot(111)
ax.axis('off')
tbl = ax.table(cellText=dfcrs.values,bbox=[0,0,1,1],colLabels=dfcrs.columns,\
rowLabels=dfcrs.index)
plt.tight_layout()
plt.show()
chi2, pval, dof, expctd = chi2_contingency(dfcrs,correction=False)
print('chi2={:.3f}, pval={:.3e}, dof={}'.format(chi2,pval,dof))
print('cramersV={:.3f}'.format(cramers_corrected_stat(dfcrs)))
# (output)
#chi2=23.703, pval=9.162e-05, dof=4
#cramersV=0.262