Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How To Pass An Optional Parameter Inside a function of ggplot

I would like to make the ybreak parameter inside my plot1 function as optional. If this parameter is not specified (NULL condition), then I would like to just return the g ggplot, otherwise use the custom ybreak. I tried the following code by referencing similar answers, but it just won't work.

plot1 <- function(df, x, y, ybreak = NULL) {
  g <- ggplot(df, aes_string(x = x, y = y)) 

  if (is.na(ybreak) == F) {
    g + scale_y_continuous(breaks = ybreak)
  }
  else {
    g
  }
}

plot1(mtcars, x = "mpg", y = "disp")
plot1(mtcars, x = "mpg", y = "disp", ybreak = seq(70, 500, by = 50))


> plot1(mtcars, x = "mpg", y = "disp")
Error in if (is.na(ybreak) == F) { : argument is of length zero
> plot1(mtcars, x = "mpg", y = "disp", ybreak = seq(70, 500, by = 50))
Warning message:
In if (is.na(ybreak) == F) { :
  the condition has length > 1 and only the first element will be used
like image 642
shsh Avatar asked Dec 08 '25 08:12

shsh


2 Answers

First case: ybreak = NULL

is.na(NULL)

Returns:

logical(0)

And therefore (because logical(0) is nothing):

is.na(NULL) == FALSE

Returns:

logical(0)

But if we use is.null (NULL is nothing) instead of is.na (NA is something (just not a number)) :

is.null(NULL)

Returns:

[1] TRUE

and then:

is.null(NULL) == FALSE
[1] FALSE

Second case: ybreak = seq(70, 500, by = 50)

is.na(seq(70, 500, by = 50))
[1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
if (is.na(seq(70, 500, by = 50)) == FALSE) print("something")
[1] "something"
Warning message:
  In if (is.na(seq(70, 500, by = 50)) == FALSE) print("something") :
  the condition has length > 1 and only the first element will be used

But we can use all to check multiple booleans at once:

if (all(is.na(seq(70, 500, by = 50)) == FALSE)) print("something")
l[1] "something"
like image 145
dario Avatar answered Dec 10 '25 22:12

dario


Note: See edits below

R's ellipses or three dots feature is designed to handle optional arguments. This may be useful if your function will have more optional arguments. In the example you provided, structure the input arguments in the following way.

plot1 <- function(df, x, y, ...) {

}

To process the ... to look for a specific variable name can be a bit tricky, but using the functions eval, substitute, and alist can help with this. Adding the following lines will put the optional arguments into a list.

plot1 <- function(df, x, y, ...) {
    args <- eval(substitute(alist(...)))
    inputs <- purrr::map(args, as.list)
}

Note: requires purrr package.

To evaluate all optional arguments to look for a specific name, you can use a similar code as mentioned above. Here is the full example.

library(ggplot2)

plot1 <- function(df, x, y, ...) {

    # eval inputs
    args <- eval(substitute(alist(...)))
    inputs <- purrr::map(args, as.list)
    print(args)
    print(inputs)

    # define base plot
    g <- ggplot(df, aes_string(x = x, y = y)) + geom_point()

    # return chart with breaks only if optional arguments are present
    # and if ybreaks exists
    if (length(inputs) > 0 && !is.na(inputs$ybreak)) {

        # rebuild seq
        breaks <- inputs$ybreak
        new_seq <- seq(breaks[[2]], breaks[[3]], by = breaks$by)

        # add to chart
        g <- g + scale_y_continuous(breaks = new_seq)
    }

    # return chart 
    return(g)
}

If you have more than one optional argument, nest the is.na(inputs$ybreak) condition in length(inputs) > 0. As the evaluation of optional arguments is only necessary if one or more optional arguments are submitted.

Depending on your function and how you intend to use it, you can use simpler methods such as:

plot1 <- function(df, x, y, ...) {
   args <- list(ybreaks = ..1)
}

However, the previous method may be a better option for packages or production code.

For more information, see the Advanced R: Chapter 6 Functions

EDITS:

The original reply still stands. However, I would like to suggest an alternative method for processing optional arguments. The function list2 and dots_list from the rlang package are easier to use and allow for more control over the ellipsis .... For example the plot1 function would be restructured to:

plot1 <- function(df, x, y, ...) {
-    args <- eval(substitute(alist(...)))
-    inputs <- purrr::map(args, as.list)
+    args <- rlang::list2(...)
     # evaluate arguments using
     # args$my_optional_argument or
     # args[["my_optional_argument"]]
}

Hope that helps!

like image 35
dcruvolo Avatar answered Dec 10 '25 22:12

dcruvolo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!