Extraiga la fila correspondiente al valor mínimo de una variable por grupo

Deseo (1) agrupar los datos por una variable ( State ), (2) dentro de cada grupo encontrar la fila del valor mínimo de otra variable ( Employees ), y (3) extraer la fila completa.

(1) y (2) son fáciles de entender, y siento que (3) debería serlo también, pero no puedo obtenerlo.

Aquí hay un conjunto de datos de muestra:

 > data State Company Employees 1 AK A 82 2 AK B 104 3 AK C 37 4 AK D 24 5 RI E 19 6 RI F 118 7 RI G 88 8 RI H 42 data <- structure(list(State = structure(c(1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L), .Label = c("AK", "RI"), class = "factor"), Company = structure(1:8, .Label = c("A", "B", "C", "D", "E", "F", "G", "H"), class = "factor"), Employees = c(82L, 104L, 37L, 24L, 19L, 118L, 88L, 42L)), .Names = c("State", "Company", "Employees"), class = "data.frame", row.names = c(NA, -8L)) 

Calcular min por grupo es fácil, usando aggregate :

 > aggregate(Employees ~ State, data, function(x) min(x)) State Employees 1 AK 24 2 RI 19 

… o data.table :

 > library(data.table) > DT  DT[ , list(Employees = min(Employees)), by = State] State Employees 1: AK 24 2: RI 19 

Pero, ¿cómo extraigo toda la fila correspondiente a estos valores mínimos, es decir, también incluyo a la Company en el resultado?

Un poco más elegante:

 library(data.table) DT[ , .SD[which.min(Employees)], by = State] State Company Employees 1: AK D 24 2: RI E 19 

Un poco menos elegante que usar .SD , pero un poco más rápido (para datos con muchos grupos):

 DT[DT[ , .I[which.min(Employees)], by = State]$V1] 

Además, simplemente reemplace la expresión which.min(Employees) con Employees == min(Employees) , si su conjunto de datos tiene múltiples valores mínimos idénticos y desea subconjuntos de todos ellos.

Ver también Subconjunto por grupo con data.table .

Una solución dplyr :

 library(dplyr) data %>% group_by(State) %>% slice(which.min(Employees)) 

Como este es el éxito principal de Google, pensé que agregaría algunas opciones adicionales que me resultan útiles. La idea es básicamente organizar una vez por Employees y luego simplemente tomar los únicos por State

Ya sea usando data.table

 library(data.table) unique(setDT(data)[order(Employees)], by = "State") # State Company Employees # 1: RI E 19 # 2: AK D 24 

Alternativamente, también podríamos ordenar primero y luego subconjunto .SD . Ambas operaciones se optimizaron en las versiones reenviadas de data.table y el order aparentemente desencadena data.table:::forderv , mientras que .SD[1L] desencadena Gforce

 setDT(data)[order(Employees), .SD[1L], by = State, verbose = TRUE] # <- Added verbose # order optimisation is on, i changed from 'order(...)' to 'forder(DT, ...)'. # i clause present and columns used in by detected, only these subset: State # Finding groups using forderv ... 0 sec # Finding group sizes from the positions (can be avoided to save RAM) ... 0 sec # Getting back original order ... 0 sec # lapply optimization changed j from '.SD[1L]' to 'list(Company[1L], Employees[1L])' # GForce optimized j to 'list(`g[`(Company, 1L), `g[`(Employees, 1L))' # Making each group and running j (GForce TRUE) ... 0 secs # State Company Employees # 1: RI E 19 # 2: AK D 24 

O dplyr

 library(dplyr) data %>% arrange(Employees) %>% distinct(State, .keep_all = TRUE) # State Company Employees # 1 RI E 19 # 2 AK D 24 

Otra idea interesante tomada de @Khashaas awesome answer (con una pequeña modificación en forma de mult = "first" para manejar múltiples coincidencias) es encontrar primero el mínimo por grupo y luego realizar una vuelta binaria. La ventaja de esto es tanto la utilización de la función data.tables gmin (que omite la sobrecarga de evaluación) como la función de combinación binaria

 tmp <- setDT(data)[, .(Employees = min(Employees)), by = State] data[tmp, on = .(State, Employees), mult = "first"] # State Company Employees # 1: AK D 24 # 2: RI E 19 

Algunos puntos de referencia

 library(data.table) library(dplyr) library(plyr) library(stringi) library(microbenchmark) set.seed(123) N <- 1e6 data <- data.frame(State = stri_rand_strings(N, 2, '[AZ]'), Employees = sample(N*10, N, replace = TRUE)) DT <- copy(data) setDT(DT) DT2 <- copy(DT) str(DT) str(DT2) microbenchmark("(data.table) .SD[which.min]: " = DT[ , .SD[which.min(Employees)], by = State], "(data.table) .I[which.min]: " = DT[DT[ , .I[which.min(Employees)], by = State]$V1], "(data.table) order/unique: " = unique(DT[order(Employees)], by = "State"), "(data.table) order/.SD[1L]: " = DT[order(Employees), .SD[1L], by = State], "(data.table) self join (on):" = { tmp <- DT[, .(Employees = min(Employees)), by = State] DT[tmp, on = .(State, Employees), mult = "first"]}, "(data.table) self join (setkey):" = { tmp <- DT2[, .(Employees = min(Employees)), by = State] setkey(tmp, State, Employees) setkey(DT2, State, Employees) DT2[tmp, mult = "first"]}, "(dplyr) slice(which.min): " = data %>% group_by(State) %>% slice(which.min(Employees)), "(dplyr) arrange/distinct: " = data %>% arrange(Employees) %>% distinct(State, .keep_all = TRUE), "(dplyr) arrange/group_by/slice: " = data %>% arrange(Employees) %>% group_by(State) %>% slice(1), "(plyr) ddply/which.min: " = ddply(data, .(State), function(x) x[which.min(x$Employees),]), "(base) by: " = do.call(rbind, by(data, data$State, function(x) x[which.min(x$Employees), ]))) # Unit: milliseconds # expr min lq mean median uq max neval cld # (data.table) .SD[which.min]: 119.66086 125.49202 145.57369 129.61172 152.02872 267.5713 100 d # (data.table) .I[which.min]: 12.84948 13.66673 19.51432 13.97584 15.17900 109.5438 100 a # (data.table) order/unique: 52.91915 54.63989 64.39212 59.15254 61.71133 177.1248 100 b # (data.table) order/.SD[1L]: 51.41872 53.22794 58.17123 55.00228 59.00966 145.0341 100 b # (data.table) self join (on): 44.37256 45.67364 50.32378 46.24578 50.69411 137.4724 100 b # (data.table) self join (setkey): 14.30543 15.28924 18.63739 15.58667 16.01017 106.0069 100 a # (dplyr) slice(which.min): 82.60453 83.64146 94.06307 84.82078 90.09772 186.0848 100 c # (dplyr) arrange/distinct: 344.81603 360.09167 385.52661 379.55676 395.29463 491.3893 100 e # (dplyr) arrange/group_by/slice: 367.95924 383.52719 414.99081 397.93646 425.92478 557.9553 100 f # (plyr) ddply/which.min: 506.55354 530.22569 568.99493 552.65068 601.04582 727.9248 100 g # (base) by: 1220.38286 1291.70601 1340.56985 1344.86291 1382.38067 1512.5377 100 h 

La función base by es a menudo útil para trabajar con datos de bloque en data.frames. Por ejemplo

 by(data, data$State, function(x) x[which.min(x$Employees), ] ) 

Devuelve los datos en una lista, pero puede colapsar eso con

 do.call(rbind, by(data, data$State, function(x) x[which.min(x$Employees), ] )) 

plyr corregida de plyr :

 ddply(df, .(State), function(x) x[which.min(x$Employees),]) # State Company Employees # 1 AK D 24 # 2 RI E 19 

gracias a @ joel.wilson

    Intereting Posts