7.2 频数表和列联表

在本节中,我们将着眼于类别型变量的频数表和列联表,以及相应的独立性检验、相关性的度量、图形化展示结果的方法。我们除了使用基础安装中的函数,还将连带使用vcd包和gmodels包中的函数。下面的示例中,假设A、B和C代表类别型变量。

本节中的数据来自vcd包中的Arthritis数据集。这份数据来自Kock & Edward (1988),表示了一项风湿性关节炎新疗法的双盲临床实验的结果。前几个观测是这样的:

  1. > library(vcd)
  2. > head(Arthritis)
  3. ID Treatment Sex Age Improved
  4. 1 57 Treated Male 27 Some
  5. 2 46 Treated Male 29 None
  6. 3 77 Treated Male 30 None
  7. 4 17 Treated Male 32 Marked
  8. 5 36 Treated Male 46 Marked
  9. 6 23 Treated Male 58 Marked

治疗情况(安慰剂治疗、用药治疗)、性别(男性、女性)和改善情况(无改善、一定程度的改善、显著改善)均为类别型因子1。下一节中,我们将使用此数据创建频数表和列联表(交叉的分类)。

1 分别对应数据中的变量TreatmentPlaceboTreated)、SexMaleFemale)和ImprovedNoneSomeMarked)。——译者注

7.2.1 生成频数表

R中提供了用于创建频数表和列联表的若干种方法。其中最重要的函数已列于表7-1中。 表7-1 用于创建和处理列联表的函数

函  数 描  述
table(var1, var2, …, varN) 使用 N 个类别型变量(因子)创建一个 N 维列联表
xtabs(formula, data) 根据一个公式和一个矩阵或数据框创建一个 N 维列联表
prop.table(table, margins) margins定义的边际列表将表中条目表示为分数形式
margin.table(table, margins) margins定义的边际列表计算表中条目的和
addmargins(table, margins) 将概述边margins(默认是求和结果)放入表中
ftable(table) 创建一个紧凑的“平铺”式列联表

接下来,我们将逐个使用以上函数来探索类别型变量。我们首先考察简单的频率表,接下来是二维列联表,最后是多维列联表。第一步是使用table()xtabs()函数创建一个表,然后使用其他函数处理它。

  • 一维列联表

可以使用table()函数生成简单的频数统计表。示例如下:

  1. > mytable <- with(Arthritis, table(Improved))
  2. > mytable
  3. Improved
  4. None Some Marked
  5. 42 14 28

可以用prop.table()将这些频数转化为比例值:

  1. > prop.table(mytable)
  2. Improved
  3. None Some Marked
  4. 0.500 0.167 0.333

或使用prop.table()*100转化为百分比:

  1. > prop.table(mytable)*100
  2. Improved
  3. None Some Marked
  4. 50.0 16.7 33.3

这里可以看到,有50%的研究参与者获得了一定程度或者显著的改善(16.7 + 33.3)。

  • 二维列联表

对于二维列联表,table()函数的使用格式为:

  1. mytable A, B)

其中的A是行变量,B是列变量。除此之外,xtabs()函数还可使用公式风格的输入创建列联表,格式为:

  1. mytable <- xtabs(~ A + B, data=mydata)

其中的mydata是一个矩阵或数据框。总的来说,要进行交叉分类的变量应出现在公式的右侧(即~符号的右方),以+作为分隔符。若某个变量写在公式的左侧,则其为一个频数向量(在数据已经被表格化时很有用)。

对于Arthritis数据,有:

  1. > mytable <- xtabs(~ Treatment+Improved, data=Arthritis)
  2. > mytable
  3. Improved
  4. Treatment None Some Marked
  5. Placebo 29 7 7
  6. Treated 13 7 21

你可以使用margin.table()prop.table()函数分别生成边际频数和比例。行和与行比例可以这样计算:

  1. > margin.table(mytable, 1)
  2. Treatment
  3. Placebo Treated
  4. 43 41
  5. > prop.table(mytable, 1)
  6. Improved
  7. Treatment None Some Marked
  8. Placebo 0.674 0.163 0.163
  9. Treated 0.317 0.171 0.512

下标1指代table()语句中的第一个变量。观察表格可以发现,与接受安慰剂的个体中有显著改善的16%相比,接受治疗的个体中的51%的个体病情有了显著的改善。

列和与列比例可以这样计算:

  1. > margin.table(mytable, 2)
  2. Improved
  3. None Some Marked
  4. 42 14 28
  5. > prop.table(mytable, 2)
  6. Improved
  7. Treatment None Some Marked
  8. Placebo 0.690 0.500 0.250
  9. Treated 0.310 0.500 0.750

这里的下标2指代table()语句中的第二个变量。

各单元格所占比例可用如下语句获取:

  1. > prop.table(mytable)
  2. Improved
  3. Treatment None Some Marked
  4. Placebo 0.3452 0.0833 0.0833
  5. Treated 0.1548 0.0833 0.2500

你可以使用addmargins()函数为这些表格添加边际和。例如,以下代码添加了各行的和与各列的和:

  1. > addmargins(mytable)
  2. Improved
  3. Treatment None Some Marked Sum
  4. Placebo 29 7 7 43
  5. Treated 13 7 21 41
  6. Sum 42 14 28 84
  7. > addmargins(prop.table(mytable))
  8. Improved
  9. Treatment None Some Marked Sum
  10. Placebo 0.3452 0.0833 0.0833 0.5119
  11. Treated 0.1548 0.0833 0.2500 0.4881
  12. Sum 0.5000 0.1667 0.3333 1.0000

在使用addmargins()时,默认行为是为表中所有的变量创建边际和。作为对照:

  1. > addmargins(prop.table(mytable, 1), 2)
  2. Improved
  3. Treatment None Some Marked Sum
  4. Placebo 0.674 0.163 0.163 1.000
  5. Treated 0.317 0.171 0.512 1.000

仅添加了各行的和。类似地,

  1. > addmargins(prop.table(mytable, 2), 1)
  2. Improved
  3. Treatment None Some Marked
  4. Placebo 0.690 0.500 0.250
  5. Treated 0.310 0.500 0.750
  6. Sum 1.000 1.000 1.000

添加了各列的和。在表中可以看到,有显著改善患者中的25%是接受安慰剂治疗的。

注意 table()函数默认忽略缺失值(NA)。要在频数统计中将NA视为一个有效的类别,请设定参数useNA="ifany"

使用gmodels包中的CrossTable()函数是创建二维列联表的第三种方法。CrossTable()函数仿照SAS中PROC FREQ或SPSS中CROSSTABS的形式生成二维列联表。示例参阅代码清单7-11。

代码清单7-11 使用CrossTable生成二维列联表

  1. > library(gmodels)
  2. > CrossTable(Arthritis$Treatment, Arthritis$Improved)
  3.  
  4. Cell Contents
  5. |-------------------------|
  6. | N |
  7. | Chi-square contribution |
  8. | N / Row Total |
  9. | N / Col Total |
  10. | N / Table Total |
  11. |-------------------------|
  12.  
  13. Total Observations in Table: 84
  14.  
  15. | Arthritis$Improved
  16. Arthritis$Treatment | None | Some | Marked | Row Total |
  17. --------------------|-----------|-----------|-----------|-----------|
  18. Placebo | 29 | 7 | 7 | 43 |
  19. | 2.616 | 0.004 | 3.752 | |
  20. | 0.674 | 0.163 | 0.163 | 0.512 |
  21. | 0.690 | 0.500 | 0.250 | |
  22. | 0.345 | 0.083 | 0.083 | |
  23. --------------------|-----------|-----------|-----------|-----------|
  24. Treated | 13 | 7 | 21 | 41 |
  25. | 2.744 | 0.004 | 3.935 | |
  26. | 0.317 | 0.171 | 0.512 | 0.488 |
  27. | 0.310 | 0.500 | 0.750 | |
  28. | 0.155 | 0.083 | 0.250 | |
  29. --------------------|-----------|-----------|-----------|-----------|
  30. Column Total | 42 | 14 | 28 | 84 |
  31. | 0.500 | 0.167 | 0.333 | |
  32. --------------------|-----------|-----------|-----------|-----------|

CrossTable()函数有很多选项,可以做许多事情:计算(行、列、单元格)的百分比;指定小数位数;进行卡方、Fisher和McNemar独立性检验;计算期望和(皮尔逊、标准化、调整的标准化)残差;将缺失值作为一种有效值;进行行和列标题的标注;生成SAS或SPSS风格的输出。参阅help(CrossTable)以了解详情。

如果有两个以上的类别型变量,那么你就是在处理多维列联表。我们将在下面考虑这种情况。

  • 多维列联表

table()xtabs()都可以基于三个或更多的类别型变量生成多维列联表。margin.table()prop.table()addmargins()函数可以自然地推广到高于二维的情况。另外,ftable()函数可以以一种紧凑而吸引人的方式输出多维列联表。代码清单7-12中给出了一个示例。

代码清单7-12 三维列联表

  1. > mytable <- xtabs(~ Treatment+Sex+Improved, data=Arthritis) # ❶ 各单元格的频数
  2. > mytable
  3. , , Improved = None
  4.  
  5. Sex
  6. Treatment Female Male
  7. Placebo 19 10
  8. Treated 6 7
  9.  
  10. , , Improved = Some
  11.  
  12. Sex
  13. Treatment Female Male
  14. Placebo 7 0
  15. Treated 5 2
  16.  
  17. , , Improved = Marked
  18.  
  19. Sex
  20. Treatment Female Male
  21. Placebo 6 1
  22. Treated 16 5
  23.  
  24. > ftable(mytable)
  25. Sex Female Male
  26. Treatment Improved
  27. Placebo None 19 10
  28. Some 7 0
  29. Marked 6 1
  30. Treated None 6 7
  31. Some 5 2
  32. Marked 16 5
  33.  
  34. > margin.table(mytable, 1) # ❷ 边际频数
  35. Treatment
  36. Placebo Treated
  37. 43 41
  38. > margin.table(mytable, 2)
  39. Sex
  40. Female Male
  41. 59 25
  42. > margin.table(mytable, 3)
  43. Improved
  44. None Some Marked
  45. 42 14 28
  46. > margin.table(mytable, c(1, 3)) # ❸ 治疗情况(Treatment) × 改善情况(Improved)的边际频数
  47. Improved
  48. Treatment None Some Marked
  49. Placebo 29 7 7
  50. Treated 13 7 21
  51. > ftable(prop.table(mytable, c(1, 2))) # ❹ 治疗情况(Treatment)× 性别(Sex)的各类改善情况比例
  52. Improved None Some Marked
  53. Treatment Sex
  54. Placebo Female 0.594 0.219 0.188
  55. Male 0.909 0.000 0.091
  56. Treated Female 0.222 0.185 0.593
  57. Male 0.500 0.143 0.357
  58.  
  59. > ftable(addmargins(prop.table(mytable, c(1, 2)), 3))
  60. Improved None Some Marked Sum
  61. Treatment Sex
  62. Placebo Female 0.594 0.219 0.188 1.000
  63. Male 0.909 0.000 0.091 1.000
  64. Treated Female 0.222 0.185 0.593 1.000
  65. Male 0.500 0.143 0.357 1.000

第❶部分代码生成了三维分组各单元格的频数。这段代码同时演示了如何使用ftable()函数输出更为紧凑和吸引人的表格。

第❷部分代码为治疗情况(Treatment)、性别(Sex)和改善情况(Improved)生成了边际频数。由于使用公式~Treatement+Sex+Improve创建了这个表,所以Treatment需要通过下标1来引用,Sex通过下标2来引用,Improve通过下标3来引用。

第❸部分代码为治疗情况(Treatment) × 改善情况(Improved)分组的边际频数,由不同性别(Sex)的单元加和而成。每个Treatment × Sex组合中改善情况为NoneSomeMarked患者的比例由❹给出。在这里可以看到治疗组的男性中有36%有了显著改善,女性为59%。总而言之,比例将被添加到不在prop.table()调用中的下标上(本例中是第三个下标,或称Improve)。在最后一个例子中可以看到这一点,你在那里为第三个下标添加了边际和。

如果想得到百分比而不是比例,可以将结果表格乘以100。例如:

  1. ftable(addmargins(prop.table(mytable, c(1, 2)), 3)) * 100

将生成下表:

  1. Sex Female Male Sum
  2. Treatment Improved
  3. Placebo None 65.5 34.5 100.0
  4. Some 100.0 0.0 100.0
  5. Marked 85.7 14.3 100.0
  6. Treated None 46.2 53.8 100.0
  7. Some 71.4 28.6 100.0
  8. Marked 76.2 23.8 100.0

列联表可以告诉你组成表格的各种变量组合的频数或比例,不过你可能还会对列联表中的变量是否相关或独立感兴趣。下一节我们会讲解独立性的检验。

7.2.2 独立性检验

R提供了多种检验类别型变量独立性的方法。本节中描述的三种检验分别为卡方独立性检验、Fisher精确检验和Cochran-Mantel–Haenszel检验。

  • 卡方独立性检验

你可以使用chisq.test()函数对二维表的行变量和列变量进行卡方独立性检验。示例参见代码清单7-13。

代码清单7-13 卡方独立性检验

  1. > library(vcd)
  2. > mytable <- xtabs(~Treatment+Improved, data=Arthritis)
  3. > chisq.test(mytable)
  4.  
  5. Pearson's Chi-squared test
  6.  
  7. data: mytable
  8. X-squared = 13.1, df = 2, p-value = 0.001463 # ❶ 治疗情况和改善情况不独立
  9.  
  10. > mytable <- xtabs(~Improved+Sex, data=Arthritis)
  11. > chisq.test(mytable)
  12.  
  13. Pearson's Chi-squared test
  14.  
  15. data: mytable
  16. X-squared = 4.84, df = 2, p-value = 0.0889 # ❷ 性别和改善情况独立
  17.  
  18. Warning message:
  19. In chisq.test(mytable) : Chi-squared approximation may be incorrect

在结果❶中,患者接受的治疗和改善的水平看上去存在着某种关系(p < 0.01)。而患者性别和改善情况之间却不存在关系(p > 0.05)❷。这里的p值表示从总体中抽取的样本行变量与列变量是相互独立的概率。由于❶的概率值很小,所以你拒绝了治疗类型和治疗结果相互独立的原假设。由于❷的概率不够小,故没有足够的理由说明治疗结果和性别之间是不独立的。代码清单7-13中产生警告信息的原因是,表中的6个单元格之一(男性 - 一定程度上的改善)有一个小于5的值,这可能会使卡方近似无效。

  • Fisher精确检验

可以使用fisher.test()函数进行Fisher精确检验。Fisher精确检验的原假设是:边界固定的列联表中行和列是相互独立的。其调用格式为fisher.test(mytable),其中的mytable是一个二维列联表。示例如下:

  1. > mytable <- xtabs(~Treatment+Improved, data=Arthritis)
  2. > fisher.test(mytable)
  3. Fisher's Exact Test for Count Data
  4.  
  5. data: mytable
  6. p-value = 0.001393
  7. alternative hypothesis: two.sided

与许多统计软件不同的是,这里的fisher.test()函数可以在任意行列数大于等于2的二维列联表上使用,但不能用于2×2的列联表。

  • Cochran-Mantel—Haenszel检验

mantelhaen.test()函数可用来进行Cochran—Mantel—Haenszel卡方检验,其原假设是,两个名义变量在第三个变量的每一层中都是条件独立的。下列代码可以检验治疗情况和改善情况在性别的每一水平下是否独立。此检验假设不存在三阶交互作用(治疗情况×改善情况×性别)。

  1. > mytable <- xtabs(~Treatment+Improved+Sex, data=Arthritis)
  2. > mantelhaen.test(mytable)
  3.  
  4. Cochran-Mantel-Haenszel test
  5.  
  6. data: mytable
  7. Cochran-Mantel-Haenszel M^2 = 14.6, df = 2, p-value = 0.0006647

结果表明,患者接受的治疗与得到的改善在性别的每一水平下并不独立(即,分性别来看,用药治疗的患者较接受安慰剂的患者有了更多的改善)。

7.2.3 相关性的度量

上一节中的显著性检验评估了是否存在充分的证据以拒绝变量间相互独立的原假设。如果可以拒绝原假设,那么你的兴趣就会自然而然地转向用以衡量相关性强弱的相关性度量。vcd包中的assocstats()函数可以用来计算二维列联表的phi系数、列联系数和Cramer’s V系数。代码清单7-14给出了一个示例。

代码清单7-14 二维列联表的相关性度量

  1. > library(vcd)
  2. > mytable <- xtabs(~Treatment+Improved, data=Arthritis)
  3. > assocstats(mytable)
  4. X^2 df P(> X^2)
  5. Likelihood Ratio 13.530 2 0.0011536
  6. Pearson 13.055 2 0.0014626
  7. Phi-Coefficient : 0.394
  8. Contingency Coeff.: 0.367
  9. Cramer's V : 0.394

总体来说,较大的值意味着较强的相关性。vcd包也提供了一个kappa()函数,可以计算混淆矩阵的Cohen's kappa值以及加权的kappa值。(举例来说,混淆矩阵可以表示两位评判者对于一系列对象进行分类所得结果的一致程度。)

7.2.4 结果的可视化

R中拥有远远超出其他多数统计软件的、可视地探索类别型变量间关系的方法。通常,我们会使用条形图进行一维频数的可视化(参见6.1节)。vcd包中拥有优秀的、用于可视化多维数据集中类别型变量间关系的函数,可以绘制马赛克图和关联图(参见11.4节)。最后,ca包中的对应分析函数允许使用多种几何表示(Nenadic & Greenacre,2007)可视地探索列联表中行和列之间的关系。

7.2.5 将表转换为扁平格式

我们将以一个在其他R书籍中极少涵盖但又非常实用的话题结束本节。在你已经拥有一个列联表却需要原始的数据时怎么办?举例来说,假设有以下列联表:

  1. Sex Female Male
  2. Treatment Improved
  3. Placebo None 19 10
  4. Some 7 0
  5. Marked 6 1
  6. Treated None 6 7
  7. Some 5 2
  8. Marked 16 5

但需要的是这种格式:

  1. ID Treatment Sex Age Improved
  2. 1 57 Treated Male 27 Some
  3. 2 46 Treated Male 29 None
  4. 3 77 Treated Male 30 None
  5. 4 17 Treated Male 32 Marked
  6. 5 36 Treated Male 46 Marked
  7. 6 23 Treated Male 58 Marked
  8. [78 more rows go here]

R中的许多统计函数接受的是后一种格式而不是前一种格式。你可以使用代码清单7-15中提供的函数将R中的表转换回扁平的数据格式。

代码清单7-15 通过table2flat将表转换为扁平格式

  1. table2flat <- function(mytable) {
  2. df <- as.data.frame(mytable)
  3. rows <- dim(df)[1]
  4. cols <- dim(df)[2]
  5. x <- NULL
  6. for (i in 1:rows){
  7. for (j in 1:df$Freq[i]){
  8. row <- df[i,c(1:(cols-1))]
  9. x <- rbind(x,row)
  10. }
  11. }
  12. row.names(x)<-c(1:dim(x)[1])
  13. return(x)
  14. }

此函数可以接受一个R中的表格(行列数任意)并返回一个扁平格式的数据框。你也可以使用这个函数来输入已发表的研究中的表格。举例来说,假设你在某期刊上看到了表7-2,并想以扁平格式将其保存到R中。 表7-2 Arthritis数据集中治疗情况和改善情况的列联表

治疗情况 改善情况
无改善 一定程度的改善 显著改善
安慰剂治疗 29 7 7
用药治疗 13 17 21

代码清单7-16描述了一种可以解决这个问题的方法。

代码清单7-16 使用table2flat()函数转换已发表的数据

  1. > treatment <- rep(c("Placebo", "Treated"), times=3)
  2. > improved <- rep(c("None", "Some", "Marked"), each=2)
  3. > Freq <- c(29,13,7,17,7,21)
  4. > mytable <- as.data.frame(cbind(treatment, improved, Freq))
  5. > mydata <- table2flat(mytable)
  6. > head(mydata)
  7. treatment improved
  8. 1 Placebo None
  9. 2 Placebo None
  10. 3 Placebo None
  11. 4 Treated None
  12. 5 Placebo Some
  13. 6 Placebo Some
  14. [12 more rows go here]

对列联表的讨论暂时到此为止,我们将在第11章和第15章中探讨更多高级话题。下面,我们将开始关注各种类型的相关系数。