先来看一个R plot绘制的图:
x <- seq(0,2*pi,length=100)
sinx <- sin(x)
plot(x,sinx,type="l")
从图中可以看出,plot函数自动把x和y轴的标题设置为两个变量的名字:x和sinx。在大部分编程语言中,我们只可以访问函数参数x和sinx的值,而不是参数自身。在R中,这种对函数参数进行计算的方式,称为非标准计算,简称为NSE。
13.1 表达式获取
Base R中的substitue()函数用来实现非标准计算。它用来查找函数参数,但并不关心函数参数的值。
f <- function(x) {
substitute(x)
}
f(1:10)
## 1:10
x <- 10
f(x)
## x
y <- 13
f(x+y^2)
## x + y^2
substitute()返回的是表达式类型。该函数的参数使用了一种特殊的类型,约定(promise)。该类型捕获用来计算的表达式和执行该表达式的环境。
substitute()和deparse()经常会搭配使用。deparse()以substitute()的返回结果(表达式)为参数,输出一个字符向量。
g <- function(x) deparse(substitute(x))
g(1:10)
## [1] "1:10"
g(x)
## [1] "x"
g(x+y^2)
## [1] "x + y^2"
上述函数组合的一个用法,譬如在加载包时,可以不用输入双引号。
library(data.table)
library("data.table")
其他函数,例如plot.default(),使用上述函数来提供默认的标签。
data.frame函数使用上述函数来记录整合进数据框的变量的名称。
x <- 1:4
y <- letters[1:4]
names(data.frame(x,y))
## [1] "x" "y"
练习
1 为什么deparse解析会返回多个字符串,这是因为,它的参数中,定义了width.cutoff参数,超过60字符,就会切割。
g(a+b+c+d+e+f+g+h+i+j+k+l+m+n+o+p+q+r+s+t+u+v+w+x+y+z)
## [1] "a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + "
## [2] " q + r + s + t + u + v + w + x + y + z"
避免切割的方法,重新定义g函数
g <- function(x) paste0(deparse(substitute(x)), collapse = "")
g(a+b+c+d+e+f+g+h+i+j+k+l+m+n+o+p+q+r+s+t+u+v+w+x+y+z)
## [1] "a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z"
2 为什么as.Date.default()使用substitute()和deparse()
从下边代码中可以看到,当无法判断x的类型时,会输出消息不知道如何转换x为日期类型。因此需要通过这两个函数获得x的name。
function (x, ...)
{
if (inherits(x, "Date"))
x
else if (is.null(x))
.Date(numeric())
else if (is.logical(x) && all(is.na(x)))
.Date(as.numeric(x))
else stop(gettextf("do not know how to convert '%s' to class %s",
deparse1(substitute(x)), dQuote("Date")), domain = NA)
}
as.Date(as.name("a"))
## Error in as.Date.default(as.name("a")): 不知如何将'as.name("a")'转换成"Date"类别
对于pairwise.t.test()
函数,其中有一行代码DNAME <- paste(deparse1(substitute(x)), "and", deparse1(substitute(g)))
,需要获得其参数x和g的name作为输出结果的数据名,因此用到上述两个函数。
3 pairwise.t.test()假设deparse()总是返回一个长度为1的字符向量,如何构建一个违反这个假设的输入?
只需要将变量名字命名为x1、x2等2个字符的变量即可。
4
#返回表达式
f <- function(x) substitute(x)
#返回字符串
g <- function(a) deparse(f(x=a))
# 返回1:10
f(1:10)
## 1:10
# 返回"x",因为对f()函数来说,substitute函数看到的是a,而不是1:10
g(1:10)
## [1] "a"
# 返回"x"
g(x+y^2/z+exp(a*sin(b)))
## [1] "a"
#
13.2 subset函数的非标准计算
sample_df <- data.frame(a = 1:5, b = 5:1, c = c(5, 3, 1, 4, 1))
subset(sample_df, a > 3)
## a b c
## 4 4 2 4
## 5 5 1 1
subset(sample_df, b == c)
## a b c
## 1 1 5 5
## 5 5 1 1
表达式 a > 3 或b == c在指定数据框sample_df中执行,而不是在当前或全局环境中。这其实是非标准计算的本质。
subset的工作机制:
- 希望对a能够解释成sample_df\(a而不是全局globalenv()\)a。因此需要用到eval()函数在特定的环境中对表达式进行计算。
- quote()函数捕获输入表达式本身,但是并不对其进行任何高级转换。
quote(1:10)
## 1:10
quote(x)
## x
quote(x+y^2)
## x + y^2
quote()和eval()是对立的。在下边的例子中,每个eval都会剥去一层quote()。
quote(2+2)
## 2 + 2
eval(quote(2+2)) #4
## [1] 4
quote(quote(2+2)) #quote(2+2)
## quote(2 + 2)
eval(quote(quote(2+2))) # 2+2
## 2 + 2
eval(eval(quote(quote(2+2)))) #4
## [1] 4
eval()的第二个参数设置执行代码的环境,在特定环境e中设定x的值为9,那么eval评估会输出9。
x <- 10
eval(quote(x))
## [1] 10
e <- new.env()
e$x <- 9
eval(expr = quote(x), envir = e)
## [1] 9
eval()的第二个参数也可以是列表或者数据框
eval(expr = quote(x), envir = list(x=8))
## [1] 8
eval(expr = quote(x), envir = data.frame(x=7))
## [1] 7
根据上述eval函数的功能,可以实现subset的部分功能
eval(expr = quote(a > 3), envir = sample_df)
## [1] FALSE FALSE FALSE TRUE TRUE
eval(expr = quote(b == c), envir = sample_df)
## [1] TRUE FALSE FALSE FALSE TRUE
如果忘记对第一个参数使用quote进行引用,会产生错误的结果
eval(a>3, envir = sample_df)
## Error in eval(a > 3, envir = sample_df): 找不到对象'a'
考虑使用eval()和subset()编写subset函数。 首先捕获代表条件的调用,然后在数据库的上下文中执行它,最后使用这个结果提取子集。
subset2 <- function(x, condition) {
row_index <- eval(substitute(condition), x)
x[row_index,]
}
subset2(sample_df, a >3)
## a b c
## 4 4 2 4
## 5 5 1 1
subset3 <- function(x, condition) {
row_index <- eval(quote(condition), x)
x[row_index,]
}
subset3(sample_df, a >3)
## Error in eval(quote(condition), x): 找不到对象'a'