R语言 自定义ggplot2中add_quantile小提琴图的美学

bcs8qyzn  于 2023-01-06  发布在  其他
关注(0)|答案(3)|浏览(179)

下面是ggplot2 in r中小提琴绘图的示例数据和代码:

library(ggplot2)
#Make data frame
Label <- c("Blue", "Blue", "Blue", "Blue","Red", "Red","Red","Red","Blue", "Blue","Blue","Blue")
n <- c(10, 223, 890, 34, 78, 902, 34, 211, 1007,209, 330, 446)
data <- data.frame(Label, n)

# make violin plot with quantiles
ggplot(data, aes(Label, n)) +
  geom_violin(legend = FALSE, trim = FALSE, adjust = 0.6, draw_quantiles = c(0.25, 0.5, 0.75))


有没有办法让0.5(中位数)线变粗,让0.25和0.75分位数的虚线变细?(或者如果无法使用点,可以改变alpha值,让中位数看起来比其他分位数更暗?)目前它们只是一条黑线。
附带问题...有没有一种简单的方法可以指定左小提琴图是"#42E894"的颜色,而右小提琴图是"#42E894"的较浅阴影。或者我应该手动找到较浅阴影的十六进制代码。
先谢了!

emeijp43

emeijp431#

如果你查看.subset2(GeomViolin, "draw_group")的代码,你会发现分位数路径的linewidth继承自小提琴几何的linewidth美学,你不能(轻易地)改变它。这是有道理的,因为对于小提琴部分,我们在原始数据上操作,而对于分位数,我们实际上需要2位数据(不同维度):

  • 原始数据以确定小提琴轮廓
  • 我们要加进去的几个分位数

基本上我有两个选择:

创建您自己的几何图形

诚然,这对我来说似乎有点大材小用,但为了好玩(也为了磨练我的自定义Geom编码技能),我尝试了一下(它远非完美,我需要发明一个新的参数名称linesize,从语义上讲,它就是linewidth,但对于前面提到的我们需要两个不同大小的数据集的问题,我需要一个“新”参数)。
好消息是,你基本上只是复制和粘贴相关的代码,剥离如果从所有不必要的部分有关的小提琴,因此你不必重新发明轮子。唯一真正需要的变化是在draw_group,而对于其他人,我们可以简单地依赖于默认的GeomViolinStatYdensity(默认的statgeom_violin)。

## 1. Create your workhorse function for draw_group
## basically a copy paste of .subset2(GeomViolin, "draw_group") 
## where all stuff related to the violin is removed
draw_group_violin_quantiles <- function(self, data, quantiles, ..., linesize = NULL, 
                                        flipped_aes = FALSE) {
  data <- flip_data(data, flipped_aes)
  data <- transform(data, xminv = x - violinwidth * (x - xmin), 
                    xmaxv = x + violinwidth * (xmax - x))
  if (!(all(quantiles >= 0) && all(quantiles <= 1))) {
    cli::cli_abort("{.arg quantiles} must be between 0 and 1")
  }
  quantiles <- ggplot2:::create_quantile_segment_frame(data, quantiles)
  aesthetics <- data[rep(1, nrow(quantiles)), 
                     setdiff(names(data), 
                             c("x", "y", "group")), drop = FALSE]
  aesthetics$alpha <- rep(1, nrow(quantiles))
  if (!is.null(linesize)) {
    aesthetics$linewidth <- rep(linesize, each = 2)
  }
  both <- vctrs::vec_cbind(quantiles, aesthetics)
  both <- both[!is.na(both$group), , drop = FALSE]
  both <- flip_data(both, flipped_aes)
  quantile_grob <- GeomPath$draw_panel(both, ...)
  ggplot2:::ggname("geom_violin_quantiles", quantile_grob)
}

## 2. Create your Geom proto object
## here we just inherit everything from `GeomViolin` and just replace the draw_group
GeomViolinQuantiles <- ggproto(
  "GeomViolinQuantiles",
  GeomViolin,
  draw_group = draw_group_violin_quantiles
)

## 3. Create the user facting geom_violin_qunatiles function
geom_violin_quantiles <- function(mapping = NULL,
                                  data = NULL,
                                  stat = "ydensity",
                                  position = "dodge",
                                  quantiles = NULL,
                                  ...,
                                  linesize = NULL,
                                  trim = TRUE, scale = "area", 
                                  na.rm = FALSE, orientation = NA, 
                                  show.legend = NA, inherit.aes = TRUE) {
  layer(data = data, mapping = mapping, stat = stat, geom = GeomViolinQuantiles, 
        position = position, show.legend = show.legend, inherit.aes = inherit.aes, 
        params = list(trim = trim, scale = scale, quantiles = quantiles, 
                       na.rm = na.rm, orientation = orientation, linesize = linesize, ...))
}

现在你可以像这样使用你的新geom_violin_quantiles了(浅色的问题可以用colorspace::lighten轻松解决):

library(colorspace)
ggplot(data, aes(Label, n, fill = Label)) +
  geom_violin(trim = FALSE, 
              adjust = 0.6) +
  geom_violin_quantiles(quantiles = c(0.25, 0.5, 0.75), adjust = .6,
                        linesize = c(.5, 1, .5)) +
  scale_fill_manual(values = c(Blue = "#42E894", Red = lighten("#42E894", .5)))

摆弄grob以更改事后宽度

另一种我经常使用的方法是,当我只想更改一个很好的ggplot的一个小方面时,在图表的grob表示中找到相应的插槽并更改图形参数。
这种方法需要在grob维斯str中进行一些搜索,并且不太稳定。也就是说,只要你对原来的绘图做了一点小的改变,你就需要再次搜索grob,因为内部位置可能会改变。
话虽如此,对于给定的示例,您可以简单地运行以下代码行:

library(grid)  
vp <- ggplot(data, aes(Label, n, fill = Label)) +
  geom_violin(trim = FALSE, 
              adjust = 0.6, draw_quantiles = c(0.25, 0.5, 0.75)) +
  scale_fill_manual(values = c(Blue = "#42E894", Red = lighten("#42E894", .5)))

## create the grob from the plot
vp_obj <- ggplotGrob(vp)

## after some searching via `str` find the relavant grob 
## in the whole object which represents the lines for the quantiles
## this position totally depends on the plot and has to be updated in
## case the plot changes

lw <- vp_obj$grobs[[6L]]$children[[3L]]$children[[1L]]$children[[2L]]$gp$lwd
lw[2L] <- 2 * lw[2L]

## violin for "Blue"
vp_obj$grobs[[6L]]$children[[3L]]$children[[1L]]$children[[2L]]$gp$lwd <- lw

## Violin for "Red"
vp_obj$grobs[[6L]]$children[[3L]]$children[[2L]]$children[[2L]]$gp$lwd <- lw

## Plot the whole thing
grid.draw(vp_obj)

mlnl4t2r

mlnl4t2r2#

我发现了另一个需要更少代码的解决方案:manual specification of draw_quantile argument in violin plot call in R
首先绘制小提琴图,其中0.25和0.75分位数显示为虚线,然后绘制另一个小提琴图,其中0.5分位数显示为实线,并添加透明背景。

ggplot(data, aes(Label, n)) +
  geom_violin(legend = FALSE, trim = FALSE, adjust = 0.6, draw_quantiles = c(0.25, 0.75), linetype = "dashed") +
  geom_violin(legend = FALSE, trim = FALSE, adjust = 0.6, draw_quantiles = 0.5, fill="transparent")

Violin Plot
我不知道如何在不更改小提琴图轮廓外观的情况下使0.5线更粗(可以通过向透明小提琴图添加参数size来实现)。
colorspace Package中的lighten函数是查找某个颜色的较浅版本的最简单方法。

ggplot(data, aes(Label, n, fill=Label)) +
  geom_violin(legend = FALSE, trim = FALSE, adjust = 0.6, draw_quantiles = c(0.25, 0.75), linetype = "dashed") +
  geom_violin(legend = FALSE, trim = FALSE, adjust = 0.6, draw_quantiles = 0.5, fill="transparent", size=0.75) +
  scale_fill_manual(values=c(Blue = "#42E894", Red = lighten("#42E894", 0.6)))

Violin Plot colored

bzzcjhmw

bzzcjhmw3#

为了完整和帮助其他人,我实际上结合了@Maria-Christina Weber和@thothal的答案,用粗中线和虚线分位数绘制了一个小提琴图。

从@thothal复制并粘贴代码

## 1. Create your workhorse function for draw_group
## basically a copy paste of .subset2(GeomViolin, "draw_group") 
## where all stuff related to the violin is removed
draw_group_violin_quantiles <- function(self, data, quantiles, ..., linesize = NULL, 
                                        flipped_aes = FALSE) {
  data <- flip_data(data, flipped_aes)
  data <- transform(data, xminv = x - violinwidth * (x - xmin), 
                    xmaxv = x + violinwidth * (xmax - x))
  if (!(all(quantiles >= 0) && all(quantiles <= 1))) {
    cli::cli_abort("{.arg quantiles} must be between 0 and 1")
  }
  quantiles <- ggplot2:::create_quantile_segment_frame(data, quantiles)
  aesthetics <- data[rep(1, nrow(quantiles)), 
                     setdiff(names(data), 
                             c("x", "y", "group")), drop = FALSE]
  aesthetics$alpha <- rep(1, nrow(quantiles))
  if (!is.null(linesize)) {
    aesthetics$linewidth <- rep(linesize, each = 2)
  }
  both <- vctrs::vec_cbind(quantiles, aesthetics)
  both <- both[!is.na(both$group), , drop = FALSE]
  both <- flip_data(both, flipped_aes)
  quantile_grob <- GeomPath$draw_panel(both, ...)
  ggplot2:::ggname("geom_violin_quantiles", quantile_grob)
}

## 2. Create your Geom proto object
## here we just inherit everything from `GeomViolin` and just replace the draw_group
GeomViolinQuantiles <- ggproto(
  "GeomViolinQuantiles",
  GeomViolin,
  draw_group = draw_group_violin_quantiles
)

## 3. Create the user facting geom_violin_qunatiles function
geom_violin_quantiles <- function(mapping = NULL,
                                  data = NULL,
                                  stat = "ydensity",
                                  position = "dodge",
                                  quantiles = NULL,
                                  ...,
                                  linesize = NULL,
                                  trim = TRUE, scale = "area", 
                                  na.rm = FALSE, orientation = NA, 
                                  show.legend = NA, inherit.aes = TRUE) {
  layer(data = data, mapping = mapping, stat = stat, geom = GeomViolinQuantiles, 
        position = position, show.legend = show.legend, inherit.aes = inherit.aes, 
        params = list(trim = trim, scale = scale, quantiles = quantiles, 
                       na.rm = na.rm, orientation = orientation, linesize = linesize, ...))
}

使用透明度和@thotal的新geom函数生成GGplot

library(colorspace)
ggplot(data, aes(Label, n, fill=Label)) +
  geom_violin(show.legend = FALSE, trim = FALSE, adjust = 0.6, draw_quantiles = c(0.25, 0.75), linetype = "dashed") +
  geom_violin(show.legend = FALSE, trim = FALSE, adjust = 0.6, fill= "transparent") +
  geom_violin_quantiles(quantiles = 0.5, adjust = .6,
                        linesize = 1) +
  theme(axis.title.x = element_blank()) +
  scale_fill_manual(values = c(Blue = "#42E894", Red = lighten("#42E894", .5)))

结果图:)x1c 0d1x
谢谢你们两个,太棒了!

相关问题