Я работаю со списком в 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("[")
. И я думаю, что это связано с %in%
? Я думаю, разница в том, как %in%
обрабатывает вектор и фрейм данных?
Голосование за повторное открытие, поскольку это не просто разница между [[
и [
, а, скорее, несколько необычный/специфичный случай того, как case_when()
обрабатывает результат %in%
с векторами и data.frames. Хотя это правда, что разные индексации извлекают разные объекты, почему разные объекты выдают соответствующие результаты - это разумный вопрос, на который я сразу не вижу ответа где-либо еще (хотя, возможно, я просто недостаточно хорошо ищу OP).
Разная индексация возвращает разные объекты: один — атомарный вектор, а другой — 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
Я только что обновил аналогичный вашему! Ваш ответ очень информативен, спасибо!
Я бы посоветовал, если нам нужно использовать case_when
в переменной внутри фрейма данных, мы всегда должны использовать переменную dataframe$, поскольку она вернет вектор.
Использование df$variable не всегда необходимо или желательно. Важно передать сам вектор логическому тесту, используя любую форму извлечения/индексации. В конце своего ответа я показал кучу эквивалентного кода.
Вы читали
help("[")
? Одинарные скобки возвращают список, двойные скобки возвращают элемент списка.