In the following example (using the iris dataset), I am creating a factor class variable in which one of the species does not contain values of level C. When I make the plot, I cannot find a way to make ggplot not drop the empty level (virginica-C). In a previous post (from 10 years ago), it indicates to use the argument drop = FALSE, but it is not working for me. any suggestions?
require(dplyr)
require(ggplot2)
iris %>%
mutate(fct_x = factor(x = sample(x = c("A", "B", "C"), size = nrow(.), replace = TRUE),
levels = c("A", "B", "C"))) %>%
filter(!(Species == "virginica" & fct_x == "C")) %>%
ggplot(aes(x = Species, y = Sepal.Length, fill = fct_x)) +
geom_boxplot() +
scale_fill_discrete(drop = FALSE)
In other words, the code shown above generates the following graphic. As you can see, the virginica group does NOT show an empty space for group C (because there are no elements of type virginica-C) and that is exactly what I want to achieve: to show that empty space in the figure.

PS: There is also another similar post (from 6 years ago) in which they suggest placing values outside the limits. It is not a bad idea when you have to make a point plot, but in my case I am making a script that generates automatic plots from incoming information and, therefore, I cannot limit the y-axis since the script itself defines the ylim according to the values that appear.
You could get the empty slot by faceting with scales = "free_x" and using scale_x_discrete(drop = FALSE):
(The strip labels could be moved to the bottom, and the fct_x labels & gaps between facets removed, if preferred per the second example.)
require(dplyr)
require(ggplot2)
iris %>%
mutate(fct_x = factor(
x = sample(x = c("A", "B", "C"), size = nrow(.), replace = TRUE),
levels = c("A", "B", "C")
)) %>%
filter(!(Species == "virginica" & fct_x == "C")) %>%
ggplot(aes(x = fct_x, y = Sepal.Length, fill = fct_x)) +
geom_boxplot() +
facet_wrap(~ Species, scales = "free_x") +
scale_x_discrete(drop = FALSE)

Created on 2022-06-16 by the reprex package (v2.0.1)
# Mimicing the original plot
require(dplyr)
require(ggplot2)
iris %>%
mutate(fct_x = factor(
x = sample(x = c("A", "B", "C"), size = nrow(.), replace = TRUE),
levels = c("A", "B", "C")
)) %>%
filter(!(Species == "virginica" & fct_x == "C")) %>%
ggplot(aes(x = fct_x, y = Sepal.Length, fill = fct_x)) +
geom_boxplot() +
facet_wrap(~ Species, scales = "free_x", strip.position = "bottom") +
scale_x_discrete(drop = FALSE) +
theme(axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
strip.background = element_blank(),
panel.spacing = unit(0, "lines")) +
labs(x = "Species")

Created on 2022-06-16 by the reprex package (v2.0.1)
You can specify the position function in the geom_boxplot call. In dodge2 (the default position parameter) you can set preserve="single" so the width of all the single columns is the same.
iris %>%
mutate(fct_x = factor(x = sample(x = c("A", "B", "C"), size = nrow(.), replace = TRUE),
levels = c("A", "B", "C"))) %>%
filter(!(Species == "virginica" & fct_x == "C")) %>%
mutate(fct_x = factor(fct_x, levels = c("A", "B", "C"))) %>%
ggplot(aes(x = Species, y = Sepal.Length, fill = fct_x)) +
geom_boxplot(position=position_dodge2(preserve="single"))
See the definition of position_dodge2(): https://ggplot2.tidyverse.org/reference/position_dodge.html
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With