搜索"亚组分析森林图"一般是基于meta分析,而对于临床回顾性研究亚组分析的介绍少之又少。本期介绍使用R语言制作亚组分析三线表及其森林图。
注:由于未能找到专门做亚组分析的包或相关代码,本期代码是小编根据以往所学而写的,算是抛砖引玉了,之后有更好更简洁的代码时会及时更新本文。
最终结果
上图,在全部人群(All parents)中,放疗(RT)对局部区域复发(LRR)没有显著影响。
通过对RT进行亚组分析发现,在N2-3、ER阴性、PR阴性或无脉管癌栓组术后放疗(PMRT)可以改善局部控制。
综上,一张森林图可以清晰明了的表达数据的更多信息,为文章增色。
亚组分析三线表
代码设计总体思路 (此处可以先跳过)
1、上面的三线表主要分3大类:变量、放疗患者在各亚组的数目、HR、95%CI及P值。
2、HR、95%CI及P值来自在各亚组中的单因素cox回归分析。小编找了coxph( )函数的说明文件,发现在函数内嵌入subset=( )可以实现,但一次只能出一个亚组分析结果,因此需要用循环函数。
2-1、循环函数本来想像批量单因素回归那样,用function(x)循环,但老是报错,所以最终选用了for循环。
2-2、亚组分析的coxph( )函数公式为:cox <-coxph (Surv (time, status ==0) ~RT, subset = (T.stage=='T1'), data = R14),可以发现会出现两个变量T.stage及其亚变量T1。因此循环函数出现了限制,由于小编才疏学浅,未找到解决办法。因此让所有变量的亚变量都变为123的数字格式,这样我们可以先做所有变量中亚变量均为“1”的亚组分析,再运行亚变量为“2”的循环函数,以此类推。这样就得到了所有变量的亚变量的结果,即要建4个for循环,但不同点在于红色部分cox <-coxph (Surv (time, status ==0) ~RT, subset = (x=='1'), data = R14)。最后用rbind()函数将4个亚组分析结果打乱顺序重新组合成上图的形式,得到HR、95%CI及P值列
3、放疗在各个亚组的数目用tableone包做一个table1,用rbind()函数做成上图形式。
4、列名,在建表和组合过程就可以一起带过。
5、森林图:是最简单的了。做好表,加载代码。
【注:其实在做完4次亚组分析后完全可以把各个表保存为Excel然后在电脑上手动改为上图形式。但小编觉用代码做一次可以对R语言的基本操作、基本函数有更进一步了解,有时间的朋友可以试试。】
总结:主要难点在于做好HR、95%CI及P值的abcd表后的组合,容易在排序环节出错,不过也是有规律可循的。
载入R包和数据
#1.载入包
library(survival)
library(plyr)
##制作table1
library(tableone)
##制作森林图
library(forestplot)
#2.清理工作环境
rm(list = ls())
#3.数据放入工作路径,加载
R14<- read.csv('R14.csv')
#4.查看数据前6行
head(R14)
#5.查看数据数据性质
str(R14)
#6.查看结局:0为局部区域复发(55人)
R14$status<-factor(R14$status)
summary(R14$status)
相关变量定义
RT:放疗
T:1=T1,2=T2;
ER: 1=阴性,2=阳性;
PR: 1=阴性,2=阳性;
KI67: 1=低表达(<=30%),2=高表达;
LVI(脉管癌栓):1=无,2=有;
Grade: 1=I级,2=2级,3=级。
本文数据的特殊处理:
1、分类变量要全变为数字,并统一变为1.2.3.格式(稍后解释);
2、变量2分类在前,然后是3分类,...(稍后解释)。一定要这么处理!!!
代码所有需要修改的地方均用红色标出
一
亚组分析:HR、95%CI及P值批量获取
#1-建一个名为"HR","5%CI","95%CI","P"的4列表:a表。
#用于放批量获取的结果。
a<-c("HR","5%CI","95%CI","P")
##2建两个for循环,第一个是批量Cox亚组分析,
##第二个是将结果批量提取放入a表
for(i in 5:dim(R14)[2]){
cox1<-coxph(Surv(time,status==0)~RT,subset=(R14[,i]=='1'),data=R14)
aa<-summary(cox1)
for(j in 1:dim(aa$coefficients)[1]){
a<-rbind(a,c(round(aa$coefficients[j,2],2),
round(aa$conf.int[j,3],2),
round(aa$conf.int[j,4],2),
round(aa$coefficients[j,5],3)))}}
解释:亚组分析公式: cox1<-coxph(Surv(time,status==0)~RT, subset= (R14[,i]=='1'), data=R14),time和ststus是数据的随访时间和结局,0为感兴趣事件;RT是要进行亚组分析的变量;subset=(x=='1')是亚组,即做关于x变量中第1个亚组的RT的单因素分析;数据来源是R14。 for循环-1:for(i in 5:dim(R14)[2])意思是从R14文件中每次提取1个变量纳入亚组分析的subset=(R14[,i]=='1'),其中R14[,i]为循环变量,并注明从第5个变量即T开始纳入。这样我们就可以得到T/ER/PR/KI67/LVI/N/Grade变量中第1亚组的分析结果。 aa<-summary(cox1):将结果放入aa中,等待提取 。 for循环-2:for( j in 1:dim(aa$coefficients)[1])即将aa中的各种系数(coefficients)提出,从第1个开始提。rbind():将aa的第2列coefficients提出(是HR),第2-3列的conf.int(是95%CI),保留两位小数;coefficients的第5列(P)提出并保留3位小数。然后放入a表。每次提取都这么干,直至结束。关于为何coefficients第2列为HR,请看下,这是coxph()汇报结果的格式。
# coxph(formula = Surv(time, status == 0) ~ RT, data = R14)
# n= 350, number of events= 55
#
# coef exp(coef) se(coef) z Pr(>|z|)
# RTYes 0.0003434 1.0003435 0.2714870 0.001 0.999
#
# exp(coef) exp(-coef) lower .95 upper .95
# RTYes 1 0.9997 0.5876 1.703
#
# Concordance= 0.513 (se = 0.036 )
# Likelihood ratio test= 0 on 1 df, p=1
# Wald test = 0 on 1 df, p=1
# Score (logrank) test = 0 on 1 df, p=1
#3-a表加入第一列名字,
#即变量T/ER/PR/KI67/LVI/N/Grade中亚变量名为1的实际含义
rownames(a)<-c("Characteristics",
"T1",
"Negative",
"Negative",
"Low",
"Absent",
"N0",
"I");a <- data.frame(a)
#4- 将a表的"HR","5%CI","95%CI"组合成HR(5CI-95CI)格式
#上图X1列是HR,X2,X3是95%CI
a$HR.CI95<-paste0(a$X1," (",a$X2,"-",a$X3,")");a
经过以上4步,a表就做完了。其他表是同理。接下来做所有亚变量为2的亚组分析。
#1- 建立b表
b<-c("HR","5%CI","95%CI","P")
#2- 所有变量为2的亚组分析for循环
for(i in 5:dim(R14)[2]){
cox1<-coxph(Surv(time,status==0)~RT,subset=(R14[,i]=='2'),data=R14)
bb<-summary(cox1)
for(j in 1:dim(bb$coefficients)[1]){
b<-rbind(b,c(round(bb$coefficients[j,2],2),
round(bb$conf.int[j,3],2),
round(bb$conf.int[j,4],2),
round(bb$coefficients[j,5],3)))}}
#3- b表第一列名字,即变量T/ER/PR/KI67/LVI/N/Grade中亚变量名为2的实际含义
rownames(b)<-c("Characteristics",
"T2",
"Positive",
"Positive",
"High",
"Present",
"N1",
"II")
b <- data.frame(b);b
#4- 加入HR(95% CI)格式
b$HR.CI95<-paste0(b$X1," (",b$X2,"-",b$X3,")");b
b表:所有亚变量为2的亚组分析完成。红色是需要根据自己数据改的的地方,蓝色为主要的地方,虽然与a表的代码是一样的,但命名要分为b和bb,不然会把数据做到a表上,导致错误。
注意,此时只剩N和Grade有第3的亚组,所以for循环要从第10列开始(N变量)循环。这就是在进行亚组分析前将数据按变量多少依次排列的原因。红色根据自己数据改的,蓝色是注意不需修改。
#1- 建立c表
c<-c("HR","5%CI","95%CI","P")
#2- 亚变量为3的亚组分析
for(i in 10:dim(R14)[2]){
cox2<-coxph(Surv(time,status==0)~RT,subset=(R14[,i]=='3'),data=R14)
cc<-summary(cox2)
for(j in 1:dim(cc$coefficients)[1]){
c<-rbind(c,c(round(cc$coefficients[j,2],2),
round(cc$conf.int[j,3],2),
round(cc$conf.int[j,4],2),
round(cc$coefficients[j,5],3)))}}
#3- c表的第一列名字,即变量N/Grade中亚变量名为3的实际含义
rownames(c)<-c("Characteristics",
"N2~3",
"III")
c <- data.frame(c);c
#4- 加入HR(95% CI)列
c$HR.CI95<-paste0(c$X1," (",c$X2,"-",c$X3,")");c
c表只有两条结果,因为只有两个变量为3分类变量。要是还有4分类,大家现在应该就会了,再复制一下上面的循环代码,稍微修改一下即可。
这次仅仅是一次单因素分析而已,做对照用。非常简单,循环函数都不需要。
#1- RT单因素分析
cox3<-coxph(Surv(time,status==0)~RT,data=R14)
dd<-summary(cox3)
#2- 提取信息
dd$coefficients
dd$conf.int
HR<- round(dd$coefficients[,2],2)
PValue <- round(dd$coefficients[,5],3)
CI5<- round(dd$conf.int[,3],2)
CI95<- round(dd$conf.int[,4],2)
HR.CI95<-paste0(HR," (",CI5,"-",CI95,")")
#3- d表是最后建的,就是把信息提取出来组合为d表
d<- data.frame("X1" = HR,
"X2" = CI5,
"X3" = CI95,
"X4" = PValue,
"HR.CI95" = HR.CI95)
#4- 行名,命名为所有人群
rownames(d)<-"All parents"
这样,所需的HR、95%CI、P值都完成了,接下来是按照变量的分门别类把他们排好顺序。注:下一步要是嫌麻烦完全可以手动合成的。
二
放疗患者在各亚组的数目
这里代码意义参考:R语言|4. 轻松绘制临床基线表Table 1
#1- 查看数据变量名
names(R14)
#"status" "time""RT" "AGE""T" "ER" "PR" "KI67""LVI" "N" "Grade"
#2- 指定需进行基线统计的变量,纳入的变量为上面进行的亚组分析的变量
myVars <- c("T","ER" ,"PR" ,"KI67","LVI","N","Grade")
#指定基线表中哪些变量是分类变量
catVars<-c("T","ER" ,"PR" ,"KI67","LVI","N","Grade")
#3- 构建table1函数,RT为分类标准
table<- print(CreateTableOne(vars=myVars,
data=R14,
strata="RT",
factorVars=catVars),
catDigits = 2,contDigits = 2,showAllLevels=TRUE)
此时,我们提取到了在放疗或未放疗两组中的患者人数,是一个17行5列的表。但我们不要p值和text列,level列要从数字变为英文格式。
#4- 去掉第4.5列(上图p和test列),新表命名为table1(17行3列)
table1<-table[,c(-4,-5)]
注:函数a[x , y]意思是a表的行(x)和列(y)。例如:b<-a[1,]提取第一行给b,b<-a[-1,]a表删除第一行命名为b。b<-a[,1]提取第一列给b,b<-a[,-1]a表删除第一列并命名为b表。b<-a[,c(-1,-3)]a表删除第1.3列并命名为b表。
三
4个亚组分析表合成一个表并与table1表合并
#1- 按照table1的格式将abcd表合成一个表
res1<-rbind(d[1,],#abcd表合为res1表
a[2,],b[2,],#T的亚变量
a[3,],b[3,],#ER
a[4,],b[4,],#PR
a[5,],b[5,],#KI67
a[6,],b[6,],#LVI
a[7,],b[7,],c[2,],#N
a[8,],b[8,],c[3,])#G
使用的是rbind()和
a[x,]
函数。rbind()意思为上下行粘贴,a[x, ]是按行提取。abcd组合结果如下:res1表(一个17行5列表)
#2- res1表列名进入表格内,并命名为Characteristics
res1<-tibble::rownames_to_column(res1, var = "Characteristics")
此时的res1表变为17行6列表(多了亚变量名列)
#3- table与res1合成为res2
res2<-cbind(table1,res1)
前面介绍rbind()为上下的行粘贴,cbind()函数为左右的列粘贴。此时我们把17行3列的table1和17行6列的res2组合为res2(17行9列)
#4- 第一列不再需要,删除(res3,17行8列)
res3<-res2[,-1]
#5- 将合成的res3进行列排序,亚变量名放首位
res4<-data.frame(res3[c(3,2,1,4:6,8,7)])
至此,快要完成了。现在只有亚变量名缺变量名;没有列名;p值有=0的,应该为<0.001; N分期要放T分期后面。
四
优化,完成亚组分析三线表
#1- 每组亚变量上一行插入他们的变量名,根据自己数据来/或保存为Excel手写
N<-rbind(res4[1, ], #提取res4表第一行
c("T stage",rep(NA, 7)),#插入一行第一个单元格为T stage,后7列为NA
res4[c(2:3), ],#提取res4表第2,3行
c("ER stage",rep(NA, 7)),#插入一行第一个单元格为ER stage,后7列为NA
res4[c(4:5),],#以此类推
c("PR stage",rep(NA, 7)),
res4[c(6:7),],
c("KI67",rep(NA, 7)),
res4[c(8:9),],
c("LVI",rep(NA, 7)),
res4[c(10:11),],
c("N stage",rep(NA, 7)),
res4[c(12:14),],
c("Grade",rep(NA, 7)),
res4[c(15:17),])
#2-插入行名
for(i in 2:8) {N[, i] = as.character(N[, i])}#先让变量性质变为character类型
result <- rbind( c("Characteristics","PMRT","No_PMRT",NA,NA,NA,"HR (95%CI)","P Value"),
N[c(1:25),])
#3- p=0的变为<0.001
result$X4[result$X4==0]<-"<0.001"
#4- N分期放在T分期下面
result<-data.frame(result,row.names=NULL);result
result <- rbind(result [c(1:5,18:21,6:17,22:25),])
放疗(RT)与否在各亚组获益情况的亚组分析三线表完成
五
亚组分析森林图
fig<-forestplot(result[,c(1,2,3,7,8)], #12378列显示为原数字格式
mean=result[,4],
lower=result[,5],
upper=result[,6],
zero=1,
boxsize=0.4,
graph.pos= 4,#图放在第四列
hrzl_lines=list("1" = gpar(lty=1,lwd=2),
"2" = gpar(lty=2),
"26"= gpar(lwd=2,lty=1)),
graphwidth = unit(.25,"npc"),
xlab="HR<1,意味着放疗能改善局部区域复发风险",
xticks=c(0,1,3) ,
#----------------#字体
is.summary=c(T,
F,
T,F,F,
T,F,F,F,
T,F,F,
T,F,F,
T,F,F,
T,F,F,
T,F,F,F
),#T=粗体
txt_gp=fpTxtGp(label=gpar(cex=1),
ticks=gpar(cex=1.1),
xlab=gpar(cex=1),
title=gpar(cex=2)),
#----------------#线条粗细(x轴、置信区间)
lwd.zero=2,
lwd.ci=2,
lwd.xaxis=1,
lty.ci=1,
ci.vertices =T,
ci.vertices.height=0.2,
clip=c(0,3),
#----------------#行间距、字间距/box形状
ineheight=unit(8, 'mm'),
line.margin=unit(8, 'mm'),
colgap=unit(6, 'mm'),
col=fpColors(zero = "#e22e2a",
box = '#048410',
lines = 'black'),
fn.ci_norm="fpDrawCircleCI",
title="亚组分析森林图")
森林图的每行代码意义在R语言|12. 森林图-1: 多因素Cox回归模型森林图 (基于forestplot包)已有详述,需要的点击查看。
总结:至此亚组分析三线表及其森林图以全部完成。难点其实就是表格组合,个人认为手动组合快点。
若使用R,用到基础函数有rbind()数据上下粘贴组合;cbind()是数据左右组合;a[行,列]可以对a表行列进行挑选和删除等,虽然过程有些繁琐,但是完成之后感觉自己对R语言又增进了许多了解。
亚组分析是用以单因素Cox回归为核心的for循环来批量提取关键信息,并结合tableone包提取人群在个亚组的数目。
本期用的所有代码其实在前面早已介绍,换汤不换药。我相信只要掌握了代码含义和使用方法,大家都能完美解决临床研究中的问题,为论文的统计分析节省大量时间。
感谢阅读,如有错误请指正!
喜欢的小伙伴还请点赞分享一波,谢谢支持!!!
END
本公众号致力于打造一个实用的科研干货和临床学习资料分享平台,假如你有临床和科研上的问题或经验分享,请私信我。
感谢阅读,如有错误请指正!