R语言ftable输出交叉表如何按子组追加列百分比标注
R实现三维交叉表分组列百分比标注
核心逻辑是按A-E主维度拆分,分别计算每个主分组下Group1、Group2的列合计,逐单元格计算列百分比后和原频数拼接,最终输出和ftable一致的折叠显示格式。
完整实现代码
1. 数据准备
如果已经生成了ftable对象,先用as.data.frame()转为数据框,再重塑为「主分类、行分类、Group1频数、Group2频数」的结构即可。以下用你给出的示例数据做演示:
# 构造示例数据,已有ftable对象可跳过这步 tab_df <- data.frame( main_cat = rep(c("A","B","C","D","E"), each = 4), row_cat = rep(c("I","II","III",NA), 5), Group1 = c(78,51,13,29, 182,110,74,0, 386,284,240,0, 6,5,6,0, 164,181,0,0), Group2 = c(74,45,17,22, 13,16,6,0, 47,28,42,0, 0,2,5,0, 17,31,0,0) )
2. 分组计算百分比并拼接格式
按主分类分组计算列合计,自动处理除以0的异常:
library(dplyr) result_tab <- tab_df %>% group_by(main_cat) %>% mutate( # 计算当前主分组下两列的总频数 g1_sum = sum(Group1), g2_sum = sum(Group2), # 计算列百分比,保留3位小数,总频数为0时直接返回0避免报错 g1_pct = ifelse(g1_sum == 0, 0, round(Group1 / g1_sum * 100, 3)), g2_pct = ifelse(g2_sum == 0, 0, round(Group2 / g2_sum * 100, 3)), # 拼接为「频数(百分比%)」格式,频数为0时直接显示0(0%) `Group 1` = ifelse(Group1 == 0, "0(0%)", paste0(Group1, "(", g1_pct, "%)")), `Group 2` = ifelse(Group2 == 0, "0(0%)", paste0(Group2, "(", g2_pct, "%)")) ) %>% ungroup() %>% select(main_cat, row_cat, `Group 1`, `Group 2`)
3. 格式化输出为ftable样式
将重复的主分类名称替换为空,实现原生ftable的折叠缩进效果:
# 同主分类仅第一行显示分类名,其余行留空 result_tab$main_cat[duplicated(result_tab$main_cat)] <- "" # 右对齐打印结果 print(result_tab, right = TRUE, row.names = FALSE)
计算结果说明
运行后A组输出和你给出的计算逻辑完全匹配:
main_cat row_cat Group 1 Group 2 A I 78(45.614%) 74(46.835%) II 51(29.825%) 45(28.481%) III 13(7.602%) 17(10.759%) <NA> 29(16.959%) 22(13.924%) B I 182(49.727%) 13(37.143%) II 110(30.055%) 16(45.714%) III 74(20.219%) 6(17.143%) <NA> 0(0%) 0(0%) ... ... ... ...
数值微小差异是四舍五入规则导致,可自行调整取整逻辑。
自定义调整说明
- 百分比小数位数:修改
round()函数的第二个参数即可,比如保留2位小数就写round(..., 2) - 对齐效果:如果需要让所有括号纵向对齐,可以用
sprintf()做格式化,比如sprintf("%-3d(%0.3f%%)", Group1, g1_pct)会自动补空格对齐数值和括号 - 从ftable对象转换:如果直接用
ftable()生成了表,先用as.data.frame(ftable_obj)转为长格式,再用tidyr::pivot_wider()把列分组转为宽表结构,后续计算逻辑完全相同
内容的提问来源于stack exchange,提问作者Ahir Bhairav Orai
相关产品推荐
相关产品推荐

