Diese Lösungen (1) hält die Rohrleitung (2) noch nicht überschreiben , die Eingabe und (3) nur erfordert , dass die Bedingung einmal spezifiziert werden:
1a) mutate_cond Erstellen Sie eine einfache Funktion für Datenrahmen oder Datentabellen, die in Pipelines integriert werden können. Diese Funktion ist wie mutate
, wirkt jedoch nur auf die Zeilen, die die Bedingung erfüllen:
mutate_cond <- function(.data, condition, ..., envir = parent.frame()) {
condition <- eval(substitute(condition), .data, envir)
.data[condition, ] <- .data[condition, ] %>% mutate(...)
.data
}
DF %>% mutate_cond(measure == 'exit', qty.exit = qty, cf = 0, delta.watts = 13)
1b) mutate_last Dies ist eine alternative Funktion für Datenrahmen oder Datentabellen, die wiederum ähnlich ist, mutate
aber nur innerhalb group_by
(wie im folgenden Beispiel) verwendet wird und nur für die letzte Gruppe und nicht für jede Gruppe ausgeführt wird. Beachten Sie, dass TRUE> FALSE. Wenn Sie also group_by
eine Bedingung angeben, mutate_last
werden nur Zeilen bearbeitet, die diese Bedingung erfüllen.
mutate_last <- function(.data, ...) {
n <- n_groups(.data)
indices <- attr(.data, "indices")[[n]] + 1
.data[indices, ] <- .data[indices, ] %>% mutate(...)
.data
}
DF %>%
group_by(is.exit = measure == 'exit') %>%
mutate_last(qty.exit = qty, cf = 0, delta.watts = 13) %>%
ungroup() %>%
select(-is.exit)
2) Bedingung ausklammern Die Bedingung ausklammern, indem sie zu einer zusätzlichen Spalte gemacht wird, die später entfernt wird. Dann verwenden ifelse
, replace
oder Arithmetik mit Logicals wie dargestellt. Dies funktioniert auch für Datentabellen.
library(dplyr)
DF %>% mutate(is.exit = measure == 'exit',
qty.exit = ifelse(is.exit, qty, qty.exit),
cf = (!is.exit) * cf,
delta.watts = replace(delta.watts, is.exit, 13)) %>%
select(-is.exit)
3) sqldf Wir könnten SQL update
über das sqldf-Paket in der Pipeline für Datenrahmen verwenden (aber keine Datentabellen, es sei denn, wir konvertieren sie - dies könnte einen Fehler in dplyr darstellen. Siehe dplyr-Ausgabe 1579 ). Es mag den Anschein haben, als würden wir die Eingabe in diesem Code unerwünscht ändern, da das vorhanden ist, update
aber tatsächlich update
wirkt sich dies auf eine Kopie der Eingabe in der vorübergehend generierten Datenbank und nicht auf die tatsächliche Eingabe aus.
library(sqldf)
DF %>%
do(sqldf(c("update '.'
set 'qty.exit' = qty, cf = 0, 'delta.watts' = 13
where measure = 'exit'",
"select * from '.'")))
4) row_case_when Überprüfen Sie auch, row_case_when
wie unter
Zurückgeben eines Tibbles definiert: Wie wird mit case_when vektorisiert? . Es verwendet eine Syntax ähnlich der case_when
, gilt jedoch für Zeilen.
library(dplyr)
DF %>%
row_case_when(
measure == "exit" ~ data.frame(qty.exit = qty, cf = 0, delta.watts = 13),
TRUE ~ data.frame(qty.exit, cf, delta.watts)
)
Anmerkung 1: Wir haben dies als verwendetDF
set.seed(1)
DF <- data.frame(site = sample(1:6, 50, replace=T),
space = sample(1:4, 50, replace=T),
measure = sample(c('cfl', 'led', 'linear', 'exit'), 50,
replace=T),
qty = round(runif(50) * 30),
qty.exit = 0,
delta.watts = sample(10.5:100.5, 50, replace=T),
cf = runif(50))
Anmerkung 2: Das Problem, wie die Aktualisierung einer Teilmenge von Zeilen einfach angegeben werden kann, wird auch in den dplyr-Ausgaben 134 , 631 , 1518 und 1573 erörtert , wobei 631 der Hauptthread und 1573 eine Überprüfung der Antworten hier ist.
Sie können dies mit
magrittr
der Zweiwege-Leitung tun%<>%
:library(dplyr) library(magrittr) dt[dt$measure=="exit",] %<>% mutate(qty.exit = qty, cf = 0, delta.watts = 13)
Dies reduziert den Schreibaufwand, ist aber immer noch viel langsamer als
data.table
.quelle
data.frame
/tibble
bereits die durch definierte Spalte enthältmutate
. Es funktioniert nicht, wenn Sie versuchen, eine neue Spalte hinzuzufügen, z. B. wenn Sie zum ersten Mal eine Schleife durchlaufen und a änderndata.frame
.data.frame
. FWIW, ich habe gerade wieder verwendet,data.table
anstatt,dplyr
weil seini
Ausdruck dies leicht handhabt - und die gesamte Schleife läuft viel schneller.Hier ist eine Lösung, die mir gefällt:
mutate_when <- function(data, ...) { dots <- eval(substitute(alist(...))) for (i in seq(1, length(dots), by = 2)) { condition <- eval(dots[[i]], envir = data) mutations <- eval(dots[[i + 1]], envir = data[condition, , drop = FALSE]) data[condition, names(mutations)] <- mutations } data }
Damit können Sie Dinge wie z
mtcars %>% mutate_when( mpg > 22, list(cyl = 100), disp == 160, list(cyl = 200) )
Das ist gut lesbar - obwohl es möglicherweise nicht so performant ist, wie es sein könnte.
quelle
Wie eipi10 oben zeigt, gibt es keine einfache Möglichkeit, eine Teilmenge in dplyr zu ersetzen, da DT die Referenz-Pass-Semantik und dplyr die Pass-By-Wert-Semantik verwendet. dplyr erfordert die Verwendung des
ifelse()
gesamten Vektors, während DT die Teilmenge ausführt und durch Referenz aktualisiert (Rückgabe des gesamten DT). In dieser Übung ist DT also wesentlich schneller.Sie können alternativ zuerst eine Teilmenge erstellen, dann aktualisieren und schließlich neu kombinieren:
dt.sub <- dt[dt$measure == "exit",] %>% mutate(qty.exit= qty, cf= 0, delta.watts= 13) dt.new <- rbind(dt.sub, dt[dt$measure != "exit",])
Aber DT wird wesentlich schneller sein: (bearbeitet, um die neue Antwort von eipi10 zu verwenden)
library(data.table) library(dplyr) library(microbenchmark) microbenchmark(dt= {dt <- dt[measure == 'exit', `:=`(qty.exit = qty, cf = 0, delta.watts = 13)]}, eipi10= {dt[dt$measure=="exit",] %<>% mutate(qty.exit = qty, cf = 0, delta.watts = 13)}, alex= {dt.sub <- dt[dt$measure == "exit",] %>% mutate(qty.exit= qty, cf= 0, delta.watts= 13) dt.new <- rbind(dt.sub, dt[dt$measure != "exit",])}) Unit: microseconds expr min lq mean median uq max neval cld dt 591.480 672.2565 747.0771 743.341 780.973 1837.539 100 a eipi10 3481.212 3677.1685 4008.0314 3796.909 3936.796 6857.509 100 b alex 3412.029 3637.6350 3867.0649 3726.204 3936.985 5424.427 100 b
quelle
Ich bin nur darüber gestolpert und mag es wirklich
mutate_cond()
@G . Grothendieck, dachte aber, es könnte nützlich sein, auch mit neuen Variablen umzugehen. Im Folgenden finden Sie zwei Ergänzungen:Ohne Bezug: Zweite letzte Zeile aus einem wenig mehr
dplyr
durch die Verwendungfilter()
Drei neue Zeilen am Anfang erhalten Variablennamen zur Verwendung in
mutate()
und initialisieren alle neuen Variablen im Datenrahmen, bevor siemutate()
auftreten. Für den Rest derdata.frame
Verwendung werden neue Variablen initialisiertnew_init
, dieNA
standardmäßig auf missing ( ) gesetzt sind.mutate_cond <- function(.data, condition, ..., new_init = NA, envir = parent.frame()) { # Initialize any new variables as new_init new_vars <- substitute(list(...))[-1] new_vars %<>% sapply(deparse) %>% names %>% setdiff(names(.data)) .data[, new_vars] <- new_init condition <- eval(substitute(condition), .data, envir) .data[condition, ] <- .data %>% filter(condition) %>% mutate(...) .data }
Hier einige Beispiele unter Verwendung der Irisdaten:
Wechseln Sie
Petal.Length
zu 88 woSpecies == "setosa"
. Dies funktioniert sowohl in der ursprünglichen Funktion als auch in dieser neuen Version.iris %>% mutate_cond(Species == "setosa", Petal.Length = 88)
Wie oben, aber auch eine neue Variable erstellen
x
(NA
in Zeilen, die nicht in der Bedingung enthalten sind). Vorher nicht möglich.iris %>% mutate_cond(Species == "setosa", Petal.Length = 88, x = TRUE)
Wie oben, jedoch werden Zeilen, die nicht in der Bedingung für enthalten
x
sind, auf FALSE gesetzt.iris %>% mutate_cond(Species == "setosa", Petal.Length = 88, x = TRUE, new_init = FALSE)
Dieses Beispiel zeigt, wie auf a gesetzt werden
new_init
kannlist
, um mehrere neue Variablen mit unterschiedlichen Werten zu initialisieren. Hier werden zwei neue Variablen erstellt, wobei ausgeschlossene Zeilen mit unterschiedlichen Wertenx
initialisiert werden ( initialisiert alsFALSE
,y
asNA
).iris %>% mutate_cond(Species == "setosa" & Sepal.Length < 5, x = TRUE, y = Sepal.Length ^ 2, new_init = list(FALSE, NA))
quelle
mutate_cond
Funktion löscht einen Fehler in meinem Datensatz und die Funktion von Grothendiecks nicht.Error: incorrect length (4700), expecting: 168
Scheint mit der Filterfunktion verbunden zu sein.if_else
oder zu kombinierencase_when
.mutate_cond ist eine großartige Funktion, gibt jedoch einen Fehler aus, wenn in den Spalten, die zum Erstellen der Bedingung verwendet werden, eine NA vorhanden ist. Ich bin der Meinung, dass eine bedingte Mutation solche Zeilen einfach in Ruhe lassen sollte. Dies entspricht dem Verhalten von filter (), das Zeilen zurückgibt, wenn die Bedingung TRUE ist, aber beide Zeilen mit FALSE und NA weglässt.
Mit dieser kleinen Änderung wirkt die Funktion wie ein Zauber:
mutate_cond <- function(.data, condition, ..., envir = parent.frame()) { condition <- eval(substitute(condition), .data, envir) condition[is.na(condition)] = FALSE .data[condition, ] <- .data[condition, ] %>% mutate(...) .data }
quelle
Ich sehe eigentlich keine Änderungen daran
dplyr
, die dies viel einfacher machen würden.case_when
Dies ist ideal, wenn für eine Spalte mehrere unterschiedliche Bedingungen und Ergebnisse vorliegen. In diesem Fall, in dem Sie mehrere Spalten basierend auf einer Bedingung ändern möchten, ist dies jedoch nicht hilfreich. In ähnlicher Weise wird dierecode
Eingabe gespeichert, wenn Sie mehrere verschiedene Werte in einer Spalte ersetzen, dies jedoch nicht in mehreren Spalten gleichzeitig hilft. Schließlich,mutate_at
wenden Sie Bedingungen nur auf die Spaltennamen an, nicht auf die Zeilen im Datenrahmen. Sie könnten möglicherweise eine Funktion für mutate_at schreiben, die dies tun würde, aber ich kann nicht herausfinden, wie Sie dafür sorgen würden, dass es sich für verschiedene Spalten unterschiedlich verhält.Das heißt, hier ist, wie ich es mit
nest
Formtidyr
undmap
von nähern würdepurrr
.library(data.table) library(dplyr) library(tidyr) library(purrr) # Create some sample data set.seed(1) dt <- data.table(site = sample(1:6, 50, replace=T), space = sample(1:4, 50, replace=T), measure = sample(c('cfl', 'led', 'linear', 'exit'), 50, replace=T), qty = round(runif(50) * 30), qty.exit = 0, delta.watts = sample(10.5:100.5, 50, replace=T), cf = runif(50)) dt2 <- dt %>% nest(-measure) %>% mutate(data = if_else( measure == "exit", map(data, function(x) mutate(x, qty.exit = qty, cf = 0, delta.watts = 13)), data )) %>% unnest()
quelle
nest(-measure)
, um diegroup_by
Eine prägnante Lösung wäre, die Mutation für die gefilterte Teilmenge durchzuführen und dann die Non-Exit-Zeilen der Tabelle wieder hinzuzufügen:
library(dplyr) dt %>% filter(measure == 'exit') %>% mutate(qty.exit = qty, cf = 0, delta.watts = 13) %>% rbind(dt %>% filter(measure != 'exit'))
quelle
Mit der Erstellung von
rlang
ist eine leicht modifizierte Version des Beispiels 1a von Grothendieck möglich, sodass dasenvir
Argument nicht mehr benötigt wird, daenquo()
die Umgebung.p
erstellt wird, in der automatisch erstellt wird.mutate_rows <- function(.data, .p, ...) { .p <- rlang::enquo(.p) .p_lgl <- rlang::eval_tidy(.p, .data) .data[.p_lgl, ] <- .data[.p_lgl, ] %>% mutate(...) .data } dt %>% mutate_rows(measure == "exit", qty.exit = qty, cf = 0, delta.watts = 13)
quelle
Sie können den Datensatz aufteilen und das
TRUE
Teil regelmäßig mutieren .dplyr 0.8 bietet die Funktion,
group_split
die nach Gruppen aufgeteilt wird (und Gruppen können direkt im Aufruf definiert werden), sodass wir sie hier verwenden, aber auchbase::split
funktionieren.library(tidyverse) df1 %>% group_split(measure == "exit", keep=FALSE) %>% # or `split(.$measure == "exit")` modify_at(2,~mutate(.,qty.exit = qty, cf = 0, delta.watts = 13)) %>% bind_rows() # site space measure qty qty.exit delta.watts cf # 1 1 4 led 1 0 73.5 0.246240409 # 2 2 3 cfl 25 0 56.5 0.360315879 # 3 5 4 cfl 3 0 38.5 0.279966850 # 4 5 3 linear 19 0 40.5 0.281439486 # 5 2 3 linear 18 0 82.5 0.007898384 # 6 5 1 linear 29 0 33.5 0.392412729 # 7 5 3 linear 6 0 46.5 0.970848817 # 8 4 1 led 10 0 89.5 0.404447182 # 9 4 1 led 18 0 96.5 0.115594622 # 10 6 3 linear 18 0 15.5 0.017919745 # 11 4 3 led 22 0 54.5 0.901829577 # 12 3 3 led 17 0 79.5 0.063949974 # 13 1 3 led 16 0 86.5 0.551321441 # 14 6 4 cfl 5 0 65.5 0.256845013 # 15 4 2 led 12 0 29.5 0.340603733 # 16 5 3 linear 27 0 63.5 0.895166931 # 17 1 4 led 0 0 47.5 0.173088800 # 18 5 3 linear 20 0 89.5 0.438504370 # 19 2 4 cfl 18 0 45.5 0.031725246 # 20 2 3 led 24 0 94.5 0.456653397 # 21 3 3 cfl 24 0 73.5 0.161274319 # 22 5 3 led 9 0 62.5 0.252212124 # 23 5 1 led 15 0 40.5 0.115608182 # 24 3 3 cfl 3 0 89.5 0.066147321 # 25 6 4 cfl 2 0 35.5 0.007888337 # 26 5 1 linear 7 0 51.5 0.835458916 # 27 2 3 linear 28 0 36.5 0.691483644 # 28 5 4 led 6 0 43.5 0.604847889 # 29 6 1 linear 12 0 59.5 0.918838163 # 30 3 3 linear 7 0 73.5 0.471644760 # 31 4 2 led 5 0 34.5 0.972078100 # 32 1 3 cfl 17 0 80.5 0.457241602 # 33 5 4 linear 3 0 16.5 0.492500255 # 34 3 2 cfl 12 0 44.5 0.804236607 # 35 2 2 cfl 21 0 50.5 0.845094268 # 36 3 2 linear 10 0 23.5 0.637194873 # 37 4 3 led 6 0 69.5 0.161431896 # 38 3 2 exit 19 19 13.0 0.000000000 # 39 6 3 exit 7 7 13.0 0.000000000 # 40 6 2 exit 20 20 13.0 0.000000000 # 41 3 2 exit 1 1 13.0 0.000000000 # 42 2 4 exit 19 19 13.0 0.000000000 # 43 3 1 exit 24 24 13.0 0.000000000 # 44 3 3 exit 16 16 13.0 0.000000000 # 45 5 3 exit 9 9 13.0 0.000000000 # 46 2 3 exit 6 6 13.0 0.000000000 # 47 4 1 exit 1 1 13.0 0.000000000 # 48 1 1 exit 14 14 13.0 0.000000000 # 49 6 3 exit 7 7 13.0 0.000000000 # 50 2 4 exit 3 3 13.0 0.000000000
Wenn die Zeilenreihenfolge wichtig ist, verwenden Sie
tibble::rowid_to_column
zuerst, danndplyr::arrange
einrowid
und wählen Sie sie am Ende aus.Daten
df1 <- data.frame(site = sample(1:6, 50, replace=T), space = sample(1:4, 50, replace=T), measure = sample(c('cfl', 'led', 'linear', 'exit'), 50, replace=T), qty = round(runif(50) * 30), qty.exit = 0, delta.watts = sample(10.5:100.5, 50, replace=T), cf = runif(50), stringsAsFactors = F)
quelle
Ich denke, diese Antwort wurde noch nicht erwähnt. Es läuft fast so schnell wie die 'Standard'-
data.table
Lösung.Verwenden
base::replace()
df %>% mutate( qty.exit = replace( qty.exit, measure == 'exit', qty[ measure == 'exit'] ), cf = replace( cf, measure == 'exit', 0 ), delta.watts = replace( delta.watts, measure == 'exit', 13 ) )
Ersetzen recycelt den Ersetzungswert. Wenn Sie also die Werte von Spalten
qty
in Spalten eingeben möchtenqty.exit
, müssen Sie auch eine Teilmenge festlegenqty
... daher dieqty[ measure == 'exit']
beim ersten Ersetzen.Jetzt möchten Sie wahrscheinlich nicht die
measure == 'exit'
ganze Zeit neu eingeben ... also können Sie einen Indexvektor erstellen, der diese Auswahl enthält, und ihn in den obigen Funktionen verwenden.#build an index-vector matching the condition index.v <- which( df$measure == 'exit' ) df %>% mutate( qty.exit = replace( qty.exit, index.v, qty[ index.v] ), cf = replace( cf, index.v, 0 ), delta.watts = replace( delta.watts, index.v, 13 ) )
Benchmarks
# Unit: milliseconds # expr min lq mean median uq max neval # data.table 1.005018 1.053370 1.137456 1.112871 1.186228 1.690996 100 # wimpel 1.061052 1.079128 1.218183 1.105037 1.137272 7.390613 100 # wimpel.index 1.043881 1.064818 1.131675 1.085304 1.108502 4.192995 100
quelle
Auf Kosten der Verletzung der üblichen dplyr-Syntax können Sie
within
von base aus Folgendes verwenden :dt %>% within(qty.exit[measure == 'exit'] <- qty[measure == 'exit'], delta.watts[measure == 'exit'] <- 13)
Es scheint sich gut in die Pipe zu integrieren, und Sie können so ziemlich alles tun, was Sie wollen.
quelle
dt %>% within({ delta.watts[measure == 'exit'] <- 13 ; qty.exit[measure == 'exit'] <- qty[measure == 'exit'] ; cf[measure == 'exit'] <- 0 })
dann funktioniert es