Parcours
/
R
R
/
Exercices
/
Arbres et Tibble-ations
Arbres et Tibble-ations

Arbres et Tibble-ations

Exercice d'apprentissage

Introduction

Dans d'autres parties du programme, on a vu différents types de données aux caractéristiques variées.

  • Les vecteurs atomiques sont unidimensionnels et de type homogène.
  • Les listes sont unidimensionnelles et leurs éléments peuvent être de types hétérogènes.
  • Les matrices et les tableaux sont multidimensionnels et homogènes.

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.

Variantes de dataframe

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.

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

Le 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 :

  • Des valeurs par défaut différentes, pour réduire les problèmes courants.
  • Une propension moindre à convertir les types de données à la saisie.
  • Des messages d'erreur plus nombreux et plus clairs.
  • Des formats d'affichage différents, généralement meilleurs.

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.

Travaille avec les tibbles

Les tibbles font partie intégrante du Tidyverse, alors charge-les avec library(tibble) ou library(tidyverse).

Crée un tibble

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.

Manipule un tibble

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.

Sous-ensembles

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.

Opérations par colonne

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    

Opérations par ligne

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   

Modifie un tibble

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 :

  • Créer de nouvelles colonnes qui sont des fonctions de colonnes existantes.
  • Remplacer une colonne existante, en créant une nouvelle colonne portant le même nom.
  • Supprimer une colonne, en lui affectant la valeur 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     

Instructions

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.

Note

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.

1. Renommer une colonne

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

2. Ajouter les colonnes de circonférence et de poids

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

3. Copie du jeu de données pour le verger

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

4. Copie du jeu de données pour le client

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
Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet
R Exercism

Prêt à commencer Arbres et Tibble-ations ?

Inscris-toi sur Exercism pour apprendre et maîtriser R avec 21 concepts111 exercices, et un vrai mentorat humain, le tout gratuitement.