技巧 | 使用基础绘图系统绘制「森林图」

森林图可以很直观地表达数学模型的结果,尤其是在对比多种情境的结果时。

R语言中有一些专门绘制森林图的工具包,不过小编目前还没仔细研究过。实际上,通过基础绘图系统的一些简单函数的组合使用就能绘制森林图。

首先生成用于绘图的模型结果:

## 全样本模型
data = mtcars
model = lm(mpg ~ qsec, data)
coeff = summary(model)$coefficients[2, 1] 
se = summary(model)$coefficients[2, 2] 

## 子样本模型
for(i in 3:5) {
  model.1 <- lm(mpg ~ qsec, data,
                subset = gear == i)
  coeff[i-1] = summary(model.1)$coefficients[2, 1] 
  se[i-1] = summary(model.1)$coefficients[2, 2] 
}

## 结果整理
library(tidyverse)
result <- tibble(
  coeff = coeff,
  se = se,
  lower = coeff - 1.96*se,
  upper = coeff + 1.96*se
)

result
## # A tibble: 4 x 4
##   coeff    se   lower upper
##   <dbl> <dbl>   <dbl> <dbl>
## 1 1.41  0.559  0.316   2.51
## 2 1.22  0.604  0.0393  2.41
## 3 0.853 0.998 -1.10    2.81
## 4 5.77  0.685  4.43    7.11
  • 上述结果中,coeff为模型系数,lowerupper为95%置信区间的下限和上限。

下面的代码提供了绘制森林图的基本思路:

plot(1, xlim = c(0,4), ylim = c(0,4),
     type = "n", ann = F)
abline(h = 0:4, col = "grey")
points(rep(3,4), (0:3)+0.5, 
       pch = 21, bg = "black")
set.seed(999)
segments(3-runif(4), (3:0)+0.5,
         3+runif(4), (3:0)+0.5)
7d0e82c3403f90bce80e0ab68e569fa7.png
  • 使用plot()函数生成绘图页面,之后的函数如abline()points()等都是低级函数;

  • plot()函数中,根据需要设置横、纵坐标的范围;type = n表示隐藏图形,只输出空白页面;

  • abline()函数用来根据需要绘制格网(图中为水平线);

  • points()函数绘制中心点;

  • segments()函数绘制置信区间的连线。

在上述框架内,坐标范围的设置与绘图数据无关,因此需要建立二者的映射关系。

例如,使用图形中横坐标在[2,4]之间的区域来绘制森林图,这样就需要把原始数据转换到该区间内。

min(result)
## [1] -1.102983

max(result)
## [1] 7.112582

根据绘图数据result的实际范围,需要将区间[-2,7.5](「数据区间」)映射到区间[2,4](「图形区间」)上去。这个过程实际就是已知两点去求直线方程,可以通过定义如下函数f()来实现:

f = function(x) {
  r1 = c(-2,7.5)
  r2 = c(2,4) 
  b = (lm(r2 ~ r1))$coefficients
  return(x*b[2] + b[1])
}

result2 = f(result)
result2
##      coeff       se    lower    upper
## 1 2.718342 2.538781 2.487594 2.949090
## 2 2.678550 2.548206 2.429329 2.927770
## 3 2.600697 2.631181 2.188846 3.012549
## 4 3.635697 2.565308 3.352956 3.918438
  • result2就是转换后的数据,所有数值均在[2,4]之间。

将前面绘图代码中points()segments()函数中的相关参数值改成result2中对应的变量:

plot(1, xlim = c(0,4), ylim = c(0,4),
     type = "n", ann = F)
abline(h = 0:4, col = "grey")
## result2$coeff
points(result2$coeff, (3:0)+0.5, 
       pch = 21, bg = "black")
## result2$lower和result2$upper
segments(result2$lower, (3:0)+0.5,
         result2$upper, (3:0)+0.5)
821e39eb097402cea544bc15562635ac.png

上述图形虽然已经能表达绘图数据的结果了,但它的坐标轴刻度与绘图数据是不对应的。

绘图数据所需要的刻度与图形实际的刻度之间也可以通过前面定义的f()函数进行转换:

xx = seq(-2, 8, 2)
xx0 = f(xx)

plot()函数中添加axes = F语句隐去图形的实际刻度,再使用低级函数axis()添加新的坐标刻度。

par(plt = c(0.05,0.95,0.2,0.8))
plot(1, xlim = c(0,4), ylim = c(0,4),
     type = "n", ann = F, axes = F)
abline(h = 0:4, col = "grey")
points(result2$coeff, (3:0)+0.5, 
       pch = 21, bg = "black")
segments(result2$lower, (3:0)+0.5,
         result2$upper, (3:0)+0.5)
## 添加坐标刻度
axis(1, at = xx0, labels = xx)
92081016a89b692c9e3974f60b6b0ce4.png

至此,森林图已经成型了。在图形左侧还可以根据需要添加相关标注,如添加如下文本。

result3 = round(result, 2)
text = paste0(result3$coeff, "(", result3$lower,
              "-", result3$upper, ")")
text
## [1] "1.41(0.32-2.51)" "1.22(0.04-2.41)" "0.85(-1.1-2.81)" "5.77(4.43-7.11)"

使用低级函数text()将文本添加到图形对应位置上:

par(plt = c(0.05,0.95,0.2,0.8))
plot(1, xlim = c(0,4), ylim = c(0,4),
     type = "n", ann = F, axes = F)
abline(h = 0:4, col = "grey")
points(result2$coeff, (3:0)+0.5, 
       pch = 21, bg = "black")
segments(result2$lower, (3:0)+0.5,
         result2$upper, (3:0)+0.5)
axis(1, at = xx0, labels = xx)
## 添加文本
text(rep(1,4), (3:0)+0.5, text, family = "mono")
09484aadf84cf0e378efaca13e8dd915.png

还可以根据需要进行其他修饰,这里再次使用低级函数abline()添加格网(竖直线):

par(plt = c(0.05,0.95,0.2,0.8))
plot(1, type = "n", ann = F, xlim = c(0,4), ylim = c(0,4),
     axes = F)
abline(h = 0:4, col = "grey")
points(result2$coeff, (3:0)+0.5, 
       pch = 21, bg = "black")
segments(result2$lower, (3:0)+0.5,
         result2$upper, (3:0)+0.5)
axis(1, at = xx0, labels = xx)
text(rep(1,4), (3:0)+0.5, text, family = "mono") 
## 添加格网
abline(v = xx0, col = "grey", lty = 2)
  • segments()函数也可以用来添加格网。

dc201291245ff157987c55d221fdfd85.png

以上就是仅使用基础绘图系统的函数绘制一个简单森林图的过程,更复杂的情况则可以通过各种细节进行调整去实现。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值