Introdução programação funcional no R com purrr

Authors

Ariel Levy

Marcus Ramalho

Published

June 6, 2023


Estruturas de repetição

Em qualquer tarefa ou trabalho é comum nos depararmos com uma atividade extremamente simples, mas que precisa ser repetida diversas vezes para que o trabalho possa ser concluído, em programação não é diferente. Quando começamos a trabalhar com transformações em linhas ou colunas de um banco de dados, por exemplo, temos três opções básicas no r, fazer a transformação elemento por elemento, usar uma das estruturas de repetição como o for e while que podem ser usados em conjunto com um teste lógico através de funções como if else ou ainda usar o pacote purrr (Wickham and Henry 2023)do tidyverse(Wickham et al. 2019) que oferece recursos para simplificar a escrita e a leitura desse tipo de estrutura.

Laço simples - FOR

A primeira estrutura de repetição é o for, sua forma é basicamente a mesma em qualquer linguagem de programação, abaixo segue um exemplo do seu uso

Exemplos FOR

Estrutura da função for no r

a <- 0

for (i in 1:10) {
  a <- a + 1
}

print(a)
[1] 10

Visualmente temos:

Também é possível fazer um loop num objeto de caracteres

nomes <- c("Ana", "André", "Bruna", "João")
for (nome in nomes) {
  print(paste("Olá,meu nome é", nome))
}
[1] "Olá,meu nome é Ana"
[1] "Olá,meu nome é André"
[1] "Olá,meu nome é Bruna"
[1] "Olá,meu nome é João"

Veja que a forma como passamos a quantidade de iterações pra função pode variar como nos exemplos abaixo:

Usando a função seq que vai gerar um vetor com o número de iterações desejada

a <- 1

for (i in seq(1, 10)) {
  a <- a + 1
}

print(a)
[1] 11

Usando a função lengthpara obter o tamanho de um objeto para passar essa informação para o FOR.

a <- 1
iteracoes <- length(1:10)

for (i in 1:iteracoes) {
  a <- a + 1
}

print(a)
[1] 11
Exemplo do FOR com data frame no r

Neste exemplo, vamos usar o loop “for” para percorrer todas as linhas do conjunto de dados “mpg” e exibir o valor da variável “cty” milhas por galão na cidade para cada carro. Note que a função nrow nos da o número máximo de iterações do nosso laço.

library(tidyverse)
for (i in 1:nrow(mpg)) {
  cty_value <- mpg$cty[i]*2
  print(cty_value)
}

Laço com teste lógico ou condição - WHILE

A segunda estrutura de repetição é o while, diferente do for, aqui a condição para que o que esta dentro da estrutura seja executado deve ser explicita, além disso podemos cair num loop infinito caso a condição nunca seja atendida.

Exemplos WHILE

Estrutura da função while no r

Importante!!

veja que a função será executada enquanto a condição passada para ela for atendida, ou seja, enquanto for verdadeira. Se a condição for sempre verdadeira, entraremos num loop infinito.

contador <- 1
while (contador <= 5) {
  print(contador)
  contador <- contador + 1
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5

Exemplo em vídeo(Transcode 2021)

Outro exemplo:

soma <- 0
while (soma < 10) {
  numero <- readline(prompt = "Digite um número: ")
  numero <- as.numeric(numero)
  soma <- soma + numero
}
print(soma)
Exemplo do WHILE com data frame no r

Vamos usar o loop “while” para iterar pelo conjunto de dados “mpg” até encontrarmos um carro com milhas por galão na estrada (variável “hwy”) maior que 30 milhas.

library(tidyverse)

i <- 1

while (mpg$hwy[i] <= 30) {
  i <- i + 1
}

car_model <- mpg$model[i]
print(car_model)

purrr

Como vimos nos itens anteriores as funções for e while nos permitem passear e aplicar funções ou operações em vários itens de um objeto. O pacote purrr nos permite fazer o mesmo, so que de forma muito mais simples e prática, diminuindo a quantidade de código e facilitando a leitura.

Vamos começar listando as funções do pacote, porém, vamos nos ater apenas às mais importantes para as nossas aplicações.

Funções do pacote purrr
library(purrr)
fcs <- ls("package:purrr")
fcs
  [1] "%@%"                 "%||%"                "%>%"                
  [4] "accumulate"          "accumulate_right"    "accumulate2"        
  [7] "array_branch"        "array_tree"          "as_mapper"          
 [10] "as_vector"           "assign_in"           "at_depth"           
 [13] "attr_getter"         "auto_browse"         "chuck"              
 [16] "compact"             "compose"             "cross"              
 [19] "cross_d"             "cross_df"            "cross_n"            
 [22] "cross2"              "cross3"              "detect"             
 [25] "detect_index"        "discard"             "discard_at"         
 [28] "done"                "every"               "exec"               
 [31] "flatten"             "flatten_chr"         "flatten_dbl"        
 [34] "flatten_df"          "flatten_dfc"         "flatten_dfr"        
 [37] "flatten_int"         "flatten_lgl"         "flatten_raw"        
 [40] "has_element"         "head_while"          "imap"               
 [43] "imap_chr"            "imap_dbl"            "imap_dfc"           
 [46] "imap_dfr"            "imap_int"            "imap_lgl"           
 [49] "imap_raw"            "imodify"             "insistently"        
 [52] "invoke"              "invoke_map"          "invoke_map_chr"     
 [55] "invoke_map_dbl"      "invoke_map_df"       "invoke_map_dfc"     
 [58] "invoke_map_dfr"      "invoke_map_int"      "invoke_map_lgl"     
 [61] "invoke_map_raw"      "is_atomic"           "is_bare_atomic"     
 [64] "is_bare_character"   "is_bare_double"      "is_bare_integer"    
 [67] "is_bare_list"        "is_bare_logical"     "is_bare_numeric"    
 [70] "is_bare_vector"      "is_character"        "is_double"          
 [73] "is_empty"            "is_formula"          "is_function"        
 [76] "is_integer"          "is_list"             "is_logical"         
 [79] "is_null"             "is_rate"             "is_scalar_atomic"   
 [82] "is_scalar_character" "is_scalar_double"    "is_scalar_integer"  
 [85] "is_scalar_list"      "is_scalar_logical"   "is_scalar_vector"   
 [88] "is_vector"           "iwalk"               "keep"               
 [91] "keep_at"             "lift"                "lift_dl"            
 [94] "lift_dv"             "lift_ld"             "lift_lv"            
 [97] "lift_vd"             "lift_vl"             "list_along"         
[100] "list_assign"         "list_c"              "list_cbind"         
[103] "list_flatten"        "list_merge"          "list_modify"        
[106] "list_rbind"          "list_simplify"       "list_transpose"     
[109] "lmap"                "lmap_at"             "lmap_if"            
[112] "map"                 "map_at"              "map_chr"            
[115] "map_dbl"             "map_depth"           "map_df"             
[118] "map_dfc"             "map_dfr"             "map_if"             
[121] "map_int"             "map_lgl"             "map_raw"            
[124] "map_vec"             "map2"                "map2_chr"           
[127] "map2_dbl"            "map2_df"             "map2_dfc"           
[130] "map2_dfr"            "map2_int"            "map2_lgl"           
[133] "map2_raw"            "map2_vec"            "modify"             
[136] "modify_at"           "modify_depth"        "modify_if"          
[139] "modify_in"           "modify_tree"         "modify2"            
[142] "negate"              "none"                "partial"            
[145] "pluck"               "pluck_depth"         "pluck_exists"       
[148] "pluck<-"             "pmap"                "pmap_chr"           
[151] "pmap_dbl"            "pmap_df"             "pmap_dfc"           
[154] "pmap_dfr"            "pmap_int"            "pmap_lgl"           
[157] "pmap_raw"            "pmap_vec"            "possibly"           
[160] "prepend"             "pwalk"               "quietly"            
[163] "rate_backoff"        "rate_delay"          "rate_reset"         
[166] "rate_sleep"          "rbernoulli"          "rdunif"             
[169] "reduce"              "reduce_right"        "reduce2"            
[172] "reduce2_right"       "rep_along"           "rerun"              
[175] "safely"              "set_names"           "simplify"           
[178] "simplify_all"        "slowly"              "some"               
[181] "splice"              "tail_while"          "transpose"          
[184] "update_list"         "vec_depth"           "walk"               
[187] "walk2"               "when"                "zap"                

map

No pacote purr, map é uma função, mas também é uma família de funções, seu objetivo é aplicar uma função em cada elemento de um objeto, retornando um objeto do mesmo tamanho do objeto de entrada.

Família map

map_lgl()map_int()map_dbl() and map_chr()

A diferença entre map e suas variantes é principalmente o objeto que encontramos na saída, enquanto podemos usar funções auxiliáres do próprio purrr para obter os valores obtidos nas listas criadas com map também podemos usar as outras funções da família map para pular essa etapa.

Sintaxe segundo a documentação:

map(.x, .f, …, .progress = FALSE)

.x : é uma lista ou um vetor

.f : Uma função, especificada de uma das seguintes maneiras:

Uma função nomeada, por exemplo, mean.

Uma função anônima, por exemplo, \(x) x + 1 ou function(x) x + 1.

Função anônima

Uma função anônima no R é uma função que não possui um nome específico e é definida no local onde é utilizada. Ela é criada de forma concisa e temporária, geralmente como argumento de outra função. Ela é útil quando precisamos executar uma operação simples em um conjunto de dados sem a necessidade de definir uma função separada.

Uma fórmula, por exemplo, ~ .x + 1. Você deve usar .x para se referir ao primeiro argumento. Recomendado apenas se você precisar de compatibilidade com versões mais antigas do R.

Uma string, inteiro ou lista, por exemplo, "idx", 1 ou list("idx", 1), que são formas abreviadas de \(x) pluck(x, "idx"), (x) pluck(x, 1) e \(x) pluck(x, "idx", 1), respectivamente. Opcionalmente, você pode fornecer .default para definir um valor padrão caso o elemento indexado seja NULL ou não exista.

: Argumentos adicionais repassados para a função mapeada.

Atualmente, geralmente não recomendamos o uso de ... para passar argumentos adicionais (constantes) para .f. Em vez disso, use uma função anônima simplificada:

# Ao invés de:
x %>%  map(f, 1, 2, collapse = ",")
# faça:
x %>%  map(\(x) f(x, 1, 2, collapse = ","))

Isso torna mais fácil entender quais argumentos pertencem a qual função e geralmente resulta em mensagens de erro mais claras.

Exemplos

Por padrão a função map sempre retorna uma lista, podemos extrair os objetos da lista usando a função reduce do próprio purrr ou usando uma das funções da família map para cada tipo de objeto como pode ser visto nos exemplos abaixo.

no primeiro exemplo vamos printar todas as funções do pacote purrr que contêm a palavra map.

fcs_map <- map(fcs, \(x) str_detect(x, "map"))
fcs_map <- fcs[unlist(fcs_map)]
fcs_map
 [1] "as_mapper"      "imap"           "imap_chr"       "imap_dbl"      
 [5] "imap_dfc"       "imap_dfr"       "imap_int"       "imap_lgl"      
 [9] "imap_raw"       "invoke_map"     "invoke_map_chr" "invoke_map_dbl"
[13] "invoke_map_df"  "invoke_map_dfc" "invoke_map_dfr" "invoke_map_int"
[17] "invoke_map_lgl" "invoke_map_raw" "lmap"           "lmap_at"       
[21] "lmap_if"        "map"            "map_at"         "map_chr"       
[25] "map_dbl"        "map_depth"      "map_df"         "map_dfc"       
[29] "map_dfr"        "map_if"         "map_int"        "map_lgl"       
[33] "map_raw"        "map_vec"        "map2"           "map2_chr"      
[37] "map2_dbl"       "map2_df"        "map2_dfc"       "map2_dfr"      
[41] "map2_int"       "map2_lgl"       "map2_raw"       "map2_vec"      
[45] "pmap"           "pmap_chr"       "pmap_dbl"       "pmap_df"       
[49] "pmap_dfc"       "pmap_dfr"       "pmap_int"       "pmap_lgl"      
[53] "pmap_raw"       "pmap_vec"      

Nesse caso usamos a função unlist para obter um vetor que contem valores lógicos em cada observação, como o número de observações é o mesmo que o vetor de funções do purrr podemos usar isso para acessar apenas as posições em que ocorre a palavra map.

Exemplo prático com visualização de dados

gráficos múltiplos
# Função para plotar um histograma para uma coluna numérica do nosso df
plot_histogram <- function(x) {
  plot <- ggplot(data.frame(x = x), aes(x = x)) + 
    geom_histogram()
  return(plot)
}

# Aplica a função a cada coluna numérica 
result <- map_if(mtcars, is.numeric, plot_histogram)
result
$mpg


$cyl


$disp


$hp


$drat


$wt


$qsec


$vs


$am


$gear


$carb

Outras referências…

References

Transcode. 2021. “Coding Basics: While Loops & Do While Loops | Programming for Beginners,” October. https://www.youtube.com/watch?v=v-K-4KuA8mQ.
Wickham, Hadley, Mara Averick, Jennifer Bryan, Winston Chang, Lucy D’Agostino McGowan, Romain François, Garrett Grolemund, et al. 2019. “Welcome to the Tidyverse 4: 1686. https://doi.org/10.21105/joss.01686.
Wickham, Hadley, and Lionel Henry. 2023. “Purrr: Functional Programming Tools.” https://CRAN.R-project.org/package=purrr.