dplyr mutate con valores condicionales

87

En un marco de datos grande ("myfile") con cuatro columnas, tengo que agregar una quinta columna con valores condicionalmente basados ​​en las primeras cuatro columnas.

Prefiere respuestas con dplyry mutate, principalmente debido a su velocidad en grandes conjuntos de datos.

Mi marco de datos se ve así:

  V1 V2 V3 V4
1  1  2  3  5
2  2  4  4  1
3  1  4  1  1
4  4  5  1  3
5  5  5  5  4
...

Los valores de la quinta columna (V5) se basan en algunas reglas condicionales:

if (V1==1 & V2!=4) {
  V5 <- 1
} else if (V2==4 & V3!=1) {
  V5 <- 2
} else {
  V5 <- 0
}

Ahora quiero usar la mutatefunción para usar estas reglas en todas las filas (para evitar bucles lentos). Algo como esto (y sí, ¡sé que no funciona de esta manera!):

myfile <- mutate(myfile, if (V1==1 & V2!=4){V5 = 1}
    else if (V2==4 & V3!=1){V5 = 2}
    else {V5 = 0})

Este debería ser el resultado:

  V1 V2 V3 V4 V5
1  1  2  3  5  1
2  2  4  4  1  2
3  1  4  1  1  0
4  4  5  1  3  0
5  5  5  5  4  0

¿Cómo hacer esto dplyr?

escultor
fuente
Es útil indicar si V1..4 son todos enteros (no factor, lógico, cadena o flotante). y te preocupas por manejar correctamente NA, ( NaN, +Inf, -Inf)?
smci
Si la velocidad parece ser un problema para preferir dplyr, entonces lo utilizaría mejor data.table.
Valentin

Respuestas:

105

Prueba esto:

myfile %>% mutate(V5 = (V1 == 1 & V2 != 4) + 2 * (V2 == 4 & V3 != 1))

dando:

  V1 V2 V3 V4 V5
1  1  2  3  5  1
2  2  4  4  1  2
3  1  4  1  1  0
4  4  5  1  3  0
5  5  5  5  4  0

o esto:

myfile %>% mutate(V5 = ifelse(V1 == 1 & V2 != 4, 1, ifelse(V2 == 4 & V3 != 1, 2, 0)))

dando:

  V1 V2 V3 V4 V5
1  1  2  3  5  1
2  2  4  4  1  2
3  1  4  1  1  0
4  4  5  1  3  0
5  5  5  5  4  0

Nota

Sugiero que obtenga un nombre mejor para su marco de datos. myfile hace que parezca que tiene un nombre de archivo.

Anteriormente utilizó esta entrada:

myfile <- 
structure(list(V1 = c(1L, 2L, 1L, 4L, 5L), V2 = c(2L, 4L, 4L, 
5L, 5L), V3 = c(3L, 4L, 1L, 1L, 5L), V4 = c(5L, 1L, 1L, 3L, 4L
)), .Names = c("V1", "V2", "V3", "V4"), class = "data.frame", row.names = c("1", 
"2", "3", "4", "5"))

Actualización 1 Desde que se publicó originalmente, dplyr ha cambiado %.%para %>%modificar la respuesta en consecuencia.

La actualización 2 dplyr ahora tiene lo case_whenque proporciona otra solución:

myfile %>% 
       mutate(V5 = case_when(V1 == 1 & V2 != 4 ~ 1, 
                             V2 == 4 & V3 != 1 ~ 2,
                             TRUE ~ 0))
G. Grothendieck
fuente
Probé tu segunda solución. Recibí este error: Error en mutate_impl (.data, named_dots (...), environment ()): REAL () solo se puede aplicar a un 'numérico', no a un 'lógico'.
rdatasculptor
5
Descubrí una forma que te permite no anidar las ifelsedeclaraciones:myfile %>% mutate(V5 = ifelse(V1 == 1 & V2 != 4, 1, 0), V5 = ifelse(V2 == 4 & V3 != 1, 2, V5))
Alex
31

Con dplyr 0.7.2, puede utilizar la case_whenfunción muy útil :

x=read.table(
 text="V1 V2 V3 V4
 1  1  2  3  5
 2  2  4  4  1
 3  1  4  1  1
 4  4  5  1  3
 5  5  5  5  4")
x$V5 = case_when(x$V1==1 & x$V2!=4 ~ 1,
                 x$V2==4 & x$V3!=1 ~ 2,
                 TRUE ~ 0)

Expresado con dplyr::mutate, da:

x = x %>% mutate(
     V5 = case_when(
         V1==1 & V2!=4 ~ 1,
         V2==4 & V3!=1 ~ 2,
         TRUE ~ 0
     )
)

Tenga en cuenta que NAno se tratan de forma especial, ya que puede ser engañoso. La función regresará NAsolo cuando no coincida ninguna condición. Si pones una línea con TRUE ~ ..., como hice en mi ejemplo, el valor de retorno nunca será NA.

Por lo tanto, debe indicar expresamente case_whenque coloque NAdonde pertenece agregando una declaración como is.na(x$V1) | is.na(x$V3) ~ NA_integer_. Sugerencia: ¡la dplyr::coalesce()función puede ser realmente útil aquí a veces!

Además, tenga en cuenta que NAsolo generalmente no funciona, usted tiene que poner especial NAlos valores: NA_integer_, NA_character_o NA_real_.

Dan Chaltiel
fuente
1
Esto fue significativamente más rápido que derivadoFactor.
Fato39
12

Parece que derivedFactorel mosaicpaquete fue diseñado para esto. En este ejemplo, se vería así:

library(mosaic)
myfile <- mutate(myfile, V5 = derivedFactor(
    "1" = (V1==1 & V2!=4),
    "2" = (V2==4 & V3!=1),
    .method = "first",
    .default = 0
    ))

(Si desea que el resultado sea numérico en lugar de un factor, envuelva el derivedFactorcon un as.numeric.)

Tenga en cuenta que la .defaultopción combinada con .method = "first"establece la condición "else"; este enfoque se describe en el archivo de ayuda de derivedFactor.

Jake Fisher
fuente
También puede evitar que el resultado sea un factor usando la .asFactor = Fopción o usando la función (similar) derivedVariableen el mismo paquete.
Jake Fisher
Parece que recodedesde dplyr 0.5 hará esto. Sin embargo, todavía no lo he investigado. Ver blog.rstudio.org/2016/06/27/dplyr-0-5-0
Jake Fisher
Esto fue lento para mis datos con 1e6 filas.
Fato39
3
@ Fato39 Sí, la mosaic::derivedFactorfamilia de funciones son muy lentas. Si averigua por qué, responda mi SO pregunta al respecto: stackoverflow.com/questions/33787691/… . Me alegra ver en su otro comentario que dplyr::case_whenes más rápido, tendré que cambiar a eso.
Jake Fisher
Estoy probando el siguiente comando, biblioteca (mosaico) VENEZ.FINAL2 <- mutate (VENEZ, SEX = derivadoFactor ("M" = (CATEGORY == "BULL" & CATEGORY! = "SIRE"), "F" = ( CATEGORY == "COW" & CATEGORY! = "HEIFER"), .method = "first", .default = "NA")) pero no funciona, solo resuelve la condición VENEZ.FINAL2 <- mutate (VENEZ, SEX = derivadoFactor ("M" = (CATEGORÍA == "BULL ¿Podrías ayudarme? ¡Muchas gracias!
Johanna Ramirez