- data.table的特点:减小计算复杂度,降低计算时间。
1.主键
利用主键进行subset,速度会更加快。随着data.table的不断升级,很多行为譬如根据条件提取子集,已经进行了优化,设置和不设置主键的性能差异已经不是太大了。
设置主键
通过函数setkey()
来设置主键。下边代码将origin列设置为主键。
setkey(flights,origin)
setkey(flights,"origin")
head(flights)
## year month day dep_time dep_delay arr_time arr_delay cancelled carrier
## 1: 2014 1 1 1824 4 2145 0 0 AA
## 2: 2014 1 1 1655 -5 2003 -17 0 AA
## 3: 2014 1 1 1611 191 1910 185 0 AA
## 4: 2014 1 1 1449 -1 1753 -2 0 AA
## 5: 2014 1 1 607 -3 905 -10 0 AA
## 6: 2014 1 1 949 4 1243 -17 0 AA
## tailnum flight origin dest air_time distance hour min
## 1: N3DEAA 119 EWR LAX 339 2454 18 24
## 2: N5CFAA 172 EWR MIA 161 1085 16 55
## 3: N471AA 300 EWR DFW 214 1372 16 11
## 4: N4WNAA 320 EWR DFW 214 1372 14 49
## 5: N5DMAA 1205 EWR MIA 154 1085 6 7
## 6: N491AA 1223 EWR DFW 215 1372 9 49
origin列设置为主键后,一个明显特征,其中的元素从小到大排序了。主键列中元素并不唯一,可以重复。 列被setkey后,速度提升很快。利用microbenchmark包进行测试。设置为主键后,的确快一些。
require(microbenchmark)
## Loading required package: microbenchmark
microbenchmark(flights[.("JFK")],flights.copy[origin == "JFK"])
## Unit: milliseconds
## expr min lq mean median uq
## flights[.("JFK")] 11.0625 11.97405 14.82183 12.6537 17.50760
## flights.copy[origin == "JFK"] 11.7488 12.97150 17.33587 13.7778 20.57225
## max neval
## 26.2247 100
## 91.2646 100
- 因为已经将主键设置为 origin列了,所以只要直接指定“JFK”就可以了。这里 .()用来在data.table的主键(也就是flights 的 origin列)里,查找“JFK”。
- 首先,满足“JFK”条件的行的索引都被获取到。然后,这些行的哪些信息是必要的呢。既然参数j里没有指定任何表达式,这些行的所有列都被返回了。
如果主键是字符型的列,那么可以省略 .(),就像用行名subset一个data.frame的行的时候。 flights[“JFK”] ## same as flights[.(“JFK”)]
我们可以根据需要指定多个值 flights[c(“JFK”, “LGA”)] ## same as flights[.(c(“JFK”, “LGA”))] 这返回所有 origin列是“JFK” 或者 “LGA”的所有行。
flights[c("JFK", "LGA")]
## 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
## ---
## 165912: 2014 10 31 609 24 843 -5 0
## 165913: 2014 10 31 1459 1 1747 -30 0
## 165914: 2014 10 31 1102 -8 1311 16 0
## 165915: 2014 10 31 1106 -4 1325 15 0
## 165916: 2014 10 31 824 -5 1045 1 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
## ---
## 165912: UA N16709 1714 LGA IAH 198 1416 6 9
## 165913: UA N23708 1744 LGA IAH 201 1416 14 59
## 165914: MQ N827MQ 3591 LGA RDU 83 431 11 2
## 165915: MQ N511MQ 3592 LGA DTW 75 502 11 6
## 165916: MQ N813MQ 3599 LGA SDF 110 659 8 24
flights[.(c("JFK", "LGA"))]
## 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
## ---
## 165912: 2014 10 31 609 24 843 -5 0
## 165913: 2014 10 31 1459 1 1747 -30 0
## 165914: 2014 10 31 1102 -8 1311 16 0
## 165915: 2014 10 31 1106 -4 1325 15 0
## 165916: 2014 10 31 824 -5 1045 1 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
## ---
## 165912: UA N16709 1714 LGA IAH 198 1416 6 9
## 165913: UA N23708 1744 LGA IAH 201 1416 14 59
## 165914: MQ N827MQ 3591 LGA RDU 83 431 11 2
## 165915: MQ N511MQ 3592 LGA DTW 75 502 11 6
## 165916: MQ N813MQ 3599 LGA SDF 110 659 8 24
使用函数 key()
,获得被设置为主键的列名。
key(flights)
## [1] "origin"
key(flights.copy)
## NULL
- 函数 key() 返回主键列名的字符型向量。
- 如果data.table没有设置过主键,返回 NULL。
2. 更多主键
可以把主键理解为功能更强大的行名。可以设置多列为主键。
setkey(flights,origin,dest)
head( flights)
## year month day dep_time dep_delay arr_time arr_delay cancelled carrier
## 1: 2014 1 2 724 -2 810 -25 0 EV
## 2: 2014 1 3 2313 88 9 79 0 EV
## 3: 2014 1 4 1526 220 1618 211 0 EV
## 4: 2014 1 4 755 35 848 19 0 EV
## 5: 2014 1 5 817 47 921 42 0 EV
## 6: 2014 1 5 2301 66 2 62 0 EV
## tailnum flight origin dest air_time distance hour min
## 1: N11547 4373 EWR ALB 30 143 7 24
## 2: N18120 4470 EWR ALB 29 143 23 13
## 3: N11184 4373 EWR ALB 32 143 15 26
## 4: N14905 4551 EWR ALB 32 143 7 55
## 5: N19966 4470 EWR ALB 26 143 8 17
## 6: N19966 4682 EWR ALB 31 143 23 1
从返回结果看,origin和dest这两列被重新排序了。
提取满足orgin==“JFK”,dest== “MIA”条件的数据集。
flights[.("JFK","MIA")]
## year month day dep_time dep_delay arr_time arr_delay cancelled
## 1: 2014 1 1 1509 -1 1828 -17 0
## 2: 2014 1 1 917 7 1227 -8 0
## 3: 2014 1 1 1227 2 1534 -1 0
## 4: 2014 1 1 546 6 853 3 0
## 5: 2014 1 1 1736 6 2043 -12 0
## ---
## 2746: 2014 10 31 1659 -1 1956 -22 0
## 2747: 2014 10 31 826 -3 1116 -20 0
## 2748: 2014 10 31 647 2 941 -17 0
## 2749: 2014 10 31 542 -3 834 -12 0
## 2750: 2014 10 31 1944 29 2232 4 0
## carrier tailnum flight origin dest air_time distance hour min
## 1: AA N5FJAA 145 JFK MIA 161 1089 15 9
## 2: AA N5DWAA 1085 JFK MIA 166 1089 9 17
## 3: AA N635AA 1697 JFK MIA 164 1089 12 27
## 4: AA N5CGAA 2243 JFK MIA 157 1089 5 46
## 5: AA N397AA 2351 JFK MIA 154 1089 17 36
## ---
## 2746: AA N5FNAA 2351 JFK MIA 148 1089 16 59
## 2747: AA N5EYAA 1085 JFK MIA 146 1089 8 26
## 2748: AA N5BTAA 1101 JFK MIA 150 1089 6 47
## 2749: AA N3ETAA 2299 JFK MIA 150 1089 5 42
## 2750: AA N5FSAA 2387 JFK MIA 146 1089 19 44
当有两个主键时,对第一个主键,这样写是ok的。
flights[.("JFK")]
## year month day dep_time dep_delay arr_time arr_delay cancelled
## 1: 2014 1 1 2011 10 2308 4 0
## 2: 2014 1 2 2215 134 145 161 0
## 3: 2014 1 7 2006 6 2314 6 0
## 4: 2014 1 8 2009 15 2252 -15 0
## 5: 2014 1 9 2039 45 2339 32 0
## ---
## 81479: 2014 10 31 800 0 1040 -18 0
## 81480: 2014 10 31 1932 1 2228 -8 0
## 81481: 2014 10 31 1443 -2 1726 -22 0
## 81482: 2014 10 31 957 -8 1255 -5 0
## 81483: 2014 10 31 831 -4 1118 -18 0
## carrier tailnum flight origin dest air_time distance hour min
## 1: B6 N766JB 65 JFK ABQ 280 1826 20 11
## 2: B6 N507JB 65 JFK ABQ 252 1826 22 15
## 3: B6 N652JB 65 JFK ABQ 269 1826 20 6
## 4: B6 N613JB 65 JFK ABQ 259 1826 20 9
## 5: B6 N598JB 65 JFK ABQ 267 1826 20 39
## ---
## 81479: DL N915AT 2165 JFK TPA 142 1005 8 0
## 81480: B6 N516JB 225 JFK TPA 149 1005 19 32
## 81481: B6 N334JB 325 JFK TPA 145 1005 14 43
## 81482: B6 N637JB 925 JFK TPA 149 1005 9 57
## 81483: B6 N595JB 1025 JFK TPA 145 1005 8 31
如何只对第二个主键进行过滤?
flights[.("MIA")]
## year month day dep_time dep_delay arr_time arr_delay cancelled carrier
## 1: NA NA NA NA NA NA NA NA <NA>
## tailnum flight origin dest air_time distance hour min
## 1: <NA> NA MIA <NA> NA NA NA NA
像上边这样是行不通的。必须要给出第一个主键的所有值,通过unique(origin)
来实现。
flights[.(unique(origin),"MIA")]
## year month day dep_time dep_delay arr_time arr_delay cancelled
## 1: 2014 1 1 1655 -5 2003 -17 0
## 2: 2014 1 1 607 -3 905 -10 0
## 3: 2014 1 1 1125 -5 1427 -8 0
## 4: 2014 1 1 1533 43 1840 42 0
## 5: 2014 1 1 2130 60 29 49 0
## ---
## 9924: 2014 10 31 1348 -11 1658 -8 0
## 9925: 2014 10 31 950 -5 1257 -11 0
## 9926: 2014 10 31 658 -2 1017 10 0
## 9927: 2014 10 31 1913 -2 2212 -16 0
## 9928: 2014 10 31 1530 1 1839 -11 0
## carrier tailnum flight origin dest air_time distance hour min
## 1: AA N5CFAA 172 EWR MIA 161 1085 16 55
## 2: AA N5DMAA 1205 EWR MIA 154 1085 6 7
## 3: AA N3AGAA 1623 EWR MIA 157 1085 11 25
## 4: UA N491UA 244 EWR MIA 155 1085 15 33
## 5: UA N476UA 308 EWR MIA 162 1085 21 30
## ---
## 9924: AA N3AMAA 2283 LGA MIA 157 1096 13 48
## 9925: AA N3LFAA 2287 LGA MIA 150 1096 9 50
## 9926: AA N3HNAA 2451 LGA MIA 156 1096 6 58
## 9927: AA N3LFAA 2455 LGA MIA 156 1096 19 13
## 9928: US N768US 1715 LGA MIA 164 1096 15 30
3. 和参数j, by一起使用
提取满足origin=“LGA”和dest=“TPA”这两个条件的数据集,并且只返回arr_delay列。
key(flights)
## [1] "origin" "dest"
flights[.("LGA","TPA"),.(arr_delay)]
## arr_delay
## 1: 1
## 2: 14
## 3: -17
## 4: -4
## 5: -12
## ---
## 1848: 39
## 1849: -24
## 1850: -12
## 1851: 21
## 1852: -11
chaining表达式,链式表达式。
对上边的表达式生成的数据集,进一步对arr_delay按照降序排列。
flights[.("LGA","TPA"),.(arr_delay)][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
j参数运算
找出从LGA到TPA的到达航班最长延迟时间。
flights[.("LGA","TPA"),.(max(arr_delay))]
## V1
## 1: 486
可以跟降序排列的第一行结果对比验证。两者应该是一致的。
利用主键,把hour列中的24替换为0。注意,替换后,由于主键列的内容发生变化,hour的主键特性消失。
setkey(flights,hour)
key(flights)
## [1] "hour"
flights[.(24),hour:=0L]
flights[,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
key(flights)
## NULL
用参数by进行聚合
每个月从“JFK”起飞航班的最大起飞延误时间,按照月排序。注意用到了keyby
参数。
setkey(flights,origin,dest)
flights[.("JFK")]
## year month day dep_time dep_delay arr_time arr_delay cancelled
## 1: 2014 7 2 40 280 336 282 0
## 2: 2014 1 13 1957 3 2254 -13 0
## 3: 2014 1 14 1955 -5 2226 -42 0
## 4: 2014 1 16 1953 -1 2232 -35 0
## 5: 2014 1 17 1946 -8 2208 -59 0
## ---
## 81479: 2014 8 9 2201 31 55 34 0
## 81480: 2014 8 10 2211 41 40 19 0
## 81481: 2014 8 21 2243 73 152 91 0
## 81482: 2014 4 15 2347 257 245 250 0
## 81483: 2014 8 16 2323 113 159 98 0
## carrier tailnum flight origin dest air_time distance hour min
## 1: B6 N520JB 65 JFK ABQ 244 1826 0 40
## 2: B6 N583JB 65 JFK ABQ 262 1826 19 57
## 3: B6 N641JB 65 JFK ABQ 254 1826 19 55
## 4: B6 N828JB 65 JFK ABQ 248 1826 19 53
## 5: B6 N531JB 65 JFK ABQ 240 1826 19 46
## ---
## 81479: B6 N591JB 725 JFK TPA 128 1005 22 1
## 81480: B6 N585JB 725 JFK TPA 128 1005 22 11
## 81481: B6 N564JB 725 JFK TPA 133 1005 22 43
## 81482: B6 N588JB 225 JFK TPA 156 1005 23 47
## 81483: B6 N590JB 725 JFK TPA 131 1005 23 23
flights.max.dep_delay.per.month <- flights[.("JFK"),max(dep_delay),keyby=month]
head(flights.max.dep_delay.per.month)
## month V1
## 1: 1 881
## 2: 2 1014
## 3: 3 920
## 4: 4 1241
## 5: 5 853
## 6: 6 798
4. 两个重要的参数mult和nomatch
mult
参数
用来设置返回的行数, first
只返回第一行,last
返回最后一行,all
返回所有行。
flights[.("JFK","MIA"),mult="first"]
## year month day dep_time dep_delay arr_time arr_delay cancelled carrier
## 1: 2014 1 1 546 6 853 3 0 AA
## tailnum flight origin dest air_time distance hour min
## 1: N5CGAA 2243 JFK MIA 157 1089 5 46
flights[.("JFK","MIA"),mult="last"]
## year month day dep_time dep_delay arr_time arr_delay cancelled carrier
## 1: 2014 2 3 2301 331 210 315 0 AA
## tailnum flight origin dest air_time distance hour min
## 1: N360AA 2351 JFK MIA 146 1089 23 1
flights[.("JFK","MIA"),mult="all"]
## year month day dep_time dep_delay arr_time arr_delay cancelled
## 1: 2014 1 1 546 6 853 3 0
## 2: 2014 1 2 544 4 915 25 0
## 3: 2014 1 4 554 14 902 12 0
## 4: 2014 1 9 538 -2 837 -13 0
## 5: 2014 1 10 539 -1 842 -8 0
## ---
## 2746: 2014 10 4 2137 142 33 125 0
## 2747: 2014 10 16 2145 150 38 130 0
## 2748: 2014 8 10 2231 302 126 277 0
## 2749: 2014 8 30 2231 196 112 164 0
## 2750: 2014 2 3 2301 331 210 315 0
## carrier tailnum flight origin dest air_time distance hour min
## 1: AA N5CGAA 2243 JFK MIA 157 1089 5 46
## 2: AA N5DTAA 2243 JFK MIA 167 1089 5 44
## 3: AA N5ENAA 2243 JFK MIA 162 1089 5 54
## 4: AA N640AA 2243 JFK MIA 161 1089 5 38
## 5: AA N5CKAA 2243 JFK MIA 158 1089 5 39
## ---
## 2746: AA N5EWAA 2387 JFK MIA 151 1089 21 37
## 2747: AA N5EKAA 2387 JFK MIA 142 1089 21 45
## 2748: AA N5FSAA 2351 JFK MIA 138 1089 22 31
## 2749: AA N5BSAA 2387 JFK MIA 133 1089 22 31
## 2750: AA N360AA 2351 JFK MIA 146 1089 23 1
nomatch
参数
指定在没有找到符合条件的数据的情况下,是返回NA呢,还是跳过(不返回)。默认是返回NA,如果想跳过,设置nomatch=0L
。JFK+XNA不匹配数据库中的任何一条记录,因此在第一种方式中跳过,在第二种方式中用NA补充。
flights[.(c("LGA", "JFK", "EWR"), "XNA"), mult="last", nomatch = 0L]
## year month day dep_time dep_delay arr_time arr_delay cancelled carrier
## 1: 2014 5 23 1803 163 2003 148 0 MQ
## 2: 2014 2 3 1208 231 1516 268 0 EV
## tailnum flight origin dest air_time distance hour min
## 1: N515MQ 3553 LGA XNA 158 1147 18 3
## 2: N14148 4419 EWR XNA 184 1131 12 8
flights[.(c("LGA", "JFK", "EWR"), "XNA"), mult="last", nomatch = NA]
## year month day dep_time dep_delay arr_time arr_delay cancelled carrier
## 1: 2014 5 23 1803 163 2003 148 0 MQ
## 2: NA NA NA NA NA NA NA NA <NA>
## 3: 2014 2 3 1208 231 1516 268 0 EV
## tailnum flight origin dest air_time distance hour min
## 1: N515MQ 3553 LGA XNA 158 1147 18 3
## 2: <NA> NA JFK XNA NA NA NA NA
## 3: N14148 4419 EWR XNA 184 1131 12 8
5. 二分法搜索vs向量搜索
探讨一下为什么设置主键后,速度会提升。
创建一个2000万行三列的数据。
set.seed(1234)
N <- 2e7L
DT <- data.table(x=sample(letters,N,TRUE),
y=sample(1000L,N,TRUE),
val=runif(N),key=c("x","y"))
print(object.size(DT),units="Mb")
## 381.5 Mb
key(DT)
## [1] "x" "y"
head(DT)
## x y val
## 1: a 1 0.5370339
## 2: a 1 0.8148401
## 3: a 1 0.5479537
## 4: a 1 0.2060227
## 5: a 1 0.1166678
## 6: a 1 0.3066754
上边代码产生了一个380M的数据集,用来测试和比较设置主键后的性能优化效果。
现在要提取x=a 和 y=100的行。来比较直接向量提取和利用主键来做的性能差异。
require(microbenchmark)
microbenchmark(DT[x=="a" & y==100L], DT[.("a",100L)])
## Unit: milliseconds
## expr min lq mean median uq max
## DT[x == "a" & y == 100L] 2.3902 2.53205 2.747706 2.64780 2.91935 3.5221
## DT[.("a", 100L)] 1.3863 1.50180 1.699601 1.62685 1.80665 4.3145
## neval
## 100
## 100
看起来,设置主键后的确是快一些。 看看给出的解释:中文地址在这里
向量扫描
- 在所有两千条数据中,逐行搜索 x列里值为“g”的行。这会生成一个有两千行的逻辑向量,根据和x列的批评结果,它每个元素的取值可能是TRUE, FALSE 以及 NA。
- 相似的,在所有两千条数据中,逐行搜索 y列里值为“877”的行,再保存在另一个逻辑向量里面。
- 操作符“&”对上面两个逻辑向量进行“且”运算,返回结果为TRUE的行 这就是所谓的“向量扫描”。效率非常低,特别是数据量很大、需要重复subset的时候。因为它每次不得不对整个数据全盘扫描。
二分法搜索
这里有一个简单的示例。看看下面这组排过序的数字: 1, 5, 10, 19, 22, 23, 30 假设我们希望找到数字1的位置,用二分法搜索(因为这组数字是排过序的),我们是这么做的:
- 从中间的数开始,它是19,不是1,而且 1<19。
- 既然我们要找的数字1小于19,那它应该排在19前面。所以我们可以无视19后面的那一半数据,因为它们都大于19.
- 现在我们的数据只剩下1, 5, 10。再找到中间的数5,它不是1,而且 1<5。
- 现在我们的数据只剩下1。符合条件。这就是我们要找的数。 相反的,向量扫描需要扫描所有的数字,在这个例子中是7。
用主键也就是二分法搜索的时间开销是O(logn),而用向量扫描,时间开销是O(n)。当n(数据行数)非常大时,性能会差别很大。