使用R语言对文件数据分组汇总是很普遍的操作,但有时我们会遇到比较大的文件,这类文件的计算结果较小,但源数据太大,无法全部放入内存进行计算,只能采用分批读取、分批计算、拼合结果的办法来解决。下面用一个例子来说明R实现大文件数据分组汇总的方法。

    有个1G的文件sales.txt,存储着大量订单记录,我们要对CLIENT字段分组并对AMOUNT字段汇总。该文件的列分割符为“\t”,前几行数据如下:

wKiom1QJVL_AKmwTAADUeaYnuA4330.jpg


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)


部分计算结果:

wKiom1QJVMCSXYkaAACHqoyKiQI433.jpg

代码解读:

    1行:打开文件句柄。

    2-3行:读入第一批的十万条数据,分组汇总后存入result

    4-8行:循环读数。每批次读入十万行数据,存入databatch变量。然后取第2和第4个字段,即“CLIENT”和“AMOUNT”。接着将databatch拼合到result中,再执行分组运算。

    同一时刻只有databatchresult会占用内存,其中databatch有十万条记录,result是汇总结果,而汇总结果通常较小,不会超出内存。

    11行:关闭文件句柄。

 

注意事项:

    关于数据框。R语言的数据框不直接支持大文件,因此要用循环语句来辅助解决。具体的算法是:读一批数据,拼合到数据框result,对result分组汇总,再读下一批数据。可以看到,循环语句这部分的代码略显复杂。

    关于列名。由于第一行数据是列名,因此第一批数据可以用header=TRUE直接设置列名,但后续数据没有列名,所以要用header=FALSE来读数。使用header=FALSE时,默认的列名是V1V2…,而分组汇总后的默认列名是Group.1x,因此还要用col.names来改名,这样才能使两者结构一致,方便后续的合并。列名这部分很容易出错,值得注意。

 

替代方案:

同样的算法也可以用Python、集算器、Perl等语言来实现。和R语言一样,这几种语言都可以实现大文本文件的分组汇总,以及后续的结构化数据计算。下面简单介绍集算器和Python的解决方案。

集算器会自动分批处理数据,程序员无需用循环语句手工控制,因此代码非常简洁:

wKioL1QJVMagDGRWAABMzIfJYI0953.jpg

    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对大文件的支持同样有限,仍然需要编写循环完成。