Разница в выводе между индексированием одинарных и двойных скобок в R case_when()

Я работаю со списком в R и заметил неожиданную разницу в выводе при использовании одинарной и двойной скобочной индексации в функции case_when() из пакета dplyr. Вот примерный список, с которым я работаю:

list1 <- list(a = as.data.frame(cbind(1:5,6:10)), b = "Hello", c = list(x = 10, y =20))

Когда я использую индексацию двойной скобки в случае_когда(), я получаю один результат:

case_when(list1[[1]][['V1']]==1~'a',
          list1[[1]][['V1']]==3~NA,
          list1[[1]][['V1']] %in% c(2:4)~'b')

Но когда я использую индексацию одной скобки в случае_когда(), я получаю другой результат:

case_when(list1[[1]]['V1']==1~'a',
          list1[[1]]['V1']==3~NA,
          list1[[1]]['V1'] %in% c(2:4)~'b')

Может ли кто-нибудь объяснить, почему существует разница в выводе этих двух методов индексации в случае_когда()? Любая информация будет принята с благодарностью! Я думаю, что разница связана с «%in%».

Обновлять: Разница составляет %in%:

Я давно знаю разницу между [ и [[. И приведенный выше код доказал, что он скорее связан с %in%, а не с [/[[. Поскольку вывод

case_when(list1[[1]][['V1']]==1~'a',
          list1[[1]][['V1']]==3~NA,
          list1[[1]][['V1']] %in% c(2:4)~'b')

является:

[1] "a" "b" NA  "b" NA 

Выход

case_when(list1[[1]]['V1']==1~'a',
          list1[[1]]['V1']==3~NA,
          list1[[1]]['V1'] %in% c(2:4)~'b')

является:

[1] "a" NA  NA  NA  NA 

Следовательно, разница должна быть связана с функцией %in%.

Согласно документации %in%,

match {base}    R Documentation
Value Matching
Description
match returns a vector of the positions of (first) matches of its first argument in its second.

Я ввожу фрейм данных вместо вектора

case_when(list1[[1]]['V1']==1~'a',
          list1[[1]]['V1']==3~NA,
          list1[[1]]['V1'] %in% c(2:4)~'b')

поэтому произошел неожиданный вывод.

Вы читали help("[")? Одинарные скобки возвращают список, двойные скобки возвращают элемент списка.

Roland 14.08.2024 07:33

Я прочитал help("["). И я думаю, что это связано с %in%? Я думаю, разница в том, как %in% обрабатывает вектор и фрейм данных?

doraemon 14.08.2024 08:04

Голосование за повторное открытие, поскольку это не просто разница между [[ и [, а, скорее, несколько необычный/специфичный случай того, как case_when() обрабатывает результат %in% с векторами и data.frames. Хотя это правда, что разные индексации извлекают разные объекты, почему разные объекты выдают соответствующие результаты - это разумный вопрос, на который я сразу не вижу ответа где-либо еще (хотя, возможно, я просто недостаточно хорошо ищу OP).

socialscientist 14.08.2024 08:20
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Разная индексация возвращает разные объекты: один — атомарный вектор, а другой — data.frame. Когда вы передаете эти объекты оператору %in%, он дает разные результаты. Эти разные результаты влияют на вывод case_when(), поскольку один содержит значение для каждого элемента вектора, а другой — одно значение для всего data.frame. Это заставляет case_when() полагаться на значения NA по умолчанию, поэтому вы получаете больше NA в случае с одной скобкой. В общем, вам захочется протестировать ВЕКТОРЫ, а не размещать другие объекты слева от вызовов case_when(). Ниже я демонстрирую это.

Демонстрация

library(dplyr)

# Simpler example data
list1 <- list(a = data.frame(V1 = 1:5))

Давайте использовать case_when с одинарными и двойными скобками, как вы. Здесь мы заменим NA на NA_character, чтобы явно избежать конфликтов типов, которые могут возникнуть при использовании case_when().

# Double bracket
brack2 <- case_when(list1[[1]][['V1']]==1~'a',
          list1[[1]][['V1']]==3~NA_character_,
          list1[[1]][['V1']] %in% 2:4 ~'b')

# Single bracket
brack1 <- case_when(list1[[1]]['V1']==1~'a',
          list1[[1]]['V1']==3~NA_character_,
          list1[[1]]['V1'] %in% 2:4 ~'b')

# Compare outputs - Single brackets gives NA where double brackets give "b"
brack2 # double
#> [1] "a" "b" NA  "b" NA
brack1 # single
#> [1] "a" NA  NA  NA  NA

Почему мы получаем разные результаты? Давайте посмотрим, какой объект возвращается в результате индексации в каждом из способов.

brack2_obj <- list1[[1]][['V1']] # double
brack1_obj <- list1[[1]]['V1'] # single

brack2_obj # double object
#> [1] 1 2 3 4 5
brack1_obj # single object
#>   V1
#> 1  1
#> 2  2
#> 3  3
#> 4  4
#> 5  5

Это явно разные объекты! Давайте посмотрим, какие они...

str(brack2_obj) # atomic vector (int)
#>  int [1:5] 1 2 3 4 5
str(brack1_obj) # data.frame with a column (int)
#> 'data.frame':    5 obs. of  1 variable:
#>  $ V1: int  1 2 3 4 5

Хорошо, это может быть проблематично. Давайте проведем логические тесты, которые вы просили case_when делать, но по одному, чтобы увидеть их выходные данные, и для каждого объекта, чтобы увидеть, не создает ли это проблем при логическом тестировании.

brack2_obj == 1
#> [1]  TRUE FALSE FALSE FALSE FALSE

brack1_obj == 1
#>         V1
#> [1,]  TRUE
#> [2,] FALSE
#> [3,] FALSE
#> [4,] FALSE
#> [5,] FALSE

Это порождает истину и ложь там, где мы ожидаем, и в том количестве, которое мы ожидаем. Конечно, структуры различаются, но мы можем углубиться в вопрос, имеет ли это значение, если не видим никаких других проблем.

brack2_obj == 3
#> [1] FALSE FALSE  TRUE FALSE FALSE

brack1_obj == 3
#>         V1
#> [1,] FALSE
#> [2,] FALSE
#> [3,]  TRUE
#> [4,] FALSE
#> [5,] FALSE

Поскольку на самом деле это ничем не отличается от == 1, мы ожидали аналогичных результатов. Проблем пока нет.

brack2_obj %in% 2:4
#> [1] FALSE  TRUE  TRUE  TRUE FALSE

brack1_obj %in% 2:4
#> [1] FALSE

Мы определили нашу проблему! Использование двойных скобок возвращает вектор, проверяющий каждый элемент атомарного вектора (длина 5). Однако мы получаем только одно значение FALSE для одиночных скобок. Остальные — NA, потому что им не передается значение через case_when(), а NA — это значение функции по умолчанию для отсутствия возвращаемого значения.

Мы можем доказать это, просто присвоив все, чему НЕ присвоено значение, через первые три оператора в вызове case_when() некоторой строки. Здесь я называю это «ДОКАЗАТЕЛЬСТВО». Мы увидим, как NA обратятся к «ДОКАЗАТЕЛЬСТВУ», если это то, что происходит с этими ценностями.

case_when(list1[[1]]['V1']==1~'a',
          list1[[1]]['V1']==3~NA_character_,
          list1[[1]]['V1'] %in% 2:4 ~'b',
          TRUE ~ "PROOF")
#> [1] "a"     "PROOF" NA      "PROOF" "PROOF"

NA остается для значения, равного 3, и меняется на другое, для которого по умолчанию установлено значение NA. Вот, разница обнаружена!

Это показывает, что вы почти всегда захотите передать вектор в case_when(). Вот пример:

# Example data
df <-   data.frame(a = 1:5)

# Produces NAs because we're passing a data.frame to %in%
case_when(df == 1 ~ "foo",
          df %in% 2:3 ~ "bar",
          df > 3 ~ "pow")  
#> [1] "foo" NA    NA    "pow" "pow"

# Can fix with new default values, but this is bad coding practice since we 
# could have other reasons for NAs!
case_when(df == 1 ~ "foo",
          df %in% 2:3 ~ "bar",
          df > 3 ~ "pow",
          TRUE ~ "bar")  
#> [1] "foo" "bar" "bar" "pow" "pow"

# Instead, pass the vector (this is equivalent to double indexing) to test if
# the values are in a range
case_when(df == 1 ~ "foo",
          df$a %in% 2:3 ~ "bar",
          df > 3 ~ "pow")  
#> [1] "foo" "bar" "bar" "pow" "pow"

Хотя в приведенном выше примере я использую df$a для извлечения вектора, вы можете использовать любую другую форму индексации, которая решает эту задачу. Некоторые примеры:

identical(df$a, df[[1]], df[1][[1]], df %>% dplyr::pull(a))
[1] TRUE

Я только что обновил аналогичный вашему! Ваш ответ очень информативен, спасибо!

doraemon 14.08.2024 08:19

Я бы посоветовал, если нам нужно использовать case_when в переменной внутри фрейма данных, мы всегда должны использовать переменную dataframe$, поскольку она вернет вектор.

doraemon 14.08.2024 08:20

Использование df$variable не всегда необходимо или желательно. Важно передать сам вектор логическому тесту, используя любую форму извлечения/индексации. В конце своего ответа я показал кучу эквивалентного кода.

socialscientist 14.08.2024 08:28

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