2015-01-28 23 views
14

den folgenden Datenrahmen vor:Filter jeder Spalte einer data.frame basierend auf einem bestimmten Wert

df <- data.frame(replicate(5,sample(1:10,10,rep=TRUE))) 

# X1 X2 X3 X4 X5 
#1 7 9 8 4 10 
#2 2 4 9 4 9 
#3 2 7 8 8 6 
#4 8 9 6 6 4 
#5 5 2 1 4 6 
#6 8 2 2 1 7 
#7 3 8 6 1 6 
#8 3 8 5 9 8 
#9 6 2 3 10 7 
#10 2 7 4 2 9 

Mit dplyr, wie kann ich filtern, auf jede Spalte (ohne sie implizit zu Benennung), für alle Werte ich mache mehr als 2.

Etwas, das eine hypothetische filter_each(funs(. >= 2))

Gerade jetzt nachahmen würde:

df %>% filter(X1 >= 2, X2 >= 2, X3 >= 2, X4 >= 2, X5 >= 2) 

, die gleich ist:

df %>% filter(!rowSums(. < 2)) 

Hinweis: Lassen Sie uns sagen, dass ich nur auf den ersten 4 Spalten filtern wollte, würde ich tun:

df %>% filter(X1 >= 2, X2 >= 2, X3 >= 2, X4 >= 2) 

oder

df %>% filter(!rowSums(.[-5] < 2)) 

Would gibt es eine effizientere Alternative?

Edit: Unter Frage

Wie ein Spaltennamen angeben und eine hypothethical filter_each(funs(. >= 2), -X5) nachahmen?

Benchmark Unter Frage

Da ich dies auf einer großen Datenmenge laufen haben, gebenchmarkt ich die Vorschläge.

df <- data.frame(replicate(5,sample(1:10,10e6,rep=TRUE))) 

mbm <- microbenchmark(
Marat = df %>% filter(!rowSums(.[,!colnames(.) %in% "X5", drop = FALSE] < 2)), 
Richard = filter_(df, .dots = lapply(names(df)[names(df) != "X5"], function(x, y) { call(">=", as.name(x), y) }, 2)), 
Docendo = df %>% slice(which(!rowSums(select(., -matches("X5")) < 2L))), 
times = 50 
) 

Hier sind die Ergebnisse:

#Unit: milliseconds 
# expr  min  lq  mean median  uq  max neval 
# Marat 1209.1235 1320.3233 1358.7994 1362.0590 1390.342 1448.458 50 
# Richard 1151.7691 1196.3060 1222.9900 1216.3936 1256.191 1266.669 50 
# Docendo 874.0247 933.1399 983.5435 985.3697 1026.901 1053.407 50 

enter image description here

+2

Muss dplyr verwendet werden? – shecode

+1

Steven, ich denke du hast es mit 'df%>% filter (! RowSums (. <2))' –

+0

@MaratTalipov Ich denke, ja. Es wäre jedoch praktisch, nur die Spalte * name * angeben zu können, nach der nicht gefiltert werden soll. Etwas wie ein hypothetisches 'filter_each (funs (.> = 2), -X5)' –

Antwort

4

Hier ist eine weitere Option mit slice, die in diesem Fall ähnlich wie filter verwendet werden kann. Der Hauptunterschied besteht darin, dass Sie einen Ganzzahlvektor an slice übergeben, während filter einen logischen Vektor verwendet.

df %>% slice(which(!rowSums(select(., -matches("X5")) < 2L))) 

Was ich über diesen Ansatz gefällt, ist, dass, weil wir select innerhalb rowSums verwenden Sie Nutzung aller Sonderfunktionen dass select liefert, wie zum Beispiel matches machen kann.


Mal sehen, wie es zu den anderen Antworten vergleicht:

df <- data.frame(replicate(5,sample(1:10,10e6,rep=TRUE))) 

mbm <- microbenchmark(
    Marat = df %>% filter(!rowSums(.[,!colnames(.) %in% "X5", drop = FALSE] < 2)), 
    Richard = filter_(df, .dots = lapply(names(df)[names(df) != "X5"], function(x, y) { call(">=", as.name(x), y) }, 2)), 
    dd_slice = df %>% slice(which(!rowSums(select(., -matches("X5")) < 2L))), 
    times = 50L, 
    unit = "relative" 
) 

#Unit: relative 
#  expr  min  lq median  uq  max neval 
# Marat 1.304216 1.290695 1.290127 1.288473 1.290609 50 
# Richard 1.139796 1.146942 1.124295 1.159715 1.160689 50 
# dd_slice 1.000000 1.000000 1.000000 1.000000 1.000000 50 

pic

Notiz bearbeiten: mit zuverlässigem Maßstab aktualisiert mit 50 Wiederholungen (mal = 50 l).


ein Kommentar Nach der Basis R die gleiche Geschwindigkeit wie der slice Ansatz (ohne Angabe von welcher Basis R Ansatz sollte genau) haben würde, habe ich beschlossen, meine Antwort mit einem Vergleich zu aktualisieren R zur Basis fast die Verwendung von der gleiche Ansatz wie in meiner Antwort. Für Base R verwendet I:

base = df[!rowSums(df[-5L] < 2L), ], 
base_which = df[which(!rowSums(df[-5L] < 2L)), ] 

Benchmark:

df <- data.frame(replicate(5,sample(1:10,10e6,rep=TRUE))) 

mbm <- microbenchmark(
    Marat = df %>% filter(!rowSums(.[,!colnames(.) %in% "X5", drop = FALSE] < 2)), 
    Richard = filter_(df, .dots = lapply(names(df)[names(df) != "X5"], function(x, y) { call(">=", as.name(x), y) }, 2)), 
    dd_slice = df %>% slice(which(!rowSums(select(., -matches("X5")) < 2L))), 
    base = df[!rowSums(df[-5L] < 2L), ], 
    base_which = df[which(!rowSums(df[-5L] < 2L)), ], 
    times = 50L, 
    unit = "relative" 
) 

#Unit: relative 
#  expr  min  lq median  uq  max neval 
#  Marat 1.265692 1.279057 1.298513 1.279167 1.203794 50 
# Richard 1.124045 1.160075 1.163240 1.169573 1.076267 50 
# dd_slice 1.000000 1.000000 1.000000 1.000000 1.000000 50 
#  base 2.784058 2.769062 2.710305 2.669699 2.576825 50 
# base_which 1.458339 1.477679 1.451617 1.419686 1.412090 50 

pic2

Nicht wirklich besser oder vergleichbare Leistung mit diesen beiden Basis R Ansätze.

Bearbeiten Sie Anmerkung # 2: hinzugefügt Benchmark mit Basis R Optionen.

+1

Das fühlt sich natürlicher an, ist dplyrischer und ist effizienter. Wird die Benchmark in OP aktualisieren. –

+0

Das ist sehr schlau. Schöne Antwort –

+0

@ColonBeauvel, wirklich? Ich habe den Benchmark mit Base R gemacht und es war langsamer. Wäre daran interessiert zu sehen, was du meinst –

6

Hier ist eine Idee, dass es ziemlich einfach macht, die Namen zu wählen. Sie können eine Liste von Anrufen einrichten, die an das .dots-Argument von filter_() gesendet werden. Zuerst eine Funktion, die einen unbewerteten Ruf erzeugt.

Call <- function(x, value, fun = ">=") call(fun, as.name(x), value) 

Jetzt verwenden wir filter_(), eine Liste der Anrufe in das .dots Argument übergeben mit lapply(), wählen einen beliebigen Namen und Wert, den Sie möchten.

nm <- names(df) != "X5" 
filter_(df, .dots = lapply(names(df)[nm], Call, 2L)) 
# X1 X2 X3 X4 X5 
# 1 6 5 7 3 1 
# 2 8 10 3 6 5 
# 3 5 7 10 2 5 
# 4 3 4 2 9 9 
# 5 8 3 5 6 2 
# 6 9 3 4 10 9 
# 7 2 9 7 9 8 

Sie einen Blick auf die unevaluierten Anrufe von Call(), zum Beispiel X4 und X5, mit

lapply(names(df)[4:5], Call, 2L) 
# [[1]] 
# X4 >= 2L 
# 
# [[2]] 
# X5 >= 2L 

So erstellt haben, wenn Sie die names() im X Argument von lapply() einstellen, sollten Sie sein fein.

5

Wie spezifiziert man einen Spaltennamen und simuliert einen hypothetischen Filter_Each (Spaß (.> = 2), -X5)?

Es ist vielleicht nicht die eleganteste Lösung, aber es wird die Arbeit getan:

df %>% filter(!rowSums(.[,!colnames(.)%in%'X5',drop=F] < 2)) 

Bei mehreren ausgeschlossenen Spalten (zB X3, X5) kann man verwenden:

df %>% filter(!rowSums(.[,!colnames(.)%in%c('X3','X5'),drop=F] < 2)) 
+2

Es könnte schneller sein, 'Namen' anstelle von' Spaltennamen' zu verwenden, da 'Namen' primitiv sind. –

Verwandte Themen