Оператор извлечения `$`() возвращает векторы нулевой длины внутри функции

Я сталкиваюсь с проблемой, когда использую оператор извлечения `$() внутри функции. Проблема не существует, если я следую той же логике за пределами цикла, поэтому я предполагаю, что может быть проблема с областью действия, о которой я не знаю.

Общая установка:

## Make some fake data for your reproducible needs.
set.seed(2345)

my_df <- data.frame(cat_1 = sample(c("a", "b"), 100, replace = TRUE),
                    cat_2 = sample(c("c", "d"), 100, replace = TRUE),
                    continuous  = rnorm(100),
                    stringsAsFactors = FALSE)
head(my_df)

Этот процесс я пытаюсь динамически воспроизвести:

index <- which(`$`(my_df, "cat_1") == "a")

my_df$continuous[index]

Но как только я программирую эту логику в функцию, она терпит неудачу:

## Function should take a string for the following:
##  cat_var - string with the categorical variable name as it appears in df
##  level - a level of cat_var appearing in df
##  df - data frame to operate on.  Function assumes it has a column 
##    "continuous".
extract_sample <- function(cat_var, level, df = my_df) {

  index <- which(`$`(df, cat_var) == level)

  df$continuous[index]

}

## Does not work.
extract_sample(cat_var = "cat_1", level = "a")

Это возвращает numeric(0). Любые мысли о том, что мне не хватает? Приветствуются и альтернативные подходы.


person apax    schedule 26.04.2018    source источник
comment
Fwiw, я бы сделал extract_sample = function(var, val, df = my_df) merge(df, setNames(data.frame(val), var), all.y=TRUE) или что-то подобное.   -  person Frank    schedule 26.04.2018


Ответы (3)


Проблема не в функции, а в том, как $ обрабатывает ввод.

cat_var = "cat_1"
length(`$`(my_df,"cat_1"))
#> [1] 100
length(`$`(my_df,cat_var))
#> [1] 0 

Вместо этого вы можете использовать [[ для достижения желаемого результата.

cat_var = "cat_1"
length(`[[`(my_df,"cat_1"))
#> [1] 100
length(`[[`(my_df,cat_var))
#> [1] 100

ОБНОВЛЕНИЕ

Было замечено, что использование [[ таким образом некрасиво. И это. Это полезно, когда вы хотите написать что-то вроде lapply(stuff,'[[',1)

Здесь вы, вероятно, должны написать это как my_df[[cat_var]].

Кроме того, этот вопрос/ответ содержит более подробную информацию о том, почему $ не работай так, как ты хочешь.

person Mark    schedule 26.04.2018
comment
Что некрасиво? Как R обрабатывает это или мой пример? - person Mark; 26.04.2018
comment
@HongOoi Я разделяю ваше эстетическое суждение, но, возможно, его можно было бы выразить по-другому? ;) Я думаю, дело в том, что нам, вероятно, следует использовать [[ с его обычной семантикой: my_df[[cat_var]]. - person joran; 26.04.2018
comment
Основная причина сделать это таким образом — поддержать семью apply. - person Mark; 26.04.2018
comment
Я думаю, возможно, это было немного запутанно, потому что OP действительно не нужно было использовать [[ в такой анонимной функции, передаваемой в контекст lapply, вот и все. - person joran; 26.04.2018
comment
Спасибо, ребята, оператор [[ — это именно то, о чем я забыл. Это немного упрощает дело. - person apax; 26.04.2018
comment
Честно, я просто пытался подражать стилю, который они использовали. Обновлен ответ, чтобы показать более привлекательную форму. - person Mark; 26.04.2018

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

Или, проще говоря, как @42 выразился в первом комментарии в связанном вопросе:

Функция "$" не оценивает свои аргументы, тогда как "[[" делает`.

Вот, например, гораздо более простой набор данных.

my_df <- data.frame(a=c(1,2))
v <- "a"

Сравните обычное использование; первые два дают один и тот же результат, если вы не цитируете его, он его анализирует. Так что третий (сейчас) явно не работает должным образом.

my_df$"a"
## [1] 1 2

my_df$a
## [1] 1 2

my_df$v
## NULL

Это именно то, что происходит с вами:

`$`(my_df, "a")
## [1] 1 2

`$`(my_df, v)
## NULL

Вместо этого нам нужно оценить v перед отправкой в ​​$ с помощью do.call.

do.call(`$`, list(my_df, v))
## [1] 1 2

Или, что более уместно, используйте версию [[, которая сначала оценивает параметры.

`[[`(my_df, v)
## [1] 1 2
person Aaron left Stack Overflow    schedule 26.04.2018
comment
Помимо этой ссылки, есть также stackoverflow.com/a/18228613, в котором цитируется исходный код для не оцененной точки. - person Frank; 26.04.2018

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

extract_sample <- function(cat_var, level, df = my_df) {
  index <- df[, cat_var] == level
  df$continuous[index]
}

Используя его динамически:

> extract_sample(cat_var = "cat_2", level = "d")
 [1] -0.42769207 -0.75650031  0.64077840 -1.02986889  1.34800344  0.70258431  1.25193247
 [8] -0.62892048  0.48822673  0.10432070  1.11986063 -0.88222370  0.39158408  1.39553002
[15] -0.51464283 -1.05265106  0.58391650  0.10555913  0.16277385 -0.55387829 -1.07822831
[22] -1.23894422 -2.32291394  0.11118881  0.34410388  0.07097271  1.00036812 -2.01981056
[29]  0.63417799 -0.53008375  1.16633422 -0.57130500  0.61614135  1.06768285  0.74182293
[36]  0.56538633  0.16784205 -0.14757303 -0.70928924 -1.91557732  0.61471302 -2.80741967
[43]  0.40552376 -1.88020372 -0.38821089 -0.42043745  1.87370600 -0.46198139  0.10788358
[50] -1.83945868 -0.11052531 -0.38743950  0.68110902 -1.48026285
person rg255    schedule 26.04.2018
comment
Обратите внимание, что это будет работать с обычными фреймами данных, но если вы используете функции tidyverse, которые возвращают таблички, df[, x] вернет табличку с 1 столбцом. Вместо этого лучше использовать df[[x]], который работает со всем. - person Hong Ooi; 26.04.2018
comment
Или просто не используйте tidyverse ;) (спасибо, и это хорошо иметь в виду) - person rg255; 26.04.2018