学习R语言:编程结构

目录

本文内容来自《R 语言编程艺术》(The Art of R Programming),有部分修改

注:从本文开始,笔者使用 RStudio 代替 Jupyter Lab 执行代码

控制语句

循环

forwhilerepeat

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"

TRUEFALSE 可以简写为 TF,在算术表达式中会被转换为 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

可以将 dy 设置为参数

f <- function(y) {
    d <- 8
    return(h(d, y))
}

h <- function(dee, yee) {
    return (dee * (w + yee))
}

f(2)
[1] 112

ftnh 环境一样

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)

顶层环境的 xz 保持不变

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>

c1c2 的环境不同,有各自独立的 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 语言系列文章

快速入门

向量

矩阵和数组

列表

数据框

因子和表

本文代码请访问如下项目:

https://github.com/perillaroc/the-art-of-r-programming