web-dev-qa-db-fra.com

filtre dplyr: Obtenir les lignes avec un minimum de variable, mais seulement le premier si plusieurs minima

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)

58
Felix S

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)
28
Felix S

Mettre à jour

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

Réponse originale

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)
78
docendo discimus

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]
14
Arun

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 .

4
junkka

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
0
nsr

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))
0
Kevin Mc

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. 

0
qed