Trilhas
/
R
R
/
Exercícios
/
Árvores e tibbleações
Árvores e tibbleações

Árvores e tibbleações

Exercício de aprendizagem

Introdução

Em outras partes do syllabus, vimos vários tipos de dados com características diferentes.

  • Vetores atômicos são unidimensionais e homogêneos quanto ao tipo.
  • Listas são unidimensionais e os elementos podem ser de tipos heterogêneos.
  • Matrizes e arrays são multidimensionais e homogêneos.

Este Conceito vai abordar formas de armazenar dados multidimensionais e heterogêneos. Na prática, a maior parte dos dados do mundo real é assim, então agora estamos chegando ao cerne de como R é (em sua maioria) usado na prática.

Variantes de dataframe

Ao longo das décadas, R adicionou vários tipos de dados para lidar com dados tabulares.

Este syllabus vai focar principalmente em tibbles, mas é útil conhecer as alternativas.

O data.frame

No R base, um data.frame é uma list de vectors de mesmo comprimento. Podemos pensar nele como uma tabela retangular de dados, em que cada coluna é homogênea, mas cada linha pode conter (e geralmente contém) tipos diferentes de dados.

Um exemplo para ilustrar isso:

# create the column vectors
languages <- c("Fortran", "R", "Python", "Julia")
created <- c(1957, 1993, 1991, 2012)
has.syllabus <- c(FALSE, TRUE, TRUE, TRUE)

# join columns to create the dataframe
df <- data.frame(languages, created, has.syllabus)
df
#>   languages created has.syllabus
#> 1   Fortran    1957        FALSE
#> 2         R    1993         TRUE
#> 3    Python    1991         TRUE
#> 4     Julia    2012         TRUE

Temos uma coluna de strings de caracteres, uma coluna de números e uma coluna de valores Boolean. Em escala maior, essa é uma forma intuitiva de representar muitas coleções de dados do mundo real.

O tibble

O design do data.frame é antigo.

Décadas de experiência, somadas a mudanças na forma como R é usado, levaram a um redesenho que criou uma alternativa modernizada no Tidyverse: os tibbles.

Comparados ao R base, os tibbles têm:

  • Padrões diferentes, para reduzir problemas comuns.
  • Menos disposição para forçar a conversão de tipos de dados na entrada.
  • Mais mensagens de erro, e mais claras.
  • Formatos de exibição diferentes e, geralmente, melhores.

Em resumo, um tibble busca "fazer menos e reclamar mais", o que também é descrito como "preguiçoso e mal-humorado".

Os tipos geralmente são intercambiáveis: qualquer função que aceita um data.frame também aceita um tibble, e vice versa.

As conversões entre os tipos são fáceis, com as_tibble(df) e as.data.frame(tbl).

Para código novo, usar tibbles provavelmente vai ajudar você a escrever código mais robusto. Porém, código e dados legados são muito abundantes no mundo do R, então é provável que o data.frame continue comum por muito tempo.

# column vectors are same as for data.frame
library(tibble)
tbl <- tibble(languages, created, has.syllabus)
tbl
  # A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 R            1993 TRUE        
#> 3 Python       1991 TRUE        
#> 4 Julia        2012 TRUE      

Repare no formato de impressão padrão: a linha de comentário com as dimensões é impressa automaticamente, e os tipos das colunas também são exibidos.

Trabalhando com tibbles

Os tibbles são parte central do Tidyverse, então adicione-os com library(tibble) ou library(tidyverse).

Criando um tibble

Da forma mais simples, podemos usar a função tibble() para juntar vetores de colunas, assim como em data.frame(). Um exemplo disso foi mostrado em uma seção anterior.

Se for mais conveniente informar os valores linha por linha, a função correspondente é tribble().

Na prática, existem dezenas de formas de criar tibbles, já que eles são o formato de saída padrão de uma grande variedade de funções do Tidyverse.

Manipulando um tibble

O Conceito de Programação Funcional apresentou a biblioteca purrr para manipular vetores e listas (estruturas de dados unidimensionais).

Para dataframes (tradicionais ou tibbles), a biblioteca correspondente é dplyr.

Subconjuntos

Dataframes, incluindo tibbles, podem ser tratados como listas de vetores de colunas, então a indexação de listas recupera uma coluna específica.

tbl
# A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 R            1993 TRUE        
#> 3 Python       1991 TRUE        
#> 4 Julia        2012 TRUE    

tbl$created
#> [1] 1957 1993 1991 2012

Um dataframe também pode ser indexado com indexação no estilo de matrizes.

tbl[c(2, 4), 1:2]
  # A tibble: 2 × 2
#>   languages created
#>   <chr>       <dbl>
#> 1 R            1993
#> 2 Julia        2012

No R moderno com o ecossistema Tidyverse, as funções do dplyr geralmente são mais flexíveis e convenientes, e serão o foco do resto deste Conceito.

Operações por coluna

Obtenha uma única coluna com pull(), usando o nome ou o número sequencial (números negativos contam da direita para a esquerda).

tbl |> pull(created)
#> [1] 1957 1993 1991 2012

Esse é o mesmo resultado de tbl$created, mas usando uma função amigável a pipelines.

Para obter várias colunas, a função adequada é select(), que é muito versátil. Obtenha (ou descarte) colunas com base em propriedades do nome ou do tipo delas.

# Range with position and/or name
tbl |> select(1:created)
  # A tibble: 4 × 2
#>   languages created
#>   <chr>       <dbl>
#> 1 Fortran      1957
#> 2 R            1993
#> 3 Python       1991
#> 4 Julia        2012      

# Use type of column
tbl |> select(where(is.numeric))
  # A tibble: 4 × 1
#>   created
#>     <dbl>
#> 1    1957
#> 2    1993
#> 3    1991
#> 4    2012

Vários critérios são permitidos, usando os operadores Boolean &, | e ! (e, ou, não).

Todo esse poder parece meio bobo com nosso dataframe de brinquedo sobre linguagens. Felizmente, o tibble starwars está incluído no dplyr, o que nos dá algo maior para praticar.

# pick a subset of columns
starwars |> 
  select(name | ends_with("color")) |> 
  head(5)
  # A tibble: 5 × 4
#>   name           hair_color skin_color  eye_color
#>   <chr>          <chr>      <chr>       <chr>    
#> 1 Luke Skywalker blond      fair        blue     
#> 2 C-3PO          NA         gold        yellow   
#> 3 R2-D2          NA         white, blue red      
#> 4 Darth Vader    none       white       yellow   
#> 5 Leia Organa    brown      light       brown    

Operações por linha

Obtenha as linhas que atendem a algum critério com filter(), ou exclua-as com filter_out().

starwars |> 
  select(name:mass) |> 
  filter(between(height, 150, 165) & !is.na(mass))
  # A tibble: 4 × 3
#>   name               height  mass
#>   <chr>               <int> <dbl>
#> 1 Leia Organa           150    49
#> 2 Beru Whitesun Lars    165    75
#> 3 Nien Nunb             160    68
#> 4 Ben Quadinaros        163    65

Os critérios de filtragem podem ser tão complexos quanto você quiser, mas sempre se baseiam no conteúdo das linhas.

Se os números das linhas forem conhecidos, podemos usar várias funções slice().

starwars |> 
  select(name | homeworld) |> 
  slice(20:25)
  # A tibble: 6 × 2
#>   name             homeworld
#>   <chr>            <chr>    
#> 1 Palpatine        Naboo    
#> 2 Boba Fett        Kamino   
#> 3 IG-88            NA       
#> 4 Bossk            Trandosha
#> 5 Lando Calrissian Socorro  
#> 6 Lobot            Bespin   

Modificando um tibble

Primeiro aviso: o padrão copy-on-modify significa que o tibble original geralmente permanece inalterado.

A maioria das modificações é aplicada por coluna.

Os nomes das colunas podem ser alterados com rename(newname = oldname), ou com rename_with() para aplicar uma função. Repare na sintaxe dentro de rename(). O conteúdo da coluna oldname é vinculado ao nome newname, daí a ordem.

A ordem das colunas pode ser alterada com relocate(). Por padrão, as colunas especificadas são movidas para as posições mais à esquerda, mas um argumento .before ou .after pode ser usado para um posicionamento mais preciso.

sw <- starwars |> select(name:species) |> slice(1:4)
sw
  # A tibble: 4 × 11
#>   name           height  mass hair_color skin_color  eye_color birth_year sex   gender    homeworld species
#>   <chr>           <int> <dbl> <chr>      <chr>       <chr>          <dbl> <chr> <chr>     <chr>     <chr>  
#> 1 Luke Skywalker    172    77 blond      fair        blue            19   male  masculine Tatooine  Human  
#> 2 C-3PO             167    75 NA         gold        yellow         112   none  masculine Tatooine  Droid  
#> 3 R2-D2              96    32 NA         white, blue red             33   none  masculine Naboo     Droid  
#> 4 Darth Vader       202   136 none       white       yellow          41.9 male  masculine Tatooine  Human  

sw |> relocate(c(species, homeworld), .after = name)
  # A tibble: 4 × 11
#>   name           species homeworld height  mass hair_color skin_color  eye_color birth_year sex   gender   
#>   <chr>          <chr>   <chr>      <int> <dbl> <chr>      <chr>       <chr>          <dbl> <chr> <chr>    
#> 1 Luke Skywalker Human   Tatooine     172    77 blond      fair        blue            19   male  masculine
#> 2 C-3PO          Droid   Tatooine     167    75 NA         gold        yellow         112   none  masculine
#> 3 R2-D2          Droid   Naboo         96    32 NA         white, blue red             33   none  masculine
#> 4 Darth Vader    Human   Tatooine     202   136 none       white       yellow          41.9 male  masculine

Para mudanças maiores, mutate() permite:

  • Criar novas colunas que são funções de colunas existentes.
  • Substituir uma coluna existente, criando uma nova coluna com o mesmo nome.
  • Excluir uma coluna, definindo seu valor como NULL.

Claramente, mutate() é poderoso, potencialmente confuso, e um motivo para ficarmos muito gratos pelo copy-on-modify.

Não há motivo óbvio para se preocupar com o Índice de Massa Corporal dos personagens de Star Wars, mas, só por precaução:

starwars |> 
  select(c(name, species, height, mass)) |> 
  mutate(BMI = mass / (height / 100)^2) |> 
  head(4)
  # A tibble: 4 × 5
#>   name           species height  mass   BMI
#>   <chr>          <chr>    <int> <dbl> <dbl>
#> 1 Luke Skywalker Human      172    77  26.0
#> 2 C-3PO          Droid      167    75  26.9
#> 3 R2-D2          Droid       96    32  34.7
#> 4 Darth Vader    Human      202   136  33.3

Operações por linha são menos comuns para modificar um único tibble (a combinação de vários tibbles será discutida em um conceito posterior).

Uma exceção: arrange() ordena as linhas pelos valores de uma ou mais colunas.

tbl
#>       A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 R            1993 TRUE        
#> 3 Python       1991 TRUE        
#> 4 Julia        2012 TRUE  

tbl |> arrange(languages)
#>       A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 Julia        2012 TRUE        
#> 3 Python       1991 TRUE        
#> 4 R            1993 TRUE     

Instruções

Um grande pomar de cerejeiras geralmente tem um pequeno estoque de árvores excedentes que gostaria de oferecer como madeira. Isso ajuda a manter o pomar saudável e, ao mesmo tempo, gera uma receita extra. Eles pediram sua ajuda para programar algumas funcionalidades que auxiliem no processo de seleção de árvores quando um possível cliente liga.

Note

A biblioteca datasets embutida tem muitos conjuntos de dados de exemplo, e este exercício usa o conjunto de dados trees. Para chamar o conjunto de dados, basta usar o nome trees.

library(datasets) # loading is optional

trees |> head(3)
#>    A data.frame: 3 × 3 	
#>   Girth	Height	Volume
#>	 <dbl>	 <dbl>	 <dbl>
#> 1   8.3	    70	  10.3  
#> 2   8.6	    65	  10.3
#> 3   8.8	    63	  10.2

Repare nas três colunas: Girth, Height e Volume

O conjunto de dados inicialmente está na forma de um data.frame, mas você pode querer trabalhar com ele como um tibble.

1. Renomeie uma coluna

Infelizmente houve um erro administrativo que nunca foi corrigido, e uma coluna do conjunto de dados se chama Girth, mas na verdade ela é o Diameter.

Defina um data.frame/tibble chamado tree_data que corrija o nome da coluna Girth em trees para Diameter.

head(tree_data, 3)
#>     A tibble: 3 × 3 
#> Diameter	 Height	Volume
#>    <dbl>	  <dbl>	 <dbl>
#>      8.3	     70	  10.3
#>      8.6	     65	  10.3
#>      8.8	     63	  10.2

2. Adicione as colunas de circunferência e peso

A circunferência ainda é uma medida útil, assim como o peso da madeira. A circunferência é facilmente calculada por πD, onde D é o diâmetro. O peso de toda a madeira pode ser calculado por ρV, onde ρ = 35 é a densidade da madeira de cerejeira e V é o volume da madeira.

Defina a função girth_n_weight(data, rnd_digits), que recebe um dataframe e argumentos numéricos. A função deve retornar um novo dataframe com as duas colunas adicionais: Girth e Weight, arredondadas para o número apropriado de dígitos dado por rnd_digits.

girth_n_weight(tree_data, 1) |> head(3)
#>           A tibble: 3 × 5 
#> Diameter  Height	 Volume	 Girth	Weight
#>    <dbl>	  <dbl>	  <dbl>	 <dbl>	 <dbl>
#>      8.3	     70	   10.3	  26.1	 360.5
#>      8.6	     65	   10.3	  27.0	 360.5
#>      8.8	     63	   10.2	  27.6	 357.0

3. Cópia do conjunto de dados do pomar

Para cada possível cliente, o pomar mantém uma cópia especial do conjunto de dados para ajudar na venda. Esse conjunto de dados tem as colunas mais relevantes, Weight e Height, movidas para a frente, e fica ordenado por Weight.

Defina a função orchard_copy(data, important_cols), que recebe um dataframe como entrada. Ela deve retornar um novo dataframe com Weight e Height movidas para a frente e as linhas ordenadas por Weight.

tree_data |> girth_n_weight() |> orchard_copy() |> head(3)
#>            A tibble: 3 × 5 
#> Weight	Height	Diameter	Volume	Girth
#>  <dbl>	 <dbl>	   <dbl>	 <dbl>	<dbl>
#>    357	    63	       9	    10	   28
#>    360	    70	       8	    10	   26
#>    360	    65	       9	    10	   27

4. Cópia do conjunto de dados do cliente

Cada possível cliente receberá uma versão pessoal do conjunto de dados com base em suas preferências. Para isso, o cliente pode especificar: a altura mínima, a altura máxima e o peso máximo de uma única árvore.

Defina a função customer_copy(data, min_height, max_height, max_weight). Retorne uma cópia do dataframe para o cliente, com as colunas Height, Weight, Diameter e Girth, contendo as linhas que estão dentro dos limites.

tree_data |> 
    girth_n_weight(1) |> 
    orchard_copy() |>
    customer_copy(65, 75, 1500) |> 
    head(3)
#>        A tibble: 3 × 4 
#> Height	Weight	Diameter	Girth
#>  <dbl>	 <dbl>	   <dbl>	<dbl>
#>     70	 360.5	     8.3	 26.1
#>     65	 360.5	     8.6	 27.0
#>     66	 546.0	    11.0	 34.6
Editar via GitHub O link abre em uma nova janela ou aba
R Exercism

Tudo pronto para começar Árvores e tibbleações?

Crie sua conta no Exercism para aprender e dominar R com 21 conceitos111 exercícios e mentoria humana de verdade, tudo de graça.