¿Se puede utilizar el paquete dplyr para mutación condicional?

Resuelto rdatasculptor asked hace 54 años • 5 respuestas

¿Se puede utilizar la mutación 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

Esperaba encontrar una solución a mi problema usando el paquete dplyr (y sí, sé que este no es un código que debería funcionar, pero supongo que deja claro 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 en 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 sólo un ejemplo, los marcos de datos con los que estoy tratando son mucho más grandes. Debido a su velocidad, intenté usar dplyr, pero ¿quizás haya otras formas mejores de manejar este problema?

rdatasculptor avatar Jan 01 '70 08:01 rdatasculptor
Aceptado

Usarifelse

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: Tenga en cuenta que en dplyr 0.5 hay una if_elsefunción definida, por lo que una alternativa sería reemplazarla ifelsecon if_else; sin embargo, tenga en cuenta que since if_elsees más estricto que ifelse(ambas partes de la condición deben tener el mismo tipo), por lo que NAen ese caso debería reemplazarse con 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: case_when Desde que se publicó esta pregunta, dplyr ha agregado case_whenotra 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_))

Agregado - aritmética/na_if Si los valores son numéricos y las condiciones (excepto el valor predeterminado de NA al final) son mutuamente excluyentes, como es el caso en la pregunta, entonces podemos usar una expresión aritmética tal que cada término se multiplique por el resultado deseado usando na_ifal final para reemplazar 0 con NA.

df %>%
  mutate(g = 2 * (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)) +
             3 * (a == 0 | a == 1 | a == 4 | a == 3 |  c == 4),
         g = na_if(g, 0))
G. Grothendieck avatar Jun 27 '2014 19:06 G. Grothendieck

Dado que solicita otras formas mejores de manejar el problema, aquí tiene otra forma de usar data.table:

require(data.table)
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 declaraciones condicionales se invierte para obtenerlas gcorrectamente. No se grealiza ninguna copia, ni siquiera durante la segunda asignación; se reemplaza en el lugar .

En datos más grandes, esto tendría un mejor rendimiento que usar nested if-else , ya que puede evaluar tanto los casos de "sí" como los de "no" , y el anidamiento puede volverse más difícil de leer/mantener en mi humilde opinión.


Aquí hay un punto de referencia sobre datos relativamente más grandes:

# NB: benchmark timings are as of R 3.1.0, data.table v1.9.2
require(data.table)
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 de si esta es una alternativa que habías pedido, pero espero que te ayude.

Arun avatar Jun 27 '2014 20:06 Arun

dplyr ahora tiene una función case_whenque ofrece un if vectorizado. La sintaxis es un poco extraña en comparación con mosaic:::derivedFactorel hecho de que no se puede acceder a las variables en la forma estándar de dplyr y es necesario declarar el modo NA, pero es considerablemente más rápida 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á utilizando dplyr::case_when()una versión anterior a la 0.7.0 del paquete, debe anteponer los nombres de las variables con ' .$' (por ejemplo, escribir .$a == 1dentro case_when).

Punto de referencia : para el punto de referencia (reutilizar funciones de la publicación de Arun) y reducir el tamaño de la muestra:

require(data.table) 
require(mosaic) 
require(dplyr)
require(microbenchmark)

set.seed(42) # To recreate the dataframe
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
  ))
}

perf_results <- microbenchmark(
  dt_fun <- DT_fun(copy(DT)),
  dplyr_ifelse <- DPLYR_fun(copy(DF)),
  dplyr_case_when <- DPLYR_case_when(copy(DF)),
  mosa <- mosa_fun(copy(DF)),
  times = 100L
)

Esto da:

print(perf_results)
Unit: milliseconds
           expr        min         lq       mean     median         uq        max neval
         dt_fun   1.391402    1.560751   1.658337   1.651201   1.716851   2.383801   100
   dplyr_ifelse   1.172601    1.230351   1.331538   1.294851   1.390351   1.995701   100
dplyr_case_when   1.648201    1.768002   1.860968   1.844101   1.958801   2.207001   100
           mosa 255.591301  281.158350 291.391586 286.549802 292.101601 545.880702   100
Matifou avatar Oct 08 '2016 18:10 Matifou

La derivedFactorfunción del mosaicpaquete 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 un factor, puede realizar derivedFactoruna as.numericllamada).

derivedFactorTambién se puede utilizar para un número arbitrario de condicionales.

Jake Fisher avatar Oct 22 '2015 19:10 Jake Fisher