Capítulo 5 Tidyverse
Pré-requisitos do capítulo
Pacotes e dados que serão utilizados neste capítulo.
## Pacotes
library(tidyverse)
library(here)
library(ggplot2)
library(purrr)
library(tibble)
library(dplyr)
library(tidyr)
library(stringr)
library(readr)
library(forcats)
library(palmerpenguins)
library(lubridate)
## Dados
penguins <- palmerpenguins::penguins
penguins_raw <- palmerpenguins::penguins_raw
tidy_anfibios_locais <- ecodados::tidy_anfibios_locais
5.1 Contextualização
Como todo idioma, a linguagem R vem passando por transformações nos últimos anos. Grande parte dessas mudanças estão dentro do paradigma de Ciência de Dados (Data Science), uma nova área de conhecimento que vem se moldando a partir do desenvolvimento da sociedade em torno da era digital e da grande quantidade de dados gerados e disponíveis pela internet, de onde advém os pilares das inovações tecnológicas: Big Data, Machine Learning e Internet of Things. A grande necessidade de computação para desenvolver esse novo paradigma colocaram o R e o python como as principais linguagens de programação frente a esses novos desafios. Apesar de não serem as únicas ferramentas utilizadas para esse propósito, elas rapidamente se tornaram uma das melhores escolhas, dado vários fatores como: ser de código-aberto e gratuitas, possuir grandes comunidades contribuidoras, ser linguagens de interpretação (orientadas a objeto) e relativamente fáceis de serem aprendidas e aplicadas.
Essas mudanças e expansões na utilização da linguagem R para a Ciência de Dados começaram a ser implementadas principalmente devido a um pesquisador: Hadley Wickham, que iniciou sua contribuição à comunidade R com o desenvolvimento do já consagrado pacote ggplot2
(Wickham 2016) para a composição de gráficos no R (ver mais no Capítulo 6), baseado na gramática de gráficos (Wilkinson and Wills 2005). Depois disso, Wickham dedicou-se ao desenvolvimento do pensamento de uma nova abordagem dentro da manipulação de dados, denominada Tidy Data (Dados organizados) (Wickham 2014), na qual focou na limpeza e organização dos mesmos. A ideia postula que dados estão tidy
quando: i) variáveis estão nas colunas, ii) observações estão nas linhas e iii) valores estão nas células, sendo que para esse último, não deve haver mais de um valor por célula.
A partir dessas ideias, o tidyverse foi operacionalizado no R como uma coleção de pacotes que atuam no fluxo de trabalho comum da ciência de dados: importação, manipulação, exploração, visualização, análise e comunicação de dados e análises (Wickham et al. 2019) (Figura 5.1). O principal objetivo do tidyverse é aproximar a linguagem para melhorar a interação entre ser humano e computador sobre dados, de modo que os pacotes compartilham uma filosofia de design de alto nível e gramática, além da estrutura de dados de baixo nível (Wickham et al. 2019). As principais leituras sobre o tema no R são os artigos “Tidy Data” (Wickham 2014) e “Welcome to the Tidyverse” (Wickham et al. 2019), e o livro “R for Data Science” (Wickham and Grolemund 2017), além do Tidyverse que possui muito mais informações.
5.2 tidyverse
Uma vez instalado e carregado, o pacote tidyverse
disponibiliza um conjunto de ferramentas através de vários pacotes. Esses pacotes compartilham uma filosofia de design, gramática e estruturas. Podemos entender o tidyverse como um “dialeto novo” para a linguagem R, onde tidy quer dizer organizado, arrumado, ordenado, e verse é universo. A seguir, listamos os principais pacotes e suas funcionalidades.
-
readr
: importa dados tabulares (e.g..csv
e.txt
) -
tibble
: implementa a classetibble
-
tidyr
: transformação de dados paratidy
-
dplyr
: manipulação de dados -
stringr
: manipulação de caracteres -
forcats
: manipulação de fatores -
ggplot2
: possibilita a visualização de dados -
purrr
: disponibiliza ferramentas para programação funcional
Além dos pacotes principais, fazemos também menção a outros pacotes que estão dentro dessa abordagem e que trataremos ainda neste capítulo, em outro momento do livro, ou que você leitor(a) deve se familiarizar. Alguns pacotes compõem o tidyverse outros são mais gerais, entretanto, todos estão envolvidos de alguma forma com ciência de dados.
-
readxl
ewritexl
: importa e exporta dados tabulares (.xlsx) -
janitor
: examina e limpa dados sujos -
DBI
: interface de banco de dados R -
haven
: importa e exporta dados do SPSS, Stata e SAS -
httr
: ferramentas para trabalhar com URLs e HTTP -
rvest
: coleta facilmente (raspagem de dados) páginas da web -
xml2
: trabalha com arquivos XML -
jsonlite
: um analisador e gerador JSON simples e robusto para R -
hms
: hora do dia -
lubridate
: facilita o tratamento de datas -
magrittr
: provê os operadores pipe (%>%
,%$%
,%<>%
) -
glue
: facilita a combinação de dados e caracteres -
rmarkdown
: cria documentos de análise dinâmica que combinam código, saída renderizada (como figuras) e texto -
knitr
: projetado para ser um mecanismo transparente para geração de relatórios dinâmicos com R -
shiny
: framework de aplicativo Web para R -
flexdashboard
: painéis interativos para R -
here
: facilita a definição de diretórios -
usethis
: automatiza tarefas durante a configuração e desenvolvimento de projetos (Git, ‘GitHub’ e Projetos RStudio) -
data.table
: pacote que fornece uma versão de alto desempenho dodata.frame
(importar, manipular e expotar) -
reticulate
: pacote que fornece ferramentas para integrar Python e R -
sparklyr
: interface R para Apache Spark -
broom
: converte objetos estatísticos em tibbles organizados -
modelr
: funções de modelagem que funcionam com o pipe -
tidymodels
: coleção de pacotes para modelagem e aprendizado de máquina usando os princípios do tidyverse
Destacamos a grande expansão e aplicabilidade dos pacotes rmarkdown, knitr e bookdown, que permitiram a escrita deste livro usando essas ferramentas e linguagem de marcação, chamada Markdown.
Para instalar os principais pacotes que integram o tidyverse podemos instalar o pacote tidyverse
.
## Instalar o pacote tidyverse
install.packages("tidyverse")
Quando carregamos o pacote tidyverse
podemos notar uma mensagem indicando quais pacotes foram carregados, suas respectivas versões e os conflitos com outros pacotes.
## Carregar o pacote tidyverse
library(tidyverse)
#> ── Attaching packages ───────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.1 ──
#> ✔ ggplot2 3.3.6 ✔ purrr 0.3.4
#> ✔ tibble 3.1.7 ✔ dplyr 1.0.9
#> ✔ tidyr 1.2.0 ✔ stringr 1.4.0
#> ✔ readr 2.1.2 ✔ forcats 0.5.1
#> ── Conflicts ──────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
#> ✖ dplyr::filter() masks stats::filter()
#> ✖ dplyr::lag() masks stats::lag()
Podemos ainda listar todos os pacotes do tidyverse com a função tidyverse::tidyverse_packages()
.
## Listar todos os pacotes do tidyverse
tidyverse::tidyverse_packages()
#> [1] "broom" "cli" "crayon" "dbplyr" "dplyr" "dtplyr" "forcats" "googledrive"
#> [9] "googlesheets4" "ggplot2" "haven" "hms" "httr" "jsonlite" "lubridate" "magrittr"
#> [17] "modelr" "pillar" "purrr" "readr" "readxl" "reprex" "rlang" "rstudioapi"
#> [25] "rvest" "stringr" "tibble" "tidyr" "xml2" "tidyverse"
Também podemos verificar se os pacotes estão atualizados, senão, podemos atualizá-los com a função tidyverse::tidyverse_update()
.
## Verificar e atualizar os pacotes do tidyverse
tidyverse::tidyverse_update(repos = "http://cran.us.r-project.org")
Todas as funções dos pacotes tidyverse usam fonte minúscula e _
(underscore) para separar os nomes internos das funções, seguindo a mesma sintaxe do Python (“Snake Case”). Neste sentido de padronização, é importante destacar ainda que existe um guia próprio para que os scripts sigam a recomendação de padronização, o The tidyverse style guide, criado pelo próprio Hadley Wickham. Para pessoas que desenvolvem funções e pacotes existe o Tidyverse design guide criado pelo Tidyverse team.
Por fim, para evitar possíveis conflitos de funções com o mesmo nome entre pacotes, recomendamos fortemente o hábito de usar as funções precedidas do operador ::
e o respectivo pacote. Assim, garante-se que a função utilizada é referente ao pacote daquela função. Segue um exemplo com as funções apresentadas anteriormente.
## Funções seguidas de seus respectivos pacotes
readr::read_csv()
readr::read_tsv()
tibble::as_tibble()
dplyr::left_join()
dplyr::group_by()
Seguindo essas ideias do novo paradigma da Ciência de Dados, outro conjunto de pacotes foi desenvolvido, chamado de tidymodels
que atuam no fluxo de trabalho da análise de dados em ciência de dados: separação e reamostragem, pré-processamento, ajuste de modelos e métricas de performasse de ajustes. Por razões de espaço e especificidade, não entraremos em detalhes desses pacotes.
Seguindo a estrutura da Figura 5.1, iremos ver nos itens das próximas seções como esses passos são realizados com funções de cada pacote.
5.3 here
Dentro do fluxo de trabalho do tidyverse, devemos sempre trabalhar com Projetos do RStudio (ver Capítulo 4). Junto com o projeto, também podemos fazer uso do pacote here
. Ele permite construir caminhos para os arquivos do projeto de forma mais simples e com maior reprodutibilidade.
Esse pacote cobre o ponto de mudarmos o diretório de trabalho que discutimos no Capítulo 4, dado que muitas vezes mudar o diretório com a função setwd()
tende a ser demorado e tedioso, principalmente quando se trata de um script em que várias pessoas estão trabalhando em diferentes computadores e sistemas operacionais. Além disso, ele elimina a questão da fragilidade dos scripts, pois geralmente um script está com os diretórios conectados exatamente a um lugar e a um momento. Por fim, ele também simplifica o trabalho com subdiretórios, facilitando importar ou exportar arquivos para subdiretórios.
Seu uso é relativamente simples: uma vez criado e aberto o RStudio pelo Projeto do RStudio, o diretório automaticamente é definido para o diretório do projeto. Depois disso, podemos usar a função here::here()
para definir os subdiretórios onde estão os dados. O exemplo da aplicação fica para a seção seguinte, quando iremos de fato importar um arquivo tabular para o R. Logo abaixo, mostramos como instalar e carregar o pacote here
.
## Instalar
install.packages("here")
## Carregar
library(here)
5.4 readr, readxl e writexl
Dado que possuímos um conjunto de dados e que geralmente esse conjunto de dados estará no formato tabular com umas das extensões: .csv, .txt ou .xlsx, usaremos o pacote readr
ou readxl
para importar esses dados para o R. Esses pacotes leem e escrevem grandes arquivos de forma mais rápida, além de fornecerem medidores de progresso de importação e exportação, e imprimir a informação dos modos das colunas no momento da importação. Outro ponto bastante positivo é que também classificam automaticamente o modo dos dados de cada coluna, i.e., se uma coluna possui dados numéricos ou apenas texto, essa informação será considerada para classificar o modo da coluna toda. A classe do objeto atribuído quando lido por esses pacotes é automaticamente um tibble
, que veremos melhor na seção seguinte. Todas as funções deste pacote são listadas na página de referência do pacote.
Usamos as funções readr::read_csv()
e readr::write_csv()
para importar e exportar arquivos .csv do R, respectivamente. Para dados com a extensão .txt, podemos utilizar as funções readr::read_tsv()
ou ainda readr::read_delim()
. Para arquivos tabulares com a extensão .xlsx, temos de instalar e carregar dois pacotes adicionais: readxl
e writexl
, dos quais usaremos as funções readxl::read_excel()
, readxl::read_xlsx()
ou readxl::read_xls()
para importar dados, atentado para o fato de podermos indicar a aba com os dados com o argumento sheet
, e writexl::write_xlsx()
para exportar.
Se o arquivo .csv foi criado com separador de decimais sendo .
e separador de colunas sendo ,
, usamos as funções listadas acima normalmente. Caso seja criado com separador de decimais sendo ,
e separador de colunas sendo ;
, devemos usar a função readr::read_csv2()
para importar e readr::write_csv2()
para exportar nesse formato, que é mais comum no Brasil.
Para exemplificar como essas funções funcionam, vamos importar novamente os dados de comunidades de anfíbios da Mata Atlântica (Vancine et al. 2018), que fizemos o download no Capítulo 4. Estamos usando a função readr::read_csv()
, indicando os diretórios com a função here::here()
, e a classe do arquivo é tibble
. Devemos atentar para o argumento locale = readr::locale(encoding = "latin1")
,que selecionamos aqui como latin1
para corrigir um erro de caracteres, que o autor dos dados cometeu quando publicou esse data paper.
## Importar locais
tidy_anfibios_locais <- readr::read_csv(
here::here("dados", "tabelas", "ATLANTIC_AMPHIBIANS_sites.csv"),
locale = readr::locale(encoding = "latin1")
)
#> Rows: 1163 Columns: 25
#> ── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
#> Delimiter: ","
#> chr (13): id, record, sampled_habitat, active_methods, passive_methods, complementary_methods, period, country, state, state_abbreviation, mun...
#> dbl (12): reference_number, species_number, month_start, year_start, month_finish, year_finish, effort_months, latitude, longitude, altitude, ...
#>
#> ℹ Use `spec()` to retrieve the full column specification for this data.
#> ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Caso o download não funcione ou haja problemas com a importação, disponibilizamos os dados também no pacote ecodados
.
## Importar os dados pelo pacote ecodados
tidy_anfibios_locais <- ecodados::tidy_anfibios_locais
head(tidy_anfibios_locais)
Para se aprofundar no tema, recomendamos a leitura do Capítulo 11 Data import de Wickham & Grolemund (2017).
5.5 tibble
O tibble
(tbl_sf
) é uma versão aprimorada do data frame (data.frame
). Ele é a classe aconselhada para que as funções do tidyverse funcionem melhor sobre conjuntos de dados tabulares importados para o R.
Geralmente, quando utilizamos funções tidyverse para importar dados para o R, é essa classe que os dados adquirem depois de serem importados. Além da importação de dados, podemos criar um tibble no R usando a função tibble::tibble()
, semelhante ao uso da função data.frame()
. Podemos ainda converter um data.frame
para um tibble
usando a função tibble::as_tibble()
. Entretanto, em alguns momentos precisaremos da classe data.frame
para algumas funções específicas, e podemos converter um tibble
para data.frame
usando a função tibble::as_data_frame()
.
Existem duas diferenças principais no uso do tibble
e do data.frame
: impressão e subconjunto. Objetos da classe tibbles
possuem um método de impressão que mostra a contagem do número de linhas e colunas, e apenas as primeiras 10 linhas e todas as colunas que couberem na tela no console, além dos modos ou tipos das colunas. Dessa forma, cada coluna ou variável, pode ser do modo numbers (int
ou dbl
), character (chr
), logical (lgl
), factor (fctr
), date + time (dttm
) e date (date
), além de outras inúmeras possibilidades.
Todas as funções deste pacote são listadas na página de referência do pacote.
## Tibble - impressão
tidy_anfibios_locais
#> # A tibble: 1,163 × 25
#> id reference_number species_number record sampled_habitat active_methods passive_methods complementary_meth… period month_start year_start
#> <chr> <dbl> <dbl> <chr> <chr> <chr> <chr> <chr> <chr> <dbl> <dbl>
#> 1 amp1001 1001 19 ab fo,ll as pt <NA> mo,da… 9 2000
#> 2 amp1002 1002 16 co fo,la,ll as pt <NA> mo,da… 12 2007
#> 3 amp1003 1002 14 co fo,la,ll as pt <NA> mo,da… 12 2007
#> 4 amp1004 1002 13 co fo,la,ll as pt <NA> mo,da… 12 2007
#> 5 amp1005 1003 30 co fo,ll,br as <NA> <NA> mo,da… 7 1988
#> 6 amp1006 1004 42 co tp,pp,la,ll,is <NA> <NA> <NA> <NA> NA NA
#> 7 amp1007 1005 23 co sp as <NA> <NA> <NA> 4 2007
#> 8 amp1008 1005 19 co sp,la,sw as,sb,tr <NA> <NA> tw,ni 4 2007
#> 9 amp1009 1005 13 ab fo <NA> pt <NA> mo,da… 4 2007
#> 10 amp1010 1006 1 ab fo <NA> pt <NA> mo,da… 5 2011
#> # … with 1,153 more rows, and 14 more variables: month_finish <dbl>, year_finish <dbl>, effort_months <dbl>, country <chr>, state <chr>,
#> # state_abbreviation <chr>, municipality <chr>, site <chr>, latitude <dbl>, longitude <dbl>, coordinate_precision <chr>, altitude <dbl>,
#> # temperature <dbl>, precipitation <dbl>
Para o subconjunto, como vimos no Capítulo 4, para selecionar colunas e linhas de objetos bidimensionais podemos utilizar os operadores []
ou [[]]
, associado com números separados por vírgulas ou o nome da coluna entre aspas, e o operador $
para extrair uma coluna pelo seu nome. Comparando um data.frame
a um tibble
, o último é mais rígido na seleção das colunas: ele nunca faz correspondência parcial e gera um aviso se a coluna que você está tentando acessar não existe.
## Tibble - subconjunto
tidy_anfibios_locais$ref
#> Warning: Unknown or uninitialised column: `ref`.
#> NULL
Por fim, podemos “espiar” os dados utilizando a função tibble::glimpse()
para ter uma noção geral de número de linhas, colunas, e conteúdo de todas as colunas. Essa é a função tidyverse da função R Base str()
.
## Espiar os dados
tibble::glimpse(tidy_anfibios_locais[, 1:10])
#> Rows: 1,163
#> Columns: 10
#> $ id <chr> "amp1001", "amp1002", "amp1003", "amp1004", "amp1005", "amp1006", "amp1007", "amp1008", "amp1009", "amp1010", "amp…
#> $ reference_number <dbl> 1001, 1002, 1002, 1002, 1003, 1004, 1005, 1005, 1005, 1006, 1006, 1006, 1006, 1006, 1006, 1006, 1006, 1006, 1006, …
#> $ species_number <dbl> 19, 16, 14, 13, 30, 42, 23, 19, 13, 1, 1, 2, 4, 4, 6, 5, 8, 2, 5, 1, 2, 2, 1, 2, 2, 1, 2, 3, 7, 8, 7, 7, 7, 7, 7, …
#> $ record <chr> "ab", "co", "co", "co", "co", "co", "co", "co", "ab", "ab", "ab", "ab", "ab", "ab", "ab", "ab", "ab", "ab", "ab", …
#> $ sampled_habitat <chr> "fo,ll", "fo,la,ll", "fo,la,ll", "fo,la,ll", "fo,ll,br", "tp,pp,la,ll,is", "sp", "sp,la,sw", "fo", "fo", "fo", "fo…
#> $ active_methods <chr> "as", "as", "as", "as", "as", NA, "as", "as,sb,tr", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
#> $ passive_methods <chr> "pt", "pt", "pt", "pt", NA, NA, NA, NA, "pt", "pt", "pt", "pt", "pt", "pt", "pt", "pt", "pt", "pt", "pt", "pt", "p…
#> $ complementary_methods <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
#> $ period <chr> "mo,da,tw,ni", "mo,da,tw,ni", "mo,da,tw,ni", "mo,da,tw,ni", "mo,da,ni", NA, NA, "tw,ni", "mo,da,tw,ni", "mo,da,tw,…
#> $ month_start <dbl> 9, 12, 12, 12, 7, NA, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 3, 3, 3, 3,…
Para se aprofundar no tema, recomendamos a leitura do Capítulo 10 Tibbles de Wickham & Grolemund (2017).
5.6 magrittr (pipe - %>%)
O operador pipe %>%
permite o encadeamento de várias funções, eliminando a necessidade de criar objetos para armazenar resultados intermediários. Dessa forma, pipes são uma ferramenta poderosa para expressar uma sequência de múltiplas operações.
O operador pipe %>%
vem do pacote magrittr
, entretanto, todos os pacotes no tidyverse automaticamente tornam o pipe disponível. Essa função torna os códigos em R mais simples, pois podemos realizar múltiplas operações em uma única linha. Ele captura o resultado de uma declaração e o torna a primeira entrada da próxima declaração, então podemos pensar como “EM SEGUIDA FAÇA” ao final de cada linha de código.
Todas as funções deste pacote são listadas na página de referência do pacote.
A principal vantagem do uso dos pipes é facilitar a depuração (debugging - achar erros) nos códigos, porque seu uso torna a linguagem R mais próxima do que falamos e pensamos, uma vez que evita o uso de funções dentro de funções (funções compostas, lembra-se do fog e gof do ensino médio? Evitamos eles aqui também).
Digitar %>%
é um pouco chato, dessa forma, existe um atalho para sua inserção nos scripts: Ctrl + Shift + M
.
Para deixar esse tópico menos estranho a quem possa ver essa operação pela primeira vez, vamos fazer alguns exemplos.
## R Base - sem pipe
sqrt(sum(1:100))
#> [1] 71.06335
## Tidyverse - com pipe
1:100 %>%
sum() %>%
sqrt()
#> [1] 71.06335
Essas operações ainda estão simples, vamos torná-las mais complexas com várias funções compostas. É nesses casos que a propriedade organizacional do uso do pipe emerge: podemos facilmente ver o encadeamento de operações, onde cada função é disposta numa linha. Apenas um adendo: a função set.seed()
fixa a amostragem de funções que geram valores aleatórios, como é o caso da função rpois()
.
## Fixar amostragem
set.seed(42)
## R Base - sem pipe
ve <- sum(sqrt(sin(log10(rpois(100, 10)))))
ve
#> [1] 91.27018
## Fixar amostragem
set.seed(42)
## Tidyverse - com pipe
ve <- rpois(100, 10) %>%
log10() %>%
sin() %>%
sqrt() %>%
sum()
ve
#> [1] 91.27018
O uso do pipe vai se tornar especialmente útil quando seguirmos para os pacotes das próximas duas seções: tidyr
e dplyr
. Com esses pacotes faremos operações em linhas e colunas de nossos dados tabulares, então podemos encadear uma série de funções para manipulação, limpeza e análise de dados.
Há ainda três outras variações do pipe que podem ser úteis em alguns momentos, mas que para funcionar precisam que o pacote magrittr
esteja carregado:
-
%T>%
: retorna o lado esquerdo em vez do lado direito da operação -
%$%
: “explode” as variáveis em um quadro de dados -
%<>%
: permite atribuição usando pipes
Para se aprofundar no tema, recomendamos a leitura do Capítulo 18 Pipes de Wickham & Grolemund (2017).
📝 Importante
A partir da versão do R 4.1+ (18/05/2021), o operador pipe se tornou nativo do R. Entretanto, o operador foi atualizado para |>
, podendo ser inserido com o mesmo atalho Ctrl + Shift + M
, mas necessitando uma mudança de opção em Tools > Global Options > Code > [x] Use native pipe operator, |> (requires R 4.1+)
, requerendo que o RStudio esteja numa versão igual ou superior a 1.4.17+.
5.7 tidyr
Um conjunto de dados tidy
(organizados) são mais fáceis de manipular, modelar e visualizar. Um conjunto de dados está no formato tidy
ou não, dependendo de como linhas, colunas e células são combinadas com observações, variáveis e valores. Nos dados tidy, as variáveis estão nas colunas, observações estão nas linhas e valores estão nas células, sendo que para esse último, não deve haver mais de um valor por célula (Figura 5.2).
- Cada variável em uma coluna
- Cada observação em uma linha
- Cada valor como uma célula
Todas as funções deste pacote são listadas na página de referência do pacote.
Para realizar diversas transformações nos dados, a fim de ajustá-los ao formato tidy
existe uma série de funções para: unir colunas, separar colunas, lidar com valores faltantes (NA
), transformar a base de dados de formato longo para largo (ou vice-e-versa), além de outras funções específicas.
-
unite()
: junta dados de múltiplas colunas em uma coluna -
separate()
: separa caracteres em múltiplas colunas -
separate_rows()
: separa caracteres em múltiplas colunas e linhas -
drop_na()
: retira linhas comNA
do conjunto de dados -
replace_na()
: substituiNA
do conjunto de dados -
pivot_wider()
: transforma um conjunto de dados longo (long) para largo (wide) -
pivot_longer()
: transforma um conjunto de dados largo (wide) para longo (long)
5.7.1 palmerpenguins
Para exemplificar o funcionamento dessas funções, usaremos os dados de medidas de pinguins chamados palmerpenguins, disponíveis no pacote palmerpenguins
.
## Instalar o pacote
install.packages("palmerpenguins")
Esses dados foram coletados e disponibilizados pela Dra. Kristen Gorman e pela Palmer Station, Antarctica LTER, membro da Long Term Ecological Research Network.
O pacote palmerpenguins
contém dois conjuntos de dados. Um é chamado de penguins
e é uma versão simplificada dos dados brutos. O segundo conjunto de dados é penguins_raw
e contém todas as variáveis e nomes originais. Ambos os conjuntos de dados contêm dados para 344 pinguins, de três espécies diferentes, coletados em três ilhas no arquipélago de Palmer, na Antártica. Destacamos também a versão traduzida desses dados para o português, disponível no pacote dados
.
Vamos utilizar principalmente o conjunto de dados penguins_raw
, que é a versão dos dados brutos.
## Carregar o pacote palmerpenguins
library(palmerpenguins)
Podemos ainda verificar os dados, pedindo uma ajuda de cada um dos objetos.
## Ajuda dos dados
?penguins
?penguins_raw
5.7.2 glimpse()
Primeiramente, vamos observar os dados e utilizar a função tibble::glimpse()
para ter uma noção geral dos dados.
## Visualizar os dados
penguins_raw
#> # A tibble: 344 × 17
#> studyName `Sample Number` Species Region Island Stage `Individual ID` `Clutch Comple…` `Date Egg` `Culmen Length…` `Culmen Depth …`
#> <chr> <dbl> <chr> <chr> <chr> <chr> <chr> <chr> <date> <dbl> <dbl>
#> 1 PAL0708 1 Adelie Penguin (Py… Anvers Torge… Adul… N1A1 Yes 2007-11-11 39.1 18.7
#> 2 PAL0708 2 Adelie Penguin (Py… Anvers Torge… Adul… N1A2 Yes 2007-11-11 39.5 17.4
#> 3 PAL0708 3 Adelie Penguin (Py… Anvers Torge… Adul… N2A1 Yes 2007-11-16 40.3 18
#> 4 PAL0708 4 Adelie Penguin (Py… Anvers Torge… Adul… N2A2 Yes 2007-11-16 NA NA
#> 5 PAL0708 5 Adelie Penguin (Py… Anvers Torge… Adul… N3A1 Yes 2007-11-16 36.7 19.3
#> 6 PAL0708 6 Adelie Penguin (Py… Anvers Torge… Adul… N3A2 Yes 2007-11-16 39.3 20.6
#> 7 PAL0708 7 Adelie Penguin (Py… Anvers Torge… Adul… N4A1 No 2007-11-15 38.9 17.8
#> 8 PAL0708 8 Adelie Penguin (Py… Anvers Torge… Adul… N4A2 No 2007-11-15 39.2 19.6
#> 9 PAL0708 9 Adelie Penguin (Py… Anvers Torge… Adul… N5A1 Yes 2007-11-09 34.1 18.1
#> 10 PAL0708 10 Adelie Penguin (Py… Anvers Torge… Adul… N5A2 Yes 2007-11-09 42 20.2
#> # … with 334 more rows, and 6 more variables: `Flipper Length (mm)` <dbl>, `Body Mass (g)` <dbl>, Sex <chr>, `Delta 15 N (o/oo)` <dbl>,
#> # `Delta 13 C (o/oo)` <dbl>, Comments <chr>
## Espiar os dados
dplyr::glimpse(penguins_raw)
#> Rows: 344
#> Columns: 17
#> $ studyName <chr> "PAL0708", "PAL0708", "PAL0708", "PAL0708", "PAL0708", "PAL0708", "PAL0708", "PAL0708", "PAL0708", "PAL0708", "PAL…
#> $ `Sample Number` <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,…
#> $ Species <chr> "Adelie Penguin (Pygoscelis adeliae)", "Adelie Penguin (Pygoscelis adeliae)", "Adelie Penguin (Pygoscelis adeliae)…
#> $ Region <chr> "Anvers", "Anvers", "Anvers", "Anvers", "Anvers", "Anvers", "Anvers", "Anvers", "Anvers", "Anvers", "Anvers", "Anv…
#> $ Island <chr> "Torgersen", "Torgersen", "Torgersen", "Torgersen", "Torgersen", "Torgersen", "Torgersen", "Torgersen", "Torgersen…
#> $ Stage <chr> "Adult, 1 Egg Stage", "Adult, 1 Egg Stage", "Adult, 1 Egg Stage", "Adult, 1 Egg Stage", "Adult, 1 Egg Stage", "Adu…
#> $ `Individual ID` <chr> "N1A1", "N1A2", "N2A1", "N2A2", "N3A1", "N3A2", "N4A1", "N4A2", "N5A1", "N5A2", "N6A1", "N6A2", "N7A1", "N7A2", "N…
#> $ `Clutch Completion` <chr> "Yes", "Yes", "Yes", "Yes", "Yes", "Yes", "No", "No", "Yes", "Yes", "Yes", "Yes", "Yes", "Yes", "Yes", "Yes", "Yes…
#> $ `Date Egg` <date> 2007-11-11, 2007-11-11, 2007-11-16, 2007-11-16, 2007-11-16, 2007-11-16, 2007-11-15, 2007-11-15, 2007-11-09, 2007-…
#> $ `Culmen Length (mm)` <dbl> 39.1, 39.5, 40.3, NA, 36.7, 39.3, 38.9, 39.2, 34.1, 42.0, 37.8, 37.8, 41.1, 38.6, 34.6, 36.6, 38.7, 42.5, 34.4, 46…
#> $ `Culmen Depth (mm)` <dbl> 18.7, 17.4, 18.0, NA, 19.3, 20.6, 17.8, 19.6, 18.1, 20.2, 17.1, 17.3, 17.6, 21.2, 21.1, 17.8, 19.0, 20.7, 18.4, 21…
#> $ `Flipper Length (mm)` <dbl> 181, 186, 195, NA, 193, 190, 181, 195, 193, 190, 186, 180, 182, 191, 198, 185, 195, 197, 184, 194, 174, 180, 189, …
#> $ `Body Mass (g)` <dbl> 3750, 3800, 3250, NA, 3450, 3650, 3625, 4675, 3475, 4250, 3300, 3700, 3200, 3800, 4400, 3700, 3450, 4500, 3325, 42…
#> $ Sex <chr> "MALE", "FEMALE", "FEMALE", NA, "FEMALE", "MALE", "FEMALE", "MALE", NA, NA, NA, NA, "FEMALE", "MALE", "MALE", "FEM…
#> $ `Delta 15 N (o/oo)` <dbl> NA, 8.94956, 8.36821, NA, 8.76651, 8.66496, 9.18718, 9.46060, NA, 9.13362, 8.63243, NA, NA, NA, 8.55583, NA, 9.185…
#> $ `Delta 13 C (o/oo)` <dbl> NA, -24.69454, -25.33302, NA, -25.32426, -25.29805, -25.21799, -24.89958, NA, -25.09368, -25.21315, NA, NA, NA, -2…
#> $ Comments <chr> "Not enough blood for isotopes.", NA, NA, "Adult not sampled.", NA, NA, "Nest never observed with full clutch.", "…
5.7.3 unite()
Primeiramente, vamos exemplificar como juntar e separar colunas. Vamos utilizar a função tidyr::unite()
para unir as colunas. Há diversos parâmetros para alterar como esta função funciona, entretanto, é importante destacar três deles: col
nome da coluna que vai receber as colunas unidas, sep
indicando o caractere separador das colunas unidas, e remove
para uma resposta lógica se as colunas unidas são removidas ou não. Vamos unir as colunas “Region” e “Island” na nova coluna “region_island”.
## Unir colunas
penguins_raw_unir <- tidyr::unite(data = penguins_raw,
col = "region_island",
Region:Island,
sep = ", ",
remove = FALSE)
head(penguins_raw_unir[, c("Region", "Island", "region_island")])
#> # A tibble: 6 × 3
#> Region Island region_island
#> <chr> <chr> <chr>
#> 1 Anvers Torgersen Anvers, Torgersen
#> 2 Anvers Torgersen Anvers, Torgersen
#> 3 Anvers Torgersen Anvers, Torgersen
#> 4 Anvers Torgersen Anvers, Torgersen
#> 5 Anvers Torgersen Anvers, Torgersen
#> 6 Anvers Torgersen Anvers, Torgersen
5.7.4 separate()
De forma contrária, podemos utilizar as funções tidyr::separate()
e tidyr::separate_rows()
para separar elementos de uma coluna em mais colunas. Respectivamente, a primeira função separa uma coluna em novas colunas conforme a separação, e a segunda função separa uma coluna, distribuindo os elementos nas linhas. Novamente, há diversos parâmetros para mudar o comportamento dessas funções, mas destacaremos aqui quatro deles: col
coluna a ser separada, into
os nomes das novas colunas, sep
indicando o caractere separador das colunas, e remove
para uma resposta lógica se as colunas separadas são removidas ou não. Vamos separar a coluna “Stage” nas colunas “stage” e “egg_stage”.
## Separar colunas
penguins_raw_separar <- tidyr::separate(data = penguins_raw,
col = Stage,
into = c("stage", "egg_stage"),
sep = ", ",
remove = FALSE)
head(penguins_raw_separar[, c("Stage", "stage", "egg_stage")])
#> # A tibble: 6 × 3
#> Stage stage egg_stage
#> <chr> <chr> <chr>
#> 1 Adult, 1 Egg Stage Adult 1 Egg Stage
#> 2 Adult, 1 Egg Stage Adult 1 Egg Stage
#> 3 Adult, 1 Egg Stage Adult 1 Egg Stage
#> 4 Adult, 1 Egg Stage Adult 1 Egg Stage
#> 5 Adult, 1 Egg Stage Adult 1 Egg Stage
#> 6 Adult, 1 Egg Stage Adult 1 Egg Stage
## Separar colunas em novas linhas
penguins_raw_separar_linhas <- tidyr::separate_rows(data = penguins_raw,
Stage,
sep = ", ")
head(penguins_raw_separar_linhas[, c("studyName", "Sample Number", "Species",
"Region", "Island", "Stage")])
#> # A tibble: 6 × 6
#> studyName `Sample Number` Species Region Island Stage
#> <chr> <dbl> <chr> <chr> <chr> <chr>
#> 1 PAL0708 1 Adelie Penguin (Pygoscelis adeliae) Anvers Torgersen Adult
#> 2 PAL0708 1 Adelie Penguin (Pygoscelis adeliae) Anvers Torgersen 1 Egg Stage
#> 3 PAL0708 2 Adelie Penguin (Pygoscelis adeliae) Anvers Torgersen Adult
#> 4 PAL0708 2 Adelie Penguin (Pygoscelis adeliae) Anvers Torgersen 1 Egg Stage
#> 5 PAL0708 3 Adelie Penguin (Pygoscelis adeliae) Anvers Torgersen Adult
#> 6 PAL0708 3 Adelie Penguin (Pygoscelis adeliae) Anvers Torgersen 1 Egg Stage
5.7.5 drop_na() e replace_na()
Valor faltante (NA
) é um tipo especial de elemento que são discutidos no Capítulo 4 e são relativamente comuns em conjuntos de dados. Em R Base, vimos algumas formas de lidar com esse tipo de elemento. No formato tidyverse
, existem também várias formas de lidar com eles, mas aqui focaremos nas funções tidyr::drop_na()
e tidyr::replace_na()
, para retirar linhas e substituir esses valores, respectivamente.
## Remover todas as linhas com NAs
penguins_raw_todas_na <- tidyr::drop_na(data = penguins_raw)
head(penguins_raw_todas_na)
#> # A tibble: 6 × 17
#> studyName `Sample Number` Species Region Island Stage `Individual ID` `Clutch Comple…` `Date Egg` `Culmen Length…`
#> <chr> <dbl> <chr> <chr> <chr> <chr> <chr> <chr> <date> <dbl>
#> 1 PAL0708 7 Adelie Peng… Anvers Torge… Adul… N4A1 No 2007-11-15 38.9
#> 2 PAL0708 8 Adelie Peng… Anvers Torge… Adul… N4A2 No 2007-11-15 39.2
#> 3 PAL0708 29 Adelie Peng… Anvers Biscoe Adul… N18A1 No 2007-11-10 37.9
#> 4 PAL0708 30 Adelie Peng… Anvers Biscoe Adul… N18A2 No 2007-11-10 40.5
#> 5 PAL0708 39 Adelie Peng… Anvers Dream Adul… N25A1 No 2007-11-13 37.6
#> 6 PAL0809 69 Adelie Peng… Anvers Torge… Adul… N32A1 No 2008-11-11 35.9
#> # … with 7 more variables: `Culmen Depth (mm)` <dbl>, `Flipper Length (mm)` <dbl>, `Body Mass (g)` <dbl>, Sex <chr>,
#> # `Delta 15 N (o/oo)` <dbl>, `Delta 13 C (o/oo)` <dbl>, Comments <chr>
## Remover linhas de colunas específicas com NAs
penguins_raw_colunas_na <- tidyr::drop_na(data = penguins_raw,
any_of("Comments"))
head(penguins_raw_colunas_na[, "Comments"])
#> # A tibble: 6 × 1
#> Comments
#> <chr>
#> 1 Not enough blood for isotopes.
#> 2 Adult not sampled.
#> 3 Nest never observed with full clutch.
#> 4 Nest never observed with full clutch.
#> 5 No blood sample obtained.
#> 6 No blood sample obtained for sexing.
## Substituir NAs por outro valor
penguins_raw_subs_na <- tidyr::replace_na(data = penguins_raw,
list(Comments = "Unknown"))
head(penguins_raw_subs_na[, "Comments"])
#> # A tibble: 6 × 1
#> Comments
#> <chr>
#> 1 Not enough blood for isotopes.
#> 2 Unknown
#> 3 Unknown
#> 4 Adult not sampled.
#> 5 Unknown
#> 6 Unknown
5.7.6 pivot_longer() e pivot_wider()
Por fim, trataremos da pivotagem ou remodelagem de dados. Veremos como mudar o formato do nosso conjunto de dados de longo (long) para largo (wide) e vice-versa. Primeiramente, vamos ver como partir de um dado longo (long) e criar um dado largo (wide). Essa é uma operação semelhante à “Tabela Dinâmica” das planilhas eletrônicas. Consiste em usar uma coluna para distribuir seus valores em outras colunas, de modo que os valores dos elementos são preenchidos corretamente, reduzindo assim o número de linhas e aumentando o número de colunas. Essa operação é bastante comum em Ecologia de Comunidades, quando queremos transformar uma lista de espécies em uma matriz de comunidades, com várias espécies nas colunas. Para realizar essa operação, usamos a função tidyr::pivot_wider()
. Dos diversos parâmetros que podem compor essa função, dois deles são fundamentais: names_from
que indica a coluna de onde os nomes serão usados e values_from
que indica que indica a coluna com os valores.
## Selecionar colunas
penguins_raw_sel_col <- penguins_raw[, c(2, 3, 13)]
head(penguins_raw_sel_col)
#> # A tibble: 6 × 3
#> `Sample Number` Species `Body Mass (g)`
#> <dbl> <chr> <dbl>
#> 1 1 Adelie Penguin (Pygoscelis adeliae) 3750
#> 2 2 Adelie Penguin (Pygoscelis adeliae) 3800
#> 3 3 Adelie Penguin (Pygoscelis adeliae) 3250
#> 4 4 Adelie Penguin (Pygoscelis adeliae) NA
#> 5 5 Adelie Penguin (Pygoscelis adeliae) 3450
#> 6 6 Adelie Penguin (Pygoscelis adeliae) 3650
## Pivotar para largo
penguins_raw_pivot_wider <- tidyr::pivot_wider(data = penguins_raw_sel_col,
names_from = Species,
values_from = `Body Mass (g)`)
head(penguins_raw_pivot_wider)
#> # A tibble: 6 × 4
#> `Sample Number` `Adelie Penguin (Pygoscelis adeliae)` `Gentoo penguin (Pygoscelis papua)` `Chinstrap penguin (Pygoscelis antarctica)`
#> <dbl> <dbl> <dbl> <dbl>
#> 1 1 3750 4500 3500
#> 2 2 3800 5700 3900
#> 3 3 3250 4450 3650
#> 4 4 NA 5700 3525
#> 5 5 3450 5400 3725
#> 6 6 3650 4550 3950
De modo oposto, podemos partir de um conjunto de dados largo (wide), ou seja, com várias colunas, e queremos que essas colunas preencham uma única coluna, e que os valores antes espalhados nessas várias colunas sejam adicionados um embaixo do outro, numa única coluna, no formato longo (long). Para essa operação, podemos utilizar a função tidyr::pivot_longer()
. Novamente, dos diversos parâmetros que podem compor essa função, três deles são fundamentais: cols
indicando as colunas que serão usadas para serem pivotadas, names_to
que indica a coluna de onde os nomes serão usados e values_to
que indica a coluna com os valores.
## Selecionar colunas
penguins_raw_sel_col <- penguins_raw[, c(2, 3, 10:13)]
head(penguins_raw_sel_col)
#> # A tibble: 6 × 6
#> `Sample Number` Species `Culmen Length (mm)` `Culmen Depth (mm)` `Flipper Length (mm)` `Body Mass (g)`
#> <dbl> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 1 Adelie Penguin (Pygoscelis adeliae) 39.1 18.7 181 3750
#> 2 2 Adelie Penguin (Pygoscelis adeliae) 39.5 17.4 186 3800
#> 3 3 Adelie Penguin (Pygoscelis adeliae) 40.3 18 195 3250
#> 4 4 Adelie Penguin (Pygoscelis adeliae) NA NA NA NA
#> 5 5 Adelie Penguin (Pygoscelis adeliae) 36.7 19.3 193 3450
#> 6 6 Adelie Penguin (Pygoscelis adeliae) 39.3 20.6 190 3650
## Pivotar para largo
penguins_raw_pivot_longer <- tidyr::pivot_longer(data = penguins_raw_sel_col,
cols = `Culmen Length (mm)`:`Body Mass (g)`,
names_to = "medidas",
values_to = "valores")
head(penguins_raw_pivot_longer)
#> # A tibble: 6 × 4
#> `Sample Number` Species medidas valores
#> <dbl> <chr> <chr> <dbl>
#> 1 1 Adelie Penguin (Pygoscelis adeliae) Culmen Length (mm) 39.1
#> 2 1 Adelie Penguin (Pygoscelis adeliae) Culmen Depth (mm) 18.7
#> 3 1 Adelie Penguin (Pygoscelis adeliae) Flipper Length (mm) 181
#> 4 1 Adelie Penguin (Pygoscelis adeliae) Body Mass (g) 3750
#> 5 2 Adelie Penguin (Pygoscelis adeliae) Culmen Length (mm) 39.5
#> 6 2 Adelie Penguin (Pygoscelis adeliae) Culmen Depth (mm) 17.4
Para se aprofundar no tema, recomendamos a leitura do Capítulo 12 Tidy data de Wickham & Grolemund (2017).
5.8 dplyr
O dplyr
é um pacote que facilita a manipulação de dados, com uma gramática simples e flexível (por exemplo, como filtragem, reordenamento, seleção, entre outras). Ele foi construído com o intuito de obter uma forma mais rápida e expressiva de manipular dados tabulares. O tibble
é a versão de data frame mais conveniente para se usar com pacote dplyr
.
Todas as funções deste pacote são listadas na página de referência do pacote.
5.8.1 Gramática
Sua gramática simples contém funções verbais para manipulação de dados, baseada em:
- Verbos:
mutate()
,select()
,filter()
,arrange()
,summarise()
,slice()
,rename()
, etc. - Replicação:
across()
,if_any()
,if_all()
,where()
,starts_with()
,ends_with()
,contains()
, etc. - Agrupamento:
group_by()
eungroup()
- Junções:
inner_join()
,full_join()
,left_join()
,right_join()
, etc. - Combinações:
bind_rows()
ebind_cols()
- Resumos, contagem e seleção:
n()
,n_distinct()
,first()
,last()
,nth()
, etc.
Existe uma série de funções para realizar a manipulação dos dados, com diversas finalidades: manipulação de uma tabela, manipulação de duas tabelas, replicação, agrupamento, funções de vetores, além de muitas outras funções específicas.
-
relocate()
: muda a ordem das colunas -
rename()
: muda o nome das colunas -
select()
: seleciona colunas pelo nome ou posição -
pull()
: seleciona uma coluna como vetor -
mutate()
: adiciona novas colunas ou resultados em colunas existentes -
arrange()
: reordena as linhas com base nos valores de colunas -
filter()
: seleciona linhas com base em valores de colunas -
slice()
: seleciona linhas de diferente formas -
distinct()
: remove linhas com valores repetidos com base nos valores de colunas -
count()
: conta observações para um grupo com base nos valores de colunas -
group_by()
: agrupa linhas pelos valores das colunas -
summarise()
: resume os dados através de funções considerando valores das colunas -
*_join()
: funções que juntam dados de duas tabelas através de uma coluna chave
5.8.2 Sintaxe
As funções do dplyr
podem seguir uma mesma sintaxe: o tibble
será sempre o primeiro argumento dessas funções, seguido de um operador pipe (%>%
) e pelo nome da função que irá fazer a manipulação nesses dados. Isso permite o encadeamento de várias operações consecutivas mantendo a estrutura do dado original e acrescentando mudanças num encadeamento lógico.
Sendo assim, as funções verbais não precisam modificar necessariamente o tibble original, sendo que as operações de manipulações podem e devem ser atribuídas a um novo objeto.
## Sintaxe
tb_dplyr <- tb %>%
funcao_verbal1(argumento1, argumento2, ...) %>%
funcao_verbal2(argumento1, argumento2, ...) %>%
funcao_verbal3(argumento1, argumento2, ...)
Além de data.frames
e tibbles
, a manipulação pelo formato dplyr
torna o trabalho com outros formatos de classes e dados acessíveis e eficientes como data.table
, SQL e Apache Spark, para os quais existem pacotes específicos.
5.8.3 palmerpenguins
Para nossos exemplos, vamos utilizar novamente os dados de pinguins palmerpenguins. Esses dados estão disponíveis no pacote palmerpenguins
. Vamos utilizar principalmente o conjunto de dados penguins
, que é a versão simplificada dos dados brutos penguins_raw
.
## Carregar o pacote palmerpenguins
library(palmerpenguins)
5.8.4 relocate()
Primeiramente, vamos reordenar as colunas com a função dplyr::relocate()
, onde simplesmente listamos as colunas que queremos mudar de posição e para onde elas devem ir. Para esse último passo há dois argumentos: .before
que indica a coluna onde a coluna realocada deve se mover antes, e o argumento .after
indicando onde deve se mover depois. Ambos podem ser informados com os nomes ou posições dessas colunas com números.
## Reordenar colunas - nome
penguins_relocate_col <- penguins %>%
dplyr::relocate(sex, year, .after = island)
head(penguins_relocate_col)
#> # A tibble: 6 × 8
#> species island sex year bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
#> <fct> <fct> <fct> <int> <dbl> <dbl> <int> <int>
#> 1 Adelie Torgersen male 2007 39.1 18.7 181 3750
#> 2 Adelie Torgersen female 2007 39.5 17.4 186 3800
#> 3 Adelie Torgersen female 2007 40.3 18 195 3250
#> 4 Adelie Torgersen <NA> 2007 NA NA NA NA
#> 5 Adelie Torgersen female 2007 36.7 19.3 193 3450
#> 6 Adelie Torgersen male 2007 39.3 20.6 190 3650
## Reordenar colunas - posição
penguins_relocate_ncol <- penguins %>%
dplyr::relocate(sex, year, .after = 2)
head(penguins_relocate_ncol)
#> # A tibble: 6 × 8
#> species island sex year bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
#> <fct> <fct> <fct> <int> <dbl> <dbl> <int> <int>
#> 1 Adelie Torgersen male 2007 39.1 18.7 181 3750
#> 2 Adelie Torgersen female 2007 39.5 17.4 186 3800
#> 3 Adelie Torgersen female 2007 40.3 18 195 3250
#> 4 Adelie Torgersen <NA> 2007 NA NA NA NA
#> 5 Adelie Torgersen female 2007 36.7 19.3 193 3450
#> 6 Adelie Torgersen male 2007 39.3 20.6 190 3650
5.8.5 rename()
Podemos renomear colunas facilmente com a função dplyr::rename()
, onde primeiramente informamos o nome que queremos que a coluna tenha, seguido do operador =
e a coluna do nosso dado (“nova_coluna = antiga_coluna”). Também podemos utilizar a função dplyr::rename_with()
, que faz a mudança do nome em múltiplas colunas, que pode depender ou não de resultados booleanos.
## Renomear as colunas
penguins_rename <- penguins %>%
dplyr::rename(bill_length = bill_length_mm,
bill_depth = bill_depth_mm,
flipper_length = flipper_length_mm,
body_mass = body_mass_g)
head(penguins_rename)
#> # A tibble: 6 × 8
#> species island bill_length bill_depth flipper_length body_mass sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Adelie Torgersen 39.1 18.7 181 3750 male 2007
#> 2 Adelie Torgersen 39.5 17.4 186 3800 female 2007
#> 3 Adelie Torgersen 40.3 18 195 3250 female 2007
#> 4 Adelie Torgersen NA NA NA NA <NA> 2007
#> 5 Adelie Torgersen 36.7 19.3 193 3450 female 2007
#> 6 Adelie Torgersen 39.3 20.6 190 3650 male 2007
## mudar o nome de todas as colunas
penguins_rename_with <- penguins %>%
dplyr::rename_with(toupper)
head(penguins_rename_with)
#> # A tibble: 6 × 8
#> SPECIES ISLAND BILL_LENGTH_MM BILL_DEPTH_MM FLIPPER_LENGTH_MM BODY_MASS_G SEX YEAR
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Adelie Torgersen 39.1 18.7 181 3750 male 2007
#> 2 Adelie Torgersen 39.5 17.4 186 3800 female 2007
#> 3 Adelie Torgersen 40.3 18 195 3250 female 2007
#> 4 Adelie Torgersen NA NA NA NA <NA> 2007
#> 5 Adelie Torgersen 36.7 19.3 193 3450 female 2007
#> 6 Adelie Torgersen 39.3 20.6 190 3650 male 2007
5.8.6 select()
Outra operação bastante usual dentro da manipulação de dados tabulares é a seleção de colunas. Podemos fazer essa operação com a função dplyr::select()
, que seleciona colunas pelo nome ou pela sua posição. Aqui há uma série de possibilidades de seleção de colunas, desde utilizar operadores como :
para selecionar intervalos de colunas, !
para tomar o complemento (todas menos as listadas), além de funções como dplyr::starts_with()
, dplyr::ends_with()
, dplyr::contains()
para procurar colunas com um padrão de texto do nome da coluna.
## Selecionar colunas por posição
penguins_select_position <- penguins %>%
dplyr::select(3:6)
head(penguins_select_position)
#> # A tibble: 6 × 4
#> bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
#> <dbl> <dbl> <int> <int>
#> 1 39.1 18.7 181 3750
#> 2 39.5 17.4 186 3800
#> 3 40.3 18 195 3250
#> 4 NA NA NA NA
#> 5 36.7 19.3 193 3450
#> 6 39.3 20.6 190 3650
## Selecionar colunas por nomes
penguins_select_names <- penguins %>%
dplyr::select(bill_length_mm:body_mass_g)
head(penguins_select_names)
#> # A tibble: 6 × 4
#> bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
#> <dbl> <dbl> <int> <int>
#> 1 39.1 18.7 181 3750
#> 2 39.5 17.4 186 3800
#> 3 40.3 18 195 3250
#> 4 NA NA NA NA
#> 5 36.7 19.3 193 3450
#> 6 39.3 20.6 190 3650
## Selecionar colunas por padrão
penguins_select_contains <- penguins %>%
dplyr::select(contains("_mm"))
head(penguins_select_contains)
#> # A tibble: 6 × 3
#> bill_length_mm bill_depth_mm flipper_length_mm
#> <dbl> <dbl> <int>
#> 1 39.1 18.7 181
#> 2 39.5 17.4 186
#> 3 40.3 18 195
#> 4 NA NA NA
#> 5 36.7 19.3 193
#> 6 39.3 20.6 190
5.8.7 pull()
Quando usamos a função dplyr::select()
, mesmo que para apenas uma coluna, o retorno da função é sempre um tibble
. Caso precisemos que essa coluna se torne um vetor dentro do encadeamento dos pipes
, usamos a função dplyr::pull()
, que extrai uma única coluna como vetor.
5.8.8 mutate()
Uma das operações mais úteis dentre as operações para colunas é adicionar ou atualizar os valores de colunas. Para essa operação, usaremos a função dplyr::mutate()
. Podemos ainda usar os argumentos .before
e .after
para indicar onde a nova coluna deve ficar, além do parâmetro .keep
com diversas possibilidades de manter colunas depois de usar a função dplyr::mutate()
. Por fim, é fundamental destacar o uso das funções de replicação: dplyr::across()
, dplyr::if_any()
e dplyr::if_all()
, para os quais a função fará alterações em múltiplas colunas de uma vez, dependendo de resultados booleanos.
## Adicionar colunas
penguins_mutate <- penguins %>%
dplyr::mutate(body_mass_kg = body_mass_g/1e3, .before = sex)
head(penguins_mutate)
#> # A tibble: 6 × 9
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g body_mass_kg sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <dbl> <fct> <int>
#> 1 Adelie Torgersen 39.1 18.7 181 3750 3.75 male 2007
#> 2 Adelie Torgersen 39.5 17.4 186 3800 3.8 female 2007
#> 3 Adelie Torgersen 40.3 18 195 3250 3.25 female 2007
#> 4 Adelie Torgersen NA NA NA NA NA <NA> 2007
#> 5 Adelie Torgersen 36.7 19.3 193 3450 3.45 female 2007
#> 6 Adelie Torgersen 39.3 20.6 190 3650 3.65 male 2007
## Modificar várias colunas
penguins_mutate_across <- penguins %>%
dplyr::mutate(across(where(is.factor), as.character))
head(penguins_mutate_across)
#> # A tibble: 6 × 8
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <chr> <chr> <dbl> <dbl> <int> <int> <chr> <int>
#> 1 Adelie Torgersen 39.1 18.7 181 3750 male 2007
#> 2 Adelie Torgersen 39.5 17.4 186 3800 female 2007
#> 3 Adelie Torgersen 40.3 18 195 3250 female 2007
#> 4 Adelie Torgersen NA NA NA NA <NA> 2007
#> 5 Adelie Torgersen 36.7 19.3 193 3450 female 2007
#> 6 Adelie Torgersen 39.3 20.6 190 3650 male 2007
5.8.9 arrange()
Além de operações em colunas, podemos fazer operações em linhas. Vamos começar com a reordenação das linhas com base nos valores das colunas. Para essa operação, usamos a função dplyr::arrange()
. Podemos reordenar por uma ou mais colunas de forma crescente ou decrescente usando a função desc()
ou o operador -
antes da coluna de interesse. Da mesma forma que na função dplyr::mutate()
, podemos usar as funções de replicação para ordenar as linhas para várias colunas de uma vez, dependendo de resultados booleanos.
## Reordenar linhas - crescente
penguins_arrange <- penguins %>%
dplyr::arrange(body_mass_g)
head(penguins_arrange)
#> # A tibble: 6 × 8
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Chinstrap Dream 46.9 16.6 192 2700 female 2008
#> 2 Adelie Biscoe 36.5 16.6 181 2850 female 2008
#> 3 Adelie Biscoe 36.4 17.1 184 2850 female 2008
#> 4 Adelie Biscoe 34.5 18.1 187 2900 female 2008
#> 5 Adelie Dream 33.1 16.1 178 2900 female 2008
#> 6 Adelie Torgersen 38.6 17 188 2900 female 2009
## Reordenar linhas - decrescente
penguins_arrange_desc <- penguins %>%
dplyr::arrange(desc(body_mass_g))
head(penguins_arrange_desc)
#> # A tibble: 6 × 8
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Gentoo Biscoe 49.2 15.2 221 6300 male 2007
#> 2 Gentoo Biscoe 59.6 17 230 6050 male 2007
#> 3 Gentoo Biscoe 51.1 16.3 220 6000 male 2008
#> 4 Gentoo Biscoe 48.8 16.2 222 6000 male 2009
#> 5 Gentoo Biscoe 45.2 16.4 223 5950 male 2008
#> 6 Gentoo Biscoe 49.8 15.9 229 5950 male 2009
## Reordenar linhas - decrescente
penguins_arrange_desc_m <- penguins %>%
dplyr::arrange(-body_mass_g)
head(penguins_arrange_desc_m)
#> # A tibble: 6 × 8
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Gentoo Biscoe 49.2 15.2 221 6300 male 2007
#> 2 Gentoo Biscoe 59.6 17 230 6050 male 2007
#> 3 Gentoo Biscoe 51.1 16.3 220 6000 male 2008
#> 4 Gentoo Biscoe 48.8 16.2 222 6000 male 2009
#> 5 Gentoo Biscoe 45.2 16.4 223 5950 male 2008
#> 6 Gentoo Biscoe 49.8 15.9 229 5950 male 2009
## Reordenar linhas - multiplas colunas
penguins_arrange_across <- penguins %>%
dplyr::arrange(across(where(is.numeric)))
head(penguins_arrange_across)
#> # A tibble: 6 × 8
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Adelie Dream 32.1 15.5 188 3050 female 2009
#> 2 Adelie Dream 33.1 16.1 178 2900 female 2008
#> 3 Adelie Torgersen 33.5 19 190 3600 female 2008
#> 4 Adelie Dream 34 17.1 185 3400 female 2008
#> 5 Adelie Torgersen 34.1 18.1 193 3475 <NA> 2007
#> 6 Adelie Torgersen 34.4 18.4 184 3325 female 2007
5.8.10 filter()
Uma das principais e mais usuais operações que podemos realizar em linhas é a seleção de linhas através do filtro por valores de uma ou mais colunas, utilizando a função dplyr::filter()
. Para realizar os filtros utilizaremos grande parte dos operadores relacionais e lógicos que listamos na Tabela 4.1 no Capítulo 4, especialmente os lógicos para combinações de filtros em mais de uma coluna. Além desses operadores, podemos utilizar a função is.na()
para filtros em elementos faltantes, e as funções dplyr::between()
e dplyr::near()
para filtros entre valores, e para valores próximos com certa tolerância, respectivamente. Por fim, podemos usar as funções de replicação para filtro das linhas para mais de uma coluna, dependendo de resultados booleanos.
## Filtrar linhas
penguins_filter <- penguins %>%
dplyr::filter(species == "Adelie")
head(penguins_filter)
#> # A tibble: 6 × 8
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Adelie Torgersen 39.1 18.7 181 3750 male 2007
#> 2 Adelie Torgersen 39.5 17.4 186 3800 female 2007
#> 3 Adelie Torgersen 40.3 18 195 3250 female 2007
#> 4 Adelie Torgersen NA NA NA NA <NA> 2007
#> 5 Adelie Torgersen 36.7 19.3 193 3450 female 2007
#> 6 Adelie Torgersen 39.3 20.6 190 3650 male 2007
## Filtrar linhas
penguins_filter_two <- penguins %>%
dplyr::filter(species == "Adelie" & sex == "female")
head(penguins_filter_two)
#> # A tibble: 6 × 8
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Adelie Torgersen 39.5 17.4 186 3800 female 2007
#> 2 Adelie Torgersen 40.3 18 195 3250 female 2007
#> 3 Adelie Torgersen 36.7 19.3 193 3450 female 2007
#> 4 Adelie Torgersen 38.9 17.8 181 3625 female 2007
#> 5 Adelie Torgersen 41.1 17.6 182 3200 female 2007
#> 6 Adelie Torgersen 36.6 17.8 185 3700 female 2007
## Filtrar linhas
penguins_filter_in <- penguins %>%
dplyr::filter(species %in% c("Adelie", "Gentoo"),
sex == "female")
head(penguins_filter_in)
#> # A tibble: 6 × 8
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Adelie Torgersen 39.5 17.4 186 3800 female 2007
#> 2 Adelie Torgersen 40.3 18 195 3250 female 2007
#> 3 Adelie Torgersen 36.7 19.3 193 3450 female 2007
#> 4 Adelie Torgersen 38.9 17.8 181 3625 female 2007
#> 5 Adelie Torgersen 41.1 17.6 182 3200 female 2007
#> 6 Adelie Torgersen 36.6 17.8 185 3700 female 2007
## Filtrar linhas - NA
penguins_filter_na <- penguins %>%
dplyr::filter(!is.na(sex) == TRUE)
head(penguins_filter_na)
#> # A tibble: 6 × 8
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Adelie Torgersen 39.1 18.7 181 3750 male 2007
#> 2 Adelie Torgersen 39.5 17.4 186 3800 female 2007
#> 3 Adelie Torgersen 40.3 18 195 3250 female 2007
#> 4 Adelie Torgersen 36.7 19.3 193 3450 female 2007
#> 5 Adelie Torgersen 39.3 20.6 190 3650 male 2007
#> 6 Adelie Torgersen 38.9 17.8 181 3625 female 2007
## Filtrar linhas - intervalos
penguins_filter_between <- penguins %>%
dplyr::filter(between(body_mass_g, 3000, 4000))
head(penguins_filter_between)
#> # A tibble: 6 × 8
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Adelie Torgersen 39.1 18.7 181 3750 male 2007
#> 2 Adelie Torgersen 39.5 17.4 186 3800 female 2007
#> 3 Adelie Torgersen 40.3 18 195 3250 female 2007
#> 4 Adelie Torgersen 36.7 19.3 193 3450 female 2007
#> 5 Adelie Torgersen 39.3 20.6 190 3650 male 2007
#> 6 Adelie Torgersen 38.9 17.8 181 3625 female 2007
## Filtrar linhas por várias colunas
penguins_filter_if <- penguins %>%
dplyr::filter(if_all(where(is.integer), ~ . > 200))
head(penguins_filter_if)
#> # A tibble: 6 × 8
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Adelie Dream 35.7 18 202 3550 female 2008
#> 2 Adelie Dream 41.1 18.1 205 4300 male 2008
#> 3 Adelie Dream 40.8 18.9 208 4300 male 2008
#> 4 Adelie Biscoe 41 20 203 4725 male 2009
#> 5 Adelie Torgersen 41.4 18.5 202 3875 male 2009
#> 6 Adelie Torgersen 44.1 18 210 4000 male 2009
5.8.11 slice()
Além da seleção de linhas por filtros, podemos fazer a seleção das linhas por intervalos, indicando quais linhas desejamos, usando a função dplyr::slice()
, e informando o argumento n
para o número da linha ou intervalo das linhas. Essa função possui variações no sufixo muito interessantes: dplyr::slice_head()
e dplyr::slice_tail()
seleciona as primeiras e últimas linhas, dplyr::slice_min()
e dplyr::slice_max()
seleciona linhas com os maiores e menores valores de uma coluna, e dplyr::slice_sample()
seleciona linhas aleatoriamente.
## Seleciona linhas
penguins_slice <- penguins %>%
dplyr::slice(n = c(1, 3, 300:n()))
head(penguins_slice)
#> # A tibble: 6 × 8
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Adelie Torgersen 39.1 18.7 181 3750 male 2007
#> 2 Adelie Torgersen 40.3 18 195 3250 female 2007
#> 3 Chinstrap Dream 50.6 19.4 193 3800 male 2007
#> 4 Chinstrap Dream 46.7 17.9 195 3300 female 2007
#> 5 Chinstrap Dream 52 19 197 4150 male 2007
#> 6 Chinstrap Dream 50.5 18.4 200 3400 female 2008
## Seleciona linhas - head
penguins_slice_head <- penguins %>%
dplyr::slice_head(n = 5)
head(penguins_slice_head)
#> # A tibble: 5 × 8
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Adelie Torgersen 39.1 18.7 181 3750 male 2007
#> 2 Adelie Torgersen 39.5 17.4 186 3800 female 2007
#> 3 Adelie Torgersen 40.3 18 195 3250 female 2007
#> 4 Adelie Torgersen NA NA NA NA <NA> 2007
#> 5 Adelie Torgersen 36.7 19.3 193 3450 female 2007
## Seleciona linhas - max
penguins_slice_max <- penguins %>%
dplyr::slice_max(body_mass_g, n = 5)
head(penguins_slice_max)
#> # A tibble: 6 × 8
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Gentoo Biscoe 49.2 15.2 221 6300 male 2007
#> 2 Gentoo Biscoe 59.6 17 230 6050 male 2007
#> 3 Gentoo Biscoe 51.1 16.3 220 6000 male 2008
#> 4 Gentoo Biscoe 48.8 16.2 222 6000 male 2009
#> 5 Gentoo Biscoe 45.2 16.4 223 5950 male 2008
#> 6 Gentoo Biscoe 49.8 15.9 229 5950 male 2009
## Seleciona linhas - sample
penguins_slice_sample <- penguins %>%
dplyr::slice_sample(n = 30)
head(penguins_slice_sample)
#> # A tibble: 6 × 8
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Adelie Biscoe 41.3 21.1 195 4400 male 2008
#> 2 Gentoo Biscoe 44.5 15.7 217 4875 <NA> 2009
#> 3 Adelie Torgersen 41.4 18.5 202 3875 male 2009
#> 4 Adelie Biscoe 37.6 17 185 3600 female 2008
#> 5 Adelie Dream 36 17.9 190 3450 female 2007
#> 6 Adelie Biscoe 35.7 16.9 185 3150 female 2008
5.8.12 distinct()
A última operação que apresentaremos para linhas é a retirada de linhas com valores repetidos com base nos valores de uma ou mais colunas, utilizando a função dplyr::distinct()
. Essa função por padrão retorna apenas a(s) coluna(s) utilizada(s) para retirar as linhas com valores repetidos, sendo necessário acrescentar o argumento .keep_all = TRUE
para retornar todas as colunas. Por fim, podemos usar as funções de replicação para retirar linhas com valores repetidos para mais de uma coluna, dependendo de resultados booleanos.
## Retirar linhas com valores repetidos
penguins_distinct <- penguins %>%
dplyr::distinct(body_mass_g)
head(penguins_distinct)
#> # A tibble: 6 × 1
#> body_mass_g
#> <int>
#> 1 3750
#> 2 3800
#> 3 3250
#> 4 NA
#> 5 3450
#> 6 3650
## Retirar linhas com valores repetidos - manter as outras colunas
penguins_distinct_keep_all <- penguins %>%
dplyr::distinct(body_mass_g, .keep_all = TRUE)
head(penguins_distinct_keep_all)
#> # A tibble: 6 × 8
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Adelie Torgersen 39.1 18.7 181 3750 male 2007
#> 2 Adelie Torgersen 39.5 17.4 186 3800 female 2007
#> 3 Adelie Torgersen 40.3 18 195 3250 female 2007
#> 4 Adelie Torgersen NA NA NA NA <NA> 2007
#> 5 Adelie Torgersen 36.7 19.3 193 3450 female 2007
#> 6 Adelie Torgersen 39.3 20.6 190 3650 male 2007
## Retirar linhas com valores repetidos para várias colunas
penguins_distinct_keep_all_across <- penguins %>%
dplyr::distinct(across(where(is.integer)), .keep_all = TRUE)
head(penguins_distinct_keep_all_across)
#> # A tibble: 6 × 8
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Adelie Torgersen 39.1 18.7 181 3750 male 2007
#> 2 Adelie Torgersen 39.5 17.4 186 3800 female 2007
#> 3 Adelie Torgersen 40.3 18 195 3250 female 2007
#> 4 Adelie Torgersen NA NA NA NA <NA> 2007
#> 5 Adelie Torgersen 36.7 19.3 193 3450 female 2007
#> 6 Adelie Torgersen 39.3 20.6 190 3650 male 2007
5.8.13 count()
Agora entraremos no assunto de resumo das observações. Podemos fazer contagens resumos dos nossos dados, utilizando para isso a função dplyr::count()
. Essa função contará valores de uma ou mais colunas, geralmente para variáveis categóricas, semelhante à função R Base table()
, mas num contexto tidyverse.
## Contagens de valores para uma coluna
penguins_count <- penguins %>%
dplyr::count(species)
penguins_count
#> # A tibble: 3 × 2
#> species n
#> <fct> <int>
#> 1 Adelie 152
#> 2 Chinstrap 68
#> 3 Gentoo 124
## Contagens de valores para mais de uma coluna
penguins_count_two <- penguins %>%
dplyr::count(species, island)
penguins_count_two
#> # A tibble: 5 × 3
#> species island n
#> <fct> <fct> <int>
#> 1 Adelie Biscoe 44
#> 2 Adelie Dream 56
#> 3 Adelie Torgersen 52
#> 4 Chinstrap Dream 68
#> 5 Gentoo Biscoe 124
5.8.14 group_by()
Uma grande parte das operações feitas nos dados são realizadas em grupos definidos por valores de colunas com dados categóricas. A função dplyr::group_by()
transforma um tibble
em um tibble grouped
, onde as operações são realizadas “por grupo”. Essa função é utilizada geralmente junto com a função dplyr::summarise()
, que veremos logo em seguida. O agrupamento não altera a aparência dos dados (além de informar como estão agrupados). A função dplyr::ungroup()
remove o agrupamento. Podemos ainda usar funções de replicação para fazer os agrupamentos para mais de uma coluna, dependendo de resultados booleanos.
## Agrupamento
penguins_group_by <- penguins %>%
dplyr::group_by(species)
head(penguins_group_by)
#> # A tibble: 6 × 8
#> # Groups: species [1]
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Adelie Torgersen 39.1 18.7 181 3750 male 2007
#> 2 Adelie Torgersen 39.5 17.4 186 3800 female 2007
#> 3 Adelie Torgersen 40.3 18 195 3250 female 2007
#> 4 Adelie Torgersen NA NA NA NA <NA> 2007
#> 5 Adelie Torgersen 36.7 19.3 193 3450 female 2007
#> 6 Adelie Torgersen 39.3 20.6 190 3650 male 2007
## Agrupamento de várias colunas
penguins_group_by_across <- penguins %>%
dplyr::group_by(across(where(is.factor)))
head(penguins_group_by_across)
#> # A tibble: 6 × 8
#> # Groups: species, island, sex [3]
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 Adelie Torgersen 39.1 18.7 181 3750 male 2007
#> 2 Adelie Torgersen 39.5 17.4 186 3800 female 2007
#> 3 Adelie Torgersen 40.3 18 195 3250 female 2007
#> 4 Adelie Torgersen NA NA NA NA <NA> 2007
#> 5 Adelie Torgersen 36.7 19.3 193 3450 female 2007
#> 6 Adelie Torgersen 39.3 20.6 190 3650 male 2007
5.8.15 summarise()
Como dissemos, muitas vezes queremos resumir nossos dados, principalmente para ter uma noção geral das variáveis (colunas) ou mesmo começar a análise exploratória resumindo variáveis contínuas por grupos de variáveis categóricas. Dessa forma, ao utilizar a função dplyr::summarise()
teremos um novo tibble
com os dados resumidos, que é a agregação ou resumo dos dados através de funções. Da mesma forma que outras funções, podemos usar funções de replicação para resumir valores para mais de uma coluna, dependendo de resultados booleanos.
## Resumo
penguins_summarise <- penguins %>%
dplyr::group_by(species) %>%
dplyr::summarize(body_mass_g_mean = mean(body_mass_g, na.rm = TRUE),
body_mass_g_sd = sd(body_mass_g, na.rm = TRUE))
penguins_summarise
#> # A tibble: 3 × 3
#> species body_mass_g_mean body_mass_g_sd
#> <fct> <dbl> <dbl>
#> 1 Adelie 3701. 459.
#> 2 Chinstrap 3733. 384.
#> 3 Gentoo 5076. 504.
## Resumo para várias colunas
penguins_summarise_across <- penguins %>%
dplyr::group_by(species) %>%
dplyr::summarize(across(where(is.numeric), ~ mean(.x, na.rm = TRUE)))
penguins_summarise_across
#> # A tibble: 3 × 6
#> species bill_length_mm bill_depth_mm flipper_length_mm body_mass_g year
#> <fct> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 Adelie 38.8 18.3 190. 3701. 2008.
#> 2 Chinstrap 48.8 18.4 196. 3733. 2008.
#> 3 Gentoo 47.5 15.0 217. 5076. 2008.
5.8.16 bind_rows() e bind_cols()
Muitas vezes teremos de combinar duas ou mais tabelas de dados. Podemos utilizar as funções R Base rbind()
e cbind()
, como vimos no Capítulo 4. Entretanto, pode ser interessante avançar para as funções dplyr::bind_rows()
e dplyr::bind_cols()
do formato tidyverse. A ideia é muito semelhante: a primeira função combina dados por linhas e a segunda por colunas. Entretanto, há algumas vantagens no uso dessas funções, como a identificação das linhas pelo argumento .id
para a primeira função, e a conferência do nome das colunas pelo argumento .name_repair
para a segunda função.
## Selecionar as linhas para dois tibbles
penguins_01 <- dplyr::slice(penguins, 1:5)
penguins_02 <- dplyr::slice(penguins, 51:55)
## Combinar as linhas
penguins_bind_rows <- dplyr::bind_rows(penguins_01, penguins_02, .id = "id")
head(penguins_bind_rows)
#> # A tibble: 6 × 9
#> id species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
#> <chr> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
#> 1 1 Adelie Torgersen 39.1 18.7 181 3750 male 2007
#> 2 1 Adelie Torgersen 39.5 17.4 186 3800 female 2007
#> 3 1 Adelie Torgersen 40.3 18 195 3250 female 2007
#> 4 1 Adelie Torgersen NA NA NA NA <NA> 2007
#> 5 1 Adelie Torgersen 36.7 19.3 193 3450 female 2007
#> 6 2 Adelie Biscoe 39.6 17.7 186 3500 female 2008
## Combinar as colunas
penguins_bind_cols <- dplyr::bind_cols(penguins_01, penguins_02, .name_repair = "unique")
head(penguins_bind_cols)
#> # A tibble: 5 × 16
#> species...1 island...2 bill_length_mm...3 bill_depth_mm...4 flipper_length_mm...5 body_mass_g...6 sex...7 year...8 species...9 island...10
#> <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int> <fct> <fct>
#> 1 Adelie Torgersen 39.1 18.7 181 3750 male 2007 Adelie Biscoe
#> 2 Adelie Torgersen 39.5 17.4 186 3800 female 2007 Adelie Biscoe
#> 3 Adelie Torgersen 40.3 18 195 3250 female 2007 Adelie Biscoe
#> 4 Adelie Torgersen NA NA NA NA <NA> 2007 Adelie Biscoe
#> 5 Adelie Torgersen 36.7 19.3 193 3450 female 2007 Adelie Biscoe
#> # … with 6 more variables: bill_length_mm...11 <dbl>, bill_depth_mm...12 <dbl>, flipper_length_mm...13 <int>, body_mass_g...14 <int>,
#> # sex...15 <fct>, year...16 <int>
5.8.17 *_join()
Finalmente, veremos o último conjunto de funções do pacote dplyr
, a junção de tabelas. Nessa operação, fazemos a combinação de pares de conjunto de dados tabulares por uma ou mais colunas chaves. Há dois tipos de junções: junção de modificação e junção de filtragem. A junção de modificação primeiro combina as observações por suas chaves e, em seguida, copia as variáveis (colunas) de uma tabela para a outra. É fundamental destacar a importância da coluna chave, que é indicada pelo argumento by
. Essa coluna deve conter elementos que sejam comuns às duas tabelas para que haja a combinação dos elementos.
Existem quatro tipos de junções de modificações, que são realizadas pelas funções: dplyr::inner_join()
, dplyr::left_join()
, dplyr::full_join()
e dplyr::right_join()
, e que podem ser representadas na Figura 5.3.
Considerando a nomenclatura de duas tabelas de dados por x
e y
, temos:
-
inner_join(x, y)
: mantém apenas as observações emx
e emy
-
left_join(x, y)
: mantém todas as observações emx
-
right_join(x, y)
: mantém todas as observações emy
-
full_join(x, y)
: mantém todas as observações emx
e emy
Aqui, vamos demostrar apenas a função dplyr::left_join()
, combinando um tibble
de coordenadas geográficas das ilhas com o conjunto de dados do penguins.
## Adicionar uma coluna chave de ids
penguin_islands <- tibble(
island = c("Torgersen", "Biscoe", "Dream", "Alpha"),
longitude = c(-64.083333, -63.775636, -64.233333, -63),
latitude = c(-64.766667, -64.818569, -64.733333, -64.316667))
## Junção - left
penguins_left_join <- dplyr::left_join(penguins, penguin_islands, by = "island")
head(penguins_left_join)
#> # A tibble: 6 × 10
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year longitude latitude
#> <fct> <chr> <dbl> <dbl> <int> <int> <fct> <int> <dbl> <dbl>
#> 1 Adelie Torgersen 39.1 18.7 181 3750 male 2007 -64.1 -64.8
#> 2 Adelie Torgersen 39.5 17.4 186 3800 female 2007 -64.1 -64.8
#> 3 Adelie Torgersen 40.3 18 195 3250 female 2007 -64.1 -64.8
#> 4 Adelie Torgersen NA NA NA NA <NA> 2007 -64.1 -64.8
#> 5 Adelie Torgersen 36.7 19.3 193 3450 female 2007 -64.1 -64.8
#> 6 Adelie Torgersen 39.3 20.6 190 3650 male 2007 -64.1 -64.8
Já o segundo tipo de junção, a junção de filtragem combina as observações da mesma maneira que as junções de modificação, mas afetam as observações (linhas), não as variáveis (colunas). Existem dois tipos.
-
semi_join(x, y)
: mantém todas as observações emx
que têm uma correspondência emy
-
anti_join(x, y)
: elimina todas as observações emx
que têm uma correspondência emy
De forma geral, semi-joins são úteis para corresponder tabelas de resumo filtradas de volta às linhas originais, removendo as linhas que não estavam antes do join. Já anti-joins são úteis para diagnosticar incompatibilidades de junção, por exemplo, ao verificar os elementos que não combinam entre duas tabelas de dados.
5.8.18 Operações de conjuntos e comparação de dados
Temos ainda operações de conjuntos e comparação de dados.
-
union(x, y)
: retorna todas as linhas que aparecem emx
,y
ou mais dos conjuntos de dados -
interesect(x, y)
: retorna apenas as linhas que aparecem emx
e emy
-
setdiff(x, y)
: retorna as linhas que aparecemx
, mas não emy
-
setequal(x, y)
: retorna sex
ey
são iguais e quais suas diferenças
Para se aprofundar no tema, recomendamos a leitura do Capítulo 13 Relational data de Wickham & Grolemund (2017).
5.9 stringr
O pacote stringr
fornece um conjunto de funções para a manipulação de caracteres ou strings. O pacote concentra-se nas funções de manipulação mais importantes e comumente usadas. Para funções mais específicas, recomenda-se usar o pacote stringi
, que fornece um conjunto mais abrangente de funções. As funções do stringr
podem ser agrupadas em algumas operações para tarefas específicas como: i) correspondência de padrões, ii) retirar e acrescentar espaços em branco, iii) mudar maiúsculas e minúsculas, além de muitas outras operações com caracteres.
Todas as funções deste pacote são listadas na página de referência do pacote.
Demonstraremos algumas funções para algumas operações mais comuns, utilizando um vetor de um elemento, com o string “penguins”.
Podemos explorar o comprimento de strings com a função stringr::str_length()
.
## Comprimento
stringr::str_length(string = "penguins")
#> [1] 8
Extrair um string por sua posição usando a função stringr::str_sub()
ou por um padrão com stringr::str_extract()
.
## Extrair pela posição
stringr::str_sub(string = "penguins", end = 3)
#> [1] "pen"
## Extrair por padrão
stringr::str_extract(string = "penguins", pattern = "p")
#> [1] "p"
Substituir strings por outros strings com stringr::str_replace()
.
## Substituir
stringr::str_replace(string = "penguins", pattern = "i", replacement = "y")
#> [1] "penguyns"
Separar strings por um padrão com a função stringr::str_split()
.
## Separar
stringr::str_split(string = "p-e-n-g-u-i-n-s", pattern = "-", simplify = TRUE)
#> [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
#> [1,] "p" "e" "n" "g" "u" "i" "n" "s"
Inserir espaços em brancos pela esquerda, direita ou ambos com a função stringr::str_pad()
.
## Inserir espacos em branco
stringr::str_pad(string = "penguins", width = 10, side = "left")
#> [1] " penguins"
stringr::str_pad(string = "penguins", width = 10, side = "right")
#> [1] "penguins "
stringr::str_pad(string = "penguins", width = 10, side = "both")
#> [1] " penguins "
Também podemos remover espaços em branco da esquerda, direita ou ambos, utilizando stringr::str_trim()
.
## Remover espacos em branco
stringr::str_trim(string = " penguins ", side = "left")
#> [1] "penguins "
stringr::str_trim(string = " penguins ", side = "right")
#> [1] " penguins"
stringr::str_trim(string = " penguins ", side = "both")
#> [1] "penguins"
Podemos também alterar minúsculas e maiúsculas em diferentes posições do string, com várias funções.
## Alterar minúsculas e maiúsculas
stringr::str_to_lower(string = "Penguins")
#> [1] "penguins"
stringr::str_to_upper(string = "penguins")
#> [1] "PENGUINS"
stringr::str_to_sentence(string = "penGuins")
#> [1] "Penguins"
stringr::str_to_title(string = "penGuins")
#> [1] "Penguins"
Podemos ainda ordenar os elementos de um vetor por ordem alfabética de forma crescente ou decrescente, usando stringr::str_sort()
.
## Ordenar
stringr::str_sort(x = letters)
#> [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"
stringr::str_sort(x = letters, dec = TRUE)
#> [1] "z" "y" "x" "w" "v" "u" "t" "s" "r" "q" "p" "o" "n" "m" "l" "k" "j" "i" "h" "g" "f" "e" "d" "c" "b" "a"
Podemos ainda utilizar essas funções em complemento com o pacote dplyr
, para alterar os strings de colunas ou nome das colunas.
## Alterar valores das colunas
penguins_stringr_valores <- penguins %>%
dplyr::mutate(species = stringr::str_to_lower(species))
## Alterar nome das colunas
penguins_stringr_nomes <- penguins %>%
dplyr::rename_with(stringr::str_to_title)
Para se aprofundar no tema, recomendamos a leitura do Capítulo 14 Strings de Wickham & Grolemund (2017).
5.10 forcats
O pacote forcats
fornece um conjunto de ferramentas úteis para facilitar a manipulação de fatores. Como dito no Capítulo 4, usamos fatores geralmente quando temos dados categóricos, que são variáveis que possuem um conjunto de valores fixos e conhecidos. As funções são utilizadas principalmente para: i) mudar a ordem dos níveis, ii) mudar os valores dos níveis, iii) adicionar e remover níveis, iv) combinar múltiplos níveis, além de outras operações.
Todas as funções deste pacote são listadas na página de referência do pacote.
Vamos utilizar ainda os dados penguins
e penguins_raw
para exemplificar o uso do pacote forcats
.
## Carregar o pacote palmerpenguins
library(palmerpenguins)
Primeiramente, vamos converter dados de string para fator, utilizando a função forcats::as_factor()
.
## String
forcats::as_factor(penguins_raw$Species) %>% head()
#> [1] Adelie Penguin (Pygoscelis adeliae) Adelie Penguin (Pygoscelis adeliae) Adelie Penguin (Pygoscelis adeliae)
#> [4] Adelie Penguin (Pygoscelis adeliae) Adelie Penguin (Pygoscelis adeliae) Adelie Penguin (Pygoscelis adeliae)
#> Levels: Adelie Penguin (Pygoscelis adeliae) Gentoo penguin (Pygoscelis papua) Chinstrap penguin (Pygoscelis antarctica)
Podemos facilmente mudar o nome dos níveis utilizando a função forcats::fct_recode()
.
## Mudar o nome dos níveis
forcats::fct_recode(penguins$species, a = "Adelie", c = "Chinstrap", g = "Gentoo") %>% head()
#> [1] a a a a a a
#> Levels: a c g
Para inverter os níveis, usamos a função forcats::fct_rev()
.
## Inverter os níveis
forcats::fct_rev(penguins$species) %>% head()
#> [1] Adelie Adelie Adelie Adelie Adelie Adelie
#> Levels: Gentoo Chinstrap Adelie
Uma operação muito comum com fatores é mudar a ordem dos níveis. Quando precisamos especificar a ordem dos níveis, podemos fazer essa operação manualmente com a função forcats::fct_relevel()
.
## Especificar a ordem dos níveis
forcats::fct_relevel(penguins$species, "Chinstrap", "Gentoo", "Adelie") %>% head()
#> [1] Adelie Adelie Adelie Adelie Adelie Adelie
#> Levels: Chinstrap Gentoo Adelie
Como vimos, a reordenação dos níveis pode ser feita manualmente. Mas existem outras formas automáticas de reordenação seguindo algumas regras, para as quais existem funções específicas.
-
forcats::fct_inorder()
: pela ordem em que aparecem pela primeira vez -
forcats::fct_infreq()
: por número de observações com cada nível (decrescente, i.e., o maior primeiro) -
forcats::fct_inseq()
: pelo valor numérico do nível
## Níveis pela ordem em que aparecem
forcats::fct_inorder(penguins$species) %>% head()
#> [1] Adelie Adelie Adelie Adelie Adelie Adelie
#> Levels: Adelie Gentoo Chinstrap
## Ordem (decrescente) de frequência
forcats::fct_infreq(penguins$species) %>% head()
#> [1] Adelie Adelie Adelie Adelie Adelie Adelie
#> Levels: Adelie Gentoo Chinstrap
Por fim, podemos fazer a agregação de níveis raros em um nível utilizando a função forcats::fct_lump()
.
## Agregação de níveis raros em um nível
forcats::fct_lump(penguins$species) %>% head()
#> [1] Adelie Adelie Adelie Adelie Adelie Adelie
#> Levels: Adelie Gentoo Other
Podemos ainda utilizar essas funções em complemento com o pacote dplyr
para fazer manipulações de fatores nas colunas de tibbles
.
## Transformar várias colunas em fator
penguins_raw_multi_factor <- penguins_raw %>%
dplyr::mutate(across(where(is.character), forcats::as_factor))
Para se aprofundar no tema, recomendamos a leitura do Capítulo 15 Factors de Wickham & Grolemund (2017).
5.11 lubridate
O pacote lubridate
fornece um conjunto de funções para a manipulação de dados de data e horário. Dessa forma, esse pacote facilita a manipulação dessa classe de dado no R, pois geralmente esses dados não são intuitivos e mudam dependendo do tipo de objeto de data e horário. Além disso, os métodos que usam datas e horários devem levar em consideração fusos horários, anos bissextos, horários de verão, além de outras particularidades. Existem diversas funções nesse pacote, sendo as mesmas focadas em: i) transformações de data/horário, ii) componentes, iii) arredondamentos, iv) durações, v) períodos, vi) intervalos, além de muitas outras funções específicas.
Todas as funções deste pacote são listadas na página de referência do pacote.
Apesar de estar inserido no escopo do tidyverse, este pacote não é carregado com os demais, requisitando seu carregamento solo.
Existem três tipos de dados data/horário:
-
Data: tempo em dias, meses e anos
<date>
-
Horário: tempo dentro de um dia
<time>
-
Data-horário: tempo em um instante (data mais tempo)
<dttm>
Para trabalhar exclusivamente com horários, podemos utilizar o pacote hms
.
É fundamental também destacar que algumas letras terão um significado temporal, sendo abreviações de diferentes períodos em inglês: year (ano), month (mês), weak (semana), day (dia), hour (hora), minute (minuto), e second (segundo).
Para acessar a informação da data e horários atuais podemos utilizar as funções lubridate::today()
e lubridate::now()
.
## Extrair a data nesse instante
lubridate::today()
#> [1] "2022-06-08"
## Extrair a data e tempo nesse instante
lubridate::now()
#> [1] "2022-06-08 00:04:44 -03"
Além dessas informações instantâneas, existem três maneiras de criar um dado de data/horário.
- De um string
- De componentes individuais de data e horário
- De um objeto de data/horário existente
Os dados de data/horário geralmente estão no formato de strings. Podemos transformar os dados especificando a ordem dos seus componentes, ou seja, a ordem em que ano, mês e dia aparecem no string, usando as letras y
(ano), m
(mês) e d
(dia) na mesma ordem, por exemplo, lubridate::dmy()
.
## Strings e números para datas
lubridate::dmy("03-03-2021")
#> [1] "2021-03-03"
Essas funções também aceitam números sem aspas, além de serem muito versáteis e funcionarem em outros diversos formatos.
## Strings e números para datas
lubridate::dmy("03-Mar-2021")
lubridate::dmy(03032021)
lubridate::dmy("03032021")
lubridate::dmy("03/03/2021")
lubridate::dmy("03.03.2021")
Além da data, podemos especificar horários atrelados a essas datas. Para criar uma data com horário adicionamos um underscore (_
) e h
(hora), m
(minuto) e s
(segundo) ao nome da função, além do argumento tz
para especificar o fuso horário (tema tratado mais adiante nessa seção).
## Especificar horários e fuso horário
lubridate::dmy_h("03-03-2021 13")
#> [1] "2021-03-03 13:00:00 UTC"
lubridate::dmy_hm("03-03-2021 13:32")
#> [1] "2021-03-03 13:32:00 UTC"
lubridate::dmy_hms("03-03-2021 13:32:01")
#> [1] "2021-03-03 13:32:01 UTC"
lubridate::dmy_hms("03-03-2021 13:32:01", tz = "America/Sao_Paulo")
#> [1] "2021-03-03 13:32:01 -03"
Podemos ainda ter componentes individuais de data/horário em múltiplas colunas. Para realizar essa transformação, podemos usar as funções lubridate::make_date()
e lubridate::make_datetime()
.
## Dados com componentes individuais
dados <- tibble::tibble(
ano = c(2021, 2021, 2021),
mes = c(1, 2, 3),
dia = c(12, 20, 31),
hora = c(2, 14, 18),
minuto = c(2, 44, 55))
## Data de componentes individuais
dados %>%
dplyr::mutate(data = lubridate::make_datetime(ano, mes, dia, hora, minuto))
#> # A tibble: 3 × 6
#> ano mes dia hora minuto data
#> <dbl> <dbl> <dbl> <dbl> <dbl> <dttm>
#> 1 2021 1 12 2 2 2021-01-12 02:02:00
#> 2 2021 2 20 14 44 2021-02-20 14:44:00
#> 3 2021 3 31 18 55 2021-03-31 18:55:00
Por fim, podemos criar datas modificando entre data/horário e data, utilizando as funções lubridate::as_datetime()
e lubridate::as_date()
.
## Data para data-horário
lubridate::as_datetime(today())
#> [1] "2022-06-08 UTC"
## Data-horário para data
lubridate::as_date(now())
#> [1] "2022-06-08"
Uma vez que entendemos como podemos criar dados de data/horário, podemos explorar funções para acessar e definir componentes individuais. Para essa tarefa existe uma grande quantidade de funções para acessar partes específicas de datas e horários.
-
year()
: acessa o ano -
month()
: acessa o mês -
month()
: acessa o dia -
yday()
: acessa o dia do ano -
mday()
: acessa o dia do mês -
wday()
: acessa o dia da semana -
hour()
: acessa as horas -
minute()
: acessa os minutos -
second()
: acessa os segundos
## Extrair
lubridate::year(now())
#> [1] 2022
lubridate::month(now())
#> [1] 6
lubridate::month(now(), label = TRUE)
#> [1] Jun
#> Levels: Jan < Feb < Mar < Apr < May < Jun < Jul < Aug < Sep < Oct < Nov < Dec
lubridate::day(now())
#> [1] 8
lubridate::wday(now())
#> [1] 4
lubridate::wday(now(), label = TRUE)
#> [1] Wed
#> Levels: Sun < Mon < Tue < Wed < Thu < Fri < Sat
lubridate::second(now())
#> [1] 44.33378
Além de acessar componentes de datas e horários, podemos usar essas funções para fazer a inclusão de informações de datas e horários.
## Data
data <- dmy_hms("04-03-2021 01:04:56")
## Incluir
lubridate::year(data) <- 2020
lubridate::month(data) <- 01
lubridate::hour(data) <- 13
Mais convenientemente, podemos utilizar a função update()
para alterar vários valores de uma vez.
## Incluir vários valores
update(data, year = 2020, month = 1, mday = 1, hour = 1)
#> [1] "2020-01-01 01:04:56 UTC"
Muitas vezes precisamos fazer operações com datas, como: adição, subtração, multiplicação e divisão. Para tanto, é preciso entender três classes importantes que representam intervalos de tempo.
- Durações: representam um número exato de segundos
- Períodos: representam unidades humanas como semanas e meses
- Intervalos: representam um ponto inicial e final
Quando fazemos uma subtração de datas, criamos um objeto da classe difftime
. Essa classe pode ser um pouco complicada de trabalhar, então dentro do lubridate usamos funções que convertem essa classe em duração, da classe Duration
. As durações sempre registram o intervalo de tempo em segundos, com alguma unidade de tempo maior entre parênteses. Há uma série de funções para tratar dessa classe.
-
duration()
: cria data em duração -
as.duration()
: converte datas em duração -
dyears()
: duração de anos -
dmonths()
: duração de meses -
dweeks()
: duração de semanas -
ddays()
: duração de dias -
dhours()
: duração de horas -
dminutes()
: duração de minutos -
dseconds()
: duração de segundos
## Subtração de datas
tempo_estudando_r <- lubridate::today() - lubridate::dmy("30-11-2011")
## Conversão para duração
tempo_estudando_r_dur <- lubridate::as.duration(tempo_estudando_r)
## Criando durações
lubridate::duration(90, "seconds")
#> [1] "90s (~1.5 minutes)"
lubridate::duration(1.5, "minutes")
#> [1] "90s (~1.5 minutes)"
lubridate::duration(1, "days")
#> [1] "86400s (~1 days)"
## Transformação da duração
lubridate::dseconds(100)
#> [1] "100s (~1.67 minutes)"
lubridate::dminutes(100)
#> [1] "6000s (~1.67 hours)"
lubridate::dhours(100)
#> [1] "360000s (~4.17 days)"
lubridate::ddays(100)
#> [1] "8640000s (~14.29 weeks)"
lubridate::dweeks(100)
#> [1] "60480000s (~1.92 years)"
lubridate::dyears(100)
#> [1] "3155760000s (~100 years)"
Podemos ainda utilizar as durações para fazer operações aritméticas com datas como adição, subtração e multiplicação.
## Somando durações a datas
lubridate::today() + lubridate::ddays(1)
#> [1] "2022-06-09"
## Subtraindo durações de datas
lubridate::today() - lubridate::dyears(1)
#> [1] "2021-06-07 18:00:00 UTC"
## Multiplicando durações
2 * dyears(2)
#> [1] "126230400s (~4 years)"
Além das durações, podemos usar períodos, que são extensões de tempo não fixados em segundos como as durações, mas flexíveis, com o tempo em dias, semanas, meses ou anos, permitindo uma interpretação mais intuitiva das datas. Novamente, há uma série de funções para realizar essas operações.
-
period()
: cria data em período -
as.period()
: converte datas em período -
seconds()
: período em segundos -
minutes()
: período em minutos -
hours()
: período em horas -
days()
: período em dias -
weeks()
: período em semanas -
months()
: período em meses -
years()
: período em anos
## Criando períodos
period(c(90, 5), c("second", "minute"))
#> [1] "5M 90S"
period(c(3, 1, 2, 13, 1), c("second", "minute", "hour", "day", "week"))
#> [1] "20d 2H 1M 3S"
## Transformação de períodos
lubridate::seconds(100)
#> [1] "100S"
lubridate::minutes(100)
#> [1] "100M 0S"
lubridate::hours(100)
#> [1] "100H 0M 0S"
lubridate::days(100)
#> [1] "100d 0H 0M 0S"
lubridate::weeks(100)
#> [1] "700d 0H 0M 0S"
lubridate::years(100)
#> [1] "100y 0m 0d 0H 0M 0S"
Além disso, podemos fazer operações com os períodos, somando e subtraindo.
## Somando datas
lubridate::today() + lubridate::weeks(10)
#> [1] "2022-08-17"
## Subtraindo datas
lubridate::today() - lubridate::weeks(10)
#> [1] "2022-03-30"
## Criando datas recorrentes
lubridate::today() + lubridate::weeks(0:10)
#> [1] "2022-06-08" "2022-06-15" "2022-06-22" "2022-06-29" "2022-07-06" "2022-07-13" "2022-07-20" "2022-07-27" "2022-08-03" "2022-08-10"
#> [11] "2022-08-17"
Por fim, intervalos são períodos de tempo limitados por duas datas, possuindo uma duração com um ponto de partida, que o faz preciso para determinar uma duração. Intervalos são objetos da classe Interval
. Da mesma forma que para duração e períodos, há uma série de funções para realizar essas operações.
-
interval()
: cria data em intervalo -
%--%
: cria data em intervalo -
as.interval()
: converte datas em intervalo -
int_start()
: acessa ou atribui data inicial de um intervalo -
int_end()
: acessa ou atribui data final de um intervalo -
int_length()
: comprimento de um intervalo em segundos -
int_flip()
: inverte a ordem da data de início e da data de término em um intervalo -
int_shift()
: desloca as datas de início e término de um intervalo -
int_aligns()
: testa se dois intervalos compartilham um ponto final -
int_standardize()
: garante que todos os intervalos sejam positivos -
int_diff()
: retorna os intervalos que ocorrem entre os elementos de data/horário -
int_overlaps()
: testa se dois intervalos se sobrepõem -
%within%
: testa se o primeiro intervalo está contido no segundo
## Criando duas datas - início de estudos do R e nascimento do meu filho
r_inicio <- lubridate::dmy("30-11-2011")
filho_nascimento <- lubridate::dmy("26-09-2013")
r_hoje <- lubridate::today()
## Criando intervalos - interval
r_intervalo <- lubridate::interval(r_inicio, r_hoje)
## Criando intervalos - interval %--%
filho_intervalo <- filho_nascimento %--% lubridate::today()
## Operações com intervalos
lubridate::int_start(r_intervalo)
#> [1] "2011-11-30 UTC"
lubridate::int_end(r_intervalo)
#> [1] "2022-06-08 UTC"
lubridate::int_length(r_intervalo)
#> [1] 332035200
lubridate::int_flip(r_intervalo)
#> [1] 2022-06-08 UTC--2011-11-30 UTC
lubridate::int_shift(r_intervalo, duration(days = 30))
#> [1] 2011-12-30 UTC--2022-07-08 UTC
Uma operação de destaque é verificar a sobreposição entre dois intervalos.
## Verificar sobreposição - int_overlaps
lubridate::int_overlaps(r_intervalo, filho_intervalo)
#> [1] TRUE
## Verificar se intervalo está contido
r_intervalo %within% filho_intervalo
#> [1] FALSE
filho_intervalo %within% r_intervalo
#> [1] TRUE
Podemos ainda calcular quantos períodos existem dentro de um intervalo, utilizando as operações de /
e %/%
.
## Períodos dentro de um intervalo - anos
r_intervalo / lubridate::years()
#> [1] 10.52055
r_intervalo %/% lubridate::years()
#> [1] 10
## Períodos dentro de um intervalo - dias e semandas
filho_intervalo / lubridate::days()
#> [1] 3177
filho_intervalo / lubridate::weeks()
#> [1] 453.8571
Ainda podemos fazer transformações dos dados para períodos e ter todas as unidades de data e tempo que o intervalo compreende.
## Tempo total estudando R
lubridate::as.period(r_intervalo)
#> [1] "10y 6m 9d 0H 0M 0S"
## Idade do meu filho
lubridate::as.period(filho_intervalo)
#> [1] "8y 8m 13d 0H 0M 0S"
Por fim, fusos horários tendem a ser um fator complicador quando precisamos analisar informações instantâneas de tempo (horário) de outras partes do planeta, ou mesmo fazer conversões dos horários. No lubridate
há funções para ajudar nesse sentido. Para isso, podemos utilizar a função lubridate::with_tz()
e no argumento tzone
informar o fuso horário para a transformação do horário.
Podemos descobrir o fuso horário que o R está considerando com a função Sys.timezone()
.
## Fuso horário no R
Sys.timezone()
#> [1] "America/Sao_Paulo"
No R há uma listagem dos nomes dos fusos horários que podemos utilizar no argumento tzone
para diferentes fusos horários.
## Verificar os fuso horários
length(OlsonNames())
#> [1] 595
head(OlsonNames())
#> [1] "Africa/Abidjan" "Africa/Accra" "Africa/Addis_Ababa" "Africa/Algiers" "Africa/Asmara" "Africa/Asmera"
Podemos nos perguntar que horas são em outra parte do globo ou fazer as conversões facilmente no lubridate.
## Que horas são em...
lubridate::with_tz(lubridate::now(), tzone = "America/Sao_Paulo")
#> [1] "2022-06-08 00:04:44 -03"
lubridate::with_tz(lubridate::now(), tzone = "GMT")
#> [1] "2022-06-08 03:04:44 GMT"
lubridate::with_tz(lubridate::now(), tzone = "Europe/Berlin")
#> [1] "2022-06-08 05:04:44 CEST"
## Altera o fuso sem mudar a hora
lubridate::force_tz(lubridate::now(), tzone = "GMT")
#> [1] "2022-06-08 00:04:44 GMT"
Para se aprofundar no tema, recomendamos a leitura do Capítulo 16 Dates and times de Wickham & Grolemund (2017).
5.12 purrr
O pacote purrr
implementa a Programação Funcional no R, fornecendo um conjunto completo e consistente de ferramentas para trabalhar com funções e vetores. A programação funcional é um assunto bastante extenso, sendo mais conhecido no R pela família de funções purrr::map()
, que permite substituir muitos loops for por um código mais sucinto e fácil de ler. Não focaremos aqui nas outras funções, pois esse é um assunto extremamente extenso.
Todas as funções deste pacote são listadas na página de referência do pacote.
Um loop for pode ser entendido como uma iteração: um bloco de códigos é repetido mudando um contador de uma lista de possibilidades. Vamos exemplificar com uma iteração bem simples, onde imprimiremos no console os valores de 1 a 10, utilizando a função for()
, um contador i
em um vetor de dez números 1:10
que será iterado, no bloco de códigos definido entre {}
, usando a função print()
para imprimir os valores.
A ideia é bastante simples: a função for()
vai atribuir o primeiro valor da lista ao contador i
, esse contador será utilizado em todo o bloco de códigos. Quando o bloco terminar, o segundo valor é atribuído ao contador i
e entra no bloco de códigos, repetindo esse processo até que todos os elementos da lista tenham sido atribuídos ao contador.
## Loop for
for(i in 1:10){
print(i)
}
#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
#> [1] 6
#> [1] 7
#> [1] 8
#> [1] 9
#> [1] 10
Com essa ideia em mente, a programação funcional faz a mesma operação utilizando a função purrr::map()
. O mesmo loop for ficaria dessa forma.
## Loop for com map
purrr::map(.x = 1:10, .f = print)
#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
#> [1] 6
#> [1] 7
#> [1] 8
#> [1] 9
#> [1] 10
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> [1] 2
#>
#> [[3]]
#> [1] 3
#>
#> [[4]]
#> [1] 4
#>
#> [[5]]
#> [1] 5
#>
#> [[6]]
#> [1] 6
#>
#> [[7]]
#> [1] 7
#>
#> [[8]]
#> [1] 8
#>
#> [[9]]
#> [1] 9
#>
#> [[10]]
#> [1] 10
Nessa estrutura, temos:
map(.x, .f)
-
.x
: um vetor, lista ou data frame -
.f
: uma função
Num outro exemplo, aplicaremos a função sum()
para somar os valores de vários elementos de uma lista.
## Função map
x <- list(1:5, c(4, 5, 7), c(1, 1, 1), c(2, 2, 2, 2, 2))
purrr::map(x, sum)
#> [[1]]
#> [1] 15
#>
#> [[2]]
#> [1] 16
#>
#> [[3]]
#> [1] 3
#>
#> [[4]]
#> [1] 10
Há diferente tipos de retornos da família purrr::map()
.
-
map()
: retorna uma lista -
map_chr()
: retorna um vetor de strings -
map_dbl()
: retorna um vetor numérico (double) -
map_int()
: retorna um vetor numérico (integer) -
map_lgl()
: retorna um vetor lógico -
map_dfr()
: retorna um data frame (por linhas) -
map_dfc()
: retorna um data frame (por colunas)
## Variações da função map
purrr::map_dbl(x, sum)
#> [1] 15 16 3 10
purrr::map_chr(x, paste, collapse = " ")
#> [1] "1 2 3 4 5" "4 5 7" "1 1 1" "2 2 2 2 2"
Essas funcionalidades já eram conhecidas no R Base pelas funções da família apply()
: apply()
, lapply()
, sapply()
, vapply()
, mapply()
, rapply()
e tapply()
. Essas funções formam a base de combinações mais complexas e ajudam a realizar operações com poucas linhas de código, para diferentes retornos.
Temos ainda duas variantes da função map()
: purrr::map2()
e purrr::pmap()
, para duas ou mais listas, respectivamente. Como vimos para a primeira função, existem várias variações do sufixo para modificar o retorno da função.
## Listas
x <- list(3, 5, 0, 1)
y <- list(3, 5, 0, 1)
z <- list(3, 5, 0, 1)
## Função map2
purrr::map2_dbl(x, y, prod)
#> [1] 9 25 0 1
## Função pmap
purrr::pmap_dbl(list(x, y, z), prod)
#> [1] 27 125 0 1
Essas funções podem ser usadas em conjunto para implementar rotinas de manipulação e análise de dados com poucas linhas de código, mas que não exploraremos em sua completude aqui. Listamos dois exemplos simples.
## Resumo dos dados
penguins %>%
dplyr::select(where(is.numeric)) %>%
tidyr::drop_na() %>%
purrr::map_dbl(mean)
#> bill_length_mm bill_depth_mm flipper_length_mm body_mass_g year
#> 43.92193 17.15117 200.91520 4201.75439 2008.02924
## Análise dos dados
penguins %>%
dplyr::group_split(island, species) %>%
purrr::map(~ lm(bill_depth_mm ~ bill_length_mm, data = .x)) %>%
purrr::map(summary) %>%
purrr::map("r.squared")
#> [[1]]
#> [1] 0.2192052
#>
#> [[2]]
#> [1] 0.4139429
#>
#> [[3]]
#> [1] 0.2579242
#>
#> [[4]]
#> [1] 0.4271096
#>
#> [[5]]
#> [1] 0.06198376
Para se aprofundar no tema, recomendamos a leitura do Capítulo 21 Iteration de Wickham & Grolemund (2017).
5.13 Para se aprofundar
Listamos a seguir livros que recomendamos para seguir com sua aprendizagem em R e tidyverse.
5.13.1 Livros
Recomendamos aos (às) interessados(as) os livros: i) Oliveira e colaboradores (2018) Ciência de dados com R, ii) Grolemund (2018) The Essentials of Data Science: Knowledge Discovery Using R,iii) Holmes e Huber (2019) Modern Statistics for Modern Biology, iv) Ismay e Kim (2020) Statistical Inference via Data Science: A ModernDive into R and the Tidyverse, v) Wickham e Grolemund (2017) R for Data Science: Import, Tidy, Transform, Visualize, and Model Data, vi) Zumel e Mount (2014) Practical Data Science with R Paperback, vii) Irizarry (2017) Introduction to Data Science: Data Analysis and Prediction Algorithms with R, e viii) Irizarry (2019) Introduction to Data Science.
5.14 Exercícios
5.1
Reescreva as operações abaixo utilizando pipes %>%
.
log10(cumsum(1:100))
sum(sqrt(abs(rnorm(100))))
sum(sort(sample(1:10, 10000, rep = TRUE)))
5.2
Use a função download.file()
e unzip()
para baixar e extrair o arquivo do data paper de médios e grandes mamíferos: ATLANTIC MAMMALS. Em seguida, importe para o R, usando a função readr::read_csv()
.
5.3
Use a função tibble::glimpse()
para ter uma noção geral dos dados importados no item anterior.
5.4
Compare os dados de penguins (palmerpenguins::penguins_raw e palmerpenguins::penguins). Monte uma série de funções dos pacotes tidyr e dplyr
para limpar os dados e fazer com que o primeiro dado seja igual ao segundo.
5.5 Usando os dados de penguins (palmerpenguins::penguins), calcule a correlação de Pearson entre comprimento e profundidade do bico para cada espécie e para todas as espécies. Compare os índices de correlação para exemplificar o Paradoxo de Simpson.
5.6 Oficialmente a pandemia de COVID-19 começou no Brasil com o primeiro caso no dia 26 de fevereiro de 2020. Calcule quantos anos, meses e dias se passou desde então. Calcule também quanto tempo se passou até você ser vacinado.