主鍵
什麼是主鍵
在前面我們討論瞭如何用“i”進行提取子集的方法,這節我們採用另一種方法,用主鍵(key)來提取子集。
開始,我們先看一個data.frame,每個data.frame都有一個行名稱,先看下面一個data.frame DF :
> set.seed(1L)
> DF = data.frame(ID1 = sample(letters[1:2], 10, TRUE),
+ ID2 = sample(1:3, 10, TRUE),
+ val = sample(10),
+ stringsAsFactors = FALSE,
+ row.names = sample(LETTERS[1:10]))
> DF
ID1 ID2 val
C a 3 5
D a 1 6
E b 2 4
G a 1 2
B b 1 10
H a 2 8
I b 1 9
F b 2 1
J a 3 7
A b 2 3
> rownames(DF)
[1] "C" "D" "E" "G" "B" "H" "I" "F" "J" "A"
我們可以通過它的行名提取這一行,如下:
> DF["C", ]
ID1 ID2 val
C a 3 5
行名或多或少就像數據框中行的一個變量,但是:
1 每行只能有一個行名,但是,打個比方,每個人至少有兩個名字,first name 和second name,就如查找一個電話簿,先查second name 再查first name ,這是非常有用的。用數據框就做不到這個,因爲他只能支持一個行名稱。
2 行名還是唯一的。不能出現重複的行名。
> rownames(DF) = sample(LETTERS[1:5], 10, TRUE) #行的名稱不能重複
Error in `row.names<-.data.frame`(`*tmp*`, value = value) :
不允許有重複的'row.names'
In addition: Warning message:
non-unique values when setting 'row.names': ‘C’, ‘D’
現在我們將DT轉化爲data.table:
> DT = as.data.table(DF)
> DT
ID1 ID2 val
1: a 3 5
2: a 1 6
3: b 2 4
4: a 1 2
5: b 1 10
6: a 2 8
7: b 1 9
8: b 2 1
9: a 3 7
10: b 2 3
> rownames(DT)
[1] "1" "2" "3" "4" "5" "6" "7" "8" "9" "10"
1 現在行名已經被重置
2 Data.table不使用行名。因爲data.table是衍化自data.frame,它仍舊保留着行名的特性,但是從來不使用行名,一會兒再探討其中的原因。如果你想保留行名,可以使用keep.rownames = TRUE 在轉化過程中,as.data.table()。它會產生一個新的列rn,內容是列名。
主鍵和它的特性
1.我們可以設置多個主鍵,在多維數據中,而且數據類型可以是字符型、整型、數字型、因子型,但不支持列表和混合類型
2.不強制主鍵的唯一性。複製主鍵是被允許的。
3.設置主鍵,可以做兩件事:
a.對列進行升序排列
b.將這些列設置爲主鍵列,data.table根據主鍵列進行排序,sorted()
因爲行是重新排序的,所以至多隻有一個主鍵,因爲不能進行排序。
設置、取得和使用主鍵
1.如何設置data.table flights中的列origin爲主鍵
> setkey(flights, origin) #這在交互界面下使用,飛鏟更方便
> head(flights[,-(1:6)])
arr_delay cancelled carrier tailnum flight origin dest air_time distance hour min
1: 0 0 AA N3DEAA 119 EWR LAX 339 2454 18 24
2: -17 0 AA N5CFAA 172 EWR MIA 161 1085 16 55
3: 185 0 AA N471AA 300 EWR DFW 214 1372 16 11
4: -2 0 AA N4WNAA 320 EWR DFW 214 1372 14 49
5: -10 0 AA N5DMAA 1205 EWR MIA 154 1085 6 7
6: -17 0 AA N491AA 1223 EWR DFW 215 1372 9 49
另一種方法:
setkeyv(flights, "origin") #在函數中使用比較方便
設置完主鍵後,flights被自動修改,並不會產生中間data.table,因此,非常的有效率,且節省空間。
在建立data.table時也可以直接設置主鍵,採用參數key,內容是列名字,字符型向量。
設置“*”和“:=”
設置origin爲主鍵以後,可以按照行提取子集:
> flights[.("JFK"),-(1:6)]
arr_delay cancelled carrier tailnum flight origin dest air_time distance hour min
1: 13 0 AA N338AA 1 JFK LAX 359 2475 9 14
2: 13 0 AA N335AA 3 JFK LAX 363 2475 11 57
3: 9 0 AA N327AA 21 JFK LAX 351 2475 19 2
4: 1 0 AA N319AA 117 JFK LAX 350 2475 13 47
5: -18 0 AA N323AA 185 JFK LAX 338 2475 21 33
---
81479: -21 0 UA N596UA 512 JFK SFO 337 2586 17 5
81480: -37 0 UA N568UA 514 JFK SFO 344 2586 18 27
81481: -33 0 UA N518UA 535 JFK LAX 320 2475 17 53
81482: -38 0 UA N512UA 541 JFK SFO 343 2586 9 24
81483: -38 0 UA N590UA 703 JFK LAX 323 2475 11 24
flights[J("JFK")]
flights[list("JFK")]
flights["JFK"]
flights[c("JFK", "LGA")]
如何返回data.table中被設置爲主鍵的列?
> key(flights)
[1] "origin"
如何同時設置兩列爲主鍵?
> setkey(flights, origin, dest)
> head(flights[,-(1:6)])
arr_delay cancelled carrier tailnum flight origin dest air_time distance hour min
1: -25 0 EV N11547 4373 EWR ALB 30 143 7 24
2: 79 0 EV N18120 4470 EWR ALB 29 143 23 13
3: 211 0 EV N11184 4373 EWR ALB 32 143 15 26
4: 19 0 EV N14905 4551 EWR ALB 32 143 7 55
5: 42 0 EV N19966 4470 EWR ALB 26 143 8 17
6: 62 0 EV N19966 4682 EWR ALB 31 143 23 1
> key(flights)
[1] "origin" "dest"
它首先根據列"origin"進行排序,然後再根據列"dest"進行排序。比如origin第一個值是EWR,則對應的dest下面的值依次是ALB,B…,C…,一直到z,然後origin取值B,dest取值ewr,b,c等等。
根據主鍵列origin=JFK且dest=MIA篩選所有的行
> flights[.("JFK", "MIA"),-(1:6)]
arr_delay cancelled carrier tailnum flight origin dest air_time distance hour min
1: -17 0 AA N5FJAA 145 JFK MIA 161 1089 15 9
2: -8 0 AA N5DWAA 1085 JFK MIA 166 1089 9 17
3: -1 0 AA N635AA 1697 JFK MIA 164 1089 12 27
4: 3 0 AA N5CGAA 2243 JFK MIA 157 1089 5 46
5: -12 0 AA N397AA 2351 JFK MIA 154 1089 17 36
---
2746: -22 0 AA N5FNAA 2351 JFK MIA 148 1089 16 59
2747: -20 0 AA N5EYAA 1085 JFK MIA 146 1089 8 26
2748: -17 0 AA N5BTAA 1101 JFK MIA 150 1089 6 47
2749: -12 0 AA N3ETAA 2299 JFK MIA 150 1089 5 42
2750: 4 0 AA N5FSAA 2387 JFK MIA 146 1089 19 44
這個語句內部是如何工作的呢?
首先匹配JFK,篩選出子集,然後再匹配MIA,得出兩者都匹配的數據集。
選擇滿足第一個主鍵origin="JFK"的所有行
> key(flights)
[1] "origin" "dest"
> flights[.("JFK")] ## or in this case simply flights["JFK"], for convenience
arr_delay cancelled carrier tailnum flight origin dest air_time distance hour min
1: 4 0 B6 N766JB 65 JFK ABQ 280 1826 20 11
2: 161 0 B6 N507JB 65 JFK ABQ 252 1826 22 15
3: 6 0 B6 N652JB 65 JFK ABQ 269 1826 20 6
4: -15 0 B6 N613JB 65 JFK ABQ 259 1826 20 9
5: 32 0 B6 N598JB 65 JFK ABQ 267 1826 20 39
---
81479: -18 0 DL N915AT 2165 JFK TPA 142 1005 8 0
81480: -8 0 B6 N516JB 225 JFK TPA 149 1005 19 32
81481: -22 0 B6 N334JB 325 JFK TPA 145 1005 14 43
81482: -5 0 B6 N637JB 925 JFK TPA 149 1005 9 57
81483: -18 0 B6 N595JB 1025 JFK TPA 145 1005 8 31
選擇滿足第二個主鍵dest="MIA"的所有行
> flights[.(unique(origin), "MIA")]
因爲不能跳過第一個主鍵,所以,需要把第一個主鍵的值設置爲唯一。----目前,還沒有想的通其中過的道理。
結合主鍵,j\by
a.選擇j
選擇滿足主鍵分別等於“LGA”,“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
也可以使用以下代碼,效果與上面一樣:
> flights[.("LGA","TPA"),"arr_delay",with=FALSE]
b.鏈接
在a結果的基礎上,對列進行排序結果如下:
> 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
c.對j進行計算
在滿足origin = "LGA" and dest = "TP"的子集上,計算arr_delay的最大值
> flights[.("LGA","TPA"),max(arr_delay)]
[1] 486
d.通過引用進行子集替代,在 j 上使用:=
查找所有不同的時間(hour)
> 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 23
結果,共有24個不同時間,0和23都在,現在用0代替23。這次的方法是,用主鍵實現:
> setkey(flights, hour)
> key(flights)
[1] "hour"
> flights[.(23), hour := 0L]
> key(flights)
NULL
> 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
當用0代替23後,hour列已經不再按照順序排列,所以,key(flights),返回值爲NULL
e.使用“by”分組
先設置主鍵:
> setkey(flights,origin,dest)
> key(flights)
[1] "origin" "dest"
計算origin=“JFK”,每月dep_delay的最大值,並按月排序
> ans <- flights["JFK", max(dep_delay), keyby = month]
> head(ans)
month V1
1: 1 881
2: 2 1014
3: 3 920
4: 4 1241
5: 5 853
6: 6 798
> key(ans)
[1] "month"
結果顯示,主鍵變爲month.
3)附加的參數“mult”、“nomatch”
a.mult參數
返回滿足條件的第一行
> flights[.("JFK", "MIA"), mult = "first"]
year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin dest air_time
1: 2014 1 1 546 6 853 3 0 AA N5CGAA 2243 JFK MIA 157
返回滿足條件的最後一行
> flights[.(c("LGA", "JFK", "EWR"), "XNA"), mult = "last"]
year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin dest air_time distance hour min
1: 2014 5 23 1803 163 2003 148 0 MQ N515MQ 3553 LGA XNA 158 2: NA NA NA NA NA NA NA NA NA NA NA JFK XNA NA
3: 2014 2 3 1208 231 1516 268 0 EV N14148 4419 EWR XNA 184
沒有滿足JFK 和 XNA 的行,故返回值都是NA
b.nomatch參數
> flights[.(c("LGA", "JFK", "EWR"), "XNA"), mult = "last", nomatch = 0L]
year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin dest air_time 1: 2014 5 23 1803 163 2003 148 0 MQ N515MQ 3553 LGA XNA 158 2: 2014 2 3 1208 231 1516 268 0 EV N14148 4419 EWR XNA 184
nomatch 默認值是NA,當設置nomatch=0L後,不匹配的列都被跳過,不予顯示。因此,只返回了兩行結果。
4)二進制檢索與矢量掃描
> flights[.("JFK", "MIA")]
> flights[origin == "JFK" & dest == "MIA"]
比較兩種檢索方法,
> t1 <- system.time(ans1 <- DT[x == "g" & y == 877L])
> t1
用戶 系統 流逝
0.33 0.28 10.16
> dim(t1)
NULL
> dim(ans1)
[1] 761 3
> t2 <- system.time(ans2 <- DT[.("g", 877L)])
> t2
用戶 系統 流逝
0.01 0.00 0.13
> dim(ans2)
[1] 761 3
第二種比第一種速度提高了78倍。很明顯了!
原因:
第一種即矢量掃描,所有的數據都要遍歷一遍,且返回邏輯值,TRUE or FALSE ,兩列,然後再比較兩列是否相等。速度很長的慢。
第二種,即二進制檢索,以以下的例子爲證:
1, 5, 10, 19, 22, 23, 30
數據已經被排序了,同主鍵的特徵,如果要尋找值等於1 的行,
(1)從中位數19開始,19==1?否,19>1
(2)所以大於19的值都不再進行檢索
(3)選取1到19的中位數5,5==1?否,5>1
(4)選取1-5的數,1==1?是,完成。
遍歷次數比第一種方法少很多,因此速度提升很快。
所以,通過使用關鍵詞進行檢索,是非常高效的。