除了 R 包自带的函数,也可以自己编写函数来执行一系列操作。作为翻译型语言,R 可以使用命令行逐行运行,也可以使用函数整体运行。此外,还可以编写脚本,构造 R 程序以运行。
print()
函数可以将对象的值输出到控制台窗口中
创建函数
创建函数的基本步骤
为函数起一个名称,作为函数名
列出函数的参数列表
将需要执行的代码放入函数体中
其基本结构为:
1 | func <- function( arglist ) |
func
为函数名,arglist
为参数(列表),多个参数之间用逗号隔开。expr
为函数主体,即需要执行的代码。value
为函数的返回值,当省略了 return
语句时,函数会把最后一行的结果作为返回值。function()
函数为函数构造函数。
可以使用单元测试对函数进行正式的自动化测试过程
函数及参数名称
函数名理论上只需要满足 R 的命名要求即可使用,但出于可维护性等人工交互方向的考量,推荐尽可能采用以下命名方式:
- 名称在能够表达作用的前提下尽可能简短
- 除非函数名简短或作用极其明显(常见),否则尽可能将函数名起为动词,将参数名起为名词
- 多单词的函数名尽可能使用 “snake-case” 命名法
- 为利用部分 IDE 的自动补全机制,同系列函数最好拥有同样的函数名前缀
决策
在完成一个复杂的任务时,最常见的做法是将其切分为几个相对简单的步骤,再将这些步骤切分为更简单的步骤,以此类推。而通常可以将 R 程序分解为两种子任务:有序步骤和同类情况。
有序步骤
分解程序的方法之一就是将其细分为一些有序的步骤。这也是程序的主要运行状况,也无需做特殊处理,因为 R 程序默认是以从头到尾的方式运行的。
同类情况
虽然程序的运行是由开头一直到结尾,但过程中可能并非直接到达终点,而是经历了众多判断。这种需要判断的状况也被称为同类情况。
完成这种判断的基本语句为 if
语句:
1 | if(condition) |
通过 condition
的逻辑值是 TRUE
还是 FALSE
来决定 if
语句中的 sentence
是否运行。condition
必须为单个逻辑值或使用运算符 any
或 all
压缩过的逻辑向量。sentence
不一定只有一行。
if 语句本身不会产生新环境,因此在语句内外的同名对象可能是同一个对象
也可以在 if
语句后附加 else
语句来达成“二选一”的判断:
1 | if(condition) |
甚至可以使用 else if
语句达成“多选一”的判断:
1 | if(condition1) |
if 语句和 else 语句中的 else if 语句使用数量理论上不受限制
R 中的逻辑运算除了之前常规的逻辑运算符外,还有短路运算符。具体地,有:
逻辑运算 | 说明 | 短路运算 | 说明 |
---|---|---|---|
& | 在 a&b 中,若 a 是假值,也会对 b 进行判断,即使整个表达式的值已经为假 | && | 在 a&&b 中,若 a 是假值,则不会对 b 进行判断或运算 |
| | 在 a|b 中,若 a 是真值,也会对 b 进行判断,即使整个表达式的值已经为真 | || | 在 a||b 中,若 a 是真值,则不会对 b 进行判断或运算 |
但是,短路运算要求两侧的对象不能是向量,只能是单个值或值为单个的表达式
当条件内含有浮点等含有小数的类型需要特别注意精度损失带来的相关问题,可以使用 dylyr
查找表
除了使用冗长的逻辑运算,R 还有其它的方式来达成同样的目的,最常见的就是取子集。
可以使用以下方式来创建一个映射表:
1 | > mappingtable <- c(a=1,b=2,c=3) |
定义时向量中的名称可以使用双引号,但索引时必须使用双引号
unname
函数可以返回一个对象的副本,并将其名称属性删除。
1 | > unname(mappingtable["c"]) |
这种创建的映射表在 R 中被称作查找表。
循环
排列
一般地,从 n 个不同元素中取出 m( m≤n )个元素,按照一定的顺序排成一列,叫做从 n 个元素中取出 m 个元素的一个排列。特别地,当 m = n 时,这个排列被称作全排列。
可以使用 expand.grid
函数将给定对象的排列:
1 | > expand.grid(1:4,c("A","B","C"),c(T,F)) |
for 循环
for 循环的结构如下:
1 | for (var in seq) |
seq
是一个对象集合(通常是包含数值或字符串的向量)。var
作为参数,会在每次循环中遍历 seq
中元素的值,并以该值运行 expr
中的命令。
for 循环在 R 中的主要作用除了执行代码,还有填充向量和列表
while 循环
while 循环的结构如下:
1 | while (cond) |
cond
是一个逻辑测试。当判断为真时,会运行 expr
的代码块,然后返回开头继续判断 cond
;当判断为假时,会跳过 expr
运行之后的代码。
repeat 循环
repeat
循环可以简单地理解为不具有判断条件的 while
循环,主要结构如下:
1 | repeat |
repeat 循环的中断方式主要是 break 命令,或者是 Esc 键
函数参数
函数的参数通常分为两大类:
- 需要进行计算的数据
- 控制计算过程的细节
通常情况下,数据参数应该放在最前面,细节参数放在后面(最好同时给出默认值)。参数默认值首先要考虑数据安全性(例如大部分函数的 na.rm
的默认值为 FALSE
,虽然设置为 TRUE
是经常使用的),其次便是常用数值。
因此设计函数时最好的方式就是使函数能做到:
- 能够省略数据参数的名称
- 能够明显表达细节参数的名称
- 最主要的数据参数设置为第一个参数
- 各个参数的名称尽可能地在前几个字母就能分辨开
参数名称
常见的参数名称有:
x
,y
,z
:向量w
:权重向量df
:数据框i
,j
:数据索引n
:长度或行的数量p
:列的数量
参数值
可以使用 if
语句来判断给入的参数值是否合法,并结合 stop()
函数来弹出捕获非法参数后的警告信息。也可以使用 stopifnot()
函数来生成通用错误信息。
不定参数
当希望函数能够接受多个不确定数量的参数时,可以使用一个特殊参数:...
。这个参数会捕获任意数量的未匹配参数。可以使用 list(...)
来检查 ...
中的值。
对于使用了 … 作为参数名的函数,任何与该函数除 … 以外的其他参数名不同的参数都会被认为是合法参数,不会弹出错误信息
惰性求值
R 中的参数求值的方式是惰性的,即直到需要该参数时才会对其进行计算。
返回值
返回值相当于函数被调用后函数的值。在通常情况下函数的返回值是函数体内最后一个语句的值,也可以使用 return
语句来返回特定的值。由于“第一参数”在 R 中的重要性,因此函数 return
语句与管道操作的契合度极高。这种情况下,函数会将第一参数(更常见的情况是第一参数的副本经操作后的值)作为返回值,这样的函数大致有两类:
- 转换函数:对第一参数进行处理并返回
- 副作用函数:不对第一参数进行处理,而是用其数据作打印、绘图、保存等为转换操作。但同样返回第一参数