I want to use facet_grid with stat_compare_means and have individual scales for y. I thought scale = "free" would take care of it, but it doesn't. Level "B" in my Name factor is not scaled properly.
library(tidyverse)
library(ggpubr)
set.seed(2)
my_comparisons <- list(c("Cond1","Cond2"),
c("Cond2","Cond3"),
c("Cond1","Cond3"))
nrName <- 4
nrCond <- 3
nrValue <- 5
Name <- rep(c("A","B","C","D"),each=nrValue*nrCond)
Condition <- rep(c("Cond1","Cond2","Cond3"),length.out=nrName*nrCond*nrValue)
Score <- c(rnorm(nrValue*nrCond,6,1),rnorm(nrValue*nrCond,0.01,0.01),rnorm(nrValue*nrCond,7,1),rnorm(nrValue*nrCond,7,1))
df <- data.frame(Name = Name, Condition = Condition, Score = Score)
plt <- ggplot(df,
aes(x= Condition,y= Score, fill= Condition))+
#geom_flat_violin(position = position_nudge(x = .2),adjust = 2)+
geom_point(position = position_jitter(width = .15), size = .25)+
geom_boxplot(outlier.shape = NA, alpha = 0.3, width = .1, colour = "BLACK") +
facet_grid(reformulate("Name","."),scales = 'free')+
scale_y_continuous(expand = expansion(mult = c(0, 0.1)))+
stat_compare_means(
comparisons = my_comparisons,
method = "t.test",paired = TRUE,
label.y.npc = 0.1)
print(plt)
I thought it might be related to the labels in stat_compare_means. But removing stat_compare_means yields similar result.
What am I not considering here?
I see my confusion now. As RonakShah pointed out correctly, I use geom_flat_violin from the the PupillometryR in my original code. I wanted to simplify the problem when I wrote the post here which is the reason why I showed the boxplots. If I add the line with the geom_flat_vioilin back in, and use greom_wrap as suggested by Suren, the scale = "free" option doesn't work anymore.
ggplot(df,
aes(x= Condition,y= Score,fill= Condition))+
geom_flat_violin(position = position_nudge(x = .2))+
geom_point(position = position_jitter(width = .15), size = .25)+
geom_boxplot(outlier.shape = NA, alpha = 0.3, width = .1, colour = "BLACK") +
facet_wrap(reformulate("Name","."),scales = 'free',nrow = 1)+
scale_y_continuous(expand = expansion(mult = c(0, 0.1)))
I guess I have to check out the geom_flat_violin function for further debuging.
Instead of facet_grid use facet_wrap for example,
facet_wrap(reformulate("Name","."), scales = 'free', nrow = 1) +
With facet_grid one can not get both x and y scales free; see here https://github.com/tidyverse/ggplot2/issues/1152
Related
I would like to create a raincloud plot. I have successfully done it. But I would like to know if instead of the density curve, I can put a histogram (it's better for my dataset).
This is my code if it can be usefull
ATSC <- ggplot(data = data, aes(y = atsc, x = numlecteur, fill = numlecteur)) +
geom_flat_violin(position = position_nudge(x = .2, y = 0), alpha = .5) +
geom_point(aes(y = atsc, color = numlecteur), position = position_jitter(width = .15), size = .5, alpha = 0.8) +
geom_point(data = sumld, aes(x = numlecteur, y = mean), position = position_nudge(x = 0.25), size = 2.5) +
geom_errorbar(data = sumld, aes(ymin = lower, ymax = upper, y = mean), position = position_nudge(x = 0.25), width = 0) +
guides(fill = FALSE) +
guides(color = FALSE) +
scale_color_brewer(palette = "Spectral") +
scale_y_continuous(breaks=c(0,2,4,6,8,10), labels=c("0","2","4","6","8","10"))+
scale_fill_brewer(palette = "Spectral") +
coord_flip() +
theme_bw() +
expand_limits(y=c(0, 10))+
xlab("Lecteur") + ylab("Age total sans check")+
raincloud_theme
I think we can maybe put the "geom_histogram()" but it doesn't work
Thank you in advance for your help !
(sources : https://peerj.com/preprints/27137v1.pdf
https://neuroconscience.wordpress.com/2018/03/15/introducing-raincloud-plots/)
This is actually not quite easy. There are a few challenges.
geom_histogram is "horizontal by nature", and the custom geom_flat_violin is vertical - as are boxplots. Therefore the final call to coord_flip in that tutorial. In order to combine both, I think best is switch x and y, forget about coord_flip, and use ggstance::geom_boxploth instead.
Creating separate histograms for each category is another challenge. My workaround to create facets and "merge them together".
The histograms are scaled way bigger than the width of the points/boxplots. My workaround scale via after_stat function.
How to nudge the histograms to the right position above Boxplot and points - I am converting the discrete scale to a continuous by mapping a constant numeric to the global y aesthetic, and then using the facet labels for discrete labels.
library(tidyverse)
my_data<-read.csv("https://data.bris.ac.uk/datasets/112g2vkxomjoo1l26vjmvnlexj/2016.08.14_AnxietyPaper_Data%20Sheet.csv")
my_datal <-
my_data %>%
pivot_longer(cols = c("AngerUH", "DisgustUH", "FearUH", "HappyUH"), names_to = "EmotionCondition", values_to = "Sensitivity")
# use y = -... to position boxplot and jitterplot below the histogram
ggplot(data = my_datal, aes(x = Sensitivity, y = -.5, fill = EmotionCondition)) +
# after_stat for scaling
geom_histogram(aes(y = after_stat(count/100)), binwidth = .05, alpha = .8) +
# from ggstance
ggstance::geom_boxploth( width = .1, outlier.shape = NA, alpha = 0.5) +
geom_point(aes(color = EmotionCondition), position = position_jitter(width = .15), size = .5, alpha = 0.8) +
# merged those calls to one
guides(fill = FALSE, color = FALSE) +
# scale_y_continuous(breaks = 1, labels = unique(my_datal$EmotionCondition))
scale_color_brewer(palette = "Spectral") +
scale_fill_brewer(palette = "Spectral") +
# facetting, because each histogram needs its own y
# strip position = left to fake discrete labels in continuous scale
facet_wrap(~EmotionCondition, nrow = 4, scales = "free_y" , strip.position = "left") +
# remove all continuous labels from the y axis
theme(axis.title.y = element_blank(), axis.text.y = element_blank(),
axis.ticks.y = element_blank())
Created on 2021-04-15 by the reprex package (v1.0.0)
I am trying to create a plot in ggplot2 similar to this one:
Here is the code I am using:
Dataset %>%
group_by(Participant, Group, Emotion) %>%
ggplot(aes(y = Score, x = Emotion, fill = Group, colour = Group)) +
geom_flat_violin(position = position_nudge(x = .2, y = 0), alpha = .4) +
geom_point(aes(y = Score, color = Group), position = position_jitter(width = .15), size = 3, alpha = 0.4) +
stat_summary(aes(y = Score, group = Emotion), fun.y = mean, geom="line", size = 2.2, alpha = 1.2, width = 0.25, colour = 'gray48') +
stat_summary(fun = mean, geom = 'pointrange', width = 0.2, size = 2, alpha = 1.2, position=position_dodge(width=0.3)) +
stat_summary(fun.data = mean_se, geom='errorbar', width = 0.25, size = 2.2, alpha = 1.2, linetype = "solid",position=position_dodge(width=0.3)) +
guides(color = FALSE) +
scale_color_brewer(palette = "Dark2") +
scale_fill_brewer(palette = "Dark2") +
ylim(0, 100) +
graph_theme
What I am failing to do is set up the stat_summary(geom = 'line') to connect the green and orange means within each emotion on the x-axis. Could anyone give any pointers on this? I'd also like all the other features to stay the same if possible (e.g., I wouldn't like to use facet_grid or facet_wrap).
Thank you!
When I change the group argument in stat_summary to 'Group' instead of 'Emotion', means for each group are connected across emotions, but I can't figure out how to connect means of different groups within each emotion:
This is a tricky one because your line needs to connect points that have different x values but even if you jitter in the point layer, they still technically have the same x value so the line doesn't know how to connect them. What others have done is to manually add the jitter to force the points to have a different x position. For more inspiration check out this, this and this. Here's an example:
library(tidyverse)
set.seed(1)
emotion <- c("anger", "fear", "sadness")
group <- letters[1:2]
participant <- 1:10
dat <- expand_grid(emotion, group, participant) %>%
mutate(across(everything(), as.factor),
score = sample(x = 1:100, size = nrow(.), replace = T))
dat %>%
mutate(new_emot = case_when(
group == "a" ~as.numeric(emotion) - 0.125,
group == "b" ~as.numeric(emotion) + 0.125
)) %>%
ggplot(aes(x = emotion, y = score)) +
stat_summary(aes(color = group), fun = mean, geom = "point", position = position_dodge(width = 0.5)) +
stat_summary(aes(color = group), fun.data = mean_se, geom = "errorbar", width = 0.5, position = position_dodge(width = 0.5)) +
stat_summary(aes(x = new_emot, group = emotion), fun = mean, geom = "line") +
theme_bw()
Created on 2021-03-24 by the reprex package (v1.0.0)
Setting geom_line to the same position as pointrange and errorbar will solve the problem.
i.e.,
stat_summary(aes(y = Score, group = Emotion), fun.y = mean, geom="line", size = 2.2, alpha = 1.2, width = 0.25, colour = 'gray48', position=position_dodge(width=0.3))
I want to use ggplot to show points and lines, but I want there to be two legends - one for the points and one for the lines.
I managed to do this using the below code, but for some reason the 'size' option no longer responds in geom_point and they are stuck at the fairly ugly size you can see in the image
.
Note that I chose stroke = NA because I do not want the points to have a border. The code is below.
Any ideas?
ggplot(data = plot_data) +
geom_point(aes(x = z.1, y = obs, fill = treatcat), alpha = 0.4, shape = 21, stroke = NA, size = 1) +
geom_line(aes(x = z.1, y = under, colour = "True"), linetype = "dashed") +
geom_line(aes(x = z.1, y = crude, colour = "Crude"), size = 1.5) +
scale_fill_manual(name = "Treatment",
values = c("0" = "#F8766D", "1" = "#C77CFF"),
breaks = c("0", "1"),
labels = c("Untreated", "Treated")) +
scale_colour_manual(name = "Model",
values = c("Crude" = "orange", "True" = "black"),
breaks = c("Crude", "True"),
labels = c("Crude", "True")) +
ylim(-30,27.5) +
theme(plot.title = element_text(size = "12")) +
labs(title = "Fitted Values for Crude Model", x = "Z", y = "Y(1)")
Maybe you want two color scales, here a solution with ggnewscale. There are a couple of github packages with similar functionality on the horizon (relayer, and ggh4x), but currently this is the only CRAN option to my knowledge.
As per comment - I am using see::geom_point2 because I also don't like those strokes
library(ggplot2)
library(see)
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point2(aes(color = Petal.Width), alpha = 0.4, size = 10) +
ggnewscale::new_scale_color() +
geom_smooth(aes(color = Species), linetype = "dashed", method = "lm")
Currently, there is a bug in ggplot2 that makes it impossible to change size once stroke = NA (https://github.com/tidyverse/ggplot2/issues/4624). Apprarently, setting 'stroke = 0' also does not eliminate the border.
To do what you want, you need to set set color to 'transparent':
library(ggplot2)
df = data.frame(x=rnorm(100), y=rnorm(100))
ggplot(df, aes(x, y)) + geom_point(shape=21, stroke=0, fill="orange", color="transparent", size=8)
Created on 2021-09-20 by the reprex package (v2.0.1)
I am having much trouble configuring plot legend in ggplot2. I have two data frames:
require(ggplot2)
mat <- rep(c("None", "wood", "steel"), each = 4)
feet = rep(seq(0,3), 3)
load = c(3:6, 4:7, 5:8)
soil <- data.frame(
feet = feet,
test = rep(1:3, each = 4),
load = c(0.1, 0.2, 0.3, 0.04,
0.5, 0.6, 0.7, 0.44,
0.8, 0.9, 1.0, 0.74)
)
dat <- rbind(
data.frame(
feet = feet,
mat = mat,
load = c(3:6, 4:7, 5:8),
SF = FALSE
),
data.frame(
feet = feet,
mat = mat,
load = c(6:9, 7:10, 8:11),
SF = TRUE
)
)
I would like a plot with a legend for dat$mat and a legend for soil$test:
myplot <- ggplot(dat, aes(x = load, y = feet)) +
geom_line(aes(color = mat, linetype = SF)) +
geom_path(dat = soil, aes(x = load, y = feet, color = factor(test)))
myplot
I don't want the legend named SF. Also, I would like to split the legend named mat into two legends, mat (values = "none", "wood", "steel") from the dat data.frame, and test (values = 1, 2, 3) from the soil data.frame.
I've tried theme(legend.position = "none"), and many other various combinations of code that would fill the page if I listed them all. Thanks for any assistance you can offer.
update - there is a much better option offered in this answer. I will leave this because hacking legends with a fake aesthetic might still be needed in certain cases.
As #M-M correctly said - ggplot doesn't want to draw two legends for one aesthetic.
I truly hope that you won't often need to do something like the following hack:
Make a fake aesthetic (I chose alpha), and define the color for each line manually.
Then change your legend keys using override.aes manually.
If you have more than this data to show, consider different ways of visualisation / data separation. A very good thing is facetting.
library(ggplot2)
library(dplyr)
ggplot(dat, aes(x = load, y = feet)) +
geom_line(aes(color = mat, linetype = SF)) +
geom_path(dat = filter(soil,test ==1),
aes(x = load, y = feet, alpha = factor(test)), color = 'red') +
geom_path(dat = filter(soil,test ==2),
aes(x = load, y = feet, alpha = factor(test)), color = 'brown') +
geom_path(dat = filter(soil,test ==3),
aes(x = load, y = feet, alpha = factor(test)), color = 'green') +
scale_alpha_manual(values = c(rep(1,3))) +
scale_linetype(guide = FALSE) +
guides( alpha = guide_legend(title = 'test',
override.aes = list(color = c('red','brown','green'))))
Or you can make two separate ggplots, then overlay one using cowplot:
library(cowplot) #cowplot_1.0.0
library(ggplot2)
myplot <- ggplot(dat, aes(x = load, y = feet)) +
geom_line(aes(color = mat, linetype = SF)) +
scale_linetype(guide = FALSE) +
lims(x = c(0,11), y = c(0,3)) +
theme(legend.justification = c(0, 1), # move the bottom legend up a bit
axis.text.x = element_blank(), # remove all the labels from the base plot
axis.text.y = element_blank(),
axis.title = element_blank())
myplot2 <- ggplot() +
geom_path(dat = soil, aes(x = load, y = feet, color = factor(test))) +
theme_half_open() +
lims(x = c(0,11), y = c(0,3))
aligned_plots <- align_plots(myplot, myplot2, align="hv", axis="tblr")
ggdraw(aligned_plots[[1]]) + draw_plot(aligned_plots[[2]])
Actually, there is a better option than my previous hack - I am sure this must have been around back then, but I was simply not aware of it. Adding a new scale is very easy with ggnewscale.
ggnewscale is currently to my knowledge the only package on CRAN that allows several (discrete!) scales for the same aesthetic. For continuous scales, there is now also ggh4x::scale_color/fill_multi. And there is also Claus Wilke's relayer package on GitHub.
I really like the ggnewscale package because it's super easy to use and works with literally all aesthetics.
ggplot(mapping = aes(x = load, y = feet)) +
geom_line(data = dat, aes(color = mat, linetype = SF)) +
scale_linetype(guide = FALSE) + # This is to delete the linetype legend
ggnewscale::new_scale_color() +
geom_path(data = soil, aes(x = load, y = feet, color = as.factor(test))) +
scale_color_manual("Test", values = c('red','brown','green'))
I have the following code, which produces the following plot:
cols <- brewer.pal(n = 3, name = 'Dark2')
p4 <- ggplot(all.m, aes(x=xval, y=yval, colour = Approach, ymax = 0.95)) + theme_bw() +
geom_errorbar(aes(ymin= yval - se, ymax = yval + se), width=5, position=pd) +
geom_line(position=pd) +
geom_point(aes(shape=Approach, colour = Approach), size = 4) +
geom_hline(aes(yintercept = cp.best$slope, colour = "C2P"), show_guide = FALSE) +
scale_color_manual(name="Approach", breaks=c("C2P", "P2P", "CP2P"), values = cols[c(1,3,2)]) +
scale_y_continuous(breaks = seq(0.4, 0.95, 0.05), "Test AUROC") +
scale_x_continuous(breaks = seq(10, 150, by = 20), "# Number of Patient Samples in Training")
p4 <- p4 + theme(legend.direction = 'horizontal',
legend.position = 'top',
plot.margin = unit(c(5.1, 7, 4.5, 3.5)/2, "lines"),
text = element_text(size=15), axis.title.x=element_text(vjust=-1.5), axis.title.y=element_text(vjust=2))
p4 <- p4 + guides(colour=guide_legend(override.aes=list(shape=c(NA,17,16))))
p4
When I try show_guide = FALSE in geom_point, the shape of the point in the upper legend are all set to default solid circles.
How can I make the lower legend to disappear, without affecting the upper legend?
This is a solution, complete with reproducible data:
library("ggplot2")
library("grid")
library("RColorBrewer")
cp2p <- data.frame(xval = 10 * 2:15, yval = cumsum(c(0.55, rnorm(13, 0.01, 0.005))), Approach = "CP2P", stringsAsFactors = FALSE)
p2p <- data.frame(xval = 10 * 1:15, yval = cumsum(c(0.7, rnorm(14, 0.01, 0.005))), Approach = "P2P", stringsAsFactors = FALSE)
pd <- position_dodge(0.1)
cp.best <- list(slope = 0.65)
all.m <- rbind(p2p, cp2p)
all.m$Approach <- factor(all.m$Approach, levels = c("C2P", "P2P", "CP2P"))
all.m$se <- rnorm(29, 0.1, 0.02)
all.m[nrow(all.m) + 1, ] <- all.m[nrow(all.m) + 1, ] # Creates a new row filled with NAs
all.m$Approach[nrow(all.m)] <- "C2P"
cols <- brewer.pal(n = 3, name = 'Dark2')
p4 <- ggplot(all.m, aes(x=xval, y=yval, colour = Approach, ymax = 0.95)) + theme_bw() +
geom_errorbar(aes(ymin= yval - se, ymax = yval + se), width=5, position=pd) +
geom_line(position=pd) +
geom_point(aes(shape=Approach, colour = Approach), size = 4, na.rm = TRUE) +
geom_hline(aes(yintercept = cp.best$slope, colour = "C2P")) +
scale_color_manual(values = c(C2P = cols[1], P2P = cols[2], CP2P = cols[3])) +
scale_shape_manual(values = c(C2P = NA, P2P = 16, CP2P = 17)) +
scale_y_continuous(breaks = seq(0.4, 0.95, 0.05), "Test AUROC") +
scale_x_continuous(breaks = seq(10, 150, by = 20), "# Number of Patient Samples in Training")
p4 <- p4 + theme(legend.direction = 'horizontal',
legend.position = 'top',
plot.margin = unit(c(5.1, 7, 4.5, 3.5)/2, "lines"),
text = element_text(size=15), axis.title.x=element_text(vjust=-1.5), axis.title.y=element_text(vjust=2))
p4
The trick is to make sure that all of the desired levels of all.m$Approach appear in all.m, even if one of them gets dropped out of the graph. The warning about the omitted point is suppressed by the na.rm = TRUE argument to geom_point.
Short answer:
Just add a dummy geom_point layer (transparent points) where shape is mapped to the same level as in geom_hline.
geom_point(aes(shape = "int"), alpha = 0)
Longer answer:
Whenever possible, ggplot merges / combines legends of different aesthetics. For example, if colour and shape is mapped to the same variable, then the two legends are combined into one.
I illustrate this using simple data set with 'x', 'y' and a grouping variable 'grp' with two levels:
df <- data.frame(x = rep(1:2, 2), y = 1:4, grp = rep(c("a", "b"), each = 2))
First we map both color and shape to 'grp'
ggplot(data = df, aes(x = x, y = y, color = grp, shape = grp)) +
geom_line() +
geom_point(size = 4)
Fine, the legends for the aesthetics, color and shape, are merged into one.
Then we add a geom_hline. We want it to have a separate color from the geom_lines and to appear in the legend. Thus, we map color to a variable, i.e. put color inside aes of geom_hline. In this case we do not map the color to a variable in the data set, but to a constant. We may give the constant a desired name, so we don't need to rename the legend entries afterwards.
ggplot(data = df, aes(x = x, y = y, color = grp, shape = grp)) +
geom_line() +
geom_point(size = 4) +
geom_hline(aes(yintercept = 2.5, color = "int"))
Now two legends appears, one for the color aesthetics of geom_line and geom_hline, and one for the shape of the geom_points. The reason for this is that the "variable" which color is mapped to now contains three levels: the two levels of 'grp' in the original data, plus the level 'int' which was introduced in the geom_hline aes. Thus, the levels in the color scale differs from those in the shape scale, and by default ggplot can't merge the two scales into one legend.
How to combine the two legends?
One possibility is to introduce the same, additional level for shape as for color by using a dummy geom_point layer with transparent points (alpha = 0) so that the two aesthetics contains the same levels:
ggplot(data = df, aes(x = x, y = y, color = grp, shape = grp)) +
geom_line() +
geom_point(size = 4) +
geom_hline(aes(yintercept = 2.5, color = "int")) +
geom_point(aes(shape = "int"), alpha = 0) # <~~~~ a blank geom_point
Another possibility is to convert the original grouping variable to a factor, and add the "geom_hline level" to the original levels. Then use drop = FALSE in scale_shape_discrete to include "unused factor levels from the scale":
datadf$grp <- factor(df$grp, levels = c(unique(df$grp), "int"))
ggplot(data = df, aes(x = x, y = y, color = grp, shape = grp)) +
geom_line() +
geom_point(size = 4) +
geom_hline(aes(yintercept = 2.5, color = "int")) +
scale_shape_discrete(drop = FALSE)
Then, as you already know, you may use the guides function to "override" the shape aesthetics in the legend, and remove the shape from the geom_hline entry by setting it to NA:
guides(colour = guide_legend(override.aes = list(shape = c(16, 17, NA))))