Operações condicionais no R

Vamos explorar operações condicionais no R com o {dplyr}
análises
case_when
if_else
dplyr
Autor

Aldani Braz Carvalho

Data de Publicação

4 de janeiro de 2025

Operações condicionais no R

No mundo da programação condicional no R, duas funções se destacam: if_else() e case_when(). Ambas são poderosas, mas cada uma tem seu momento ideal de uso.

  • if_else() é perfeito para situações binárias. Ele tem possibilidades mais amplas, mas tem opções reduzidas.

  • case_when(), por outro lado, brilha em cenários complexos, onde múltiplas condições precisam ser avaliadas. Mas se limita a criar valores com base em condições específicas.

Carregar pacotes:

Nova variável:

starwars_2 <- dplyr::starwars |> 
dplyr::glimpse()
Rows: 87
Columns: 14
$ name       <chr> "Luke Skywalker", "C-3PO", "R2-D2", "Darth Vader", "Leia Or…
$ height     <int> 172, 167, 96, 202, 150, 178, 165, 97, 183, 182, 188, 180, 2…
$ mass       <dbl> 77.0, 75.0, 32.0, 136.0, 49.0, 120.0, 75.0, 32.0, 84.0, 77.…
$ hair_color <chr> "blond", NA, NA, "none", "brown", "brown, grey", "brown", N…
$ skin_color <chr> "fair", "gold", "white, blue", "white", "light", "light", "…
$ eye_color  <chr> "blue", "yellow", "red", "yellow", "brown", "blue", "blue",…
$ birth_year <dbl> 19.0, 112.0, 33.0, 41.9, 19.0, 52.0, 47.0, NA, 24.0, 57.0, …
$ sex        <chr> "male", "none", "none", "male", "female", "male", "female",…
$ gender     <chr> "masculine", "masculine", "masculine", "masculine", "femini…
$ homeworld  <chr> "Tatooine", "Tatooine", "Naboo", "Tatooine", "Alderaan", "T…
$ species    <chr> "Human", "Droid", "Droid", "Human", "Human", "Human", "Huma…
$ films      <list> <"The Empire Strikes Back", "Revenge of the Sith", "Return…
$ vehicles   <list> <"Snowspeeder", "Imperial Speeder Bike">, <>, <>, <>, "Imp…
$ starships  <list> <"X-wing", "Imperial shuttle">, <>, <>, "TIE Advanced x1",…

If_else

Nós vamos utilizar a versão do dplyr, mas antes vamos verificar o que o Rbase pode nos oferecer e analisar algumas diferenças.

If

  • A estrutura if é usada para executar um bloco de código apenas se uma condição for verdadeira.
x <- 5
if (x > 0) {
  print("O x é positivo")
}
[1] "O x é positivo"

If else

  • O if else permite definir um bloco de código para ser executado caso a condição seja verdadeira e outro bloco caso a condição seja falsa.
x <- -3
if (x > 0) {
  print("x é positivo")
} else {
  print("x é negativo ou zero")
}
[1] "x é negativo ou zero"

Ifelse

  • A função ifelse() é uma versão vetorizada doif else.

  • Ela é útil quando você precisa aplicar uma condição em um vetor e retornar valores diferentes dependendo da condição.

  • A principal vantagem da função ifelse() é que ela pode processar múltiplos elementos ao mesmo tempo, sem a necessidade de usar loops.

    Importante

    A estrutura da função é: ifelse(condição, valor_se_verdadeiro, valor_se_falso)

x <- c(1, -2, 3, 0)
resultado <- ifelse(x > 0, "positivo", "não positivo")
print(resultado)
[1] "positivo"     "não positivo" "positivo"     "não positivo"

Elseif

  • Para testar múltiplas condições de forma sequencial, você pode usar a combinação de else if. Isso permite realizar vários testes em sequência, até que uma condição seja satisfeita.
x <- 0
if (x > 0) {
  print("x é positivo")
} else if (x < 0) {
  print("x é negativo")
} else {
  print("x é zero")
}
[1] "x é zero"

If_else

  • Também é vetorizada e similar ao ifelse(), mas é parte do pacote dplyr, o que a torna mais adequada para trabalhar com tibbles ou data.frames.

  • Exige que os valores de valor_se_verdadeiro e valor_se_falso sejam do mesmo tipo. Caso contrário, gera um erro, ao contrário do ifelse() que realiza coerção automática de tipos.

  • Oferece o parâmetro missing para lidar explicitamente com valores ausentes (NA), enquanto o ifelse() do R base lida com NA de forma implícita e muitas vezes imprevisível.

Importante

A estrutura da função é: if_else(condição, valor_se_verdadeiro, valor_se_falso, missing = valor_ausente)



Veja a seguir o código com um erro derivado do fato de os valores de valor_se_verdadeiro e valor_se_falso serem de classes distintas.

resultado <- starwars_2  |> 
  dplyr::mutate(
    classe_altura = dplyr::if_else(
    height >= 180, 
    1, 
    "Abaixo da média")
    ) |>
  dplyr::glimpse()
Error in `dplyr::mutate()`:
ℹ In argument: `classe_altura = dplyr::if_else(height >= 180, 1, "Abaixo
  da média")`.
Caused by error in `dplyr::if_else()`:
! Can't combine `true` <double> and `false` <character>.
resultado <- starwars_2  |> 
  mutate(
    classe_altura = if_else(
    height >= 180, 
    "Acima da média", 
    "Abaixo da média")
    ) |>
  dplyr::glimpse()
Rows: 87
Columns: 15
$ name          <chr> "Luke Skywalker", "C-3PO", "R2-D2", "Darth Vader", "Leia…
$ height        <int> 172, 167, 96, 202, 150, 178, 165, 97, 183, 182, 188, 180…
$ mass          <dbl> 77.0, 75.0, 32.0, 136.0, 49.0, 120.0, 75.0, 32.0, 84.0, …
$ hair_color    <chr> "blond", NA, NA, "none", "brown", "brown, grey", "brown"…
$ skin_color    <chr> "fair", "gold", "white, blue", "white", "light", "light"…
$ eye_color     <chr> "blue", "yellow", "red", "yellow", "brown", "blue", "blu…
$ birth_year    <dbl> 19.0, 112.0, 33.0, 41.9, 19.0, 52.0, 47.0, NA, 24.0, 57.…
$ sex           <chr> "male", "none", "none", "male", "female", "male", "femal…
$ gender        <chr> "masculine", "masculine", "masculine", "masculine", "fem…
$ homeworld     <chr> "Tatooine", "Tatooine", "Naboo", "Tatooine", "Alderaan",…
$ species       <chr> "Human", "Droid", "Droid", "Human", "Human", "Human", "H…
$ films         <list> <"The Empire Strikes Back", "Revenge of the Sith", "Ret…
$ vehicles      <list> <"Snowspeeder", "Imperial Speeder Bike">, <>, <>, <>, "…
$ starships     <list> <"X-wing", "Imperial shuttle">, <>, <>, "TIE Advanced x…
$ classe_altura <chr> "Abaixo da média", "Abaixo da média", "Abaixo da média",…


No entanto, se usarmos o ifelse() do Rbase isso não acontece devido à coersão automática desse pacote. Desse modo, ele vai transformar a class() de numeric de valor_se_verdadeiro para character.

resultado <- starwars_2  |>
  dplyr::mutate(
    classe_altura = base::ifelse(
    height >= 180,  
    1, 
    "Abaixo da média")
    ) |>
  dplyr::glimpse()
Rows: 87
Columns: 15
$ name          <chr> "Luke Skywalker", "C-3PO", "R2-D2", "Darth Vader", "Leia…
$ height        <int> 172, 167, 96, 202, 150, 178, 165, 97, 183, 182, 188, 180…
$ mass          <dbl> 77.0, 75.0, 32.0, 136.0, 49.0, 120.0, 75.0, 32.0, 84.0, …
$ hair_color    <chr> "blond", NA, NA, "none", "brown", "brown, grey", "brown"…
$ skin_color    <chr> "fair", "gold", "white, blue", "white", "light", "light"…
$ eye_color     <chr> "blue", "yellow", "red", "yellow", "brown", "blue", "blu…
$ birth_year    <dbl> 19.0, 112.0, 33.0, 41.9, 19.0, 52.0, 47.0, NA, 24.0, 57.…
$ sex           <chr> "male", "none", "none", "male", "female", "male", "femal…
$ gender        <chr> "masculine", "masculine", "masculine", "masculine", "fem…
$ homeworld     <chr> "Tatooine", "Tatooine", "Naboo", "Tatooine", "Alderaan",…
$ species       <chr> "Human", "Droid", "Droid", "Human", "Human", "Human", "H…
$ films         <list> <"The Empire Strikes Back", "Revenge of the Sith", "Ret…
$ vehicles      <list> <"Snowspeeder", "Imperial Speeder Bike">, <>, <>, <>, "…
$ starships     <list> <"X-wing", "Imperial shuttle">, <>, <>, "TIE Advanced x…
$ classe_altura <chr> "Abaixo da média", "Abaixo da média", "Abaixo da média",…

O poder do case_when()

O case_when() avalia uma sequência de condições lógicas, retornando valores definidos para cada caso correspondente.

Vamos criar uma nova variável que identifica os NA's e, em seguida, categoriza registros com base na altura e massa corporal.

resultado <- starwars |>
  dplyr::mutate(
    categoria = case_when(
      is.na(height) | is.na(mass) ~ "Dados ausentes",
      height < 100 ~ "Muito baixo",
      height >= 100 & height < 150 ~ "Baixo",
      height >= 150 & height < 200 & mass < 100 ~ "Médio e leve",
      height >= 150 & height < 200 & mass >= 100 ~ "Médio e pesado",
      height >= 200 ~ "Muito alto",
      TRUE ~ "Outros"
    )
  ) |>
  select(name, height, mass, categoria) |>
  glimpse()
Rows: 87
Columns: 4
$ name      <chr> "Luke Skywalker", "C-3PO", "R2-D2", "Darth Vader", "Leia Org…
$ height    <int> 172, 167, 96, 202, 150, 178, 165, 97, 183, 182, 188, 180, 22…
$ mass      <dbl> 77.0, 75.0, 32.0, 136.0, 49.0, 120.0, 75.0, 32.0, 84.0, 77.0…
$ categoria <chr> "Médio e leve", "Médio e leve", "Muito baixo", "Muito alto",…

If_else vs Case_when


Vimos que o case_when() é poderoso como múltiplas categorias. Contudo, o if_else() também pode nos dar o mesmo resultado. Mas a legibilidade do código não é ideal.

Importante

Essa prática 👇 com múltiplos if_else() dentro do outro é chamada de aninhamento de condições (nested conditions). Essa estrutura ocorre quando uma função condicional está contida dentro de outra, criando uma hierarquia de decisões. Embora seja funcional, o aninhamento excessivo pode comprometer a legibilidade e a manutenção do código, especialmente quando a lógica se torna mais complexa.

resultado <- starwars |>
  mutate(
    categoria = if_else(
      is.na(height) | is.na(mass),
      "Dados ausentes",
      if_else(
        height < 100, 
        "Muito baixo",
        if_else(
          height >= 100 & height < 150, 
          "Baixo",
          if_else(
            height >= 150 & height < 200 & mass < 100, 
            "Médio e leve",
            if_else(
              height >= 150 & height < 200 & mass >= 100, 
              "Médio e pesado",
              if_else(
                height >= 200, "Muito alto",
                "Outros"
              )
            )
          )
        )
      )
    )
  ) |>
  select(name, height, mass, categoria) |>
  glimpse()
Rows: 87
Columns: 4
$ name      <chr> "Luke Skywalker", "C-3PO", "R2-D2", "Darth Vader", "Leia Org…
$ height    <int> 172, 167, 96, 202, 150, 178, 165, 97, 183, 182, 188, 180, 22…
$ mass      <dbl> 77.0, 75.0, 32.0, 136.0, 49.0, 120.0, 75.0, 32.0, 84.0, 77.0…
$ categoria <chr> "Médio e leve", "Médio e leve", "Muito baixo", "Muito alto",…



Veja que o if_else() pode fazer o que o case_when() faz, mesmo que não seja a maneira ideial. Mas a recíproca não é verdadeira. O if_else() pode ser utilizado em outras situações mais complexas que não envolvam rótulos.

A seguir eu desenvolvo uma função para substituir valores ausentes por elementos de outro dataframe a partir de parâmetros específicos.

coleta_3_pvh_media <- coleta_3_pvh |>
  dplyr::mutate(
    temp_media = dplyr::if_else(
      data == coleta_3_pvh_noaa$data & 
        is.na(temp_media),
      coleta_3_pvh_noaa$temp,
      temp_media
      )
    ) |>
  dplyr::mutate(
    ws_media = dplyr::if_else(
      data == coleta_3_pvh_noaa$data & 
        is.na(ws_media),
      coleta_3_pvh_noaa$ws,
      ws_media
     )
   ) |>
  dplyr::mutate(
   chuva_total = dplyr::if_else(
      data == coleta_3_pvh_noaa$data & 
        is.na(chuva_total),
      coleta_3_pvh_noaa$chuva,
     chuva_total
     )
   )


É possível sintetizar o tema com a tabela seguinte.

Função Tipo de Condição Controle de Tipos Controle de NA Usabilidade
base::ifelse() Condição vetorizada Coerção de tipo Implicado Para vetores simples e manipulação de dados
dplyr::if_else() Condição vetorizada, do dplyr Rigoroso (mesmo tipo para TRUE/FALSE) Explicito com missing Para trabalhar com tibbles ou data.frame, com controle de tipo
dplyr::case_when() Múltiplas condições Rigoroso Explicito com NA Para múltiplas condições complexas em dplyr

Conclusão

Para cenários com condições extensivas que necessitam de rótulos, use o case_when() sem medo de ser feliz. Mas se a situação exige uma função ou transformações condicionais, use o if_else() ou ifelse(). Lembre que o if_else() não permite o uso de diferentes class() nos argumentos dois e três.


Nos vemos na próxima postagem, Aldani👋.