rggplot2default

How can I hinder ggplot from overwriting arguments to default that have been set before?


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:

one

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

two

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.


Solution

  • 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
    

    Update

    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)