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 min
nach 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 min
Werten entspricht, dh auch Company
im Ergebnis?
quelle
Hier eine
dplyr
Lösung (Beachten Sie, dass ich kein regulärer Benutzer bin):quelle
group_by(data, State) %>% slice_min(order_by = Employees)
.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
Employees
und 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 undorder
ist scheinbar Auslöserdata.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.tablesgmin
Funktion (die die Auswertung Kopfüberspringt) und die binären Join - Funktiontmp <- 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
quelle
data.table
.Die Basisfunktion
by
ist häufig nützlich, um mit Blockdaten in data.frames zu arbeiten. Zum Beispielby(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), ] ))
quelle
Korrigierte
plyr
Lö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
quelle
In der Basis können Sie verwenden
ave
, ummin
pro Gruppe zu erhalten und dies mit zu vergleichenEmployees
und einen logischen Vektor zu erhalten, um die zu unterteilendata.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
quelle