2016-04-03 6 views
8

dieses Beispiel Daten vor:neuen Variable nur für eine Teilmenge der Daten definierte erstellen mit `dplyr`

set.seed(1234567) 
mydf <- data.frame(var1 = runif(10), var2 = c(runif(5), rep(NA, 5))) 

Und diese vektorisiert Beispiel Funktion, die leider einen Fehler auslöst, wenn eines des Arguments ist NA

myfn <- function(x, y){ 
    sum(x:y) 
} 
myfn <- Vectorize(myfn) 

nun in der Mitte einer dplyr Kette brauche ich eine neue Variable erstellen myfn verwenden. Diese neue Variable (var3) wird nur definiert, wenn var1 und var2 nicht NA sind.

Die gebräuchlichste Lösung für ähnliche Situationen ist also ifelse. Etwas wie das.

mydf %>% 
    mutate(var3 = ifelse(
     test = is.na(var2), 
     yes = NA, 
     no = myfn(var1, var2))) 

Aber nicht in meinem Fall arbeiten, weil ifelse sowieso passiert eigentlich den ganzen Vektor var1 und var2 zu myfn und nicht nur den Untervektor, wenn testFALSE ist. Und es bricht alles, weil myfn bricht, wann immer eine NA erhält.

Also, was ist die clevere dplyr Lösung dafür? (Kann ich mir vorstellen viele Lösungen für diese ohne dplyr, aber ich bin nur Interesse an einer dplyr -friendly Lösung)

Es fiel mir ein, dass filter helfen könnte, und arbeitet in der Tat mit einem sehr gut lesbar und dplyr y Code

mydf %>% 
    filter(!is.na(var2)) %>% 
    mutate(var3 = myfn(var1, var2)) 

     var1  var2  var3 
1 0.56226084 0.62588794 0.56226084 
2 0.72649850 0.24145251 0.72649850 
3 0.91524985 0.03768974 0.91524985 
4 0.02969437 0.51659297 0.02969437 
5 0.76750970 0.81845788 0.76750970 

Aber dann hätte ich diese in einem temporären Objekt speichern, dann var3 in den Originaldaten erstellen, die alle mit NA und alle wieder zusammen in den gleichen Daten setzen ('verursachen, soweit ich das unfilter wissen, dass einige haben suggested existiert nicht, ..., noch).

Also nur die Ausgabe zu veranschaulichen, was ich will, dieser Code erzeugt es (kein dplyr überhaupt verwendet wird):

mydf$var3 <- NA 
index <- !is.na(mydf$var2) 
mydf$var3[index] <- myfn(mydf$var1[index], mydf$var2[index]) 
mydf 

> mydf 
     var1  var2  var3 
1 0.56226084 0.62588794 0.56226084 
2 0.72649850 0.24145251 0.72649850 
3 0.91524985 0.03768974 0.91524985 
4 0.02969437 0.51659297 0.02969437 
5 0.76750970 0.81845788 0.76750970 
6 0.48005398   NA   NA 
7 0.08837960   NA   NA 
8 0.86294587   NA   NA 
9 0.49660306   NA   NA 
10 0.85350403   NA   NA 

EDIT:

I @ krlmlr Lösung akzeptiert, weil es, was ich ist war auf der Suche nach: klaren, leicht lesbaren und prägnanten Code, der sich mühelos in eine dplyr-Kette integrieren lässt. Für mein Beispiel sieht diese Lösung so aus.

mydf %>% 
     rowwise %>% 
     mutate(var3 = if(is.na(var2)) NA else myfn(var1, var2)) 

Doch wie @krlmlr wies in seiner Antwort aus, Zeile für Zeile zu betreiben eine Kosten in Bezug auf die Leistung hat. Für kleine Datensätze oder einmalige Operationen ist es möglicherweise nicht von Bedeutung, aber für größere Datenmengen oder die millionenfache Wiederholung der Operation könnte es beträchtlich sein. Zur Veranschaulichung, hier ist ein Vergleich mit microbenchmark und drei Lösungen (Basis, dyplr und data.table) über einen etwas größeren Datensatz (nicht massiv oder irgendetwas, nur 1000 Zeilen statt 10 in meinem ursprünglichen Beispiel) angewendet.

library(data.table) 
library(dplyr) 

set.seed(1234567) 
mydf <- data.frame(var1 = runif(1000), var2 = c(runif(500), rep(NA, 500))) 

myfn <- function(x, y){ 
    sum(x:y) 
} 
myfn <- Vectorize(myfn) 

using_base <- function(){ 
    mydf$var3 <- NA 
    index <- !is.na(mydf$var2) 
    mydf$var3[index] <- myfn(mydf$var1[index], mydf$var2[index]) 
} 

using_dplyr <- function(){ 
    mydf <- mydf %>% 
     rowwise %>% 
     mutate(var3 = if(is.na(var2)) NA else myfn(var1, var2)) 
} 

using_datatable <- function(){ 
    setDT(mydf)[!is.na(var2), var3 := myfn(var1, var2)] 
} 

library(microbenchmark) 
mbm <- microbenchmark(
    using_base(), using_dplyr(), using_datatable(), 
    times = 1000) 

library(ggplot2) 
autoplot(mbm) 

enter image description here

Und wie Sie sehen können, die dplyr Lösung rowwise mit wesentlich langsamer als seine base und data.table Rivalen.

+0

Ihre Funktion ist nur das Kopieren von nicht 'NA' Werte von' var1' in 'var3', ist, dass beabsichtigt? – mtoto

+0

ist eine Beispielfunktion. Das ist nicht meine eigentliche Funktion. Es ist nur ein Beispiel, um hier einen kurzen reproduzierbaren Code zur Verfügung zu stellen, der das Problem veranschaulicht. – elikesprogramming

+2

Wie wäre es, Ihre Funktion so zu reparieren, dass sie nicht bricht, wenn sie NA empfängt? – krlmlr

Antwort

2

Wenn Ihre ursprüngliche Funktion nicht vektorisiert und mit bestimmten Eingaben nicht bewältigen kann, gibt es keinen Leistungsvorteil es Vectorize() Verwendung in Vektorisierung. Verwenden Sie stattdessen dplyr::rowwise() Zeile für Zeile zu bedienen:

iris %>% 
    rowwise %>% 
    mutate(x = if (Sepal.Length < 5) 1 else NA) %>% 
    ungroup 

Beachten Sie, dass if hier mit absolut sicher ist, wie die Eingabelänge hat 1.

+0

danke,' rowwise' ist eine gute Idee. Aber ich habe es nur mit 'do' benutzt. Ich werde versuchen, Sie wissen zu lassen, wie es geht. ... unterdessen habe ich einfach Ihren Code kopiert, um die Ausgabe zu sehen, aber es wirft diesen Fehler auf "Fehler: inkompatible Typen, erwarte einen numerischen Vektor", ..., ich habe nicht untersucht, was dort vor sich geht (später Ich tue es), aber wenn Sie dies vorher lesen, wäre es nett zu hören, was den Fehler verursacht – elikesprogramming

+2

Um den Fehler zu vermeiden, verwenden Sie 'NA_real_' anstelle von' NA'. – eipi10

+0

@elikesprogramming: Ich verwende die dev-Version von dplyr, der Fehler tritt hier nicht auf. Andernfalls ist 'NA_real_' eine sichere Option. – krlmlr

5

Sie könnten vielleicht in Betracht ziehen, data.table zu verwenden, vorausgesetzt, dplyr unterstützt derzeit nicht in-place mutation, was ist, wonach Sie zu suchen scheinen.

library(data.table) 
setDT(mydf)[!is.na(var2), var3 := myfn(var1, var2)] 
#  var1  var2  var3 
# 1: 0.56226084 0.62588794 0.56226084 
# 2: 0.72649850 0.24145251 0.72649850 
# 3: 0.91524985 0.03768974 0.91524985 
# 4: 0.02969437 0.51659297 0.02969437 
# 5: 0.76750970 0.81845788 0.76750970 
# 6: 0.48005398   NA   NA 
# 7: 0.08837960   NA   NA 
# 8: 0.86294587   NA   NA 
# 9: 0.49660306   NA   NA 
#10: 0.85350403   NA   NA 
+0

danke @ Mtoto, ja solche teilweise Ersetzungen ist eine sehr nette Funktion von 'data.table'. Ich suchte nur nach etwas ähnlichem mit 'dplyr', denn obwohl ich ein Fan von' data.table' Leistung bin, nicht so sehr wegen seiner Syntax (irgendwie undurchsichtig und schwer zu verstehen, ...), nicht in diesem Fall, obwohl, für diesen speziellen Fall ist der Code auch sehr klar, aber in einigen Fällen ist der Code für 'data.table'-basierte Lösungen schwer zu lesen. – elikesprogramming

1

Sie könnten die Funktion auf den kompletten Reihen laufen und dann zurück binden die Zeilen mit NA (obwohl dies umständlicher als die if ist ... else Ansatz):

mydf %>% filter(complete.cases(.)) %>% 
    mutate(var3 = myfn(var1, var2)) %>% 
    bind_rows(mydf %>% filter(!complete.cases(.))) 
  var1  var2  var3 
     (dbl)  (dbl)  (dbl) 
1 0.56226084 0.62588794 0.56226084 
2 0.72649850 0.24145251 0.72649850 
3 0.91524985 0.03768974 0.91524985 
4 0.02969437 0.51659297 0.02969437 
5 0.76750970 0.81845788 0.76750970 
6 0.48005398   NA   NA 
7 0.08837960   NA   NA 
8 0.86294587   NA   NA 
9 0.49660306   NA   NA 
10 0.85350403   NA   NA 
+0

Es ist auch teuer, weil auch die unzusammenhängenden Spalten auseinander gerissen und ohne Grund wieder zusammengefügt werden :-) – krlmlr

2

Hier sind zwei weitere Optionen, die Sie in dplyr-Rohren verwenden:

a) mit einem temporären Variablen

mutate(mydf, temp = !(is.na(var1) | is.na(var2)), 
     var3 = replace(NA, temp, myfn(var1[temp], var2[temp])), 
     temp = NULL) 
#   var1  var2  var3 
#1 0.56226084 0.62588794 0.56226084 
#2 0.72649850 0.24145251 0.72649850 
#3 0.91524985 0.03768974 0.91524985 
#4 0.02969437 0.51659297 0.02969437 
#5 0.76750970 0.81845788 0.76750970 
#6 0.48005398   NA   NA 
#7 0.08837960   NA   NA 
#8 0.86294587   NA   NA 
#9 0.49660306   NA   NA 
#10 0.85350403   NA   NA 

b) mit einer Wrapper-Funktion der ursprünglichen myfn) (ohne Änderung:

myfn2 <- function(x, y) { 
    i <- !(is.na(x) | is.na(y)) 
    res <- rep(NA, length(x)) 
    res[i] <- myfn(x[i], y[i]) 
    res 
} 

mutate(mydf, var3 = myfn2(var1, var2)) 
#   var1  var2  var3 
#1 0.56226084 0.62588794 0.56226084 
#2 0.72649850 0.24145251 0.72649850 
#3 0.91524985 0.03768974 0.91524985 
#4 0.02969437 0.51659297 0.02969437 
#5 0.76750970 0.81845788 0.76750970 
#6 0.48005398   NA   NA 
#7 0.08837960   NA   NA 
#8 0.86294587   NA   NA 
#9 0.49660306   NA   NA 
#10 0.85350403   NA   NA 
1

Dies ist ein großartiger Fall, um die pythonic style des Bettelns Vergebung eher als um Erlaubnis zu bitten.

Sie können dieses Problem lösen mit tryCatch und vermeiden Anlage Prüfung insgesamt:

myfn <- function(x, y){ 
    tryCatch(sum(x:y), error = function(e) NA) 
} 

Dann

myfn <- Vectorize(myfn) 
mydf %>% 
    mutate(var3 = myfn(var1, var2)) 

gibt das gewünschte Ergebnis

  var1  var2  var3 
1 0.56226084 0.62588794 0.56226084 
2 0.72649850 0.24145251 0.72649850 
3 0.91524985 0.03768974 0.91524985 
4 0.02969437 0.51659297 0.02969437 
5 0.76750970 0.81845788 0.76750970 
6 0.48005398   NA   NA 
7 0.08837960   NA   NA 
8 0.86294587   NA   NA 
9 0.49660306   NA   NA 
10 0.85350403   NA   NA 

Nachtrag

Natürlich ist es eine gute Idee, nur NA auf die richtige Art von Fehler passieren, die

ist
> tryCatch(sum(NA:NA), error = function(e) print(str(e))) 
List of 2 
$ message: chr "NA/NaN argument" 
$ call : language NA:NA 
- attr(*, "class")= chr [1:3] "simpleError" "error" "condition" 
NULL 
+0

danke @jaimedash nette Idee, 'tryCatch' zu verwenden, obwohl ich es in der' dplyr' Kette tun würde, anstatt in der Funktion (teilweise, weil die Funktion nicht so einfach ist wie dieses Beispiel, das ich gepostet habe, und obwohl ich eine Wrapper-Funktion schreiben könnte, um Fehler zu finden, bin ich kein Fan von solchen Wrappern) – elikesprogramming

+0

Es macht Sinn, das Umschreiben der Funktion zu vermeiden. Die Idee, 'tryCatch' direkt als Inline-Wrapper in die Kette zu stellen, erscheint cool, aber wenn ich versuche, zB 'mydf%>% muate (var3 = tryCatch (myfn (var1, var2), error = funktion (e) NA)) 'dann ist var3 alles NA. Wie funktioniert es? (PS 'rowwise' hilft auch nicht) – jaimedash

+0

Ich habe es nicht versucht, aber ich denke, inline' tryCatch' mit 'rowwise' könnte funktionieren. Ohne 'rowwise' sollte es nicht funktionieren, da wiederum der gesamte Vektor an die Funktion übergeben wird und der 'tryCatch' einen Fehler bekommt und dir NA zurückgibt. In jedem Fall funktioniert die 'rowwise' Lösung sicher mit' do' anstelle von 'mutate' (letzteres funktioniert vielleicht nur auf der dev Version von' dplyr'?). Lesen Sie den Kommentar von @Psidom in seiner eigenen Antwort unten, der Code, den er dort zur Verfügung stellt, funktioniert. – elikesprogramming

Verwandte Themen