Em outras partes do syllabus, vimos vários tipos de dados com características diferentes.
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.
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.
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.
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:
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.
Os tibbles são parte central do Tidyverse, então adicione-os com library(tibble) ou library(tidyverse).
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.
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.
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.
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
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
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:
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
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.
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.
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
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
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
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
Crie sua conta no Exercism para aprender e dominar R com 21 conceitos111 exercícios e mentoria humana de verdade, tudo de graça.