什么是 R 等价函数,如 Oracle ROW_NUMBER()
、RANK()
或DENSE_RANK()
(“根据行的顺序为行分配整数值”;参见http://www.orafaq.com/node/55)?
我同意每个功能的功能都可以通过特殊方式实现。但我主要关心的是性能。为了内存和速度,最好避免使用连接或索引访问。
什么是 R 等价函数,如 Oracle ROW_NUMBER()
、RANK()
或DENSE_RANK()
(“根据行的顺序为行分配整数值”;参见http://www.orafaq.com/node/55)?
我同意每个功能的功能都可以通过特殊方式实现。但我主要关心的是性能。为了内存和速度,最好避免使用连接或索引访问。
该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
递减来对每个进行排名(注意前面的表示递减顺序):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
对于DENSE_RANK()
正在排名的值的关系,您可以将该值转换为一个因子,然后返回基础整数值。例如,ID
根据info
within对每个进行排名group
(与 比较infoRank
)infoRankDense
:
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 条记录的值,请使用group
andidRank
变量并减去and 使用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
对于 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
从data.table v1.9.5+
, 功能frank()
(用于快速排名)已实现。frank()
在交互式场景中很有用,其中 asfrankv()
允许轻松编程。
它实现了base::rank
. 此外,优点是:
frank()
除了原子向量之外,还对list、data.frames和data.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]
您也可以使用其他方法min
、max
、random
和。average
first
按降序排列:
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
我和其他人一样喜欢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))))
尽管在最后一种情况下您确实丢失了一些语法细节。
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))
我不认为有直接等同于 Oracle 的分析功能。Plyr 可能能够实现一些分析功能,但不能直接实现所有功能。我确信 R 可以单独复制每个功能,但我认为没有一个包可以做到这一切。
如果您需要在 R 中实现特定操作,则进行一些谷歌搜索,如果您没有找到,请在 StackOverflow 上提出特定问题。