purrr
Introdução
Parte desse texto é uma tradução desse site: http://joshuamccrain.com/tutorials/purrr/purrr_introduction.html
Este tutorial fornece uma breve introdução ao pacote purrr, focando nas funções mais úteis e como elas se combinam com o dplyr para facilitar a nossa vida.
O pacote purrr é incrivelmente versátil e pode se tornar muito complexo dependendo da sua aplicação. Aqui, meu objetivo é construir a intuição em torno da família de funções chamadas map, mostrando aplicações.
Se você estiver familiarizado com a lógica por trás da família de pacotes apply do R-base, essa função deve ser familiar.
Introdução ao purrr::map
Vamos ver com exemplos como algumas das principais funções do purrr funcionam. A função mais usada é map:
x <- seq(5, 10)
map(x, ~ .* 5)
[[1]]
[1] 25
[[2]]
[1] 30
[[3]]
[1] 35
[[4]]
[1] 40
[[5]]
[1] 45
[[6]]
[1] 50
A função map precisa de dois argumentos: (1) um vetor (ou uma lista) e (2) uma função (ou uma fórmula).
Neste caso básico estou passando um vetor x, que é simplesmente uma sequência de números. Depois estou realizando uma operação básica para cada número nessa sequência, multiplicando-o por 5.
A função map retorna uma lista e é isso que obtemos. Também podemos usar variações de map_ que permitem retornar outros tipos de saídas de dados. Falarei mais sobre isso mais tarde, mas este é um exemplo básico usando map_dbl:
x <- seq(5, 10)
map_dbl(x, ~ .*5)
[1] 25 30 35 40 45 50
Outras variantes incluem map_lgl, map_int e map_chr. Observe que você também pode inserir seus dados via pipe %>%:
x <- seq(5, 10)
x %>% map_dbl(~ .*5)
[1] 25 30 35 40 45 50
Uma função adicional é map2. Ele é útil quando você tem dois vetores ou listas que deseja combinar em uma única fórmula. Uma observação importante aqui é que ambos devem ter o mesmo comprimento. Se não, as coisas ficam complicadas:
x <- seq(2000, 2010)
y <- seq(10, 20)
map2_dbl(x, y, ~ .x + .y)
[1] 2010 2012 2014 2016 2018 2020 2022 2024 2026 2028 2030
Observe que as funções map funcionam tão bem com outros tipos de dados, como strings ou caracteres:
movies <- c("A New Hope",
"The Empire Strikes Back",
"Return of the Jedi",
"Phantom Menace",
"Attack of the Clones",
"Revenge of the Sith",
"The Force Awakens",
"The Last Jedi",
"Rise of Skywalker")
years <- c(1977, 1980, 1983, 1999, 2002, 2005, 2015, 2017, 2019)
map2_chr(movies, years, ~paste(.x, .y, sep=": "))
[1] "A New Hope: 1977" "The Empire Strikes Back: 1980"
[3] "Return of the Jedi: 1983" "Phantom Menace: 1999"
[5] "Attack of the Clones: 2002" "Revenge of the Sith: 2005"
[7] "The Force Awakens: 2015" "The Last Jedi: 2017"
[9] "Rise of Skywalker: 2019"
map_if: um exemplo aplicado
data("iris")
map_if(iris, is.numeric, shapiro.test)
$Sepal.Length
Shapiro-Wilk normality test
data: .x[[i]]
W = 0.97609, p-value = 0.01018
$Sepal.Width
Shapiro-Wilk normality test
data: .x[[i]]
W = 0.98492, p-value = 0.1012
$Petal.Length
Shapiro-Wilk normality test
data: .x[[i]]
W = 0.87627, p-value = 0.0000000007412
$Petal.Width
Shapiro-Wilk normality test
data: .x[[i]]
W = 0.90183, p-value = 0.0000000168
$Species
[1] setosa setosa setosa setosa setosa setosa
[7] setosa setosa setosa setosa setosa setosa
[13] setosa setosa setosa setosa setosa setosa
[19] setosa setosa setosa setosa setosa setosa
[25] setosa setosa setosa setosa setosa setosa
[31] setosa setosa setosa setosa setosa setosa
[37] setosa setosa setosa setosa setosa setosa
[43] setosa setosa setosa setosa setosa setosa
[49] setosa setosa versicolor versicolor versicolor versicolor
[55] versicolor versicolor versicolor versicolor versicolor versicolor
[61] versicolor versicolor versicolor versicolor versicolor versicolor
[67] versicolor versicolor versicolor versicolor versicolor versicolor
[73] versicolor versicolor versicolor versicolor versicolor versicolor
[79] versicolor versicolor versicolor versicolor versicolor versicolor
[85] versicolor versicolor versicolor versicolor versicolor versicolor
[91] versicolor versicolor versicolor versicolor versicolor versicolor
[97] versicolor versicolor versicolor versicolor virginica virginica
[103] virginica virginica virginica virginica virginica virginica
[109] virginica virginica virginica virginica virginica virginica
[115] virginica virginica virginica virginica virginica virginica
[121] virginica virginica virginica virginica virginica virginica
[127] virginica virginica virginica virginica virginica virginica
[133] virginica virginica virginica virginica virginica virginica
[139] virginica virginica virginica virginica virginica virginica
[145] virginica virginica virginica virginica virginica virginica
Levels: setosa versicolor virginica
map_at: um exemplo aplicado
iris %>% map_at(c(4, 5), is.numeric)
$Sepal.Length
[1] 5.1 4.9 4.7 4.6 5.0 5.4 4.6 5.0 4.4 4.9 5.4 4.8 4.8 4.3 5.8 5.7 5.4 5.1
[19] 5.7 5.1 5.4 5.1 4.6 5.1 4.8 5.0 5.0 5.2 5.2 4.7 4.8 5.4 5.2 5.5 4.9 5.0
[37] 5.5 4.9 4.4 5.1 5.0 4.5 4.4 5.0 5.1 4.8 5.1 4.6 5.3 5.0 7.0 6.4 6.9 5.5
[55] 6.5 5.7 6.3 4.9 6.6 5.2 5.0 5.9 6.0 6.1 5.6 6.7 5.6 5.8 6.2 5.6 5.9 6.1
[73] 6.3 6.1 6.4 6.6 6.8 6.7 6.0 5.7 5.5 5.5 5.8 6.0 5.4 6.0 6.7 6.3 5.6 5.5
[91] 5.5 6.1 5.8 5.0 5.6 5.7 5.7 6.2 5.1 5.7 6.3 5.8 7.1 6.3 6.5 7.6 4.9 7.3
[109] 6.7 7.2 6.5 6.4 6.8 5.7 5.8 6.4 6.5 7.7 7.7 6.0 6.9 5.6 7.7 6.3 6.7 7.2
[127] 6.2 6.1 6.4 7.2 7.4 7.9 6.4 6.3 6.1 7.7 6.3 6.4 6.0 6.9 6.7 6.9 5.8 6.8
[145] 6.7 6.7 6.3 6.5 6.2 5.9
$Sepal.Width
[1] 3.5 3.0 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 3.7 3.4 3.0 3.0 4.0 4.4 3.9 3.5
[19] 3.8 3.8 3.4 3.7 3.6 3.3 3.4 3.0 3.4 3.5 3.4 3.2 3.1 3.4 4.1 4.2 3.1 3.2
[37] 3.5 3.6 3.0 3.4 3.5 2.3 3.2 3.5 3.8 3.0 3.8 3.2 3.7 3.3 3.2 3.2 3.1 2.3
[55] 2.8 2.8 3.3 2.4 2.9 2.7 2.0 3.0 2.2 2.9 2.9 3.1 3.0 2.7 2.2 2.5 3.2 2.8
[73] 2.5 2.8 2.9 3.0 2.8 3.0 2.9 2.6 2.4 2.4 2.7 2.7 3.0 3.4 3.1 2.3 3.0 2.5
[91] 2.6 3.0 2.6 2.3 2.7 3.0 2.9 2.9 2.5 2.8 3.3 2.7 3.0 2.9 3.0 3.0 2.5 2.9
[109] 2.5 3.6 3.2 2.7 3.0 2.5 2.8 3.2 3.0 3.8 2.6 2.2 3.2 2.8 2.8 2.7 3.3 3.2
[127] 2.8 3.0 2.8 3.0 2.8 3.8 2.8 2.8 2.6 3.0 3.4 3.1 3.0 3.1 3.1 3.1 2.7 3.2
[145] 3.3 3.0 2.5 3.0 3.4 3.0
$Petal.Length
[1] 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 1.5 1.6 1.4 1.1 1.2 1.5 1.3 1.4
[19] 1.7 1.5 1.7 1.5 1.0 1.7 1.9 1.6 1.6 1.5 1.4 1.6 1.6 1.5 1.5 1.4 1.5 1.2
[37] 1.3 1.4 1.3 1.5 1.3 1.3 1.3 1.6 1.9 1.4 1.6 1.4 1.5 1.4 4.7 4.5 4.9 4.0
[55] 4.6 4.5 4.7 3.3 4.6 3.9 3.5 4.2 4.0 4.7 3.6 4.4 4.5 4.1 4.5 3.9 4.8 4.0
[73] 4.9 4.7 4.3 4.4 4.8 5.0 4.5 3.5 3.8 3.7 3.9 5.1 4.5 4.5 4.7 4.4 4.1 4.0
[91] 4.4 4.6 4.0 3.3 4.2 4.2 4.2 4.3 3.0 4.1 6.0 5.1 5.9 5.6 5.8 6.6 4.5 6.3
[109] 5.8 6.1 5.1 5.3 5.5 5.0 5.1 5.3 5.5 6.7 6.9 5.0 5.7 4.9 6.7 4.9 5.7 6.0
[127] 4.8 4.9 5.6 5.8 6.1 6.4 5.6 5.1 5.6 6.1 5.6 5.5 4.8 5.4 5.6 5.1 5.1 5.9
[145] 5.7 5.2 5.0 5.2 5.4 5.1
$Petal.Width
[1] TRUE
$Species
[1] FALSE
um exemplo aplicado com pipe
mtcars %>%
split(.$cyl) %>% # from base R
map(~ lm(mpg ~ wt, data = .)) %>%
map(summary) %>%
map_dbl("r.squared")
4 6 8
0.5086326 0.4645102 0.4229655
pluck: um exemplo aplicado
Se você já trabalhou com listas no R, sabe que a sintaxe é um pouco contra-intuitiva. Em vez de ter que fazer coisas como list[[1]][2] para recuperar elementos específicos de listas, podemos usar a função de pluck muito útil:
Aqui está a funcionalidade básica:
example <- list(movies, years,preference = c(2, 1, 3, 7, 8, 9, 4, 6, 5))
example
[[1]]
[1] "A New Hope" "The Empire Strikes Back"
[3] "Return of the Jedi" "Phantom Menace"
[5] "Attack of the Clones" "Revenge of the Sith"
[7] "The Force Awakens" "The Last Jedi"
[9] "Rise of Skywalker"
[[2]]
[1] 1977 1980 1983 1999 2002 2005 2015 2017 2019
$preference
[1] 2 1 3 7 8 9 4 6 5
example[[1]][5]
[1] "Attack of the Clones"
example[[2]][5]
[1] 2002
example[[3]][5]
[1] 8
com o pluck:
pluck(example, 1)
[1] "A New Hope" "The Empire Strikes Back"
[3] "Return of the Jedi" "Phantom Menace"
[5] "Attack of the Clones" "Revenge of the Sith"
[7] "The Force Awakens" "The Last Jedi"
[9] "Rise of Skywalker"
pluck(example, 1, 5)
[1] "Attack of the Clones"
pluck(example, 2, 5)
[1] 2002
pluck(example, 3, 5)
[1] 8
Por que você pode querer usar isso além de uma sintaxe mais fácil? Talvez algo assim, onde queremos extrair a última palavra de cada título de filme:
example %>%
pluck(1) %>%
map_chr(~ word(., -1))
[1] "Hope" "Back" "Jedi" "Menace" "Clones" "Sith"
[7] "Awakens" "Jedi" "Skywalker"
O que está acontecendo aqui?
exemplo %>% pluck(1): pipe em nossa lista e pegue o primeiro elemento da lista, o título do filme; map_chr(~ word(., 1)): pegue cada título de filme e extraia a última palavra dele usando a função de palavra.
Veja como ainda podemos fazer isso com pipes, mas sem a funcionalidade purrr:
example %>%
.[[1]] %>%
word(-1)
[1] "Hope" "Back" "Jedi" "Menace" "Clones" "Sith"
[7] "Awakens" "Jedi" "Skywalker"
Não é tão ruim, mas você pode ver como isso pode ficar complexo e difícil de ler rapidamente com coisas como .[[1]].
reduce(), flatten(), invoke(), modify(), possibly(), walk(),cross(), every() e keep()
outras funções do purrr interessantes: reduce(), flatten(), invoke(), modify(), possibly(), walk(),cross(), every() e keep().
Recursos adicionais
Este foi realmente um curso sobre a intuição do purrr e suas funções mais usadas. Há muita flexibilidade aqui e muitas outras aplicações. Aqui estão alguns bons recursos:
http://joshuamccrain.com/tutorials/purrr/purrr_introduction.html
https://jennybc.github.io/purrr-tutorial/index.html
http://www.leg.ufpr.br/~walmes/cursoR/data-vis/slides/07-purrr.pdf
https://colinfay.me/tags/#purrr
https://lente.dev/posts/magica-purrr/
https://livro.curso-r.com/10-funcionais.html