Tag

controle de fluxo Archives - Análise Macro

Controles de fluxo: if, else, for e while no R

By | Data Science

No R, como em qualquer boa linguagem de programação, existem ferramentas de controle de fluxo essenciais para manipular dados de maneira eficiente, que podem ser divididas em duas categorias: condicionantes e iteradores. Neste exercício mostraremos como funcionam os famosos ifelsefor e while, de modo a tornar seus scripts de R mais eficientes e menos repetitivos.

Provavelmente você já se deparou com estas estruturas em algum código ou precisou usá-las em alguma rotina, mas no primeiro contato a experiência costuma ser traumatizante para iniciantes da linguagem. Para evitar a frustração, comece aprendendo elementos mais básicos sobre as ferramentas que você está utilizando.

Ou seja, inicie se familiarizando com as principais estruturas de dados existentes (veja aqui um post sobre) e tenha sempre em mãos um resumo dos principais operadores lógicos que você precisará usar no dia a dia. A seguir iremos abordar brevemente os operadores e testes lógicos para usá-los nas estruturas de controle de fluxo.

Testes e operadores lógicos

Antes de explorar os controles de fluxo, é importante você entender e estar familiarizado com os principais operadores lógicos no R, possibilitando a construção de operações lógicas em seu código. Uma operação lógica nada mais é do que um teste que retorna um resultado verdadeiro (TRUE) ou falso (FALSE). Por exemplo, suponha que você precise testar se um valor é igual ao outro. Para isso use o operador == (igual):


# Com números
2 == 4

# [1] FALSE

# Com textos
"fernando" == "fernando"

# [1] TRUE

Bem simples, não? Não doeu!

Tabela de operadores lógicos

Existem diversos operadores lógicos no R, aqui resumimos os mais utilizados:

Condicionantes: if else

Os condicionantes if else permitem que você execute códigos dependendo da condição (teste lógico) a ser satisfeita.

sintaxe básica de uma instrução if  no R é a seguinte:


if (condicao) acao_verdadeira
if (condicao) acao_verdadeira else acao_falsa

A leitura do código é praticamente literal: se a condicao for verdadeira (TRUE), acao_verdadeira será avaliada (executada); se a condicao for falsa (FALSE), a acao_falsa (opcional) será avaliada.

Em resumo, estamos dizendo para o R testar algo e fazer alguma coisa se a condição do teste for satisfeita, e, opcionalmente, fazer outra coisa se a condição do teste não for satisfeita. Caso o else não seja declarado, o R retornará invisivelmente o valor NULL.

Geralmente, as ações são declaradas dentro das chaves {}, conforme os exemplos abaixo. No primeiro exemplo, o R só irá calcular o log de x se o valor deste objeto for maior que 0 (condição TRUE). Caso contrário, no segundo exemplo, o R calculará o exponencial do valor de x (condição FALSE).


x <- 2
if (x > 0) { # condição lógica
log(x) # ação se a condição é verdadeira
}

# [1] 0.6931472

x <- -1
if (x > 0) { # condição lógica
log(x) # ação se a condição é verdadeira
} else exp(x) # ação se a condição é falsa

# [1] 0.3678794

Seguindo essa lógica, podemos trabalhar com estas estruturas encadeando-as, atribuindo/salvando os resultados em objetos, especificando mais de uma condição, aumentando a complexidade com outras estruturas de controle, etc. O céu é o limite (ou não)!

Iteradores: for

A segunda categoria de controles de fluxo são os iteradores ou loopings, que servem para quando precisamos executar uma tarefa repetidas vezes utilizando um conjunto de valores diferentes. Se você trabalha intensivamente com dados no R, é provável que você já tenha usado os iteradores, mesmo que implicitamente através de alguma função.

O for é usado para iterar (repetir) uma determinada ação/tarefa sobre os elementos de um vetor. Para sair da abstração, primeiro vamos entender a sintaxe básica de uma instrução for:


for (elemento in vetor) executar_acao

Lemos o código da seguinte forma: para cada elemento no vetor, executar_acao é chamado uma vez, atualizando o valor do elemento a cada vez.

Alguns conceitos básicos:

  • Iteração: é cada repetição da tarefa (executar_acao)
  • Iterador: o vetor que muda de valor a cada iteração

Um dos exemplos mais básicos da vida real que podemos explorar é o cálculo da mediana de uma série de variáveis de um conjunto de dados. Imagine que temos o seguinte data.frame:


dados <- data.frame(
a = rnorm(10),
b = rnorm(10),
c = rnorm(10)
)

Queremos calcular a mediana de cada coluna, que poderia ser feito da seguinte forma (manual e repetitiva):


median(dados$a)

# [1] 0.2477374

median(dados$b)

# [1] -0.1486918

median(dados$c)

# [1] 0.1975281

Repetir uma mesma tarefa (cálculo da mediana) copiando e colando o código mais do que duas vezes não é recomendável, por isso podemos usar o for:


for (i in 1:ncol(dados)) { # iterador
print(median(dados[[i]])) # iteração
}

# [1] 0.2477374
# [1] -0.1486918
# [1] 0.1975281

Neste exemplo, para cada posição das colunas do objeto data.frame (vetor com sequência de 1 até 3, ditado pelo ncol), calculamos a mediana e imprimimos o resultado no Console. Neste caso, o valor de i é 1 na primeira iteração, 2 na segunda iteração e, por fim, 3 na última iteração.

Iteradores: while

Iteradores for são úteis se você souber com antecedência o conjunto de valores sobre o qual deseja repetir uma ação. Caso não saiba, o iterador while é mais flexível, possuindo apenas dois componentes: a condição (teste lógico) e a iteração (tarefa), conforme abaixo.


while(condicao) executar_acao

Essa instrução diz para o R avaliar executar_acao enquanto a condicao for verdadeira (TRUE).

Vamos a um exemplo: o código a seguir irá exibir na tela o valor de i enquanto o valor deste objeto for menor ou igual a 10. No momento em que esta condição não for mais verdadeira, o processo é interrompido.


i <- 7
while (i <= 10) { # condição
print(i) # iteração
i <- i + 1 # atualiza valor de i, evitando um loop infinito
}

# [1] 7
# [1] 8
# [1] 9
# [1] 10

O exemplo acima apenas demonstra a sintaxe básica a ser seguida. O iterador while é mais usado para simulações, por exemplo: descobrir quantas vezes é necessário jogar uma moeda para obter 3 caras seguidas.

Saiba mais

Para se aprofundar sobre os tópicos abordados, confira estes posts no blog da Análise Macro:

E também o ótimo livro de Hadley Wickham intitulado “Advanced R”, especificamente o capítulo 5.

 

Nested loop com o pacote purrr no R

By | Data Science

O pacote purrr é um dos pilares do tidyverse, ao lado do dplyr e do operador pipe em termos de utilidade e generalização. Um de seus principais objetivos é facilitar a intenção de fazer um loop com a sintaxe envolvida. Ao invés de ter que escrever um monte de códigos tediosos do tipo "for-loop" - que pouco diz ao leitor sobre o que o loop faz -, a função map do purrr pode ser lida quase como inglês puro e se encaixa perfeitamente em operações encadeadas (pipe).

Mas isso não é tudo que o purrr tem a oferecer. O pacote vai muito além das funcionalidades map, walk e suas variantes, ajudando ainda mais no trabalho com listas. Neste exercício vamos utilizar algumas dessas funcionalidades do purrr para aplicações do tipo nested loop.

Primeiro vamos entender o nested loop com um exemplo mínimo, sem usar o pacote purrr. Vamos supor que você tenha dois vetores numéricos de tamanhos diferentes e que você queira todas as combinações possíveis dos valores de ambos os vetores, concatenando os valores em um único vetor separado por um hífen. Isso poderia ser feito com o controle de fluxo for:


# Nested loop
for (i in 1:2) {
for (j in 1:4) {
print(paste(i, j, sep = "-"))
}
}

## [1] "1-1"
## [1] "1-2"
## [1] "1-3"
## [1] "1-4"
## [1] "2-1"
## [1] "2-2"
## [1] "2-3"
## [1] "2-4"

Com o purrr poderíamos fazer a mesma operação com a função walk neste contexto, onde o .y do loop de dentro é o vetor numérico 1:2 do loop de fora:


library(purrr)

# Nested loop
walk(1:2, ~walk(1:4, ~print(paste(.y, .x, sep = "-")), .y = .x))

## [1] "1-1"
## [1] "1-2"
## [1] "1-3"
## [1] "1-4"
## [1] "2-1"
## [1] "2-2"
## [1] "2-3"
## [1] "2-4"

Uma outra opção seria, em verdade, evitar o nested loop, já que é de leitura dificultosa e requer um trabalho adicional desnecessário. Portanto, seguindo o conselho do pai da linguagem de programação C++, Bjarne Stroustrup:

To become significantly more reliable, code must become more transparent. In particular, nested conditions and loops must be viewed with great suspicion. Complicated control flows confuse programmers. Messy code often hides bugs.

Poríamos criar antes uma combinação de valores usando a função cross2 e então iterar esses elementos em um loop:


library(magrittr)

cross2(1:2, 1:4) %>%
walk(
~print(paste(.[[1]], .[[2]], sep = "-"))
)

## [1] "1-1"
## [1] "2-1"
## [1] "1-2"
## [1] "2-2"
## [1] "1-3"
## [1] "2-3"
## [1] "1-4"
## [1] "2-4"

Muito melhor, concorda?

Agora vamos a um exemplo prático da vida real. Suponha que você esteja trabalhando com os dados desagregados da inflação brasileira, medida pelo IPCA do IBGE e que precise coletar esses dados usando sua API através do pacote sidrar no R. A coleta dos dados pela API tem uma limitação de 50 mil observações por requisição, portanto você precisa criar uma estratégia para coletar esses dados sem ultrapassar esse limite da API.

Uma maneira de fazer isso, não a melhor, é justamente através de um nested loop. Para operacionalizar vamos coletar os dados do IPCA provenientes das tabelas 1419 e 7060 e suas variáveis do SIDRA/IBGE:


library(sidrar)

# Vetor com códigos das tabelas do IPCA
tables <- c("Tabela 1419" = 1419, "Tabela 7060" = 7060)

# Vetor com códigos das variáveis da tabela
variables <- c(
"IPCA - Variacao mensal (%)" = 63,
"IPCA - Peso mensal" = 66,
"IPCA - Variação acumulada no ano (%)" = 69,
"IPCA - Variação acumulada em 12 meses (%)" = 2265
)

# Coletar dados com nested loop
ipca <- tables %>%
map_dfr(
~map_dfr(
.x = variables,
~get_sidra(
x = .y, # vetor de códigos das tabelas (tables)
variable = .x, # vetor de códigos das variáveis (variables)
period = "all"
),
.y = .x
)
)

A outra possibilidade é gerar primeiro uma lista com combinações entre os códigos de tabelas e códigos das variáveis e, então, iterar estes elementos em um único loop:


library(dplyr)

# Coletar dados iterando combinações no loop
ipca2 <- cross2(tables, variables) %>%
map_dfr(
~get_sidra(
x = .[[1]],
variable = .[[2]],
period = "all"
)
)

# Verificar se resultados são equivalentes
all.equal(
arrange(ipca, `Mês`),
arrange(ipca2, `Mês`)
)

## [1] TRUE

Vale enfatizar que eu apenas arranhei a superfície do assunto neste texto, mas espero ter pelo menos instigado você a fazer sua própria investigação sobre o que o purrr pode oferecer, além de tornar as iterações mais legíveis, principalmente para os casos de nested loop.

 

Receba diretamente em seu e-mail gratuitamente nossas promoções especiais
e conteúdos exclusivos sobre Análise de Dados!

Assinar Gratuitamente