列联表

statsmodels 支持多种方法来分析列联表,包括用于评估独立性、对称性、同质性以及用于处理来自分层总体的一组表格的方法。

此处描述的方法主要用于双向表。可以使用对数线性模型分析多维表。statsmodels 目前还没有专门用于对数线性建模的 API,但是 statsmodels.genmod.GLM 中的泊松回归可用于此目的。

列联表是一个多维表,它描述了一个数据集,其中每个观察值属于几个变量中的每一个的某个类别。例如,如果有两个变量,一个有 \(r\) 个水平,另一个有 \(c\) 个水平,那么我们就有 \(r \times c\) 个列联表。该表可以用落在表中某个单元格的观察次数来描述,例如 \(T_{ij}\) 是具有第一个变量的水平 \(i\) 和第二个变量的水平 \(j\) 的观察次数。请注意,每个变量都必须具有有限数量的水平(或类别),这些水平可以是有序的也可以是无序的。在不同的上下文中,定义列联表轴的变量可能被称为 **分类变量** 或 **因子变量**。它们可以是 **名义的**(如果它们的水平是无序的)或 **序数的**(如果它们的水平是有序的)。

列联表的底层总体用 **分布表** \(P_{i, j}\) 描述。\(P\) 的元素是概率,\(P\) 中所有元素的总和为 1。用于分析列联表的方法使用 \(T\) 中的数据来了解 \(P\) 的属性。

代码 statsmodels.stats.Table 是用于处理列联表的两个最基本的类之一。我们可以直接从任何包含列联表单元格计数的矩形类数组对象创建 Table 对象。

In [1]: import numpy as np

In [2]: import pandas as pd

In [3]: import statsmodels.api as sm

In [4]: df = sm.datasets.get_rdataset("Arthritis", "vcd").data

In [5]: df.fillna({"Improved":"None"}, inplace=True)

In [6]: tab = pd.crosstab(df['Treatment'], df['Improved'])

In [7]: tab = tab.loc[:, ["None", "Some", "Marked"]]

In [8]: table = sm.stats.Table(tab)

或者,我们可以传入原始数据,让 Table 类为我们构建单元格计数数组。

In [9]: data = df[["Treatment", "Improved"]]

In [10]: table = sm.stats.Table.from_data(data)

独立性

**独立性** 是行因子和列因子独立发生的属性。**关联** 是缺乏独立性。如果联合分布是独立的,它可以写成行和列边缘分布的外积

\[P_{ij} = \sum_k P_{ij} \cdot \sum_k P_{kj} \quad \text{for all} \quad i, j\]

我们可以获得观察数据的最佳拟合独立分布,然后查看残差,这些残差识别出最强烈地违反独立性的特定单元格。

In [11]: print(table.table_orig)
Improved   Marked  None  Some
Treatment                    
Placebo         7    29     7
Treated        21    13     7

In [12]: print(table.fittedvalues)
Improved      Marked  None      Some
Treatment                           
Placebo    14.333333  21.5  7.166667
Treated    13.666667  20.5  6.833333

In [13]: print(table.resid_pearson)
Improved     Marked      None      Some
Treatment                              
Placebo   -1.936992  1.617492 -0.062257
Treated    1.983673 -1.656473  0.063758

在这个例子中,与来自行和列独立的总体的样本相比,我们在安慰剂/无改善和治疗/明显改善单元格中观察到太多观察值,而在安慰剂/明显改善和治疗/无改善单元格中观察到太少观察值。这反映了治疗的明显益处。

如果表的行和列是无序的(即为名义因子),那么正式评估独立性的最常见方法是使用皮尔逊的 \(\chi^2\) 统计量。查看单元格对 \(\chi^2\) 统计量的贡献通常很有用,以了解依赖性的证据来自何处。

In [14]: rslt = table.test_nominal_association()

In [15]: print(rslt.pvalue)
0.0014626434089526352

In [16]: print(table.chi2_contribs)
Improved     Marked      None      Some
Treatment                              
Placebo    3.751938  2.616279  0.003876
Treated    3.934959  2.743902  0.004065

对于具有有序行和列因子的表,我们可以使用 **线性对线性** 关联检验来获得更多关于尊重排序的备择假设的效力。线性对线性关联检验的检验统计量为

\[\sum_k r_i c_j T_{ij}\]

其中 \(r_i\)\(c_j\) 是行和列得分。通常,这些得分被设置为序列 0、1、…。这将给出“科克伦-阿米塔奇趋势检验”。

In [17]: rslt = table.test_ordinal_association()

In [18]: print(rslt.pvalue)
0.023644578093923983

我们可以通过构建一系列 \(2\times 2\) 表格并计算它们的优势比来评估 \(r\times x\) 表格中的关联性。有两种方法可以做到这一点。**局部优势比** 从相邻的行和列类别构建 \(2\times 2\) 表格。

In [19]: print(table.local_oddsratios)
Improved     Marked      None  Some
Treatment                          
Placebo    0.149425  2.230769   NaN
Treated         NaN       NaN   NaN

In [20]: taloc = sm.stats.Table2x2(np.asarray([[7, 29], [21, 13]]))

In [21]: print(taloc.oddsratio)
0.14942528735632185

In [22]: taloc = sm.stats.Table2x2(np.asarray([[29, 7], [13, 7]]))

In [23]: print(taloc.oddsratio)
2.230769230769231

**累积优势比** 通过在每个可能的点对行和列因子进行二分法来构建 \(2\times 2\) 表格。

In [24]: print(table.cumulative_oddsratios)
Improved     Marked      None  Some
Treatment                          
Placebo    0.185185  1.058824   NaN
Treated         NaN       NaN   NaN

In [25]: tab1 = np.asarray([[7, 29 + 7], [21, 13 + 7]])

In [26]: tacum = sm.stats.Table2x2(tab1)

In [27]: print(tacum.oddsratio)
0.18518518518518517

In [28]: tab1 = np.asarray([[7 + 29, 7], [21 + 13, 7]])

In [29]: tacum = sm.stats.Table2x2(tab1)

In [30]: print(tacum.oddsratio)
1.0588235294117647

马赛克图是一种图形方法,用于非正式地评估双向表中的依赖性。

In [31]: from statsmodels.graphics.mosaicplot import mosaic

In [32]: fig, _ = mosaic(data, index=["Treatment", "Improved"])

对称性和同质性

**对称性** 是 \(P_{i, j} = P_{j, i}\) 对于每个 \(i\)\(j\) 的属性。**同质性** 是行因子和列因子的边缘分布相同的属性,这意味着

\[\sum_j P_{ij} = \sum_j P_{ji} \forall i\]

请注意,为了使这些属性适用,表 \(P\)(和 \(T\))必须是正方形的,并且行和列类别必须相同,并且必须以相同的顺序出现。

为了说明这一点,我们加载一个数据集,创建一个列联表,并计算行和列边缘。代码 Table 类包含用于分析 \(r \times c\) 列联表的方法。下面加载的数据集包含对人们左右眼视力评估的结果。我们首先加载数据并创建一个列联表。

In [33]: df = sm.datasets.get_rdataset("VisualAcuity", "vcd").data

In [34]: df = df.loc[df.gender == "female", :]

In [35]: tab = df.set_index(['left', 'right'])

In [36]: del tab["gender"]

In [37]: tab = tab.unstack()

In [38]: tab.columns = tab.columns.get_level_values(1)

In [39]: print(tab)
right     1     2     3    4
left                        
1      1520   234   117   36
2       266  1512   362   82
3       124   432  1772  179
4        66    78   205  492

接下来,我们从列联表创建一个 SquareTable 对象。

In [40]: sqtab = sm.stats.SquareTable(tab)

In [41]: row, col = sqtab.marginal_probabilities

In [42]: print(row)
right
1    0.255049
2    0.297178
3    0.335295
4    0.112478
dtype: float64

In [43]: print(col)
right
1    0.264277
2    0.301725
3    0.328474
4    0.105524
dtype: float64

代码 summary 方法打印对称性和同质性检验过程的结果。

In [44]: print(sqtab.summary())
            Statistic P-value DF
--------------------------------
Symmetry       19.107   0.004  6
Homogeneity    11.957   0.008  3
--------------------------------

如果我们在一个名为 data 的数据框中具有单个案例记录,我们也可以通过使用 SquareTable.from_data 类方法传入原始数据来执行相同的分析。

sqtab = sm.stats.SquareTable.from_data(data[['left', 'right']])
print(sqtab.summary())

单个 2x2 表格

代码 sm.stats.Table2x2 类中提供了几种用于处理单个 2x2 表格的方法。代码 summary 方法显示了表格行和列之间关联性的几种度量。

In [45]: table = np.asarray([[35, 21], [25, 58]])

In [46]: t22 = sm.stats.Table2x2(table)

In [47]: print(t22.summary())
               Estimate   SE   LCB   UCB  p-value
-------------------------------------------------
Odds ratio        3.867       1.890 7.912   0.000
Log odds ratio    1.352 0.365 0.636 2.068   0.000
Risk ratio        2.075       1.411 3.051   0.000
Log risk ratio    0.730 0.197 0.345 1.115   0.000
-------------------------------------------------

请注意,风险比是不对称的,因此如果分析转置后的表格,将会获得不同的结果。

In [48]: table = np.asarray([[35, 21], [25, 58]])

In [49]: t22 = sm.stats.Table2x2(table.T)

In [50]: print(t22.summary())
               Estimate   SE   LCB   UCB  p-value
-------------------------------------------------
Odds ratio        3.867       1.890 7.912   0.000
Log odds ratio    1.352 0.365 0.636 2.068   0.000
Risk ratio        2.194       1.436 3.354   0.000
Log risk ratio    0.786 0.216 0.362 1.210   0.000
-------------------------------------------------

分层 2x2 表格

分层发生在当我们有一组由相同的行和列因子定义的列联表时。在下面的示例中,我们有一组 2x2 表格,反映了中国几个地区的吸烟和肺癌的联合分布。有可能所有表格都具有共同的优势比,即使边缘概率在不同层之间变化。‘Breslow-Day’ 过程测试数据是否与共同的优势比一致。它在下面显示为 常数 OR 检验。Mantel-Haenszel 过程测试此共同的优势比是否等于 1。它在下面显示为 OR=1 检验。还可以估计共同的优势比和风险比,并获得它们的置信区间。代码 summary 方法显示所有这些结果。可以从类方法和属性中获得单个结果。

In [51]: data = sm.datasets.china_smoking.load_pandas()

In [52]: mat = np.asarray(data.data)

In [53]: tables = [np.reshape(x.tolist(), (2, 2)) for x in mat]

In [54]: st = sm.stats.StratifiedTable(tables)

In [55]: print(st.summary())
                   Estimate   LCB    UCB 
-----------------------------------------
Pooled odds           2.174   1.984 2.383
Pooled log odds       0.777   0.685 0.868
Pooled risk ratio     1.519              
                                         
                 Statistic P-value 
-----------------------------------
Test of OR=1       280.138   0.000 
Test constant OR     5.200   0.636 
                       
-----------------------
Number of tables    8  
Min n             213  
Max n            2900  
Avg n            1052  
Total n          8419  
-----------------------

模块参考

Table(table[, shift_zeros])

一个双向列联表。

Table2x2(table[, shift_zeros])

可以在 2x2 列联表上执行的分析。

SquareTable(table[, shift_zeros])

用于分析平方列联表的方法。

StratifiedTable(tables[, shift_zeros])

对一系列 2x2 列联表的分析。

mcnemar(table[, exact, correction])

McNemar 同质性检验。

cochrans_q(x[, return_object])

Cochran Q 检验,用于检验二项式比例是否相同。

参见

Scipy 提供了几个用于分析列联表的函数,包括 Fisher 精确检验,目前 statsmodels 中尚未提供。


最后更新时间:2024 年 10 月 3 日