Использование оператора switch в dplyr mutate

Я хотел бы использовать оператор switch внутри dplyr mutate. У меня есть простая функция, которая выполняет некоторые операции и назначает альтернативные значения через переключатель, например:

convert_am <- function(x) {
    x <- as.character(x)
    switch(x,
           "0" = FALSE,
           "1" = TRUE,
           NA)
}

Это работает как нужно, когда применяется к скалярам:

>> convert_am(1)
[1] TRUE
>> convert_am(2)
[1] NA
>> convert_am(0)
[1] FALSE

Я хотел бы получить эквивалентные результаты через mutate вызов:

mtcars %>% mutate(am = convert_am(am))

Это не удается:

Ошибка в mutate_impl(.data, dots): Ошибка оценки: EXPR должен быть вектором длины 1.

Я понимаю, что это потому, что значения, передаваемые для переключения, не единичны, как в примере:

convert_am(c(1,2,2)) Ошибка в switch(x, 0 = FALSE, 1 = TRUE, NA): EXPR должен быть длиной 1 вектор

Векторизация

Попытка векторизации также дает желаемые результаты:

convert_am <- function(x) {
    x <- as.character(x)

    fun_switch <- function(x) {
        switch(x,
               "0" = FALSE,
               "1" = TRUE,
               NA)
    }

    vf <- Vectorize(fun_switch, "x")
}

>> mtcars %>% mutate(am = convert_am(am))
Error in mutate_impl(.data, dots) : 
  Column `am` is of unsupported type function

Заметки

  • Я в курсе case_when в dplyr, и я не заинтересован в его использовании, я заинтересован только в создании switch работать внутри мутировать
  • Идеальное решение позволит для дальнейшего расширения использовать mutate_at с переменными, переданными как .

2 ответа

Решение

switch не векторизовано, поэтому для эффективности вам нужно использовать ifelse или же case_when - но так как ваш вопрос конкретно о switch, вы можете достичь того, что вы хотите, векторизация, например,

convert_am <- Vectorize(function(x) {
    x <- as.character(x)
    switch(x,
       "0" = FALSE,
       "1" = TRUE,
       NA)
})

или же

convert_am <- function(x) {
    x <- as.character(x)
    sapply(x, function(xx) switch(xx,
       "0" = FALSE,
       "1" = TRUE,
       NA))
}

Они оба неэффективны, так как включают в себя петлю под капотом.

      This is simple enough to handle with ifelse directly:
        
            Test <- tibble::tibble(
              am = c(-1:5,NA,1, 0)
            ) 
        
            Test %>%
              mutate(
                newam = ifelse(am == 1, TRUE,
                       ifelse(am == 0, FALSE, NA))
              )
        
        
With more categories, use a named vector:
            Test %>%
              mutate(
                newam = ifelse(is.na(am) | !am %in% c(1,3,5), NA,
                               c("1" = "in1", "3" = "in3", "5" = "in5")[as.character(am)])
              )
        
In fact if the value is not in the named list it will default to an NA
I think this will be pretty efficient
            Test %>%
              mutate(
                newam = c("1" = "in1", "3" = "in3", "5" = "in5")[as.character(am)]
              )
Другие вопросы по тегам