4 min read

data.table学习笔记4

  • data.table的特点:减小计算复杂度,降低计算时间

1.二级索引(Secondary index)

1.1 什么是二级索引

二级索引与主键的区别:

  • 不在内存中将整个data.table数据集重新排序。它只会计算某列的顺序,将这个顺序向量保存在一个附件的属性index里面。
  • 一个data.table可以有多个二级索引。 见下面代码实例。
setindex(flights,origin)
head(flights)
##    year month day dep_time dep_delay arr_time arr_delay cancelled carrier
## 1: 2014     1   1      914        14     1238        13         0      AA
## 2: 2014     1   1     1157        -3     1523        13         0      AA
## 3: 2014     1   1     1902         2     2224         9         0      AA
## 4: 2014     1   1      722        -8     1014       -26         0      AA
## 5: 2014     1   1     1347         2     1706         1         0      AA
## 6: 2014     1   1     1824         4     2145         0         0      AA
##    tailnum flight origin dest air_time distance hour min
## 1:  N338AA      1    JFK  LAX      359     2475    9  14
## 2:  N335AA      3    JFK  LAX      363     2475   11  57
## 3:  N327AA     21    JFK  LAX      351     2475   19   2
## 4:  N3EHAA     29    LGA  PBI      157     1035    7  22
## 5:  N319AA    117    JFK  LAX      350     2475   13  47
## 6:  N3DEAA    119    EWR  LAX      339     2454   18  24

1.2 查看增加的index属性

names(attributes(flights))
## [1] "names"             "row.names"         "class"            
## [4] ".internal.selfref" "index"

1.3 查看data.table的二级索引

indices(flights)
## [1] "origin"

1.4 删除二级索引

setindex(flights,NULL)
indices(flights)
## NULL

重新创建一个索引

setindex(flights,origin,dest)
indices(flights)
## [1] "origin__dest"
setindex(flights,origin)

1.5 什么情况下使用二级索引

  • 如果循环使用一个主键,那么重排序是可以的;
  • 如果经常更换主键,那么重排序会耗费大量的时间,使用二级索引非常有用的。

2. 使用参数on更为方便的进行二级索引和提取子集

2.1 利用参数i提取子集

提取origin是JFK的所有航班记录:

flights[.("JFK"),on="origin"]
##        year month day dep_time dep_delay arr_time arr_delay cancelled
##     1: 2014     1   1      914        14     1238        13         0
##     2: 2014     1   1     1157        -3     1523        13         0
##     3: 2014     1   1     1902         2     2224         9         0
##     4: 2014     1   1     1347         2     1706         1         0
##     5: 2014     1   1     2133        -2       37       -18         0
##    ---                                                               
## 81479: 2014    10  31     1705        -4     2024       -21         0
## 81480: 2014    10  31     1827        -2     2133       -37         0
## 81481: 2014    10  31     1753         0     2039       -33         0
## 81482: 2014    10  31      924        -6     1228       -38         0
## 81483: 2014    10  31     1124        -6     1408       -38         0
##        carrier tailnum flight origin dest air_time distance hour min
##     1:      AA  N338AA      1    JFK  LAX      359     2475    9  14
##     2:      AA  N335AA      3    JFK  LAX      363     2475   11  57
##     3:      AA  N327AA     21    JFK  LAX      351     2475   19   2
##     4:      AA  N319AA    117    JFK  LAX      350     2475   13  47
##     5:      AA  N323AA    185    JFK  LAX      338     2475   21  33
##    ---                                                              
## 81479:      UA  N596UA    512    JFK  SFO      337     2586   17   5
## 81480:      UA  N568UA    514    JFK  SFO      344     2586   18  27
## 81481:      UA  N518UA    535    JFK  LAX      320     2475   17  53
## 81482:      UA  N512UA    541    JFK  SFO      343     2586    9  24
## 81483:      UA  N590UA    703    JFK  LAX      323     2475   11  24
  • 这段语句执行的subset是通过创建二级索引,基于快速二分法搜索的。但记住,它不会把这个二级索引自动创建为data.table的一个属性。
  • 如果已经添加了一个二级索引了,那么参数on就可以直接使用这个二级索引,而不是再对整个航班信息flights进行计算。
  • on必须是一个字符型的向量

2.2 进一步选择列

筛选满足条件origin=“LGA” 和dest=“TPA”的数据集,并且只返回arr_delay列。

flights[.("LGA","TPA"),.(arr_delay),on=c("origin","dest")]
##       arr_delay
##    1:         1
##    2:        14
##    3:       -17
##    4:        -4
##    5:       -12
##   ---          
## 1848:        39
## 1849:       -24
## 1850:       -12
## 1851:        21
## 1852:       -11

2.3 进一步对结果按照降序排列

flights[.("LGA","TPA"),.(arr_delay),on=c("origin","dest")][order(-arr_delay)]
##       arr_delay
##    1:       486
##    2:       380
##    3:       351
##    4:       318
##    5:       300
##   ---          
## 1848:       -40
## 1849:       -43
## 1850:       -46
## 1851:       -48
## 1852:       -49

2.4 参数j计算

找出满足条件origin=“LGA” 和dest=“TPA”的航班记录中,最长到达延误时间。

flights[.("LGA","TPA"),.(MaxArrDelay=max(arr_delay),MinArrDelay=min(arr_delay)),on=c("origin","dest")]
##    MaxArrDelay MinArrDelay
## 1:         486         -49

2.5 参数by聚合

flights[.("JFK"),max(dep_delay),keyby=month,on="origin"]
##     month   V1
##  1:     1  881
##  2:     2 1014
##  3:     3  920
##  4:     4 1241
##  5:     5  853
##  6:     6  798
##  7:     7  926
##  8:     8  772
##  9:     9  553
## 10:    10  848

2.6 参数j里使用操作符“:=”进行sub-assign

是继续使用以前的例子,hour中的24替换为0。

flights[,sort(unique(hour))]
##  [1]  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22
## [24] 23 24
flights[.(24L),hour:=0L,on="hour"]
  • 这是二级索引的一大优点。以前章节,只是为了更新一些行的hour列的取值,我们不得不调用函数setkey()将hour列设置为主键,这必须对整个data.table进行重新排序。但是现在,用参数on,原数据的顺序并没有改变,操作反而更快了!而代码还是如此简洁。

3.自动索引

data.table 会默认对==%in%操作符自动创建索引,并且作为data.table的属性保存起来。

首先创建1个非常大的data.table来测试性能。

set.seed(1L)
dt = data.table(x=sample(1e5L,1e7L,TRUE),y=runif(100L))
print(object.size(dt),units = "Mb")
## 114.4 Mb

列出dt的属性。

names(attributes(dt))
## [1] "names"             "row.names"         "class"            
## [4] ".internal.selfref"

第一次使用==%in%时,会自动创建一个二级索引,用来进行subset。

(t1 <- system.time(ans <- dt[x==989L]))
##    user  system elapsed 
##    1.11    0.08    0.33
names(attributes(dt))
## [1] "names"             "row.names"         "class"            
## [4] ".internal.selfref" "index"

重新进行一次subset,由于索引已经创建,速度会很快。

(t2 <- system.time(ans <- dt[x==989L]))
##    user  system elapsed 
##       0       0       0

从两次对比时间,可以看出,第二次时间已经大为缩短。到写这篇博客为止,还没有对>=,<=等符号自动二级索引。

indices(dt)
## [1] "x"
setindex(dt,NULL)
(t3 <- system.time(ans <- dt[x>=989L]))
##    user  system elapsed 
##    0.14    0.04    0.15
indices(dt)
## NULL