Extraire la ligne correspondant à la valeur minimale d'une variable par groupe

Je souhaite (1) regrouper les données par une variable (State), (2) dans chaque groupe trouver la ligne de valeur minimale d'une autre variable (Employees), et (3) extraire la ligne entière.

(1) et (2) sont des one-liners faciles, et j'ai l'impression que (3) devrait l'être aussi, mais je ne peux pas l'obtenir.

Voici un exemple de jeu de données:

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

Calculer min par groupe est facile, en utilisant aggregate:

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

...ou data.table:

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

Mais comment extraire la ligne entière correspondant à ces min valeurs, c'est-à-dire incluant également Company dans le résultat?

45
demandé sur ekad 2014-06-06 01:45:16

5 réponses

Légèrement plus élégant:

library(data.table)
DT[ , .SD[which.min(Employees)], by = State]

   State Company Employees
1:    AK       D        24
2:    RI       E        19

Légèrement moins élégant que d'utiliser .SD, mais un peu plus rapide (pour les données avec de nombreux groupes):

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

En outre, remplacez simplement l'expression which.min(Employees) par Employees == min(Employees), si votre ensemble de données a plusieurs valeurs min identiques et que vous souhaitez les sous-estimer toutes.

Voir aussi sous-ensemble par groupe avec des données.tableau.

40
répondu Señor O 2017-05-23 12:18:16

Un dplyr solution :

library(dplyr)    
data %>% 
    group_by(State) %>% 
    slice(which.min(Employees))
36
répondu agstudy 2016-03-20 21:46:55

Comme C'est le meilleur succès de Google, j'ai pensé ajouter quelques options supplémentaires que je trouve utiles de savoir. L'idée est d'organiser une fois par Employees, puis il suffit de prendre les uniques par State

Soit en utilisant data.table

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

Alternativement, nous pourrions aussi d'abord commander et ensuite sous-ensemble .SD. Ces deux opérations ont été optimisées dans les données renvoyées.les versions de table et {[9] } sont apparemment déclencheurs data.table:::forderv, tandis que .SD[1L] déclenche 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

Ou dplyr

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

Une autre idée intéressante empruntée à @ Khashaas awesome answer (avec une petite modification sous la forme de mult = "first" afin de gérer plusieurs correspondances) est de trouver d'abord un minimum par groupe, puis d'effectuer une jointure binaire. L'avantage, c'est à la fois l'utilisation des données.tables gmin fonction (qui ignore la surcharge d'évaluation) et la fonctionnalitéBinary join

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

Quelques repères

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, '[A-Z]'),
                   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
20
répondu David Arenburg 2017-05-23 12:18:16

La fonction de base by est souvent utile pour travailler avec des données de bloc de données.cadre. Par exemple

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

Retourne les données dans une liste, mais vous pouvez réduire qu'avec

do.call(rbind, by(data, data$State, function(x) x[which.min(x$Employees), ] ))
7
répondu MrFlick 2014-06-05 23:42:21

Corrigé plyr solution:

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

Merci à @ joel.wilson

2
répondu C8H10N4O2 2017-05-23 12:18:16