Как правильно выполнить встроенную условную проверку для фильтра, который игнорирует входной аргумент NULL?
Недавно меня научили чистому методу встроенной условной фильтрации с помощью dplyr. Теперь мне интересно применить это к функции, где один или несколько входов могут иметь значение NULL. Если указан аргумент, вам следует фильтровать на основе этого аргумента, но если он равен нулю, не следует этого делать. В этом случае данные будут просто iris %>% tibble()
. Раньше я делал это громоздким способом:
testfun <- function(data, range = NULL, spec = NULL){
if (!is.null(range)) {
data %<>% filter(between(Petal.Length, range[1], range[2]))
}
if (!is.null(spec)) {
data %<>% filter(Species %in% spec)
}
return(data)
}
Моя попытка встроенных условных проверок выглядит так
testfun <- function(data, range = NULL, spec = NULL){
data %>%
filter(
if (!is.null(range)) {between(Petal.Length, range[1], range[2])},
if (!is.null(spec)) {Species %in% spec},
)
}
Это работает до тех пор, пока я предоставляю входные данные для диапазона и спецификации. Однако, если я оставлю один из них нулевым, я получу сообщение об ошибке, например:
Ошибка в «фильтре()»:
ℹ В аргументе: 'if (...) NULL'.
Вызвано ошибкой:
! '..2' должен иметь размер 150 или 1, а не размер 0.
@lroha Великолепно! Вы правы: «... range[2])} else TRUE» работает. Если вы опубликуете это как ответ, я приму его как правильный. Спасибо!
Установите значения по умолчанию в списке аргументов на значения, которые приведут к тому, что выражения фильтра будут оценены как TRUE.
testfun2 <- function(data, range = c(-Inf, Inf), spec = data$Species) {
data %>%
filter(
between(Petal.Length, range[1], range[2]),
Species %in% spec
)
}
или сохраните их как NULL в списке аргументов, но затем сбросьте их в коде
testfun3 <- function(data, range = NULL, spec = NULL) {
range <- range %||% c(Inf, Inf)
spec <- spec %||% data$Species
data %>%
filter(
between(Petal.Length, range[1], range[2]),
Species %in% spec
)
}
Другая возможность — включить проверку NULL в условия
testfun4 <- function(data, range = NA, spec = NA) {
data %>%
filter(
is.na(range) | between(Petal.Length, range[1], range[2]),
is.na(spec) | Species %in% spec
)
}
Хотя это заметные обходные пути, я все же предпочитаю метод, который Ироха предоставил в своем комментарии к исходному сообщению.
Можно возразить, что добавление операторов if для обхода использования неправильных значений по умолчанию является проблемой, и даже если мы решим сохранить значения NULL по умолчанию в фильтре, тогда как можно просто добавить дополнительные условия в фильтр (см. третий подход в ответ на разрешение) является проблемой.
Я бы не сказал, что NULL — неправильное значение по умолчанию. У меня очень большие наборы данных, и отказаться от фильтрации быстрее, чем использовать фильтр, который принимает все записи. Должен признаться, что у меня возникли проблемы с вашим третьим подходом (testfun4), поскольку он выдает ошибку, когда не указан аргумент диапазона. Я не говорю, что ваш ответ плохой, просто вариант Ирохи предпочтительнее, учитывая мою ситуацию.
Я тестировал testfun4
на R 4.0.3/dplyr 1.0.2, и там не возникало никаких ошибок, но теперь я протестировал его на R 4.4.1/dplyr 1.1.4, и кажется, что код dplyr для between
был полностью переписан (вероятно, в dplyr 1.1), и в этой версии генерируется ошибка при передаче NULL, поэтому мы заменили NULL на NA в testfun4
, и теперь это снова работает. Спасибо, что указали на проблему. В любом случае обратите внимание, что неверно, что if
во 2-й (последней) версии testfun
, о которой идет речь, избегает использования filter
для аргументов NULL. Если бы это было важно, только первая рассматриваемая версия делает это.
Пользователь lroha прокомментировал сообщение, предоставив, как мне кажется, правильный ответ, но они опубликовали его как ответ.
Вы не можете передать
NULL
вfilter()
, поэтому просто добавьте... else TRUE
в свои операторы условий.
Итак, вместо:
testfun <- function(data, range = NULL, spec = NULL){
data %>%
filter(
if (!is.null(range)) {between(Petal.Length, range[1], range[2])},
if (!is.null(spec)) {Species %in% spec},
)
}
Это должно быть:
testfun <- function(data, range = NULL, spec = NULL){
data %>%
filter(
if (!is.null(range)) {between(Petal.Length, range[1], range[2])} else TRUE,
if (!is.null(spec)) {Species %in% spec} else TRUE,
)
}
Вы не можете передать
NULL
вfilter()
, поэтому просто добавьте... else TRUE
в свои операторы условий.