Home > Mobile >  Customise aesthetics in add_quantile violin plot in ggplot2
Customise aesthetics in add_quantile violin plot in ggplot2

Time:01-04

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)) 

violin plot 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 Geomcodig 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)))

Violin plot where the quantiles lines have different widths

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)

Violin plot where the quantiles lines have different widths

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")

Violin Plot

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))) 

Violin Plot colored

  • Related