You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

在R语言table1包中为不同连续变量分别配置均数/标准差、中位数/四分位数展示方式的实现方案及简化方法

在R语言table1包中为不同连续变量分别配置均数/标准差、中位数/四分位数展示方式的实现方案及简化方法

我完全懂你的需求——给table1里的不同连续变量配不同的统计展示:正态分布的用均数±标准差,偏态的用中位数+四分位数,还想摆脱冗余代码对吧?之前的方法出现重复统计量,大概率是返回的向量结构不符合table1的预期,我给你整理了一个简洁可靠的实现方案:

一、核心优化思路

  1. 抽离通用的数字格式化逻辑,避免重复写两次fmt_num函数
  2. 把变量判断和统计计算整合到一个渲染函数里,结构更紧凑
  3. 确保每个变量只返回一组统计项,彻底解决重复展示问题

二、完整可运行代码

# 加载所需包
library(table1)
library(tidyverse) # 用%||%运算符,若没装tidyverse可自己定义:`%||% <- function(a,b) if(is.null(a)) b else a`

# 1. 通用数字格式化函数(复用所有变量的格式化逻辑)
fmt_num <- function(val, digits = 3) {
  if (is.na(val)) return("NA")
  if (!is.numeric(val)) return(as.character(val))
  # 保留指定位数后,去除末尾无效0和多余小数点
  out <- sprintf(paste0("%.", digits, "f"), val)
  sub("\\.?0+$", "", out)
}

# 2. 自定义连续变量渲染函数(核心逻辑)
custom_cont_render <- function(x, ...) {
  # 获取变量名:优先用标签,没有则用原变量名(可按需切换)
  var_name <- attr(x, "label") %||% deparse(substitute(x))
  
  # 分类变量直接用table1默认渲染逻辑
  if (!is.numeric(x)) {
    return(render.categorical.default(x, ...))
  }
  
  # 一次性计算所有基础统计量,避免重复计算
  stats <- stats.default(x, na.rm = TRUE)
  
  # 按变量名指定统计展示方式
  if (var_name == "age") {
    # 均数±标准差
    res <- sprintf("%s (± %s)", fmt_num(stats$MEAN), fmt_num(stats$SD))
    names(res) <- "Mean (SD)"
  } else if (var_name == "BMI_BMIbase60") {
    # 中位数(Q1-Q3)
    res <- sprintf("%s (%s–%s)", fmt_num(stats$MEDIAN), fmt_num(stats$Q1), fmt_num(stats$Q3))
    names(res) <- "Median (Q1–Q3)"
  } else {
    # 其他变量用table1默认连续变量渲染
    return(render.continuous.default(x, ...))
  }
  
  # 返回符合table1要求的命名向量(关键:每个变量只返回一组统计项)
  res
}

# 3. 你的示例数据集
dat <- structure(list(age = c(74, 68, 71, 77, 72, 74, 75, 70, 68, 67, 
65, 64, 63, 82, 82, 88, 68, 79, 64, 68, 64, 71, 65, 72, 65, 67, 
75, 83, 67, 61), BMI_BMIbase60 = c(38.79, 38.98, 26.78, 26, 20.33, 
31.26, 29.3, 25, NA, NA, NA, NA, 19.48, NA, 23.362773874018, 
23.08, 22.81, 21.44, NA, NA, 18.95, 27.06, 33.84, 19.3199444515662, 
31.5121251102396, 30.59, 33.0432141675824, 25.82, 22.26, 24.227672807154
), hypertension = c(1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 
0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0)), row.names = c(NA, 
30L), class = "data.frame")

# 4. 生成目标表格
table1(~ age + BMI_BMIbase60 | hypertension, 
       data = dat,
       overall = c(right = "Total"),
       render.missing = NULL,
       render.continuous = custom_cont_render)

三、关键细节解释

1. 为什么之前的方法出现重复统计量?

你之前的custom_rndr返回了c(" " = out),而如果你的my.render.cont返回的是长度为2的向量(比如空字符串+Mean(SD)),table1会把每个向量元素都作为一行统计项,导致重复。现在的方法里,每个变量只返回一个命名元素,完美匹配table1的预期格式。

2. 批量变量的简化处理

如果有很多变量需要分组(比如10个变量用均数SD,8个用中位数IQR),可以用变量组向量来简化判断:

# 提前定义变量组
mean_sd_vars <- c("age", "variable1", "variable2")
median_iqr_vars <- c("BMI_BMIbase60", "variable3", "variable4")

# 修改渲染函数里的判断逻辑
if (var_name %in% mean_sd_vars) {
  # 均数±SD逻辑
} else if (var_name %in% median_iqr_vars) {
  # 中位数IQR逻辑
}

这样不用写一堆else if,扩展超方便。

3. 变量名/标签的选择

如果给变量加了友好标签(比如用Hmisc::label(dat$age) <- "年龄(岁)"),可以选择用标签判断还是原变量名判断:

  • 用标签判断:var_name <- attr(x, "label")(适合标签固定的场景)
  • 用原变量名判断:var_name <- deparse(substitute(x))(更稳定,不受标签修改影响)

四、极致简化版(轻量场景)

如果不想调用stats.default,也可以直接计算统计量,代码更短:

custom_cont_render <- function(x, ...) {
  var_name <- deparse(substitute(x))
  if (!is.numeric(x)) return(render.categorical.default(x, ...))
  
  if (var_name == "age") {
    c("Mean (SD)" = sprintf("%s (± %s)", fmt_num(mean(x, na.rm=T)), fmt_num(sd(x, na.rm=T))))
  } else if (var_name == "BMI_BMIbase60") {
    qs <- quantile(x, c(0.25, 0.5, 0.75), na.rm=T)
    c("Median (Q1–Q3)" = sprintf("%s (%s–%s)", fmt_num(qs[2]), fmt_num(qs[1]), fmt_num(qs[3])))
  } else {
    render.continuous.default(x, ...)
  }
}

这个方案既解决了重复统计量的问题,又大幅减少了冗余代码,扩展起来也很方便。如果还有其他变量要加,直接在变量组里加名字就行,不用改渲染函数的核心逻辑~

火山引擎 最新活动