转载自:http://site.douban.com/182577/widget/notes/15728235/note/348517696/
data.table可以看作是对大家熟悉的R数据格式data.frame的功能的扩展和增强。使用data.table可以对数据集进行快速的索引、指派、按组快速的连接、修正和删除列,以及对大型数据集的快速聚合(e.g. 100GB in RAM[1])。
使用data.table对大型数据集进行处理可以大大加快编程速度和计算速度。速度的秘诀在于data.table采用了一种类似数据库的索引方式,称之为key。
1.data.table的创建和基本操作
创建一个data.table和创建data.frame很相似。
library(data.table)
# create a data.frame
DF <- data.frame(a = 1:20, b = rep(LETTERS[1:10], 2), c = rep(c(T, F), 10))
# create a data.table
DT <- data.table(a = 1:20, b = rep(LETTERS[1:10], 2), c = rep(c(T, F), 10))
# compare
head(DF, 3)
## a b c
## 1 1 A TRUE
## 2 2 B FALSE
## 3 3 C TRUE
head(DT, 3)
## a b c
## 1: 1 A TRUE
## 2: 2 B FALSE
## 3: 3 C TRUE
str(DF, 3)
## 'data.frame': 20 obs. of 3 variables:
## $ a: int 1 2 3 4 5 6 7 8 9 10 ...
## $ b: Factor w/ 10 levels "A","B","C","D",..: 1 2 3 4 5 6 7 8 9 10 ...
## $ c: logi TRUE FALSE TRUE FALSE TRUE FALSE ...
str(DT, 3)
## Classes 'data.table' and 'data.frame': 20 obs. of 3 variables:
## $ a: int 1 2 3 4 5 6 7 8 9 10 ...
## $ b: chr "A" "B" "C" "D" ...
## $ c: logi TRUE FALSE TRUE FALSE TRUE FALSE ...
## - attr(*, ".internal.selfref")=<externalptr>
看起来数据是一样的,但是在data.frame里,输入的字符型数据会默认转化为因子而在data.table里,字符型数据依然是字符。
也可以把一个已经存在的data.frame转化为data.table。下面采用鸢尾花数据集iris。
iris.dt <- data.table(iris)
iris.dt
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1: 5.1 3.5 1.4 0.2 setosa
## 2: 4.9 3.0 1.4 0.2 setosa
## 3: 4.7 3.2 1.3 0.2 setosa
## 4: 4.6 3.1 1.5 0.2 setosa
## 5: 5.0 3.6 1.4 0.2 setosa
## ---
## 146: 6.7 3.0 5.2 2.3 virginica
## 147: 6.3 2.5 5.0 1.9 virginica
## 148: 6.5 3.0 5.2 2.0 virginica
## 149: 6.2 3.4 5.4 2.3 virginica
## 150: 5.9 3.0 5.1 1.8 virginica
data.table对于大的数据集只输出前5行和后5行。
对data.table的行的操作,和data.frame是很相似的:
DT[c(1, 6), ]
## a b c
## 1: 1 A TRUE
## 2: 6 F FALSE
DT[DT$a < 3, ] #DT[DT$a<3,]
## a b c
## 1: 1 A TRUE
## 2: 2 B FALSE
但是对data.table的列操作和data.frame有很大不同。data.frame可以采用数字或者列名字符(或由它们组成的向量)访问相应的列。data.table可以通过真实列名(或列名向量)以及真实列名列表的方式访问相应的列,在后一种方式下输出的结果会保持data.table的形式。
请比较以下的结果:
# accessing columns data.frame
DF[, 2]
DF[, "b"]
DF[, 1:2]
DF[, c("a", "b")]
## data.table
DT[, b]
## [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "A" "B" "C" "D" "E" "F" "G"
## [18] "H" "I" "J"
DT[, c(a, b)]
DT[, list(b)]
## b
## 1: A
## 2: B
## 3: C
## 4: D
## 5: E
## 6: F
## 7: G
## 8: H
## 9: I
## 10: J
## 11: A
## 12: B
## 13: C
## 14: D
## 15: E
## 16: F
## 17: G
## 18: H
## 19: I
## 20: J
DT[, list(a, b)]
data.table也可以把列名写成字符方式进行引用,此时要加上参数 with=F。请注意这两种使用方式的差别。
DT[, "b", with = F]
str(DT[, "b", with = F])
## Classes 'data.table' and 'data.frame': 20 obs. of 1 variable:
## $ b: chr "A" "B" "C" "D" ...
## - attr(*, ".internal.selfref")=<externalptr>
str(DT[, "b"])
## chr "b"
2.keys
看一下现在在内存中的data.table
tables()
## NAME NROW MB
## [1,] DT 20 1
## [2,] iris.dt 150 1
## COLS KEY
## [1,] a,b,c
## [2,] Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species
## Total: 2MB
这里显示了data.table的名字、行数、MB的大小,列名以及KEY(尚未指派,是空的)。
key用来对data.table索引,能提供更快的计算速度。使用key的时候,需要使用setkey函数进行设置。可以把一个或多个列名设置为key。
# set one key
setkey(DT, b)
# show the DT again
DT
## a b c
## 1: 1 A TRUE
## 2: 11 A TRUE
## 3: 2 B FALSE
## 4: 12 B FALSE
## 5: 3 C TRUE
## 6: 13 C TRUE
## 7: 4 D FALSE
## 8: 14 D FALSE
## 9: 5 E TRUE
## 10: 15 E TRUE
## 11: 6 F FALSE
## 12: 16 F FALSE
## 13: 7 G TRUE
## 14: 17 G TRUE
## 15: 8 H FALSE
## 16: 18 H FALSE
## 17: 9 I TRUE
## 18: 19 I TRUE
## 19: 10 J FALSE
## 20: 20 J FALSE
此时DT这个data.table的b这一列已经被排序了。
key的用途之一是便于对data.table的行进行索引
DT[c("A", "D"), ]
## b a c
## 1: A 1 TRUE
## 2: A 11 TRUE
## 3: D 4 FALSE
## 4: D 14 FALSE
这种检索比data.frame式的按照行的数字或表达式进行索引的运算速度要快(速度快的原因是data.frame式的索引是一种向量的扫描,即通过生成一个对每一行标记了TRUE/FALSE的向量实现的)。在较大的数据集diamonds(其实也很小,5w多行)来试一下。
library(ggplot2)
dia.dt <- data.table(diamonds)
system.time(dia.dt[dia.dt$color == "E", ])
## user system elapsed
## 0.004 0.000 0.006
setkey(dia.dt, color)
system.time(dia.dt["E", ])
## user system elapsed
## 0.004 0.000 0.003
sessionInfo()
## R version 3.1.0 (2014-04-10)
## Platform: x86_64-pc-linux-gnu (64-bit)
当设置的key多于一个的时候,函数J()可以用来设置索引条件:
setkey(DT, b, c)
DT[J(c("A", "B"), TRUE), ]
## b c a
## 1: A TRUE 1
## 2: A TRUE 11
## 3: B TRUE NA
注:部分输出结果省略
3.data.table的基本用法
dt[i, j]:
如果i不是一个data.table,执行的是类似data.frame的行的子集索引;
如果i是一个data.table,就把i和dt的key进行连接(join),效果相当于插入;
j可以是一个单独的列名,列名的表达式,或者列名表达式的列表,函数引用。
iris.dt <- data.table(iris)
iris.dt[, plot(Sepal.Length, Sepal.Width)]
## NULL
dt[i, j,
by='colname',
mult={'first', 'last', 'all'},
nomatch={0, NA},
roll={FALSE, TRUE}]
by对应分组操作变量名;
当dt的多个行与i匹配的时候,mult控制返回的方式;
nomatch设定匹配方式,默认是内连接inner join(0),外连接outer join则设为NA; 当对一个时间序列进行匹配的时候,roll=TRUE可以通过选择之前最接近的时间进行不完全的匹配。
下面引用统计之都上的一个例子( http://cos.name/cn/topic/110312 ):
a = data.frame(startdate = c(as.Date("1999-1-1"), as.Date("2001-1-1")), enddate = c(as.Date("2001-1-1"),
as.Date("2099-12-31")), value = c(1, 2))
b = data.frame(date = seq.Date(as.Date("1999-1-1"), by = "months", length = 26))
a = data.table(a)
b = data.table(b)
setkey(a, startdate, enddate)
setkey(b, date)
a[b, ][c(1:4, 23:26)]
## startdate enddate value
## 1: 1999-01-01 2001-01-01 1
## 2: 1999-02-01 <NA> NA
## 3: 1999-03-01 <NA> NA
## 4: 1999-04-01 <NA> NA
## 5: 2000-11-01 <NA> NA
## 6: 2000-12-01 <NA> NA
## 7: 2001-01-01 2099-12-31 2
## 8: 2001-02-01 <NA> NA
a[b, roll = TRUE][c(1:4, 23:26)]
## startdate enddate value
## 1: 1999-01-01 2001-01-01 1
## 2: 1999-02-01 2001-01-01 1
## 3: 1999-03-01 2001-01-01 1
## 4: 1999-04-01 2001-01-01 1
## 5: 2000-11-01 2001-01-01 1
## 6: 2000-12-01 2001-01-01 1
## 7: 2001-01-01 2099-12-31 2
## 8: 2001-02-01 2099-12-31 2
关于data.table的基本操作也请参考: example(data.table)
4.使用data.table进行快速的分组计算
对数据的分组操作往往是进行一项数据分析工作必不可少的阶段。对于data.frame,有诸如stats包的aggregate函数,base包的apply族函数,plyr包的*ply族等适用的函数。data.tablen内置的分组计算函数,可以提高分组计算的速度。以ggplot2包的diamonds数据为例,按变量cut分组,对变量price求均值。分别使用aggregate,ddply和data.table,并比较运算时间。
str(diamonds)
## 'data.frame': 53940 obs. of 10 variables:
## $ carat : num 0.23 0.21 0.23 0.29 0.31 0.24 0.24 0.26 0.22 0.23 ...
## $ cut : Ord.factor w/ 5 levels "Fair"<"Good"<..: 5 4 2 4 2 3 3 3 1 3 ...
## $ color : Ord.factor w/ 7 levels "D"<"E"<"F"<"G"<..: 2 2 2 6 7 7 6 5 2 5 ...
## $ clarity: Ord.factor w/ 8 levels "I1"<"SI2"<"SI1"<..: 2 3 5 4 2 6 7 3 4 5 ...
......
library(plyr)
aggregate(price ~ cut, diamonds, mean)
## cut price
## 1 Fair 4359
## 2 Good 3929
## 3 Very Good 3982
## 4 Premium 4584
## 5 Ideal 3458
ddply(diamonds, .(cut), summarise, price.mean = mean(price))
## cut price.mean
## 1 Fair 4359
## 2 Good 3929
## 3 Very Good 3982
## 4 Premium 4584
## 5 Ideal 3458
dia.dt <- data.table(diamonds)
dia.dt[, list(price.mean = mean(price)), by = cut]
## cut price.mean
## 1: Ideal 3458
## 2: Premium 4584
## 3: Good 3929
## 4: Very Good 3982
## 5: Fair 4359
system.time(aggregate(price ~ cut, diamonds, mean))
## user system elapsed
## 0.320 0.000 0.323
system.time(ddply(diamonds, .(cut), summarise, price.mean = mean(price)))
## user system elapsed
## 0.012 0.000 0.012
system.time(dia.dt[, list(price.mean = mean(price)), by = cut])
## user system elapsed
## 0.004 0.000 0.002
在这三种方法里data.table的运算速度是最快的。在上面的data.table中参数list(price.mean=mean(price))的含义是对变量price求均值,并把结果命名为price.mean。
使用data.table也可以对多个列进行分组计算:
dia.dt[, list(carat.max = max(carat), price.min = min(price)), by = list(cut,
color)]
## cut color carat.max price.min
## 1: Ideal E 2.28 326
## 2: Premium E 3.05 326
## 3: Good E 3.00 327
## 4: Premium I 4.01 334
## 5: Good J 3.00 335
## 6: Very Good J 2.74 336
## 7: Very Good I 4.00 336
## 8: Very Good H 3.00 337
## 9: Fair E 2.04 337
## 10: Ideal J 3.01 340
......
## cut color carat.max price.min