R语言整合Cox单因素与多因素分析结果绘制发表级森林图
2026/9/3 23:34:30 网站建设 项目流程

你肯定见过那种密密麻麻、信息量巨大的森林图(Forest Plot),尤其是在医学、流行病学或生存分析的论文里。它把一堆风险比(Hazard Ratio, HR)和置信区间(Confidence Interval, CI)直观地铺开,一眼就能看出哪些因素是保护性的,哪些是危险的。但当你自己动手,特别是用R语言处理Cox比例风险模型时,从跑出coxph()结果到画出一张既专业又清晰、同时包含单因素和多因素分析结果的森林图,中间往往隔着好几个让人头疼的“坑”。

比如,单因素分析结果是一堆变量,多因素分析结果是筛选后的另一堆变量,怎么把它们优雅地整合到一张图里?置信区间的横线怎么调整才不显得拥挤?变量名的标签太长怎么办?那些代表统计显著性的星号(*)和P值,是该放在图里还是图外的表格里?更实际的是,当你把代码从自己电脑搬到合作者的环境,或者准备把图嵌入报告、论文时,字体、尺寸、导出格式又是一堆问题。这远不是调用一个plot()函数那么简单,它涉及数据整理、图形美学和科研表达的交叉。

今天,我们不谈复杂的统计学原理(假设你已经了解Cox回归的基本思想),而是聚焦于一个非常实际的工程问题:如何将Cox单因素和多因素分析的结果,系统性地、可复现地整合成一张用于发表级报告的森林图。我会基于常见的survival包和forestplot包的工作流,拆解从数据准备、图形绘制到细节打磨的全过程。你会发现,真正的难点不在于画图本身,而在于对分析流程的掌控和对结果呈现的深度定制。

1. 理解任务核心:不是“画图”,而是“结果整合与呈现”

在开始写任何代码之前,我们必须明确一点:绘制森林图本身只是一个可视化步骤,它之前有更重要的数据准备步骤。对于Cox回归的森林图,尤其是结合单因素和多因素分析时,我们通常要完成以下数据流:

  1. 单因素分析 (Univariate Analysis):将每个感兴趣的变量单独放入Cox模型,得到一组HR和CI。目的是初步筛选可能与生存结局相关的变量。
  2. 多因素分析 (Multivariate Analysis):将单因素分析中显著(或基于学科知识)的变量同时放入一个Cox模型,得到调整了其他因素后的HR和CI。目的是评估每个变量的独立效应。
  3. 结果整合:将两步分析的结果(变量名、HR值、CI上下限、P值)整理成一个结构化的数据框(Data Frame)。
  4. 可视化:利用这个数据框绘制森林图,并清晰地标注出哪些结果来自单因素分析,哪些来自多因素分析。

很多教程只讲第4步,但前3步的数据处理才是决定森林图是否准确、清晰的关键。你的主判断应该是:一张好的Cox森林图,其质量70%取决于前期数据整理的严谨与清晰,30%才是图形参数的调整。

2. 构建分析流程:从数据到结果数据框

让我们用一个模拟的生存数据集来演示。假设我们有一个名为my_surv_data的数据框,包含生存时间time、生存状态status,以及若干个协变量,如年龄age(连续)、性别sex(分类)、肿瘤分级grade(分类)、治疗方案treatment(分类)等。

2.1 单因素Cox回归循环

我们不想对每个变量手动运行coxph,那样效率低且容易出错。更通用的方法是编写一个循环或使用lapply

library(survival) library(broom) # 用于整洁地提取模型结果 # 假设这是你的数据 # my_surv_data <- read.csv("your_data.csv") # 定义要分析的变量名列表 univar_vars <- c("age", "sex", "grade", "treatment", "another_var") # 初始化一个列表来存储每个单因素模型的结果 univar_results <- list() for (var in univar_vars) { # 构建公式:Surv(time, status) ~ variable formula <- as.formula(paste("Surv(time, status) ~", var)) # 拟合Cox模型 cox_model <- coxph(formula, data = my_surv_data) # 使用broom::tidy提取关键结果,并加上变量名 result_df <- broom::tidy(cox_model, exponentiate = TRUE, conf.int = TRUE) result_df$variable <- var result_df$analysis <- "Univariate" # 存储 univar_results[[var]] <- result_df } # 将列表合并成一个数据框 univar_df <- do.call(rbind, univar_results) # 查看整理后的单因素结果 head(univar_df)

broom::tidy()函数非常有用,它把模型输出变成一个整洁的数据框,默认包含term(模型项)、estimate(HR,因为exponentiate=TRUE)、std.errorstatisticp.valueconf.lowconf.high等列。

2.2 多因素Cox回归

多因素分析需要你事先决定放入模型的变量。这里假设我们基于单因素结果或临床意义,选择age,grade,treatment进入多因素模型。

# 拟合多因素Cox模型 multivar_formula <- Surv(time, status) ~ age + grade + treatment multivar_model <- coxph(multivar_formula, data = my_surv_data) # 提取多因素结果 multivar_df <- broom::tidy(multivar_model, exponentiate = TRUE, conf.int = TRUE) multivar_df$analysis <- "Multivariate" # 注意:多因素模型的term列已经是变量名,我们不需要再添加variable列,但为了与单因素数据框结构一致,可以重命名或新增 # 这里我们简单处理,将term复制到variable列 multivar_df$variable <- multivar_df$term # 查看多因素结果 multivar_df

2.3 整合单因素与多因素结果

这是关键步骤。我们需要一个最终的数据框,能够清晰地对应每个变量在单因素和多因素分析中的结果。通常,我们会把单因素和多因素的结果行并排放在一起,或者用子图区分。

# 选择我们需要展示的列 cols_to_keep <- c("variable", "analysis", "estimate", "conf.low", "conf.high", "p.value") univar_for_plot <- univar_df[univar_df$variable %in% c("age", "grade", "treatment"), cols_to_keep] multivar_for_plot <- multivar_df[, cols_to_keep] # 合并 combined_df <- rbind(univar_for_plot, multivar_for_plot) # 为了绘图时顺序正确,我们可以设定因子水平 combined_df$variable <- factor(combined_df$variable, levels = c("age", "grade", "treatment")) combined_df$analysis <- factor(combined_df$analysis, levels = c("Univariate", "Multivariate")) # 按变量和分析类型排序 combined_df <- combined_df[order(combined_df$variable, combined_df$analysis), ] # 生成森林图所需的标签文本 # 通常包括变量名、HR(95% CI)、P值 combined_df$label <- paste0( combined_df$variable, " (", combined_df$analysis, ")\n", sprintf("%.2f", combined_df$estimate), " (", sprintf("%.2f", combined_df$conf.low), "-", sprintf("%.2f", combined_df$conf.high), ")\n", "P=", sprintf("%.3f", combined_df$p.value) ) print(combined_df)

现在,combined_df数据框包含了绘制森林图所需的所有核心数据:每个估计值(HR)及其置信区间,以及我们自定义的标签。

3. 使用forestplot包进行高级绘图

虽然R基础绘图或survminer包的ggforest()也能画森林图,但forestplot包在定制化方面更灵活,尤其适合处理像我们这样整合了多种分析的数据结构。

3.1 基础森林图绘制

首先安装并加载包:install.packages("forestplot")

library(forestplot) library(dplyr) # 为forestplot准备数据矩阵 # forestplot需要几个部分: # 1. 文本标签(tabletext) # 2. 均值估计(mean) # 3. 置信区间下限(lower) # 4. 置信区间上限(upper) # 提取数据 mean <- combined_df$estimate lower <- combined_df$conf.low upper <- combined_df$conf.high # 创建文本标签矩阵。通常第一列是变量/分析标签,后面是HR(95%CI)和P值。 tabletext <- cbind( c("Variable (Analysis)", combined_df$label), # 第一列:标签 c("HR (95% CI)", paste0(sprintf("%.2f", combined_df$estimate), " (", sprintf("%.2f", combined_df$conf.low), "-", sprintf("%.2f", combined_df$conf.high), ")")), c("P Value", sprintf("%.3f", combined_df$p.value)) ) # 绘制基础森林图 forestplot(labeltext = tabletext, mean = c(NA, mean), # 第一行是标题,所以用NA lower = c(NA, lower), upper = c(NA, upper), is.summary = c(TRUE, rep(FALSE, nrow(combined_df))), # 第一行是汇总行(标题) xlog = TRUE, # Cox模型的HR通常取对数刻度显示 boxsize = 0.2, col = fpColors(box = "royalblue", line = "darkblue"), xticks = c(0.5, 1, 2, 4), # 根据你的HR范围设置 graph.pos = 2) # 森林图放在第几列文本后面

这段代码会生成一张包含所有结果的森林图。xlog = TRUE非常重要,因为HR的尺度是对称的(HR=1表示无效应,<1表示保护因素,>1表示危险因素),对数刻度能让图形更直观。

3.2 区分单因素与多因素分析

上面的图把所有结果混在一起了。为了更清晰,我们通常希望用视觉元素区分单因素和多因素结果。

# 方法:使用不同的颜色或形状 # 首先,为不同分析类型定义颜色 analysis_colors <- c("Univariate" = "#E69F00", "Multivariate" = "#0072B2") # 橙色和蓝色 # 创建颜色向量,对应每一行数据(不包括标题行) box_colors <- analysis_colors[combined_df$analysis] # 扩展is.summary,除了标题行,我们还可以将变量名所在行设为“汇总行”以加粗显示 # 这里我们创建一个逻辑向量,标记每个变量第一次出现的行为TRUE(作为分组标题) var_first_occurrence <- !duplicated(combined_df$variable) is_summary_vec <- c(TRUE, var_first_occurrence) # 加上第一行标题 # 绘制 forestplot(labeltext = tabletext, mean = c(NA, mean), lower = c(NA, lower), upper = c(NA, upper), is.summary = is_summary_vec, xlog = TRUE, boxsize = 0.2, col = fpColors(box = box_colors, line = box_colors), # 按分析类型着色 fn.ci_norm = fpDrawCircleCI, # 用圆圈代替方块,可能更清晰 vertices = TRUE, xticks = c(0.25, 0.5, 1, 2, 4), graph.pos = 2, txt_gp = fpTxtGp(label = gpar(cex=0.8), # 调整标签字体大小 ticks = gpar(cex=0.7), xlab = gpar(cex=0.9)), hrzl_lines = list("2" = gpar(lty=2)), # 在第二行(第一个标题行后)加虚线 mar = unit(c(4,1,4,1), "mm")) # 调整图形边距

通过col参数和自定义的box_colors向量,单因素和多因素的结果点现在用不同颜色显示。is.summary参数将每个变量的第一行加粗,起到了视觉分组的作用。

4. 进阶定制与避坑指南

一张能直接用于论文或报告的森林图,还需要考虑许多细节。

4.1 处理分类变量和参照组

在Cox模型中,分类变量(如grade II,grade III)会以参照组(如grade I)为基础生成多个哑变量。在整合结果时,你需要明确展示出每个级别与参照组的比较。

  • 在数据整理阶段broom::tidy()提取的结果中,term列会显示为gradeII,gradeIII。你需要更清晰的标签,例如“Grade II vs I”。
  • 在标签文本(tabletext)中:第一列应该清晰地标明比较对象。你可能需要手动构建一个更易读的标签向量,而不是直接使用变量名。
  • 参照线:务必确保森林图的垂直参照线在xlog=TRUE时位于x=1的位置。

4.2 控制图形尺寸与导出

在RStudio的预览窗口里看起来不错的图,导出为PDF或TIFF用于投稿时可能会走样。

# 保存为高分辨率PDF pdf("Cox_ForestPlot.pdf", width = 10, height = 6) # 宽度通常需要大一些以容纳文本 forestplot(...你的绘图参数...) # 重新运行绘图命令 dev.off() # 保存为高分辨率TIFF(适合投稿) tiff("Cox_ForestPlot.tiff", width = 10, height = 6, units = "in", res = 300) forestplot(...你的绘图参数...) dev.off()

注意:在脚本中,pdf()dev.off()之间的所有绘图命令都会输出到文件。确保你的图形在屏幕显示时就已经布局合理,否则保存后问题会更明显。如果标签被截断,尝试增加width参数或减小txt_gp中的字体大小cex

4.3 常见错误排查

  1. 置信区间异常宽或窄:检查数据是否存在共线性、模型是否收敛、或生存数据是否存在极端值。使用coxph()后运行summary(model)查看输出,确认没有警告信息(如“Loglik converged before variable X”可能预示问题)。
  2. 图形中HR或CI值显示为NA:检查combined_df中的estimate,conf.low,conf.high列是否存在NA或Inf值。这通常是由于某个变量在某个亚组中事件数为0导致的(完全分离)。需要考虑合并类别或使用其他统计方法。
  3. 标签错位或重叠forestplotlabeltext参数接受矩阵。确保你构建的矩阵行数与meanlowerupper参数的长度一致(考虑标题行)。使用cex参数调整字体大小,或考虑将过长的变量名缩写。
  4. P值格式:对于非常小的P值(如<0.001),在表格中通常表示为“P<0.001”而不是具体的科学计数法值。你可以在构建tabletext时用ifelse语句处理:ifelse(p.value < 0.001, "<0.001", sprintf("%.3f", p.value))

4.4 创建可复现的分析脚本

最好的实践是将整个流程封装在一个R脚本或R Markdown文档中。结构如下:

# 1. 加载库与数据 library(survival) library(broom) library(forestplot) library(dplyr) my_data <- read.csv("data.csv") # 2. 定义分析变量 univar_vars <- c("age", "sex", "grade", "treatment") multivar_formula <- Surv(time, status) ~ age + grade + treatment # 3. 单因素分析函数 run_univariate <- function(vars, data) { ... } # 4. 多因素分析函数 run_multivariate <- function(formula, data) { ... } # 5. 结果整合函数 combine_results <- function(univar_df, multivar_df) { ... } # 6. 绘图函数 draw_forest_plot <- function(combined_df) { ... } # 7. 执行主流程 univar_res <- run_univariate(univar_vars, my_data) multivar_res <- run_multivariate(multivar_formula, my_data) final_df <- combine_results(univar_res, multivar_res) draw_forest_plot(final_df) # 8. 导出图形 ggsave("final_forestplot.png", width=10, height=7, dpi=300) # 如果使用ggplot2系 # 或使用pdf()/tiff()

这样的脚本确保了从原始数据到最终图形的全过程可复现,也便于你后续更新数据或调整变量。

绘制Cox回归的森林图,尤其是整合单因素与多因素结果,是一个典型的“数据分析管道”任务。它考验的不仅仅是你对某个绘图包函数的熟悉程度,更是你对整个统计分析流程的理解和数据操作能力。核心在于构建一个清晰、准确、包含所有必要信息的中间结果数据框。一旦这个数据框准备妥当,无论你是用forestplotggplot2还是其他工具,绘图都变成了相对简单的参数调整问题。

因此,下次当你需要绘制这样的图时,请把至少一半的时间和精力分配给数据整理和验证。先用View()print()仔细检查combined_df里的每一个HR、CI和P值是否合理,确认分类变量的处理方式,然后再进入绘图阶段。这张图最终会成为你研究结论的视觉基石,值得你投入时间把它打磨精确。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询