10.3 Iterações avançadas
Agora que já vimos como substituir iterações de nível básico e de nível
intermediário com a família map()
, podemos passar para os tipos mais obscuros
de laços. Cada item desta seção será mais denso do que os das passadas, por isso
encorajamos todos os leitores para que também leiam a documentação de cada função
aqui abordada.
10.3.1 Iterações com condicionais
Imagine que precisamos aplicar uma função somente em alguns elementos de um vetor.
Com um laço isso é uma tarefa fácil, mas com as funções da família map()
apresentadas até agora isso seria extremamente difícil. Veja o trecho de código
a seguir por exemplo:
<- function(x) { x*2 }
dobra <- 10:15
obj for (i in seq_along(obj)) {
if (obj[i] %% 2 == 1) { obj[i] <- dobra(obj[i]) }
else { obj[i] <- obj[i] }
} obj
## [1] 10 22 12 26 14 30
No exemplo acima, aplicamos a função dobra()
apenas nos elementos ímpares do
vetor obj
. Com o pacote purrr
temos duas maneiras de fazer isso: com
map_if()
ou map_at()
.
A primeira dessas funções aplica a função dada apenas quando um predicado é
TRUE
. Esse predicado pode ser uma função ou uma fórmula (que serão aplicadas
em cada elemento da entrada e devem retornar TRUE
ou FALSE
). Infelizmente
a map_if()
não funciona com sufixos, então devemos achatar o resultado:
<- function(x) { x%%2 == 1 }
eh_impar <- function(x) { x*2 }
dobra <- 10:15
obj map_if(obj, eh_impar, dobra) %>% flatten_dbl()
## [1] 10 22 12 26 14 30
Com fórmulas poderíamos eliminar completamente a necessidade de funções declaradas:
<- 10:15
obj map_if(obj, ~.x%%2 == 1, ~.x*2) %>% flatten_dbl()
## [1] 10 22 12 26 14 30
A segunda dessas funções é a irmã gêmea de map_if()
e funciona de forma muito
semelhante. Para map_at()
devemos passar um vetor de nomes ou índices onde a
função deve ser aplicada:
<- 10:15
obj map_at(obj, c(2, 4, 6), ~.x*2) %>% flatten_dbl()
## [1] 10 22 12 26 14 30
10.3.2 Iterações com tabelas e funções
Duas funções menos utilizadas da família map()
são map_dfc()
e map_dfr()
,
que equivalem a um map()
seguido de um dplyr::bind_cols()
ou de um
dplyr::bind_rows()
respectivamente.
A maior utilidade dessas funções é quando temos uma tabela espalhada em muitos arquivos. Se elas estiverem divididas por grupos de colunas, podemos usar algo como
map_dfc(arquivos, readr::read_csv)
e se elas estiverem divididas por grupos de linhas,map_dfr(arquivos, readr::read_csv)
Outro membro obscuro da família map()
é a invoke_map()
. Na verdade essa
função pode ser considerada um membro da família invoke()
, mas vamos ver
que as semelhanças são muitas. Primeiramente, vamos demonstrar o que faz a
invoke()
sozinha:
<- function(x, y) { x + y }
soma_ambos invoke(soma_ambos, list(x = 10, y = 15))
## [1] 25
É fácil de ver que essa função recebe uma função e uma lista de argumentos para
usar em uma chamada desta. Agora generalizando esta lógica temos invoke_map()
,
que chama uma mesma função com uma lista de listas de argumentos ou uma lista
de funções com uma lista de argumentos. A família invoke()
também aceita os
sufixos como veremos a seguir:
<- function(x, y) { x + y }
soma_ambos <- function(x) { x + 1 }
soma_um <- function(x) { x + 2 }
soma_dois invoke_map_dbl(soma_ambos, list(list(x = 10, y = 15), list(x = 20, y = 25)))
## [1] 25 45
invoke_map_dbl(list(soma_um, soma_dois), list(x = 10))
## [1] 11 12
10.3.3 Redução e acúmulo
Outras funções simbólicas de programação funcional além da map()
são reduce
e accumulate
, que aplicam transformações em valores acumulados. Observe o laço
a seguir:
<- function(x, y) { x + y }
soma_ambos <- 10:15
obj for (i in 2:length(obj)) {
<- soma_ambos(obj[i-1], obj[i])
obj[i]
} obj
## [1] 10 21 33 46 60 75
Essa soma cumulativa é bastante simples, mas não é difícil imaginar uma situação
em que um programador desavisado confunde um índice com o outro e o bug acaba
passando desapercebido. Para evitar esse tipo de situação, podemos utilizar
accumulate()
(tanto com uma função quanto com uma fórmula):
<- function(x, y) { x + y }
soma_ambos <- 10:15
obj accumulate(obj, soma_ambos)
## [1] 10 21 33 46 60 75
accumulate(obj, ~.x+.y)
## [1] 10 21 33 46 60 75
Obs.: Nesse caso, os placeholders têm significados ligeiramente diferentes.
Aqui, .x
é o valor acumulado e .y
é o valor “atual” do objeto sendo iterado.
Se não quisermos o valor acumulado em cada passo da iteração, podemos usar
reduce()
:
<- 10:15
obj reduce(obj, ~.x+.y)
## [1] 75
Para a nossa comodidade, essas duas funções também têm variedades paralelas
(accumulate2()
e reduce2()
), assim como variedades invertidas
accumulate_right()
e reduce_right()
).