Используйте case_когда в конвейерном рабочем процессе по большому количеству критериев без последовательного

#r #dplyr

Вопрос:

Я хотел бы использовать конвейерный рабочий процесс для регулярного применения dplyr::case_when к набору данных из 10^4 строк и хотел бы избежать rowwise этого, так как он очень медленный.

Я хотел бы сопоставить большое количество критериев с заданными значениями, например any(x = c(1:999, 3000:200000, 250000:250100) , где length(x) 1, и применить его к каждой строке в a data.frame .

Что-то вроде этой функции, но с гораздо большим количеством критериев:

 is_good_car <- function(x){
  any(
    x == c(
      "Mazda RX4",
      "Datsun 710",
      "Valiant"
    )
  )
}
 

Я мог бы применить это так:

 library(dplyr)

mtcars %>%
  mutate(
    car = rownames(.)
  ) %>%
  as_tibble %>%
  mutate(
    good_cars = case_when(
      is_good_car(car) ~ "good",
      TRUE ~ "rubbish"
    )
  ) %>%
  select(car, good_cars)
#> Warning in x == c("Mazda RX4", "Datsun 710", "Valiant"): longer object length is
#> not a multiple of shorter object length
#> # A tibble: 32 x 2
#>    car               good_cars
#>    <chr>             <chr>    
#>  1 Mazda RX4         good     
#>  2 Mazda RX4 Wag     good     
#>  3 Datsun 710        good     
#>  4 Hornet 4 Drive    good     
#>  5 Hornet Sportabout good     
#>  6 Valiant           good     
#>  7 Duster 360        good     
#>  8 Merc 240D         good     
#>  9 Merc 230          good     
#> 10 Merc 280          good     
#> # ... with 22 more rows
 

Но это не работает, потому что он просто возвращает один TRUE из is_good_car и возвращает это в каждую строку.

Я могу использовать rowwise , чтобы получить правильный ответ, но для моей цели это слишком медленно:

 mtcars %>%
  mutate(
    car = rownames(.)
  ) %>%
  as_tibble %>%
  rowwise %>%
  mutate(
    good_cars = case_when(
      is_good_car(car) ~ "good",
      TRUE ~ "rubbish"
    )
  ) %>%
  select(car, good_cars)
#> # A tibble: 32 x 2
#> # Rowwise: 
#>    car               good_cars
#>    <chr>             <chr>    
#>  1 Mazda RX4         good     
#>  2 Mazda RX4 Wag     rubbish  
#>  3 Datsun 710        good     
#>  4 Hornet 4 Drive    rubbish  
#>  5 Hornet Sportabout rubbish  
#>  6 Valiant           good     
#>  7 Duster 360        rubbish  
#>  8 Merc 240D         rubbish  
#>  9 Merc 230          rubbish  
#> 10 Merc 280          rubbish  
#> # ... with 22 more rows
 

Я мог sapply бы также использовать, но я хочу включить его в конвейерный рабочий процесс, как описано выше:

 
sapply(
  X = rownames(mtcars),
  FUN = is_good_car
)
#>           Mazda RX4       Mazda RX4 Wag          Datsun 710      Hornet 4 Drive 
#>                TRUE               FALSE                TRUE               FALSE 
#>   Hornet Sportabout             Valiant          Duster 360           Merc 240D 
#>               FALSE                TRUE               FALSE               FALSE 
#>            Merc 230            Merc 280           Merc 280C          Merc 450SE 
#>               FALSE               FALSE               FALSE               FALSE 
#>          Merc 450SL         Merc 450SLC  Cadillac Fleetwood Lincoln Continental 
#>               FALSE               FALSE               FALSE               FALSE 
#>   Chrysler Imperial            Fiat 128         Honda Civic      Toyota Corolla 
#>               FALSE               FALSE               FALSE               FALSE 
#>       Toyota Corona    Dodge Challenger         AMC Javelin          Camaro Z28 
#>               FALSE               FALSE               FALSE               FALSE 
#>    Pontiac Firebird           Fiat X1-9       Porsche 914-2        Lotus Europa 
#>               FALSE               FALSE               FALSE               FALSE 
#>      Ford Pantera L        Ferrari Dino       Maserati Bora          Volvo 142E 
#>               FALSE               FALSE               FALSE               FALSE
 

Есть ли какие-либо варианты использования функции, например is_good_car , по желанию, внутри case_when без использования rowwise ?

Создано 2021-09-22 пакетом reprex (v2.0.1)

Ответ №1:

Для сравнения нескольких значений вы должны использовать %in% .

 is_good_car <- function(x){
    x %in% c(
      "Mazda RX4",
      "Datsun 710",
      "Valiant"
  )
}
 

тогда вы можете использовать его без rowwise

 library(dplyr)

mtcars %>%
  mutate(
    car = rownames(.)
  ) %>%
  as_tibble %>%
  mutate(
    good_cars = case_when(is_good_car(car) ~ "good",
      TRUE ~ "rubbish"
    )
  ) %>%
  select(car, good_cars)

#   car               good_cars
#   <chr>             <chr>    
# 1 Mazda RX4         good     
# 2 Mazda RX4 Wag     rubbish  
# 3 Datsun 710        good     
# 4 Hornet 4 Drive    rubbish  
# 5 Hornet Sportabout rubbish  
# 6 Valiant           good     
# 7 Duster 360        rubbish  
# 8 Merc 240D         rubbish  
# 9 Merc 230          rubbish  
#10 Merc 280          rubbish  
# … with 22 more rows
 

Ответ №2:

Если ты изменишься == на %in% такой:

 is_good_car <- function(x){
  any(
    x %in% c(
      "Mazda RX4",
      "Datsun 710",
      "Valiant"
    )
  )
}
 

Функция теперь векторизована и будет быстрой и не понадобится rowwise .

Ответ №3:

Мы могли бы использовать map , и это должно быть быстрее, чем rowwise

 library(purrr)
library(tibble)
library(dplyr)
mtcars %>%
    rownames_to_column('car') %>%
     mutate(good_cars = case_when(map_lgl(car, is_good_car) ~ 
          'good', TRUE ~ 'rubbish'))  %>%
     as_tibble
 

-выход

 # A tibble: 32 x 13
   car                 mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb good_cars
   <chr>             <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <chr>    
 1 Mazda RX4          21       6  160    110  3.9   2.62  16.5     0     1     4     4 good     
 2 Mazda RX4 Wag      21       6  160    110  3.9   2.88  17.0     0     1     4     4 rubbish  
 3 Datsun 710         22.8     4  108     93  3.85  2.32  18.6     1     1     4     1 good     
 4 Hornet 4 Drive     21.4     6  258    110  3.08  3.22  19.4     1     0     3     1 rubbish  
 5 Hornet Sportabout  18.7     8  360    175  3.15  3.44  17.0     0     0     3     2 rubbish  
 6 Valiant            18.1     6  225    105  2.76  3.46  20.2     1     0     3     1 good     
 7 Duster 360         14.3     8  360    245  3.21  3.57  15.8     0     0     3     4 rubbish  
 8 Merc 240D          24.4     4  147.    62  3.69  3.19  20       1     0     4     2 rubbish  
 9 Merc 230           22.8     4  141.    95  3.92  3.15  22.9     1     0     4     2 rubbish  
10 Merc 280           19.2     6  168.   123  3.92  3.44  18.3     1     0     4     4 rubbish  
# … with 22 more rows
 

Или есть другой вариант использования %in% , и без case_when него его можно изменить

 is_good_car <- function(x){
  
    x %in% c(
      "Mazda RX4",
      "Datsun 710",
      "Valiant"
    )
  
}

 

-тестирование

 mtcars %>%
    rownames_to_column('car') %>%
    mutate(good_cars = c("rubbish", "good")[1   is_good_car(car)])
 

-выход

                  car  mpg cyl  disp  hp drat    wt  qsec vs am gear carb good_cars
1            Mazda RX4 21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4      good
2        Mazda RX4 Wag 21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4   rubbish
3           Datsun 710 22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1      good
4       Hornet 4 Drive 21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1   rubbish
5    Hornet Sportabout 18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2   rubbish
6              Valiant 18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1      good
7           Duster 360 14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4   rubbish
....
 

Кроме того, если нам нужен более быстрый подход, используйте data.table

 library(data.table)
mtcars1 <- copy(mtcars)
setDT(mtcars, keep.rownames = TRUE)[, good_cars := 'rubbish'
     ][rn %chin% c(  "Mazda RX4",
      "Datsun 710",
      "Valiant"), good_cars := 'good'][]