0

I am trying to make a ggplot2 wrapper that adjusts the scale on the y-axis depending on the scale of y.

  • plot_1: gives an error
  • plot_2: with tidyeval, but the output has no values on the y-axis
  • plot_3: this is how it should look

Any idea what causes this problem? Maybe there is a different approach to this problem?

I hope the example helps to understand my problem. Thanks in advance!

# setup -------------------------------------------------------------------

library(ggplot2)

df <- data.frame(
  childs = c(3, 0, 2, 4, 2, 2, 2, 3, 3, 4, 5, 4, 3, 5, 7, 2, 6, 5, 0, 2),
  agegrp = as.factor(c("Age 45-55",
                       "Age 55-65","Age 65+","Age 35-45","Age 45-55","Age 45-55",
                       "Age 45-55","Age 18-35","Age 35-45","Age 65+",
                       "Age 18-35","Age 65+","Age 18-35","Age 55-65",
                       "Age 65+","Age 18-35","Age 55-65","Age 55-65",
                       "Age 18-35","Age 35-45")),
  religion = as.factor(c("None","None",
                         "Catholic","Catholic","None","None","None","Catholic",
                         "Protestant","None","Protestant","Protestant",
                         "Catholic","Protestant","Catholic","Other",
                         "Protestant","Protestant","None","Protestant"))
)

# function ----------------------------------------------------------------

plot_1 <- function(data, x, y, fill) {
  
  plot <-
    ggplot(data = data,
           mapping = aes(x = {{ x }}, y = {{ y }}, fill = {{ fill }})) +
    geom_col(position = "dodge")

  if (is.numeric(y)) {
    scale <-
      scale_y_continuous(expand = expansion(mult = c(0, 0.05)),
                         breaks = scales::breaks_extended(),
                         labels = scales::label_number(big.mark = "'"))
  } else {
    scale <-
      scale_y_discrete(expand = expansion(mult = c(0, 0.05)),
                       breaks = scales::breaks_extended(),
                       labels = scales::label_number(big.mark = "'"))
  }

  plot +
    scale +
    cowplot::theme_minimal_hgrid()
}

plot_2 <- function(data, x, y, fill) {
  
  plot <-
    ggplot(data = data,
           mapping = aes(x = {{ x }}, y = {{ y }}, fill = {{ fill }})) +
    geom_col(position = "dodge")
  
  cond <- enquo(y) # tidyeval
  
  if (is.numeric(cond)) {
    scale <-
      scale_y_continuous(expand = expansion(mult = c(0, 0.05)),
                         breaks = scales::breaks_extended(),
                         labels = scales::label_number(big.mark = "'"))
  } else {
    scale <-
      scale_y_discrete(expand = expansion(mult = c(0, 0.05)),
                       breaks = scales::breaks_extended(),
                       labels = scales::label_number(big.mark = "'"))
  }
  
  plot +
    scale +
    cowplot::theme_minimal_hgrid()
}

plot_3 <- function(data, x, y, fill) {
  
  plot <-
    ggplot(data = data,
           mapping = aes(x = {{ x }}, y = {{ y }}, fill = {{ fill }})) +
    geom_col(position = "dodge")

  plot +
    cowplot::theme_minimal_hgrid()
}

# test --------------------------------------------------------------------

plot_1(df, agegrp, childs, religion)
#> Error in plot_1(df, agegrp, childs, religion): object 'childs' not found
plot_2(df, agegrp, childs, religion)
plot_3(df, agegrp, childs, religion)

Created on 2022-10-14 by the reprex package (v2.0.1)

  • 1
    Try passing `y` to `dplyr::mutate()` with a new unique column name, e.g. `mutate(.my_y = {{ y }})`. Then you can inspect `data$.my_y` and pass it to ggplot2. – Lionel Henry Oct 14 '22 at 13:45
  • @LionelHenry the error is thrown at the line `if(is.numeric(y))`, where `y` is obviously not recognised without a data mask being applied. I have suggested `if (is.numeric(data[[deparse(substitute(y))]]))` instead, but I guess `if(is.numeric(eval_tidy(enquo(y), data = data)))` is more tidyverse-like. I also guess you will know a neater way of expressing this? – Allan Cameron Oct 14 '22 at 14:01
  • @LionelHenry thanks for your idea. unfortunately this throws the same error. Allans solution works. – tricktracktriu Oct 14 '22 at 14:40
  • There is also a more intuitive way for tidyverse users: `cond <- pull(data, {{ y }})` `if(is.numeric(cond))` [more Information](https://community.rstudio.com/t/tidyeval-and-if-statement-in-ggplot-wrapper/150069) – tricktracktriu Oct 15 '22 at 06:26
  • Glad you found a solution to your problem, sorry I didn't have time to illustrate mine. – Lionel Henry Oct 17 '22 at 10:40

1 Answers1

0

The error arises inside if(is.numeric(y)), since the variable y does not exist in this context, where the data mask does not apply. If you change it to

  if (is.numeric(data[[deparse(substitute(y))]])) {

Then your function works, resulting in

plot_1(df, agegrp, childs, religion)

enter image description here

Allan Cameron
  • 147,086
  • 7
  • 49
  • 87
  • I thought `if(is.numeric(y))` is the problem. But i didn't know how to solve it. `if(is.numeric(eval_tidy(substitute(y), data = data)))` works fine for me. Also thanks for the explanation. – tricktracktriu Oct 14 '22 at 14:33
  • 1
    I'd use `rlang::as_name()` instead of `deparse()` because the latter might return a multi-line vector with some expressions. The former is safer and fails if the supplied expression is not a symbol. – Lionel Henry Oct 17 '22 at 10:39
  • @LionelHenry thanks - I thought you would know a neater way. – Allan Cameron Oct 17 '22 at 15:18