提取运算符 `$`() returns 函数内的零长度向量

Extraction operator `$`() returns zero-length vectors within function

我在函数内部使用提取运算符 `$() 时遇到问题。如果我在循环外遵循相同的逻辑,则问题不存在,因此我假设可能存在我不知道的范围问题。

一般设置:

## 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)。对我所缺少的有什么想法吗?也欢迎使用其他方法。

问题不在于功能,而在于 $ 处理输入的方式。

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]]

此外,this question/answer 更详细地说明了为什么 $ 无法按您希望的方式工作。

问题在于您对该列进行索引的方式。这只是对你的稍作调整:

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

问题是 $ 是非标准的,在某种意义上,当您不引用参数输入时,它仍然会尝试解析它并使用您键入的内容,即使那是意味着引用另一个变量。

或者更简单地说,正如@42 在 linked question 的第一条评论中所说:

The "$" function does not evaluate its arguments, whereas "[[" does`.

这里有一个更简单的数据集作为示例。

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

相反,我们需要在使用 do.call.

发送到 $ 之前评估 v
do.call(`$`, list(my_df, v))
## [1] 1 2

或者,更恰当地说,使用首先评估参数的 [[ 版本。

`[[`(my_df, v)
## [1] 1 2