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?


É ú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))

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


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.


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.