Je souhaite (1) grouper 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 le ligne entière.
(1) et (2) sont simples, et je pense 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 puis-je extraire la ligne entière correspondant à ces valeurs min
, c’est-à-dire inclure également Company
dans le résultat?
Un peu plus élégant:
library(data.table)
DT[ , .SD[which.min(Employees)], by = State]
State Company Employees
1: AK D 24
2: RI E 19
Un peu moins élégant que l’utilisation de .SD
, Mais un peu plus rapide (pour les données contenant 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 contient plusieurs valeurs min identiques et que vous souhaitez toutes les sous-définir.
Voir aussi Sous-ensemble par groupe avec data.table .
Une solution dplyr
:
library(dplyr)
data %>%
group_by(State) %>%
slice(which.min(Employees))
Comme il s’agit là du succès de Google, j’ai pensé ajouter quelques options supplémentaires que je trouve utile de connaître. L'idée est fondamentalement d'organiser une fois par Employees
et ensuite 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, puis sous-ensemble .SD
. Ces deux opérations ont été optimisées dans les versions précédentes de data.table et order
semble déclencher apparemment 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 géniale réponse (avec une petite modification sous la forme de mult = "first"
afin de gérer plusieurs correspondances) consiste à rechercher d’abord un minimum par groupe, puis à effectuer une jointure binaire. L'avantage de ceci est à la fois l'utilisation de la fonction data.tables gmin
(qui évite la surcharge de l'é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
La fonction de base by
est souvent utile pour utiliser des données de bloc dans data.frames. Par exemple
by(data, data$State, function(x) x[which.min(x$Employees), ] )
Il renvoie les données dans une liste, mais vous pouvez le réduire avec
do.call(rbind, by(data, data$State, function(x) x[which.min(x$Employees), ] ))
Solution corrigée plyr
:
ddply(df, .(State), function(x) x[which.min(x$Employees),])
# State Company Employees
# 1 AK D 24
# 2 RI E 19