认识非标准化求值 第一部分:基础知识

非标准化求值(Non-standard evaluation),英文缩写为NSE,是行家们在谈论R语言时喜欢使用的一个术语。那么非标准化求值到底是什么意思呢?要回答这个问题,揭开这一概念的神秘面纱,我先为大家介绍与之相对的一个概念,那就是标准化求值(是的,它不叫非-非标准化求值😉)。

以从数据中选取一列为例。在base R中,我们可以通过[[$来实现。前者使用的是标准化求值语义,而后者使用的则是非标准化求值。

使用[[的时候,我们需要在里面输入字符串。我们通常使用的是一串*文字*字符。

data(iris)
head(iris[["Species"]])
## [1] setosa setosa setosa setosa setosa setosa
## Levels: setosa versicolor virginica

此外,我们也可以在[[内输入*对象名*。这个对象名会被*求值*(这个值最好是一串字符或者数值,否则会出现异常)。

var <- "Species"
head(iris[[var]])
## [1] setosa setosa setosa setosa setosa setosa
## Levels: setosa versicolor virginica

前面的介绍不足为奇。接下来,我们来谈谈$的行为。类似[[,我们可以在$右侧输入一串字符。

head(iris$"Species")
## [1] setosa setosa setosa setosa setosa setosa
## Levels: setosa versicolor virginica

但是,在现实中,你可能永远不会这样做,因为有了$我们就不必这样做了。相反,我们可以在$右侧直接输入对象名。

head(iris$Species)
## [1] setosa setosa setosa setosa setosa setosa
## Levels: setosa versicolor virginica

这样做可以节省两次键入,因此大大方便了我们在控制台编辑交互式代码。那么,如果我们将前面定义好的var输入到$右侧,会怎么样呢?

head(iris$var)
## NULL

没有想到吧?你并不是唯一一个对此感到意外的人。我身边的很多R初学者都对此感到困惑。请记住,对象var的值为"Species"。使用标准化求值语义时,R会对var求值。而$利用的是非标准化求值,因此在使用$的时候,情况不一样。$会在数据iris中查找名为var的列。因为这个数据内没有这样命名的列,所以结果显示NULL(我个人认为结果显示为异常更好些,不过无妨)。

除了Speciesiris数据还包含了一个名为Sepal.Length的列。正如前面所说,我们有两种方式来选择这一列,通过iris[["Sepal.Length"]]或者iris$Sepal.Length。可是,如果我们的运行环境中已经存在一个名为Sepal.Length的变量,会怎么样呢?

Sepal.Length <- "Species"

iris[[Sepal.Length]]iris$Sepal.Length分别会得到什么结果呢?在你继续阅读之前,停下来,思考这个问题。 根据此前我们所讨论过的内容,你应该能够正确回答这个问题。如果你不是很确定的话,请回到顶部,再次阅读前面的段落。

先来看iris[[Sepal.Length]]。当我们使用[[时,对象名Sepal.Length被求值为"Species"。因此,在这种情况下,iris[[Sepal.Length]]iris[["Species"]]的结果是一致的。

head(iris[[Sepal.Length]])
## [1] setosa setosa setosa setosa setosa setosa
## Levels: setosa versicolor virginica

再来看iris$Sepal.Length。 R根本不关心运行环境中是否存在名为Sepal.Length的变量。相反,它的第一反应是在数据iris中查找名为Sepal.Length的变量,嗯,确实有一个。

head(iris$Sepal.Length)
## [1] 5.1 4.9 4.7 4.6 5.0 5.4

因此,即便你的运行环境中存在一个名为Sepal.Length的对象并且这一对象已经被赋予了某个值,当你执行iris$Sepal.Length的时候,R会直接绕过这个值。相反,它把数据*本身*视作运行环境,当对Sepal.Length求值时,你就会得到那一列的内容。这个过程完全不遵循R的标准化求值语义,这就是它被称为非标准化求值的原因。

如果你已经掌握了到目前为止我们所讨论的内容,那么恭喜你,你在掌握R的道路上向前迈进了一大步。但是,前方还有很多内容在等着你!在这系列文章的第二篇,我会示范如何执行非标准化求值。通过自己进行非标准化求值,你将进一步加深对R的理解,也会更加了解R的内部结构,这将帮助你写出像{dplyr}那样强大的功能包。敬请期待!

本文由林诗敏译自英文。


Thomas Neitmann

109 Words

2021-01-27 00:00 +0700

860d4e8 @ 2021-10-03