Categories
日常应用

探索R包plyr:脱离R中显式循环

所有R用户接受的第一个“莫名其妙”的原则就是:

不要在R中写显式循环...

不要写显式循环...

不要写循环...

不循环...

不...

我第一次接受到这个“黄金律”,就跟当年从basic语言转到C语言的时候,老师说:

不要写go to...

不go to...

不...

一样的,好震撼。往往对于R用户来说,R基本上不可能是他们学习的第一门计算机语言,什么C啊Java啊甚至matlab或者VBA都可能排在R前面。所以,循环,无论是for还是while,好像都是再家常便饭不过的事情了。换句话说,不准写循环,我要你计算机还辛辛苦苦的码代码干啥?你丫不就是一免费精确的重复劳动力么!

带着一种到处循环的思维,接触R的初期我是各种不适应不适应啊。循环不让写???后来习惯了去搜R的各种稀奇古怪的函数,发现基本上我想用的功能都被其他大牛们实现了,只需要知道怎么调用那些函数和参数就可以了。这个,挺好的嘛,适合我这种懒人。可是,总有一些时刻需要写循环的嘛...呜啊。

后来,lijian哥给我不断的潜移默化各种展示sapply等apply类函数的强大,越来越体会到一种思维习惯的变化——不再是循环,而是向量操作。这就好比以前只知道求和公式的孩子一朝学习了矩阵乘法,各种惊讶膜拜。其实,从这个角度来讲,R里面很多东西都是更希望借助向量来做而不是自己一个一个的写循环。嗯啊,果然思维方式是有很大提升的。

在痛苦的跟apply类函数纠结了一阵子之后,惊讶的在stackoverflow.com上看到许多人用一个莫名其妙的ddply函数或者ldply函数来实现类似sapply的功能,一时之间难免好奇。于是按图索骥,找到了神奇的plyr包。于是,开启了一扇门(顿时想到叶诗文拿到第二枚金牌的时候,两位央视解说员激情四射的即时附和)。相映成趣啊。plyr的解释只有一句:The split-apply-combine strategy for R。嗯,超级符合其作者一贯的风格...

简单来说,这个包就是用来简化apply类函数的使用的。作者给出了一个稳健回归的例子(原文载于JSS):

已有函数:

deseasf <- function(value) rlm(value ~ month - 1)

循环版:

models <- as.list(rep(NA, 24 * 24))
dim(models) <- c(24, 24)
deseas <- array(NA, c(24, 24, 72))
dimnames(deseas) <- dimnames(ozone)
for (i in seq_len(24)) {
for(j in seq_len(24)) {
mod <- deseasf(ozone[i, j, ])
models[[i, j]] <- mod
deseas[i, j, ] <- resid(mod)
}
}


非循环版:

models <- apply(ozone, 1:2, deseasf)
resids_list <- lapply(models, resid)
resids <- unlist(resids_list)
dim(resids) <- c(72, 24, 24)
deseas <- aperm(resids, c(2, 3, 1))
dimnames(deseas) <- dimnames(ozone)

plyr版

models <- dlply(ozonedf, .(lat, long), deseasf_df)
deseas <- ldply(models, resid)

嗯,代码长度上可以看出来显著差别了吧,嘻嘻。基本上,plyr就是一步步的从split()到lapply()最后rbind()结果嗯。我个人是怎么用的呢?小小剧透一下,最近在处理一堆XML数据,虽然自认对HTML很熟,但是对XML还是各种两眼一抹黑。为了把XML转为方便的data.frame格式,网上一通乱搜最终找到了简洁的解决方案:

## xml_names中含有一系列的XML文件地址,为字符串向量。
xml_df <- ldply(xml_names,
function(x) {
as.data.frame(t(xmlToList(x)$weibo_fans))
}
)

调用XML包的xmlToList()函数之后,就可以用ldply方便的开始揉数据了。嘻嘻,然后加一个 print()函数,就可以舒舒服服的见证屏幕上几千个XML文件被慢慢刷成自己想要的格式的过程了。爽死了。

从数据输入上来看,支持三大类——array,list和dataframe。我个人最偏爱dataframe,虽然list有时候更方便灵活。另外还有几个方便的函数可以用,比如:

  • each():each(min, max)等价于function(x) c(min = min(x), max = max(x))。
  • colwise():colwise(median)将计算列的中位数。
  • arrange():超级顺手的函数,可以方便的给dataframe排序。
  • rename():又是一个handy的函数,按变量名而不是变量位置重命名。
  • count():返回unique值,等价于length(unique(**))。
  • match_df():方便的配合count()等,选出符合条件的行,有点像merge(...,all=F)的感觉。
  • join():对于习惯SQL的童鞋,可能比merge()用起来更顺手吧(当然也更快一点),不过灵活性还是比不上merge()嗯。

好吧,看出这位作者Hadley的风格了吧,基本上能save your life的函数都给预备好了。现在我的办公桌上常年挂着stringr的简短说明,然后习惯ggplot2画图,reshape2揉数据...这算不算Hadley依赖症捏?

------Happy Hour (欢乐时光模式开启)------