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
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"
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With