Je souhaite créer un filtre groupé à l'aide de dplyr
, de manière à ne renvoyer dans chaque groupe que la ligne qui a la valeur minimale de variable x
.
Mon problème est le suivant: comme prévu, dans le cas de plusieurs minima toutes les lignes avec la valeur minimale sont renvoyées. Mais dans mon cas, je ne veux que la première ligne si plusieurs minima sont présents.
Voici un exemple:
df <- data.frame(
A=c("A", "A", "A", "B", "B", "B", "C", "C", "C"),
x=c(1, 1, 2, 2, 3, 4, 5, 5, 5),
y=rnorm(9)
)
library(dplyr)
df.g <- group_by(df, A)
filter(df.g, x == min(x))
Comme prévu, tous les minima sont retournés:
Source: local data frame [6 x 3]
Groups: A
A x y
1 A 1 -1.04584335
2 A 1 0.97949399
3 B 2 0.79600971
4 C 5 -0.08655151
5 C 5 0.16649962
6 C 5 -0.05948012
Avec ddply, j'aurais abordé la tâche de cette façon:
library(plyr)
ddply(df, .(A), function(z) {
z[z$x == min(z$x), ][1, ]
})
... qui fonctionne:
A x y
1 A 1 -1.04584335
2 B 2 0.79600971
3 C 5 -0.08655151
Q: Y a-t-il un moyen d'aborder cela dans dplyr? (Pour des raisons de vitesse)
Juste pour compléter: voici la dernière solution dplyr
, dérivée des commentaires de @hadley et @Arun:
library(dplyr)
df.g <- group_by(df, A)
filter(df.g, rank(x, ties.method="first")==1)
Avec dplyr> = 0.3, vous pouvez utiliser la fonction slice
en combinaison avec which.min
, ce qui serait mon approche préférée pour cette tâche:
df %>% group_by(A) %>% slice(which.min(x))
#Source: local data frame [3 x 3]
#Groups: A
#
# A x y
#1 A 1 0.2979772
#2 B 2 -1.1265265
#3 C 5 -1.1952004
Pour les exemples de données, il est également possible d’utiliser deux filter
l’une après l’autre:
group_by(df, A) %>%
filter(x == min(x)) %>%
filter(1:n() == 1)
Pour ce que cela vaut, voici une solution data.table
, pour ceux qui pourraient être intéressés
# approach with setting keys
dt <- as.data.table(df)
setkey(dt, A,x)
dt[J(unique(A)), mult="first"]
# without using keys
dt <- as.data.table(df)
dt[dt[, .I[which.min(x)], by=A]$V1]
Ceci peut être accompli en utilisant row_number
combiné avec group_by
. row_number
traite les égalités en attribuant un rang non seulement par la valeur mais aussi par l'ordre relatif dans le vecteur. Pour obtenir la première ligne de chaque groupe avec la valeur minimale de x
:
df.g <- group_by(df, A)
filter(df.g, row_number(x) == 1)
Pour plus d'informations, voir la vignette dplyr sur les fonctions de la fenêtre .
J'aime sqldf pour sa simplicité.
sqldf("select A,min(X),y from 'df.g' group by A")
Sortie:
A min(X) y
1 A 1 -1.4836989
2 B 2 0.3755771
3 C 5 0.9284441
Je suis venu chercher un moyen de faire cela avec plus d’un. Cela donnera les dix derniers, brisant les liens par un dernier, je crois
df.g %>%
top_n(-10,row_number(x))
Une autre façon de le faire:
set.seed(1)
x <- data.frame(a = rep(1:2, each = 10), b = rnorm(20))
x <- dplyr::arrange(x, a, b)
dplyr::filter(x, !duplicated(a))
Résultat:
a b
1 1 -0.8356286
2 2 -2.2146999
Pourrait également être facilement adapté pour obtenir la ligne dans chaque groupe avec une valeur maximale.