1

There's a lot of questions about how to combine two scale_ type calls in ggplot2. However, most of them involve something like this:

ggplot(iris, aes(Sepal.Length, Sepal.Width)) + 
    geom_point() + 
    scale_x_log10() + 
    scale_x_continuous("some new label")

#> Scale for 'x' is already present. Adding another scale for 'x', which will replace the existing scale.

Here the solution is easy enough, one just has to use the trans param in scale_x_continuous.

My situation is that I have a number of ggplot2 helper functions for quickly doing plots. These functions call scale_x inside the function, so one cannot just edit it so easily. Example:

#helper function
table2 = function(x, prop = F, include_NA = T, sort_descending = T) {

  #NA param
  if (include_NA) {
    NA_param = "always"
  } else {
    NA_param = "no"
  }

  #get regular table
  tbl = table(x, useNA = NA_param)

  #as data frame
  d = tibble::data_frame("Group" = names(tbl),
                         "Count" = as.numeric(tbl)
  )

  #percent/prob
  if (!prop) d$Percent = d$Count / sum(d$Count) * 100 else d$Proportion = d$Count / sum(d$Count)

  #sort?
  if (!is.null(sort_descending)) {
    if (sort_descending) {
      d %<>% dplyr::arrange(-Count)
    } else {
      d %<>% dplyr::arrange(Count)
    }
  }

  d
}

GG_boxplot = function(df, y, x) {
  #determine sample sizes
  sample_n = df[c(x, y)] %>%
    na.omit() %>%
    {table2(.[[x]], include_NA = F, sort_descending = NULL)}

  #plot
  ggplot(df, aes_string(x = x, y = y)) +
    geom_boxplot() +
    scale_x_discrete(labels = function(x) {str_c(x, "\nn = ", sample_n$Count)})
}

Use case:

GG_boxplot(iris, "Sepal.Length", "Species")

enter image description here

So, it's a nice way to easily add the class level sample sizes to the labels. However, if we wanted to change something else with the axis, these labels would get overwritten except in special cases where we can use e.g. xlab.

GG_boxplot(iris, "Sepal.Length", "Species") + xlab("no problem")

enter image description here

But if one wants to do something more fancy, the issue occurs:

#> GG_boxplot(iris, "Sepal.Length", "Species") + scale_x_discrete(position = "top")
Scale for 'x' is already present. Adding another scale for 'x', which will replace the existing scale.

enter image description here

Is there a good way to solve these kinds of overwriting issues?

I tried two ways to attempt to tell ggplot2 not to overwrite the previous setting but no luck:

> GG_boxplot(iris, "Sepal.Length", "Species") + scale_x_discrete(position = "top", labels = NULL)
Scale for 'x' is already present. Adding another scale for 'x', which will replace the existing scale.
> GG_boxplot(iris, "Sepal.Length", "Species") + scale_x_discrete(position = "top", labels = waiver())
Scale for 'x' is already present. Adding another scale for 'x', which will replace the existing scale.
CoderGuy123
  • 6,219
  • 5
  • 59
  • 89
  • What's `table2` in the `sample_n` line? – twedl Jan 24 '18 at 22:30
  • Also I might be dumb but how are you getting `x + "\nn" + sample_n$count` to concatenate a string instead of `paste`? – twedl Jan 24 '18 at 22:48
  • Sorry, I forgot to provide definitions of some custom functions. `table2` is just a univariate replacement for `table` and `prop.table`. I overloaded `+` to function as string concatenation and addition (see https://stackoverflow.com/a/18175871/3980197). – CoderGuy123 Jan 24 '18 at 22:53

1 Answers1

1

Hacky answer

If you save the plot that's returned from your function, you can access the scale labels that exist, and re-set the part of the scale that you need.

p <- GG_boxplot(iris, "Sepal.Length", "Species")
p + scale_x_discrete(position = 'top', 
                     labels = ggplot_build(p)$layout$panel_scales$x[[1]]$labels)

The labels = bit returns the function that writes the existing labels from the plot p.

twedl
  • 1,588
  • 1
  • 17
  • 28
  • I thought something like this might be possible. I did look around, but didn't try the `ggplot_build` approach. Still, counters the whole point of these kinds of convenience functions, namely to save time/coding. – CoderGuy123 Jan 24 '18 at 22:56
  • Yep, it's pretty ugly. But I suppose if you were doing it often you could do `get_x_labels <- function(p) { }`. Or maybe Hadley's hidden a real way to do this somewhere in `ggplot2`. – twedl Jan 25 '18 at 00:18
  • I was thinking that there perhaps was some kind of clever infix operator (e.g. `%+add%`) that would allow one to avoid overwriting settings when duplicating ggplot2 scales, layers etc. – CoderGuy123 Jan 25 '18 at 00:38