按组计算排名

14

我有一个带有分组变量'ID'和一些值('Value')的数据框:

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

我可以这样计算“Value”列的总体排名:

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”内的排名顺序,而不是全局排名顺序呢?

#    ID Value  rnk
# 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

在 T-SQL 中,我们可以使用以下语法来完成此操作:

RANK() OVER ( [ < partition_by_clause > ] < order_by_clause > )

有什么想法吗?

4个回答

14

有很多选择。

如果性能是一个问题(即数据非常大),请使用data.table包:

library(data.table)
setDT(dt)
# or: dt <- as.data.table(dt)
dt[ , Order := frank(Value, ties.method = "first"), by = ID]

#          ID Value Order
#      <char> <num> <int>
#  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

请查看?frank了解其他的排序方法,比如"dense"


dplyr:

library(dplyr)
dt %>% group_by(ID) %>% mutate(rnk = row_number(Value))

来自?ranking:

row_number(): 等同于 rank(ties.method = "first")

dplyr 还有其他几个排名函数,例如 dense_rank


或者使用 split, lapply, do.callrbindbase R解决方案(包含所有详细信息):

do.call(rbind, lapply(split(dt, dt$ID), transform,
              Order = rank(Value, ties.method = "first")))

使用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 在旧包版本中的替代方案。


6
以下是几种方法: ave:它会对每个相同ID的数值集合单独应用排序。不使用任何包。
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中添加一个序列号列,并添加适当的排序顺序将结果重新排序回序列号顺序)。


我知道这是很久以前的事了,但你能详细说明一下你的第一种方法吗?它似乎为我的表中的每个条目都给出了一个排名为一。就像你在这里一样,我只有将我想要分组的列放在第二个参数中,将我想要排名的列放在第一个参数中。 - Kory
我已经添加了一些解释和输出。 - G. Grothendieck

4

这是我的方法,但可能有更好的方法。我从来没有使用过排名,甚至不知道它的存在。谢谢,这可能很有用。

#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

0
你可以使用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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接