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.

Modelo das ferramentas necessárias em um projeto típico de ciência de dados: importar, organizar, entender (transformar, visualizar, modelar) e comunicar, envolto à essas ferramentas está a programação. Adaptado de: Wickham & Grolemund [-@wickham2017].

Figura 5.1: Modelo das ferramentas necessárias em um projeto típico de ciência de dados: importar, organizar, entender (transformar, visualizar, modelar) e comunicar, envolto à essas ferramentas está a programação. Adaptado de: Wickham & Grolemund (2017).

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 classe tibble
  • tidyr: transformação de dados para tidy
  • 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 e writexl: 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 do data.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.

## Funções no formato snake case
read_csv()
read_tsv()
as_tibble()
left_join()
group_by()

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).

  1. Cada variável em uma coluna
  2. Cada observação em uma linha
  3. Cada valor como uma célula
As três regras que tornam um conjunto de dados *tidy*. Adaptado de: Wickham & Grolemund [-@wickham2017]).

Figura 5.2: As três regras que tornam um conjunto de dados tidy. Adaptado de: Wickham & Grolemund (2017)).

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 com NA do conjunto de dados
  • replace_na(): substitui NA 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:

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.

  • dtplyr: manipular conjuntos de dados data.table
  • dbplyr: manipular conjuntos de dados SQL
  • sparklyr: manipular conjuntos de dados no Apache Spark

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.

## Coluna como vetor
penguins_select_pull <- penguins %>% 
    dplyr::pull(bill_length_mm)
head(penguins_select_pull, 15)
#>  [1] 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

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.

Diferentes tipos de joins, representados com um diagrama de Venn. Adaptado de: Wickham & Grolemund [-@wickham2017].

Figura 5.3: Diferentes tipos de joins, representados com um diagrama de Venn. Adaptado de: Wickham & Grolemund (2017).

Considerando a nomenclatura de duas tabelas de dados por x e y, temos:

  • inner_join(x, y): mantém apenas as observações em x e em y
  • left_join(x, y): mantém todas as observações em x
  • right_join(x, y): mantém todas as observações em y
  • full_join(x, y): mantém todas as observações em x e em y

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 em x que têm uma correspondência em y
  • anti_join(x, y): elimina todas as observações em x que têm uma correspondência em y

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 em x, y ou mais dos conjuntos de dados
  • interesect(x, y): retorna apenas as linhas que aparecem em x e em y
  • setdiff(x, y): retorna as linhas que aparecem x, mas não em y
  • setequal(x, y): retorna se x e y 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.

## 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.

## Carregar
library(lubridate)

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.

## 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.

## 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.

## 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().

## 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.

Soluções dos exercícios.