使用每个组的最大值按组排序

  • 本文关键字:最大值 排序 r
  • 更新时间 :
  • 英文 :


有没有办法使用递减每个组的最大值和通过递减值在组内排序?

输入

x <- read.table(text = "Name Value 
A    20    
A    40   
A    35   
B    70    
B    80   
B    90    
C    10   
C    20  
C    30 ", header = T)

输出

Name Value 
B    90    
B    80   
B    70   
A    40    
A    35   
A    20    
C    30   
C    20  
C    10   

按名称分组,创建一个临时列,然后按它和值排列

df %>% group_by(Name) %>% 
mutate(mx = max(Value)) %>% 
arrange(desc(mx), desc(Value)) %>% 
select(-mx)

编辑:更正和基准

在准备基准测试期间,我注意到我的一些解决方案仅适用于 OP 提供的部分有序样本数据,但对于任意随机排列的数据却失败。我已经从我的答案中删除了这些,但它们仍然可以从历史记录中找到。

添加的基准部分比较了两个不同问题大小的基本 R、data.table和两个dpylr解决方案。


为了完整起见,这里有两个data.table和一个基本 R 解决方案:

1.data.table

library(data.table)
X <- data.table(x)
X[, tmp := max(Value), by = Name][order(-tmp, -Value)][, tmp := NULL][]
Name Value
1:    B    90
2:    B    80
3:    B    70
4:    A    40
5:    A    35
6:    A    20
7:    C    30
8:    C    20
9:    C    10

@Frank提出了一个更简洁data.table的解决方案:

X <- data.table(x)
X[order(-Value), .SD, by = Name]

这是有效的,因为在排序后,每个组中行的顺序都会保留,组的顺序也会保留。(help("data.table"by参数上(

2. 基数 R

这是对弗兰克建议的修改:

x[with(x, order(-ave(Value, Name, FUN = max), -Value)), ]
Name Value
6    B    90
5    B    80
4    B    70
2    A    40
3    A    35
1    A    20
9    C    30
8    C    20
7    C    10

基准

为了针对不同的问题大小运行基准测试,我们需要编造示例数据:

library(magrittr)
library(data.table)
# prepare data
n_rows <- 1E1
set.seed(1234L)
x0 <- data.frame(
Name = sample(LETTERS[seq_len(min(round(n_rows/3), length(LETTERS)))], n_rows, TRUE),
Value = sample(n_rows))
# coerce to data.table
X0 <- data.table(x0)

某些解决方案会就地更改数据,因此每次运行都以全新副本开始。复制操作也是计时的。

# run benchmarks
microbenchmark::microbenchmark(
copy = x <- copy(x0),
base_avg = {
x <- copy(x0)
x[with(x, order(-ave(Value, Name, FUN = max), -Value)),]
},
dt_tmp = {
X <- copy(X0)
X[, tmp := max(Value), by = Name][order(-tmp, -Value)][, tmp := NULL][]
},
dt_SD = {
X <- copy(X0)
X[order(-Value), .SD, by = Name]
},
dplyr_rt = {
copy(x0) %>% 
dplyr::group_by(Name) %>% 
dplyr::mutate(mx = max(Value)) %>% 
dplyr::arrange(desc(mx), desc(Value)) %>% 
dplyr::select(-mx)
},
dplyr_loki = {
copy(x0) %>% 
dplyr::group_by(Name) %>%  # group by Name
dplyr::mutate(NameMax = max(Value)) %>%  # create a temp variable holding the max of each Name
dplyr::arrange(desc(NameMax), desc(Value)) %>%  # arrange with two columns
dplyr::select(Name, Value) # select only the two input columns
},
times = 100L
)

以下是 10 行和 1 M 行两种不同问题大小的结果。

只有 10 行(主要是测量开销(,

Unit: microseconds
expr      min        lq       mean    median        uq       max neval  cld
copy   12.839   19.8235   23.69004   24.1660   27.9415    38.892   100 a   
base_avg  149.145  193.5115  256.32913  227.8710  243.5400  2180.155   100 a   
dt_tmp  951.883 1046.2780 1185.33735 1175.2215 1256.9685  1903.387   100  b  
dt_SD  758.184  827.2810 1386.55800  970.0065 1035.5170 40170.486   100  b  
dplyr_rt 7329.984 7700.2025 8107.00109 7925.8075 8273.7485 11631.389   100   c 
dplyr_loki 8650.386 9059.3065 9820.92511 9269.0525 9745.3710 33805.600   100    d

基 R 显然是最快的,其次是两种data.table方法,两种dplyr方法都排在最后。dplyr之间的差异很有趣。似乎dplyr::select(-mx)更快,即开销比dplyr::select(Name, Value)少,这是代码中唯一的区别。

1 M 行 (n_rows <- 1E6(,

Unit: milliseconds
expr        min         lq       mean     median         uq        max neval  cld
copy   1.378927   1.809747   2.404129   2.268131   2.374798   5.561016    11 a   
base_avg 131.783388 143.402694 207.434114 255.460485 259.142658 276.782117    11   c 
dt_tmp  70.030740  72.104982  84.694791  75.020852  76.197961 192.827694    11  b  
dt_SD  53.406217  55.149698  58.090283  58.156189  60.084684  67.835483    11  b  
dplyr_rt 752.707729 779.147098 821.327276 809.360011 878.231387 895.566707    11    d
dplyr_loki 747.559410 765.500369 792.089826 793.458180 803.056861 895.127580    11    d

这两种data.table解决方案都比基本 R 方法快约 4 倍,比dplyr解决方案快 10 倍以上,具有dt_SD优势。dplyr_rtdplyr_loki之间的巨大差异已经消失,现在看来dplyr_loki更快一些。

这是一个dplyr的解决方案。请参阅@UweBlock的答案以获取基准测试。

library(dplyr)
x %>% group_by(Name) %>%  # group by Name
mutate(NameMax = max(Value)) %>%  # create a temp variable holding the max of each Name
arrange(desc(NameMax), desc(Value)) %>%  # arrange with two columns
select(Name, Value) # select only the two input columns
#     Name Value
#    <fctr> <int>
# 1      B    90
# 2      B    80
# 3      B    70
# 4      A    40
# 5      A    35
# 6      A    20
# 7      C    30
# 8      C    20
# 9      C    10

最新更新