- 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