Controles de fluxo e funções
Introdução
Em muitas análises, precisamos tomar decisões com base nos dados e repetir operações de forma sistemática. Nesta aula, vamos aprender como controlar o fluxo de execução do código usando estruturas condicionais e como automatizar tarefas criando nossas próprias funções. Esses conceitos permitem transformar procedimentos repetitivos em etapas reprodutíveis e mais fáceis de aplicar a diferentes conjuntos de dados.
Condicionantes
Em R, podemos avaliar condições usando blocos if, else if e else. Para entender como isso é feito, vamos utilizar como exemplo uma rocha que tem 42 wt% de sílica:
Suponha que queremos determinar automaticamente se a rocha é máfica. Podemos fazê-lo com um bloco if, que segue o seguinte padrão:
if (condição) {
código
}O código abaixo avalia se a nossa rocha tem entre 45 e 52 wt% de sílica. Caso sim, o código retorna uma mensagem.
Perceba que o código acima não retornou nenhuma mensagem pois nossa rocha tem um teor de sílica (42 wt%) que está fora da condição do código.
Mas e se quisermos que uma outra mensagem seja exibida caso a condição não seja verdadeira? Podemos usar um bloco else junto com if da seguinte maneira:
if (condição) {
código
} else {
outro código
}Por exemplo, no código abaixo caso nossa rocha não esteja entre 45 e 52 wt% de sílica, uma outra mensagem será exibida:
Nós podemos avaliar mais de uma condição com um mesmo código, seguindo o seguinte padrão:
if (condição) {
código
} else if (outra condição) {
outro código
} else {
mais código
}Por exemplo, digamos que em vez de saber se a rocha é máfica ou não, queremos classificá-la como ultramáfica, máfica, intermediária e félsica. Seguindo a classificação de rochas ígneas, podemos escrever o seguinte código com base na estrutura acima:
Embora esse código seja útil para classificar o objeto SiO2 em específico, ele não é facilmente reutilizável para outros dados. Na próxima seção desta aula, vamos aprender a escrever nossas próprias funções para tornar nosso código mais reprodutível.
Funções
Até agora, temos trabalhado com funções base do R ou funções de pacotes que carregamos. Mas e se precisarmos fazer uma operação para a qual não há uma função específica? Para isso, podemos escrever nossa própria função seguindo o seguinte padrão:
my_function <- function(argumento) {
código
return(resultado)
}Por exemplo, digamos que queremos uma função que pegue qualquer número como input e adicione um a ele. Podemos fazê-lo da seguinte maneira:
Nós também podemos combinar blocos if e else com funções. Por exemplo, a função abaixo avalia se nossa rocha é máfica ou não. Se sim, retorna TRUE; se não, retorna FALSE.
Perceba que a função acima funciona para o nosso objeto SiO2, mas também para qualquer outro número. Essa é a vantagem de uma função.
Uma observação que é importante fazer é que não precisamos usar SiO2 como argumento para definir a função. O argumento poderia ser simplesmente x (ou outra variável), e a função funcionaria da mesma forma:
Vamos criar uma outra função! Dessa vez, vamos adaptar o código que usamos para classificar nossa rocha de acordo com seu teor de sílica:
O que acontece quando executamos o código abaixo?
Perceba que um teor de 45 e 52 wt% está sendo classificado com félsico. Por que isso ocorre?
Na nossa função não há nenhuma condição que lida com situações onde o teor de sílica é exatamente 45 ou 52. Portanto, o bloco else está capturando condições que não foram definidas explicitamente por nós.
Na prática, concentrações geoquímicas de sílica raramente são números inteiros. É mais comum ver, por exemplo, teores como 45.03 ou 52.1 wt%. No entanto, pra melhorar nossa função, podemos incluir condições que lidam com esses casos explicitamente:
Funções com mais de um argumento
Em aulas passadas, nós discutimos como funções podem ter vários argumentos, alguns obrigatórios e outros opcionais.
Para entender como criar uma função com mais de um argumento, vamos criar uma função que converte o teor de sílica (wt%) para silício (wt%).
Uma maneira de fazer isso é multiplicando sílica pelo fator 0.4674:
No entanto, digamos que queiramos dar uma opção ao usuário da função para que possa obter o valor de silício em partes por milhão (ppm). Para isso, podemos incluir um outro argumento: unidade na nossa função SiO2_to_Si():
No código da função acima, nós incluímos um argumento unidade que tem um valor padrão “wt%”. Por isso, quando executamos a função sem definir o argumento opcional, o resultado sai em wt% por padrão.
Porém, quando unidade é igual a “ppm”, o código executa um outro cálculo que multiplica os valores por 10000. Por isso, quando chamamos a função e definimos unidade como “ppm”, obtemos o resultado em ppm.
Qualquer outro valor para unidade que não seja “wt%” ou “ppm” resultará em “Unidade inválida” porque é capturado pelo bloco else.
Repetição
Vamos revisitar a função classificador() que escrevemos anteriormente:
O que acontece quando tentamos aplicá-la ao vetor abaixo?
Nossa função foi escrita pensando em um único valor por vez. Quando passamos um vetor, o if recebe várias respostas ao mesmo tempo e não consegue decidir qual usar. Uma possível alternativa seria fazer o seguinte:
No entanto, isso não é nada prático. Quando precisamos repetir um código dessa maneira, podemos usar for loops. Eles seguem o seguinte padrão:
for (variavel in sequencia) {
código
}Seguindo a estrutura acima, o loop abaixo está aplicando a função classificador() para cada um dos elementos do vetor SiO2 e mostrando o resultado com print:
Nós usamos teor no exemplo acima, mas poderíamos ter usado qualquer outra variável:
Em R, costuma-se usar funções da família apply no lugar de for loops para escrever código mais limpo, conciso e, frequentemente, mais rápido.
Para aplicar uma função para todos os elementos de um vetor podemos usar a função sapply():
Com sapply() também podemos determinar os argumentos opcionais das funções que estamos aplicando:
Funções anônimas
Vimos que nós podemos definir uma função em R da seguinte maneira:
my_function <- function(argumento) {
código
return(resultado)
}No entanto, também podemos usar funções sem defini-las, isto é, sem dá-las um nome. Vamos usar como exemplo a função adicione_um que criamos anteriormente:
adicione_um <- function(x) {
y <- x + 1
return(y)
}Desde a versão 4.1.0, o R nos permite escrever funções da seguinte maneira:
\(x) x + 1A função acima é uma função anônima. Funções anônimas são úteis porque podemos usá-las dentro de outras funções como sapply() sem precisar defini-las com antecedência.
Por exemplo, veja o exemplo abaixo. Nele estamos transformando todos os teores de sílica (wt%) para silício (wt%) usando a função SiO2_to_Si() que havíamos definido antes:
No entanto, também podemos usar uma função anônima dentro de sapply() que faz a mesma operação:
Para códigos curtos, funções anônimas funcionam muito bem. Por outro lado, uma função como classificador() é muito extensa para usar dessa maneira, sendo preferível defini-la de antemão.
Exercício
Neste exercício, vamos trabalhar com o dataset de terremotos que usamos na aula passada. Ele já foi carregado para você nesta página e pode ser acessado por meio do objeto terremotos.
- No código abaixo, por que apenas uma mensagem é exibida, mesmo que o valor satisfaça mais de uma condição?
- Remova todos os valores
NAdo objetomag:
- Com base na imagem abaixo, escreva uma função que descreva o terremoto com base na sua magnitude:
- Use a função
classificar_magnitude()para classificar cada um dos elementos do objetomagusando umforloop:
- Use a função
sapply()para aplicarclassificar_magnitude()a cada um dos elementos demag:
Considerações finais
Nesta aula, vimos como usar estruturas condicionais para tomar decisões no código e como criar funções para automatizar tarefas. Em muitas análises, essas regras são aplicadas repetidamente a colunas e tabelas inteiras. Na próxima aula, vamos explorar formas de realizar esse tipo de transformação em dados tabulares de maneira mais direta e organizada com o pacote dplyr.