在特定列上排名时如何分区?

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

或者使用splitlapplydo.callrbind的基本R解决方案的所有细节:

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

【讨论】:

不错的答案,像往常一样。为了从 data.table 中获得最佳性能,最好尽可能避免使用.SD。这对于大型 data.tables 应该更快(这是您最有可能首先使用该包的地方!):DT &lt;- 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

【讨论】:

以上是关于在特定列上排名时如何分区?的主要内容,如果未能解决你的问题,请参考以下文章

选择时如何从配置单元视图中丢弃分区列?

如何在 MySql 中对分区进行排名

列中的 BigQuery 日期分区

使用 GROUP BY 时如何选择分区内的最佳行

如何在 spark scala 中覆盖特定的表分区

sci如何分区?