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.