teoten
teoten

Reputation: 31

How can I pass variables between ggplot personal functions?

I am trying to create some functions based on ggplot to create custom plots. The creation of functions is very easy and straight forward, but I cannot figure out the way to share information between the functions to help in the plot creation.

Here is a simplified example

library(ggplot2)

my_ggplot <- function(dat, x, y, cols) {
  ggplot(dat, aes(!!sym(x), !!sym(y))) + 
    geom_point(color = cols$dots)
}


my_geom <- function(dat, x, cols) {
  xmean <- mean(dat[[x]], na.rm = T)
  exit <- list(
    geom_smooth(aes(color = cols$line), method = "loess", se = FALSE),
    geom_vline(xintercept = xmean, color = cols$line)
  )
}


mycolors <- list(dots = "blue", line = "red")

Here, my_plot creates the base plot and, if I want to, I can add couple of lines to it using my_geom. I need a way to control the colors so, I have defined an S3 class object, which in this example is simply the list mycolors.

So, when passing all the parameters to each function, the result is perfectly fine:

my_ggplot(mpg, 'displ', 'hwy', mycolors) +
  my_geom(mpg, "displ",  mycolors)

But I want to be able to "inherit" values from my_ggplot to my_geom so that the following code could work:

my_ggplot(mpg, 'displ', 'hwy', mycolors) +
  my_geom()

But still, my_geom keeps certain level of independence in case I want to use it with different ggplot() functions. Especially important for me is to be able to pass the dataset between functions, in the example I calculate the mean and use it later in geom_vline to keep it simple, but in practice I need to do some data wrangling and calculations before I can pass the values to the geom.

Upvotes: 1

Views: 329

Answers (2)

tjebo
tjebo

Reputation: 23807

Another option. This might work by defining your data and color arguments as NULL, and with a simple if/else statement to create a list based on presence of provided data, respectively. It really depends on the use case. In my example, there are two if else statements. One for the data, the other for the color (in case the data was not passed to the second function).

It might be best to create your own stat, it really depends on what type of data transformation and geometry you have in mind. geom_vline is a bit of a special situation and might not be the best chosen example.

The advantage of this little bit of extra effort is that it doesn’t need a hard coded y aesthetic for your line.

I think Stefan's approach with the color is excellent - I've used this here too.

library(ggplot2)

my_ggplot <- function(dat, x, y, cols) {
  ggplot(dat, aes(x = !!sym(x), y = !!sym(y))) +
    geom_point(aes(color = "dots"), show.legend = F) +
    scale_color_manual(values = cols) 
}

StatMyline <- ggproto("StatMyline", Stat,
  compute_group = function(data, scales) {
    data.frame(
      x = mean(data$x),
      xend = mean(data$x),
      y = -Inf, yend = Inf
    )
  },
  required_aes = c("x", "y")
)
stat_myline <- function(mapping = NULL, data = NULL, geom = "segment",
                        position = "identity", na.rm = FALSE, show.legend = NA,
                        inherit.aes = TRUE, ...) {
  layer(
    stat = StatMyline, data = data, mapping = mapping, geom = geom,
    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
    params = list(na.rm = na.rm, ...)
  )
}

mycolors <- list(dots = "blue", line = "red")

my_geom <- function(dat = NULL, x, cols = NULL) {
  ## if dat is provided, compute using your provided data and the provided color
  if (!is.null(dat)) {
    xmean <- mean(dat[[x]], na.rm = T)
    list(
      geom_smooth(aes(color = "line"), method = "loess", se = FALSE, show.legend = F),
      geom_vline(aes(color = "line", xintercept = xmean), show.legend = F)
    )
  } else {
    list(
      geom_smooth(method = "loess", se = FALSE, aes(color = "line"), show.legend = F),
      stat_myline(aes(color = "line"), show.legend = F), 
      if(!is.null(cols)) scale_color_manual(values = cols) else NULL
    )
  }
}

p1 <- my_ggplot(mpg, "displ", "hwy", mycolors) +
  my_geom(mpg, "displ", mycolors) +
  ggtitle("With data + color ")

p2 <- my_ggplot(mpg, "displ", "hwy", mycolors) +
  my_geom() +
  ggtitle("Inheriting data + color")

p3 <- ggplot(mtcars, aes(hp, mpg)) +
  geom_point() +
  my_geom(cols = mycolors) +
  ggtitle("without my_ggplot")

library(patchwork)
p1 + p2 + p3
#> `geom_smooth()` using formula = 'y ~ x'
#> `geom_smooth()` using formula = 'y ~ x'
#> `geom_smooth()` using formula = 'y ~ x'

Created on 2023-04-13 with reprex v2.0.2

Upvotes: 2

stefan
stefan

Reputation: 125797

One possible approach to remove the dependency on the dat and the x argument would be to use stat_summary to compute the mean of the variable mapped on the x aes and to add the vline similar to my answer on this post. Second, for the colors one option would be to map on the color aes and to set the color palette via scale_color_manual. This way the colors would be available in my_geom too. Of course does this only work when you create your plot via my_ggplot. Not perfect.

library(ggplot2)

my_ggplot <- function(dat, x, y, cols) {
  ggplot(dat, aes(!!sym(x), !!sym(y))) +
    geom_point(aes(color = "dots"), show.legend = FALSE) +
    scale_color_manual(values = cols)
}

my_geom <- function() {
  list(
    geom_smooth(aes(color = "line"), method = "loess", se = FALSE, show.legend = FALSE),
    stat_summary(aes(xintercept = after_stat(x), y = 0, color = "line"),
      fun = mean, geom = "vline", orientation = "y", show.legend = FALSE
    )
  )
}

mycolors <- list(dots = "blue", line = "red")

my_ggplot(mpg, "displ", "hwy", mycolors) +
  my_geom()
#> `geom_smooth()` using formula = 'y ~ x'

Finally here is an example of applying my_geom to a ggplot created from scratch:

ggplot(mtcars, aes(hp, mpg)) +
  geom_point() +
  my_geom()
#> `geom_smooth()` using formula = 'y ~ x'

Upvotes: 2

Related Questions