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:

dobra <- function(x) { x*2 }
obj <- 10:15
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:

eh_impar <- function(x) { x%%2 == 1 }
dobra <- function(x) { x*2 }
obj <- 10:15
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:

obj <- 10:15
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:

obj <- 10:15
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:

soma_ambos <- function(x, y) { x + y }
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:

soma_ambos <- function(x, y) { x + y }
soma_um <- function(x) { x + 1 }
soma_dois <- function(x) { x + 2 }
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:

soma_ambos <- function(x, y) { x + y }
obj <- 10:15
for (i in 2:length(obj)) {
  obj[i] <- soma_ambos(obj[i-1], 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):

soma_ambos <- function(x, y) { x + y }
obj <- 10:15
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():

obj <- 10:15
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()).


Curso-R