Привет, сообщество Stackoverflow!

Я использую динамическую переменную (например, ID) как способ ссылки на имя столбца, которое будет меняться в зависимости от того, какой ген я обрабатываю в данный момент. Затем я использую case_when в mutate, чтобы создать новый столбец, значения которого зависят от динамического столбца.

Я думал, что !! (бах-бах) мне нужно, чтобы принудительно выполнить eval содержимого переменной; однако я не получил ожидаемого результата в своей новой колонке. Только !!as.name дал мне результат, которого я ожидал, и я не совсем понимаю, почему. Может кто-нибудь объяснить, почему в этом случае использование только !! не подходит и что происходит в !!as.name?

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

library(tidyverse)

ID <- "birth_year"

# Correct output
test <- starwars %>%
  mutate(FootballLeague = case_when(
    !!as.name(ID) < 10 ~ "U10",
    !!as.name(ID) >= 10 & !!as.name(ID) < 50 ~ "U50",
    !!as.name(ID) >= 50 & !!as.name(ID) < 100 ~ "U100",
    !!as.name(ID) >= 100 ~ "Senior",
    TRUE ~ "Others"
  ))

# Incorrect output
test2 <- starwars %>%
  mutate(FootballLeague = case_when(
    !!(ID) < 10 ~ "U10",
    !!(ID) >= 10 & !!(ID) < 50 ~ "U50",
    !!(ID) >= 50 & !!(ID) < 100 ~ "U100",
    !!(ID) >= 100 ~ "Senior",
    TRUE ~ "Others"
  ))

# Incorrect output
test3 <- starwars %>%
  mutate(FootballLeague = case_when(
    as.name(ID) < 10 ~ "U10",
    as.name(ID) >= 10 & as.name(ID) < 50 ~ "U50",
    as.name(ID) >= 50 & as.name(ID) < 100 ~ "U100",
    as.name(ID) >= 100 ~ "Senior",
    TRUE ~ "Others"
  ))

identical(test, test2)
# FALSE

identical(test2, test3)
# TRUE

sessionInfo()
#R version 4.0.2 (2020-06-22)
#Platform: x86_64-centos7-linux-gnu (64-bit)
#Running under: CentOS Linux 7 (Core)

# tidyverse_1.3.0
# dplyr_1.0.2

Ура!

12
Yuka Takemon 26 Ноя 2020 в 19:19

2 ответа

Лучший ответ

Вы можете заключить свои выражения в функцию quo(), чтобы увидеть результат операции после применения оператора !!. Для простоты я буду использовать более короткое выражение для демонстрации:

Препараты:

library(tidyverse)
ID <- "birth_year"

## Test without quasiquotation:
starwars %>% 
  filter(birth_year < 50)

Эксперимент 1:

quo(
  starwars %>% 
    filter(ID < 50)
)
## result: starwars %>% filter(ID < 50)

Мы узнаем: filter() не обрабатывает ID как переменную, а "как есть". Итак, нам нужен механизм, сообщающий filter(), что он должен рассматривать ID как переменную и использовать его значение.

- & gt; Оператор !! может использоваться, чтобы указать filter(), что он должен рассматривать выражение как переменную и подставлять его значение.

Эксперимент 2:

quo(
  starwars %>% 
    filter(!!ID < 50)
) 
## result: starwars %>% filter("birth_year" < 50)

Мы узнаем: оператор !! действительно сработал: ID был заменен его значением. Но: значение ID - это строка "birth_year". Обратите внимание на кавычки в результате. Но, как вы, наверное, знаете, функции tidyverse не принимают имена переменных как строки, им нужны необработанные имена без кавычек. Сравните с экспериментом 1: filter() принимает все "как есть", поэтому ищет столбец с именем "birth_year" (включая кавычки!)

Что делает функция as.name()?

Это базовая функция R, которая принимает строку (или переменную, содержащую строку) и возвращает содержимое строки как имя переменной. Итак, если вы вызываете as.name(ID) в базе R, результатом будет birth_year, на этот раз без кавычек - точно так, как этого ожидает тидиверс. Итак, попробуем:

Эксперимент 3:

quo(
  starwars %>% 
    filter(as.name(ID) < 50)
) 
## result: starwars %>% filter(as.name(ID) < 50)

Мы узнаем: это не сработало, потому что, опять же, filter() принимает все "как есть". Итак, теперь он ищет столбец с именем as.name(ID), которого, конечно же, не существует.

-> Нам нужно объединить две вещи, чтобы это работало:

  1. Используйте as.name(), чтобы преобразовать строку в имя переменной.
  2. Используйте !!, чтобы указать filter(), что он не должен принимать вещи "как есть", а подставлять реальное значение.

Эксперимент 4:

quo(
  starwars %>% 
    filter(!!as.name(ID) < 50)
) 
## result: starwars %>% filter(birth_year < 50)

Теперь это работает! :)

Я использовал filter() в своих экспериментах, но он работает точно так же с mutate() и другими функциями tidyverse.

9
AEF 26 Ноя 2020 в 17:20

Чтобы упростить задачу, вы также можете использовать .data[[]], как предлагает @Lionel Henry в этом комментарий. См. Также примечания к выпуску rlang 0.4.0

library(tidyverse)

ID <- "birth_year"

# Correct output
test <- starwars %>%
  mutate(FootballLeague = case_when(
    !!as.name(ID) < 10 ~ "U10",
    !!as.name(ID) >= 10 & !!as.name(ID) < 50 ~ "U50",
    !!as.name(ID) >= 50 & !!as.name(ID) < 100 ~ "U100",
    !!as.name(ID) >= 100 ~ "Senior",
    TRUE ~ "Others"
  ))
test

Использование .data

test2 <- starwars %>%
  mutate(FootballLeague = case_when(
    .data[[ID]]   < 10 ~ "U10",
    .data[[ID]]  >= 10 & .data[[ID]]  < 50 ~ "U50",
    .data[[ID]]  >= 50 & .data[[ID]]  < 100 ~ "U100",
    .data[[ID]]  >= 100 ~ "Senior",
    TRUE ~ "Others"
  ))
test2
#> # A tibble: 87 x 15
#>    name               height  mass hair_color    skin_color  eye_color
#>    <chr>               <int> <dbl> <chr>         <chr>       <chr>    
#>  1 Luke Skywalker        172    77 blond         fair        blue     
#>  2 C-3PO                 167    75 <NA>          gold        yellow   
#>  3 R2-D2                  96    32 <NA>          white, blue red      
#>  4 Darth Vader           202   136 none          white       yellow   
#>  5 Leia Organa           150    49 brown         light       brown    
#>  6 Owen Lars             178   120 brown, grey   light       blue     
#>  7 Beru Whitesun lars    165    75 brown         light       blue     
#>  8 R5-D4                  97    32 <NA>          white, red  red      
#>  9 Biggs Darklighter     183    84 black         light       brown    
#> 10 Obi-Wan Kenobi        182    77 auburn, white fair        blue-gray
#> 11 Anakin Skywalker      188    84 blond         fair        blue     
#> 12 Wilhuff Tarkin        180    NA auburn, grey  fair        blue     
#> 13 Chewbacca             228   112 brown         unknown     blue     
#> 14 Han Solo              180    80 brown         fair        brown    
#> 15 Greedo                173    74 <NA>          green       black    
#>    birth_year sex    gender    homeworld species films     vehicles  starships
#>         <dbl> <chr>  <chr>     <chr>     <chr>   <list>    <list>    <list>   
#>  1       19   male   masculine Tatooine  Human   <chr [5]> <chr [2]> <chr [2]>
#>  2      112   none   masculine Tatooine  Droid   <chr [6]> <chr [0]> <chr [0]>
#>  3       33   none   masculine Naboo     Droid   <chr [7]> <chr [0]> <chr [0]>
#>  4       41.9 male   masculine Tatooine  Human   <chr [4]> <chr [0]> <chr [1]>
#>  5       19   female feminine  Alderaan  Human   <chr [5]> <chr [1]> <chr [0]>
#>  6       52   male   masculine Tatooine  Human   <chr [3]> <chr [0]> <chr [0]>
#>  7       47   female feminine  Tatooine  Human   <chr [3]> <chr [0]> <chr [0]>
#>  8       NA   none   masculine Tatooine  Droid   <chr [1]> <chr [0]> <chr [0]>
#>  9       24   male   masculine Tatooine  Human   <chr [1]> <chr [0]> <chr [1]>
#> 10       57   male   masculine Stewjon   Human   <chr [6]> <chr [1]> <chr [5]>
#> 11       41.9 male   masculine Tatooine  Human   <chr [3]> <chr [2]> <chr [3]>
#> 12       64   male   masculine Eriadu    Human   <chr [2]> <chr [0]> <chr [0]>
#> 13      200   male   masculine Kashyyyk  Wookiee <chr [5]> <chr [1]> <chr [2]>
#> 14       29   male   masculine Corellia  Human   <chr [4]> <chr [0]> <chr [2]>
#> 15       44   male   masculine Rodia     Rodian  <chr [1]> <chr [0]> <chr [0]>
#>    FootballLeague
#>    <chr>         
#>  1 U50           
#>  2 Senior        
#>  3 U50           
#>  4 U50           
#>  5 U50           
#>  6 U100          
#>  7 U50           
#>  8 Others        
#>  9 U50           
#> 10 U100          
#> 11 U50           
#> 12 U100          
#> 13 Senior        
#> 14 U50           
#> 15 U50           
#> # ... with 72 more rows

Проверьте, одинаковые ли они

identical(test, test2)
#> [1] TRUE

Создано 26 ноября 2020 г. пакетом REPEX (v0.3.0)

3
Tung 27 Ноя 2020 в 06:49