Как передать необязательный параметр внутри функции ggplot - PullRequest
1 голос
/ 13 марта 2020

Я бы хотел сделать параметр ybreak внутри моей функции plot1 необязательным. Если этот параметр не указан (NULL условие), то я хотел бы просто вернуть g ggplot, в противном случае использовать пользовательский ybreak. Я попробовал следующий код, ссылаясь на похожие ответы, но он просто не будет работать.

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

Ответы [ 2 ]

1 голос
/ 14 марта 2020

Примечание : см. Правки ниже

Функция эллипсов или трех точек R предназначена для обработки необязательных аргументов. Это может быть полезно, если у вашей функции будет больше необязательных аргументов. В приведенном вами примере структурируйте входные аргументы следующим образом.

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

}

Обработка ... для поиска указанного имени переменной c может быть немного сложнее, но с использованием функций eval, substitute и alist могут помочь с этим. Добавление следующих строк приведет к добавлению необязательных аргументов в список.

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

Примечание: требуется пакет purrr.

Чтобы оценить все необязательные аргументы для поиска указанного c имени Вы можете использовать аналогичный код, как указано выше. Вот полный пример.

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)
}

Если у вас более одного необязательного аргумента, вложите условие is.na(inputs$ybreak) в length(inputs) > 0. Поскольку оценка необязательных аргументов необходима только в том случае, если представлен один или несколько необязательных аргументов.

В зависимости от вашей функции и способа ее использования вы можете использовать более простые методы, такие как:

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

Однако предыдущий метод может быть лучшим вариантом для пакетов или производственного кода.

Для получения дополнительной информации см. Advanced R: Глава 6 Функции

РЕДАКТИРОВАТЬ :

Оригинальный ответ остается в силе. Тем не менее, я хотел бы предложить альтернативный метод для обработки необязательных аргументов. Функции list2 и dots_list из пакета rlang более просты в использовании и обеспечивают больший контроль над многоточием .... Например, функция plot1 будет изменена на:

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"]]
}

Надеюсь, это поможет!

1 голос
/ 13 марта 2020

Первый случай: ybreak = NULL

is.na(NULL)

Возвращает:

logical(0)

И поэтому (поскольку логическое (0) - ничто):

is.na(NULL) == FALSE

Возвращает:

logical(0)

Но если мы используем is.null (NULL - ничто) вместо is.na (NA - что-то (только не число)):

is.null(NULL)

Возвращает:

[1] TRUE

, а затем:

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

Второй случай: 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

Но мы можем использовать all для проверки нескольких логических значений одновременно:

if (all(is.na(seq(70, 500, by = 50)) == FALSE)) print("something")
l[1] "something"
...