Here is an example data and code for a violin plot in 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))
Is there a way to make the 0.5 (median) line bolder/thicker, and the 0.25 and 0.75 quantiles a smaller dotted line? (or if dots isn't possible maybe changing the alpha so the median looks darker than the other quantiles?) At the moment they are just a black line.
Side-question... is there an easy way to specify that the left violin plot is the colour "#42E894", whilst the right violin plot is a lighter shade of "#42E894". Or should I just manually find the hex code for a lighter shade.
Thanks in advance!
CodePudding user response:
If you look into the code of .subset2(GeomViolin, "draw_group")
you will see that the linewidth
for the quantile paths is inherited from the the linewidth
aesthetic from the violin geom and you cannot (easily) change it. This makes sense, as for the violin part we are operating on the original data, while for the quantiles, we actually need 2 bits of data (of different dimensions):
- the original data to determine the violine contour
- the couple of quantiles which we want to add
Basically I see 2 options:
Create your own Geom
Admittedly, it seems a bit of an overkill to me, but for the fun of it (and to hone my custom Geom
codig skills) I gave it a try (it is far from being perfect and I needed to invent a new argument name linesize
which is semantically nothing else but linewidth
but for the aforementioned problem that we need two datasets of different sizes, I needed a "new" parameter).
Good news is that you basically just copy and paste the relevant code, strip if from all unnecessary parts realted to the violin and thus you do not have to reinvent the wheel. The only change really needed is in draw_group
while for the others we can simply rely on the defaults of GeomViolin
and StatYdensity
(the default stat
of geom_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, ...))
}
Now you can use your new geom_violin_quantiles
like this (The problem with the lighter color can be easily solved with 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)))
Fiddle with the grob
to change the width ex-post
Another approach I quite often use, when I want to change just a small aspect of an otherwise fine ggplot
is to find the corresponding slots in the grob
representation of the chart and change the graphical parameters.
This approach requires some searching in the grob vis str
and is not really stable. That is as soon as you make a slight change to your original plot you need to search the grob again, because internal positions may change.
Having said that, with the given example you can simply run the following lines:
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)
CodePudding user response:
I have found another solution which requires less code: manual specification of draw_quantile argument in violin plot call in R
You first draw the violin plot with the 0.25 and 0.75 Quantiles displayed as dashed lines and then draw another violin plot with the solid line for the 0.5 Quantile and add a transparent background.
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")
I don't know how to make the 0.5 line thicker without changing the appearance of the violin plots outline (which can be done by adding the parameter size
to the transparent violin plot).
The lighten
function from the colorspace Package is the easiest way to find a lighter version of a certain color.
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)))