简体   繁体   中英

Passing extra arguments to ggplot2 geoms: using ellipsis (...)

This is a follow-up on this question. I am trying to write my own geoms with custom parameters. My question is how to use ellipsis (...) to pass extra arguments.

The following example code works as expected:

draw_panel_func <- function(data, panel_params, coord, showpoints=FALSE) {
  print(showpoints)
  if(showpoints) {
    coords <- coord$transform(data, panel_params)
    grid::pointsGrob(coords$x, coords$y)
  } else {
    zeroGrob()
  } 
} 


## definition of the new geom. setup_data inserts the parameter 
## into data, therefore making it accessible for .draw_panel_func
GeomSimplePoint <- ggproto("GeomSimplePoint", Geom,
  required_aes = c("x", "y"),
  default_aes = aes(shape = 19, colour = "black"),
  draw_key = draw_key_point,
  extra_params = c("na.rm", "showpoints"),
  draw_panel = draw_panel_func
) 

geom_simple_point <- function(mapping = NULL, data = NULL, stat = "identity",
                              position = "identity", na.rm = FALSE, show.legend = NA,
                              inherit.aes = TRUE, showpoints=TRUE, ...) {
  layer(
    geom = GeomSimplePoint, mapping = mapping,  data = data, stat = stat,
    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
    params = list(na.rm = na.rm, showpoints=showpoints, ...)
  )   
}

This works, and I can call

ggplot(mpg, aes(displ, hwy)) + geom_simple_point(showpoints=FALSE)

to omit the plotting of the points.

However, because I want to use a generic function for different new geoms, I would prefer to define the draw_panel function without explicitly naming the showpoints parameter, but using ellipsis. I tried the following (all the remainder of the code remains identical), it doesn't work:

draw_panel_func <- function(data, panel_params, coord, ...) {
  showpoints <- list(...)$showpoints
  if(showpoints) {
    coords <- coord$transform(data, panel_params)
    grid::pointsGrob(coords$x, coords$y)
  } else {
    zeroGrob()
  }
}

The error returned is:

Error in if (showpoints) { : argument is of length zero

An interesting thing happens when I do the following:

draw_panel_func <- function(data, panel_params, coord, showpoints=FALSE, ...) {
  #showpoints <- list(...)$showpoints
  if(showpoints) {
    coords <- coord$transform(data, panel_params)
    grid::pointsGrob(coords$x, coords$y)
  } else {
    zeroGrob()
  }
}

For some reason, showpoints is now always FALSE. However, the ... list is NULL. This is completely unexpected.

This is all very confusing and for certain it does not behave like I would expected. Is it possible to use ellipsis in this context? If yes, then how? What is happening here?

EDIT: Following the suggestion of Gilean0709 I tried to remove showpoints from the definition of geom_simple_point :

geom_simple_point <- function(mapping = NULL, data = NULL, stat = "identity",
                              position = "identity", na.rm = FALSE, show.legend = NA,
                              inherit.aes = TRUE, ...) {
  layer(
    geom = GeomSimplePoint, mapping = mapping,  data = data, stat = stat,
    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
    params = list(na.rm = na.rm, ...)
  )   
}

This still gives the same error (showpoints is NULL).

This has to do with the ways the parameters are being distributed to stats, geoms and positions. If we look at the code of layer() :

function(arguments){
  ...some_body...
  params <- rename_aes(params)
  aes_params <- params[intersect(names(params), geom$aesthetics())]
  geom_params <- params[intersect(names(params), geom$parameters(TRUE))]
  stat_params <- params[intersect(names(params), stat$parameters(TRUE))]
  all <- c(geom$parameters(TRUE), stat$parameters(TRUE), geom$aesthetics())
  extra_param <- setdiff(names(params), all)
  if (check.param && length(extra_param) > 0) {
    warning("Ignoring unknown parameters: ", paste(extra_param, 
      collapse = ", "), call. = FALSE, immediate. = TRUE)
  }
  ...some_more_body...
}

We can see that the parameters you put in params (including ... ) are being distributed across the different aspects of a layer based on the names of the params. Anything leftover ( extra_param ) gets discarded and warned about. Since the ellipses ... is a special case that has no name in and of itself (but the elements in ... do), this gets discarded if ggplot geoms/stats/aes doesn't know about the elements in ... .

Now there is an ugly way around this, and that is to make a copy of the ellipses in the very beginning and pass that around as a named argument:

geom_simple_point <- function(mapping = NULL, data = NULL, stat = "identity",
                              position = "identity", na.rm = FALSE, show.legend = NA,
                              inherit.aes = TRUE, ...) {
  elli <- list(...)
  layer(
    geom = GeomSimplePoint, mapping = mapping,  data = data, stat = stat,
    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
    params = list(na.rm = na.rm, elli = elli, ...)
  )   
}

Then, we can have the panel drawing function accept this copy of the ellipses as an argument:

draw_panel_func <- function(data, panel_params, coord, elli) {
  if(elli$showpoints) {
    coords <- coord$transform(data, panel_params)
    grid::pointsGrob(coords$x, coords$y)
  } else {
    zeroGrob()
  }
}

And update the ggproto accordingly:

GeomSimplePoint <- ggproto("GeomSimplePoint", Geom,
                           required_aes = c("x", "y"),
                           default_aes = aes(shape = 19, colour = "black"),
                           draw_key = draw_key_point,
                           extra_params = c("na.rm", "elli"),
                           draw_panel = draw_panel_func
) 

And now that produces a plot that will work as intended, but it will throw the warning that it is ignoring the showpoints parameter because it get's passed to extra_param (even though technically it isn't ignored, since it is now part of elli ). You probably want to code some extra checks in the panel drawing function though, to make sure that showpoints was even a provided argument in the beginning.

Hope this helped!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM