Daniel Gardiner
Daniel Gardiner

Reputation: 1066

Keep ggplot secondary axis scale fixed

I'm making a ggplot with a secondary axis using the sec_axis() function but am having trouble retaining the correct scale.

Below is a reproducible example

# load package 

library(ggplot2)

# produce dummy data    

data = data.frame(week = 1:5,
                  count = c(45, 67, 21, 34, 50),
                  rate = c(3, 6, 2, 5, 3))

# calculate scale (and save as an object called 'scale')

scale = max(data$count)/10

# produce ggplot 

p = ggplot(data, aes(x = week)) +
  geom_bar(aes(y = count), stat = "identity") +
  geom_point(aes(y = rate*scale)) + 
  scale_y_continuous(sec.axis = sec_axis(~./scale, name = "% positive", 
                                         breaks = seq(0, 10, 2)))

# look at ggplot - all looks good

p

# change the value of the scale object 

scale = 2

# look at ggplot - you can see the scale has now change 

p

In reality I am producing a series of ggplot's within a loop and within each iteration of the loop the 'scale' object changes

Question

How do I ensure the scale of my secondary y-axis remains fixed? (even if the value of the 'scale' object changes)

EDIT

I wanted to keep the example as simple as possible (see example above) but on request I'll add an example which includes a loop

# load package 

library(ggplot2)

# produce dummy data    

data = data.frame(group = c(rep("A", 5), rep("B", 5)),
                  week = rep(1:5, 2),
                  count = c(45, 67, 21, 34, 50,
                            120, 200, 167, 148, 111),
                  rate = c(3, 6, 2, 5, 3,
                           15, 17, 20, 11, 12))

# define the groups i want to loop over 

group = c("A", "B")

# initalize an empty list (to store figures)

fig_list = list()

for(i in seq_along(group)){

  # subset the data 

  data.sub = data[data$group == group[i], ]

  # calculate scale (and save as an object called 'scale')

  scale = max(data.sub$count)/20

  # produce the plot 

  p = ggplot(data.sub, aes(x = week)) +
    geom_bar(aes(y = count), stat = "identity") +
    geom_point(aes(y = rate*scale), size = 4, colour = "dark red") + 
    scale_y_continuous(sec.axis = sec_axis(~./scale, name = "% positive", 
                                           breaks = seq(0, 20, 5))) + 
    ggtitle(paste("Plot", group[i]))

  # print the plot 

  print(p)

  # store the plot in a list 

  fig_list[[group[i]]] = p

}

I get the following figures when printing within the loop (everything looks good)

loop_print_A

loop_print_B

However... if I call the figure for group A from the list I created you can see the secondary y-axis scale is now incorrect (it has used the scale created for group B)

fig_list[["A"]]

enter image description here

Upvotes: 0

Views: 1407

Answers (1)

Thomas K
Thomas K

Reputation: 3311

Thanks for your edit, this makes things clearer. Your problem stems from the way R evaluates objects. The plot in your fig_list is not an image, but an outline on how the plot should be generated. It is only generated when you call print (by typing fig_list["A"]and hitting enter). Since the value for scale changes throughout the loop, if you evaluate the plot later, it will be incorrect, since it will use the last iteration of scale.

An easy solution is to wrap your code for plotting in a function and use lapply:

make_plot <- function(df) {

  scale = max(df$count)/20

  ggplot(df, aes(x = week)) +
    geom_bar(aes(y = count), stat = "identity") +
    geom_point(aes(y = rate*scale), size = 4, colour = "dark red") + 
    scale_y_continuous(sec.axis = sec_axis(~./scale, name = "% positive", 
                                           breaks = seq(0, 20, 5))) + 
    ggtitle(paste("Plot", unique(df$group)))
}

grouped_data <- split(data, data$group)
fig_list <- lapply(grouped_data, make_plot)

Now when you call the first plot, it is evaluated correctly.

fig_list["A"]
#> $A

This still works when you happen to have an object scale with a bogus value in your environment, since R looks up scale within the function call, and not in the global environment.

Created on 2018-09-02 by the reprex package (v0.2.0).

Upvotes: 2

Related Questions