学习R语言:编程结构
本文内容来自《R 语言编程艺术》(The Art of R Programming),有部分修改
注:从本文开始,笔者使用 RStudio 代替 Jupyter Lab 执行代码
控制语句
循环
for
,while
,repeat
x <- c(5, 12, 13)
for (n in x) print(n)
[1] 5
[1] 12
[1] 13
i <- 1
while (i <= 10) i <- i + 4
i
[1] 13
i <- 1
while (TRUE) {
i <- i + 4
if (i > 10) break
}
i
[1] 13
i <- 1
while (TRUE) {
i <- i + 4
if (i > 10) break
}
i
[1] 13
repeat
相当于 while(TRUE)
i <- 1
repeat {
i <- i + 4
if (i > 10) break
}
i
[1] 13
next
类似 C++ 中的 continue
对非向量集合的循环
方法1:使用 lapply()
,每次循环迭代之间相互独立。向量化方法可以加快执行速度
方法2:使用 get()
函数,通过指定代表对象名字的字符串名称返回对象。将对象名称作为向量集合实现循环
u <- matrix(
c(1, 2, 3, 1, 2, 4),
nrow=3
)
u
[,1] [,2]
[1,] 1 1
[2,] 2 2
[3,] 3 4
v <- matrix(
c(8, 12, 10, 15, 10, 2),
nrow=3
)
v
[,1] [,2]
[1,] 8 15
[2,] 12 10
[3,] 10 2
for (m in c("u", "v")) {
z <- get(m)
print(lm(z[,2] ~ z[,1]))
}
Call:
lm(formula = z[, 2] ~ z[, 1])
Coefficients:
(Intercept) z[, 1]
-0.6667 1.5000
Call:
lm(formula = z[, 2] ~ z[, 1])
Coefficients:
(Intercept) z[, 1]
21.50 -1.25
if-else结构
if-else 语句会返回最后赋予的值
x <- 2
y <- if (x == 2) x else x + 1
y
[1] 2
x <- 3
y <- if(x == 2) x else x + 1
y
[1] 4
处理向量时最好使用 ifelse()
函数
算术和逻辑运算符及数值
R 语言基本运算符
x + y
x - y
x * y
x / y
x ^ y
x %% y
:模运算x %/% y
:整数除法x == y
x <= y
x >= y
x && y
:标量的逻辑“与”运算x || y
:标量的逻辑“或”运算x & y
:向量的逻辑“与”运算x | y
:向量的逻辑“或”运算!x
:逻辑非
x <- c(TRUE, FALSE, TRUE)
x
[1] TRUE FALSE TRUE
y <- c(TRUE, TRUE, FALSE)
y
[1] TRUE TRUE FALSE
x & y
[1] TRUE FALSE FALSE
x[1] && y[1]
[1] TRUE
对向量使用 &&
,仅查看向量中的第一个值
x && y
[1] TRUE
if (x[1] && y[1]) print("both TRUE")
[1] "both TRUE"
if (x & y) print("both TRUE")
the condition has length > 1 and only the first element will be used
[1] "both TRUE"
TRUE
和 FALSE
可以简写为 T
和 F
,在算术表达式中会被转换为 1 和 0
1 < 2
[1] TRUE
(1 < 2) * (3 < 4)
[1] 1
(1 < 2) * (3 < 4) * (5 < 1)
[1] 0
(1 < 2) == TRUE
[1] TRUE
(1 < 2) == 1
[1] TRUE
参数的默认值
具名实参,有默认值的参数,如果不使用默认值,函数调用时必须给出参数名
scores <- read.csv(
"../data/student-mat.csv",
header=TRUE
)
head(scores)
返回值
返回值可以是任意 R 对象。通常为列表形式。
不调用 return()
语句时,返回值是最后执行的语句的值。
odd <- function(x) {
k <- 0
for (n in x) {
if (n %% 2 == 0) k <- k + 1
}
k
}
odd(1:10)
[1] 5
注意:如果省略最后一行的 k
,则返回结果为 for
语句的值:NULL
。
决定是否显示调用 return()
现在 R 语言普遍的习惯用法是避免显式调用 return()
。
原因之一是 return()
会延长执行时间。
但简单函数的返回时间往往可以忽略不计。
注:笔者强烈建议使用 return()
语句显式返回函数。优化往往不是最先考虑的问题。
返回复杂对象
g <- function() {
t <- function(x) {
return(x^2)
}
return(t)
}
g()
function(x) {
return(x^2)
}
<environment: 0x0000022f5d0ab1c0>
注:返回的函数包含函数的环境,实际上是一个闭包 (closure)
函数都是对象
R 函数是 first-class object,在绝大部分情况下可以作为对象使用。
创建函数的语法:
g <- function(x) {
return (x)
}
function()
是一个内置的 R 函数,用于创建函数。
右边是该函数的两个参数:
- 形式参数列表:上例中只有一个参数
- 函数体:expression 类,上例中是用大括号括起来的表达式
{
也是一个函数,将多个语句组织成一个单元。
两个参数可以通过 formals()
和 body()
函数获得。
formals(g)
$x
body(g)
{
return(x)
}
formals()
和 body()
也可以当做替代函数 (replacement functions)。
通过赋值修改函数体
g <- function(h, a, b) h(a, b)
formals(g) <- alist(x=)
body(g) <- quote(2 * x + 3)
g
function (x)
2 * x + 3
quota()
生成 call
类,表示函数体。2 *x + 3
实际上是函数调用。
g(3)
[1] 9
输入函数名称会显示整个函数
g
function (x)
2 * x + 3
R 中一些最基本的函数是用 C 语言写的,不能直接查看代码
sum
function (..., na.rm = FALSE) .Primitive("sum")
可以给函数对象赋值
f1 <- function(a, b) return (a + b)
f2 <- function(a, b) return (a - b)
f <- f1
f(3, 2)
[1] 5
f <- f2
f(3, 2)
[1] 1
g <- function(h, a, b) h(a, b)
g(f1, 3, 2)
[1] 5
g(f2, 3, 2)
[1] 1
函数也可以组成向量
g1 <- function(x) return(sin(x))
g2 <- function(x) return(sqrt(x^2 + 1))
g3 <- function(x) return(2 * x - 1)
plot(c(0, 1), c(-1, 1.5))
for (f in c(g1, g2, g3)) plot(f, 0, 1, add=T)
环境和变量作用域的问题
R 语言中函数被正式地称为闭包 (closure)。 函数不仅包含参数和函数体,也包括环境 (environment)。
顶层环境
w <- 12
f <- function(y) {
d <- 8
h <- function() {
return (d * (w + y))
}
return(h())
}
environment(f)
<environment: R_GlobalEnv>
ls()
列举环境中的所有对象
ls()
[1] "f" "f1" "f2" "g" "g1" "g2" "g3" "h" "i" "m"
[11] "n" "odd" "scores" "u" "v" "w" "x" "y" "z"
ls.str()
查看更详细的信息
ls.str()
f : function (y)
f1 : function (a, b)
f2 : function (a, b)
g : function (h, a, b)
g1 : function (x)
g2 : function (x)
g3 : function (x)
h : function (dee, yyy)
i : num 13
m : chr "v"
n : num 13
odd : function (x)
scores : 'data.frame': 395 obs. of 33 variables:
$ school : chr "GP" "GP" "GP" "GP" ...
$ sex : chr "F" "F" "F" "F" ...
$ age : int 18 17 15 15 16 16 16 17 15 15 ...
$ address : chr "U" "U" "U" "U" ...
$ famsize : chr "GT3" "GT3" "LE3" "GT3" ...
$ Pstatus : chr "A" "T" "T" "T" ...
$ Medu : int 4 1 1 4 3 4 2 4 3 3 ...
$ Fedu : int 4 1 1 2 3 3 2 4 2 4 ...
$ Mjob : chr "at_home" "at_home" "at_home" "health" ...
$ Fjob : chr "teacher" "other" "other" "services" ...
$ reason : chr "course" "course" "other" "home" ...
$ guardian : chr "mother" "father" "mother" "mother" ...
$ traveltime: int 2 1 1 1 1 1 1 2 1 1 ...
$ studytime : int 2 2 2 3 2 2 2 2 2 2 ...
$ failures : int 0 0 3 0 0 0 0 0 0 0 ...
$ schoolsup : chr "yes" "no" "yes" "no" ...
$ famsup : chr "no" "yes" "no" "yes" ...
$ paid : chr "no" "no" "yes" "yes" ...
$ activities: chr "no" "no" "no" "yes" ...
$ nursery : chr "yes" "no" "yes" "yes" ...
$ higher : chr "yes" "yes" "yes" "yes" ...
$ internet : chr "no" "yes" "yes" "yes" ...
$ romantic : chr "no" "no" "no" "yes" ...
$ famrel : int 4 5 4 3 4 5 4 4 4 5 ...
$ freetime : int 3 3 3 2 3 4 4 1 2 5 ...
$ goout : int 4 3 2 2 2 2 4 4 2 1 ...
$ Dalc : int 1 1 2 1 1 1 1 1 1 1 ...
$ Walc : int 1 1 3 1 2 2 1 1 1 1 ...
$ health : int 3 3 3 5 5 5 3 1 1 5 ...
$ absences : int 6 4 10 2 4 10 0 6 0 0 ...
$ G1 : int 5 5 7 15 6 15 12 6 16 14 ...
$ G2 : int 6 5 8 14 10 15 12 5 18 15 ...
$ G3 : int 6 6 10 15 10 15 11 6 19 15 ...
u : num [1:3, 1:2] 1 2 3 1 2 4
v : num [1:3, 1:2] 8 12 10 15 10 2
w : num 12
x : logi [1:3] TRUE FALSE TRUE
y : logi [1:3] TRUE TRUE FALSE
z : num [1:3, 1:2] 8 12 10 15 10 2
变量作用域的层次
f
function(y) {
d <- 8
h <- function() {
return (d * (w + y))
}
return(h())
}
f(12)
[1] 192
h()
是 f()
内部的局部对象,在顶层环境中不可见。
下面的语句会报错
h
Error: object 'h' not found
查看环境
f <- function(y) {
d <- 8
h <- function() {
return (d * (w + y))
}
print(environment(h))
return(h())
}
f(2)
<environment: 0x0000022f55c614d8>
[1] 112
下面的代码会报错,h()
定义在顶层环境,无法使用定义在 f()
环境中的局部变量 d
f <- function(y) {
d <- 8
return(h())
}
h <- function() {
return (d * (w + y))
}
f(5)
Error in h() : object 'd' not found
可以将 d
和 y
设置为参数
f <- function(y) {
d <- 8
return(h(d, y))
}
h <- function(dee, yee) {
return (dee * (w + yee))
}
f(2)
[1] 112
ftn
与 h
环境一样
f <- function(y, ftn) {
d <- 8
print(environment(ftn))
return(ftn(d, y))
}
h <- function(dee, yee) {
return (dee * (w + yee))
}
w <- 12
f(3, h)
<environment: R_GlobalEnv>
[1] 120
关于 ls()
的进一步讨论
函数中调用 ls()
返回当前的局部变量。
使用 envir
参数,返回指定框架 (frame) 中的局部变量。
parent.frame()
沿函数调用链向上追溯框架
f <- function(y) {
d <- 8
return (h(d, y))
}
h <- function(dee, yyy) {
print(ls())
print(ls(envir=parent.frame(n=1)))
return(dee * (w + yyy))
}
f(2)
[1] "dee" "yyy"
[1] "d" "y"
[1] 112
函数(几乎)没有副作用
函数不会修改非局部变量。
w <- 12
f <- function(y) {
d <- 8
w <- w + 1
y <- y - 2
print(w)
h <- function() {
return (d * (w + y))
}
return(h())
}
t <- 4
f(t)
[1] 13
[1] 120
顶层变量 w
没有被改变
w
[1] 12
t
4
使用超赋值运算符 (superassignment operator) 可以修改全局变量。
扩展案例:显示调用框的函数
f <- function() {
a <- 1
return (g(a) + a)
}
g <- function() {
b <- 2
aab <- h(aa + b)
return (aab)
}
h <- function(aaa) {
c <- 3
return (aaa + c)
}
showframe()
显示某调用框环境中的变量
showframe <- function(upn) {
if (upn < 0) {
env <- .GlobalEnv
} else {
env <- parent.frame(n=upn+1)
}
vars <- ls(envir=env)
for (vr in vars) {
vrg <- get(vr, envir=env)
if (!is.function(vrg)) {
cat(vr, ":\n", sep="")
print(vrg)
}
}
}
g <- function(aa) {
b <- 2
print("=====")
showframe(0)
print("=====")
showframe(1)
print("=====")
aab <- h(aa + b)
return (aab)
}
f()
[1] "====="
aa:
[1] 1
b:
[1] 2
[1] "====="
a:
[1] 1
[1] "====="
[1] 7
get()
函数根据变量名返回对象,支持 envir
参数
注:字符串 => 对象
m <- rbind(1:3, 20:22)
m
[,1] [,2] [,3]
[1,] 1 2 3
[2,] 20 21 22
get("m")
[,1] [,2] [,3]
[1,] 1 2 3
[2,] 20 21 22
R 语言中没有指针
注:R 语言中有引用类
x <- c(13, 5, 12)
sort(x)
[1] 5 12 13
x <- sort(x)
x
[1] 5 12 13
使用列表返回多个变量
oddsevens <- function(v) {
odds <- which(v %% 2 == 1)
evens <- which(v %% 2 == 0)
list(o=odds, e=evens)
}
向上层次进行写操作
使用超赋值运算符 <<-
或函数 assign()
利用超赋值运算符对非局部变量进行写操作
two <- function(u) {
u <<- 2*u
z <- 2*z
}
x <- 1
z <- 3
two(x)
顶层环境的 x
和 z
保持不变
x
[1] 1
z
[1] 3
已在顶层环境创建新的变量 u
u
[1] 2
<<-
赋值运算符会逐级向上查找,直到顶层环境
f <- function() {
inc <- function() {
x <<- x + 1
}
x <- 3
inc()
return (x)
}
f()
[1] 4
f()
中的 x
在顶层环境中不存在。
用 assign()
函数对非局部变量进行写操作
assign()
函数可以直接指定变量的环境
two <- function(u) {
assign("u", 2 * u, pos=.GlobalEnv)
z <- 2 * z
}
执行前
u
[1] 2
x <- c(2)
x
[1] 2
执行后
two(x)
x
[1] 2
u
[1] 4
什么时候使用全局变量
注:笔者认为还是要尽量避免使用全局变量,全局变量在本应该对外独立的函数内引入外部变量,不利于程序的模块化和组件化。 作为 R 语言的初学者,笔者在后续学习过程中还需要进一步领会。
闭包
R 语言中的闭包包含函数的参数、函数体以及调用时的环境
counter <- function() {
ctr <- 0
f <- function() {
ctr <<- ctr + 1
cat("this count currently has value", ctr, "\n")
}
return (f)
}
执行函数
c1 <- counter()
c2 <- counter()
c1
function() {
ctr <<- ctr + 1
cat("this count currently has value", ctr, "\n")
}
<environment: 0x0000022f5e16d0e0>
c2
function() {
ctr <<- ctr + 1
cat("this count currently has value", ctr, "\n")
}
<bytecode: 0x0000022f5e416638>
<environment: 0x0000022f5e186040>
c1
和 c2
的环境不同,有各自独立的 ctr
变量
c1()
this count currently has value 1
c1()
this count currently has value 2
c2()
this count currently has value 1
c2()
this count currently has value 2
c2()
this count currently has value 3
c1()
this count currently has value 3
递归
递归是一种强大的编程工具。
Quicksort 的具体实现
qs <- function(x) {
if (length(x) <= 1) return (x)
pivot <- x[1]
therest <- x[-1]
sv1 <- therest[therest < pivot]
sv2 <- therest[therest >= pivot]
sv1 <- qs(sv1)
sv2 <- qs(sv2)
return (c(sv1, pivot, sv2))
}
qs(c(5, 4, 12, 13, 3, 8, 88))
[1] 3 4 5 8 12 13 88
拓展举例:二叉查找树
R 语言中没有指针,可以使用数组保存二叉树,使用数组索引模拟指针。
注:省略具体代码。作为初学者,笔者暂时不关心算法方面的代码。
置换函数
names()
函数是一个置换函数
x <- c(1, 2, 4)
names(x)
NULL
names(x) <- c("a", "b", "c")
names(x)
[1] "a" "b" "c"
x
a b c
1 2 4
什么是置换函数
任何左边不是标识符的赋值语句都可以看做置换函数
g(u) <- u
R 语言会尝试执行
u <- "g<-"(u, value=v)
"["()
函数用于读向量元素,"[<-"()
函数用于写操作
x <- c(8, 88, 5, 12, 13)
x
[1] 8 88 5 12 13
x[3]
[1] 5
"["(x, 3)
[1] 5
x <- "[<-"(x, 2:3, value=99:100)
x
[1] 8 99 100 12 13
扩展案例:可记录元素修改次数的向量类
创建 bookvec
类的对象
newbookvec <- function(x) {
tmp <- list()
tmp$vec <- x
tmp$wrts <- rep(0, length(x))
class(tmp) <- "bookvec"
re
读数据
"[.bookvec" <- function(bv, subs) {
return (bv$vec[subs])
}
写数据
"[<-.bookvec" <- function(bv, subs, value) {
bv$wrts[subs] <- bv$wrts[subs] + 1
bv$vec[subs] <- value
return(bv)
}
测试
b <- newbookvec(c(3, 4, 5, 5, 12, 13))
b
$vec
[1] 3 4 5 5 12 13
$wrts
[1] 0 0 0 0 0 0
attr(,"class")
[1] "bookvec"
b[2]
[1] 4
b[2] <- 88
b[2]
[1] 88
b$wrts
[1] 0 1 0 0 0 0
创建二元运算符
以 %
开头和结束的函数
"%a2b%" <- function(a, b) return (a + 2*b)
3 %a2b% 5
[1] 13
匿名函数
z <- matrix(1:6, nrow=3)
z
[,1] [,2]
[1,] 1 4
[2,] 2 5
[3,] 3 6
y <- apply(
z,
1,
function(x) x/c(2, 8)
)
y
[,1] [,2] [,3]
[1,] 0.5 1.000 1.50
[2,] 0.5 0.625 0.75
参考
学习 R 语言系列文章:
《快速入门》
《向量》
《矩阵和数组》
《列表》
《数据框》
《因子和表》
本文代码请访问如下项目: