选择每个组中具有最大值的行

Select the row with the maximum value in each group

提问人:Xinting WANG 提问时间:7/3/2014 最后编辑:HenrikXinting WANG 更新时间:4/24/2023 访问量:190845

问:

在每个主题具有多个观测值的数据集中。对于每个主题,我想选择最大值为“pt”的行。例如,使用以下数据集:

ID    <- c(1,1,1,2,2,2,2,3,3)
Value <- c(2,3,5,2,5,8,17,3,5)
Event <- c(1,1,2,1,2,1,2,2,2)

group <- data.frame(Subject=ID, pt=Value, Event=Event)
#   Subject pt Event
# 1       1  2     1
# 2       1  3     1
# 3       1  5     2 # max 'pt' for Subject 1
# 4       2  2     1
# 5       2  5     2
# 6       2  8     1
# 7       2 17     2 # max 'pt' for Subject 2
# 8       3  3     2
# 9       3  5     2 # max 'pt' for Subject 3

受试者 1、2 和 3 的最大 pt 值分别为 5、17 和 5。

我怎样才能首先找到每个受试者的最大pt值,然后将这个观察结果放在另一个数据框中?生成的数据框应仅具有每个主题的最大 pt 值。

数据帧 R-常见问题解答

评论

3赞 David Arenburg 2/13/2017
这是非常密切相关的,但针对的是最小而不是最大 stackoverflow.com/questions/24070714/......
0赞 Henrik 10/18/2017
相关新闻: 使用 data.table 按组划分子集

答:

8赞 MrFlick 7/4/2014 #1

我不确定你想对事件列做什么,但如果你也想保留它,怎么样

isIDmax <- with(dd, ave(Value, ID, FUN=function(x) seq_along(x)==which.max(x)))==1
group[isIDmax, ]

#   ID Value Event
# 3  1     5     2
# 7  2    17     2
# 9  3     5     2

在这里,我们习惯于查看每个“ID”的“值”列。然后,我们确定哪个值是最大值,然后将其转换为可用于子集原始 data.frame 的逻辑向量。ave

评论

0赞 Xinting WANG 7/4/2014
非常感谢,但我在这里还有另一个问题。为什么在此方法中使用函数,因为 ave(Value, ID, FUN=function(x) seq_along(x)==which.max(x))==1 效果非常好?我有点困惑。
0赞 MrFlick 7/4/2014
我之所以使用,是因为在 data.frame 内部和外部都提供数据有点奇怪。如果读入数据 with 或其他内容,则需要使用,因为这些列名在 data.frame 之外不可用。withgroupread.tablewith
19赞 tkmckenzie 7/4/2014 #2

解决方案:dplyr

library(dplyr)
ID <- c(1,1,1,2,2,2,2,3,3)
Value <- c(2,3,5,2,5,8,17,3,5)
Event <- c(1,1,2,1,2,1,2,2,2)
group <- data.frame(Subject=ID, pt=Value, Event=Event)

group %>%
    group_by(Subject) %>%
    summarize(max.pt = max(pt))

这将生成以下数据框:

  Subject max.pt
1       1      5
2       2     17
3       3      5

评论

14赞 talat 7/4/2014
我认为 OP 希望将列保留在子集中,在这种情况下您可以这样做:(如果存在,包括领带)Eventdf %>% group_by(Subject) %>% filter(pt == max(pt))
139赞 Arun 7/4/2014 #3

这是一个解决方案:data.table

require(data.table) ## 1.9.2
group <- as.data.table(group)

如果要保留每个组中与最大值对应的所有条目:pt

group[group[, .I[pt == max(pt)], by=Subject]$V1]
#    Subject pt Event
# 1:       1  5     2
# 2:       2 17     2
# 3:       3  5     2

如果您只想要第一个最大值:pt

group[group[, .I[which.max(pt)], by=Subject]$V1]
#    Subject pt Event
# 1:       1  5     2
# 2:       2 17     2
# 3:       3  5     2

在这种情况下,这不会有什么区别,因为数据中的任何组中都没有多个最大值。

评论

4赞 Ben 5/25/2016
鉴于 data.table 自 2014 年以来发生了很多变化,这仍然是这个问题的最快/最佳解决方案吗?
2赞 Arun 5/25/2016
@Ben,在这种情况下,最快的答案仍然是这个,是的。 针对这些情况的优化仍在列表中。关注#735.SD
8赞 sriharsha KB 6/1/2016
嗨,这里的 $V 1 是什么?#noob
1赞 Arun 6/1/2016
访问自动命名的列。在没有它的情况下运行它以更好地理解。
2赞 Arun 6/30/2017
@HappyCoding,看看那里的解释和例子是否有帮助??`.I`
55赞 Mark Chamness 4/8/2015 #4

使用以下更短的解决方案:data.table

setDT(group)[, .SD[which.max(pt)], by=Subject]
#    Subject pt Event
# 1:       1  5     2
# 2:       2 17     2
# 3:       3  5     2

评论

8赞 Valentin_Ștefan 1/22/2019
请注意,这可能比@Arun上面建议的要慢;在此处查看比较group[group[, .I[which.max(pt)], by=Subject]$V1]
1赞 arvi1000 7/20/2019
我喜欢这个,因为它对于我当前的上下文来说足够快,并且与版本相比更容易为我摸索.I
0赞 Ferroao 2/21/2020
setDT(group)[, .SD[ pt== max(pt) ] , by=主题]
0赞 Herman Toothrot 7/21/2022
如何排除具有两个最大值的情况?
0赞 Daniel V 5/11/2023
对不起,坏死,但对于未来的用户:当有多个条目时,将采取第一个条目。setDT(group)[, .SD[which.max(pt)][1], by=Subject]
117赞 Xi Liang 2/18/2016 #5

最直观的方法就是在group_bytop_ndplyr

group %>% group_by(Subject) %>% top_n(1, pt)

你得到的结果是

Source: local data frame [3 x 3]
Groups: Subject [3]

  Subject    pt Event
    (dbl) (dbl) (dbl)
1       1     5     2
2       2    17     2
3       3     5     2

评论

2赞 cw' 1/16/2019
当您想要访问组中的最小值和最大值时,dplyr 也很有用,因为这些值可以作为数组使用。因此,您可以先按 pt 降序排序,然后使用 pt[1] 或 first(pt) 来获取最大值:group %>% group_by(Subject) %>% arrange(desc(pt), .by_group = TRUE) %>% summarise(max_pt=first(pt), min_pt=last(pt), Event=first(Event))
13赞 cakraww 7/18/2019
如果有平局,这将包括多行。用于每个组仅包含一行。slice(which.max(pt))
31赞 akrun 2/12/2017 #6

另一种选择是slice

library(dplyr)
group %>%
     group_by(Subject) %>%
     slice(which.max(pt))
#    Subject    pt Event
#    <dbl> <dbl> <dbl>
#1       1     5     2
#2       2    17     2
#3       3     5     2

使用 1.1.0dplyr

slice_max(group, pt, by = 'Subject')
12赞 Kalees Waran 7/25/2017 #7
do.call(rbind, lapply(split(group,as.factor(group$Subject)), function(x) {return(x[which.max(x$pt),])}))

使用底座R

-1赞 Mutyalama 7/9/2018 #8

如果您想要主题的最大 pt 值,您可以简单地使用:

   pt_max = as.data.frame(aggregate(pt~Subject, group, max))
2赞 Kyoma G 4/18/2019 #9

这是另一种解决方案,因为不适用于角色data.tablewhich.max

library(data.table)
group <- data.table(Subject=ID, pt=Value, Event=Event)

group[, .SD[order(pt, decreasing = TRUE) == 1], by = Subject]
6赞 Ape 7/29/2019 #10

另一个基本解决方案

group_sorted <- group[order(group$Subject, -group$pt),]
group_sorted[!duplicated(group_sorted$Subject),]

# Subject pt Event
#       1  5     2
#       2 17     2
#       3  5     2

按(降序)对数据框进行排序,然后删除ptSubject

10赞 s_baldur 12/29/2019 #11

另一个基本 R 解决方案:

merge(aggregate(pt ~ Subject, max, data = group), group)

  Subject pt Event
1       1  5     2
2       2 17     2
3       3  5     2
0赞 chinsoon12 4/6/2020 #12

另一种选择:data.table

library(data.table)
setDT(group)
group[group[order(-pt), .I[1L], Subject]$V1]

或者另一个(可读性较差但速度稍快):

group[group[, rn := .I][order(Subject, -pt), {
    rn[c(1L, 1L + which(diff(Subject)>0L))]
}]]

时序代码:

library(data.table)
nr <- 1e7L
ng <- nr/4L
set.seed(0L)
DT <- data.table(Subject=sample(ng, nr, TRUE), pt=1:nr)#rnorm(nr))
DT2 <- copy(DT)


microbenchmark::microbenchmark(times=3L,
    mtd0 = {a0 <- DT[DT[, .I[which.max(pt)], by=Subject]$V1]},
    mtd1 = {a1 <- DT[DT[order(-pt), .I[1L], Subject]$V1]},
    mtd2 = {a2 <- DT2[DT2[, rn := .I][
        order(Subject, -pt), rn[c(TRUE, diff(Subject)>0L)]
    ]]},
    mtd3 = {a3 <- unique(DT[order(Subject, -pt)], by="Subject")}
)
fsetequal(a0[order(Subject)], a1[order(Subject)])
#[1] TRUE
fsetequal(a0[order(Subject)], a2[, rn := NULL][order(Subject)])
#[1] TRUE
fsetequal(a0[order(Subject)], a3[order(Subject)])
#[1] TRUE

计时:

Unit: seconds
 expr      min       lq     mean   median       uq      max neval
 mtd0 3.256322 3.335412 3.371439 3.414502 3.428998 3.443493     3
 mtd1 1.733162 1.748538 1.786033 1.763915 1.812468 1.861022     3
 mtd2 1.136307 1.159606 1.207009 1.182905 1.242359 1.301814     3
 mtd3 1.123064 1.166161 1.228058 1.209257 1.280554 1.351851     3
1赞 Vykta Wakandigara 4/27/2020 #13

另一种解决方案:data.table

library(data.table)
setDT(group)[, head(.SD[order(-pt)], 1), by = .(Subject)]
1赞 Karolis Koncevičius 4/28/2020 #14

by是 for data frames 的一个版本:tapply

res <- by(group, group$Subject, FUN=function(df) df[which.max(df$pt),])

它返回一个类对象,因此我们将其转换为数据帧:by

do.call(rbind, b)
  Subject pt Event
1       1  5     2
2       2 17     2
3       3  5     2
4赞 GKi 5/18/2020 #15

base 中,您可以使用获取每个组并将其与此进行比较,并获取逻辑向量来子集 .avemaxptdata.frame

group[group$pt == ave(group$pt, group$Subject, FUN=max),]
#  Subject pt Event
#3       1  5     2
#7       2 17     2
#9       3  5     2

或者另外使用.with

group[with(group, pt == ave(pt, Subject, FUN=max)),]

或者已经在函数中进行比较。

group[as.logical(ave(group$pt, group$Subject, FUN=function(x) x==max(x))),]
#group[ave(group$pt, group$Subject, FUN=function(x) x==max(x))==1,] #Variant
24赞 pat-s 8/18/2020 #16

从 {dplyr} v1.0.0(2020 年 5 月)开始,有新的语法取代了 .slice_*top_n()

另请参阅 https://dplyr.tidyverse.org/reference/slice.html

library(tidyverse)

ID    <- c(1,1,1,2,2,2,2,3,3)
Value <- c(2,3,5,2,5,8,17,3,5)
Event <- c(1,1,2,1,2,1,2,2,2)

group <- data.frame(Subject=ID, pt=Value, Event=Event)

group %>% 
  group_by(Subject) %>% 
  slice_max(pt)
#> # A tibble: 3 x 3
#> # Groups:   Subject [3]
#>   Subject    pt Event
#>     <dbl> <dbl> <dbl>
#> 1       1     5     2
#> 2       2    17     2
#> 3       3     5     2

reprex 软件包 (v0.3.0.9001) 于 2020-08-18 创建

0赞 Zoë Turner 12/7/2020 #17

使用 dplyr 1.0.2 现在有两种方法可以做到这一点,一种是长手,另一种是使用动词 across():

      # create data
      ID    <- c(1,1,1,2,2,2,2,3,3)
      Value <- c(2,3,5,2,5,8,17,3,5)
      Event <- c(1,1,2,1,2,1,2,2,2)
      
      group <- data.frame(Subject=ID, pt=Value, Event=Event)

长手动词是 max(),但请注意 na.rm = TRUE,这对于存在 NA 的示例很有用,如封闭式问题:合并数据帧中的行,其中行不相交且包含 NA

       group %>% 
        group_by(Subject) %>% 
        summarise(pt = max(pt, na.rm = TRUE),
                  Event = max(Event, na.rm = TRUE))

如果只有几列,这是可以的,但如果表中有很多列 cross() 很有用。这个动词的例子通常带有 summarise(across(start_with...但在此示例中,列不以相同的字符开头。可以更改它们或列出位置:

    group %>% 
        group_by(Subject) %>% 
        summarise(across(1:ncol(group)-1, max, na.rm = TRUE, .names = "{.col}"))

请注意,动词 across() 1 是指第一个实际列之后的第一列,因此使用 ncol(group) 不起作用,因为列太多(使其位置为 4 而不是 3)。

0赞 U W 1/5/2023 #18

我有时会依靠围绕函数的解决方案,因为这适用于任何类型的列(即不仅仅是数字)。基本上,按您想要的最大值或最小值对表格进行排序,然后取每组的第一组或最后一组。order

我还发现这对于初学者来说相当容易阅读。data.table

library(data.table)

setDT(group)[order(pt)][, last(.SD), by = Subject]

请注意,替换为可实现最小值的等效值。lastfirst

0赞 Maël 2/2/2023 #19

使用 ,您可以使用 with 来执行内联分组切片:dplyr 1.1.0slice_maxby

library(dplyr)
group %>% 
  slice_max(pt, n = 1, by = Subject)

#  Subject pt Event
#1       1  5     2
#2       2 17     2
#3       3  5     2