21

什么是 R 等价函数,如 Oracle ROW_NUMBER()RANK()DENSE_RANK()(“根据行的顺序为行分配整数值”;参见http://www.orafaq.com/node/55)?

我同意每个功能的功能都可以通过特殊方式实现。但我主要关心的是性能。为了内存和速度,最好避免使用连接或索引访问。

4

5 回答 5

30

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)

创建一些示例数据:

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

通过在范围内ID递减来对每个进行排名(注意前面的表示递减顺序):valuegroup-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

对于DENSE_RANK()正在排名的值的关系,您可以将该值转换为一个因子,然后返回基础整数值。例如,ID根据infowithin对每个进行排名group(与 比较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

ps 嗨,马修·道尔。


领先和落后

要模仿 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

然后要获取前 1 条记录的值,请使用groupandidRank变量并减去and 使用1参数。要从上面的两个记录中获取值,请减去。idRankmulti = '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

对于 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
于 2012-07-12T09:18:38.107 回答
7

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

它实现了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)))

在单列上:

  • 计算dense排名:

    DT[, rank := frank(value, ties.method="dense"), by=group]
    

您也可以使用其他方法minmaxrandom和。averagefirst

  • 按降序排列:

    DT[, rank := frank(-value, ties.method="dense"), by=group]
    
  • 使用frankv,类似于frank

    # increasing order
    frankv(DT, "value", ties.method="dense")
    
    # decreasing order
    frankv(DT, "value", order=-1L, ties.method="dense")
    

在多个列上

您可以使用.SD,它代表数据子集并包含与组对应的数据。有关更多信息,请参阅data.table HTML 小插图简介.SD

  • info, value分组时按列排名group

    DT[, rank := frank(.SD,  info, value, ties.method="dense"), by=group]
    
  • 用于-指定降序:

    DT[, rank := frank(.SD,  info, -value, ties.method="dense"), by=group]
    
  • 您也可以-直接在字符列上使用

    DT[, rank := frank(.SD, -info, -value, ties.method="dense"), by=group]
    

您可以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
于 2015-01-25T20:20:55.753 回答
3

我和其他人一样喜欢data.table,但这并不总是必要的。data.table总是会更快,但即使对于中等大小的数据集,如果数相当少,plyr仍然可以充分执行。

BenBarnes 使用s 所做的事情可以使用plyrdata.table紧凑地完成(但正如我之前提到的,在许多情况下可能会更慢):

library(plyr)                
ddply(DT,.(group),transform,valRank = rank(-value))
ddply(DT,.(group),transform,valRank = rank(info,ties.method = "min"),
                            valRankDense = as.integer(factor(info)))

甚至根本没有加载一个额外的包:

do.call(rbind,by(DT,DT$group,transform,valRank = rank(-value)))
do.call(rbind,by(DT,DT$group,transform,valRank = rank(info,ties.method = "min"),
                                        valRankDense = as.integer(factor(info))))

尽管在最后一种情况下您确实丢失了一些语法细节。

于 2012-07-13T03:31:20.427 回答
0

Dplyr 现在有 windows 函数,包括 row_number 和 dense_rank:https://dplyr.tidyverse.org/reference/ranking.html

df <- tibble::tribble(
~subjects,        ~date, ~visits, 
     1L, "21/09/1999",      2L, 
     1L, "29/04/1999",      4L, 
     2L, "18/02/1999",     15L, 
     3L, "10/07/1999",     13L, 
     4L, "27/08/1999",      7L, 
     7L, "27/10/1999",     14L, 
    10L, "18/04/1999",      8L, 
    13L, "27/09/1999",     14L, 
    14L, "15/09/1999",      6L, 
    16L, "27/11/1999",     14L, 
    20L, "06/02/1999",      4L, 
    22L, "07/09/1999",     12L, 
    23L, "24/03/1999",     14L, 
    24L, "19/01/1999",      7L, 
 )

注意 ORDER BY 不需要像 ROW_NUMBER() SQL 代码那样规定。

df_partition <- df %>% 
  group_by(subjects) %>% # group_by is equivalent to GROUP BY in the SQL partition 
ROW_NUMBER()
  mutate(rn = row_number(visits),
         rn_reversed = row_number(desc(visits))) %>% 
ungroup() %>% # grouping by subjects remains on data unless removed like this
  mutate(dense_rank = dense_rank(visits))
于 2020-12-04T20:19:59.017 回答
-6

我不认为有直接等同于 Oracle 的分析功能。Plyr 可能能够实现一些分析功能,但不能直接实现所有功能。我确信 R 可以单独复制每个功能,但我认为没有一个包可以做到这一切。

如果您需要在 R 中实现特定操作,则进行一些谷歌搜索,如果您没有找到,请在 StackOverflow 上提出特定问题。

于 2012-07-12T08:14:10.953 回答