在R中使用波浪号(~)和句点(.)的方法

7

我正在使用Hadley的R4DS书籍学习tidyverse和purrr中的循环,并且对于波浪号~符号和句点符号的确切使用有些困惑。

因此,在编写for循环或使用map()时,您似乎可以使用波浪线符号~代替function()。

这只适用于for循环吗?

如下所示...

models <- mtcars %>% 
  split(.$cyl) %>% 
  map(~lm(mpg ~ wt, data = .))

此外,我被告知可以使用“period i”来指代当前列表元素。但我不确定这是什么意思。这是不是只有在循环时,“period i”才表示它指的是正在循环的列表中的元素?这与管道有何不同?当您使用管道时,您将一行代码的结果传输到下一行代码中。
因此,在上面的例子中,mtcars被输入到split()函数的第二行,但是使用了一个点。为什么?
下面的例子总结了我的困惑:
x <- c(1:10)

detect(x, ~.x > 5)

使用detect函数可以找到第一个匹配项,我认为我只需要使用

detect(x, x >5)

但是我收到一个错误,说x>5不是一个函数。所以我添加了一个波浪符。

detect(x, ~ x > 5)

你会得到一个错误,说它期望一个单独的TRUE或FALSE,而不是10。因此,如果你添加一个句号

detect(x, ~.x >5) 

突然之间它像是循环一样工作了。那么这里的 ~ 和 . 有什么关系/用法,以及点号与简单管道符 | 相比如何?


1个回答

14

这整体被称为tidyverse非标准评估(NSE)。您可能已经发现也在公式中用于表示左侧取决于右侧

tidyverse NSE中,表示function(...)。因此,这两个表达式是等价的。

x %>% detect(function(...) ..1 > 5)
#[1] 6

x %>% detect(~.x > 5)
#[1] 6

~自动将函数的每个参数分配给..x.y..1..2..3这些特殊符号。请注意,只有第一个参数变成了.

map2(1, 2, function(x,y) x + y)
#[[1]]
#[1] 3

map2(1, 2, ~.x + .y)
#[[1]]
#[1] 3

map2(1, 2, ~..1 + ..2)
#[[1]]
#[1] 3

map2(1, 2, ~. + ..2)
#[[1]]
#[1] 3

map2(1, 2, ~. + .[2])
#[[1]]
#[1] NA

当变量很多时,这种自动分配非常有帮助。

mtcars %>% pmap_dbl(~ ..1/..4)
# [1] 0.19090909 0.19090909 0.24516129 0.19454545 0.10685714 0.17238095 0.05836735 0.39354839 0.24000000 0.15609756
#[11] 0.14471545 0.09111111 0.09611111 0.08444444 0.05073171 0.04837209 0.06391304 0.49090909 0.58461538 0.52153846
#[21] 0.22164948 0.10333333 0.10133333 0.05428571 0.10971429 0.41363636 0.28571429 0.26902655 0.05984848 0.11257143
#[31] 0.04477612 0.19633028

除了上述所有特殊符号之外,参数还分配给...。 就像R中的所有内容一样,...有点像参数的命名列表,因此您可以与with一起使用它:

mtcars %>% pmap_dbl(~ with(list(...), mpg/hp))
# [1] 0.19090909 0.19090909 0.24516129 0.19454545 0.10685714 0.17238095 0.05836735 0.39354839 0.24000000 0.15609756
#[11] 0.14471545 0.09111111 0.09611111 0.08444444 0.05073171 0.04837209 0.06391304 0.49090909 0.58461538 0.52153846
#[21] 0.22164948 0.10333333 0.10133333 0.05428571 0.10971429 0.41363636 0.28571429 0.26902655 0.05984848 0.11257143
#[31] 0.04477612 0.19633028

另一种理解这个操作的方式是,因为 data.frame 实际上就是带有行名称的 list
a <- list(a = c(1,2), b = c("A","B"))
a
#$a
#[1] 1 2
#$b
#[1] "A" "B"
attr(a,"row.names") <- as.character(c(1,2))
class(a) <- "data.frame"
a
#  a b
#1 1 A
#2 2 B

那么特殊符号,.x、.y、..1、..2、..3 和 . 只是引用符号吗?根据示例,它们似乎只是引用代码中较早的值。在 map(1,2, ~.x + .y) 中,.x 引用第一个值,.y 引用第二个值。然后在 mtcars 示例中,它几乎像是子集,因为 ..1 指的是 mtcars 的第一列。我理解得对吗? - Kevin Lee
1
你明白了。还要记住的一件事是,.在函数环境中被评估,而不是在对detect的调用中被评估。这就是为什么.指的是当前值x而不是整个向量的原因。 - Ian Campbell
在detect()的示例中,.指的是x的当前值而不是整个向量。这是因为在detect函数内部有一个for循环吗?接下来,在上面的示例中,.1.2在map(1,2,~.x + .y)示例中是“直接”引用的,但在mtcars示例中,它们更多地是子集。我如何知道符号何时被用于从原始值中进行子集操作,以及何时被用于直接引用该值。我猜在我们有map(mtcars, 1, 2, ~ )这样的情况下,我如何引用mtcars中的第三列而不是第三个值(2)? - Kevin Lee
1
抱歉,为了澄清,“.”指的是函数内第一个参数的值。请参见(function(x) x > 5)(x)help(detect)的评估。至于你关于map(mtcars, 1, 2, ~ )的问题,这不会产生你期望的结果,因为map只接受单个列表或向量进行应用。 - Ian Campbell
我明白了。最后一个问题!当我评估map(mtcars, ~ ..1/2)时,结果是每一列的值都除以2。而当我使用pmap(mtcars, ~ ..1 /2)时,只有第一列被除以2。所以在pmap的情况下,..1子集由第一列组成,但在map的情况下却不是。在map的情况下发生了什么? - Kevin Lee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接