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:
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 elsepermite definir um bloco de código para ser executado caso a condição seja verdadeira e outro bloco caso a condição seja falsa.
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.ImportanteA estrutura da função é: ifelse(condição, valor_se_verdadeiro, valor_se_falso)
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.
If_else
Também é vetorizada e similar ao
ifelse(), mas é parte do pacote dplyr, o que a torna mais adequada para trabalhar comtibblesoudata.frames.Exige que os valores de
valor_se_verdadeiroevalor_se_falsosejam do mesmo tipo. Caso contrário, gera um erro, ao contrário doifelse()que realiza coerção automática de tipos.Oferece o parâmetro
missingpara lidar explicitamente com valores ausentes (NA), enquanto oifelse()do R base lida comNAde forma implícita e muitas vezes imprevisível.
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.
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>.
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.
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.
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👋.