Ronronando com programação funcional e o pacote purrr no R
Neste post vamos falar de mais um pacote da família tidyverse: o
purrr
. O purrr
é um pacote que possui diversas ferramentas para
trabalhar com funções e vetores. Muitas funções do pacote tem como
objetivo aprimorar o uso da programação funcional (FP, do inglês
functional programming), já que o R não é uma linguagem diretamente
ligada a FP. FP é, em resumo, quando seu código é organizado em funções
que realizam as operações de que você precisa, ou seja, em muitos casos
você cria suas próprias funções. Aqui vamos tentar mostrar como usar o
purrr
focando na sua principal função: map()
.
Como já dito, o pacote pertence ao tidyverse
então você pode
carregá-lo pelo usando o comando library(tidyverse)
ou separadamente
library(purrr)
. Para os exemplos do post iremos trabalhar com um banco
de dados iris
que o próprio R possui. Você pode carregar bases de
dados que o R possui utilizando o comando data()
library(purrr)
library(dplyr)
library(ggplot2)
map()
Dentre as diversas funções do pacote purrr
uma das mais conhecidas e
utilizadas é a map()
e suas variações, nada mais justo do que começar
falando dela. Essa função (e suas variações) é usada para aplicar a
mesma ação/função a todos elementos de um objeto. Esses objetos podem
ser uma lista, um vetor, variáveis de um banco de dados, etc. Esse tipo
de função é muito útil para evitar usar loops e for
’s.
Se em algum momento você já fez uso da família de funções apply()
você
pode ter percebido uma certa semelhança entre elas. Existem algumas
discussões entre o uso das funções apply()
e map()
. Não é o foco
deste post fazer uma comparação aprofundada sobre essas funções, mas
podemos trazer alguns pontos. De forma geral, escolher qual delas usar
vai depender de um contexto. Em questão de tempo bruto de execução, a
família apply()
é ligeiramente mais rápida (a diferença é bem
pequena), porém as funções de map()
são mais convenientes. Todas as
suas variações seguem a mesma ordem de argumentos (o primeiro argumento
é sempre o vetor), diferente da família apply()
, onde os argumentos
podem mudar. Existem mais pontos a serem considerados, mas em resumo, se
ignorar questões de sintaxe e funcionalidade (as variações de map()
),
decidir qual delas usar restringe-se a usar as funções base do R ou
utilizar funções obtidas por meio de pacotes, neste caso o purrr
.
Voltando ao foco, vamos mostrar algumas variações (não todas) de
map()
.
-
map(.x, .f)
é a principal função de mapeamento e retorna uma lista. -
map_df(.x, .f)
retorna umdata.frame
. -
map_dbl(.x, .f)
retorna um vetor numérico (duplo). -
map_chr(.x, .f)
retorna um vetor de caracteres. -
map_lgl(.x, .f)
retorna um vetor lógico.
Essas funções, assim como a maiores das funções do universo tidyverse
,
tem como primeiro argumento o objeto a qual você deseja aplicar uma
certa função e o segundo argumento é a função a ser aplicada em cada
elemento do seu objeto. Para ficar mais claro vamos a uma breve
aplicação e ao decorrer do post, iremos utilizar mais as outras
funções. Vamos criar um vetor com poucos elementos para usar como
exemplo, depois criar uma função qualquer e usar o map()
para aplicar
essa função em cada elemento do vetor.
vetor_ex <- c(0, 3, 6, 9, 12)
funcao1 <- function(x){
return(x^2)
}
map(vetor_ex, funcao1)
## [[1]]
## [1] 0
##
## [[2]]
## [1] 9
##
## [[3]]
## [1] 36
##
## [[4]]
## [1] 81
##
## [[5]]
## [1] 144
map_chr(vetor_ex, funcao1)
## [1] "0.000000" "9.000000" "36.000000" "81.000000" "144.000000"
Note que usar apenas map()
, como dito antes, irá retornar uma lista
onde o primeiro elemento da saída é o resultado da aplicação da função
ao primeiro elemento da entrada e assim por diante. Já quando usamos
map_chr()
a função nos retorna um vetor de caracteres.
Vamos agora criar uma lista com alguns vetores de tamanhos diferentes.
Caso a gente queira, por exemplo, tirar a média de cada um dos vetores
dentro da lista, precisaríamos fazer isso acessando cada elemento da
lista, criando funções complexas ou utilizando for
’s e loops. Veja
abaixo como o map()
pode nos ajudar.
lista_ex <- list(c(1, 2, 3),
seq(0,100,10),
c(1:10))
lista_ex
## [[1]]
## [1] 1 2 3
##
## [[2]]
## [1] 0 10 20 30 40 50 60 70 80 90 100
##
## [[3]]
## [1] 1 2 3 4 5 6 7 8 9 10
lista_ex %>%
map(mean)
## [[1]]
## [1] 2
##
## [[2]]
## [1] 50
##
## [[3]]
## [1] 5.5
lista_ex %>%
map_dbl(sd)
## [1] 1.00000 33.16625 3.02765
Note também que fizemos o uso da função junto com o pipe (%>%
) e é
bem comum essa prática. Lembrando que já explicamos como utilizar o
pipe, caso tenha alguma dúvida você pode acessar o post que contém
essa explicação clicando
aqui.
Nos exemplos acima utilizamos uma função criada por nós e outras já
presentes dentro do R. Além dessas, também é possível utilizar o que
chamamos de funções anônimas. Uma função anônima é basicamente uma
função criada e usada, mas nunca atribuída a um objeto. Esse tipo de
função é bem útil dentro do map()
. Para usar essa função você começa
com um ~
(para indicar que você começou uma função anônima). O
argumento da função é consultado utilizando o .x
ou apenas .
,
diferente das funções normais onde você consegue usar qualquer
argumento. Para ficar mais claro vamos a um exemplo.
map_dbl(vetor_ex, ~ .x ^2)
## [1] 0 9 36 81 144
vetor_ex %>%
map_dbl(~ .x ^2)
## [1] 0 9 36 81 144
vetor_ex %>%
map_dbl(~ . ^2)
## [1] 0 9 36 81 144
vetor_ex %>%
map_dbl(~{.x ^2})
## [1] 0 9 36 81 144
Acima mostramos maneiras diferentes de utilizar funções anônimas. Todas
retornam o mesmo resultado (e deveriam). Um destaque para a última onde
foi usado {}
. O uso das chaves é interessante para organização e boas
práticas no seu código, principalmente se a sua função for mais
complexa.
Uma função muito útil também é a modify()
. Ela funciona de forma igual
à função map()
, a diferença é que ela sempre irá retornar um objeto do
mesmo tipo que o objeto de entrada. Por exemplo, se meu objeto de
entrada for um vetor de caracteres, a saída também será um vetor de
caracteres. Note que isso pode ser considerado como uma boa prática e já
fizemos um post sobre isso no software R disponível
aqui.
objeto <- data.frame(x = c(1, 10, 11),
y = c(9, 20, 30))
modify(objeto, funcao1)
## x y
## 1 1 81
## 2 100 400
## 3 121 900
A função modify()
possui uma variação interessante. A modify_if()
permite que você forneça um critério e assim, a função só será aplicada
aos elementos que atendam a esse critério. O critério torna-se o segundo
argumento da função, ou seja, o primeiro argumento continua sendo o
objeto de entrada, o segundo passa a ser o critério e o terceiro
argumento é a função. Para ficar mais claro vamos a um exemplo
modify_if(vetor_ex,
~ . >= 9,
funcao1)
## [1] 0 3 6 81 144
Veja que a função só foi aplicada para valores maiores ou iguais a 9.
Agora que temos uma noção de como funciona as funções map()
, vamos
deixar as coisas um pouco mais interessantes trabalhando com uma base de
dados. Para os exemplos do post iremos trabalhar com um banco de dados
“iris” que o próprio R possui. Você pode olhar bases de dados que o R
possui utilizando o comando data()
dados <- iris
# visualizando os 10 primeiros dados da tabela
knitr::kable(head(dados, 10))
Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | Species |
---|---|---|---|---|
5.1 | 3.5 | 1.4 | 0.2 | setosa |
4.9 | 3.0 | 1.4 | 0.2 | setosa |
4.7 | 3.2 | 1.3 | 0.2 | setosa |
4.6 | 3.1 | 1.5 | 0.2 | setosa |
5.0 | 3.6 | 1.4 | 0.2 | setosa |
5.4 | 3.9 | 1.7 | 0.4 | setosa |
4.6 | 3.4 | 1.4 | 0.3 | setosa |
5.0 | 3.4 | 1.5 | 0.2 | setosa |
4.4 | 2.9 | 1.4 | 0.2 | setosa |
4.9 | 3.1 | 1.5 | 0.1 | setosa |
Como estamos trabalhando com uma base de dados, as funções serão
aplicadas em cada coluna. Por exemplo, ao aplicar uma função para
resumir de alguma maneira os dados, ela irá fazer isso para cada uma das
colunas. Vamos começar utilizando a função n_distinct()
do pacote
dplyr
que nos retorna o número de valores distintos em cada coluna.
dados %>%
map(n_distinct)
## $Sepal.Length
## [1] 35
##
## $Sepal.Width
## [1] 23
##
## $Petal.Length
## [1] 43
##
## $Petal.Width
## [1] 22
##
## $Species
## [1] 3
Observe que map()
aplicou a função desejada em cada uma das 5
variáveis do nosso banco de dados e retornou o valor correspondente.
Vamos começar a fazer algo mais elaborado. Podemos querer retornar
algumas medidas resumos ou outras informações gerais sobre nossos dados
em uma base de dados. Esse é um momento bem legal e útil de utilizar o
map_df()
, uma das funções mais poderosas do pacote. Basicamente
utilizamos a estrutura map_df( ~ data_frame(x = .x" )
, onde x = o nome
da coluna e .x a função anônima. Pra ficar mais claro, vamos à pratica.
dados1 <- select_if(dados, is.numeric)
dados1 <- dados1 %>% map_df(~ (data.frame(v_distintos = n_distinct(.x),
classe = class(.x),
media = round(mean(.x), digits = 2),
desvio_p = round(sd(.x), digits = 2),
minimo = min(.x),
maximo = max(.x))),
.id = "variavel")
dados1
No exemplo acima, começamos utilizando a função select_if()
do pacote
dplyr
para pegar do banco de dados original apenas as variáveis do
tipo númericas e armazenamos em um outro banco de dados (apenas para
facilitar o entendimento). Após isso já começamos a pensar na medidas
que queremos para acrescentar no banco de dados. Adicionamos então os
valores distintos de cada variável, as classes (apenas para mostrar que
select_if()
fez o que queriamos) e algumas medidas resumo como média,
desvio padrão, valores máximos e mínimos. Uma observação importante é
que ao utilizar essa função de map()
, o nome das variáveis são
perdidos, por isso ao fim do código foi utilizado o .id
(argumento de
map_df()
) para incluir o nome das variáveis. Veja o
resultado.
knitr::kable(head(dados1))
variavel | v_distintos | classe | media | desvio_p | minimo | maximo |
---|---|---|---|---|---|---|
Sepal.Length | 35 | numeric | 5.84 | 0.83 | 4.3 | 7.9 |
Sepal.Width | 23 | numeric | 3.06 | 0.44 | 2.0 | 4.4 |
Petal.Length | 43 | numeric | 3.76 | 1.77 | 1.0 | 6.9 |
Petal.Width | 22 | numeric | 1.20 | 0.76 | 0.1 | 2.5 |
map2()
Após obtermos uma certa noção de como funcionam as funções de map()
podemos falar de sua outra versão, a map2()
. Essa função segue os
mesmos critérios que a map()
“original”, porém permite que você
trabalhe não só com um, mas com dois objetos. Em map2()
também
conseguimos especificar o tipo de saída da função, segue abaixo a lista
de algumas das variações da função.
-
map2 (.x, .y, .f, ...)
-
map2_dfc (.x, .y, .f, ...)
-
map2_dbl (.x, .y, .f, ...)
-
map2_chr (.x, .y, .f, ...)
-
map2_lgl (.x, .y, .f, ...)
Lembrando que .x
e .y
são os vetores da função e devem ter o mesmo
tamanho, caso contrário a função retornará uma mensagem de erro.
dados3 <- dados %>%
group_by(Sepal.Width, Species) %>%
summarise(n = n())
## `summarise()` has grouped output by 'Sepal.Width'. You can override using the `.groups` argument.
largura <- dados3$Sepal.Width
especie <- dados3$Species
fmp2 <- function(x, y) paste("Espécie:", x, "largura:", y)
map2(especie, largura, fmp2) %>% head(5)
## [[1]]
## [1] "Espécie: versicolor largura: 2"
##
## [[2]]
## [1] "Espécie: versicolor largura: 2.2"
##
## [[3]]
## [1] "Espécie: virginica largura: 2.2"
##
## [[4]]
## [1] "Espécie: setosa largura: 2.3"
##
## [[5]]
## [1] "Espécie: versicolor largura: 2.3"
Acima criamos dois vetores, cada um contendo uma variável do banco de
dados e depois criamos uma função simples, que irá concatenar, nesse
caso, os dois vetores com algumas frases. Note que a função foi aplicada
aos dois vetores. Uma observação é que no começo do exemplo criamos um
outro banco de dados (dados3) com apenas as duas variáveis que foi usada
no exemplo e agrupamos elas utlizando group_by()
esummarise()
. Isso
foi feito apenas para que fosse possível ver os resultados com
observações diferentes na saida da função.
É possível utilizar o map()
para mais de dois objetos. Neste caso não
existe um map3()
ou map4()
mas sim o pmap()
. pmap()
é
basicamente uma generalização de map2()
para 3 ou mais objetos. Essa
função possui uma pequena diferença, nesse caso você não irá especificar
n objetos dentro da função, mas sim apenas uma única lista que contém
todos os vetores (ou listas).
Um uso um pouco mais elaborado de “map2()”
Para esse exemplo, vejamos a função split
que nos será útil. Usaremos
essa função que divide os dados de entrada (x) em grupos diferentes (f).
Um breve exemplo de como a função funciona.
sp <- c(a = 1, b = 25, b = "exemplo", a = "split", a = 3)
sp
## a b b a a
## "1" "25" "exemplo" "split" "3"
split(sp, f = names(sp))
## $a
## a a a
## "1" "split" "3"
##
## $b
## b b
## "25" "exemplo"
Note que o objeto sp
foi separado em duas listas de acordo com os
nomes do vetor.
Agora considerando os dados iris
, separamos os dados em listas de
acordo com as espécies. No caso do nosso exemplo o objeto “especies” vai
ser uma lista com as 3 especies presentes no banco de dados iris.
especies <- iris %>%
split(.$Species)
Uma forma de observar o resultado obtido é verificar a classe e as
dimensões de cada uma das partes obtidas, utilizando a função map
.
especies %>%
map_chr(class)
## setosa versicolor virginica
## "data.frame" "data.frame" "data.frame"
especies %>%
map(dim)
## $setosa
## [1] 50 5
##
## $versicolor
## [1] 50 5
##
## $virginica
## [1] 50 5
Para checar que a divisão foi feita de forma correta, podemos avaliar o banco de dados original e contar a quantidade de observações para cada tipo de espécie.
table(iris$Species)
##
## setosa versicolor virginica
## 50 50 50
modelos <- especies %>%
map(~ lm (Sepal.Length ~ Petal.Length, .))
Depois partimos para criar o nosso modelo. Com ajuda da função map()
,
utilizamos a função lm()
para ajustar um modelo de regressão linear. A
função lm()
possui vários argumentos e maneiras de utilizar, não é
nosso foco aprofundar nessa função, mas você pode saber mais clicando
aqui.
De maneira geral, fornecemos à função uma variável preditora (ou
independente) que nesse caso foi Petal.Length
e a variável dependente
(aquela que estamos tentando explicar) que foi Sepal.Length
. O segundo
argumento é o banco de dados, nesse caso utilizamos .
, pois
gostaríamos de ajustar um modelo linear para cada elemento da lista de
banco de dados. Vamos imprimir o resultado para entender melhor o que
foi feito.
especies %>%
map(~ lm (Sepal.Length ~ Petal.Length, .))
## $setosa
##
## Call:
## lm(formula = Sepal.Length ~ Petal.Length, data = .)
##
## Coefficients:
## (Intercept) Petal.Length
## 4.2132 0.5423
##
##
## $versicolor
##
## Call:
## lm(formula = Sepal.Length ~ Petal.Length, data = .)
##
## Coefficients:
## (Intercept) Petal.Length
## 2.4075 0.8283
##
##
## $virginica
##
## Call:
## lm(formula = Sepal.Length ~ Petal.Length, data = .)
##
## Coefficients:
## (Intercept) Petal.Length
## 1.0597 0.9957
Note que a função estimou uma reta com dois parâmetros: intercepto e a
inclinação da reta segundo a variável preditora. Essa reta tenta
explicar o comprimento médio da pétala (Petal.Length) como função do
comprimento da sépala (Sepal.Length). Repare que os interceptos são bem
distintos entre os três ajustes e a inclinação entre as espécies
versicolor
e virginica
são bem parecidas, de acordo com o resultado
impresso. Esse resultado pode ser visualizado com a ajuda do pacote
ggplot2
com o seguinte código.
ggplot(iris, aes(Petal.Length, Sepal.Length, colour = Species)) +
geom_point() +
geom_smooth(se = FALSE, method = lm)
## `geom_smooth()` using formula 'y ~ x'
Note que as retas estimam uma relação linear entre o valor médio do comprimento da pétala como função do comprimento da sépala, para cada uma das espécies.
predicoes <- map2(modelos, especies, predict)
Por fim fizemos o uso de map2()
com os os objetos “modelos” e
“especies”, e aplicamos a função predict()
. Em resumo, predict()
serve para prever valores tendo como base um modelo (você pode se
aprofundar nessa função clicando
aqui).
Nesse caso, estamos obtendo as predições de cada um dos modelos
estimados, para cada uma das observações considerando os diferentes
bancos de dados, armazenados no objeto especies
. Podemos visualizar os
resultados obtidos fazendo um gráfico simples de pontos e comparar com o
gráfico anterior do modelo estimado.
1:length(predicoes) %>%
map(~ plot(x = especies[[.]]$Petal.Length,
y = predicoes[[.]], xlab = "Petal.Length", ylab = "Valor predito")) %>%
invisible()
Nesse exemplo, nós utilizamos o comprimento do objeto predicoes
para
acessar as diferentes predições e suas respectivas variáveis preditoras.
Veja que como os dois objetos especies
e predicoes
são uma lista,
nós utilizamos o operador [[]]
para acessar suas posições. Note que
para cada valor de x
(Petal.Length
) existe um valor predito segundo
o modelo ajustado e todos estão descritos de acordo com uma reta, que
foi aquela estimada segundo a função lm
. A função invisible()
foi
utilizada somente para evitar a impressão de uma lista de valores NULL
ao final do processo. Veja como seria o resultado sem essa função e
compare.
Manipulações de listas
Para mostrar algumas funções do purrr
que nos auxiliam na manipulação
de listas, vamos transformar o banco de dados que estamos usando
(iris
) em uma lista de banco de dados. Já fizemos isso no exemplo
anterior, mas aqui para garantir que seja fácil de seguir e visualizar,
vamos usar apenas as primeiras 5 linhas de cada espécie.
dados4 <- iris %>%
split(.$Species) %>%
map(~ slice_head(., n = 5))
dados4
## $setosa
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1 5.1 3.5 1.4 0.2 setosa
## 2 4.9 3.0 1.4 0.2 setosa
## 3 4.7 3.2 1.3 0.2 setosa
## 4 4.6 3.1 1.5 0.2 setosa
## 5 5.0 3.6 1.4 0.2 setosa
##
## $versicolor
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1 7.0 3.2 4.7 1.4 versicolor
## 2 6.4 3.2 4.5 1.5 versicolor
## 3 6.9 3.1 4.9 1.5 versicolor
## 4 5.5 2.3 4.0 1.3 versicolor
## 5 6.5 2.8 4.6 1.5 versicolor
##
## $virginica
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1 6.3 3.3 6.0 2.5 virginica
## 2 5.8 2.7 5.1 1.9 virginica
## 3 7.1 3.0 5.9 2.1 virginica
## 4 6.3 2.9 5.6 1.8 virginica
## 5 6.5 3.0 5.8 2.2 virginica
keep(), discard() e compact()
A função keep()
e discard()
são bem parecidas e opostas. keep()
mantém apenas os elementos de uma lista que satisfazem uma determinada
condição, enquanto discard()
faz exatamente o oposto (descarta
quaisquer elementos que satisfaçam a condição lógica). Essas funções
possuem 2 argumentos principais, .x
uma lista ou vetor e .p
uma
função. Apenas aqueles elementos .p
avaliados como TRUE
serão
mantidos ou descartados. Também é possível, como terceiro argumento,
fornecer argumentos adicionais transmitidos para “.p”. A função
compact()
tem argumentos parecidos com as funções anteriores, a
diferença é que para essa função, o .p
é uma função que é aplicada a
cada elemento de .x
e somente aqueles elementos .p
avaliados como um
vetor vazio (NULL
ou listas com comprimento zero) serão descartados.
dados4 %>%
keep(~ {mean(.x$Petal.Width) > 0.5})
## $versicolor
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1 7.0 3.2 4.7 1.4 versicolor
## 2 6.4 3.2 4.5 1.5 versicolor
## 3 6.9 3.1 4.9 1.5 versicolor
## 4 5.5 2.3 4.0 1.3 versicolor
## 5 6.5 2.8 4.6 1.5 versicolor
##
## $virginica
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1 6.3 3.3 6.0 2.5 virginica
## 2 5.8 2.7 5.1 1.9 virginica
## 3 7.1 3.0 5.9 2.1 virginica
## 4 6.3 2.9 5.6 1.8 virginica
## 5 6.5 3.0 5.8 2.2 virginica
dados4 %>%
discard(~ {mean(.x$Petal.Width) > 0.5})
## $setosa
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1 5.1 3.5 1.4 0.2 setosa
## 2 4.9 3.0 1.4 0.2 setosa
## 3 4.7 3.2 1.3 0.2 setosa
## 4 4.6 3.1 1.5 0.2 setosa
## 5 5.0 3.6 1.4 0.2 setosa
list(a = "a", b = NULL, c = integer(0), d = NA, e = list()) %>%
compact()
## $a
## [1] "a"
##
## $d
## [1] NA
every(), some() e none()
Caso a gente queira saber se todos, alguns ou nenhum dos elementos de
uma lista satisfazem um determinado critério, podemos usar as funções
every()
, some()
e none()
, respectivamente. Essas funções são
básicas e possuem apenas dois argumentos, sendo o primeiro uma lista ou
vetor, e o segundo argumento é o critério. A saída da função será
valores lógicos (TRUE
ou FALSE
).
dados4 %>%
every(~{mean(.x$Petal.Width) > 0.5})
## [1] FALSE
dados4 %>%
some(~{mean(.x$Petal.Width) > 0.5})
## [1] TRUE
dados4 %>%
none(~{mean(.x$Petal.Width) > 20})
## [1] TRUE
Aqui podemos puxar um gancho para uma outra fução de pesquisa que também
retorna um valor lógico, has_element
. Basicamente serve para testar se
uma lista ou vetor contém um determinado objeto, a função tem o seguinte
formato:
has_element(.x, .y)
onde .x
é a lista ou vetor e .y
o objeto para testar
has_element(dados$Petal.Width, 1.0)
## [1] TRUE
has_element(dados$Species, 1.0)
## [1] FALSE
detect()
A função detect()
serve para encontrar o valor ou posição da primeira
correspondência. Em outras palavras, ela retorna o primeiro elemento que
passa no teste ou condição lógica.
detect(.x, .f, ..., .right = FALSE, .p)
.x
é o argumento da função onde você fornece uma lista ou vetor
atômico, .f
uma função, fórmula ou vetor atômico.
Se uma fórmula , por exemplo ~ .x + 2
, ela é convertida em uma função.
Existem três maneiras de se referir aos argumentos:
-
Para uma função de argumento único, use
.
-
Para uma função de dois argumentos, use
.x
e.y
-
Para mais argumentos, utilização
..1
,..2
,..3
etc
Vamos a um exemplo simples para entender melhor a função.
exemplo <- list( "exemplo", 4, 2, "purrr" , "DasLab" , 3)
detect(exemplo, is.numeric)
## [1] 4
Como dito antes, ela irá retornar o primeiro valor que responde ao
critério usado (segundo argumento). Quando se trata de funções deste
tipo (detectar), o pacote stringr
pode ser mais interessante. Já
fizemos um post muito massa sobre este pacote, basta clicar
aqui para acessá-lo.
O purrr
possui diversas funções, muitas delas não aparecem nesse post.
Aqui focamos em explicar as funções que são mais utilizadas e algumas
mais simples. As funções de purrr
possuem uma sintaxe muito parecida,
então se entender bem as funções que trouxemos aqui, as outras serão bem
mais fáceis de entender. Vale lembrar que você pode acessar o cheatsheet
para ver todas as funções clicando
aqui.
Referências
https://www.rebeccabarter.com/blog/2019-08-19_purrr/
https://jennybc.github.io/purrr-tutorial/
https://www.r-bloggers.com/2020/05/one-stop-tutorial-on-purrr-package-in-r/