I have several plots like the one below. My problem is that the legend for geom_vline() (Type) shifts across plots, sometimes appearing above the "Mean" legend, sometimes below.
How can I specify the position of the geom_vline() legend (or the other legend), such that I do not have variation across plots in my paper?
set.seed(1234)
data <- data.frame(value = rnorm(n = 10000, mean = 50, sd = 20),
Type = sample(letters[1:2], size = 10000, replace = TRUE))
data$value[data$Type == "b"] <- data$value[data$Type == "b"] +
rnorm(sum(data$Type == "b"), mean = 55)
gp <- ggplot(data = data, aes_string(x = "value"))
gp <- gp + geom_density(aes_string(fill = "Type"), alpha = 0.3)
vlines <- data.frame(value = c(mean(data$value[data$Type == "a"]),
mean(data$value[data$Type == "b"])),
Mean = c("A", "B"))
gp2 <- gp + geom_vline(data = vlines, aes(xintercept = value, colour = Mean),
size = 1.05, linetype = "dashed", show.legend = TRUE)
gp3 <- gp2 + geom_vline(xintercept = (50 + 55 + 50) / 2, size = 1.05)
gp3
You can pass the order parameter of guide_legend passed to the guide parameter of the scale_* functions for the guides you want to rearrange. For example:
library(ggplot2)
set.seed(1234)
data <- data.frame(value = rnorm(n = 10000, mean =50, sd = 20),
Type = sample(letters[1:2], size = 10000, replace = TRUE))
data$value[data$Type == "b"] <- data$value[data$Type == "b"] +
rnorm(sum(data$Type == "b"), mean = 55)
vlines <- data.frame(value = c(mean(data$value[data$Type == "a"]),
mean(data$value[data$Type == "b"])),
Mean = c("A", "B"))
ggplot(data, aes(x = value)) +
geom_density(aes(fill = Type), alpha = 0.3) +
geom_vline(data = vlines, aes(xintercept = value, colour = Mean),
size = 1.05, linetype = "dashed", show.legend = TRUE) +
geom_vline(xintercept = (50 + 55 + 50) / 2, size = 1.05) +
scale_fill_discrete(guide = guide_legend(order = 1)) + # fill first
scale_color_discrete(guide = guide_legend(order = 2)) # color second
ggplot(data, aes(x = value)) +
geom_density(aes(fill = Type), alpha = 0.3) +
geom_vline(data = vlines, aes(xintercept = value, colour = Mean),
size = 1.05, linetype = "dashed", show.legend = TRUE) +
geom_vline(xintercept = (50 + 55 + 50) / 2, size = 1.05) +
scale_fill_discrete(guide = guide_legend(order = 2)) + # now fill second
scale_color_discrete(guide = guide_legend(order = 1)) # and color first
Related
I can't seem to arrange the order of legend items when using the new_scale_color() feature of the ggnewscale package. Here's a minimal example:
n <- 10
sd_res <- 1
beta0 <- 0
beta1 <- 1
x <- runif(n, 0, 10)
y <- beta0 + beta1*x + rnorm(n, 0, sd_res)
dat <- data.frame(x,y)
ggplot() + theme_minimal() + labs(x = '', y = '') +
geom_abline(aes(color = 'x', linetype = 'x', slope = 1, intercept = 0)) +
geom_abline(aes(color = 'y', linetype = 'y', slope = 2, intercept = 0)) +
scale_color_manual(name = '', values = c('x' = 'orange', 'y' = 'black')) +
scale_linetype_manual(name = '', values = c('x' = 1, 'y' = 2)) +
new_scale_color() +
geom_point(data=dat, aes(x=x, y=y, shape = 'z', size = 'z', color = 'z'), alpha = 1) +
scale_size_manual(name = '', values = c('z' = 1.5)) +
scale_shape_manual(name = '', values = c('z' = 16)) +
scale_color_manual(name = '', values = c('z' = 'black')) +
theme(legend.position = 'bottom')
What I'd like is for the z-item to appear before the x- and y-items in the legend.
You could use guides with guide_legend and specify the order of your aes. Here I set the order of your "z" to 1 which will set it as the first legend like this:
n <- 10
sd_res <- 1
beta0 <- 0
beta1 <- 1
x <- runif(n, 0, 10)
y <- beta0 + beta1*x + rnorm(n, 0, sd_res)
dat <- data.frame(x,y)
library(ggplot2)
library(ggnewscale)
p <- ggplot() + theme_minimal() + labs(x = '', y = '') +
geom_abline(aes(color = 'x', linetype = 'x', slope = 1, intercept = 0)) +
geom_abline(aes(color = 'y', linetype = 'y', slope = 2, intercept = 0)) +
scale_color_manual(name = '', values = c('x' = 'orange', 'y' = 'black')) +
scale_linetype_manual(name = '', values = c('x' = 1, 'y' = 2)) +
new_scale_color() +
geom_point(data=dat, aes(x=x, y=y, shape = 'z', size = 'z', color = 'z'), alpha = 1) +
scale_size_manual(name = '', values = c('z' = 1.5)) +
scale_shape_manual(name = '', values = c('z' = 16)) +
scale_color_manual(name = '', values = c('z' = 'black')) +
theme(legend.position = 'bottom') +
guides(size = guide_legend(order = 1),
color = guide_legend(order = 1),
shape = guide_legend(order = 1))
p
Created on 2022-08-26 with reprex v2.0.2
When using ggnewscale, I don't recommend using guides() to define guides, since the name of the aesthetics change internally. It's better to use the guide argument of the scale_ function
Example from ggnewscale documentation:
library(ggplot2)
library(ggnewscale)
# Equivalent to melt(volcano)
topography <- expand.grid(x = 1:nrow(volcano),
y = 1:ncol(volcano))
topography$z <- c(volcano)
# point measurements of something at a few locations
set.seed(42)
measurements <- data.frame(x = runif(30, 1, 80),
y = runif(30, 1, 60),
thing = rnorm(30))
ggplot(mapping = aes(x, y)) +
geom_contour(data = topography, aes(z = z, color = stat(level))) +
# Color scale for topography
scale_color_viridis_c(option = "D",
guide = guide_colorbar(order = 2)) +
# geoms below will use another color scale
new_scale_color() +
geom_point(data = measurements, size = 3, aes(color = thing)) +
# Color scale applied to geoms added after new_scale_color()
scale_color_viridis_c(option = "A",
guide = guide_colorbar(order = 8))
Created on 2022-08-26 by the reprex package (v2.0.1)
I want to create a graph where I can change the line size for each line c(1,2,3) and the alpha values for each line c(0.5,0.6,0.7). I tried to use scale_size_manual but it didn't make any difference. Any ideas on how to proceed?
var <- c("T","T","T","M","M","M","A","A","A")
val <- rnorm(12,4,5)
x <- c(1:12)
df <- data.frame(var,val,x)
ggplot(aes(x= x , y = val, color = var, group = var), data = df) +
scale_color_manual(values = c("grey","blue","black")) + geom_smooth(aes(x = x, y = val), formula = "y ~ x", method = "loess",se = FALSE, size = 1) + scale_x_continuous(breaks=seq(1, 12, 1), limits=c(1, 12)) + scale_size_manual(values = c(1,2,3))
To set the size and alpha values for your lines you have to map on aesthetics. Otherwise scale_size_manual will have no effect:
library(ggplot2)
ggplot(aes(x = x, y = val, color = var, group = var), data = df) +
scale_color_manual(values = c("grey", "blue", "black")) +
geom_smooth(aes(x = x, y = val, size = var, alpha = var), formula = "y ~ x", method = "loess", se = FALSE) +
scale_x_continuous(breaks = seq(1, 12, 1), limits = c(1, 12)) +
scale_size_manual(values = c(1, 2, 3)) +
scale_alpha_manual(values = c(.5, .6, .7))
I have created a graph with a curve for each individual and a mean curve that is created the same way. I would like to have a confidence interval on my mean curve. How can I do this? Should the mean curve be created in a different way?
This is my code until now:
DNAMorfR %>%
drop_na(`Normal morphology (%)`) %>%
ggplot(aes(x = Time, y = `Normal morphology (%)`, linetype = Patient, color = Patient, group
= Patient, na.rm = TRUE)) +
geom_line(size = 1) +
theme_minimal() + ggtitle("(A1) Normal morphology") +
geom_point(size = 1.5) +
scale_y_continuous(limits = c(0, 25), breaks=seq(0, 25, by = 5)) +
geom_hline(yintercept = 4, color = "grey", size = 1) +
scale_color_manual(values = c("black", "#FF3333", "#FF9933", "#CC9900"))
And this is my data:
data.frame(
stringsAsFactors = FALSE,
check.names = FALSE,
Patient = c("1","1","1","2","2","2","3","3","3","mean","mean","mean"),
`Normal morphology (%)` = c(7, 2, 3, 1, 3, 3, 6, 7, 8, 7, 9, 8),
Time = as.factor(c("Week 1","Week 2","Week 3","Week 1","Week 2","Week 3","Week 1","Week 2",
"Week 3","Week 1","Week 2","Week 3")))
This could be achieved like so:
Instead of adding the mean as additional rows you could make a summary df using e.g. dplyr::summarize
make use of stat_summay to compute the summary statistics on the fly as I do in my approach below and computed the condfidence interval as mean(x) +/- 1.96 / (length(x) - 1) * sd(x)
library(ggplot2)
library(tidyr)
library(dplyr)
DNAMorfR1 <- DNAMorfR %>%
drop_na(`Normal morphology (%)`) %>%
filter(Patient != "mean")
ggplot(DNAMorfR1, aes(x = Time, y = `Normal morphology (%)`)) +
geom_line(aes(linetype = Patient, color = Patient, group = Patient), size = 1) +
geom_point(aes(color = Patient, group = Patient), size = 1.5) +
stat_summary(aes(color = "mean", linetype = "mean", group = "mean"), geom = "line", fun = "mean") +
stat_summary(aes(color = "mean", group = "mean"), geom = "pointrange", fun = "mean",
fun.min = function(x) mean(x) - 1.96 / (length(x) - 1) * sd(x),
fun.max = function(x) mean(x) + 1.96 / (length(x) - 1) * sd(x), show.legend = FALSE) +
theme_minimal() +
ggtitle("(A1) Normal morphology") +
scale_y_continuous(limits = c(0, 25), breaks=seq(0, 25, by = 5)) +
geom_hline(yintercept = 4, color = "grey", size = 1) +
scale_color_manual(values = c("black", "#FF3333", "#FF9933", "#CC9900"))
You can use geom = "ribbon" to get the 95% CI band to your mean line. Credits to stefan where the main logic is already answered!
DNAMorfR %>%
drop_na(`Normal morphology (%)`) %>%
filter(row_number() <= n()-3) %>%
ggplot(aes(x = Time, y = `Normal morphology (%)`)) +
geom_line(aes(linetype = Patient, color = Patient, group = Patient), size = 1) +
geom_point(aes(color = Patient, group = Patient), size = 2) +
stat_summary(aes(color = "mean", linetype = "mean", group = "mean"), size=1.5, geom = "line", fun = "mean") +
stat_summary(aes(color = "mean", group = "mean"), geom = "ribbon", fun = "mean", size= 0.5, alpha=0.1,
fun.min = function(x) mean(x) - 1.96 / (length(x) - 1) * sd(x),
fun.max = function(x) mean(x) + 1.96 / (length(x) - 1) * sd(x), show.legend = FALSE) +
theme_minimal() +
ggtitle("(A1) Normal morphology") +
scale_y_continuous(limits = c(0, 25), breaks=seq(0, 25, by = 5)) +
geom_hline(yintercept = 4, color = "grey", size = 1) +
scale_color_manual(values = c("black", "#FF3333", "#FF9933", "#CC9900"))
I am trying to reproduce this kind of Figure, with two densities, a first one pointing upwards and a second one pointing downwards. I would also like to have some blank space between the two densities.
Here is the code I am currently using.
library(hrbrthemes)
library(tidyverse)
library(RWiener)
# generating data
df <- rwiener(n = 1e2, alpha = 2, tau = 0.3, beta = 0.5, delta = 0.5)
df %>%
ggplot(aes(x = q) ) +
geom_density(
data = . %>% filter(resp == "upper"),
aes(y = ..density..),
colour = "steelblue", fill = "steelblue",
outline.type = "upper", alpha = 0.8, adjust = 1, trim = TRUE
) +
geom_density(
data = . %>% filter(resp == "lower"),
aes(y = -..density..), colour = "orangered", fill = "orangered",
outline.type = "upper", alpha = 0.8, adjust = 1, trim = TRUE
) +
# stimulus onset
geom_vline(xintercept = 0, lty = 1, col = "grey") +
annotate(
geom = "text",
x = 0, y = 0,
# hjust = 0,
vjust = -1,
size = 3, angle = 90,
label = "stimulus onset"
) +
# aesthetics
theme_ipsum_rc(base_size = 12) +
theme(axis.text.y = element_blank() ) +
labs(x = "Reaction time (in seconds)", y = "") +
xlim(0, NA)
Which results in something like...
How could I add some vertical space between the two densities to reproduce the above Figure?
If you want to try without faceting, you're probably best to just plot the densities as polygons with adjusted y values according to your desired spacing:
s <- 0.25 # set to change size of the space
ud <- density(df$q[df$resp == "upper"])
ld <- density(df$q[df$resp == "lower"])
x <- c(ud$x[1], ud$x, ud$x[length(ud$x)],
ld$x[1], ld$x, ld$x[length(ld$x)])
y <- c(s, ud$y + s, s, -s, -ld$y - s, -s)
df2 <- data.frame(x = x, y = y,
resp = rep(c("upper", "lower"), each = length(ud$x) + 2))
df2 %>%
ggplot(aes(x = x, y = y, fill = resp, color = resp) ) +
geom_polygon(alpha = 0.8) +
scale_fill_manual(values = c("steelblue", "orangered")) +
scale_color_manual(values = c("steelblue", "orangered"), guide = guide_none()) +
geom_vline(xintercept = 0, lty = 1, col = "grey") +
annotate(
geom = "text",
x = 0, y = 0,
# hjust = 0,
vjust = -1,
size = 3, angle = 90,
label = "stimulus onset"
) +
# aesthetics
theme_ipsum_rc(base_size = 12) +
theme(axis.text.y = element_blank() ) +
labs(x = "Reaction time (in seconds)", y = "")
you can try facetting
set.seed(123)
q=rbeta(100, 0.25, 1)
df_dens =data.frame(gr=1,
x=density(df$q)$x,
y=density(df$q)$y)
df_dens <- rbind(df_dens,
data.frame(gr=2,
x=density(df$q)$x,
y=-density(df$q)$y))
ggplot(df_dens, aes(x, y, fill = factor(gr))) +
scale_x_continuous(limits = c(0,1)) +
geom_area(show.legend = F) +
facet_wrap(~gr, nrow = 2, scales = "free_y") +
theme_minimal() +
theme(strip.background = element_blank(),
strip.text.x = element_blank(),
axis.text.y = element_blank(),
axis.title.y = element_blank())
The space between both plots can be increased using panel.spacing = unit(20, "mm"). Instead of facet_grid you can also try facet_grid(gr~., scales = "free_y")
I am trying to create a plot in R using ggplot that shows the difference between my two bars in a nice way.
I found an example that did part of what I wanted, but I have two major problems:
It is based on comparing groups of bars, but I only have two, so I added one group with both of them.
I would like to draw the arrow in nicer shape. I attached an image.
Code:
transactions <- c(5000000, 1000000)
time <- c("Q1","Q2")
group <- c("A", "A")
data <- data.frame(transactions, time, group)
library(ggplot2)
fun.data <- function(x){
print(x)
return(data.frame(y = max(x) + 1,
label = paste0(round(diff(x), 2), "cm")))
}
ylab <- c(2.5, 5.0, 7.5, 10)
gg <- ggplot(data, aes(x = time, y = transactions, fill = colors_hc[1], label = round(transactions, 0))) +
geom_bar(stat = "identity", show.legend = FALSE) +
geom_text(position = position_dodge(width = 0.9),
vjust = 1.1) +
geom_line(aes(group = group), position = position_nudge(0.1),
arrow = arrow()) +
stat_summary(aes(x = group, y = transactions),
geom = "label",
fun.data = fun.data,
fontface = "bold", fill = "lightgrey",
inherit.aes = FALSE) +
expand_limits(x = c(0, NA), y = c(0, NA)) +
scale_y_continuous(labels = paste0(ylab, "M"),
breaks = 10 ^ 6 * ylab)
gg
The arrows I am aiming for:
Where I am (ignore the ugliness, didn't style it yet):
This works, but you still need to play around a bit with the axes (or rather beautify them)
library(dplyr)
library(ggplot2)
transactions <- c(5000000, 1000000)
time <- c("Q1","Q2")
group <- c("A", "A")
my_data <- data.frame(transactions, time, group)
fun.data <- function(x){
return(data.frame(y = max(x) + 1,
label = as.integer(diff(x))))
}
my_data %>%
ggplot(aes(x = group, y = transactions, fill = time)) +
geom_bar(stat = 'identity', position = 'dodge') +
geom_text(aes(label = as.integer(transactions)),
position = position_dodge(width = 0.9),
vjust = 1.5) +
geom_line(aes(group = group), position = position_nudge(0.1),
arrow = arrow()) +
stat_summary(aes(x = group, y = transactions),
geom = "label",
size = 5,
position = position_nudge(0.05),
fun.data = fun.data,
fontface = "bold", fill = "lightgrey",
inherit.aes = FALSE)
Edit2:
y_limit <- 6000000
my_data %>%
ggplot(aes(x = time, y = transactions)) +
geom_bar(stat = 'identity',
fill = 'steelblue') +
geom_text(aes(label = as.integer(transactions)),
vjust = 2) +
coord_cartesian(ylim = c(0, y_limit)) +
geom_segment(aes(x = 'Q1', y = max(my_data$transactions),
xend = 'Q1', yend = y_limit)) +
geom_segment(aes(x = 'Q2', y = y_limit,
xend = 'Q2', yend = min(my_data$transactions)),
arrow = arrow()) +
geom_segment(aes(x = 'Q1', y = y_limit,
xend = 'Q2', yend = y_limit)) +
geom_label(aes(x = 'Q2',
y = y_limit,
label = as.integer(min(my_data$transactions)- max(my_data$transactions))),
size = 10,
position = position_nudge(-0.5),
fontface = "bold", fill = "lightgrey")