dplyr muta con valori condizionali


88

In un dataframe di grandi dimensioni ("myfile") con quattro colonne devo aggiungere una quinta colonna con valori basati condizionatamente sulle prime quattro colonne.

Preferisci risposte con dplyre mutate, principalmente a causa della sua velocità in set di dati di grandi dimensioni.

Il mio dataframe ha questo aspetto:

  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
...

I valori della quinta colonna (V5) si basano su alcune regole condizionali:

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

Ora voglio utilizzare la mutatefunzione per utilizzare queste regole su tutte le righe (per evitare cicli lenti). Qualcosa del genere (e sì, lo so che non funziona in questo modo!):

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

Questo dovrebbe essere il risultato:

  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

Come farlo in dplyr?


È utile affermare se V1..4 sono tutti interi (non fattore, logico, stringa o float)? e ti interessa gestire correttamente NA, ( NaN, +Inf, -Inf)?
smci

Se la velocità sembra essere un problema da preferire dplyr, è meglio usarla data.table.
Valentin

Risposte:


108

Prova questo:

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 questo:

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

Ti suggerisco di ottenere un nome migliore per il tuo data frame. myfile fa sembrare che contenga un nome di file.

Sopra utilizzato questo input:

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"))

Aggiornamento 1 Da quando originariamente pubblicato dplyr è cambiato %.%in %>%quindi ho modificato la risposta di conseguenza.

L'aggiornamento 2 dplyr ora ha case_whenche fornisce un'altra soluzione:

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

Ho provato la tua seconda soluzione. Ho ricevuto questo errore: Errore in mutate_impl (.data, named_dots (...), environment ()): REAL () può essere applicato solo a un "numerico", non a un "logico" Sai cosa non va?
rdatasculptor

5
Ho scoperto un modo che permette di non annidare le ifelsedichiarazioni:myfile %>% mutate(V5 = ifelse(V1 == 1 & V2 != 4, 1, 0), V5 = ifelse(V2 == 4 & V3 != 1, 2, V5))
Alex

32

Con dplyr 0.7.2puoi usare la case_whenfunzione molto utile :

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)

Espresso con dplyr::mutate, dà:

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

Si prega di notare che NAnon sono trattati in modo speciale, in quanto possono essere fuorvianti. La funzione tornerà NAsolo quando nessuna condizione viene soddisfatta. Se metti una riga con TRUE ~ ..., come ho fatto nel mio esempio, il valore restituito non sarà maiNA .

Pertanto, devi dire espressamente case_whendi mettere NAdove appartiene aggiungendo una dichiarazione come is.na(x$V1) | is.na(x$V3) ~ NA_integer_. Suggerimento: la dplyr::coalesce()funzione può essere davvero utile qui a volte!

Inoltre, si ricorda che NAda sola sarà solitamente non funzionano, bisogna mettere particolari NAvalori: NA_integer_, NA_character_o NA_real_.


1
Questo è stato significativamente più veloce di derivatoFactor.
Fato39

12

Sembra che derivedFactordal mosaicpacchetto sia stato progettato per questo. In questo esempio, sarebbe simile a:

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

(Se vuoi che il risultato sia numerico invece di un fattore, racchiudi il derivedFactorcon un as.numeric.)

Notare che l' .defaultopzione combinata con .method = "first"imposta la condizione "else" - questo approccio è descritto nel file della guida per derivedFactor.


È inoltre possibile impedire che il risultato sia un fattore utilizzando l' .asFactor = Fopzione o utilizzando la funzione (simile) derivedVariablenello stesso pacchetto.
Jake Fisher

Sembra che recodeda dplyr 0.5 lo farà. Non l'ho ancora studiato però. Vedi blog.rstudio.org/2016/06/27/dplyr-0-5-0
Jake Fisher

Questo è stato lento per i miei dati con righe 1e6.
Fato39

3
@ Fato39 Sì, la mosaic::derivedFactorfamiglia di funzioni è molto lenta. Se capisci perché, rispondi alla mia domanda SO: stackoverflow.com/questions/33787691/… . Sono contento di vedere dal tuo altro commento che dplyr::case_whenè più veloce - dovrò passare a quello.
Jake Fisher

Sto provando il seguente comando, library (mosaic) VENEZ.FINAL2 <- mutate (VENEZ, SEX = derivedFactor ("M" = (CATEGORY == "BULL" & CATEGORY! = "SIRE"), "F" = ( CATEGORY == "COW" & CATEGORY! = "HEIFER"), .method = "first", .default = "NA")) ma non funziona, risolvi semplicemente la condizione VENEZ.FINAL2 <- mutate (VENEZ, SEX = derivedFactor ("M" = (CATEGORY == "BULL Puoi aiutarmi? Grazie mille!
Johanna Ramirez
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.