Is it possible to have a second "free" y-axis specifically for the nester "Short leaves"? I do not want to have an independent y-axis for all 3, just 2 for the respective nesters. How can I do that?
library(tidyverse)
library(ggh4x)
df <- as_tibble(iris) %>%
select(3, 5) %>%
mutate(Nester = if_else(Species == "setosa", "Short Leaves", "Long Leaves"),
Nester = factor(Nester))
df %>%
pivot_longer(!c(Species, Nester), names_to = "Measure", values_to = "Value") %>%
ggplot(aes(Measure, Value)) +
geom_boxplot() +
facet_nested(~ Nester + Species)
EDIT:
So far, I only found those two options that do free y-axis for all 3:
facet_nested(~ Nester + Species, scales = "free_y", independent = "y")
and
facet_nested_wrap(~ Nester + Species, scales = "free_y", nrow = 1)
which do not give the desired result.
There isn't really a good amount of control at the facet levels about which panel uses which y-scale. However, facetted_pos_scales() gives you exactly that control.
Let's suppose we have this plot from your example.
library(tidyverse)
library(ggh4x)
df <- as_tibble(iris) %>%
select(3, 5) %>%
mutate(Nester = if_else(Species == "setosa", "Short Leaves", "Long Leaves"),
Nester = factor(Nester))
p <- df %>%
pivot_longer(!c(Species, Nester), names_to = "Measure", values_to = "Value") %>%
ggplot(aes(Measure, Value)) +
geom_boxplot() +
facet_nested(~ Nester + Species, scales = "free_y", independent = "y")
We can 'fix' a scale by giving it a constant limit, which we'd have to pre-calculate. You can then set that scale to some panels.
ylim <- range(df$Petal.Length[df$Nester == "Long Leaves"])
p + facetted_pos_scales(
y = list(Nester == "Long Leaves" ~ scale_y_continuous(limits = ylim))
)
If you also wish to omit the axis in between the panels with fixed scales, you'd need to set the scales separately for each panel. In the middle panel, you'd have to set guide = "none" to hide the axis.
p + facetted_pos_scales(
y = list(
Species == "versicolor" ~ scale_y_continuous(limits = ylim),
Species == "virginica" ~ scale_y_continuous(limits = ylim, guide = "none")
)
)
Created on 2022-08-19 by the reprex package (v2.0.0)
Related
I try to get rid of the nesting (upper) facet label. So far I can only apply changes to all facet labels together and not just the grouping ones.
library(tidyverse)
library(ggh4x)
df <- as_tibble(iris) %>%
mutate(
Nester = if_else(Species == "setosa", "Short Leaves", "Long Leaves"),
Nester = factor(Nester)
) %>%
pivot_longer(!c(Species, Nester), names_to = "Measure", values_to = "Value")
ylim <- df %>%
split(.$Nester) %>%
map(., ~ range(.$Value))
df %>%
ggplot(aes(Measure, Value)) +
geom_boxplot() +
theme(axis.text.x = element_text(angle = 90, , vjust = .5)) +
facet_nested(~ Nester + Species, scales = "free_y", independent = "y") +
facetted_pos_scales(
y = list(
Species == "versicolor" ~ scale_y_continuous(limits = ylim[[1]]),
Species == "virginica" ~ scale_y_continuous(limits = ylim[[1]], guide = "none"),
Species == "setosa" ~ scale_y_continuous(limits = ylim[[2]])
)
)
Created on 2022-08-23 with reprex v2.0.2
I manually edited the plot above to show the desired plot:
I need a method that works with nested facets, as my actual data is far bigger and more complex.
EDIT: The solution needs to use nested facets.
Update on OP request: This is a solution using the color trick white on white is not visible:
From https://teunbrand.github.io/ggh4x/articles/Facets.html
my_strips <- strip_themed(
# Horizontal strips
background_x = elem_list_rect(fill = c("white", "dodgerblue")),
text_x = elem_list_text(colour = c("white", "white"),
face = c("bold", "bold")),
by_layer_x = TRUE,
text_y = elem_list_text(angle = c(0, 90)),
by_layer_y = FALSE
)
df %>%
pivot_longer(!c(Species, Nester), names_to = "Measure", values_to = "Value") %>%
ggplot(aes(Measure, Value)) +
geom_boxplot() +
facet_nested(~ Nester + Species , scales = "free_y", independent = "y",
strip = my_strips)+
theme(
axis.text.x=element_text(angle=90, hjust=0, vjust=0))
First answer:
We could use just facet_wrap for this. No need for facet_nested:
library(tidyverse)
p <- df %>%
pivot_longer(!c(Species, Nester), names_to = "Measure", values_to = "Value") %>%
ggplot(aes(Measure, Value)) +
geom_boxplot() +
theme(axis.text.x = element_text(angle = 90, , vjust = .5)) +
facet_wrap(.~Species)
p
This answer resembles #TarJae's answer, but uses element_blank()s instead. The result is pretty similar, but may be relevant if exporting graphics with a transparent background.
library(tidyverse)
library(ggh4x)
df <- as_tibble(iris) %>%
mutate(
Nester = if_else(Species == "setosa", "Short Leaves", "Long Leaves"),
Nester = factor(Nester)
) %>%
pivot_longer(!c(Species, Nester), names_to = "Measure", values_to = "Value")
ylim <- df %>%
split(.$Nester) %>%
map(., ~ range(.$Value))
strips <- strip_nested(
text_x = list(element_blank(), element_text()),
background_x = list(element_blank(), element_rect()),
by_layer_x = TRUE
)
df %>%
ggplot(aes(Measure, Value)) +
geom_boxplot() +
theme(axis.text.x = element_text(angle = 90, , vjust = .5)) +
facet_nested(
~ Nester + Species, scales = "free_y", independent = "y",
strip = strips
) +
facetted_pos_scales(
y = list(
Species == "versicolor" ~ scale_y_continuous(limits = ylim[[1]]),
Species == "virginica" ~ scale_y_continuous(limits = ylim[[1]], guide = "none"),
Species == "setosa" ~ scale_y_continuous(limits = ylim[[2]])
)
)
Created on 2022-08-23 by the reprex package (v2.0.1)
library(ggplot2)
library(dplyr)
#install.packages("ggrepel")
library(ggrepel)
#Code
mpg %>%
mutate(Color=ifelse(class == '2seater','2seater','Other')) %>%
ggplot(aes(displ, hwy, colour = Color)) +
geom_point() +
geom_text_repel(aes(label = ifelse(Color == '2seater', '2seater', "")),
force_pull = 0, show.legend = FALSE) +
theme(legend.position = "none")
in the above code if I want to add another label for 'compact' how would I change the code please, so i would like 2 labels- one for compact and 2seater
You can do with case_when where you have used if else. [Although you could use an ifelse inside another ifelse. But I find case_when cleaner.]
But have a look. Aren't there too many labels? Why not just leave it color coded?
library(ggplot2)
library(dplyr)
library(ggrepel)
#Code
ggplot2::mpg %>%
mutate(Color = case_when(
class == "2seater" ~ "2seater",
class == "compact" ~ "compact",
TRUE ~ "other"
)) %>%
ggplot(aes(displ, hwy, colour = Color)) +
geom_point() +
geom_text_repel(aes(
label =
case_when(
class == "2seater" ~ "2seater",
class == "compact" ~ "compact"
)
),
force_pull = 0, show.legend = FALSE
) +
scale_color_manual(values = c("red", "blue", "gray")) +
theme(legend.position = "none")
As an alternative to the case_when() method proposed by MarBlo, you can also subset the data while specifying the layer. Upside is that is shorter to write, downside is that the labels don't repel the unlabelled points. As mentioned by MarBlo, ggrepel protests that there might be too many labels.
library(ggplot2)
library(dplyr)
library(ggrepel)
mpg %>%
mutate(Color=ifelse(class == '2seater','2seater','Other')) %>%
ggplot(aes(displ, hwy, colour = Color)) +
geom_point() +
geom_text_repel(aes(label = class),
data = ~ subset(., class %in% c("2seater", "compact")),
force_pull = 0, show.legend = FALSE) +
theme(legend.position = "none")
#> Warning: ggrepel: 31 unlabeled data points (too many overlaps). Consider
#> increasing max.overlaps
Created on 2021-01-10 by the reprex package (v0.3.0)
I am having trouble placing the p-values in the correct position on the y axis of a ggplot using rstatix. I can get the example provided on the package author's blog to work fine, but when I change the values, the positions are incorrect. Here is the working version:
library(tidyverse)
library(rstatix)
##Example provided by the package author which works correctly
df <- ToothGrowth%>%
as_tibble()
#Check df
df
#Stats calculation
stat.test <- df %>%
group_by(dose) %>%
t_test(len ~ supp) %>%
adjust_pvalue(method = "bonferroni") %>%
add_significance()
# Make facet and add p-values
stat.test <- stat.test %>% add_xy_position(x = "supp", fun = "max")
#Check p value positions - y.position looks good
stat.test
#Plot
ggplot(df, aes(x = supp, y = len)) +
geom_boxplot() +
geom_jitter() +
facet_wrap( ~ dose, scales = "free") +
stat_pvalue_manual(stat.test, hide.ns = F,
label = "{p.adj}")
However, when I change the values, the position of the p values are too high.
## My example which plots incorrectly
##--- This is a very inelegant way to change the values!!
df <- ToothGrowth %>%
mutate(helper = paste0(supp, dose))
df$RecordingNo <- ave(seq.int(nrow(df)), df$helper, FUN = seq_along)
df <- df %>%
select(-helper) %>%
pivot_wider(names_from = c(dose), values_from = len) %>%
mutate(`0.5` = `0.5` * 0.1) %>%
mutate(`2` = `2` * 10) %>%
select(-RecordingNo) %>%
pivot_longer(-supp) %>%
rename(len = value, dose = name) %>%
mutate(dose = as_factor(dose)) %>%
as_tibble()
#Check df
df
##------
#This code is exactly the same as the working code above.
#Stats calculation
stat.test <- df %>%
group_by(dose) %>%
t_test(len ~ supp) %>%
adjust_pvalue(method = "bonferroni") %>%
add_significance()
# Make facet and add p-values
stat.test <- stat.test %>% add_xy_position(x = "supp", fun = "max")
#Check p value positions - y.position looks incorrect
stat.test
ggplot(df, aes(x = supp, y = len)) +
geom_boxplot() +
geom_jitter() +
facet_wrap( ~ dose, scales = "free") +
stat_pvalue_manual(stat.test, hide.ns = F,
label = "{p.adj}")
I guess there is a difference in the second dataframe which is causing the problems, but I can't figure it out. Thanks!
Like the scales option on facet_wrap, there is a scales option on add_xy_position that controls the p value position . As I am using "facet_wrap(...,scales = "free")" I should use add_xy_position(...,scales = "free") to make sure the positions match.
In my example:
stat.test <- stat.test %>% add_xy_position(x = "supp", fun = "max",scales = "free")
ggplot(df, aes(x = supp, y = len)) +
geom_boxplot() +
geom_jitter() +
facet_wrap( ~ dose, scales = "free") +
stat_pvalue_manual(stat.test, hide.ns = F,
label = "{p.adj}")
Answer from author's Github page.
I am trying to create a grid of bargraphs that show the average for different species. I am using the iris dataset for this question.
I summarised the data, melted it into long form long, and tried to use facet_wrap.
iris %>%
group_by(Species) %>%
summarise(M.Sepal.Length=mean(Sepal.Length),
M.Sepal.Width=mean(Sepal.Width),
M.Petal.Length= mean(Petal.Length),
M.Petal.Width=mean(Petal.Width)) %>%
gather(key = Part, value = Value, M.Sepal.Length:M.Petal.Width) %>%
ggplot(., aes(Part, Value, group = Species, fill=Species)) +
geom_col(position = "dodge") +
facet_grid(cols=vars(Part)) +
facet_grid(cols = vars(Part))
However, the graph I am getting has x.axis labels that are strung across each facet grid. Additionally the clustered graphs are not centered within each facet box. Instead they appear at the location of their respective x-axis label. I'd like to get rid of the x-axis labels, center the graphs, and scale the graphs within each facet.
Here is an image of the resulting graph marked up with my expected output:
Perhaps this is what you're looking for?
The key changes are:
Remove Part as the variable mapped to x, that way the data is plotted in the same location in every facet
Switch to facet_wrap so you can use scales = "free_y"
Use labs to manually add the x title
Add theme to get rid of the x-axis ticks and tick labels.
library(ggplot2)
library(dplyr) # Version >= 1.0.0
iris %>%
group_by(Species) %>%
summarise(across(1:4, mean, .names = "M.{col}")) %>%
gather(key = Part, value = Value, M.Sepal.Length:M.Petal.Width) %>%
ggplot(., aes(x = 1, y = Value, group = Species, fill=Species)) +
geom_col(position = "dodge") +
facet_wrap(.~Part, nrow = 1, scales = "free_y") +
labs(x = "Part") +
theme(axis.ticks.x = element_blank(),
axis.text.x = element_blank())
I also took the liberty of switching out your manual call to summarise with the new across functionality.
Here's how you might also calculate error bars:
library(tidyr)
iris %>%
group_by(Species) %>%
summarise(across(1:4, list(M = mean, SE = ~ sd(.)/sqrt(length(.))),
.names = "{fn}_{col}")) %>%
pivot_longer(-Species, names_to = c(".value","Part"),
names_pattern = "([SEM]+)_(.+)") %>%
ggplot(., aes(x = 1, y = M, group = Species, fill=Species)) +
geom_col(position = "dodge") +
geom_errorbar(aes(ymin = M - SE, ymax = M + SE), width = 0.5,
position = position_dodge(0.9)) +
facet_wrap(.~Part, nrow = 1, scales = "free_y") +
labs(x = "Part", y = "Value") +
theme(axis.ticks.x = element_blank(),
axis.text.x = element_blank())
I would like to plot some barplots on top of each other using facet_grid:
library(ggplot2)
df <- group_by(mpg, manufacturer) %>%
summarise(cty = mean(cty), hwy = mean(hwy)) %>%
ungroup()
df <- melt(df, id.vars = "manufacturer")
ggplot() +
geom_bar(data =df, aes(x = variable, y = value), stat = "identity") +
facet_grid(manufacturer ~ ., switch = "y")
I use the switchargument of ggplot2::facet_grid()to let the facet labels be displayed on the y-axis instead of on top of each facet. Problem is that the facet labels are plotted vertically and therefore cropped. Is there any way to plot the facet -labels horizontally? All the questions I found so far related to rotating the x-axis labels only, not the facet labels.
You just need to add the theme() and specify the angle in strip.text.y.left.
library(tidyverse)
library(reshape2)
df <- group_by(mpg, manufacturer) %>%
summarise(cty = mean(cty), hwy = mean(hwy)) %>%
ungroup()
df <- melt(df, id.vars = "manufacturer")
ggplot() +
geom_bar(data =df, aes(x = variable, y = value), stat = "identity") +
facet_grid(manufacturer ~ ., switch = "y")+
theme(strip.text.y.left = element_text(angle = 0))
Created on 2020-03-15 by the reprex package (v0.3.0)
Note that strip.text.y.left was added in ggplot2 3.3.0. For earlier versions, you need to write strip.text.y = element_text(angle = 180).