使用 ggplot2 绘制云雨图

本文翻译自:https://www.cedricscherer.com/2021/06/06/visualizing-distributions-with-raincloud-plots-and-how-to-create-them-with-ggplot2/

如何展示你的数据?

对于大多数人来说,小提琴图已经不是展示数据的新鲜方法了:这种图表在 23 年就前开始使用。而在图中加入数据分布的总览,甚至是原始数据,将会是一个非常好的补充。而包含了箱线图、小提琴图以及抖动点图的云雨图,就是这种情况比较合适的实现。

云雨图于 2019 年推出,作为一种克服在绘制带有误差条的条形图(也称为炸药图)或箱线图时隐藏真实数据分布问题的方法。云雨图结合了多种图表类型来同时可视化原始数据、数据密度分布以及关键汇总统计。

图一

从箱线图开始

箱线图是一种表现数据分布汇总的有效形式。然而,它并不总是能达到我们想要的效果。为了说明这个问题,我们先模拟一组数据:

set.seed(2021)

data <- tibble(
group = factor(c(rep("Group 1", 100), rep("Group 2", 250), rep("Group 3", 25))),
value = c(seq(0, 20, length.out = 100),
c(rep(0, 5), rnorm(30, 2, .1), rnorm(90, 5.4, .1), rnorm(90, 14.6, .1), rnorm(30, 18, .1), rep(20, 5)),
rep(seq(0, 20, length.out = 5), 5))
) %>%
rowwise() %>%
mutate(value = if_else(group == "Group 2", value + rnorm(1, 0, .4), value))

然后绘制箱线图:

ggplot(data, aes(x = group, y = value)) +
geom_boxplot(fill = "grey92")
图二

三个组别的箱线图完全一致,但每组的样本量大小,潜在的分布模式却难以识别。 当然,在样本大小上添加注释可能被认为是一种很好的做法,但它仍然不能告诉你有关实际模式的太多信息。

## 生成中位数和标签的函数
n_fun <- function(x){
return(data.frame(y = median(x) - 1.25,
label = paste0("n = ",length(x))))
}

ggplot(data, aes(x = group, y = value)) +
geom_boxplot(fill = "grey92") +
## use summary function to add text labels
stat_summary(
geom = "text",
fun.data = n_fun,
size = 5
)
图三

更进一步:添加原始数据

一个显著的改进方法是添加数据点。由于我们已经知道第 2 组由 200 个观测值组成,让我们使用抖动带代替传统的带状图:

ggplot(data, aes(x = group, y = value)) +
geom_boxplot(fill = "grey92") +
## 使用 geom_point() 或 geom_jitter()
geom_point(
## 将点扩大
size = 2,
## 设置透明度
alpha = .3,
## 设置抖动
position = position_jitter(
## 控制散点抖动的随机性和宽度
seed = 1, width = .2
)
)
图四

图案很不一样!第 1 组的值均匀分布。第2组中的值聚集在该组四分位数周围有明显差距!并且第 3 组的少数观察结果都是整数。

我们可以用ggbeeswarm::geom_quasirandom()或ggforce::geom_sina()包来改进散点:

ggplot(data, aes(x = group, y = value)) +
geom_boxplot(fill = "grey92") +
ggbeeswarm::geom_quasirandom(
## 将点扩大
size = 1.5,
## 设置透明度
alpha = .4,
## 控制蜂群图宽度
width = .2
)
图五
ggplot(data, aes(x = group, y = value)) +
geom_boxplot(fill = "grey92") +
ggforce::geom_sina(
## 将点扩大
size = 1.5,
## 设置透明度
alpha = .4,
## 控制西娜图宽度
maxwidth = .5
)
图六

小提琴图:审稿人的噩梦?

小提琴图可用于可视化数值变量的分布。它是一条镜像密度曲线,表示沿连续轴的数据点数量。

ggplot(data, aes(x = group, y = value)) +
geom_violin(fill = "grey92")
图七

默认情况下,小提琴图可能看起来有点奇怪。默认设置 (scale = "area") 具有误导性。第 1 组看起来与第 3 组几乎相同,但包含的观察值是其四倍。此外,平滑核的默认标准差在该数据的情况下不是最佳的,因为它平滑了没有任何数据的区域,隐藏了数据的真实模式。

我们可以通过将宽度设置为观察次数scale = "count"并调整带宽(bw)来操纵这两个默认值。从美学上讲,可以删除轮廓:

ggplot(data, aes(x = group, y = value)) +
geom_violin(
fill = "grey72",
## 去除轮廓
color = NA,
## 小提琴的宽度映射为数据量
scale = "count",
## 设置带宽(平滑)
bw = .4
)
图八

小提琴图允许明确表示分布,但不提供汇总统计数据。为了两全其美,它通常与箱线图混合使用——要么是带有”胡须“和异常值的完整箱线图,要么是仅指示中位数和四分位距 (IQR) 的箱线图:

ggplot(data, aes(x = group, y = value)) +
geom_violin(
fill = "grey72",
color = NA,
scale = "count",
bw = .5
) +
geom_boxplot(
## 去除白色填充
fill = NA,
## 减少宽度
width = .1
)
图九
ggplot(data, aes(x = group, y = value)) +
geom_violin(
fill = "grey72",
color = NA,
scale = "count",
bw = .5
) +
geom_boxplot(
## 去除白色填充
fill = NA,
## 减少宽度
width = .1,
## 去除胡须
coef = 0,
## 去除异常值
outlier.color = NA ## `outlier.shape = NA` 同样有效
)
图十

你可能想知道:为什么不在箱线图上叠加原始数据,而是要使用小提琴图?好吧,如果你有很多的原始数据,绘制原始数据的所有方法都会变得困难,包括可读性以及运算方面。在这种情况下,小提琴图是一个很好的选择,结合一些汇总统计数据甚至更好。但我们也可以将这三者结合起来……

云雨图:一个绝佳的选择

云雨图可用于同时可视化原始数据、数据分布和关键汇总统计数据。实际上,它是一个混合图:由一半小提琴图、箱线图和作为某种散点的原始数据组成:

ggplot(data, aes(x = group, y = value)) +
## 用{ggdist}包添加半小提琴图
ggdist::stat_halfeye(
## 定义带宽
adjust = .5,
## 调整高度
width = .6,
## 将几何对象移动到右边
justification = -.2,
## 去掉平板间隔
.width = 0,
point_colour = NA
) +
geom_boxplot(
width = .12,
## 去除离群点
outlier.color = NA ## `outlier.shape = NA` 有同样的效果
) +
## 用{ggdist}包添加点图
ggdist::stat_dots(
## 方向朝向左边
side = "left",
## 几何图形向左侧平移
justification = 1.1,
## 设置带宽
binwidth = .25
) +
## 去除左侧空白
coord_cartesian(xlim = c(1.2, NA))
图十一

在这里,我们使用了{ggdist}包中的两个几何对象的图层,即{stat_dots()}来绘制“雨”,用{stat_halfeye}来绘制云。我们对两个对象的位置都进行了一定的偏移调整,使得他们能彼此相邻并为中间的箱线图腾出空间。我们还通过设置.width为0以及point_colour来将halfeye中的平板间隔去掉。图像需要手动调整一些样式,比如justification和带的数量很大程度上取决于你的数据。我们还可以简单地设置x轴的范围来去除左侧的空白部分。

也可以使用{ggdist}中默认的密度曲线和平板间隔来进行展示:

ggplot(data, aes(x = group, y = value)) +
ggdist::stat_halfeye(
adjust = .5,
width = .6,
## 设置平板间隔以展示四分位间距和95%数据区间
.width = c(.5, .95)
) +
ggdist::stat_dots(
side = "left",
dotsize = .8,
justification = 1.05,
binwidth = .3
) +
coord_cartesian(xlim = c(1.2, NA))
图十二

这种方式虽然可以直观展示四分位间距和 95% 区间,但也可能产生误导,让人以为这是置信区间。所以,使用这种图形时,最好再添加一个注释,以确保读者理解平板间隔表示的内容。

当然,可以用抖动点图甚至是条码图来代替点图。我们可以使用{gghalves}包中的geom_half_dotplot()函数。这个函数可以进行ggplot2默认图层geom_point()和geom_jitter()无法做到的调整:

ggplot(data, aes(x = group, y = value)) +
ggdist::stat_halfeye(
adjust = .5,
width = .6,
.width = 0,
justification = -.2,
point_colour = NA
) +
geom_boxplot(
width = .15,
outlier.shape = NA
) +
## 添加{gghalves}包中的抖动点图
gghalves::geom_half_point(
## 将抖动点图画在左侧
side = "l",
## 控制抖动的宽度
range_scale = .4,
## 调整透明度
alpha = .3
) +
coord_cartesian(xlim = c(1.2, NA), clip = "off")
图十三

注意,{gghalves}包也会沿y轴添加一些抖动。这个包也提供了绘制半个箱线图的方法,但这里不推荐。

另一个替代方法是使用geom_jitter()(或geom_point())将抖动点放在箱线图上:

ggplot(data, aes(x = group, y = value)) +
ggdist::stat_halfeye(
adjust = .5,
width = .6,
.width = 0,
justification = -.3,
point_colour = NA) +
geom_boxplot(
width = .25,
outlier.shape = NA
) +
geom_point(
size = 1.3,
alpha = .3,
position = position_jitter(
seed = 1, width = .1
)
) +
coord_cartesian(xlim = c(1.2, NA), clip = "off")
图十四

最后一种选择是,使用条码图而非抖动点:

ggplot(data, aes(x = group, y = value)) +
ggdist::stat_halfeye(
adjust = .5,
width = .6,
.width = 0,
justification = -.2,
point_colour = NA
) +
geom_boxplot(
width = .15,
outlier.shape = NA
) +
geom_point(
## 绘制条码而非点
shape = 95,
size = 10,
alpha = .2
) +
coord_cartesian(xlim = c(1.2, NA), clip = "off")
图十五

如图,这并不是最优选择,因为第三组的实际值很难倍被观察到,因为它们完全和箱线图重合。我们可以用geom_half_pint(),并取消沿x轴增加的抖动:

ggplot(data, aes(x = group, y = value)) +
ggdist::stat_halfeye(
adjust = .5,
width = .6,
.width = 0,
justification = -.2,
point_colour = NA
) +
geom_boxplot(
width = .15,
outlier.shape = NA
) +
## 使用{gghalves}包添加图层
gghalves::geom_half_point(
## 将图形放在左侧
side = "l",
## 绘制条码而非散点
shape = 95,
## 将x轴上的抖动取消
range_scale = 0,
size = 10,
alpha = .2
) +
coord_cartesian(xlim = c(1.2, NA), clip = "off")
图十六

点击这里跳转到 RStata 短书平台获取附件:使用 ggplot2 绘制云雨图

评论