R: фильтрация dplyr внутри функции с входным значением NULL

Как правильно выполнить встроенную условную проверку для фильтра, который игнорирует входной аргумент 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.

Вы не можете передать NULL в filter(), поэтому просто добавьте ... else TRUE в свои операторы условий.

lroha 12.08.2024 02:19

@lroha Великолепно! Вы правы: «... range[2])} else TRUE» работает. Если вы опубликуете это как ответ, я приму его как правильный. Спасибо!

David Robie 12.08.2024 02:23
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
69
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Установите значения по умолчанию в списке аргументов на значения, которые приведут к тому, что выражения фильтра будут оценены как 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
    )
}

Хотя это заметные обходные пути, я все же предпочитаю метод, который Ироха предоставил в своем комментарии к исходному сообщению.

David Robie 12.08.2024 05:49

Можно возразить, что добавление операторов if для обхода использования неправильных значений по умолчанию является проблемой, и даже если мы решим сохранить значения NULL по умолчанию в фильтре, тогда как можно просто добавить дополнительные условия в фильтр (см. третий подход в ответ на разрешение) является проблемой.

G. Grothendieck 12.08.2024 12:32

Я бы не сказал, что NULL — неправильное значение по умолчанию. У меня очень большие наборы данных, и отказаться от фильтрации быстрее, чем использовать фильтр, который принимает все записи. Должен признаться, что у меня возникли проблемы с вашим третьим подходом (testfun4), поскольку он выдает ошибку, когда не указан аргумент диапазона. Я не говорю, что ваш ответ плохой, просто вариант Ирохи предпочтительнее, учитывая мою ситуацию.

David Robie 13.08.2024 03:07

Я тестировал 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. Если бы это было важно, только первая рассматриваемая версия делает это.

G. Grothendieck 13.08.2024 14:17
Ответ принят как подходящий

Пользователь 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,
    )
}

Другие вопросы по теме