入口

假设我们有一个变量, 这个变量的值是字符串, 这个字符串是某一个数据集的变量名.

library(rlang)
VarName <- "Species"

然后,我们想要通过变量提取 iris 数据集中的Species列

iris$VarName
## NULL

可以看到,返回结果是NULL. 对于这个例子,实现的方式有两种:

x <- parse(text = paste("iris$",VarName,sep = ""))
x
## expression(iris$Species)
head(eval(x)) # iris[VarName]
## [1] setosa setosa setosa setosa setosa setosa
## Levels: setosa versicolor virginica

通过字符串创建表达式,然后运行表达式是非常常用的一种技巧.

在base R中,expression函数可以将执行的代码暂停到expression的中间态,而eval函数(evaluate)则可以继续执行一个被暂停的expression语句。在rlang包中,expr函数类似于expression函数(expr函数暂停后的代码对象是call,基本上和expression是一个意思就行,以下统一使用expression),而eval_tidy函数类似于eval函数。

library(rlang)
x <- expr(!!paste("iris$",VarName,sep = ""))

eval(x)
## [1] "iris$Species"

R表达式基本概念

在R语言中,“表达式”的概念有狭义和广义两种意义。狭义的表达式指表达式(expression)类对象,由expression函数产生;而广义的的表达式既包含expression类,也包含R“语言”类(language)。expression和language是R语言中两种特殊数据类:

getClass("expression")
## Class "expression" [package "methods"]
## 
## No Slots, prototype of class "expression"
## 
## Extends: "vector"
getClass("language")
## Virtual Class "language" [package "methods"]
## 
## No Slots, prototype of class "name"
## 
## Known Subclasses: 
## Class "name", directly
## Class "call", directly
## Class "{", directly
## Class "if", directly
## Class "<-", directly
## Class "for", directly
## Class "while", directly
## Class "repeat", directly
## Class "(", directly
## Class ".name", by class "name", distance 2, with explicit coerce

可以看到expression类由向量派生得到,而language类是虚拟类,它包括我们熟悉的程序控制关键词/符号和name、call 子类。

虽然我们在R终端键入的任何有效语句都是表达式,但这些表达式在输入后即被求值(evaluate)了,获得未经求值的纯粹“表达式”就要使用函数。下面我们从函数参数和返回值两方面了解expression、quote、bquote和substitute这几个常用函数。

expression

expression函数可以有一个或多个参数,它把全部参数当成一个列表,每个参数都被转成一个表达式向量,所以它的返回值是表达式列表,每个元素都是表达式类型对象,返回值的长度等于参数的个数:

(ex <- expression(x = 1, 1 + sqrt(a)))
## expression(x = 1, 1 + sqrt(a))
class(ex)
## [1] "expression"
a=100
eval(ex[2])
## [1] 11

quote函数

quote函数只能有一个参数。quote函数的返回值一般情况下是call类型

(cl <- quote(1 + sqrt(a) + b^c))
## 1 + sqrt(a) + b^c
b=4
c=2
eval(cl)
## [1] 27

不使用环境变量或环境变量参数,bquote 和 substitute 函数得到的结果与quote函数相同。

但是bquote 和 substitute 函数可以在表达式中使用变量,变量的值随运行进程而被替换。bquote 和 substitute 函数变量替换的方式不一样,bquote函数中需要替换的变量用 .( ) 引用,substitute函数中需要替换的变量用列表参数方式给出。除了这一点,bquote 和 substitute 函数没有差别:

a <- 3
 
b <- 2
 
(bq <- bquote(y == sqrt(.(a), .(b))))
## y == sqrt(3, 2)
(ss <- substitute(y == sqrt(a, b), list(a = 3, b = 2)))
## y == sqrt(3, 2)

parse 函数

parse函数用于从文件读取文本作为表达式,返回的值是expression类型,这函数也很有用。后面有例子。

x <- parse(text = "10^3")

x
## expression(10^3)
eval(x)
## [1] 1000

call / do.call

cl <- call("round", 10.5)
cl
## round(10.5)
eval(cl)
## [1] 10

do.call 可以接受多个参数,并且会直接获取结果

do.call(paste, list(as.name("A"), as.name("B")), quote = TRUE) 
## [1] "A B"

rlang

rlang 是专门用于进行非标准化计算的一个包,即处理表达式方面的包. 首先再回顾一些基本概念.

symbol

Symbol-符号, 例如pi, 储存在R中用来表示一个变量或者对象的一个名词.

expr(pi)
## pi
class(expr(pi))
## [1] "name"
is_symbol(expr(pi))
## [1] TRUE

constant 常量

就是一个值

is_bare_atomic(1)
## [1] TRUE

expression

还没有被运行的代码

is_expression(expr(a + b))
## [1] TRUE

environment

绑定了一系列变量的对象

is_environment(current_env())
## [1] TRUE

Call object

函数调用的表达式

is_call(expr(abs(1)))
## [1] TRUE

Quosure

存储了引用代码和对应环境的对象.

is_quosure(quo(a + b))
## [1] TRUE

rlang 详细介绍

Quoting Code

带有环境信息的表达式

a <- 1; b <- 2; q <- quo(a + b); qs <- quos(a, b)

q
## <quosure>
## expr: ^a + b
## env:  global
qs
## <list_of<quosure>>
## 
## [[1]]
## <quosure>
## expr: ^a
## env:  global
## 
## [[2]]
## <quosure>
## expr: ^b
## env:  global

类似的函数还包括:

quote_this < - function(x) enquo(x)  
quote_these < - function(…) enquos(…) # 通过函数创建


new_quosure(expr(a + b), current_env()) # 指定环境

enquo 与quo 类似, 一个是在函数内部使用, 一个是在函数外部

Parsing and Deparsing

parse 表示将文本转化成为表达式 depares 将表达式转化成为字符串.

# parse_ 族函数
e<-parse_expr("a+b")
e
## a + b
expr_text(e)
## [1] "a + b"

parse_exprs是parse_expr的加强版,可以处理多个字符串。

在eval_tidy函数之外,还有轻量级的eval,就是eval_bare。两个函数差别不大,eval_bare不能接收数据,eval_tidy可以接收数据。

如果求值的时候不涉及到新的数据,而是像”mean(mtcars$wt)“,那么就可以用eval_bare

Building Calls

log (x = 4 , base = 2 ) # 等价于
## [1] 2
call2("log", x = 4, base = 2)
## log(x = 4, base = 2)
exec("log", x = 4, base = 2)
## [1] 2

EXPRESSION

创建表达式

a <- 1; b <- 2; e <- expr(a + b); es <- exprs(a, b, a + b)

表达式的创建其实和Quoting类似

Evaluation

eval_tidy(e)
## [1] 3

Quasiquotation (!!, !!!, :=)

!! 表示强制运算(结束引用)所有环境的结果

 a <- 1; b <- 2
expr(log(!!a + b))
## log(1 + b)
a <- 1; b <- 2
expr(log(!!(a + b))) 
## log(3)

!!! 结束引用列表,向量等等

x <- list(8, b = 2) 

expr(log(!!!x))
## log(8, b = 2)
expr(log(x))
## log(x)

:= 允许unquoting 表达式出现在等号左边

n <- expr(uno) 
tibble::tibble(!!n := 1)
## # A tibble: 1 × 1
##     uno
##   <dbl>
## 1     1

Programming Recipes

如果要在函数使用使用,tidyverse ,需要结合rlang

PROGRAM WITH A QUOTING FUNCTION

如果我要写一个函数, 这个函数会根据参数选择计算数据集中的某一列的平均值

data_mean <- function(data,var){
  require(dplyr)
  data %>% 
    summarise(mean = mean(var))
}
# data_mean(iris,"Sepal.Length")

# Error in `summarise()`:
# ℹ In argument: `mean = mean(var)`.
# Caused by error in `mean()`:
# ! object 'Sepal.Length' not found
# Run `rlang::last_trace()` to see where the error occurred.
# Called from: signal_abort(cnd, .file)

这样写诗会报错的.需要改为

data_mean <- function(data, var) { 
 require(dplyr) 
 var <- rlang::enquo(var)
 data %>% 
 summarise(mean = mean(!!var))
}

data_mean(iris,Sepal.Length)
## Loading required package: dplyr
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
##       mean
## 1 5.843333

其他用法还包括

重新赋值

named_m <- function(data, var, name) { 
 require(dplyr) 
 var <- rlang::enquo(var) 
 name <- rlang::ensym(name)
 data %>% 
 summarise(!!name := mean(!!var))
}

group by

group_mean <- function(data, var, ...) { 
 require(dplyr) 
 var <- rlang::enquo(var) 
 group_vars <- rlang::enquos(...)
 data %>% 
 group_by(!!!group_vars) %>% 
 summarise(mean = mean(!!var))
}