dplyr muda com valores condicionais

87

Em um grande dataframe ("myfile") com quatro colunas, tenho que adicionar uma quinta coluna com valores condicionalmente baseados nas primeiras quatro colunas.

Prefira respostas com dplyr e mutate, principalmente por causa de sua velocidade em grandes conjuntos de dados.

Meu dataframe é parecido com este:

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

Os valores da quinta coluna (V5) são baseados em algumas regras condicionais:

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

Agora eu quero usar a mutatefunção para usar essas regras em todas as linhas (para evitar loops lentos). Algo assim (e sim, eu sei que não funciona assim!):

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

Este deve ser o 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

Como fazer isso dplyr?

rdatasculptor
fonte
É útil afirmar se V1..4 são todos inteiros (não fator, lógico, string ou float). e você se preocupa com o manuseio correto NA, ( NaN, +Inf, -Inf)?
smci
Se a velocidade parece ser um problema para a preferência dplyr, é melhor usar data.table.
Valentin

Respostas:

105

Experimente isto:

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

ou isto:

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

Sugiro que você obtenha um nome melhor para seu quadro de dados. myfile faz parecer que contém um nome de arquivo.

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

Atualização 1 Desde que postado originalmente, o dplyr mudou %.%para, %>%então modifiquei a resposta de acordo.

A atualização 2 dplyr agora case_whenoferece outra solução:

myfile %>% 
       mutate(V5 = case_when(V1 == 1 & V2 != 4 ~ 1, 
                             V2 == 4 & V3 != 1 ~ 2,
                             TRUE ~ 0))
G. Grothendieck
fonte
Tentei sua segunda solução. Recebi este erro: Erro em mutate_impl (.data, named_dots (...), environment ()): REAL () só pode ser aplicado a um 'numérico', não a um 'lógico'. Você sabe o que está acontecendo de errado?
rdatasculptor
5
Eu descobri uma maneira que permite que você não aninhe as ifelseafirmações:myfile %>% mutate(V5 = ifelse(V1 == 1 & V2 != 4, 1, 0), V5 = ifelse(V2 == 4 & V3 != 1, 2, V5))
Alex
31

Com dplyr 0.7.2, você pode usar a case_whenfunção muito ú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)

Expresso com dplyr::mutate, dá:

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

Observe que NAnão são tratados de maneira especial, pois podem ser enganosos. A função retornará NAapenas quando nenhuma condição for correspondida. Se você colocar uma linha com TRUE ~ ..., como fiz no meu exemplo, o valor de retorno nunca será NA.

Portanto, você deve dizer expressivamente case_whenpara colocar NAonde pertence, adicionando uma instrução como is.na(x$V1) | is.na(x$V3) ~ NA_integer_. Dica: a dplyr::coalesce()função pode ser muito útil aqui às vezes!

Além disso, por favor, note que NApor si só, geralmente não trabalho, você tem que colocar especiais NAvalores: NA_integer_, NA_character_ou NA_real_.

Dan Chaltiel
fonte
1
Isso foi significativamente mais rápido do que associatedFactor.
Fato39 de
12

Parece que derivedFactoro mosaicpacote foi projetado para isso. Neste exemplo, seria algo como:

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

(Se você quiser que o resultado seja numérico em vez de um fator, envolva o derivedFactorcom um as.numeric.)

Observe que a .defaultopção combinada com .method = "first"define a condição "else" - essa abordagem é descrita no arquivo de ajuda do derivedFactor.

Jake Fisher
fonte
Você também pode evitar que o resultado seja um fator usando a .asFactor = Fopção ou usando a derivedVariablefunção (semelhante) no mesmo pacote.
Jake Fisher
Parece que recodedo dplyr 0.5 vai fazer isso. Eu não investiguei isso ainda. Veja blog.rstudio.org/2016/06/27/dplyr-0-5-0
Jake Fisher
Isso era lento para meus dados com 1e6 linhas.
Fato39 de
3
@ Fato39 Sim, a mosaic::derivedFactorfamília de funções é muito lenta. Se você descobrir o porquê, responda à minha pergunta do SO sobre isso: stackoverflow.com/questions/33787691/… . Fico feliz em ver em seu outro comentário que dplyr::case_whené mais rápido - vou ter que mudar para isso.
Jake Fisher de
Estou tentando o seguinte comando, library (mosaic) VENEZ.FINAL2 <- mutate (VENEZ, SEX = associatedFactor ("M" = (CATEGORY == "BULL" & CATEGORY! = "SIRE"), "F" = ( CATEGORY == "COW" & CATEGORY! = "HEIFER"), .method = "first", .default = "NA")) mas não funciona, apenas resolva a condição VENEZ.FINAL2 <- mutate (VENEZ, SEX = associatedFactor ("M" = (CATEGORY == "BULL Você poderia me ajudar? Muito obrigada!
Johanna Ramirez