Extrahieren Sie die Zeile, die dem Mindestwert einer Variablen entspricht, nach Gruppe

73

Ich möchte (1) Daten nach einer Variablen gruppieren ( State), (2) innerhalb jeder Gruppe die Zeile mit dem Mindestwert einer anderen Variablen finden ( Employees) und (3) die gesamte Zeile extrahieren.

(1) und (2) sind einfache Einzeiler, und ich denke, (3) sollte es auch sein, aber ich kann es nicht verstehen.

Hier ist ein Beispieldatensatz:

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

Das Berechnen minnach Gruppen ist einfach mit aggregate:

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

... oder data.table:

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

Aber wie extrahiere ich die gesamte Zeile, die diesen minWerten entspricht, dh auch Companyim Ergebnis?

Ed Swindelles
quelle

Antworten:

64

Etwas eleganter:

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

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

Etwas weniger elegant als die Verwendung .SD, aber etwas schneller (für Daten mit vielen Gruppen):

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

Auch ersetzen Sie einfach den Ausdruck which.min(Employees)mit Employees == min(Employees), wenn Ihr Datensatz mehrere identische min Werte hat und Sie möchten , dass sie alle der Teilmenge.

Siehe auch Teilmengenzeilen, die dem Maximalwert nach Gruppe mit data.table entsprechen .

Señor O.
quelle
Wie bekomme ich es, wenn die Nummer das Minimum zurückgeben soll, aber basierend auf der Häufigkeit, mit der diese Nummer verwendet wurde?
Abhishek Singh
57

Hier eine dplyrLösung (Beachten Sie, dass ich kein regulärer Benutzer bin):

library(dplyr)    
data %>% 
    group_by(State) %>% 
    slice(which.min(Employees))
Agstudy
quelle
1
Mit dplyr 1.0.0 können Sie dasselbe auf folgende Weise erreichen : group_by(data, State) %>% slice_min(order_by = Employees).
Jazzurro
32

Da dies Googles Top-Hit ist, dachte ich, ich würde einige zusätzliche Optionen hinzufügen, die ich nützlich finde. Die Idee ist im Grunde, einmal vorbei zu arrangieren Employeesund dann einfach die Unikate per zu nehmenState

Entweder mit data.table

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

Alternativ könnten wir auch zuerst bestellen und dann unterteilen .SD. Diese beiden Operationen wurden in den resent data.table Versionen optimiert und orderist scheinbar Auslöser data.table:::forderv, während .SD[1L]TriggerGforce

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

Oder dplyr

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

Eine weitere interessante Idee, die von @Khashaas fantastischer Antwort entlehnt wurde (mit einer kleinen Änderung in Form von mult = "first", um mehrere Übereinstimmungen zu verarbeiten), besteht darin, zuerst das Minimum pro Gruppe zu finden und dann einen binären Join-Back durchzuführen. Der Vorteil dabei ist , sowohl die Nutzung von data.tables gminFunktion (die die Auswertung Kopfüberspringt) und die binären Join - Funktion

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

Einige Benchmarks

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
David Arenburg
quelle
Schön gemacht. Nur der Vollständigkeit halber habe ich eine korrekte Plyr-Lösung bereitgestellt. Fühlen Sie sich frei, in Benchmark aufzunehmen ... Ich erwarte jedoch nicht, dass es standhalten wird data.table.
C8H10N4O2
1
@ C8H10N4O2 Aktualisiert.
David Arenburg
8

Die Basisfunktion byist häufig nützlich, um mit Blockdaten in data.frames zu arbeiten. Zum Beispiel

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

Es gibt zwar die Daten in einer Liste zurück, aber Sie können diese mit reduzieren

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

Korrigierte plyrLösung:

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

danke an @ joel.wilson

C8H10N4O2
quelle
1

In der Basis können Sie verwenden ave, um minpro Gruppe zu erhalten und dies mit zu vergleichen Employeesund einen logischen Vektor zu erhalten, um die zu unterteilen data.frame.

data[data$Employees == ave(data$Employees, data$State, FUN=min),]
#  State Company Employees
#4    AK       D        24
#5    RI       E        19

Oder vergleichen Sie es bereits in der Funktion.

data[as.logical(ave(data$Employees, data$State, FUN=function(x) x==min(x))),]
#data[ave(data$Employees, data$State, FUN=function(x) x==min(x))==1,] #Variant
#  State Company Employees
#4    AK       D        24
#5    RI       E        19
GKi
quelle