Cómo seleccionar la fila con el valor máximo en cada grupo

94

En un conjunto de datos con múltiples observaciones para cada sujeto, quiero tomar un subconjunto con solo el valor máximo de datos para cada registro. Por ejemplo, con el siguiente conjunto de datos:

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)

Los sujetos 1, 2 y 3 tienen el mayor valor de pt de 5, 17 y 5 respectivamente.

¿Cómo podría encontrar primero el valor de pt más grande para cada tema y luego poner esta observación en otro marco de datos? El marco de datos resultante solo debe tener los valores de pt más grandes para cada sujeto.

Xinting WANG
fuente
2
Esto está muy relacionado, pero para el mínimo en lugar del máximo stackoverflow.com/questions/24070714/…
David Arenburg

Respuestas:

96

He aquí una data.tablesolución:

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

Si desea mantener todas las entradas correspondientes a los valores máximos de ptcada grupo:

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

Si solo desea el primer valor máximo de 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

En este caso, no hace ninguna diferencia, ya que no hay varios valores máximos dentro de ningún grupo en sus datos.

Arun
fuente
2
dado que data.table ha tenido MUCHOS cambios desde 2014, ¿sigue siendo esta la mejor solución para esta pregunta?
Ben
2
@Ben, en este caso, la respuesta más rápida sigue siendo esta, sí. .SDla optimización para estos casos todavía está en la lista. Eche un vistazo al # 735 .
Arun
6
Hola, ¿Qué es $ V1 aquí? #noob
sriharsha KB
1
Accediendo a la columna con nombre automático. Ejecútelo sin él para comprender mejor.
Arun
2
@HappyCoding, eche un vistazo ?`.I`y vea si la explicación y los ejemplos allí ayudan.
Arun
63

El método más intuitivo es utilizar la función group_by y top_n en dplyr

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

El resultado que obtienes es

    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
Xi Liang
fuente
2
dplyr también es útil cuando desea acceder al valor más pequeño y más grande de un grupo porque los valores están disponibles como una matriz. Entonces, primero puede ordenar por pt descendente y luego usar pt [1] o primero (pt) para obtener el valor más alto: group %>% group_by(Subject) %>% arrange(desc(pt), .by_group = TRUE) %>% summarise(max_pt=first(pt), min_pt=last(pt), Event=first(Event))
cw '16 de
3
Esto incluirá varias filas si hay empates. Úselo slice(which.max(pt))para incluir solo una fila por grupo.
cakraww
36

Una solución más corta usando 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
Mark Chamness
fuente
4
Tenga en cuenta que esto puede ser más lento que el group[group[, .I[which.max(pt)], by=Subject]$V1]propuesto anteriormente por @Arun; ver comparaciones aquí
Valentin
1
Me gusta este porque es lo suficientemente rápido para mi contexto actual y más fácil de asimilar para mí en comparación con la .Iversión
arvi1000
setDT (grupo) [, .SD [pt == max (pt)], por = Asunto]
Ferroao
19

Otra opcion es 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
Akrun
fuente
14

Una dplyrsolucion:

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))

Esto produce el siguiente marco de datos:

  Subject max.pt
1       1      5
2       2     17
3       3      5
tkmckenzie
fuente
11
Creo que el OP quiere mantener la Eventcolumna en el subconjunto, en cuyo caso podría hacer: df %>% group_by(Subject) %>% filter(pt == max(pt))(incluye empates si está presente)
talat
8

No estaba seguro de lo que querías hacer con la columna Evento, pero si también quieres conservarla, ¿qué tal si

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

Aquí usamos avepara mirar la columna "Valor" para cada "ID". Luego determinamos qué valor es el máximo y luego lo convertimos en un vector lógico que podemos usar para crear subconjuntos de los datos originales.

MrFlick
fuente
Muchas gracias, pero tengo otra pregunta aquí. ¿Por qué usar con función en este método ya que ave (Value, ID, FUN = function (x) seq_along (x) == which.max (x)) == 1 funciona extremadamente bien? Estoy un poco confundida.
Xinting WANG
Lo usé withporque es un poco extraño tener los datos disponibles tanto dentro como fuera del groupdata.frame. Si lee los datos con read.tableo algo así, deberá usarlos withporque esos nombres de columna no estarán disponibles fuera del data.frame.
MrFlick
6
do.call(rbind, lapply(split(group,as.factor(group$Subject)), function(x) {return(x[which.max(x$pt),])}))

Usando Base R

Kalees Waran
fuente
6

Desde {dplyr} v1.0.0 (mayo de 2020) existe una nueva slice_*sintaxis que reemplaza top_n().

Consulte tambié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

Creado el 2020-08-18 por el paquete reprex (v0.3.0.9001)

Información de la sesión
sessioninfo::session_info()
#> ─ Session info ───────────────────────────────────────────────────────────────
#>  setting  value                                      
#>  version  R version 4.0.2 Patched (2020-06-30 r78761)
#>  os       macOS Catalina 10.15.6                     
#>  system   x86_64, darwin17.0                         
#>  ui       X11                                        
#>  language (EN)                                       
#>  collate  en_US.UTF-8                                
#>  ctype    en_US.UTF-8                                
#>  tz       Europe/Berlin                              
#>  date     2020-08-18                                 
#> 
#> ─ Packages ───────────────────────────────────────────────────────────────────
#>  package     * version    date       lib source                            
#>  assertthat    0.2.1      2019-03-21 [1] CRAN (R 4.0.0)                    
#>  backports     1.1.8      2020-06-17 [1] CRAN (R 4.0.1)                    
#>  blob          1.2.1      2020-01-20 [1] CRAN (R 4.0.0)                    
#>  broom         0.7.0      2020-07-09 [1] CRAN (R 4.0.2)                    
#>  cellranger    1.1.0      2016-07-27 [1] CRAN (R 4.0.0)                    
#>  cli           2.0.2      2020-02-28 [1] CRAN (R 4.0.0)                    
#>  colorspace    1.4-1      2019-03-18 [1] CRAN (R 4.0.0)                    
#>  crayon        1.3.4      2017-09-16 [1] CRAN (R 4.0.0)                    
#>  DBI           1.1.0      2019-12-15 [1] CRAN (R 4.0.0)                    
#>  dbplyr        1.4.4      2020-05-27 [1] CRAN (R 4.0.0)                    
#>  digest        0.6.25     2020-02-23 [1] CRAN (R 4.0.0)                    
#>  dplyr       * 1.0.1      2020-07-31 [1] CRAN (R 4.0.2)                    
#>  ellipsis      0.3.1      2020-05-15 [1] CRAN (R 4.0.0)                    
#>  evaluate      0.14       2019-05-28 [1] CRAN (R 4.0.0)                    
#>  fansi         0.4.1      2020-01-08 [1] CRAN (R 4.0.0)                    
#>  forcats     * 0.5.0      2020-03-01 [1] CRAN (R 4.0.0)                    
#>  fs            1.5.0      2020-07-31 [1] CRAN (R 4.0.2)                    
#>  generics      0.0.2      2018-11-29 [1] CRAN (R 4.0.0)                    
#>  ggplot2     * 3.3.2      2020-06-19 [1] CRAN (R 4.0.1)                    
#>  glue          1.4.1      2020-05-13 [1] CRAN (R 4.0.0)                    
#>  gtable        0.3.0      2019-03-25 [1] CRAN (R 4.0.0)                    
#>  haven         2.3.1      2020-06-01 [1] CRAN (R 4.0.0)                    
#>  highr         0.8        2019-03-20 [1] CRAN (R 4.0.0)                    
#>  hms           0.5.3      2020-01-08 [1] CRAN (R 4.0.0)                    
#>  htmltools     0.5.0      2020-06-16 [1] CRAN (R 4.0.1)                    
#>  httr          1.4.2      2020-07-20 [1] CRAN (R 4.0.2)                    
#>  jsonlite      1.7.0      2020-06-25 [1] CRAN (R 4.0.2)                    
#>  knitr         1.29       2020-06-23 [1] CRAN (R 4.0.2)                    
#>  lifecycle     0.2.0      2020-03-06 [1] CRAN (R 4.0.0)                    
#>  lubridate     1.7.9      2020-06-08 [1] CRAN (R 4.0.1)                    
#>  magrittr      1.5        2014-11-22 [1] CRAN (R 4.0.0)                    
#>  modelr        0.1.8      2020-05-19 [1] CRAN (R 4.0.0)                    
#>  munsell       0.5.0      2018-06-12 [1] CRAN (R 4.0.0)                    
#>  pillar        1.4.6      2020-07-10 [1] CRAN (R 4.0.2)                    
#>  pkgconfig     2.0.3      2019-09-22 [1] CRAN (R 4.0.0)                    
#>  purrr       * 0.3.4      2020-04-17 [1] CRAN (R 4.0.0)                    
#>  R6            2.4.1      2019-11-12 [1] CRAN (R 4.0.0)                    
#>  Rcpp          1.0.5      2020-07-06 [1] CRAN (R 4.0.2)                    
#>  readr       * 1.3.1      2018-12-21 [1] CRAN (R 4.0.0)                    
#>  readxl        1.3.1      2019-03-13 [1] CRAN (R 4.0.0)                    
#>  reprex        0.3.0.9001 2020-08-13 [1] Github (tidyverse/reprex@23a3462) 
#>  rlang         0.4.7      2020-07-09 [1] CRAN (R 4.0.2)                    
#>  rmarkdown     2.3.3      2020-07-26 [1] Github (rstudio/rmarkdown@204aa41)
#>  rstudioapi    0.11       2020-02-07 [1] CRAN (R 4.0.0)                    
#>  rvest         0.3.6      2020-07-25 [1] CRAN (R 4.0.2)                    
#>  scales        1.1.1      2020-05-11 [1] CRAN (R 4.0.0)                    
#>  sessioninfo   1.1.1      2018-11-05 [1] CRAN (R 4.0.2)                    
#>  stringi       1.4.6      2020-02-17 [1] CRAN (R 4.0.0)                    
#>  stringr     * 1.4.0      2019-02-10 [1] CRAN (R 4.0.0)                    
#>  styler        1.3.2.9000 2020-07-05 [1] Github (pat-s/styler@51d5200)     
#>  tibble      * 3.0.3      2020-07-10 [1] CRAN (R 4.0.2)                    
#>  tidyr       * 1.1.1      2020-07-31 [1] CRAN (R 4.0.2)                    
#>  tidyselect    1.1.0      2020-05-11 [1] CRAN (R 4.0.0)                    
#>  tidyverse   * 1.3.0      2019-11-21 [1] CRAN (R 4.0.0)                    
#>  utf8          1.1.4      2018-05-24 [1] CRAN (R 4.0.0)                    
#>  vctrs         0.3.2      2020-07-15 [1] CRAN (R 4.0.2)                    
#>  withr         2.2.0      2020-04-20 [1] CRAN (R 4.0.0)                    
#>  xfun          0.16       2020-07-24 [1] CRAN (R 4.0.2)                    
#>  xml2          1.3.2      2020-04-23 [1] CRAN (R 4.0.0)                    
#>  yaml          2.2.1      2020-02-01 [1] CRAN (R 4.0.0)                    
#> 
#> [1] /Users/pjs/Library/R/4.0/library
#> [2] /Library/Frameworks/R.framework/Versions/4.0/Resources/library
palmaditas
fuente
5

Otra solución base

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

Ordene el marco de datos por pt(descendente) y luego elimine las filas duplicadas enSubject

Mono
fuente
3

Una solución de R base más:

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

  Subject pt Event
1       1  5     2
2       2 17     2
3       3  5     2
sindri_baldur
fuente
2

Aquí hay otra data.tablesolución, ya which.maxque no funciona con personajes.

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

group[, .SD[order(pt, decreasing = TRUE) == 1], by = Subject]
Kyoma G
fuente
1

byes una versión de tapplypara marcos de datos:

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

Devuelve un objeto de clase, bypor lo que lo convertimos en un marco de datos:

do.call(rbind, b)
  Subject pt Event
1       1  5     2
2       2 17     2
3       3  5     2
Karolis Koncevičius
fuente
1

En base , puede usar avepara obtener maxpor grupo y comparar esto con pty obtener un vector lógico para subconjuntar el data.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

O compárelo ya en la función.

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
#  Subject pt Event
#3       1  5     2
#7       2 17     2
#9       3  5     2
GKi
fuente
0

Otra data.tableopcion:

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

U otro (menos legible pero un poco más rápido):

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

código de tiempo:

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

tiempos:

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
chinsoon12
fuente
0

Otra data.tablesolucion:

library(data.table)
setDT(group)[, head(.SD[order(-pt)], 1), by = .(Subject)]
Vykta Wakandigara
fuente
-1

Si desea el mayor valor de pt para un tema, simplemente puede usar:

   pt_max = as.data.frame(aggregate(pt~Subject, group, max))
Mutyalama
fuente