Hot questions for Using Ggplot2 in ggridges

Question:

Is it possible to add a joyplot as a panel to a plot that includes a ggtree, as shown in these examples? Examples of joyplots are here.

I realize that I could manually put the species labels for the joyplot in the same order as the tree tip labels, but I am looking for an automatic solution. I would like to associate the joyplot rows with the tips of the trees automatically, akin to how the boxplot data are associated with the tip labels.

I think that Guangchuang Yu's examples at the above link provide suitable data:

require(ggtree)
require(ggstance)

# generate tree
tr <- rtree(30)

# create simple ggtree object with tip labels
p <- ggtree(tr) + geom_tiplab(offset = 0.02)

# Generate categorical data for each "species"
d1 <- data.frame(id=tr$tip.label, location=sample(c("GZ", "HK", "CZ"), 30, replace=TRUE))

#Plot the categorical data as colored points on the tree tips
p1 <- p %<+% d1 + geom_tippoint(aes(color=location))

# Generate distribution of points for each species
d4 = data.frame(id=rep(tr$tip.label, each=20), 
            val=as.vector(sapply(1:30, function(i) 
                            rnorm(20, mean=i)))
            )               

# Create panel with boxplot of the d4 data
p4 <- facet_plot(p1, panel="Boxplot", data=d4, geom_boxploth, 
        mapping = aes(x=val, group=label, color=location))           
plot(p4)

This produces the plot below:

Is it possible to create a joyplot in place of the boxplot?

Here is code for a quick joyplot of the demo dataset d4 above:

require(ggjoy)

ggplot(d4, aes(x = val, y = id)) + 
geom_joy(scale = 2, rel_min_height=0.03) + 
scale_y_discrete(expand = c(0.01, 0)) + theme_joy()

The result is:

I am new to ggplot2, ggtree, and ggjoy so I am totally at a loss with how to even begin doing this.


Answer:

Note: As of 2017-09-14, the ggjoy package has been deprecated. Instead, use the ggridges package. For the code below to work with ggridges, use geom_density_ridges instead of geom_joy.


It looks like you can just replace geom_boxplot with geom_joy in facet_plot:

facet_plot(p1, panel="Joy Plot", data=d4, geom_joy, 
           mapping = aes(x=val, group=label, fill=location), colour="grey50", lwd=0.3) 

If you're new to ggplot2, the visualization chapter of Data Science with R (an open-source book by the author of ggplot2) should be helpful for learning the basics.

ggjoy and ggtree extend the capabilities of ggplot2. When such extensions are done well, the "obvious" thing to do (in terms of the usual ggplot "grammar of graphics") often works, because the extension package is written in a way that tries to be faithful to the underlying ggplot2 approach.

Here, my first thought was to just substitute geom_joy for geom_boxplot, which turned out to get the job done. Each geom is just a different way to visualize the data, in this case box plot vs. density plot. But all of the other "structure" of the plot stays the same, so you can just change geoms and get a new plot that follows the same axis ordering, color mappings, etc. This will make more sense once you get some experience with the ggplot2 grammar of graphics.

Here's a slightly different labeling approach for the left-hand plot:

p1 = ggtree(tr) %<+% d1 +
  geom_tippoint(aes(color=location), size=6) +
  geom_tiplab(offset=-0.01, hjust=0.5, colour="white", size=3.2, fontface="bold") 

facet_plot(p1, panel="Joy Plot", data=d4, geom_joy, 
           mapping = aes(x=val, group=label, fill=location), colour="grey40", lwd=0.3) 

UPDATE: This is in response to your comment asking how to get the same custom colors in both facet panels. Here's code to do that with the example data in your question:

p1 = ggtree(tr) %<+% d1 +
  geom_tippoint(aes(color=location), size=5) +
  geom_tiplab(offset=-0.01, hjust=0.5, colour="white", size=3, fontface="bold") +
  scale_colour_manual(values = c("grey", "red3", "blue")) +
  scale_fill_manual(values = c("grey", "red3", "blue"))

facet_plot(p1, panel="Joy Plot", data=d4, geom_joy, 
           mapping = aes(x=val, group=label, fill=location), colour="grey40", lwd=0.3) 

Question:

The geom_density_ridges geom from the ggridges package created ridgelines, and if a bandwidth is not specified, it attempts to find a sensible value. It then uses the base R message function to report that value (see https://twitter.com/ClausWilke/status/921363157553172480).

The base R function suppressMessages function is designed to suppress such messages. For example, this code outputs a message:

message('This is a message');

And this code outputs nothing:

suppressMessages(message('This is a message'));

However, for some reason, the suppressing of messages seems, um, suppressed when this geom is added to a ggplot. The following code does still produce a message:

require('ggplot2');
require('ggridges');
suppressMessages(ggplot(Orange, aes(x=age,y=Tree)) + geom_density_ridges());

(Specifically, "Picking joint bandwidth of 319".)

Why is this? Does ggplot do something to ensure that messages come through regardless of the users' specification? Or is this actually sensible behavior that I just happen to not know about?

When generating RMarkdown reports, the chunk option message can be set to message=FALSE, which suppresses all messages at the rendering level. And since that's my use case, my problem is solved.

And as Claus Wilke, the author of the ggridges package, suggested, you can always set the bandwidth of manually to avoid the message (https://twitter.com/ClausWilke/status/921361195231215616).

But why doesn't suppressMessages suppress the message in the first place?

Is this expected behavior that I just happen to not know about?


Answer:

When you call ggplot(), that command doesn't actually draw a plot -- it creates a ggplot object. Only when that object is printed is a plot actually drawn. When you type an expression in the R console, the default behavior is to call print() on the result which is why it seems like ggplot() draws a plot.

Note that the warnings you are experiencing do not occur during the creation of the ggplot object; they occur during the printing of this object. So if you run

suppressMessages(ggplot(...))

that's essentially the same as

print(suppressMessages(ggplot(...)))

when running R in interactive mode. But since no messages are generated by ggplot(), nothing is suppressed and those messages still appear when the resulting object is printed. To suppress the messages created during printing, you need to wrap the actual print() statement with suppressMessages().

suppressMessages(print(ggplot(...)))

Question:

The ggridges package lets you draw ridgeplots with either solid colour:

ggplot(iris, aes(x=Sepal.Width, y=Species))+
geom_density_ridges(alpha=0.33, scale=2, fill="#0570b0", colour=alpha(0.1))+
theme_classic()

or with horizontal colour gradients:

ggplot(iris, aes(x=Sepal.Width, y=Species, fill=..x..))+
geom_density_ridges_gradient(scale=2,colour=alpha(0.1))+
theme_classic()+
scale_fill_gradient(low="#0570b0", high="White")

But I want to know if it is possible to produce a similar chart with a vertical colour gradient, like this example (which was drawn using D3.js). Is there a way to implement something similar in R?

Image source ONS: Middle-aged generation most likely to die by suicide and drug poisoning


Answer:

We can do this using the devoutsvg and related svgpatternsimple packages:

# install packages    
# devtools::install_github("coolbutuseless/lofi")      
# devtools::install_github("coolbutuseless/minisvg")   
# devtools::install_github("coolbutuseless/devout")    
# devtools::install_github("coolbutuseless/devoutsvg") 
# devtools::install_github("coolbutuseless/poissoned") 

library(lofi)
library(minisvg)
library(devout)
library(devoutsvg)
library(svgpatternsimple)
library(poissoned)

#create gradient
grad <- create_gradient_pattern(id="p1", angle=90, colour1="White", 
colour2="#0570b0")

#visualise it
grad$show()

#encode it
gradRGB <- encode_pattern_params_as_hex_colour(pattern_name="gradient",angle=90, 
colour1="White", colour2="#0570b0")   

#draw graph
svgout(filename = "test.svg", pattern_pkg="svgpatternsimple")
ggplot(iris, aes(x=Sepal.Width, y=Species))+
  geom_density_ridges(alpha=0.33, scale=2, 
fill=gradRGB, colour=alpha(0.1))+
  theme_classic()
invisible(dev.off())    

This gives you an .svg file with a vertical gradient as that looks like this: Vertical gradient fill ridgeplot.


Update: Function is now on GitHub: VictimOfMaths/DeathsOfDespair.

Question:

No matter what I try I cannot create a ridgeline plot using ggridges. Using a dataframe graphing_dataframe that looks as follows:

str(graphing_dataframe)
summary(graphing_dataframe)

> str(graphing_dataframe)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   14 obs. of  3 variables:
 $ id    : chr  "00343" "00343" "00343" "00343" ...
 $ week  : num  14 1 2 3 4 5 6 7 8 9 ...
 $ rating: num  14 4 12 8 14 19 16 16 7 8 ...
 - attr(*, "spec")=
  .. cols(
  ..   id = col_character(),
  ..   week = col_double(),
  ..   rating = col_double()
  .. )
> summary(graphing_dataframe)
      id                 week           rating     
 Length:14          Min.   : 1.00   Min.   : 4.00  
 Class :character   1st Qu.: 4.25   1st Qu.: 8.00  
 Mode  :character   Median : 7.50   Median :10.50  
                    Mean   : 7.50   Mean   :11.43  
                    3rd Qu.:10.75   3rd Qu.:15.50  
                    Max.   :14.00   Max.   :19.00 

My data is

structure(list(id = c("00343", "00343", "00343", "00343", "00343", 
"00343", "00343", "00343", "00343", "00343", "00343", "00343", 
"00343", "00343"), week = c(14, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 
11, 12, 13), rating = c(14, 4, 12, 8, 14, 19, 16, 16, 7, 8, 9, 
18, 9, 6)), class = c("tbl_df", "tbl", "data.frame"), row.names = c(NA, 
-14L), spec = structure(list(cols = list(id = structure(list(), class = c("collector_character", 
"collector")), week = structure(list(), class = c("collector_double", 
"collector")), rating = structure(list(), class = c("collector_double", 
"collector"))), default = structure(list(), class = c("collector_guess", 
"collector"))), class = "col_spec"))

My code is

ggplot(graphing_dataframe, 
       aes(x = rating, y = week, fill = ..x..)
       ) +
  geom_density_ridges()

Picking joint bandwidth of 2.53
Error: geom_density_ridges requires the following missing aesthetics: y

I've tried using unlist per this question but that doesn't work either.


Answer:

As @JonnyPhelps commented my data is not compatible with a ridgeline plot (or vice versa).

Question:

When I use geom_density_ridges, the plot often ends up showing long tails of values that don't exist in the data.

Here's an example:

library(tidyverse)
library(ggridges)

data("lincoln_weather")

# Remove all negative values for "Minimum Temperature"
d <- lincoln_weather[lincoln_weather$`Min Temperature [F]`>=0,]

ggplot(d, aes(`Min Temperature [F]`, Month)) +
  geom_density_ridges(rel_min_height=.01)

As you can see, January, February, and December all show negative temperatures, but there are no negative values in the data at all.

Of course, I can add limits to the x-axis, but that doesn't solve the problem because it just truncates the existing erroneous density.

ggplot(d, aes(`Min Temperature [F]`, Month)) +
  geom_density_ridges(rel_min_height=.01) +
  xlim(0,80)

Now the plot makes it look like there are zero values for January and February (there are none). It also makes it look like 0 degrees happened often in December, when in reality there was only 1 such day.

How can I fix this?


Answer:

One option is to use stat_density() instead of stat_density_ridges(). There are some things that stat_density() can't do, such as drawing vertical lines or overlaying points, but on the flip side it can do some things that stat_density_ridges() can't do, such as trimming the distributions to the data ranges.

# Remove all negative values for "Minimum Temperature"
d <- lincoln_weather[lincoln_weather$`Min Temperature [F]`>=0,]

ggplot(d, aes(`Min Temperature [F]`, Month, group = Month, height = ..density..)) +
  geom_density_ridges(stat = "density", trim = TRUE)

As an alternative, you could draw a point rug, maybe that serves your purpose as well or better:

ggplot(d, aes(`Min Temperature [F]`, Month)) +
  geom_density_ridges(rel_min_height = 0.01, jittered_points = TRUE,
                      position = position_points_jitter(width = 0.5, height = 0),
                      point_shape = "|", point_size = 2,
                      alpha = 0.7)

Note: those two approaches cannot currently be combined, that would require some modifications to the stat code.

Question:

I would like to add a vertical line by row to joy plots using ggridges.

# toy example
ggplot(iris, aes(x=Sepal.Length, y=Species, fill=..x..)) +
geom_density_ridges_gradient(jittered_points = FALSE, quantile_lines = 
FALSE, quantiles = 2, scale=0.9, color='white') +
scale_y_discrete(expand = c(0.01, 0)) +
theme_ridges(grid = FALSE, center = TRUE)

I want to add a vertical line at 7 for virginica, 4 for versicolor, and 5 for setosa. Any ideas on how to do it?


Answer:

Since your densities don't overlap, it may be easiest to just add additional segments.

iris_lines <- data.frame(Species = c("setosa", "versicolor", "virginica"),
                         x0 = c(5, 4, 7))

ggplot(iris, aes(x=Sepal.Length, y=Species, fill=..x..)) +
  geom_density_ridges_gradient(jittered_points = FALSE, quantile_lines = 
                                 FALSE, quantiles = 2, scale=0.9, color='white') +
  geom_segment(data = iris_lines, aes(x = x0, xend = x0, y = as.numeric(Species),
                                      yend = as.numeric(Species) + .9),
               color = "red") +
  scale_y_discrete(expand = c(0.01, 0)) +
  theme_ridges(grid = FALSE, center = TRUE)

Question:

I am able to draw density_ridge using this code. I want to add geom_point at percentile 0.50 without changing the current design. Any help would be much appreciated.

library(ggplot2)
library(ggridges)

 ggplot(iris, aes(x=Sepal.Length, y=Species, fill = factor(stat(quantile)))) +
  stat_density_ridges(
    geom = "density_ridges_gradient", calc_ecdf = TRUE,
    quantiles = 4, quantile_lines = TRUE
  )


Answer:

Try

p + geom_point(data = aggregate(Sepal.Length ~ Species, iris, median),
               aes(x = Sepal.Length, y = Species),
               color = "red",
               size = 5,
               inherit.aes = FALSE)

(along the way you must have called viridis color palette it seems)

data

library(ggplot2)
library(ggridges)

p <- ggplot(iris, aes(x=Sepal.Length, y=Species, fill = factor(stat(quantile)))) +
  stat_density_ridges(
    geom = "density_ridges_gradient", calc_ecdf = TRUE,
    quantiles = 4, quantile_lines = TRUE
  )

Question:

Why is the top of the plot cut off and how do I fix this? I've increased the margins and it made no difference.

See the curve for year 1854, the very top of the left hump. It appears the line is thinner at the top of the hump. For me, changing the size to 0.8 does not help.

This is the code needed to produce this example:

library(tidyverse)
library(ggridges)

t2 <-   structure(list(Date = c("1853-01", "1853-02", "1853-03", "1853-04", 
                                "1853-05", "1853-06", "1853-07", "1853-08", "1853-09", "1853-10", 
                                "1853-11", "1853-12", "1854-01", "1854-02", "1854-03", "1854-04", 
                                "1854-05", "1854-06", "1854-07", "1854-08", "1854-09", "1854-10", 
                                "1854-11", "1854-12"), t = c(-5.6, -5.3, -1.5, 4.9, 9.8, 17.9, 
                                                             18.5, 19.9, 14.8, 6.2, 3.1, -4.3, -5.9, -7, -1.3, 4.1, 10, 16.8, 
                                                             22, 20, 16.1, 10.1, 1.8, -5.6), year = c("1853", "1853", "1853", 
                                                                                                      "1853", "1853", "1853", "1853", "1853", "1853", "1853", "1853", 
                                                                                                      "1853", "1854", "1854", "1854", "1854", "1854", "1854", "1854", 
                                                                                                      "1854", "1854", "1854", "1854", "1854")), row.names = c(NA, -24L
                                                                                                      ), class = c("tbl_df", "tbl", "data.frame"), .Names = c("Date", 
                                                                                                                                                              "t", "year"))

# Density plot -----------------------------------------------
jj <- ggplot(t2, aes(x = t, y = year)) +
  stat_density_ridges(
    geom = "density_ridges_gradient",
    quantile_lines = TRUE,
    size = 1,
    quantiles = 2) +
  theme_ridges() +
  theme(
    plot.margin = margin(t = 1, r = 1, b = 0.5, l = 0.5, "cm") 
  )


# Build ggplot and extract data
d <- ggplot_build(jj)$data[[1]]

# Add geom_ribbon for shaded area
jj +
  geom_ribbon(
    data = transform(subset(d, x >= 20), year = group),
    aes(x, ymin = ymin, ymax = ymax, group = group),
    fill = "red",
    alpha = 0.5) 

Answer:

Some commenters say they cannot reproduce this problem, but it does absolutely exist. It's easier to see if we increase the line size:

library(ggridges)
library(ggplot2)
ggplot(iris, aes(x = Sepal.Length, y = Species)) + 
  geom_density_ridges(size = 2)

It's a property of how ggplot expands discrete scales. The density line extends beyond the normal additive expansion value that ggplot uses (the magnitude of which is the distance from the "setosa" baseline to the x axis). In this situation, ggplot expands the axis further, but only exactly to the maximum data point. Therefore, half of the line extends beyond the plot area at that maximum point and that half is cut off.

The upcoming ggplot2 2.3.0 (currently available via github) will have two new ways of dealing with this problem. First, you can set clip = "off" in the coordinate system to allow the line to extend beyond the plot range:

ggplot(iris, aes(x = Sepal.Length, y = Species)) + 
  geom_density_ridges(size = 2) +
  coord_cartesian(clip = "off")

Second, you can separately expand the bottom and the top part of the scale. For discrete scales, I prefer additive expansion, and I think in this case we want to make the lower value smaller than the default but the upper value quite a bit larger:

ggplot(iris, aes(x = Sepal.Length, y = Species)) + 
  geom_density_ridges(size = 2) +
  scale_y_discrete(expand = expand_scale(add = c(0.2, 1.5)))

Question:

If I want to add a point estimate to a ggridge object, but I keep getting an error:

library(ggplot2)
library(ggridges)

iris_med <- iris %>% group_by(Species) %>% summarise(Sepal.Length = median(Sepal.Length))

ggplot(iris, aes(x = Sepal.Length, y = Species, fill = 0.5 - abs(0.5-..ecdf..))) +
  stat_density_ridges(geom = "density_ridges_gradient", calc_ecdf = TRUE) +
  geom_point(aes(x = Sepal.Length, y = Species, color = "red"), data = iris_med)

Picking joint bandwidth of 0.181
Error in eval(expr, envir, enclos) : object 'ecdf' not found

Output I am hoping to achieve:


Answer:

The problem can be solved by specifying inherit.aes = F in the geom_point call:

ggplot(iris, aes(x = Sepal.Length, y = Species, fill = 0.5 - abs(0.5-..ecdf..))) +
  stat_density_ridges(geom = "density_ridges_gradient", calc_ecdf = TRUE) +
  geom_point(aes(x = Sepal.Length, y = Species, color = "red"), data = iris_med, inherit.aes = F)

produces just he following message:

Picking joint bandwidth of 0.181

EDIT: Another approach (thanks to @Axeman's comment) would be to move the the fill aesthetic to stat_density_ridges layer.

Question:

Please have a look at the following histograms of temperature for the various months of the year. I limit the temperature to 50+ degrees to purposefully force some of the the histograms to be small, for the colder months. Make note of months 1, 2, and 3, so small they barely register on the facet plot.

library(nycflights13)
library(ggplot2)
library(dplyr)
ggplot(weather %>% filter(temp > 50), aes(temp)) +
  geom_histogram() + 
  facet_wrap(~ as.factor(month))

This ggridges package is awesome. It also plots histograms. By default it scales the histograms such that the y-values are relatively the same height. How do I disable this? I know I have to somehow specify height = ..stat_identity_count.. or height = ..y.. but I've tried every conceivable combination and can't figure it out. In the plot below months 1, 2, and 3, which were barely noticeable above, have now been scaled to become enormous. I want the height of the y-axis to reflect actual counts of their respective histogram bins. Like the original facet wrap example.

library(ggridges)
ggplot(weather %>% filter(temp > 50), aes(x = temp, y = as.factor(month))) + 
  geom_density_ridges()

and I do understand it can often be easier to compare histograms by ..density.. vs absolute counts, but that's not what's desired in my current analysis.


Answer:

This can be done with stat_density(), using the ..count.. aesthetic instead of ..density..:

ggplot(weather %>% filter(temp > 50),
       aes(x = temp, y = as.factor(month),
           group = as.factor(month), height = ..count..)) + 
  geom_density_ridges(stat = "density")

Question:

I've been searching around for a way to use png images as axis ticks in ggplot2, more precisely in ggridges I've read and tried to replicate answers to these posts, but packages have changed a lot in syntax from the time when they were posted to the date:

Annotate ggplot with an extra tick and label How can I use a graphic imported with grImport as axis tick labels in ggplot2 (using grid functions)? Icons as x-axis labels in R - ggplot2

I'd like to add png (or another kind of) images to tick labels, instead of the labels virginica, setosa and versicolor;

library(ggridges)
library(ggplot2)
ggplot(iris, aes(x = Sepal.Length, y = Species)) + geom_density_ridges()

Answer:

cowplot package has made this somewhat easier.

Build the plot:

library(ggridges)
library(ggplot2)

p <- ggplot(iris, aes(x = Sepal.Length, y = Species)) + geom_density_ridges()

Load the images and use axis_canvas() to build a strip of vertical images:

library(cowplot)

pimage <- axis_canvas(p, axis = 'y') + 
  draw_image("https://upload.wikimedia.org/wikipedia/commons/thumb/9/9f/Iris_virginica.jpg/295px-Iris_virginica.jpg", y = 2.5, scale = 0.5) +
  draw_image("https://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Iris_versicolor_3.jpg/320px-Iris_versicolor_3.jpg", y = 1.5, scale = 0.5) +
  draw_image("https://upload.wikimedia.org/wikipedia/commons/thumb/5/56/Kosaciec_szczecinkowaty_Iris_setosa.jpg/450px-Kosaciec_szczecinkowaty_Iris_setosa.jpg", y = 0.5, scale = 0.5)


# insert the image strip into the plot
ggdraw(insert_yaxis_grob(p, pimage, position = "left"))

Without the axis.text.y:

p <- ggplot(iris, aes(x = Sepal.Length, y = Species)) + geom_density_ridges() +
  theme(axis.text.y = element_blank())
ggdraw(insert_yaxis_grob(p, pimage, position = "left"))

You could remove also the vertical line, currently I can't find a way of having the image strip on the left-side of the axis line.

Question:

There probably is an obvious solution to this, but I'm a bit naive with plotting. I'd like to have point_shape and point_fill defined manually to indicate e.g. gender groups. How would I go about doing this?

In other words, I'd like to see e.g. green squares for females and blue triangles for non-females (as an arbitrary example). (A commenter mentioned that there are no points on the plot, but there are when using the current development version of ggridges.)

# Simulate data:
df <- data.frame(female = factor(sample(0:1, size = 500, replace = TRUE)),
                 intervention = factor(sample(0:1, size = 500, replace = TRUE))) %>% 
  dplyr::mutate(value = ifelse(female == "1", runif(n = 500, min = 0, max = 100), 
                               rnorm(n = 500, mean = 50, sd = 20)))

# Draw plot:
df %>% 
  ggplot2::ggplot(aes(y = intervention)) +
  ggridges::geom_density_ridges2(aes(x = value, 
                                     colour = "black", 
                                     fill = female),
                                 scale = .7,
                                 alpha = 0.6, 
                                 size = 0.25,
                                 jittered_points = TRUE, 
                                 point_shape = 21,
                                 point_size = 0.85,
                                 point_fill = "black")


Answer:

Package author here. What's going on is the following: In the general case, you want to be able to style point color, size, etc. independently from line color, size, etc. However, standard ggplot can't do this. It has only one color aesthetic, for example, that is applied to all points and lines.

To work around this issue I created new aesthetics point_color, point_size, point_shape, etc. that apply specifically to points. You can map data onto them as you normally would. However, ggplot then doesn't have a way to create scales for them, and therefore I created scale_discrete_manual() (and a few other scales) that you can use to define appropriate scales.

Putting all of this together, you arrive at something like this:

# Draw plot:
df %>% 
  ggplot2::ggplot(aes(y = intervention)) +
  ggridges::geom_density_ridges2(aes(x = value, 
                                     point_color = female,
                                     point_fill = female,
                                     point_shape = female,
                                     fill = female),
                                 scale = .7,
                                 alpha = 0.6, 
                                 size = 0.25,
                                 jittered_points = TRUE, 
                                 point_size = 0.85) +
  ggplot2::scale_fill_manual(values = c("#A0FFA0", "#A0A0FF")) +
  ggridges::scale_discrete_manual(aesthetics = "point_color", values = c("#00BF00", "#0000BF")) +
  ggridges::scale_discrete_manual(aesthetics = "point_fill", values = c("#80FF80", "#8080FF")) +
  ggridges::scale_discrete_manual(aesthetics = "point_shape", values = c(22, 24))