史前人类怎么使用R
群里大家给出各种解法,但似乎避开了dplyr之后,大家还是会用到plyr或者purrr,没人给出我讲的apply套split的纯base R的解法。
套两重for循环,会让人容易理解一点,而且代码也非常简单,但效率不行,用apply的话,处理各种list,特别如果要list套list,很多人就比较懵逼,需要借助于plyr或purrr,甚至还有人的解法虽然不用dplyr的group_by套summarise,但用了dplyr其它的函数。tidyverse真的太好用,对低端R用户照顾得太好,大家都被惯坏了,但想一下,以前的人真的就不用活了吗?
用我说的split + apply,完全不需要额外的library,其实也蛮简单的,下面是我写的代码,仅供参考:
PS: 尽管这里用到了管道,但管道可有可无,无非是把从里到外变成从左到右,让代码易读性高一点而已。
res <- read.delim("Temperature.txt")[,c("Year", "Month", "Temperature")] %>%
split(.$Year) %>%
lapply(function(y) split(y, y$Month)) %>%
lapply(function(m) lapply(m, function(x)
mean(x$Temperature, na.rm=TRUE))) %>%
lapply(function(x)
data.frame(Month=names(x),
Temperature=unlist(x))) %>%
do.call("rbind", .) %>%
cbind(Year=sub("\\.\\d{1,2}", "",
rownames(.)), .,
row.names=NULL)
结果如下:
> head(res)
Year Month Temperature
1 1990 1 6.313889
2 1990 2 6.475000
3 1990 3 8.192857
4 1990 4 9.153333
5 1990 5 14.357576
6 1990 6 16.277273
> tail(res)
Year Month Temperature
187 2005 7 18.799111
188 2005 8 17.692105
189 2005 9 17.883455
190 2005 10 14.498125
191 2005 11 11.562759
192 2005 12 7.111481
>
拆解代码
这个代码看着挺复杂,其实很简单,我们来拆解一下:
读数据:
x <- read.delim("Temperature.txt")[,c("Year", "Month", "Temperature")]
切分数据,并计算
按照两个因子分层切分,对第二层数据进行求均值。
x %>%
split(.$Year) %>%
lapply(function(y) split(y, y$Month)) %>%
lapply(function(m) lapply(m, function(x)
mean(x$Temperature, na.rm=TRUE)))
对于第二步,如果我们套用tapply的话,就可以少一行,因为tapply相当于是sapply + split,所以对于Month可以不用切,交给tapply。
x %>%
split(.$Year) %>%
lapply(function(x)
tapply(x$Temperature, x$Month, mean, na.rm=TRUE))
结果转为data.frame
这里写成函数,因为后面会重用到
ls2df <- function(ls) {
lapply(ls, function(x)
data.frame(Month=names(x),
Temperature=unlist(x))) %>%
do.call("rbind", .) %>%
cbind(Year=sub("\\.\\d{1,2}", "",
rownames(.)), .,
row.names=NULL)
}
来个简单版本
其实啊,split可以一次切两个因子,所以用split一切,套个sapply即可。
temp <- sapply(split(x$Temperature, x[,c("Year", "Month")]), mean, na.rm=TRUE)
res <- data.frame(Year = sub("(\\d{4}).*", "\\1", names(temp)),
Month = sub("\\d{4}.", "", names(temp)),
Temperature = temp,
row.names = NULL)
一行代码版本
前面我说过tapply相当于sapply + split,所以用tapply可以一行代码解决:
tapply(x$Temperature, list(x$Year, x$Month), mean, na.rm=TRUE)
不过这个输出是以Year为row, Month为column的matrix,如果要转成像上面的tidy data frame的话,还需要额外的操作。
用aggregate也可以实现一行代码,不过没有tapply快,当然输出不完全一致的情况下,比较速度也有点bias。
b <- microbenchmark(
tapply(x$Temperature, list(x$Year, x$Month), mean, na.rm=TRUE),
aggregate(x, by=list(x$Year, x$Month), mean, na.rm=T)
)
tapply胜出:
> b
Unit: milliseconds
expr min
tapply(x$Temperature, list(x$Year, x$Month), mean, na.rm = TRUE) 1.2762
aggregate(x, by = list(x$Year, x$Month), mean, na.rm = T) 7.1846
lq mean median uq max neval
1.33325 1.499571 1.41000 1.4750 5.6744 100
7.37845 8.205050 8.07395 8.3865 15.9764 100
强行总结
尽管split、aggregate和tapply都可以支持多重因子,我还是喜欢各种split+apply套一堆的版本,因为做为练习,这才是好的学习代码,而且我写的这个,已经非常模块化了,三个步骤,非常明确,再者多玩一下list,熟悉一下不规则的数据,才好应对现实世界中的数据,总是data.frame进,data.frame出是不太现实的。尽管tapply一行可以解决,但内部的逻辑无非也是与我们的代码类似。
番外篇:假如我要用R包呢?
plyr
library(plyr)
ddply(x, .(Year,Month), summarize,
Temperature=mean(Temperature, na.rm=T))
reshape2
library(reshape2)
dcast(x, Year+Month~..., fun=function(x) mean(x, na.rm=T),
value.var = c("Temperature"))
purrr
用purrr就跟用lapply似的,所以并不显示有什么优势,当然如果你直接按照Year + Month去split的话,用lapply跟用map一样,非常简单出结果,也就是上面的来个简单版本所写的版本。并没有什么新玩意。
如果要用purrr的话,我想还是用我原先的代码,先产生list of list,再对第二层求均值,最后用ls2df函数(上面拆解代码的第三步)整理为data.frame,这样子的话,在第二步求均值的时候,用purrr就显得有优势了,因为要对两层list操作的时候,不用套两个lapply,可以用map_depth,这样逻辑还是一样,但代码简洁了,可读性,易于理解等都有所改善。
library(purrr)
lapply(split(x, x$Year), function(.) split(., .$Month)) %>%
map_depth(2, function(.) mean(.$Temperature, na.rm=T)) %>%
ls2df
sqldf
让你用SQL语法来summarize数据。
library(sqldf)
sqldf("SELECT Year, Month, avg(Temperature)
FROM x
GROUP BY Year, Month")
doBy
顾名思义,干的就是这档事。
library(doBy)
summaryBy(Temperature ~ Year + Month, data = x, FUN=mean, na.rm=T)
data.table
library(data.table)
data.table(x)[, list(mean=mean(Temperature, na.rm=TRUE)),
by=c("Year", "Month")]