如何在R中模拟SQL"分区依据"?

Wei*_*Wei 17 sql r dense-rank dplyr data.table

如何在R数据帧上执行分析函数,如Oracle ROW_NUMBER(),RANK()或DENSE_RANK()函数(请参阅http://www.orafaq.com/node/55)?CRAN包"plyr"非常接近,但仍然不同.

我同意每个功能的功能可以以特别的方式实现.但我主要担心的是表现.为了记忆和速度,最好避免使用连接或索引访问.

Ben*_*nes 28

data.table软件包,特别是从1.8.1版开始,提供了SQL术语中的大部分分区功能.rank(x, ties.method = "min")在R中类似于Oracle RANK(),并且有一种方法可以使用因子(如下所述)来模拟DENSE_RANK()函数.模仿的方法最终ROW_NUMBER应该是显而易见的.

这是一个例子:data.table从R-Forge 加载最新版本:

install.packages("data.table",
  repos= c("http://R-Forge.R-project.org", getOption("repos")))

library(data.table)
Run Code Online (Sandbox Code Playgroud)

创建一些示例数据:

set.seed(10)

DT<-data.table(ID=seq_len(4*3),group=rep(1:4,each=3),value=rnorm(4*3),
  info=c(sample(c("a","b"),4*2,replace=TRUE),
  sample(c("c","d"),4,replace=TRUE)),key="ID")

> DT
    ID group       value info
 1:  1     1  0.01874617    a
 2:  2     1 -0.18425254    b
 3:  3     1 -1.37133055    b
 4:  4     2 -0.59916772    a
 5:  5     2  0.29454513    b
 6:  6     2  0.38979430    a
 7:  7     3 -1.20807618    b
 8:  8     3 -0.36367602    a
 9:  9     3 -1.62667268    c
10: 10     4 -0.25647839    d
11: 11     4  1.10177950    c
12: 12     4  0.75578151    d
Run Code Online (Sandbox Code Playgroud)

ID通过减少value内部排名group(注意-前面value表示减少顺序):

> DT[,valRank:=rank(-value),by="group"]
    ID group       value info valRank
 1:  1     1  0.01874617    a       1
 2:  2     1 -0.18425254    b       2
 3:  3     1 -1.37133055    b       3
 4:  4     2 -0.59916772    a       3
 5:  5     2  0.29454513    b       2
 6:  6     2  0.38979430    a       1
 7:  7     3 -1.20807618    b       2
 8:  8     3 -0.36367602    a       1
 9:  9     3 -1.62667268    c       3
10: 10     4 -0.25647839    d       3
11: 11     4  1.10177950    c       1
12: 12     4  0.75578151    d       2
Run Code Online (Sandbox Code Playgroud)

对于DENSE_RANK()排序值的关联,您可以将值转换为因子,然后返回基础整数值.例如,排名每个ID基于infogroup(比较infoRankinfoRankDense):

DT[,infoRank:=rank(info,ties.method="min"),by="group"]
DT[,infoRankDense:=as.integer(factor(info)),by="group"]

R> DT
    ID group       value info valRank infoRank infoRankDense
 1:  1     1  0.01874617    a       1        1             1
 2:  2     1 -0.18425254    b       2        2             2
 3:  3     1 -1.37133055    b       3        2             2
 4:  4     2 -0.59916772    a       3        1             1
 5:  5     2  0.29454513    b       2        3             2
 6:  6     2  0.38979430    a       1        1             1
 7:  7     3 -1.20807618    b       2        2             2
 8:  8     3 -0.36367602    a       1        1             1
 9:  9     3 -1.62667268    c       3        3             3
10: 10     4 -0.25647839    d       3        2             2
11: 11     4  1.10177950    c       1        1             1
12: 12     4  0.75578151    d       2        2             2
Run Code Online (Sandbox Code Playgroud)

ps Hi Matthew Dowle.


LEAD和LAG

对于模仿LEAD和LAG,请从这里提供的答案开始.我会根据组内ID的顺序创建一个排名变量.对于上面的假数据,这不是必需的,但如果ID在组内不按顺序排列,那么这将使生活变得更加困难.所以这里有一些带有非顺序ID的新假数据:

set.seed(10)

DT<-data.table(ID=sample(seq_len(4*3)),group=rep(1:4,each=3),value=rnorm(4*3),
  info=c(sample(c("a","b"),4*2,replace=TRUE),
  sample(c("c","d"),4,replace=TRUE)),key="ID")

DT[,idRank:=rank(ID),by="group"]
setkey(DT,group, idRank)

> DT
    ID group       value info idRank
 1:  4     1 -0.36367602    b      1
 2:  5     1 -1.62667268    b      2
 3:  7     1 -1.20807618    b      3
 4:  1     2  1.10177950    a      1
 5:  2     2  0.75578151    a      2
 6: 12     2 -0.25647839    b      3
 7:  3     3  0.74139013    c      1
 8:  6     3  0.98744470    b      2
 9:  9     3 -0.23823356    a      3
10:  8     4 -0.19515038    c      1
11: 10     4  0.08934727    c      2
12: 11     4 -0.95494386    c      3
Run Code Online (Sandbox Code Playgroud)

然后,要获取前一条记录的值,请使用groupidRank变量并1从中减去idRank并使用multi = 'last'参数.要从记录中获取上述两个条目的值,请减去2.

DT[,prev:=DT[J(group,idRank-1), value, mult='last']]
DT[,prev2:=DT[J(group,idRank-2), value, mult='last']]

    ID group       value info idRank        prev      prev2
 1:  4     1 -0.36367602    b      1          NA         NA
 2:  5     1 -1.62667268    b      2 -0.36367602         NA
 3:  7     1 -1.20807618    b      3 -1.62667268 -0.3636760
 4:  1     2  1.10177950    a      1          NA         NA
 5:  2     2  0.75578151    a      2  1.10177950         NA
 6: 12     2 -0.25647839    b      3  0.75578151  1.1017795
 7:  3     3  0.74139013    c      1          NA         NA
 8:  6     3  0.98744470    b      2  0.74139013         NA
 9:  9     3 -0.23823356    a      3  0.98744470  0.7413901
10:  8     4 -0.19515038    c      1          NA         NA
11: 10     4  0.08934727    c      2 -0.19515038         NA
12: 11     4 -0.95494386    c      3  0.08934727 -0.1951504
Run Code Online (Sandbox Code Playgroud)

对于LEAD,将适当的偏移量添加到idRank变量并切换到multi = 'first':

DT[,nex:=DT[J(group,idRank+1), value, mult='first']]
DT[,nex2:=DT[J(group,idRank+2), value, mult='first']]

    ID group       value info idRank        prev      prev2         nex       nex2
 1:  4     1 -0.36367602    b      1          NA         NA -1.62667268 -1.2080762
 2:  5     1 -1.62667268    b      2 -0.36367602         NA -1.20807618         NA
 3:  7     1 -1.20807618    b      3 -1.62667268 -0.3636760          NA         NA
 4:  1     2  1.10177950    a      1          NA         NA  0.75578151 -0.2564784
 5:  2     2  0.75578151    a      2  1.10177950         NA -0.25647839         NA
 6: 12     2 -0.25647839    b      3  0.75578151  1.1017795          NA         NA
 7:  3     3  0.74139013    c      1          NA         NA  0.98744470 -0.2382336
 8:  6     3  0.98744470    b      2  0.74139013         NA -0.23823356         NA
 9:  9     3 -0.23823356    a      3  0.98744470  0.7413901          NA         NA
10:  8     4 -0.19515038    c      1          NA         NA  0.08934727 -0.9549439
11: 10     4  0.08934727    c      2 -0.19515038         NA -0.95494386         NA
12: 11     4 -0.95494386    c      3  0.08934727 -0.1951504          NA         NA
Run Code Online (Sandbox Code Playgroud)


Aru*_*run 5

从开始data.table v1.9.5+,已实现功能frank()(用于快速排名)。frank()frankv()允许轻松编程的交互式场景中很有用。

它实现了中可用的每个操作base::rank。另外,优点是:

  • frank()原子向量外,listdata.framesdata.tables进行操作。

  • 我们可以为每一列指定排名是按升序还是降序计算。

  • dense除了中的其他类型外,它还实现了等级类型base

  • 您也可以-在字符列上使用降序排列。

这是使用@BenBarnes的(优秀)文章中的相同data.table 对上述所有要点的说明DT

数据:

require(data.table)
set.seed(10)
sample_n <- function(x, n) sample(x, n, replace=TRUE)
DT <- data.table(
        ID = seq_len(4*3),
        group = rep(1:4,each=3),
        value = rnorm(4*3),
        info = c(sample_n(letters[1:2], 8), sample_n(letters[3:4], 4)))
Run Code Online (Sandbox Code Playgroud)

在单列上:

您还可以使用其他方法minmaxrandomaveragefirst

在多列上

您可以使用.SD,代表数据的子集,并包含与组相对应的数据。见介绍data.table HTML小品更多的.SD

您可以frankv类似地使用,并为cols参数提供列,并使用order参数对列进行排序。


与之比较的小基准base::rank

set.seed(45L)
x = sample(1e4, 1e7, TRUE)
system.time(ans1 <- base::rank(x, ties.method="first"))
#    user  system elapsed 
#  22.200   0.255  22.536 
system.time(ans2 <- frank(x, ties.method="first"))
#    user  system elapsed 
#   0.745   0.014   0.762 
identical(ans1, ans2) # [1] TRUE
Run Code Online (Sandbox Code Playgroud)