Estas soluciones (1) mantienen la tubería, (2) no sobrescriben la entrada y (3) solo requieren que la condición se especifique una vez:
1a) mutate_cond Crea una función simple para marcos de datos o tablas de datos que se pueden incorporar a las canalizaciones. Esta función es similar mutate
pero solo actúa en las filas que cumplen la condición:
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 Esta es una función alternativa para marcos de datos o tablas de datos que nuevamente es similar mutate
pero solo se usa dentro group_by
(como en el ejemplo a continuación) y solo opera en el último grupo en lugar de en cada grupo. Tenga en cuenta que TRUE> FALSE, por lo que si group_by
especifica una condición mutate_last
, solo funcionará en las filas que satisfagan esa condición.
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) factorizar la condición Factorizar la condición convirtiéndola en una columna adicional que luego se elimina. Luego use ifelse
, replace
o aritmética con lógicas como se ilustra. Esto también funciona para tablas de datos.
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 Podríamos usar SQL a update
través del paquete sqldf en la canalización para marcos de datos (pero no tablas de datos a menos que las convirtamos; esto puede representar un error en dplyr. Consulte el número 1579 de dplyr ). Puede parecer que estamos modificando indeseablemente la entrada en este código debido a la existencia de update
pero, de hecho, update
está actuando sobre una copia de la entrada en la base de datos generada temporalmente y no sobre la entrada real.
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 También consulte row_case_when
definido en
Devolver un tibble: ¿cómo vectorizar con case_when? . Utiliza una sintaxis similar case_when
pero se aplica a las filas.
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)
)
Nota 1: utilizamos esto comoDF
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))
Nota 2: El problema de cómo especificar fácilmente la actualización de un subconjunto de filas también se discute en cuestiones dplyr 134 , 631 , 1518 y 1573 con 631 siendo el hilo principal y 1573 siendo un examen de las respuestas aquí.
Puede hacer esto con
magrittr
la tubería de dos vías%<>%
:library(dplyr) library(magrittr) dt[dt$measure=="exit",] %<>% mutate(qty.exit = qty, cf = 0, delta.watts = 13)
Esto reduce la cantidad de escritura, pero sigue siendo mucho más lento que
data.table
.fuente
data.frame
/tibble
ya contiene la columna definida pormutate
. No funcionará si está intentando agregar una nueva columna, por ejemplo, la primera vez que ejecuta un bucle y modifica un archivodata.frame
.data.frame
. FWIW, simplemente volví a usar endata.table
lugar dedplyr
porque sui
expresión maneja esto fácilmente, además, el ciclo general se ejecuta mucho más rápido.Aquí hay una solución que me gusta:
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 }
Te permite escribir cosas como, por ejemplo,
mtcars %>% mutate_when( mpg > 22, list(cyl = 100), disp == 160, list(cyl = 200) )
que es bastante legible, aunque puede que no sea tan eficaz como podría ser.
fuente
Como muestra eipi10 arriba, no hay una manera simple de hacer un reemplazo de subconjunto en dplyr porque DT usa semántica de paso por referencia frente a dplyr usando paso por valor. dplyr requiere el uso de
ifelse()
en todo el vector, mientras que DT hará el subconjunto y actualizará por referencia (devolviendo el DT completo). Entonces, para este ejercicio, DT será sustancialmente más rápido.Alternativamente, podría crear un subconjunto primero, luego actualizar y finalmente recombinar:
dt.sub <- dt[dt$measure == "exit",] %>% mutate(qty.exit= qty, cf= 0, delta.watts= 13) dt.new <- rbind(dt.sub, dt[dt$measure != "exit",])
Pero DT será sustancialmente más rápido: (editado para usar la nueva respuesta de eipi10)
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
fuente
Me encontré con esto y realmente me gustó
mutate_cond()
@G. Grothendieck, pero pensó que podría ser útil manejar también nuevas variables. Entonces, a continuación tiene dos adiciones:No relacionado: la segunda última línea se hizo un poco más
dplyr
usandofilter()
Tres nuevas líneas al principio obtienen nombres de variables para su uso
mutate()
e inicializan cualquier nueva variable en el marco de datos antes de quemutate()
ocurra. Las nuevas variables se inicializan durante el resto deldata.frame
usonew_init
, que se establece en missing (NA
) de forma predeterminada.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 }
A continuación, se muestran algunos ejemplos que utilizan los datos del iris:
Cambie
Petal.Length
a 88 dondeSpecies == "setosa"
. Esto funcionará tanto en la función original como en esta nueva versión.iris %>% mutate_cond(Species == "setosa", Petal.Length = 88)
Igual que el anterior, pero también crea una nueva variable
x
(NA
en filas no incluidas en la condición). No era posible antes.iris %>% mutate_cond(Species == "setosa", Petal.Length = 88, x = TRUE)
Igual que el anterior, pero las filas no incluidas en la condición para
x
se establecen en FALSE.iris %>% mutate_cond(Species == "setosa", Petal.Length = 88, x = TRUE, new_init = FALSE)
Este ejemplo muestra cómo
new_init
se puede establecer en alist
para inicializar múltiples variables nuevas con valores diferentes. Aquí, se crean dos nuevas variables con filas excluidas que se inicializan con valores diferentes (x
inicializados comoFALSE
,y
comoNA
)iris %>% mutate_cond(Species == "setosa" & Sepal.Length < 5, x = TRUE, y = Sepal.Length ^ 2, new_init = list(FALSE, NA))
fuente
mutate_cond
función arroja un error en mi conjunto de datos y la función de Grothendiecks no.Error: incorrect length (4700), expecting: 168
Parece estar relacionado con la función de filtro.if_else
ocase_when
.mutate_cond es una gran función, pero da un error si hay un NA en las columnas usadas para crear la condición. Siento que un mutado condicional simplemente debería dejar esas filas en paz. Esto coincide con el comportamiento de filter (), que devuelve filas cuando la condición es TRUE, pero omite ambas filas con FALSE y NA.
Con este pequeño cambio la función funciona a las mil maravillas:
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 }
fuente
En realidad, no veo ningún cambio en
dplyr
eso que lo haría mucho más fácil.case_when
es ideal para cuando hay varias condiciones y resultados diferentes para una columna, pero no ayuda en este caso en el que desea cambiar varias columnas en función de una condición. Del mismo modo, serecode
ahorra escribir si está reemplazando varios valores diferentes en una columna, pero no ayuda a hacerlo en varias columnas a la vez. Finalmente,mutate_at
etc. solo aplican condiciones a los nombres de las columnas, no a las filas del marco de datos. Potencialmente, podría escribir una función para mutate_at que lo haría, pero no puedo entender cómo haría que se comportara de manera diferente para diferentes columnas.Dicho esto, aquí es cómo lo abordaría usando
nest
formtidyr
ymap
frompurrr
.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()
fuente
nest(-measure)
para evitar elgroup_by
Una solución concisa sería hacer la mutación en el subconjunto filtrado y luego volver a agregar las filas que no son de salida de la tabla:
library(dplyr) dt %>% filter(measure == 'exit') %>% mutate(qty.exit = qty, cf = 0, delta.watts = 13) %>% rbind(dt %>% filter(measure != 'exit'))
fuente
Con la creación de
rlang
, es posible una versión ligeramente modificada del ejemplo 1a de Grothendieck, eliminando la necesidad delenvir
argumento, ya queenquo()
captura el entorno que.p
se crea automáticamente.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)
fuente
Puede dividir el conjunto de datos y hacer una llamada mutada regular en la
TRUE
pieza.dplyr 0.8 presenta la función
group_split
que se divide por grupos (y los grupos se pueden definir directamente en la llamada), así que la usaremos aquí, pero tambiénbase::split
funciona.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
Si el orden de las filas es importante, utilícelo
tibble::rowid_to_column
primero, luegodplyr::arrange
enciéndalorowid
y selecciónelo al final.datos
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)
fuente
Creo que esta respuesta no se ha mencionado antes. Funciona casi tan rápido como la
data.table
solución "predeterminada" .Utilizar
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 ) )
reemplazar recicla el valor de reemplazo, por lo que cuando desee que los valores de las columnas se
qty
ingresen en columnasqty.exit
, también tenga que subconjuntosqty
... de ahí elqty[ measure == 'exit']
el primer reemplazo ...ahora, probablemente no querrá volver a escribir el
measure == 'exit'
todo el tiempo ... así que puede crear un vector de índice que contenga esa selección y usarlo en las funciones anteriores.#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 ) )
puntos de referencia
# 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
fuente
A expensas de romper con la sintaxis habitual de dplyr, puede usar
within
desde la base:dt %>% within(qty.exit[measure == 'exit'] <- qty[measure == 'exit'], delta.watts[measure == 'exit'] <- 13)
Parece integrarse bien con la tubería y puedes hacer prácticamente todo lo que quieras dentro de ella.
fuente
dt %>% within({ delta.watts[measure == 'exit'] <- 13 ; qty.exit[measure == 'exit'] <- qty[measure == 'exit'] ; cf[measure == 'exit'] <- 0 })
entonces funciona