Categories
日常应用

从R里面底层操纵Excel/xlsx(自动化报告福音)

好吧,我在eBay折腾的最多的就是生成自动化报告时候各种软件之间的相互调用,什么R啊,SAS啊,Teradata啊,Excel啊,Python啊,反正基本都有机会相互调用一下。每到此时我就深深感慨选择一个library丰富的工具是多么的重要!You could hardly expect what you colleagues are handy with!(P.s. 不要跟我提VBA这种逆天存在的东西。有哪个时间研究它你学点啥别的不好...)

今天忍无可忍+心情大好的折腾了一下R和excel。这个不是简单的从R里面读写excel数据,而是真心用R去操纵excel里面的单元格(cell),除了读写数据之外还要定义样式什么的。excel作为一个奇葩的软件,you may never expect where people would paste data to! 然后他们再自定义一堆样式(我恨这种点点鼠标就能改的东西,你丫又不是Photoshop...)。

但是没办法,人家定义好的“高端洋气”的报表姿态你不能轻易动啊。只能乖乖的往里面paste数据。这件事虽说一次两次手动也就罢了,三五次真的是要疯掉的。anyway,万事总有解决的途径...

很久以前从Yixuan 的博客上得知有xlsx这么个包,当时只记得这东西可以读写xlsx...直到后面折腾了一下才知道这货底层居然调用的是java的xlsx API,也就是说不用写Java也可以操作xlsx了,yeah!

为了生成excel格式的自动化报告(不要问我为啥不用knitr,不用***,说起来都是泪呀!),我主要需要解决的就是:

  • 读取原有xlsx文件,保持格式、附加新格式。
  • 在相应的位置粘进去新的数据。(当然如果只有这么一个需求可以通过ODBC来做...)

第一个倒是满简单的,就是较之yixuan代码里面的createWorkbook(),改成loadWorkbook()就可以了。然后就是找到相应的sheet,这个也满简单的,一行getSheets搞定。

然后第二步建议不要去操作cell(太没效率了),直接操作cellblock。CellBlock()可以用来定义一个新的CellBlock,然后灵活运用CB.setBorder()和CB.setColData()就可以先增加边框、然后一列列填充数据。这里使用按列填充数据主要是因为R里面的Data Frame是一列一个数据格式的,一下子把一块儿都paste到excel的cellblock里面的话,会报错...BTW为了定义边框的样式,需要用到Border()。类似的还可以定义Fill和Font这些。

同上,最好不要直接用addDataFrame()来直接贴数据...格式不能覆盖。如果是要在一个新的sheet上贴数据,那么就write.xlsx(sheetName="newsheet",append=T)好了。不需要通过上述底层的API折腾了。

最后还有一个比较有用的函数,autoSizeColumn()可以用来自动调整列宽。全鼓捣完之后saveWorkbook()保存就可以啦。

最后的最后,一个珍贵的建议——都在R里面把数据整理好再去想输出到excel里面(什么reshape2啊,data.table啊,plyr啊,该上的一起上啊!),千万别手贱在excel里面改一点点小东西...每一次都手动改一下下你的时间就被白白浪费了好几分钟!珍爱生命,远离excel...

附上一段我最后搞定自动化报告的代码:

library("xlsx")
test_template <- loadWorkbook("template.xlsx") #读入template.xlsx文件。定义好各种乱七八糟的格式的。
design_tab <- getSheets(test_template)[["design"]] #转到design这个sheet。
data_block <- CellBlock(design_tab, 5,5,nrow(mydata),ncol(mydata)) #准备贴数据的方块,我这里从第5行第5列开始贴。
border <-  Border(color="black", position=c("LEFT", "RIGHT"),
pen=c("BORDER_THIN", "BORDER_THIN")) #定义边框样式——左右黑色细直线。
for (i in 1:ncol(mydata))
{
CB.setBorder(data_block, border,colIndex = i,rowIndex=1:nrow(onetime_design_tab)) #给每一列都贴上边框
CB.setColData(data_block, mydata[,i], i, rowOffset=0, showNA=F, colStyle=NULL)#给每一列贴数据
}
border_bottom <-  Border(color="black", position=c("LEFT", "RIGHT","BOTTOM"),pen="BORDER_THIN") #定义结尾行样式——底端黑细直线
data_block_bottom <- CellBlock(design_tab, 5+nrow(mydata),5,1,ncol(mydata)) #选择最后一行
CB.setBorder( data_block_bottom, border_bottom, 1, 1:ncol(onetime_design_tab)) #定义最后一行格式
autoSizeColumn(design_tab, 5:(5+ncol(onetime_design_tab)))#调整列宽
saveWorkbook(test_template, file=output_xlsx_name) #保存
##add row data
write.xlsx(rawdata, file=output_xlsx_name, sheetName="raw_data",append=T,row.names=F) #直接贴原始数据,无格式
###add queries
R_file <- readLines(R_file_name, n=-1) #直接贴R代码
SQL_file <- readLines(SQL_file_name, n=-1)
write.xlsx(SQL_file, file=output_xlsx_name, sheetName="query_SQL",append=T,row.names=F) #直接贴代码到新的sheet中
write.xlsx(R_file, file=output_xlsx_name, sheetName="query_R",append=T,row.names=F)

 


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,各种沦落伤不起啊。

Categories
网站建设

初识Markdown语法

听着Yihui兄及COS的一群骨干们说了好久的MD(markdown)和knitr,各种心里痒痒啊。无奈最近的一段时间总觉得学习新东西的成本太高,就懒得开始动手。今天忍不住去GitHub上看了一眼,发觉自己已然没有任何理由再不快点把那个小册子弄完了,于是乎,赶紧补课。

补课的第一项工作就是升级各类软件,包括R、RStudio和Git。悄悄的说一句,现在的Git版本貌似比以前的容易用多了,我是对Github的使用没有任何印象了,重新跟着help鼓捣了一遍发现还是不怎么困难的。在一不小心删掉所有的文件之后,成功的fetch并push了所有的文件(还好有下载zip备份)。

然后下一站就是开始研究Markdown语法……好吧,这东西真的比HTML还简单啊(一直认为HTML已经是超级简洁了)。随便从网路上搜了一个语法说明(http://markdown.tw/),然后就可以开始玩转这东西了——真的很符合日常排版习惯啊!引用这个网页上的一句话:

HTML 是一種發佈的格式,Markdown 是一種編寫的格式。

习惯了Wordpress编辑器HTML模式的人,大概会对Markdown瞬间上手吧。完全米有难度……而且,符合习惯代码洁癖的人……

好吧,我怎么开始研究起这些东西来了?不是说好不玩这些geek的东西么?还是各种忍不住啊……下一个牺牲的应该不会是落园的wordpress架构吧,暂时我还不想动它,搬日志什么的很麻烦的……