如何制作表格?

为什么要制表?

数据分析过程是对数据进行减化的过程,是把大量的原始数据浓缩成一个或者一组数字,比如频数、均值、相关系数,用这些所谓的统计值来描述研究对象的特征和特征之间的关联。呈现分析结果的形式有两种:表格与图形。表格通过二维方格的形式将统计值直接显示出来。而图形则是通过色彩与形状间接地呈现这些统计值。虽然图形在表达信息方向比较直观,更容易发现多个统计值之间的关联。但是表格在表达统计信息时比较准确和全面,且图形的基础依然是统计值,因此,制表是数据分析过程中一项基本工作。R作为一门数据分析语言,提供了很多便捷的制表工具。

R制表package

表格是用来有条理地呈现统计数据,制表不仅涉及将原始数据统计计算生成统计表之外,还涉及对表格样式的设计与调整。R语言在这两个方面都有许多优秀的功能包。用于生成表格的package有base,stats, rstatix, sjmisc, janitor, gtsummary, modelsummarybase中的table(), summary()可分别对变量进行描述性统计,statst.test(), cor(), lm(), glm()可以用来生成相关性的统计值,但这些function的输出结果不是太友好。而第三方package提供很多有效的解决方案。其中gtsummarymodelsummary专注于制表,gtsummary兼容pipe,对labelled的数据支持比较友好,但其将统计值整合在一个单元格内,后期调整空间较小。相反,modelsummary生成表格的功能及可调整空间都较大,尤其对统计回归表格的支持更为广泛。rstatix, sjmisc, janitor虽然不是专门生成表格的package,但也提供一系列制表的function, 比如sjmisc中的frq(), janitor中的tabyl(). rstatix既有计算频数和统计值的function,比如freq_table()get_summary_stats(),且具有全面的相关性分析的function,比如t_test(), chisq_test(), cor_test(), anova_test()等,其生成的结果为data.frame格式,可直接用于制表。

对表格进行设计与调整的package有gt, gtExtras, flextable, formattable, kableExtra, DT, reactable. 其中,flextable可对表格全方位调整,语法结构容易理解,输出结果简约工整,适合制作正式出版所需的表格。gtgtExtras配合使用,也可以对表格全方位调整,表格样式比较适合网站显示,且可以在表格中加入图形等要素。formattablekableExtra配合使用,也可以对表格进行全方位的调整。DTreactable主要是可以生成具有交互功能的表格,二者功能具有重合之处。本文主要讲述modelsummaryflextable,部分讲解rstatix, sjmisc, kableExtra, DT.

R制表技术介绍

根据表格统计信息的复杂程度,可将表格分为三种类型:描述性表格,比如频数表、统计表、交叉表等;相关性分析结果表,比如correlation, anovo, t-test, chi2-test的结果表;回归分析结果表。本文主要关注第一类,也会涉及相关性分析表,不探讨回归分析结果表制作的问题。

频数表主要是对离散型变量各取值的频数和占比进行统计。交叉表是两个或以上的离散型变量的频数及占比的统计描述。统计表主要是对连续型变量的均值、方差、区间等信息的统计。还有一种两格就是分组生成连续型变量的统计表。

# setup
library(tidyverse)
library(rio)
library(here)
library(sjPlot)
library(sjmisc)
std_sm <- import(here("files/data/student_sm.dta"))

单一变量的表述

base包有两个快速统计分析的function: table()summary()。可以有助于快速获得单一变量的统计信息。

#base
table(std_sm$sex)

  1   2 
418 394 
#base
summary(std_sm$tr_chn)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
    2.0    98.0   112.0   106.8   120.0   137.0      42 

sjmisc包中的frq()可快速生成、格式友好的频数表。

library(sjmisc)
std_sm %>% 
  frq(computer, out="viewer")
你家里有电脑和网络吗 (computer) <numeric>
val label frq raw.prc valid.prc cum.prc
0 都没有 38 4.66 4.69 4.69
1 有电脑,无网络 41 5.03 5.06 9.75
2 有电脑和网络 731 89.69 90.25 100.00
NA NA 5 0.61 NA NA
total N=815 · valid N=810 · x̄=1.86 · σ=0.47

对连续型变量,rstatix中的get_summary_stats()方便易用。

library(rstatix)
std_sm %>% 
  get_summary_stats(tr_chn)
# A tibble: 1 × 13
  variable     n   min   max median    q1    q3   iqr   mad  mean    sd    se
  <chr>    <dbl> <dbl> <dbl>  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 tr_chn     773     2   137    112    98   120    22  13.3  107.  17.9 0.643
# … with 1 more variable: ci <dbl>

同时描述多个变量

在探索性分析初级阶段,需要首先对单个变量统计分析,在数据分析的下一阶段或者在分析结果展示阶段,会涉及对多个变量同时描述的情况。对于多个变量同时描述,我们将集中使用modelsummary中的function。首先是对多个离散型变量的分析,使用datasummary_skim()

#modelsummary
library(sjmisc)
std_sm <- std_sm %>%
  mutate(
    across(c(matbias, party_p,ses_p,aftercls,
    sex, hukou, computer, psyroom),to_label)
    )
#datasummary_skim
library(modelsummary)
std_sm %>% 
  select(sex, party_p, computer, aftercls) %>% 
  datasummary_skim(type = "categorical")
N %
sex 418 51.3
394 48.3
party_p 共产党员 105 12.9
民主党派 10 1.2
群众 664 81.5
computer 都没有 38 4.7
有电脑,无网络 41 5.0
有电脑和网络 731 89.7
aftercls 589 72.3
225 27.6

接下来是对多个连续型变量的同时描述,使用datasummary_skim()

std_sm %>% 
  select(tr_chn, tr_mat,tr_eng, height, weight, health) %>% 
  datasummary_skim(type = "numeric")
Unique (#) Missing (%) Mean SD Min Median Max
tr_chn 92 5 106.8 17.9 2.0 112.0 137.0
tr_mat 124 5 111.1 30.4 3.0 121.0 150.0
tr_eng 183 5 107.4 30.3 10.0 116.0 148.0
height 44 2 167.1 7.8 145.0 166.0 190.0
weight 110 7 112.1 25.2 50.0 106.0 220.0
health 6 0 3.9 0.9 1.0 4.0 5.0

datasummary_balance()可以将多个连续型变量和离散型变量同时显示。

std_sm %>% 
  select(tr_chn, tr_mat,tr_eng, height, weight, health,
                  sex, party_p, computer, aftercls) %>% 
  datasummary_balance(~ 1,data = .)
Mean Std. Dev.
tr_chn 106.8 17.9
tr_mat 111.1 30.4
tr_eng 107.4 30.3
height 167.1 7.8
weight 112.1 25.2
health 3.9 0.9
N Pct.
sex 418 51.3
394 48.3
party_p 共产党员 105 12.9
民主党派 10 1.2
群众 664 81.5
computer 都没有 38 4.7
有电脑,无网络 41 5.0
有电脑和网络 731 89.7
aftercls 589 72.3
225 27.6

datasummary_balance()还可以对变量分组生成描述统计表。

std_sm %>% 
  select(tr_chn, tr_mat,tr_eng, height, weight, health,
        sex, party_p, computer, aftercls) %>% 
  datasummary_balance(~ sex,data = .,dinm_statistic = "p.value")
男 (N=418)
女 (N=394)
Mean Std. Dev. Mean Std. Dev. Diff. in Means p
tr_chn 103.2 19.4 110.6 15.3 7.4 0.0
tr_mat 108.7 33.8 113.8 26.0 5.1 0.0
tr_eng 99.9 33.4 115.4 24.1 15.4 0.0
height 171.9 6.8 162.1 5.1 -9.8 0.0
weight 121.2 28.6 102.4 16.1 -18.8 0.0
health 4.0 0.9 3.8 0.9 -0.2 0.0
N Pct. N Pct.
party_p 共产党员 50 12.0 55 14.0
民主党派 4 1.0 6 1.5
群众 338 80.9 323 82.0
computer 都没有 22 5.3 15 3.8
有电脑,无网络 28 6.7 12 3.0
有电脑和网络 367 87.8 363 92.1
aftercls 278 66.5 310 78.7
139 33.3 84 21.3

交叉表

交叉表是对两个或以上离散型变量统计计算而产生的表,二维交叉表是两个变量统计分析的结果。datasummary_crosstab()可以生成二维交叉表。

datasummary_crosstab(sex ~ matbias, data = std_sm)
sex All
N 261 149 418
% row 62.4 35.6 100.0
N 233 158 394
% row 59.1 40.1 100.0
All N 495 309 815
% row 60.7 37.9 100.0

三维交叉表是三个变量交叉分析得到的分析表。datasummary_crosstab()可以生成三维交叉表。

std_sm %>%
  datasummary_crosstab(computer ~ sex * aftercls ,
                       statistic = 1 ~ 1 + N + Percent("col"),
                       data= .)
computer All
都没有 N 9 12 9 6 38
% col 3.2 8.6 2.9 7.1 4.7
有电脑,无网络 N 22 6 6 6 41
% col 7.9 4.3 1.9 7.1 5.0
有电脑和网络 N 247 120 293 70 731
% col 88.8 86.3 94.5 83.3 89.7
All N 278 139 310 84 815
% col 100.0 100.0 100.0 100.0 100.0

连续型变量分析表

相关系数是描述两个连续型变量之间关系强度一个统计值,datasummary_correlation()可以快速生成多个连续变量之间的相关系数。

#datasummary_correlation
std_sm %>%
  select(stcog, tr_chn, tr_mat, tr_eng, height, weight, health) %>%
  datasummary_correlation()
stcog tr_chn tr_mat tr_eng height weight health
stcog 1 . . . . . .
tr_chn .27 1 . . . . .
tr_mat .40 .77 1 . . . .
tr_eng .35 .75 .77 1 . . .
height .08 −.07 .03 −.11 1 . .
weight .05 −.05 −.02 −.13 .56 1 .
health −.01 −.06 −.04 .00 .14 .05 1

R表格调整与设计技术

表格设计可以分为两种情况,一种是对其它package生成后的表格进行再调整,比如对modelsummary生成的表格进行调整,另一种情况是对原始数据进行表格的设计。

表格的二次调整

flextable提供了对表格表头、表尾、行、列和单元格调整的各种function。可对文字、背景、边框等多个要素调整,以下是对modelsummary生成表格进一步调整的结果。

library(flextable)
std_sm %>%
  select(tr_chn, tr_mat, tr_eng, stcog, height, weight, health,sex) %>%
  datasummary_balance(~ sex,data = .,output = "flextable",
                      dinm_statistic = "p.value",fmt = 2) %>%
  set_header_labels(
    values =list(`男 (N=418) / Mean`="均值",
                 `男 (N=418) / Std. Dev.`="方差", 
                `女 (N=394) / Mean`="均值",
                `女 (N=394) / Std. Dev.`="方差",
                `Diff. in Means`="均值差",
                p="P值")) %>%
  add_header_row(
    values = c("","男 (N=418)","女 (N=394)",""),
    colwidths = c(1,2,2,2)) %>%
  bold(j=7,bold = TRUE) %>%
  color(i=4,color = "red") %>%
  bg(i=7,bg="lightblue") %>%
  width(j=7,width = 0.5) %>%
  hline(i=3:4,border = fp_border_default()) %>%
  flextable::footnote(j=7,value = as_paragraph("男女均值之间的差值"),
           ref_symbols = c("1"),part = "header") %>%
  set_caption(caption = "Table 1. 数学偏见人群之间的差异对比表")

男 (N=418)

女 (N=394)

均值

方差

均值

方差

均值差

P值1

tr_chn

103.22

19.36

110.57

15.33

7.35

0.00

tr_mat

108.72

33.77

113.82

25.95

5.10

0.02

tr_eng

99.93

33.45

115.37

24.13

15.44

0.00

stcog

10.79

3.82

10.68

3.56

-0.11

0.67

height

171.86

6.83

162.10

5.11

-9.75

0.00

weight

121.18

28.61

102.39

16.06

-18.79

0.00

health

4.04

0.92

3.80

0.88

-0.24

0.00

1男女均值之间的差值

kableExtra也提供了对表格表头、表尾、行、列和单元格调整的各种function,也可对文字、背景、边框等多个要素调整,以下是使用kableExtramodelsummary生成表格进一步调整的结果。

library(kableExtra)
std_sm %>%
  select(tr_chn, tr_mat, tr_eng, stcog, 
         height, weight, health, matbias) %>%
  datasummary_balance(~ matbias,data = .,output = "kableExtra",
                      dinm_statistic = "p.value",
                      title = "数学偏见人群之间的差异对比表",
                    col.names=c("","均值","方差","均值","方差","均值差","P值")) %>%
  kable_styling() %>%
  group_rows("学习成绩", 1,3) %>%
  group_rows("认知能力",4,4) %>%
  group_rows("身体健康",5,7)%>%
  column_spec(6:7, bold = T) %>%
  row_spec(4, bold = T, color = "white", 
           background = "#D7261E",hline_after = FALSE)
数学偏见人群之间的差异对比表
是 (N=495)
否 (N=309)
均值 方差 均值 方差 均值差 P值
学习成绩
tr_chn 107.2 17.4 106.6 18.5 -0.6 0.6
tr_mat 110.4 30.2 112.9 30.3 2.5 0.3
tr_eng 107.3 30.3 108.7 29.7 1.3 0.6
认知能力
stcog 10.5 3.7 11.0 3.7 0.5 0.0
身体健康
height 167.4 7.9 166.4 7.4 -1.1 0.1
weight 113.5 25.6 110.0 24.3 -3.6 0.1
health 4.0 0.9 3.8 1.0 -0.2 0.0

对原始表格的直接调整

对原始表格进行美化,两个package组合,一个是formattablekableExtra,以下是使用这两个package调整后的结果。这两个package的使用方法可参见其官方网站。

#筛选class id为100班级的学生数据
cls_perf <- std_sm %>%
  filter(clsids==100) %>%
  select(ids, stcog, tr_chn, tr_mat,
         tr_eng,stcog, ses_p, height,weight, health, aftercls, sex)
#kableExtra & formattable
library(formattable)
library(kableExtra)
cls_perf %>% 
  arrange(stcog) %>%
  drop_na() %>% 
  mutate(
    stcog=color_tile("white","orange")(stcog),
    health = cell_spec(health, "html", angle = (1:5)*60,
                    background = "red", color = "white", align = "center"),
    aftercls = ifelse(aftercls == "是",
                  cell_spec(aftercls, "html", color = "red", bold = T),
                  cell_spec(aftercls, "html", color = "green", italic = T)),
    tr_eng = cell_spec(tr_eng,color = if_else(tr_eng>120,"#AE0B2A","black")),
    height = color_bar("lightgreen",na.rm=TRUE)(height),
    tr_mat = color_bar("lightblue",na.rm=TRUE)(tr_mat)
  ) %>%
  kable(escape = FALSE,
        col.names = c("ID","认知能力","语文","数学","英语",
                      "经济条件","身高","体重","健康水平","课外班","性别"),
        caption = "Table 1. 3900班学生成绩表") %>%
  kable_styling(bootstrap_options = c("condensed"),
                full_width = FALSE) %>%
  column_spec(7,width = "2cm") %>%
  row_spec(0,color="white",background = "#AE0B2A") %>%
  kableExtra::footnote(general = "数据来源于CEPS.")
Table 1. 3900班学生成绩表
ID 认知能力 语文 数学 英语 经济条件 身高 体重 健康水平 课外班 性别
3983 4 108 122 107 比较富裕 164 116 4
3984 6 105 126 97 中等 177 214 2
3995 7 105 120 115 中等 160 88 3
3988 9 106 108 109 中等 160 98 4
3990 10 106 138 80 中等 165 90 3
3997 10 99 135 126 中等 166 110 4
3998 10 91 126 100 中等 169 90 3
3991 11 109 125 117 比较富裕 169 92 3
3992 13 103 125 102 中等 160 120 4
3993 13 100 130 107 比较富裕 173 120 4
3985 14 98 133 89 中等 168 120 4
3986 15 105 139 130 中等 161 100 4
3999 16 108 131 121 比较困难 180 148 4
Note:
数据来源于CEPS.

gtgtExtras也提供强大的表格设计功能,以下是使用这两个package调整后的结果。这两个package的使用方法可参见其官方网站。

library(gt)
library(gtExtras)
cls_perf2 <- cls_perf %>% 
  mutate(sex=if_else(sex=="男","mars","mercury"),
        aftercls=if_else(aftercls=="是","check","xmark")) %>%
  arrange(stcog) %>% 
  drop_na()

cls_perf2 %>%
  gt() %>%
  gt_hulk_col_numeric(starts_with("tr"),trim=TRUE) %>%
  gt_color_box(stcog, domain = 4:16,
               palette = c("purple",  "green")) %>%
  gt_highlight_cols(ses_p,fill="green",alpha = 0.1) %>%
  gt_fa_column(sex,palette = c("purple","grey")) %>%
  gt_fa_column(aftercls,palette = c("grey","green")) %>%
  gt_fa_rating(health,color="green",icon = "heart") %>%
  gt_duplicate_column(weight) %>%
  gt_plt_bar_pct(weight_dupe) %>%
  cols_move(weight_dupe,weight) %>%
  gt_plt_point(height,
               palette=c("purple", "lightgrey", "green"),width = 15) %>%
  gt_plt_dot(height,ids) %>%
  cols_label(ids="ID",stcog="认知能力",tr_chn="语文",tr_mat="数学", tr_eng="英语",
             ses_p="经济条件",height="身高",health="健康水平",aftercls="课外班",
             sex="性别",weight="体重", weight_dupe="体重指示") %>%
  cols_align("center") %>%
  tab_header(title = "Table 1. N3900班学生基本信息及成绩表") %>%
  tab_source_note("Note: 数据来源于CEPS.") %>%
  tab_style(cell_text(weight = "bold"),locations = cells_column_labels()) 
Table 1. N3900班学生基本信息及成绩表
ID 认知能力 语文 数学 英语 经济条件 身高 体重 体重指示 健康水平 课外班 性别
3983
4
108 122 107 比较富裕 158182 116
Heart Heart Heart Heart Heart
X Mark
Mercury
3984
6
105 126 97 中等 214
Heart Heart Heart Heart Heart
Check
Mars
3995
7
105 120 115 中等 88
Heart Heart Heart Heart Heart
X Mark
Mercury
3988
9
106 108 109 中等 98
Heart Heart Heart Heart Heart
Check
Mercury
3990
10
106 138 80 中等 90
Heart Heart Heart Heart Heart
X Mark
Mercury
3997
10
99 135 126 中等 110
Heart Heart Heart Heart Heart
X Mark
Mars
3998
10
91 126 100 中等 90
Heart Heart Heart Heart Heart
Check
Mars
3991
11
109 125 117 比较富裕 92
Heart Heart Heart Heart Heart
X Mark
Mercury
3992
13
103 125 102 中等 120
Heart Heart Heart Heart Heart
X Mark
Mercury
3993
13
100 130 107 比较富裕 120
Heart Heart Heart Heart Heart
X Mark
Mercury
3985
14
98 133 89 中等 120
Heart Heart Heart Heart Heart
X Mark
Mars
3986
15
105 139 130 中等 100
Heart Heart Heart Heart Heart
Check
Mars
3999
16
108 131 121 比较困难 158182 148
Heart Heart Heart Heart Heart
Check
Mars
Note: 数据来源于CEPS.