I'm trying to create a barplot that shows the range of points falling in each group, and is shaded by the density of those points (using kernel density or any other method). Here is an example of the barplot using segments, and showing the specific points:
library(dplyr)
library(tidyr)
library(ggplot2)
#sample df
set.seed(100)
df <- data.frame(a=rnorm(100, mean=5, sd=1), b=rnorm(100, mean=10, sd=2), c=rnorm(100, mean=15, sd=3)) %>%
pivot_longer(everything(), names_to = "group", values_to = "value")
#minmax values for geom_segment
df_minmax <- df %>% group_by(group) %>%
summarize(min=min(value), max=max(value))
ggplot() +
geom_segment(data=df_minmax, aes(x=group, y=min, yend=max), size=18, color='black') +
geom_jitter(data=df, aes(x=group, y=value), color='white', width=0.1)
Instead of showing the discrete points, I'd like to replace them with a color scale going from low to high density. The answer here seems similar to what I want to do, except that I'd like to start from the minimum value in each bar, not zero. When I replicate that example using their sample data, I can get close to what I want:
df1 <- data.frame(
y = c(rnorm(100, 4), rnorm(100, 12)),
x = rep(c(1, 2), each = 100)
)
ggplot(df1, aes(x, y, group = x)) +
stat_ydensity(aes(fill = after_stat(density),
xmin = x - 0.4, xmax = x + 0.4,
# Nudge y by a bit depending on the spread of your data
ymin = stat(y) - 0.01, ymax = stat(y) + 0.01),
geom = "rect", trim = FALSE)
However, I'm very confused by the documentation for stat_ydensity, and how to set up the nudging used in the example, and it seems that changing the names of the columns away from x and y causes this to fail. For example, the following fails saying that xdat and ydat are not found:
df3 <- data.frame(
ydat = c(rnorm(100, 4), rnorm(100, 12)),
xdat = rep(c(1, 2), each = 100)
)
ggplot(df3, aes(xdat, ydat, group = xdat)) +
stat_ydensity(aes(fill = after_stat(density),
xmin = xdat - 0.4, xmax = xdat + 0.4,
# Nudge y by a bit depending on the spread of your data
ymin = stat(ydat) - 0.01, ymax = stat(ydat) + 0.01),
geom = "rect", trim = FALSE)
So I can't figure out how to adapt it to my data. Would greatly appreciate any help, either with successfully using stat_ydensity with different data, or with using some other method to apply 1-dimensional density shading onto a bar.
If you change ymin = stat(ydat) - 0.01, ymax = stat(ydat) + 0.01)
to ymin = stat(y) - 0.1, ymax = stat(y) + 0.1
you get an outcome, but the 'density bars' overlap the ymin/ymax values of the segments, e.g. with thicker segments to show the discordance:
library(tidyverse)
set.seed(100)
df <- data.frame(a=rnorm(100, mean=5, sd=1), b=rnorm(100, mean=10, sd=2), c=rnorm(100, mean=15, sd=3)) %>%
pivot_longer(everything(), names_to = "group", values_to = "value")
#minmax values for geom_segment
df_minmax <- df %>% group_by(group) %>%
summarize(min=min(value), max=max(value))
ggplot() +
geom_segment(data = df_minmax, aes(x = group, y = min, yend = max),
color='black', size = 28) +
stat_ydensity(data = df,
aes(x = group,
y = value,
fill = after_stat(density),
xmin = stat(x) - 0.15, xmax = stat(x) + 0.15,
# Nudge y by a bit depending on the spread of your data
ymin = stat(y) - 0.1, ymax = stat(y) + 0.1),
geom = "rect", trim = TRUE)
Created on 2025-06-12 with reprex v2.1.1
Perhaps you could switch from geom_segment()
to geom_rect()
and use e.g.
library(tidyverse)
set.seed(100)
df <- data.frame(a=rnorm(100, mean=5, sd=1), b=rnorm(100, mean=10, sd=2), c=rnorm(100, mean=15, sd=3)) %>%
pivot_longer(everything(), names_to = "group", values_to = "value") %>%
mutate(group = factor(group))
#minmax values for geom_segment
df_minmax <- df %>% group_by(group) %>%
summarize(min=min(value), max=max(value))
ggplot() +
geom_rect(data = df_minmax, aes(xmin = as.numeric(group) - 0.21,
xmax = as.numeric(group) + 0.21,
ymin = min - 0.05,
ymax = max + 0.05),
color = "black", fill = "black") +
stat_ydensity(data = df,
aes(x = group,
y = value,
fill = after_stat(density),
xmin = stat(x) - 0.2, xmax = stat(x) + 0.2,
# Nudge y by a bit depending on the spread of your data
ymin = stat(y) - 0.05, ymax = stat(y) + 0.05),
geom = "rect", trim = TRUE)
Created on 2025-06-12 with reprex v2.1.1
Is this your expected outcome?