dplyr filter: Ruft Zeilen mit einem Minimum an Variablen ab, aber nur die ersten, wenn mehrere Minima vorhanden sind


73

Ich möchte einen gruppierten Filter so erstellen dplyr, dass innerhalb jeder Gruppe nur die Zeile zurückgegeben wird, die den Mindestwert der Variablen hat x.

Mein Problem ist: Wie erwartet werden bei mehreren Minima alle Zeilen mit dem Mindestwert zurückgegeben. In meinem Fall möchte ich die erste Zeile jedoch nur, wenn mehrere Minima vorhanden sind.

Hier ist ein Beispiel:

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

Wie erwartet werden alle Minima zurückgegeben:

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

Mit ddply hätte ich die Aufgabe folgendermaßen angegangen:

library(plyr)
ddply(df, .(A), function(z) {
    z[z$x == min(z$x), ][1, ]
})

... was funktioniert:

  A x           y
1 A 1 -1.04584335
2 B 2  0.79600971
3 C 5 -0.08655151

F: Gibt es eine Möglichkeit, dies in dplyr anzugehen? (Aus Geschwindigkeitsgründen)


5
filter(df.g, rank(x) == 1)?
Hadley

2
@FelixS, gibt rank(x)==1die gewünschten Ergebnisse?
Ricardo Saporta

4
@ Hadley, 1) Ich glaube nicht, min_rankdass hier hilft. Er braucht den ersten Min-Wert (siehe plyrLösung). 2) In jeder Programmiersprache, die Sie schreiben, ist die algorithmische Komplexität von rank(Bindungen = min, max, zuerst usw.) größer als nur das Rechnen min.
Arun

2
@Arun: True, rank(x, ties.method="first")==1funktioniert nur , da min und min_rank nicht zwischen mehreren Minima unterscheiden.
Felix S

4
@ Hadley, ich sehe immer noch nicht, wie Sie dies als which.minvorzeitige Optimierung betrachten. AFAIK ist eine natürliche Wahl, liest sich gut, ist leicht zu verstehen und schnell, da es auch O (n) ist.
Arun

Antworten:


104

Aktualisieren

Mit dplyr> = 0.3 können Sie die sliceFunktion in Kombination mit verwenden which.min, was mein Lieblingsansatz für diese Aufgabe wäre:

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

Ursprüngliche Antwort

Für die Beispieldaten können auch zwei filternacheinander verwendet werden:

group_by(df, A) %>% 
  filter(x == min(x)) %>% 
  filter(1:n() == 1)

3
Ich finde do(head)leichter zu lesen,df %>% group_by(A) %>% filter(x == min(x)) %>% do(head(.,1))
Baptiste

@baptiste das sieht in der Tat gut aus (aber wenn ich es starte, bekomme ich eine Fehlermeldung Error: expecting a single value) - weißt du warum?
Talat

Ich bin mir nicht sicher, vielleicht verwenden wir eine andere Version. Ich habedplyr_0.2, magrittr_1.0.0
Baptiste

Ok, das Problem ist, dass ich immer noch dplyr 0.1.3 verwende. Thx
Talat

1
Ich würde es vorziehen, hier verwenden zu können, top_naber aufgrund von Bindungen ist diese Methode wahrscheinlich der klare Gewinner - definitiv in Bezug auf die Leistung (im Vergleich zu arrange %>% slice).
Konrad Rudolph

37

Nur dplyrder Vollständigkeit halber: Hier ist die endgültige Lösung, abgeleitet aus den Kommentaren von @hadley und @Arun:

library(dplyr)
df.g <- group_by(df, A)
filter(df.g, rank(x, ties.method="first")==1)

16

Für das, was es wert ist, ist hier eine data.tableLösung für diejenigen, die interessiert sein könnten:

# 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]

5

Dies kann erreicht werden, indem in row_numberKombination mit verwendet wird group_by. row_numberBehandelt Bindungen, indem ein Rang nicht nur nach dem Wert, sondern auch nach der relativen Reihenfolge innerhalb des Vektors zugewiesen wird. So erhalten Sie die erste Zeile jeder Gruppe mit dem Mindestwert x:

df.g <- group_by(df, A)
filter(df.g, row_number(x) == 1)

Weitere Informationen finden Sie in der dplyr- Vignette zu Fensterfunktionen .


1

Ein anderer Weg, es zu tun:

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

Ergebnis:

  a          b
1 1 -0.8356286
2 2 -2.2146999

Könnte auch leicht angepasst werden, um die Zeile in jeder Gruppe mit maximalem Wert zu erhalten.


0

Ich mag sqldf wegen seiner Einfachheit.

sqldf("select A,min(X),y from 'df.g' group by A")

Ausgabe:

A min(X)          y

1 A      1 -1.4836989

2 B      2  0.3755771

3 C      5  0.9284441

0

Kam hierher und suchte nach einer Möglichkeit, dies mit mehr als einer zu tun. Dies wird die untersten zehn geben und die Bindungen bis zum letzten brechen, glaube ich

df.g %>%
top_n(-10,row_number(x))
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.