集算器和R语言都是典型的数据处理及分析语言,都具有二维结构化数据对象,都擅长多步骤的复杂计算。但两者的二维结构化数据对象在底层机制上存在较大的差异,这种差异导致了集算器对于结构化数据的计算更为擅长,特别适合应用程序员进行商业计算,而R对矩阵计算更为擅长,特别适合科学工作者进行科学计算或工程计算。
集算器的二维结构化数据类型是序表对象(TSeq)。序表对象以记录为基础,多条记录形成行式的二维表,二维表配合列名形成完整的数据结构。R语言以向量为基础,多条向量形成列式的二维表,二维表配合列名形成完整的数据结构。
底层机制影响着用户的实际体验,下面我们将从基本功能、高级功能、实际案例、实测性能这四个方面比较序表对象和数据框在实际运用中的具体差异。
说明:下列比较均使用开发语言原生的函数,不涉及第三方扩展包。
基本功能
例1:从文件中读取二维结构化数据,并按坐标访问第一行第二列的值。
数据框:
data<-read.table("e:/sales.txt",header=TRUE,sep="\t") result<-data[1,2]序表:
=data=file("e:/sales.txt").import@t() =data(1).#2
比较:在最基本的功能上,两者无明显差别。
注:sales.txt文件是tab分割的结构化数据,前几行如下:
例2:按行号字段名访问第一行第二列的值。
数据框:
Result1<-data$Client[1] Result2<-data[1,]$Client序表:
=data(1).(Client) =data.(Client)(1)比较:两者没有明显差别。
例3:访问列数据。分两类:按列号和列名访问,每类分两种情况:单独取第二列,合并取第二、第四列。
数据框:
Result1<-data[2] Result2<-data[,c(2,4)] Result3<-data$Client Result4<-data[,c("Client","Amount")]序表:
=data.(#2) =data.new(#2,#4) =data.(Client) =data.new(Client,Amount)比较:两者都可以访问列数据,唯一的区别在于取多列数据时的语法,数据框是直接取数,而序表是用new函数新建序表。语法虽有不同,但两者的实际处理方法相同,都是从原对象复制两列数据到新对象。
例4:记录操控。包括:按行号取前两条记录、追加记录、在第二行插入记录、删除第二行的记录。
数据框:
Record1<-data[c(1,2),] append<- data.frame(OrderID=152, Client="CA", SellerId=5, Amount=2961.40, OrderDate="2010-12-5 0:00:00") data<- rbind(data, append) insert<-data.frame(OrderID=153, Client="RA", SellerId=4, Amount=1931.20, OrderDate="2009-11-5 0:00:00") data<-rbind(data[1,], insert,data[2:151,]) data<-data[-2,]序表:
=data([1,2]) =data.insert(0,152:OrderID,"CA":Client,5:SellerId,2961.40:Amount,"2010-12-5 0:00:00":OrderDate) =data.insert(2,153:OrderID,"RA":Client,4:SellerId,1931.20:Amount,"2009-11-5 0:00:00":OrderDate) =data.delete(2)比较:两者都可以实现记录的操控。相比之下集算器 更方便些,它可以用insert函数直接向序表追加或插入记录,而R语言需要用拆分再合并数据框的办法间接实现。
总结:由于序表和数据框都是结构化二维数据对象,因此在读写、访问、维护等基本功能方面没有明显的差异。
高级功能
例5:关联修改。A1、A2都是二维架构化数据对象,具有相同的字段ID,现在需要根据ID把A2的bonus字段值加到A1的salary字段值中。
序表:
A1=db.query("select id,name,salary from salary order by id") A2=db.query("select id,bonus from bonus order by id") A1.modify(1:A2,salary+bonus:salary)
数据框没有关联修改函数,需要手工写代码间接实现,代码略去。
例6:归并关联。A1、A2、A3都是二维架构化数据对象,具有相同的字段ID,请将它们用左连接关联起来。由于数据已按ID排序,请使用归并法提高关联的速度。
序表:
join@m1(A1:salary,id; A2: bonus,id; A3,attendance,id)
数据框支持两表关联,例如:merge(A1,A2,by.x="id",by.y="id",all=TRUE),本例是三表关联,可以通过两个两表关联来间接实现。
另外,数据框不支持归并关联,因此无法提高关联速度。换句话说,数据框无法利用有序数据来提高性能,不仅在关联运算上,在其他运算上同样如此。
例7:记录查找。分四种情况:取出Amount大于1000记录,取出Amount大于1000的记录序号,返回主键为v的记录,返回主键为v的记录序号。
序表:
=data.select(Amount>1000) = data.pselect(Amount >1000) = data.find(v) = data.pfind(v)数据框:只能直接实现前两种情况,如下:
data [data $ Amount >1000,] which(data $ Amount >1000)数据框没有主键的概念,因此其他两种情况需要手工写代码间接实现,或借助第三方package(比如data.table),代码略去。
例8:分组汇总。将data按照Client和SellerId分组,再对另两个字段汇总:对Amount字段求和,对OrderID字段计数。
序表:
=data.groups(Client,SellerId;sum(Amount),count(OrderID))数据框:只支持对单字段的汇总,比如对Am
result<- aggregate(data[,4],data[c(2,3)],sum)如果想对两个字段同时汇总,数据框只能用两句aggregate分别计算,再将结果合并。此处代码略去。
例9:重复利用分组。对data按照Client分组,分组后的结果再进行多种后续计算,包括:按照amount汇总,按照SellerId再分组并计数。
序表:
A2=data.group(Client) =A2.(~.sum(Amount)) =A2.(~.groups(SellerId;count(OrderID)))数据框不直接支持重复利用分组,分组和汇总通常要一步完成,也就是说必须进行两次同样的分组动作才能实现本功能,如下:
result<-aggregate(data[,2],sum) result<-aggregate(data[,2],data[,3],count)
如果想重复利用分组,就要使用split函数加循环的方式来实现,代码冗长性能低下。
总结:在高级功能上序表和数据框的区别比较大,主要表现在以下五点:
1. 功能丰富程度。序表函数丰富,针对结构化数据的计算非常方便,而数据框脱胎于矩阵,对结构化数据的计算支持不足,很多功能缺失。使用第三方package可以在一定程度上补充数据框缺失的功能,但这些package的成熟度和稳定性无法和R原生的库函数相媲美。
2.语法难易程度。序表函数名比较直观,比如select就是查找,pselect就是查找位置(position),而数据框的语法比较晦涩,比如按字段查找是data [data $ Amount >1000,],而按字段取数是data[,"Amount"],两者很容易混淆,应用程序员难以理解,必须学习一定的向量知识才能真正掌握。
3. 内存占用。序表函数基本上只返回引用,对内存的占用极少,而数据框是从原对象中复制记录出来,如果是对大量数据进行多次的查找、关联、分组等常规操作,数据框占用内存会大得多,会影响系统正常工作。
4. 代码工作量和代码性能。数据框支持的函数不够丰富,只能手工书写代码间接实现,因此工作量较大。而R的解释器慢得出名,手工书写的代码比库函数性能低太多。
5.库函数性能。序表有很多函数可以提高计算性能,比如归并关联,分组函数、二分查找、hash查找。而数据框虽能实现关联、汇总、查找,却很难提高性能。
实际案例
下面,我们通过一个实际案例对序表和数据框进行综合比较。
计算目标:通过日交易数据,从多只蓝筹股中选出连续上涨5天的股票。
思路:导入数据;过滤出上个月的数据;按照股票代码分组;将数据按日期排序;计算出每天比上一天的收盘价的增长额;计算出连续正增长的天数;过滤出正增长天数大于等于5的那些股票。
序表解法:
01 library(gdata) #use excel function library 02 A1<- read.xls("e:\\data\\all.xlsx") #import data 03 A2<-subset(A1,as.POSIXlt(Date)>=as.POSIXlt('2012-06-01') & as.POSIXlt(Date)<=as.POSIXlt('2012-06-30')) #filter by date 04 A3 <- split(A2,A2$Code) #group by Code 05 A8<-list() 06 for(i in 1:length(A3)){ 07 A3[[i]][order(as.numeric(A3[[i]]$Date)),] #sort by Date in each group 08 A3[[i]]$INC<-with(A3[[i]], Close-c(0,Close[- length (Close)])) #add a column, increased price 09 if(nrow(A3[[i]])>0){ #add a column, continuous increased days 10 A3[[i]]$CID[[1]]<-1 11 for(j in 2:nrow(A3[[i]])){ 12 if(A3[[i]]$INC[[j]]>0 ){ 13 A3[[i]]$CID[[j]]<-A3[[i]]$CID[[j-1]]+1 14 }else{ 15 A3[[i]]$CID[[j]]<-0 16 } 17 } 18 } 19 if(max(A3[[i]]$CID)>=5){ #stock max CID is bigger than 5 20 A8[[length(A8)+1]]<-A3[[i]] 21 } 22 } 23 A9<-lapply(A8,function(x) x$Code[[1]]) #finally,stock code
比较:
1. 数据框函数不够丰富,缺乏专业性,需要使用嵌套循环来实现本案例,计算效率低。序表函数丰富多样,无需循环就可以实现同样的功能,并且代码更简短,性能更高。
2. 针对数据框编程时,代码晦涩难懂,书写不便交流不便。针对序表编程时,代码清晰易懂,学习成本更低。
3. 本案例涉及的数据量比较大时需要占用较多的内存。序表是引用式计算,对内存的消耗很低。数据框是传值式计算,需要占用序表数倍的内存,容易发生内存溢出的情况。
4. 为了将数据从Excel导入数据框,R需要第三方软件包,但似乎和数据框的配合不够好,需要十分钟才能完成数据的导入,而序表完成相同的动作只需要几十秒。
实测性能
测试1:内存中产生1000万条记录,每记录三个字段,值均为随机数。过滤记录,并分别对每个字段汇总求和。
序表:
> library(timeDate) > start=Sys.timeDate() > col1=rnorm(n=10000000,mean=20000,sd=10000) > col2=rnorm(n=10000000,mean=40000,sd=10000) > col3=rnorm(n=10000000,mean=80000,sd=10000) > data1=data.frame(col1,col2,col3) > data2=subset(data1,col1>90) > result=colSums(data2) > print(result) col1 col2 col3 200844165732 390691612886 781453730448 > end=Sys.timeDate() > print(end-start) Time difference of 1.533333 mins
比较:序表用时50.534秒,数据框用时91.999秒,差距比较明显。
测试2:读入1.2G的txt文件,过滤并对两个字段汇总求和
序表:
> library(timeDate) > start=Sys.timeDate() > data<-read.table("d:/T21.txt",sep = "\t") > data1=subset(data,V1>90,select=c(V9,V11)) > result=colSums(data1) > print(result) V9 V11 5942982895 59484930179 > end=Sys.timeDate() > print(end-start) Time difference of 1.134722 hours
比较:序表用时87.122秒,数据框用时1.1347小时,两者性能相差几十倍。差距之所以这么大,主要因为数据框读取文件的速度太慢。
通过上述对比,我们可以发现在功能丰富程度、语法难易程度、内存占用、开发工作量、库函数性能、自编码性能等方面,序表对象都要优于数据框。当然,数据框不是R语言的全部,R有强大的向量矩阵以及相关的海量函数,在科学计算和工程计算方面要比集算器更加专业。