I am frustrated from ggplot2
overwriting arguments that have been set before. In fact I encouter the described problem with different ggplot2
functions, but in order to illustrate what I mean I write only about scale_y_continuous()
. So let's say I provide a different breaks
argument for scale_y_continuous()
as here:
df <- data.frame(sex= factor(sample(c("male", "female"), 100, replace= TRUE)))
library(ggplot2)
my_plot <- ggplot(df, aes(sex)) +
geom_bar() +
scale_y_continuous(breaks= 1:nrow(df))
my_plot
This returns the expected plot:
Let's assume I got the my_plot
object from a colleague and I want to further work on the layout by changing the y axis to start at zero, for example. So what I do is:
my_plot2 <- my_plot +
scale_y_continuous(expand= expansion(mult= c(0, 0.1)))
my_plot2
The problem is that adding another argument to scale_y_continuous()
changes the former breaks
argument back to default. The breaks are not in one steps anymore. How can I hinder ggplot2
from overwriting the arguments that have been set before?
EDIT
@langtang provided a great answer but the problem is that when we select my_plot$scales$scales[[1]]$expand
the order in which the scales have been defined in my_plot
matters. See the following three examples:
# define x then y scale
my_plot1 <- ggplot(df, aes(sex)) +
geom_bar() +
scale_x_discrete(labels= LETTERS[1:2]) +
scale_y_continuous(breaks= 1:nrow(df))
# same code, but first y then x scale
my_plot2 <- ggplot(df, aes(sex)) +
geom_bar() +
scale_y_continuous(breaks= 1:nrow(df)) +
scale_x_discrete(labels= LETTERS[1:2])
# no scales defined
my_plot3 <- ggplot(df, aes(sex)) +
geom_bar()
Doing my_plot1$scales$scales[[1]]$expand <- expansion(mult=c(0,.1))
changes the x axis not the y axis. Doing my_plot2$scales$scales[[1]]$expand <- expansion(mult=c(0,.1))
changes the y axis (as desired). my_plot3$scales$scales[[1]]$expand <- expansion(mult=c(0,.1))
gives an error. So it all depends on how my_plot
has been defined and I don't know in what order the functions were added (if at all) if I just get the my_plot
abject.
You can access the scales directly and update like this:
my_plot$scales$scales[[1]]$expand = expansion(mult=c(0,.1))
Note that my_plot
will still have the original breaks
set:
my_plot$scales$scales[[1]]$breaks
[1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
[49] 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
[97] 97 98 99 100
You could write a function that adds a new scale if not there, or updates an existing scale:
update_scale <- function(plot, scale) {
# helper function to get the scale names
scale_layers <- \(p) sapply(p$scales$scales, \(s) as.character(s$call[[1]]))
# get the existing scale layer names
existing_scales = scale_layers(plot)
# get the name of the scale to update
new_scale = as.character(scale$call[[1]])
# if the scale doesn't existed, just return with the new one added
if(!new_scale %in% existing_scales) return(plot+scale)
# else update the params
i = which(new_scale == existing_scales)
for(n in names(scale$call)[-1]) { plot$scales$scales[[i]][[n]] <- eval(scale$call[[n]])}
return(plot)
}
now, the following calls all should work:
scale = scale_y_continuous(expand=expansion(mult=c(0,.1))
update_scale(my_plot1, scale)
update_scale(my_plot2, scale)
update_scale(my_plot3, scale)