Yulong Niu

个人博客

R小技巧集锦

Posted at — May 2, 2012

持续更新R语言编程中的一些小技巧,包括一些易错点。但是,需要注意的,一旦需要使用一些非常稀奇古怪的技巧时,可以尝试反思是否是自己数据结构或者算法出了问题。

1. if爱你,else就要和你在一起,又要给你一些距离

if (con){
  expr
  } else{
  expr2
  }

if...else..结构中,}else必须在同一行。

简易形式(不推荐):

# else不能与expr在同一行
if (con) expr
else expr2

建议将所有if ... else ...结构写成第一种形式,如果没有else,用else {}代替。

2. NULL是空,NULL是气,NULL在R中就是空气。

如果表达式(expression)或者函数(function)的值没有定义,那么将返回NULL。在R中,NULL与很多对象合并在一起时,都被忽略掉。比如以下例子:

# NULL is neglected in vector
> c(1, NA, NULL, FALSE)
[1]  1 NA  0

# also in matrix
> matrix(c(1, NA, NULL, 2), c(2, 2))
     [,1] [,2]
[1,]    1    2
[2,]   NA    1
警告信息:
In matrix(c(1, NA, NULL, 2), c(2, 2)) : 数据长度[3]不是矩阵行数[2]的整倍

# also in list
> tmp <- list(1, 2)
> tmp[1] <- NULL
> tmp
[[1]]
[1] 2
> tmp[[1]] <- NULL
> tmp
list()

如果一定要将列表中的某个个元素赋值为NULL,可以使用list(NULL),比如:

> tmp <- list()
> tmp[1] <- list(NULL)
> tmp
[[1]]
NULL
> is.null(tmp[[1]])
[1] TRUE

3. list不是万能的,但没有list是万万不能的

列表(list)在R中是个全能选手、无所不“包”;list还是金箍棒,别看它丫的开始长度(length())短,之后想变长就变长,多长都行。

这暗示list可以这样用:在循环开始时,我们不知道会产生多少个元素,可以事先规定输出结果为a <- list(),之后赋值;甚至可以按照名字赋值,比如a$add <- exprs

如果要创建一个长度为n的、每个元素都为NULL的list,可以使用vector("list", n)

如果要在list中插入元素,可以使用append(x, values, after = length(x))函数。append()函数原本是对vector设计的,但是list也能使用。

4. 创建“空”值很淡定,要么空来要么0

如果要创建一个空的对象(对象的长度是0)很容易,比如:

numeric()或者numeric(0)

character()或者character(0)

integer()或者integer(0)可以起到同样的效果

factor()创建一个长度为0的因子

注意:factor(0)的长度是1

matrix(0, 0, 0)

注意:array()创建的对象长度为1。

list()

data.frame()

5. 对象转换要小心,data.frame是陷阱

R中一些对象转换要格外小心,特别是牵扯到data.frame类型。以下是几个常见的陷阱:

5.1 “字符型因子”转换为“数值型向量”

需要先转成字符串向量过渡一下:

as.numeric(as.character(MyFactor))

5.2 字符和数值因子混搭data.frame

每一列是按照字符型因子保存。留意表面上是数值的一列,它非常危险,特别是排序。一个例子:

> a1 <- as.factor(as.character(a1))
> a1
 [1] 5  6  7  8  9  10 11 12 13 14 15
Levels: 10 11 12 13 14 15 5 6 7 8 9
> sort(a1)
 [1] 10 11 12 13 14 15 5  6  7  8  9
Levels: 10 11 12 13 14 15 5 6 7 8 9

6. 多个else/if别担心,switch()函数来帮助

如果需要在不同情况下,对变量进行不同的处理,自然会联想到使用多个多个else/if。如果else/if是用来处理的简单的赋值,可以使用switch(EXPR, ...)函数。其中,EXPR是一个表达式,生成一个数字或者字符串;接下来,使用生成的数字和字符串匹配...中的内容。

所以,我们很容易想到,如果EXPR生成的数字,则对应匹配...的第几个元素,此时...中的变量可以没有名字;如果是字符串,就会严格地匹配...中相同名字的变量;如果EXPR生成是字符串,...中没有相匹配的变量名,则返回NULL;如果...中有一个“无名”变量(即没有对变量赋值),上一种情况下返回该无名变量,相当于最后一个else。比如:

> switch(1, a = 3, b = 2)
[1] 3
> switch(2, aa = 'red', bb = 'blue', cc = 'yellow', 'No value')
[1] "blue"
> switch('aa', aa = 'red', bb = 'blue', cc = 'yellow', 'No value')
[1] "red"
> switch('dd', aa = 'red', bb = 'blue', cc = 'yellow', 'No value')
[1] "No value"
> switch('bb', aa = 'red', bb = , cc = 'yellow')
[1] "yellow"
> switch(2, aa = 'red', bb = , cc = 'yellow')
Error: empty alternative in numeric switch
> switch('dd', aa = 'red', bb = , cc = 'yellow', 'no', 'value')
Error: duplicate switch defaults: '"no"' and '"value"'

注意:

总结swich()函数是一个好玩却又危险的函数,一个没有歧义的使用方法是:EXPR明确写出,且返回字符串,...中每一个变量都有确切的值,无名变量只有一个,比如:switch(EXPR='dd', aa = 'red', bb = 'blue', cc = 'yellow', 'no value')

7. R取值有技巧,drop选项巧帮助

当对象是一个矩阵的时候,通常我们要获取其中一些元素。如果取出的正好是一行或者一列,此时,R会返回给我们一个向量,而不是矩阵。这会带来很大的麻烦,因为我们必须判断返回对象是向量,还是矩阵,才能继续下面的操作。此时,drop可以帮助我们,如果设置drop = FALSE,就会保持原样返回结果。

> b <- matrix(1:6, nrow = 2, ncol = 3)
> b
     [,1] [,2] [,3]
[1,]    1    3    5
[2,]    2    4    6
> b[, 3]
[1] 5 6
> b[, 3, drop = FALSE]
     [,1]
[1,]    5
[2,]    6
> b[1, , drop = FALSE]
     [,1] [,2] [,3]
[1,]    1    3    5

8. ||| 有区别,返回向量与单值

R中的逻辑运算符号有两组,|或者||&或者&&),看起来觉得有些多余。其实,这是为了R独特的“向量运算”考虑的。|或者&首先考虑返回一个向量,而||&&则只返回第一个比较结果。比如:

> c(FALSE, TRUE) | c(FALSE, FALSE)
[1] FALSE  TRUE
> c(FALSE, TRUE) || c(FALSE, FALSE)
[1] FALSE

9. 递归

R在解释代码时,不支持尾递归优化,所以在R中不要写递归。

10. R图例空元素,0NA来代替

在R绘图的图例中,通常需要“线条”和“点”分开单独表示,这里用到0NA的小技巧。如果在legend()函数函数中,pch = NA表示没有点,lty=0表示没有线条。

11. 字符串转代码很容易,eval()/parse()很随意

如果我们有一个字符字符串,现在需要将其转为可执行代码。方法如下:

> test1 = '1:3'
> test1
[1] "1:3"
> eval(parse(text = test1))
[1] 1 2 3

12. factor如需换标签,c()函数有妙招

如果我们需要对一个factor的标签(level)进行转换,并且返回一个向量,有两种方法可以使用:

> tmp1 <- factor(rep(1:3, each = 3))
> tmp1
[1] 1 1 1 2 2 2 3 3 3
Levels: 1 2 3
> c(letters[1:3])[tmp1]
[1] "a" "a" "a" "b" "b" "b" "c" "c" "c"
> tmp2 <- factor(tmp1, labels = letters[1:3])
> tmp2
[1] a a a b b b c c c
Levels: a b c
> as.character(tmp2)
[1] "a" "a" "a" "b" "b" "b" "c" "c" "c"

同时,配合使用droplevels()(去除多余level)、relevel()(以某个level为基准)和ordered(.., levels)(调整level顺序)等操作factor的level。

13. 操作符也是函数,使用起来有妙招

> "+"(1, 2)
[1] 3
> '>'(1, 2)
[1] FALSE
> tmp1 = list()
> for(i in 1:10)
  tmp1[[i]] = i:(i+1)
> tmp1
[[1]]
[1] 1 2

[[2]]
[1] 2 3

[[3]]
[1] 3 4

[[4]]
[1] 4 5

[[5]]
[1] 5 6

[[6]]
[1] 6 7

[[7]]
[1] 7 8

[[8]]
[1] 8 9

[[9]]
[1]  9 10

[[10]]
[1] 10 11
> sapply(tmp1, '[[', 2)
 [1]  2  3  4  5  6  7  8  9 10 11

最后一个例子中,使用了[[,巧妙地取得了第二个元素。

14. 输出table有引号,quote参数来帮忙

在使用write.table()命令输入一个data.frame对象时候,输入文件会加引号。这时,设定quote参数,write.table(..., quote = FALSE)

15. 比较两个元素identical()

16. 压缩list用do.call()

do.call(rbind, list)

17. 分割数据使用split()

> test1 <- rep(c('a', 'b'), c(3, 4))
> test1
[1] "a" "a" "a" "b" "b" "b" "b"
> split(test1, factor(test1))
$a
[1] "a" "a" "a"

$b
[1] "b" "b" "b" "b"

18. 输出txt文档找sink()

sink()可以将R的对象输出到外部文档中,第一遍可能包括了系统信息,需要输出第二遍。一个典型的流程如下:

> sink(filename)
> test1
> sink()

19. R中循环for妙用

R中for循环中,需要指定一个计数器,比如i in 1:10之类。R可以直接将一个向量用于计数,返回向量中对应的值,比如:

> tmp1 <- letters[1:10]
for (i in tmp1) {
  print(i)
}
> [1] "a"
[1] "b"
[1] "c"
[1] "d"
[1] "e"
[1] "f"
[1] "g"
[1] "h"
[1] "i"
[1] "j"

20. 多个序列取交集并集用Reduce()

> Reduce(union, list(letters[1:3], letters[1:4], letters[1:5]))
[1] "a" "b" "c" "d" "e"

21. 载入github储存的R代码使用source_url

需要安装和调用devtools

> library(devtools)
> source_url('https://raw.githubusercontent.com/YulongNiu/FunFunc/master/test_two_proportion.R')

参考网址

更新记录

2022年09月06日