mutate - if con varias condiciones en r
¿Se puede usar el paquete dplyr para la mutación condicional? (5)
¿Se puede usar el mutado cuando la mutación es condicional (dependiendo de los valores de ciertos valores de columna)?
Este ejemplo ayuda a mostrar lo que quiero decir.
structure(list(a = c(1, 3, 4, 6, 3, 2, 5, 1), b = c(1, 3, 4,
2, 6, 7, 2, 6), c = c(6, 3, 6, 5, 3, 6, 5, 3), d = c(6, 2, 4,
5, 3, 7, 2, 6), e = c(1, 2, 4, 5, 6, 7, 6, 3), f = c(2, 3, 4,
2, 2, 7, 5, 2)), .Names = c("a", "b", "c", "d", "e", "f"), row.names = c(NA,
8L), class = "data.frame")
a b c d e f
1 1 1 6 6 1 2
2 3 3 3 2 2 3
3 4 4 6 4 4 4
4 6 2 5 5 5 2
5 3 6 3 3 6 2
6 2 7 6 7 7 7
7 5 2 5 2 6 5
8 1 6 3 6 3 2
Tenía la esperanza de encontrar una solución a mi problema usando el paquete dplyr (y sí, sé que este código no debería funcionar, pero supongo que aclara el propósito) para crear una nueva columna g:
library(dplyr)
df <- mutate(df, if (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)){g = 2},
if (a == 0 | a == 1 | a == 4 | a == 3 | c == 4){g = 3})
El resultado del código que estoy buscando debería tener este resultado en este ejemplo particular:
a b c d e f g
1 1 1 6 6 1 2 3
2 3 3 3 2 2 3 3
3 4 4 6 4 4 4 3
4 6 2 5 5 5 2 NA
5 3 6 3 3 6 2 NA
6 2 7 6 7 7 7 2
7 5 2 5 2 6 5 2
8 1 6 3 6 3 2 3
¿Alguien tiene una idea sobre cómo hacer esto en dplyr? Este marco de datos es solo un ejemplo, los marcos de datos con los que trato son mucho más grandes. Debido a su velocidad, traté de usar dplyr, pero tal vez haya otras formas mejores de manejar este problema.
Como usted solicita otras formas mejores de manejar el problema, esta es otra forma de usar data.table
:
require(data.table) ## 1.9.2+
setDT(df)
df[a %in% c(0,1,3,4) | c == 4, g := 3L]
df[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]
Tenga en cuenta que el orden de las instrucciones condicionales se invierte para obtener g
correctamente. No hay una copia de g
hecha, incluso durante la segunda tarea: se reemplaza in situ .
En datos más grandes, esto tendría un mejor rendimiento que el uso de if-else
anidado , ya que puede evaluar los casos de "sí" y "no" , y la anidación puede ser más difícil de leer / mantener en mi humilde opinión.
Aquí hay un punto de referencia en datos relativamente más grandes:
# R version 3.1.0
require(data.table) ## 1.9.2
require(dplyr)
DT <- setDT(lapply(1:6, function(x) sample(7, 1e7, TRUE)))
setnames(DT, letters[1:6])
# > dim(DT)
# [1] 10000000 6
DF <- as.data.frame(DT)
DT_fun <- function(DT) {
DT[(a %in% c(0,1,3,4) | c == 4), g := 3L]
DT[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]
}
DPLYR_fun <- function(DF) {
mutate(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L,
ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}
BASE_fun <- function(DF) { # R v3.1.0
transform(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L,
ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}
system.time(ans1 <- DT_fun(DT))
# user system elapsed
# 2.659 0.420 3.107
system.time(ans2 <- DPLYR_fun(DF))
# user system elapsed
# 11.822 1.075 12.976
system.time(ans3 <- BASE_fun(DF))
# user system elapsed
# 11.676 1.530 13.319
identical(as.data.frame(ans1), as.data.frame(ans2))
# [1] TRUE
identical(as.data.frame(ans1), as.data.frame(ans3))
# [1] TRUE
No estoy seguro si esta es una alternativa que había pedido, pero espero que ayude.
La función derivedFactor
del paquete de mosaic
parece estar diseñada para manejar esto. Usando este ejemplo, se vería así:
library(dplyr)
library(mosaic)
df <- mutate(df, g = derivedFactor(
"2" = (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)),
"3" = (a == 0 | a == 1 | a == 4 | a == 3 | c == 4),
.method = "first",
.default = NA
))
(Si desea que el resultado sea numérico en lugar de factor, puede ajustar derivedFactor
en una llamada as.numeric
).
derivedFactor
se puede usar para un número arbitrario de condicionales.
Use ifelse
df %>%
mutate(g = ifelse(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4), 2,
ifelse(a == 0 | a == 1 | a == 4 | a == 3 | c == 4, 3, NA)))
Agregado - if_else: Note que en dplyr 0.5 hay una función if_else
definida así que una alternativa sería reemplazar if_else
con if_else
; sin embargo, tenga en cuenta que dado que if_else
es más estricto que ifelse
(ambas if_else
de la condición deben tener el mismo tipo), entonces NA
en ese caso tendría que ser reemplazado por NA_real_
.
df %>%
mutate(g = if_else(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4), 2,
if_else(a == 0 | a == 1 | a == 4 | a == 3 | c == 4, 3, NA_real_)))
Agregado - caso_cuando Desde que se publicó esta pregunta, dplyr ha agregado el case_when
otra alternativa sería:
df %>% mutate(g = case_when(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4) ~ 2,
a == 0 | a == 1 | a == 4 | a == 3 | c == 4 ~ 3,
TRUE ~ NA_real_))
dplyr ahora tiene una función case_when
que ofrece un vectorizado if. La sintaxis es un poco extraña en comparación con mosaic:::derivedFactor
ya que no se puede acceder a las variables de la forma estándar dplyr, y es necesario declarar el modo de NA, pero es considerablemente más rápido que mosaic:::derivedFactor
.
df %>%
mutate(g = case_when(a %in% c(2,5,7) | (a==1 & b==4) ~ 2L,
a %in% c(0,1,3,4) | c == 4 ~ 3L,
TRUE~as.integer(NA)))
EDITAR: Si está usando dplyr::case_when()
desde antes de la versión 0.7.0 del paquete, debe preceder los nombres de las variables con '' .$
'' (Por ejemplo, escriba .$a == 1
dentro de case_when
).
Punto de referencia : para el punto de referencia (funciones de reutilización de la publicación de Arun) y reducción del tamaño de la muestra:
require(data.table)
require(mosaic)
require(dplyr)
require(microbenchmark)
DT <- setDT(lapply(1:6, function(x) sample(7, 10000, TRUE)))
setnames(DT, letters[1:6])
DF <- as.data.frame(DT)
DPLYR_case_when <- function(DF) {
DF %>%
mutate(g = case_when(a %in% c(2,5,7) | (a==1 & b==4) ~ 2L,
a %in% c(0,1,3,4) | c==4 ~ 3L,
TRUE~as.integer(NA)))
}
DT_fun <- function(DT) {
DT[(a %in% c(0,1,3,4) | c == 4), g := 3L]
DT[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]
}
DPLYR_fun <- function(DF) {
mutate(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L,
ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}
mosa_fun <- function(DF) {
mutate(DF, g = derivedFactor(
"2" = (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)),
"3" = (a == 0 | a == 1 | a == 4 | a == 3 | c == 4),
.method = "first",
.default = NA
))
}
microbenchmark(
DT_fun(DT),
DPLYR_fun(DF),
DPLYR_case_when(DF),
mosa_fun(DF),
times=20
)
Esto da:
expr min lq mean median uq max neval
DT_fun(DT) 1.503589 1.626971 2.054825 1.755860 2.292157 3.426192 20
DPLYR_fun(DF) 2.420798 2.596476 3.617092 3.484567 4.184260 6.235367 20
DPLYR_case_when(DF) 2.153481 2.252134 6.124249 2.365763 3.119575 72.344114 20
mosa_fun(DF) 396.344113 407.649356 413.743179 412.412634 416.515742 459.974969 20
case_when
ahora es una implementación bastante limpia del caso de estilo SQL cuando:
structure(list(a = c(1, 3, 4, 6, 3, 2, 5, 1), b = c(1, 3, 4,
2, 6, 7, 2, 6), c = c(6, 3, 6, 5, 3, 6, 5, 3), d = c(6, 2, 4,
5, 3, 7, 2, 6), e = c(1, 2, 4, 5, 6, 7, 6, 3), f = c(2, 3, 4,
2, 2, 7, 5, 2)), .Names = c("a", "b", "c", "d", "e", "f"), row.names = c(NA,
8L), class = "data.frame") -> df
df %>%
mutate( g = case_when(
a == 2 | a == 5 | a == 7 | (a == 1 & b == 4 ) ~ 2,
a == 0 | a == 1 | a == 4 | a == 3 | c == 4 ~ 3
))
Usando dplyr 0.7.4
El manual: http://dplyr.tidyverse.org/reference/case_when.html