使用R语言对文件数据分组汇总是很普遍的操作,但有时我们会遇到比较大的文件,这类文件的计算结果较小,但源数据太大,无法全部放入内存进行计算,只能采用分批读取、分批计算、拼合结果的办法来解决。下面用一个例子来说明R实现大文件数据分组汇总的方法。
有个1G的文件sales.txt,存储着大量订单记录,我们要对CLIENT字段分组并对AMOUNT字段汇总。该文件的列分割符为“\t”,前几行数据如下:
R语言解决方案
con <- file("E: \\sales.txt", "r")
result=read.table(con,nrows=100000,sep="\t",header=TRUE)
result<-aggregate(result[,4],list(result[,2]),sum)
while(nrow(databatch<-read.table(con,header=FALSE,nrows=100000,sep="\t",col.names=c("
ORDERID","Group.1","SELLERID","x","ORDERDATE")))!=0) {
databatch<-databatch[,c(2,4)]
result<-rbind(result,databatch)
result<-aggregate(result[,2],list(result[,1]),sum)
}
close(con)
部分计算结果:
代码解读:
1行:打开文件句柄。
2-3行:读入第一批的十万条数据,分组汇总后存入result。
4-8行:循环读数。每批次读入十万行数据,存入databatch变量。然后取第2和第4个字段,即“CLIENT”和“AMOUNT”。接着将databatch拼合到result中,再执行分组运算。
同一时刻只有databatch和result会占用内存,其中databatch有十万条记录,result是汇总结果,而汇总结果通常较小,不会超出内存。
11行:关闭文件句柄。
注意事项:
关于数据框。R语言的数据框不直接支持大文件,因此要用循环语句来辅助解决。具体的算法是:读一批数据,拼合到数据框result,对result分组汇总,再读下一批数据。可以看到,循环语句这部分的代码略显复杂。
关于列名。由于第一行数据是列名,因此第一批数据可以用header=TRUE直接设置列名,但后续数据没有列名,所以要用header=FALSE来读数。使用header=FALSE时,默认的列名是V1、V2…,而分组汇总后的默认列名是Group.1、x,因此还要用col.names来改名,这样才能使两者结构一致,方便后续的合并。列名这部分很容易出错,值得注意。
替代方案:
同样的算法也可以用Python、集算器、Perl等语言来实现。和R语言一样,这几种语言都可以实现大文本文件的分组汇总,以及后续的结构化数据计算。下面简单介绍集算器和Python的解决方案。
集算器会自动分批处理数据,程序员无需用循环语句手工控制,因此代码非常简洁:
cursor是集算器中用于结构化数据计算的数据类型,和数据框的用法差不多,但对大文件和复杂计算更擅长。另外,代码中的@t选项表示文件的第一行是列名,因此之后的计算可以直接用列名,这一点比较方便。
Python的代码结构和R差不多,也是手工控制循环,但python本身缺乏数据框或cursor等结构化数据类型,因此代码更底层些:
from itertools import groupby
from operator import itemgetter
result = []
myfile = open("E:\\sales.txt",‘r‘)
BUFSIZE = 10240000
myfile.readline()
lines = myfile.readlines(BUFSIZE)
value=0
while lines:
for line in lines:
record=line.split(‘\t‘)
result.append([record[1],float(record[3])])
result=sorted(result,key=lambda x:(x[0])) #分组前的排序
batch=[]
for key, items in groupby(result, itemgetter(0)): #使用groupby函数分组
value=0
for subItem in items:value+=subItem[1]
batch.append([key,value]) #最后把汇总结果拼成二维数组
result=batch
lines = myfile.readlines(BUFSIZE)
myfile.close()
除了上述的二维数组,Python也可以用第三方包来实现,比如pandas就有类似数据框的结构化数据对象。pandas可以简化代码,和R的算法差不多,但pandas对大文件的支持同样有限,仍然需要编写循环完成。