1. 什么是Cramér’s V 相关系数
在统计中,Cramér’s V (又称为Cramér’s phi,表示为φc) 是一个衡量两个
分类变量之间关联的度量,它是一个介于0和+1(包括)之间的值, 0表示两个变量无关,1表示完全相关。它是基于Pearson’s chi-squared statistic(皮尔森的卡方统计),由Harald Cramér于1946年发表的。
所以在介绍Cramér’s V 相关系数之前,我们先来了解一下皮尔森的卡方统计。
2. 皮尔森的卡方统计(Pearson’s chi-squared statistic)
2.1 皮尔森的卡方检验(Pearson’s chi-squared test)
是最有名卡方检验之一,最早由卡尔·皮尔逊在1900年发表。
理论不多说了,我们通过一个例子来说明什么是皮尔森的卡方检验。
以下数据来自一个研究关于自闭症和疫苗的关联性的数据。
–表格1–
有自闭症 | 无自闭症 | 总共 | |
打疫苗 | 621 | 440,034 | 440,655 |
不打疫苗 | 117 | 96,531 | 96,648 |
总共 | 738 | 536,565 | 537,303 |
在打疫苗的440,655人中,有自闭症的人有621人。在不打疫苗的96,648中,有自闭症的有117人。那么从这一组数据中,我们能否看出疫苗和自闭症是否有关联呢?
第一步
使用皮尔森的卡方检验,我们首先假设:
H0: 疫苗和自闭症并不关联
H1: 疫苗和自闭症并有关联
如果H0假设成立,那么在打疫苗和不打疫苗的人群中,有自闭症的人数比例应该是一致的。
我们由第三行数据得到,537,303人中有738人患有自闭症,得到患有自闭症的总统比例是。大约是千分之一多一点。
第二部
如果不考虑样本偏差,那么在打疫苗和不打疫苗的人群中,这个比例是一致的,我们就通过计算得到理想状态下打疫苗和不打疫苗两个群体中的自闭症患者人数。
–表格2–
期望值 | 有自闭症 | 无自闭症 | 总共 |
打疫苗 | 440,655*(738/537,303) =602.8 | 440,655*(536,565/537,303)=440,049.7 | 440,655 |
不打疫苗 | 96,648*(738/537,303)=132.7 | 96,648*(536,565/537,303)=96,515.3 | 96,648 |
总共 | 738 | 536,565 | 537,303 |
第三步
我们将上面的的计算结果和实际实验数据进行比较,对每个单元格计算出以下统计量,然后对它们求和。
这个结果服从于分布, 我们可以通过查表得知
根据经验值,一般拒绝域取0.05,0.1309>0.05, H0为真。
因此,我们认为没有明显证据证明疫苗和自闭症有联系。
3. Cramér’s V 相关系数
3.1 自由度
Cramér’s V 相关系数是统计量除以卡方检验中的自由度。
所谓自由度与卡方检验中的行和列数有关,如果我们仔细观察上面的表格2,我们会发现其实四个单元格中如果一个单元格的值已经确定,则其他三个单元格的值就都确定了,我们称之自由度为1。
对于卡方检验中的行列的表格其自由度为:
3.2 计算Cramér’s V 相关系数
在算得卡方统计量的基础上再进行计算的。
说明
- 统计样本量是指总体样本条数,以上面卡方统计的例子为例,这个统计样本量是537,303。
- 卡方统计的n行m列数中较小一个是指卡方统计中使用的这个表格的行列数,在上面例子中,行数是2,列数也是2,那么min(2,2)=2,那min(2,2)-1=1
所以,以上面例子为例,则计算中的分子是537,303*1。
因此,打疫苗和自闭症之间的相关系数是。
4. Python代码
直接奉上Python代码。
def calculate_caremers_v(df, column_a, column_b):
"""
calculate carmer v for the 2 input columns in dataframe
:param df: Pandas dataframe object
:param column_a: 1st column to study
:param column_b: 2nd column to study
:return: Pandas dataframe object with the duplicated recorders removed.
"""
if column_a not in df.columns:
print("the input columne %s doesn't exit in the dataframe."%column_a)
return None
elif column_b not in df.columns:
print("the input columne %s doesn't exit in the dataframe."%column_b)
return None
else:
cross_tb = pd.crosstab(index = df[column_a], columns = df[column_b])
np_tb = cross_tb.to_numpy()
min_row_column = min(np_tb.shape[0],np_tb.shape[1])
colume_sum = np_tb.sum(axis = 0)
row_sum = np_tb.sum(axis = 1)
total_sum = np_tb.sum()
np_mid = np.matmul(row_sum.reshape(len(row_sum),1),
colume_sum.reshape(1,len(colume_sum)))/total_sum
new_tb = np.divide(np.power((np_tb-np_mid),np.array([2])),
np_mid)
return new_tb.sum()/(total_sum*(min_row_column-1))