Key(主键)和基于子集的快速检索

本文深入探讨了数据框中主键的概念,包括其特性和如何设置、获取与使用。通过实例讲解了如何利用主键进行高效的数据检索和操作,对比了二进制检索与矢量扫描的不同效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

主键

什么是主键

 

在前面我们讨论了如何用“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个不同时间,023都在,现在用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?是,完成。

遍历次数比第一种方法少很多,因此速度提升很快。

 

所以,通过使用关键词进行检索,是非常高效的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值