在特定列上排名时如何分区?
Posted
技术标签:
【中文标题】在特定列上排名时如何分区?【英文标题】:How to partition when ranking on a particular column? 【发布时间】:2012-04-15 05:51:18 【问题描述】:全部:
我有一个如下所示的数据框。我知道我可以像这样进行全局排名:
dt <- data.frame(
ID = c('A1','A2','A4','A2','A1','A4','A3','A2','A1','A3'),
Value = c(4,3,1,3,4,6,6,1,8,4)
);
> dt
ID Value
1 A1 4
2 A2 3
3 A4 1
4 A2 3
5 A1 4
6 A4 6
7 A3 6
8 A2 1
9 A1 8
10 A3 4
dt$Order <- rank(dt$Value,ties.method= "first")
> dt
ID Value Order
1 A1 4 5
2 A2 3 3
3 A4 1 1
4 A2 3 4
5 A1 4 6
6 A4 6 8
7 A3 6 9
8 A2 1 2
9 A1 8 10
10 A3 4 7
但是我怎样才能为特定 ID 设置排名顺序而不是全局排名顺序。我怎样才能完成这项工作?在 T-SQL 中,我们可以使用以下语法来完成此操作:
RANK() OVER ( [ < partition_by_clause > ] < order_by_clause > )
有什么想法吗?
【问题讨论】:
【参考方案1】:许多选项。
使用 plyr 包中的ddply
:
library(plyr)
ddply(dt,.(ID),transform,Order = rank(Value,ties.method = "first"))
ID Value Order
1 A1 4 1
2 A1 4 2
3 A1 8 3
4 A2 3 2
5 A2 3 3
6 A2 1 1
7 A3 6 2
8 A3 4 1
9 A4 1 1
10 A4 6 2
或者如果使用 data.table 包的性能是一个问题(即非常大的数据):
library(data.table)
DT <- data.table(dt,key = "ID")
DT[,transform(.SD,Order = rank(Value,ties.method = "first")),by = ID]
ID Value Order
[1,] A1 4 1
[2,] A1 4 2
[3,] A1 8 3
[4,] A2 3 2
[5,] A2 3 3
[6,] A2 1 1
[7,] A4 1 1
[8,] A4 6 2
[9,] A3 6 2
[10,] A3 4 1
或者使用split
lapply
do.call
和rbind
的基本R解决方案的所有细节:
do.call(rbind,lapply(split(dt,dt$ID),transform,
Order = rank(Value,ties.method = "first")))
【讨论】:
不错的答案,像往常一样。为了从 data.table 中获得最佳性能,最好尽可能避免使用.SD
。这对于大型 data.tables 应该更快(这是您最有可能首先使用该包的地方!):DT <- data.table(dt,key = c("ID", "Value")); DT[, list(Value, Order=seq_len(.N)), by=ID]
我一直在尝试实施您的 data.table 解决方案,但每行的排名仅为 1。我几乎逐字逐句地使用了您的代码,只更改了变量名。你知道我可能犯的错误吗?我知道您看不到代码,所以这是一个难题,但我不想重复提问。
我想在这里添加一条评论,data.table
现在提供了一个快速排名功能frank
... 应该几乎可以插入上面的rank
。 . 不幸的是,当我与数百万组打交道时,它对我来说仍然很慢。如果我找到更快的方法会发布更多。
与其使用丑陋的 do.call(rbind(lapply())),不如使用专门设计的函数:ave()
。
有人可以用 dplyr 重写吗,请【参考方案2】:
我的方式,但可能会更好。没用过等级,也不知道。谢谢,可能有用。
#Your Data
dt <- data.frame(
ID = c('A1','A2','A4','A2','A1','A4','A3','A2','A1','A3'),
Value = c(4,3,1,3,4,6,6,1,8,4)
)
dt$Order <- rank(dt$Value,ties.method= "first")
#My approach
dt$id <- 1:nrow(dt) #needed for ordering and putting things back together
dt <- dt[order(dt$ID),]
dt$Order.by.group <- unlist(with(dt, tapply(Value, ID, function(x) rank(x,
ties.method = "first"))))
dt[order(dt$id), -4]
产量:
ID Value Order Order.by.group
1 A1 4 5 1
2 A2 3 3 2
3 A4 1 1 1
4 A2 3 4 3
5 A1 4 6 2
6 A4 6 8 2
7 A3 6 9 2
8 A2 1 2 1
9 A1 8 10 3
10 A3 4 7 1
编辑:
如果您不关心保留数据的原始顺序,那么这可以使用更少的代码:
dt <- dt[order(dt$ID),]
dt$Order.by.group <- unlist(with(dt, tapply(Value, ID, function(x) rank(x,
ties.method= "first"))))
ID Value Order.by.group
1 A1 4 1
5 A1 4 2
9 A1 8 3
2 A2 3 2
4 A2 3 3
8 A2 1 1
7 A3 6 2
10 A3 4 1
3 A4 1 1
6 A4 6 2
【讨论】:
【参考方案3】:这里有几种方法:
ave 这采用具有相同 ID 的每组 Value 数字,并将排名分别应用于每个这样的集合。不使用任何包。
Rank <- function(x) rank(x, ties.method = "first")
transform(dt, rank = ave(Value, ID, FUN = Rank))
给予:
ID Value rank
1 A1 4 1
2 A2 3 2
3 A4 1 1
4 A2 3 3
5 A1 4 2
6 A4 6 2
7 A3 6 2
8 A2 1 1
9 A1 8 3
10 A3 4 1
请注意,上述解决方案保持原始行顺序。如果需要,可以在之后对其进行排序。
带有 RPostgreSQL 的 sqldf
# see FAQ #12 on the sqldf github home page for info on sqldf and PostgreSQL
# https://cran.r-project.org/web/packages/sqldf/README.html
library(RPostgreSQL)
library(sqldf)
sqldf('select
*,
rank() over (partition by "ID" order by "Value") rank
from "dt"
')
此解决方案对行重新排序。假设这没问题,因为您的示例解决方案这样做了(但如果没有将序列号列附加到 dt
并添加适当的 order by 子句以将结果重新排序回序列号顺序)。
【讨论】:
我知道这是很久以前的事了,但你能详细说明你的第一种方法吗?它似乎为我表中的每个条目给了我一个排名。我只有我想按第二个分组的列和我想在第一个参数中排名的列,就像你在这里一样。 我已经添加了一些解释和输出。【参考方案4】:您可以使用 data.table 包。
setDT(dt)
dt[, Order := rank(Value, ties.method = "first"), by = "ID"]
dt <- as.data.frame(dt)
给出所需的输出:
ID Value Order
1 A1 4 1
2 A2 3 2
3 A4 1 1
4 A2 3 3
5 A1 4 2
6 A4 6 2
7 A3 6 2
8 A2 1 1
9 A1 8 3
10 A3 4 1
【讨论】:
以上是关于在特定列上排名时如何分区?的主要内容,如果未能解决你的问题,请参考以下文章