本文是笔者近期使用R语言的一个记录。
计算各列的NA数量
这个可以用apply或者sapply快速实现
代码语言:javascript复制> df <- data.frame(col1=c(1, NA, 2), col2=c(2, 3, NA), col3=c(NA, NA, 3))
> df
代码语言:javascript复制 col1 col2 col3
1 1 2 NA
2 NA 3 NA
3 2 NA 3
代码语言:javascript复制> sapply(df, function(v) sum(is.na(v)))
代码语言:javascript复制col1 col2 col3
1 1 2
或者:
代码语言:javascript复制> apply(df, 2, function(v) sum(is.na(v)))
代码语言:javascript复制col1 col2 col3
1 1 2
关于apply的说明
apply对一行或者一列是按照向量来处理的
假设要对每一行求和
代码语言:javascript复制> df <- data.frame(a=c(1, 2), b=c(3, 4), d=c("5", "6"), stringsAsFactors=F)
> str(df)
代码语言:javascript复制'data.frame': 2 obs. of 3 variables:
$ a: num 1 2
$ b: num 3 4
$ d: chr "5" "6"
代码语言:javascript复制> apply(df, 1, function(v) sum(as.numeric(v)))
代码语言:javascript复制[1] 9 12
这个功能很简单也很常用,但是不加注意还是容易写错,比如只对每一行的前两个元素求和:
代码语言:javascript复制> apply(df, 1, function(v) sum(v[1:2]))
代码语言:javascript复制Error in sum(v[1:2]): 'type'(character)参数不对
会报错,提示参数类型不对。 按道理前两列都是数值型,那么apply后每一行的前两个元素也应该是数值型呀,那是不是呢,我们看看:
代码语言:javascript复制> apply(df, 1, function(v) mode(v[1:2]))
代码语言:javascript复制[1] "character" "character"
不是预想中的数值型,而是字符型。其实apply是将每一行当作一个向量来处理的。因为第三列是字符型,所以当一行中只要有一个值是字符型,其他数值型的值都会被自动转换为字符型。
上面说了那么多,关键就是apply是把一行或者一列当作向量来处理的;R中的向量要求值类型一致。 我看到不少人,包括我自己,都曾经因为不知道这一点而吃亏。
apply中可以利用行名或者列名来增强可读性
什么意思呢?比如在下面这个数据集中我们想计算每个人语文成绩和英语成绩的和。可以
代码语言:javascript复制> df <- data.frame(a=1:3, b=6:8)
> row.names(df) <- c("chi", "math", "eng")
> df
代码语言:javascript复制 a b
chi 1 6
math 2 7
eng 3 8
代码语言:javascript复制> apply(df, 2, function(v) v[1] v[3])
代码语言:javascript复制 a b
4 14
但其实如果用行名的话,可以更一目了然,并且不会受行顺序的影响。
代码语言:javascript复制> apply(df, 2, function(v) v["chi"] v["eng"])
代码语言:javascript复制 a b
4 14
列名也是类似的用法。
当然,上面的两个例子中代码都不是最佳的,只是为了说明问题姑且那样写。
移动文件
R语言对目录和文件的操作有一些基本的支持。网上又很多的教程,但是当我想找一个函数把一个文件从一个目录移动到另一个目录的时候,却都没找到。 后来自己回想了一下Linux中目录的本质,移动文件一般就是改变其“完整的路径名”。于是上述功能其实可以用file.rename函数来实现。 比如,要把1.txt从dir1中移到dir2中,可以这样写:
代码语言:javascript复制> file.rename("dir1/1.txt", "dir2/1.txt")
代码语言:javascript复制[1] FALSE
或者更简单的写法:
代码语言:javascript复制> file.rename("dir1/1.txt", "dir2")
代码语言:javascript复制[1] FALSE
结果是FALSE表示没有移动成功,那是肯定的,因为目录都是我假想的。如果你用真实的目录和文件来操作,是会成功的。
dplyr包
最近用dplyr包的次数比较多,虽然还不是很熟练,但已经感到用它的好处了。除了代码变简洁之外,最大的好处就是灵活。 我们经常要对一个数据集做多步处理,如果用基础包里的功能也能实现,但是一旦需要调整处理的先后顺序,那就很麻烦,通常需要进行很大的改动。但是如果用dplyr包就可以轻松很多。
按行合并list中的向量
用dplyr包中的bind_rows函数实现
代码语言:javascript复制> lis <- list(
a=1:5,
b=2:6,
d=3:7
)
> library(dplyr)
> t(bind_rows(lis))
代码语言:javascript复制 [,1] [,2] [,3] [,4] [,5]
a 1 2 3 4 5
b 2 3 4 5 6
d 3 4 5 6 7
用Reduce函数实现
代码语言:javascript复制> Reduce(rbind, lis, init=NULL)
代码语言:javascript复制 [,1] [,2] [,3] [,4] [,5]
[1,] 1 2 3 4 5
[2,] 2 3 4 5 6
[3,] 3 4 5 6 7
如何从字符串中提取特定值
比如,如何从“d 0 a 1 b 2 c 3 a 6 d 9”中只提取紧接在a后面的数字。以前都是这样做:
代码语言:javascript复制> library(stringr)
> s = "d 0 a 1 b 2 c 3 a 6 d 9"
> s
代码语言:javascript复制[1] "d 0 a 1 b 2 c 3 a 6 d 9"
代码语言:javascript复制> tmp <- str_extract_all(s, "a \d ")
> tmp
代码语言:javascript复制[[1]]
[1] "a 1" "a 6"
代码语言:javascript复制> lapply(tmp, function(v) str_extract_all(v, "\d "))
代码语言:javascript复制[[1]]
[[1]][[1]]
[1] "1"
[[1]][[2]]
[1] "6"
然后再将结果转为向量。 很长一段时间内都是这样做的,但是我一直记得Python中可以很方便的用()直接提取想要的部分。 所以,最近又仔细看了一下正则表达式的规则,还真发现了一个新方法:
代码语言:javascript复制> str_extract_all(s, "(?<=a )\d ")
代码语言:javascript复制[[1]]
[1] "1" "6"
其中,(?<=pattern) 表示前面匹配,比如 (?<=a )d 表示前面匹配了a和空格的数字。
最后
近期使用R语言的一些收获罗列于此,希望能对大家有所助益。