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 (欢乐时光模式开启)------

Categories
读书有感

夜半耕读「The Ph.D Grind」

最近的落园文章多少有点反常,从category来看原来少有更新的「游来游去」、「读书有感」和「日常应用」开始频繁的更新,而且频率连我自己都多少被吓到了。转折期想法和接受的新知识都比较多吧,所以文字也开始泛滥。

今天本来打算早早入睡的,前几天连着忙碌,多少还是有点吃不消了。不过,一切的plan夭折在打开「The Ph.D Grind」这个PDF的那一刹那--完全没想到自己会一口气读完100多页的纯英文回忆录,而且是在今天白天已经阅读了大量的英文书籍之后。但是,没办法,这部回忆录的主题实在是我太过于关心的了,如何survive in a Ph.D life?

常来的朋友们大致都知道我离开象牙塔已经一年整了,而且在可见的一段时间之内都不会折回去。可是这份纪实还是深深的勾起了我心底的疑问与渴望。一直在问自己这么一个问题:how can you survive?没有一点点把握之前,申请Ph.D也白搭。至少五年的时间,人生最好的年华。这个赌注着实太大。

我非常感谢命运的眷顾,在我盲目的就是想拿到Ph.D offer的那年,2010年,把我狠狠的拒之门外,然后用一个类似的master项目把我收留。master的一年,是从research角度最最productive的一年。虽然没有一篇文章投出去,但是这一年思想的自由与research life的体验,让我切身体会了可能到来的Ph.D life是什么样子。而最大的收获,就是了解了更多的自己,知道为了达到自己的目标到底还要做多少准备。当年没有留在UPF让很多老师朋友都多多少少不理解,而现在回头看依然觉得自己当时的决定虽然仓促武断,还有若干突发因素干扰,但终究还是正确的。离开象牙塔的这一年,重新感受到生机勃勃的感觉。我又不得不再次感谢命运的眷顾,在某些时刻总可以帮我痛快的下一些本应纠结的决定。

这本书让我欲罢不能,只是因为作者叙事的腔调和平实的文风。深有同感,虽然所处的field还是有区别的。很欣赏最后总结的那句,「读Ph.D最大的收获不是研究上的突破,也不是学历上的荣耀,而是在人生的一段时间、用心地做一件事情,得到了坚韧而诚实的品质」。作文易,做人难。

夜已深,不知还有多少的问题足以让我辗转难眠。有的时候苦笑自己好好的学什么动态规划和那一堆线性非线性的最优化方法,总是在试图计算自己离最优路径是不是偏离了太远。一个ultimate goal在那里,就忍不住不断调整方向盘。其实有的时候是不是应该更加放手而随心,相信曲径通幽呢?

最近新的冲击太多,每天都在摄入大量的信息并试图形成自己的思考,多少有点脑力吃不消了。每天早晨定时起床、出门、穿越一个十字路口的时候,都觉得人生仿佛「Groundhog Day」那样不断重复播放,需要不断努力才能打破并突破。却也钦佩那些在魔都奋斗寻梦的打工仔打工妹们--我怕是受不了work for living这样的艰苦吧。有的时候,人的懦弱真的很顽强,远远比不上那些无路可退时刻的无可选择的坚持。

Categories
读书有感

「Data Manipulation with R」若干笔记

最近两天读R的手册效率奇高无比,果然是和跟taiyun说起的一样,“有需求便有动力”。昨天一上午看完了ggplot2的手册,虽然有些晦涩难懂,但是还是很好的体系理解。p.s. ggplot2新手推荐「Cook Book for R」,先用起来再慢慢回头看原理嘛。ggplot2也是延年益寿的利器,嗯...默认的图都看起来好专业,嘻嘻。

回到本文的正题。看完了ggplot2之后,下一本被我扫荡的手册就是「Data Manipulation with R」,基本的数据整理操作。虽说数据整理是一件很没有技术含量只是耗时间的事情,但是正因如此节省起来时间也是大把大把的,顿时觉得人生加速运行了好多。说来惭愧,用R也有些年头了,一直没有静下心来好好的研究基本的R数据操作方式,总是遇到问题才会亡羊补牢似的上网开始搜,好在现在stackoverflow.com这些网站累积了大量类似的问题,所以搜起来也算方便。但终究不是个长久之计,当忍者太久了总觉得还是应该老老实实的学习一下王道正术。于是,开始花些时间细细的研读起在R里面收拾数据的那九九八十一招。

简单记录一些以前忽略的函数之类的。很多来自神奇的plyr包,如果直接?调不出来帮助那就先加载这个包吧。

  • expand.grid() : 最开始用R的时候,数据都是教材里面给的,整理的规范的很,基本就是调用一个lm()之类的函数扔进去就可以了,所以习惯于直接用factor类型相乘。后来发现经常要建立一些factor相乘出来的矩阵/data.frame之类的东西,却一直不知道怎么办。终于找到了这个函数,嘻嘻。哎,我是有多么懒才一直没有去搜这个需求啊。
  • cut():yihui兄前阵子提到的非常elegent的函数之一(另一个是with(),哎我居然连这个都一直没注意过),基本就是把连续变量离散化,即numeric型的数据转换成factor型的万能钥匙。
  • which():可能以前也没大用到类似的需求,所以没注意。一般来说,对于逻辑型的数据(很多数据筛选问题最后都可以归为逻辑型数据问题),只是选择出来符合条件的元素还是比较容易的,所以一直没留意这个函数。简而言之,就是这个函数返回的不是符合条件的元素的值,而是他们的位置(比如在一个vector中的位置,即下标)。这样有时候还是比较方便的~
  • with():这个就不多说了,基本拯救了需要attach(), detach()的地方,不用常年打dataframe的名称了。p.s. 不知道是什么缘故,很多R的教程上会用attach/detach,但实际中其实很不建议使用啊,容易把object搞混的。
  • arrange():当你需要对一个data.frame进行按照多列依次排序的时候,就不需要依次order了。说来有趣,它的函数帮助里面简洁明了,“This saves a lot of typing!”,可以少打字的都是好东西,嗯嗯。
  • cat():其实也用到过,只是很多时候更习惯paste(),毕竟不是所有的时候都要直接输出。不过需要的时候,还是比print()加paste()方便一些吧。看思考习惯了。
  • substring():常年只会用substr(),其实这两个函数蛮像的,只是参数不同。部分情况下substring()会更方便一些,不过反正有length(), nchar()这种东西,其实问题不大。
  • aggregate(), cast():前几天gaotao回复的时候提到的函数,其实某种程度上我现在更喜欢data.table()了...
  • apply类:sapply(), apply(), lapply(), mapply(),基本就是消灭显式循环的利器(当然消灭循环不仅仅是美观目的,还是提高效率的不二法宝,后面更是各种并行处理的基本架构函数,比如RHadoop重写的那堆函数)。当然,其实有的时候我会更倾向于把显式循环写出来(如果循环量不大比如<10而且每一次循环都还挺快的话)。这么做虽然效率上牺牲了一点,但是提高了代码可读性啊,就不用写很多注释提醒自己为什么当时这么弄了。由此可见我的编程水平基本停留在翻译脑子里面的逻辑化思维过程的模式,并没有实质性的在程序本身架构的角度来思考编程逻辑。咳咳,人家是做分析的,不是码农,效率的问题交给专业人士去解决吧,我更喜欢专注于思考分析的逻辑(多么苍白无力的狡辩,从来不肯在编程上原理上多花功夫的孩子飘过)。

暂时就是这些,最喜欢的就是R这种无限的可能性,总有人会贴心的帮你写好很多函数,然后傻傻的打一个?,看看函数怎么调用怎么附上参数就可以了。这才是美好的人生嘛,不喜欢过多关注那些脏活累活背后的原理,计算机自己辛苦去好了(当然还有那些辛勤的R包开发者们,嘻嘻,谢过大家的努力劳动)。不是有句话么,「科技都是为懒人服务的」。越来越赞同taiyun这次在北京R会上的惊人之语——省时间就是延年益寿。

Categories
游来游去

周末两日在无锡(文艺篇)

下面转为正常篇。周日无锡很给面子,虽然天阴沉沉的,但是没有下雨。生物钟果然被我调过来了,在IC那么又大又软的床上我居然都没睡过九点(ps 无数人表示我自己一个人住IC简直是太浪费了,呃~来日方长)。爬起来整理一番,毛华的玉兰饼用作早餐,然后就跑到酒店对面的无锡博物馆里面去了。很赞的博物馆(除了外形诡异之外),里面布置的很精致很有文化底蕴的感觉。毕竟江苏多才子嘛~虽然博物馆本身资源有限,但是无锡名人大家实在是太多了,所以捐赠了很多很精致的文物,尤其是书画!弄得我一直在想要是自己有个书房,能挂上一二幅如斯水准的水墨画,该是一件多么美好的事情啊。 另外了解了很多无锡家族的故事,感觉错过了那个风起云涌的时代,真是遗憾呢。这里多谢被我拉出来一起犯二的xiaobean童鞋,在无锡居然有这么一个忠实的读者(他说看过我每一篇博文),我真的是有点受宠若惊。不过眼前的人知道你那么多故事,或大或小,这种感觉也挺诡异的~严重的信息不对称,我基本对对方一无所知,对方却知道我的大多数所以都不用解释,呃,还好不是谈判,要不我一定一败涂地啊。anyway,大家可以想象一下这种感觉...

DSC05312-r

Inside of the Wuxi Museum, Wuxi.

然后跑到天福苑去吃正宗的无锡菜,无锡排骨太湖一锅鲜什么的,我真的是个彻头彻尾的吃货...吃货填饱了肚子,就开始无锡四处转悠,XX故居一路经过了很多,崇安寺也去阿炳纪念馆凑了凑热闹。而后受前段时间「明朝那些事儿」的影响,对顾宪成等敬仰之情开始生长,于是就去了东林书院。这群人真会选地方啊,难怪可以那么遥远的影响朝政。亭台楼阁,错落有致。虽比不上拙政园的精雕细琢,却因其琅琅读书声瑟瑟琴鸣乐超出一筹。游人不多,大致是正在修葺的缘故,但却正合了吾等喜欢清静的人之心意。那是一个回不去的辉煌岁月,是无锡现在再多有钱的主儿也盖不住的时代气势。每次在江浙游览,都对这种人文的细致滋生着强烈的崇敬感。

DSC05340

无锡排骨

DSC05394

灯笼,东林书院

DSC05403

朗朗读书班,东林书院

下午,惠山古镇。虽然有刻意人为的嫌疑,但是还是很喜欢这种略带穿越的感觉。碰到拍婚纱照的女子,灿烂的红裙飘扬在这灰白色的水调船头,鲜明的对比冲美好的冲击着视线。遗世而独立的超脱气质。

DSC05442-r

黑白色系,惠山古镇

DSC05465

红娘子,惠山古镇

DSC05524

专注,惠山古镇
犯二的事实有的时候需要一点点见证,比如穿着公司的文化衫拎着IC的纸袋子...哈哈,水乡做背景,真的又是一个鲜明的对比呢!这是需要莫大的勇气滴~!嘻嘻。

先锋书店,小资而文艺的下午茶时间。翻着泡沫的冰红茶,一下子勾起来在上大学时候喝掉的那一杯右一杯,那和朋友们在各种咖啡馆度过的一个又一个美好的下午或者晚上。那美好的,青春年华。

DSC05584-r

窗外的画?

DSC05575-r

光影的艺术,先锋书店

夜幕微降,启程回上海。不再是我心中的魔都,它在我眼中越来越美好。享受着生活的滋味与节奏,不愿浪费大好的年华。岁月,留下了些许痕迹,沧桑了阅历,却掩不住心底越来越浓厚的好奇。

本次无锡之行出片率较高,故后文多图,慎入。

Categories
日常应用

关于R的若干SQL等价问题

以前总是觉得不同的计算机语言之间只是语法问题,思路其实还是差不多的--后来才知道不尽然如此。比如用惯了R作分析,切换到其他语言顿时觉得效率降低了好多,尤其是很多一行命令在R里面就可以搞定的时候-思维习惯了一定程度的跳跃,常用的操作(尤其是数据整理!)封装成函数之后工作效率那叫一个倍增啊!结合knitr,原来的时候生成定期报告的效率极其之高,基本属于10倍以上的时间节省。

现在公司的数据平台是teradata,典型的SQL结构,各种join。在这么大的数据量下,不可能直接取数据到本机来分析,只能借助SQL进行一定程度的降维。而后剩下的收尾分析工作,可以由R完成。至于两者之间分工的界限在哪里,我还在摸索一个效率最高的平衡点。不得不吐槽一下,SQL的逻辑思维方式真心没效率,完全是为了数据库性能和空间单位平衡而设计的,做分析的时候就额外的痛苦许多——90%以上的时间都用来琢磨怎么鼓捣出来自己需要的数据格式,全在数据清理上了!

抱怨完毕,除了祈祷hadoopR和oracle连接起来彻底摆脱SQL阴影之外,暂时只能跟SQL硬战。下面说说最近常见的几个相同功能在R和SQL里面分别的实现方法。

1. 生成新变量

多见的明确的任务啊。如果是数值型,比如变量D是其他三个变量ABC的显性函数f(A,B,C),最简单诸如D=A+B+C,在R和SQL里面都是直接写。

  • R:
    my_dataframe$D <- my_dataframe$A+ my_dataframe$B + my_dataframe$C

    (当然还有更elegant的with()函数)

  • SQL(以select为例):
    SELECT A,B,C, A+B+C D from my_datatable;

    然后如果f()稍稍复杂的话,R的可以定义函数的优势就明显了,SQL只有macro模式显然不足够灵活强大。如,

R:

generate_D <- function(VarA=A, VarB=B, VarC=C) {
VarD <- VarA * VarB *(VarB %*% VarC)
return( VarD)}
my_dataframe$D <- generate_D(my_dataframe$A, my_dataframe$B, my_dataframe$C)

注:%*%代表向量内积或矩阵乘法,这里为一个数字。理论上这里可以调用任何R中函数。

如果新变量是字符型,R的优势就更明显了,字符串操作函数例如substr()取字符串其中一段,paste()连接多个字符串,grep()和sub()查找替换类,自然比SQL灵活的多。还是那句话,只要能用函数写出来,R都可以方便地搞定。你问我拿SQL跟R比这个有意思么?明显SQL就不是为了这个功能专门设计的啊。好吧,常见的生成新变量的情况:有条件的生成新变量,比如年龄分组等,基本就是按照若干已知条件生成一个新的变量。这里,SQL的case when确实方便,比如年龄分为老中青三组:

SQL:

SELECT CASE WHEN AGE>50 THEN 'old'
WHEN AGE between 25 and 50 THEN 'mid'
ELSE 'young'
END AGE_GROUP
FROM my_datatable

而R中,我一直用一种最笨的办法-刚刚搜了一下发现其实我的办法还是挺好用的。

My_dataframe$AGE_GROUP <- 'young'
My_dataframe[My_dataframe$AGE > 50,]$AGE_GROUP <- 'mid'
My_dataframe[(My_dataframe$AGE >=25 )& (My_datafame$AGE<= 50),]$AGE_GROUP <- 'mid'

当然也可以用ifelse()或者transform的方法,我倒是觉得没有这种笨办法清晰简洁易读,易于回头看代码。ifelse那堆括号哦!没有高亮匹配会死人的。

这里边界值随意,不考虑直接除法取整的情况。两种分类时可以直接用逻辑型简化,一行出结果;另,数值型离散化转换为factor型其实可以简单的用一个函数cut()搞定..(多谢yihui一语道破天机)

2. 分组加总等数据整理统计

要知道在很多时候,什么都比不上基本的求和均值方差有用,偶尔来个计数最大最小值就不错了。SQL一个group by 就神马都搞定了,比如对每组顾客购买的图书本书去重、求和。
SQL:

SELECT sum(TA.quantity) quantity ,
TB.book_type
FROM Table_A TA
OUTER JOIN Table_B TB
ON TA.book_id = TB.book_id
GROUP BY book_type

 

SELECT user_group, SUM(book_quantity) quantity, count(distinct book_id) sold_book
FROM my_datatable
GROUP BY user_group

那么相对应的,在R中,我们的解决策略是万能的data.table()。
R:

book_stat <- data.table(my_dataframe)[,list(quantity=sum(book_quantity), sold_book = length(unique(book_id))), by="user_group]

也不麻烦对嘛~可是,R里面还是有可以调用多种函数的优势哦。嘻嘻。

3. 表的连接和数据混合

咳咳,thanks to 著名的三大范式,SQL语句永远逃不掉各种各样的连接,内外左右,inner join, outer join, left join, right join 写来写去有没有!R里面呢,类似于SAS,有个神奇的merge()函数。每次看到讲left join 的教程示例的时候都觉得真心罗嗦难懂,相比而言R的merge()函数简洁明了了许多有木有!

依旧,假设我们第一个表, 两个字段 book_id, book_quantity, 然后第二个表两个字段,book_id, book_type,包含的是书的分类信息。现在需要分类统计书的数量。

SQL:

这里用外链接,既如果图书在TB中没有分类信息,会自动归于NULL这一列。

用R呢,嘻嘻,很简单。

Book_stat <- merge(TableA, TableB, by="book_id", all.x=T, all.y=T)

这里其实可以简写all=TRUE (T 在R中等价于逻辑值TRUE),只是为了更清晰所以把x,y分开了。多明显啊,我就是要保留两个表中所有的观测对象,如果任意表缺失标记为NA即可。很简单的,merge()的参数和四大连接的关系就是:
INNER JOIN 等价于 merge(all=F)
LEFT JOIN 等价于 merge(all.x=T, all.y=F)
RIGHT JOIN 等价于 merge(all.x=F, all.y=T)
OUTER JOIN 等价于 merge(all=T)

嗯啊,反正对我来说,这个更好理解...

至于SQL的where和having条件,基本就是R中对于行的选择,不再赘述,参见新变量生成那里对于行的选择。TOP或者limit也可以通过head或者直接指定行序号n:m来搞定。其他的常见的就不多了吧...过去两周的时间,我基本就在用R的整理数据框架思路来实现SQL语句撰写的煎熬中度过,多少次烦的时候都想直接砸了显示器或者哀叹如果能导出到R里面该多好...磨合期啊。

注:我现在理解SQL架构还有一项主要的feature就是索引,哈希表是个很强大的东东。这东西某种程度上类似R中factor类型的数据,但是貌似水要深很多,为了提升性能值得继续好好研究。

注2:NOSQL架构拯救分析师啊

注3:数据整理绝对是最耗分析师时间的活,如果思路不清晰不知道想要进入分析模型的数据长什么样子,那就真的悲剧了,往往一天两天就是徒劳。这也是我的小册子新版第一章加的就是数据整理,血泪的教训啊。
另外一个耗时间的就是excel或者word中作图配文字,这个绝对需要knitr来拯救-亲,对于每个分类统计是很简单,但是对于每个分类都画图的话,您难道还准备告诉excel作循环?然后一张张复制粘贴到word里面?省省时间吧,knitr会save your life的,绝对是工欲善其事,必先利其器。分析不是也不应该是体力活哦。下周的上海R沙龙,一定要好好称赞一下knitr,相比于 reproducible research,它对于业界的意义就在于没有BI系统之前,自动写报告...轻量化高效工具!

注4:ipad果然不适合码代码...有typo或不满排版的,容我稍后电脑上修改。

注5:开始研究RHadoop,各种沦落伤不起啊。