R语言高级编程-表达式、数学公式与特殊符号

在R语言的绘图函数中,如果文本参数是合法的R语言表达式,那么这个表达式就被用Tex类似的规则进行文本格式化。

AD:

y <- function(x) log(x) + sqrt(x) + x^(1/3)
lot(y, 1, 1000, main = expression(y == log(x) + sqrt(x) + sqrt(x, 3)), lwd = 3,
col = "blue")

一、R语言的“表达式”

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

## 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这几个常用函数。

1、expression 函数

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

(ex <- expression(x = 1, 1 + sqrt(a))) 
## expression(x = 1, 1 + sqrt(a)) 
length(ex) 
## [1] 2 
ex[1] 
## expression(x = 1) 
mode(ex[1]) 
## [1] "expression" 
typeof(ex[1]) 
## [1] "expression" 
ex[2] 
## expression(1 + sqrt(a)) 
mode(ex[2]) 
## [1] "expression" 
typeof(ex[2]) 
## [1] "expression" 

因为expression函数把参数当成列表处理,所以等号‘=’两边的表达式要符合R语言列表元素的书写规则,否则出错,比如:

expression(x+11=1)

2、quote函数

quote函数只能有一个参数。quote函数的返回值一般情况下是call类型,表达式参数是单个变量的话返回值就是name类型,如果是常量那么返回值的存储模式就和相应常量的模式相同:

  • quo()
  • expr(expr)
  • enexpr(arg)
  • exprs(
    ...,
    .named = FALSE,
    .ignore_empty = c("trailing", "none", "all"),
    .unquote_names = TRUE
    )
  • enexprs(
    ...,
    .named = FALSE,
    .ignore_empty = c("trailing", "none", "all"),
    .unquote_names = TRUE,
    .homonyms = c("keep", "first", "last", "error"),
    .check_assign = FALSE
    )
  • ensym(arg)
  • ensyms(
    ...,
    .named = FALSE,
    .ignore_empty = c("trailing", "none", "all"),
    .unquote_names = TRUE,
    .homonyms = c("keep", "first", "last", "error"),
    .check_assign = FALSE
    )
  • quo(expr)
  • enquo(arg)
  • quos(
    ...,
    .named = FALSE,
    .ignore_empty = c("trailing", "none", "all"),
    .unquote_names = TRUE
    )
  • enquos(
    ...,
    .named = FALSE,
    .ignore_empty = c("trailing", "none", "all"),
    .unquote_names = TRUE,
    .homonyms = c("keep", "first", "last", "error"),
    .check_assign = FALSE
    )
(cl <- quote(1 + sqrt(a) + b^c)) 
## 1 + sqrt(a) + b^c 
mode(cl) 
## [1] "call" 
typeof(cl) 
## [1] "language" 
(cl <- quote(a)) 
## a 
mode(cl) 
## [1] "name" 
typeof(cl) 
## [1] "symbol" 
(cl <- quote(1)) 
## [1] 1 
mode(cl) 
## [1] "numeric" 
typeof(cl) 
## [1] "double" 

quote返回值如果是name或常量类型,它的长度就是1;如果是call类型,返回值长度就与函数/运算符的参数个数n对应,长度等于n+1,多出的长度1是函数/符号名。

length(quote(a))  #name或常量类型,返回值长度为1 
## [1] 1 
length(quote(!a))  #单目运算符,返回值长度为2 
## [1] 2 
length(quote(-b))  #单目运算符,返回值长度为2 
## [1] 2 
length(quote(a + b))  #双目运算符,返回值长度为3 
## [1] 3 
length(quote((a + b) * c))  #多个运算符只算优先级最低的一个 
## [1] 3 

3、quote、bquote 、 substitute 函数(base R函数)

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

substitute(expr, env)
quote(expr)
enquote(cl)
bquote(1 + sqrt(a) + b^c) == quote(1 + sqrt(a) + b^c) 
## [1] TRUE 
substitute(1 + sqrt(a) + b^c) == quote(1 + sqrt(a) + b^c) 
## [1] TRUE

但是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) 
bq == ss 
## [1] TRUE 

搞出两个功能完全一样的函数不算很奇怪,R语言里面太多了,可能是照顾不同使用习惯的人们吧。
bquote函数的帮助档说这个函数类似于LISP的backquote宏,对于像我这样的LISP盲,使用substitute函数好一些。
substitute函数的典型用途是替换表达式中的变量,如果我们希望在表达式中使用变量并且希望这些变量在运行过程中做出相应改变,就可以使用substitute函数。

par(mar = rep(0.1, 4), cex = 2) 
plot.new() 
plot.window(c(0, 10), c(0, 1)) 
for (i in 1:9) text(i, 0.5, substitute(sqrt(x, a), list(a = i + 1))) 

4、parse 函数

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

三、表达式规则与paste函数:

与在R终端直接输入的表达式不一样,expression、quote、bquote和substitute等函数对参数中的(变量)名称都不做任何检查:

x <- 1 
x + "x" 
## Error: 二进列运算符中有非数值参数 
expression(x + "x") 
## expression(x + "x") 
quote(x + "x") 
## x + "x" 

但R要检查表达式中的运算符,不符合运算符使用规则的表达式将出错:

expression(x + +++y) 
## expression(x + +++y) 
expression(x - ---y) 
## expression(x - ---y) 
# expression(x****y) (Not run) expression(x////y) (Not run) 
# expression(1<=x<=4) (Not run) 
quote(x + +++y) 
## x + +++y 
quote(x - ---y) 
## x - ---y 
# quote(x****y) (Not run) quote(x////y) (Not run) quote(1<=x<=4) (Not run) 

+ - 运算连续使用不出错是因为它们还可以当成求正/负值运算的符号。 在表达式产生函数中使用paste函数可以解决这样的问题。在这种条件下,paste对参数的处理方式和表达式产生函数一样,检查运算符但不检查变量名。用NULL作为运算符的参数可以获得意外的效果:

ex <- expression(paste(x, "////", y)) 
cl <- quote(paste(x, "****", y)) 
par(mar = rep(0.1, 4), cex = 2) 
plot.new() 
plot.window(c(0, 1.2), c(0, 1)) 
text(0.2, 0.5, ex) 
text(0.6, 0.5, cl) 
cl <- quote(paste(1 <= x, NULL <= 4)) 
text(1, 0.5, cl) 

四、R绘图函数对文本参数中的表达式的处理

quote, bquote 和 substitute 的返回值有三种类型call, name 和 常量,事实上expression 函数的结果最终也是这三种类型。因为expression函数的结果是expression列表,我们取列表元素的值检查看看:

(ex <- expression(1 + sqrt(x), x, 1)) 
## expression(1 + sqrt(x), x, 1) 
ex[[1]] 
## 1 + sqrt(x) 
mode(ex[[1]]) 
## [1] "call" 
typeof(ex[[1]]) 
## [1] "language" 
ex[[2]] 
## x 
mode(ex[[2]]) 
## [1] "name" 
typeof(ex[[2]]) 
## [1] "symbol" 
ex[[3]] 
## [1] 1 
mode(ex[[3]]) 
## [1] "numeric" 
typeof(ex[[3]]) 
## [1] "double" 

确实是这样。所以绘图函数对文本参数中的表达式处理就有三种情况。先看看处理结果:

par(mar = rep(0.1, 4), cex = 2) 
plot.new() 
plot.window(c(0, 1.2), c(0, 1)) 
text(0.2, 0.5, ex[1]) 
text(0.6, 0.5, ex[2]) 
text(1, 0.5, ex[3]) 

name 和常量类型都很简单,直接输出文本,而call类型就不好判断了。我们前面说过call类型返回值的长度与函数/运算符的参数个数有关。这是怎么体现的呢?由于文本参数最终得到的是文本,我们用as.character函数来看看:

as.character(quote(x - y)) 
## [1] "-" "x" "y" 
as.character(quote(1 - x + y)) 
## [1] "+"     "1 - x" "y" 
as.character(quote((1 + x) * y)) 
## [1] "*"       "(1 + x)" "y" 
as.character(quote(!a)) 
## [1] "!" "a" 
as.character(quote(sqrt(x))) 
## [1] "sqrt" "x" 

转换成字符串向量后排在第一位的是运算符或函数名称,后面是参数(如果参数中还有运算符或函数名,R还会对其进行解析)。运算符和函数是相同的处理方式。事实上,在R语言中,所有运算符(包括数学运算符和逻辑运算符)都是函数,你可以用函数的方式使用运算符:

2 + 4 
## [1] 6 
2 - 4 
## [1] -2 
2 <= 4 
## [1] TRUE 
2 >= 4 
## [1] FALSE 

R绘图函数对表达式中包含的函数名和它们的参数首先应用Tex文本格式化规则进行处理,这种规则的具体情况可以使用 ?plotmath 进行查看,主要是一些数学公式和符号的表示方法。把这个说明文档中字符串拷贝到maths.txt文件中并保存到当前工作目录后可以用下面的代码做出后面的表格:

ex <- parse("maths.txt") 
labs <- readLines("maths.txt") 
n <- length(ex) 
par(mar = rep(0.1, 4), cex = 0.8) 
plot.new() 
plot.window(c(0, 8), c(0, n/4)) 
y <- seq(n/4, by = -1, length = n/4) 
x <- seq(0.1, by = 2, length = 4) 
xy <- expand.grid(x, y) 
text(xy, labs, adj = c(0, 0.5)) 
xy <- expand.grid(x + 1.3, y) 
text(xy, ex, adj = c(0, 0.5), col = "blue") 
box(lwd = 2) 
abline(v = seq(1.3, by = 2, length = 4), lty = 3) 
abline(v = seq(2, by = 2, length = 3), lwd = 1.5) 

表中奇数列是字符串(表达式),偶数列(蓝色)是Tex格式化的图形。除了上表列出的规则外还有一些拉丁文和希腊文符号,可以在表达式中用 symbol 函数或名称(如alpha)等表示,用到时自己去找吧。 如果函数名(包括运算符)有对应的Tex格式化规则,函数名和参数都按规则进行图形绘制;如果没有,就当成是R语言普通函数:

ex <- expression(sqrt(x), x + y, x^2, x %in% A, x <= y, mean(x, y, z), x | y,  
    x & y) 
n <- length(ex) 
par(mar = rep(0.1, 4), cex = 1.5) 
col <- c("red", "blue") 
plot.new() 
plot.window(c(0, n), c(0, 1)) 
for (i in 1:n) text(i - 0.5, 0.5, ex[i], colcol = col[i%%2 + 1]) 

上面例子中前5种运算函数都是有对应数学符号的,所以它出的图(符号和顺序)与数学习惯一致,后三种运算函数没有对应数学符号,所以用普通函数方式(函数名在前,参数在括号内用逗号分隔)出图。其他还有一些琐碎的规则,自己找找吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容