purrr tidyverse rstats
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.
Vamos ver com exemplos como algumas das principais funções do purrr funcionam. A função mais usada é map:
<- seq(5, 10)
x
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:
<- seq(5, 10)
x
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 %>%:
<- seq(5, 10)
x
%>% map_dbl(~ .*5) x
[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:
<- seq(2000, 2010)
x <- seq(10, 20)
y
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:
<- c("A New Hope",
movies "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")
<- c(1977, 1980, 1983, 1999, 2002, 2005, 2015, 2017, 2019)
years
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"
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(c(4, 5), is.numeric) iris
$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
%>%
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
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:
<- list(movies, years,preference = c(2, 1, 3, 7, 8, 9, 4, 6, 5))
example
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
1]][5] example[[
[1] "Attack of the Clones"
2]][5] example[[
[1] 2002
3]][5] example[[
[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]].
library(rvest)
library(tidyverse)
<- function(url){
get_album_list read_html(url) %>%
html_nodes(".col-md-12") %>%
html_nodes("a") %>%
html_attr("href")
}
<- get_album_list("http://paroles2chansons.lemonde.fr/paroles-michel-sardou/discographie.html") url_album
Ainda não tem {purrr} aqui, vamos pegar todas as informações de um album:
<- function(url){
get_album_info <- read_html(url)
page <- page %>%
date html_nodes("small") %>%
html_text() %>%
::str_replace_all("Date de Sortie : ", "") %>%
stringr::dmy()
lubridate<- page %>%
song_list html_nodes(".font-small") %>%
html_text() %>%
discard(~ .x == "Plan de site" | .x == "Mention legale" | .x == "Chansons de mariage" | .x == "Chansons d'enterrement" )
<- page %>%
url_list html_nodes(".font-small") %>%
html_attr("href") %>%
discard(~ .x == "/plan-du-site.html" | .x == "/mentions-legales.html" | .x == "/paroles-chansons-de-messe-d-enterrement/"| .x == "/paroles-chansons-de-messe-de-mariage/")
<- page %>%
album_name html_nodes(".breadcrumb") %>%
html_text() %>%
::str_extract("\t.*$") %>%
stringr::str_replace_all("\t", "")
stringr
tibble(chanson = song_list,
url = url_list,
nom = album_name,
date = date)
}
<- map_df(url_album, get_album_info) %>%
albums_infos filter(grepl("sardou", url))
outras funções do purrr interessantes: reduce(), flatten(), invoke(), modify(), possibly() e keep().
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