Dans d'autres parties du programme, on a vu différents types de données aux caractéristiques variées.
Ce concept s'intéresse aux façons de stocker des données multidimensionnelles et hétérogènes. En pratique, la plupart des données du monde réel sont de ce type, et on arrive donc au cœur de l'usage que l'on fait de R en pratique.
Au fil des décennies, R a ajouté de nombreux types de données pour gérer les données tabulaires.
Ce programme portera principalement sur les tibbles, mais il est utile de connaître les alternatives.
data.frame
Dans le R de base, un data.frame est une list de vectors de même longueur.
On peut le voir comme un tableau rectangulaire de données, dans lequel chaque colonne est homogène, mais où chaque ligne peut contenir (et contient généralement) des types de données différents.
Un exemple pour illustrer cela :
# 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
On a une colonne de chaînes de caractères, une colonne de nombres et une colonne de booléens. À plus grande échelle, c'est une façon intuitive de représenter de nombreux ensembles de données du monde réel.
tibble
La conception du data.frame est ancienne.
Des décennies d'expérience, ainsi que l'évolution de la façon dont R est utilisé, ont conduit à une refonte, donnant naissance dans le Tidyverse à une alternative modernisée : les tibbles.
Par rapport au R de base, les tibbles ont :
En bref, un tibble cherche à « faire moins et se plaindre plus », ce que l'on décrit aussi comme « paresseux et grincheux ».
Ces types sont généralement interchangeables : toute fonction qui accepte un data.frame acceptera aussi un tibble, et vice versa.
La conversion entre les types est facile, avec as_tibble(df) et as.data.frame(tbl).
Pour les nouveaux projets, utiliser des tibbles t'aidera sans doute à écrire du code plus robuste.
Cependant, le code et les données hérités sont très abondants dans le monde de R, donc le data.frame devrait rester courant encore longtemps.
# 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
Remarque le format d'affichage par défaut : la ligne de commentaire avec les dimensions est imprimée automatiquement, et les types des colonnes sont eux aussi affichés.
Les tibbles font partie intégrante du Tidyverse, alors charge-les avec library(tibble) ou library(tidyverse).
Le plus simple est d'utiliser la fonction tibble() pour assembler des vecteurs colonnes, comme pour data.frame().
On en a vu un exemple dans une section précédente.
S'il est plus pratique de saisir les valeurs ligne par ligne, la fonction correspondante est tribble().
En pratique, il existe des dizaines de façons de créer des tibbles, car c'est le format de sortie par défaut d'un large éventail de fonctions du Tidyverse.
Le concept de programmation fonctionnelle a présenté la bibliothèque purrr pour manipuler des vecteurs et des listes (des structures de données à une dimension).
Pour les dataframes (traditionnels ou sous forme de tibbles), la bibliothèque à utiliser est dplyr.
Les dataframes, y compris les tibbles, peuvent être traités comme des listes de vecteurs colonnes, si bien qu'on récupère une colonne donnée comme on le ferait avec une liste.
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
On peut aussi accéder à un dataframe avec la syntaxe des matrices.
tbl[c(2, 4), 1:2]
# A tibble: 2 × 2
#> languages created
#> <chr> <dbl>
#> 1 R 1993
#> 2 Julia 2012
Dans le R moderne et son écosystème Tidyverse, les fonctions de dplyr sont généralement plus souples et plus pratiques, et c'est sur elles que se concentrera la suite de ce concept.
Récupère une seule colonne avec pull(), en donnant son nom ou son numéro d'ordre (les nombres négatifs comptent de droite à gauche).
tbl |> pull(created)
#> [1] 1957 1993 1991 2012
C'est le même résultat que tbl$created, mais avec une fonction adaptée aux pipelines.
Pour récupérer plusieurs colonnes, la fonction appropriée est select(), qui est très polyvalente.
Récupère (ou supprime) des colonnes selon les propriétés de leur nom ou de leur type.
# 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
On peut combiner plusieurs critères avec les opérateurs booléens &, | et ! (et, ou, non).
Une telle puissance semble assez ridicule avec notre petit dataframe jouet sur les langages.
Heureusement, le tibble starwars est fourni avec dplyr, ce qui nous donne un ensemble plus gros pour nous entraîner.
# 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
Récupère les lignes qui correspondent à un critère avec filter(), ou exclus-les avec 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
Les critères de filtrage peuvent être arbitrairement complexes, mais reposent toujours sur le contenu des lignes.
Si l'on connaît le numéro des lignes, on peut utiliser divers membres de la famille de fonctions 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
Première mise en garde : le comportement par défaut copy-on-modify fait que le tibble d'origine reste généralement inchangé.
La plupart des modifications s'appliquent colonne par colonne.
On peut changer les noms de colonnes avec rename(newname = oldname), ou rename_with() pour appliquer une fonction.
Remarque la syntaxe à l'intérieur de rename().
Le contenu de la colonne oldname est lié au nom newname, d'où l'ordre.
L'ordre des colonnes peut être modifié avec relocate().
Par défaut, la ou les colonnes indiquées sont déplacées vers la ou les positions les plus à gauche, mais on peut utiliser un argument .before ou .after pour un positionnement plus fin.
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
Pour des modifications plus importantes, mutate() propose plusieurs opérations :
NULL.De toute évidence, mutate() est puissant, potentiellement déroutant, et une bonne raison d'être très reconnaissant envers copy-on-modify.
Il n'y a aucune raison évidente de s'intéresser à l'indice de masse corporelle des personnages de Star Wars, mais au cas 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
Les opérations par ligne sont moins courantes pour modifier un seul tibble (la fusion de plusieurs tibbles sera abordée dans un autre concept).
Une exception : arrange() trie les lignes selon les valeurs d'une ou plusieurs colonnes.
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
Un grand verger de cerisiers dispose généralement d'un petit stock d'arbres en surplus qu'il aimerait proposer comme bois d'œuvre. Cela permet de garder le verger en bonne santé tout en générant des revenus supplémentaires. On t'a demandé d'écrire du code pour faciliter la sélection des arbres quand un client potentiel appelle.
La bibliothèque datasets intégrée contient de nombreux jeux de données d'exemple, et cet exercice utilise le jeu de données trees.
Pour appeler le jeu de données, il suffit d'utiliser le nom 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
Remarque les trois colonnes : Girth, Height et Volume
Le jeu de données se présente au départ sous la forme d'un data.frame, mais tu peux choisir de le manipuler comme un tibble.
Malheureusement, une erreur de saisie n'a jamais été corrigée et une colonne du jeu de données s'appelle Girth, alors qu'il s'agit en réalité du Diameter.
Définis un data.frame/tibble appelé tree_data qui corrige le nom de la colonne Girth de trees en 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
La circonférence reste une mesure utile, tout comme le poids du bois.
La circonférence se calcule facilement par πD, où D est le diamètre.
Le poids de tout le bois peut se calculer par ρV, où ρ = 35 est la densité du bois de cerisier et V est le volume du bois.
Définis la fonction girth_n_weight(data, rnd_digits) qui prend en arguments un dataframe et un nombre.
La fonction doit renvoyer un nouveau dataframe avec les deux colonnes supplémentaires : Girth et Weight, arrondies au nombre de chiffres indiqué par 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
Pour chaque client potentiel, le verger conserve une copie spéciale du jeu de données pour faciliter la vente.
Ce jeu de données place les colonnes les plus pertinentes, Weight et Height, au début, et est trié par Weight.
Définis la fonction orchard_copy(data, important_cols) qui prend un dataframe en entrée.
Elle doit renvoyer un nouveau dataframe avec Weight et Height déplacées au début et les lignes triées par 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
Chaque client potentiel recevra une version personnelle du jeu de données selon ses préférences. Pour cela, le client peut préciser : la hauteur minimale, la hauteur maximale et le poids maximal d'un arbre.
Définis la fonction customer_copy(data, min_height, max_height, max_weight).
Renvoie une copie du dataframe pour le client avec les colonnes Height, Weight, Diameter et Girth, dont les lignes respectent les contraintes.
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
Inscris-toi sur Exercism pour apprendre et maîtriser R avec 21 concepts111 exercices, et un vrai mentorat humain, le tout gratuitement.