Manipulation rapide de bases de données avec dplyr et ses alliés

Charles Martin

Octobre 2018

Notre jeu de données

library(ggplot2)
data(msleep)
msleep
# A tibble: 83 x 11
   name   genus vore  order conservation sleep_total sleep_rem sleep_cycle
   <chr>  <chr> <chr> <chr> <chr>              <dbl>     <dbl>       <dbl>
 1 Cheet… Acin… carni Carn… lc                  12.1      NA        NA    
 2 Owl m… Aotus omni  Prim… <NA>                17         1.8      NA    
 3 Mount… Aplo… herbi Rode… nt                  14.4       2.4      NA    
 4 Great… Blar… omni  Sori… lc                  14.9       2.3       0.133
 5 Cow    Bos   herbi Arti… domesticated         4         0.7       0.667
 6 Three… Brad… herbi Pilo… <NA>                14.4       2.2       0.767
 7 North… Call… carni Carn… vu                   8.7       1.4       0.383
 8 Vespe… Calo… <NA>  Rode… <NA>                 7        NA        NA    
 9 Dog    Canis carni Carn… domesticated        10.1       2.9       0.333
10 Roe d… Capr… herbi Arti… lc                   3        NA        NA    
# ... with 73 more rows, and 3 more variables: awake <dbl>, brainwt <dbl>,
#   bodywt <dbl>

Les 5 opérations de base

Comme lors de la formation précédente, toutes ces opérations pourraient s’effectuer avec les fonctions R de base, mais de façon beaucoup moins lisible et intégrée (nous y reviendront)

Filtrer

P. ex., disons que nous désirons conserver uniquement les observations pour les mammifères de plus de 200 g

library(dplyr)

Attaching package: 'dplyr'
The following objects are masked from 'package:stats':

    filter, lag
The following objects are masked from 'package:base':

    intersect, setdiff, setequal, union
filter(msleep, bodywt > 0.2)
# A tibble: 61 x 11
   name   genus vore  order conservation sleep_total sleep_rem sleep_cycle
   <chr>  <chr> <chr> <chr> <chr>              <dbl>     <dbl>       <dbl>
 1 Cheet… Acin… carni Carn… lc                  12.1      NA        NA    
 2 Owl m… Aotus omni  Prim… <NA>                17         1.8      NA    
 3 Mount… Aplo… herbi Rode… nt                  14.4       2.4      NA    
 4 Cow    Bos   herbi Arti… domesticated         4         0.7       0.667
 5 Three… Brad… herbi Pilo… <NA>                14.4       2.2       0.767
 6 North… Call… carni Carn… vu                   8.7       1.4       0.383
 7 Dog    Canis carni Carn… domesticated        10.1       2.9       0.333
 8 Roe d… Capr… herbi Arti… lc                   3        NA        NA    
 9 Goat   Capri herbi Arti… lc                   5.3       0.6      NA    
10 Guine… Cavis herbi Rode… domesticated         9.4       0.8       0.217
# ... with 51 more rows, and 3 more variables: awake <dbl>, brainwt <dbl>,
#   bodywt <dbl>

Attention, comme toutes les manipulations d’objets dans R, le résultat est “perdu” si il n’est pas assigné à un nouvel objet…

grands_mams <- filter(msleep, bodywt > 0.2)

Dans RStudio, on peut aussi consulter le résultat d’une opération, avec ou sans la sauvegarde dans un objet :

View(filter(msleep, bodywt > 0.2))
View(grands_mams)

Quatre pièges à éviter

#1 Les nombres à virgule ne sont pas indéfiniment précis

1/49*49 == 1
[1] FALSE

R conserve dans ses calculs un nombre limité de décimales, ce qui fait que certaines erreurs d’arondissement peuvent compliquer les comparaisons.

On peut contourner le problème avec l’opérateur near

near(1/49*49, 1)
[1] TRUE

*#2 Le = ne veut pas dire *égal **

Le symbole = dans R est utilisé pour les assignations, comme un synonyme de <-. Pour effectuer des comparaisons (comme dans la majorité des langages informatiques), il faut utiliser ==.

filter(msleep, vore = "omni")
Error: `vore` (`vore = "omni"`) must not be named, do you need `==`?

**#3 Les données manquantes présentent certaines particularités **

On ne peut pas vérifier si une valeur est manquante avec ==

NA == NA
[1] NA

La logique de R sous-jacente étant que, si je ne connaîs pas l’âge de Paul et que je ne connais pas l’âge de Jacques, la réponse à “Est-ce que Paul et Jacques ont le même âge” est “Je ne sais pas”, et non “vrai”

Si on veut tester des valeurs manquantes, il faut utiliser la fonction is.na

filter(msleep, is.na(conservation))
# A tibble: 29 x 11
   name   genus vore  order conservation sleep_total sleep_rem sleep_cycle
   <chr>  <chr> <chr> <chr> <chr>              <dbl>     <dbl>       <dbl>
 1 Owl m… Aotus omni  Prim… <NA>                17         1.8      NA    
 2 Three… Brad… herbi Pilo… <NA>                14.4       2.2       0.767
 3 Vespe… Calo… <NA>  Rode… <NA>                 7        NA        NA    
 4 Afric… Cric… omni  Rode… <NA>                 8.3       2        NA    
 5 Weste… Euta… herbi Rode… <NA>                14.9      NA        NA    
 6 Galago Gala… omni  Prim… <NA>                 9.8       1.1       0.55
 7 Human  Homo  omni  Prim… <NA>                 8         1.9       1.5  
 8 Macaq… Maca… omni  Prim… <NA>                10.1       1.2       0.75
 9 "Vole… Micr… herbi Rode… <NA>                12.8      NA        NA    
10 Littl… Myot… inse… Chir… <NA>                19.9       2         0.2  
# ... with 19 more rows, and 3 more variables: awake <dbl>, brainwt <dbl>,
#   bodywt <dbl>

**#4 Il faut modifier notre pensée pour combiner certaines conditions **

R possède un opérateur permettant de faire des OU (|) et un permettant de faire des ET (&). Leur usage est cependant différent du français.

Si on veut p. ex. tous les mammifères omnivores ou carnivores, on serait tentés de faire :

filter(msleep, vore == "omni" | "carni")
Error in filter_impl(.data, quo): Evaluation error: operations are possible only for numeric, logical or complex types.

Mais il faut en fait spécifier toutes les éventualités, et les séparer par l’opérateur |. P. ex. :

filter(msleep, vore == "omni" | vore == "carni")
# A tibble: 39 x 11
   name   genus vore  order conservation sleep_total sleep_rem sleep_cycle
   <chr>  <chr> <chr> <chr> <chr>              <dbl>     <dbl>       <dbl>
 1 Cheet… Acin… carni Carn… lc                  12.1      NA        NA    
 2 Owl m… Aotus omni  Prim… <NA>                17         1.8      NA    
 3 Great… Blar… omni  Sori… lc                  14.9       2.3       0.133
 4 North… Call… carni Carn… vu                   8.7       1.4       0.383
 5 Dog    Canis carni Carn… domesticated        10.1       2.9       0.333
 6 Grivet Cerc… omni  Prim… lc                  10         0.7      NA    
 7 Star-… Cond… omni  Sori… lc                  10.3       2.2      NA    
 8 Afric… Cric… omni  Rode… <NA>                 8.3       2        NA    
 9 Lesse… Cryp… omni  Sori… lc                   9.1       1.4       0.15
10 Long-… Dasy… carni Cing… lc                  17.4       3.1       0.383
# ... with 29 more rows, and 3 more variables: awake <dbl>, brainwt <dbl>,
#   bodywt <dbl>

On peut cependant raccourcir ce genre d’affirmation avec l’opérateur %in%

filter(msleep, vore %in% c("omni", "carni"))
# A tibble: 39 x 11
   name   genus vore  order conservation sleep_total sleep_rem sleep_cycle
   <chr>  <chr> <chr> <chr> <chr>              <dbl>     <dbl>       <dbl>
 1 Cheet… Acin… carni Carn… lc                  12.1      NA        NA    
 2 Owl m… Aotus omni  Prim… <NA>                17         1.8      NA    
 3 Great… Blar… omni  Sori… lc                  14.9       2.3       0.133
 4 North… Call… carni Carn… vu                   8.7       1.4       0.383
 5 Dog    Canis carni Carn… domesticated        10.1       2.9       0.333
 6 Grivet Cerc… omni  Prim… lc                  10         0.7      NA    
 7 Star-… Cond… omni  Sori… lc                  10.3       2.2      NA    
 8 Afric… Cric… omni  Rode… <NA>                 8.3       2        NA    
 9 Lesse… Cryp… omni  Sori… lc                   9.1       1.4       0.15
10 Long-… Dasy… carni Cing… lc                  17.4       3.1       0.383
# ... with 29 more rows, and 3 more variables: awake <dbl>, brainwt <dbl>,
#   bodywt <dbl>

Avec l’opérateur %in%, on peut même préparer notre liste de valeurs à l’avance

a_garder <- c("omni", "carni", "herbi")
filter(msleep, vore %in% a_garder)
# A tibble: 71 x 11
   name   genus vore  order conservation sleep_total sleep_rem sleep_cycle
   <chr>  <chr> <chr> <chr> <chr>              <dbl>     <dbl>       <dbl>
 1 Cheet… Acin… carni Carn… lc                  12.1      NA        NA    
 2 Owl m… Aotus omni  Prim… <NA>                17         1.8      NA    
 3 Mount… Aplo… herbi Rode… nt                  14.4       2.4      NA    
 4 Great… Blar… omni  Sori… lc                  14.9       2.3       0.133
 5 Cow    Bos   herbi Arti… domesticated         4         0.7       0.667
 6 Three… Brad… herbi Pilo… <NA>                14.4       2.2       0.767
 7 North… Call… carni Carn… vu                   8.7       1.4       0.383
 8 Dog    Canis carni Carn… domesticated        10.1       2.9       0.333
 9 Roe d… Capr… herbi Arti… lc                   3        NA        NA    
10 Goat   Capri herbi Arti… lc                   5.3       0.6      NA    
# ... with 61 more rows, and 3 more variables: awake <dbl>, brainwt <dbl>,
#   bodywt <dbl>

On peut aussi inverser des conditions

Avec le point d’exclamation, p. ex. pour avoir tout sauf les omnivores :

filter(msleep, !(vore == "omni"))
# A tibble: 56 x 11
   name   genus vore  order conservation sleep_total sleep_rem sleep_cycle
   <chr>  <chr> <chr> <chr> <chr>              <dbl>     <dbl>       <dbl>
 1 Cheet… Acin… carni Carn… lc                  12.1      NA        NA    
 2 Mount… Aplo… herbi Rode… nt                  14.4       2.4      NA    
 3 Cow    Bos   herbi Arti… domesticated         4         0.7       0.667
 4 Three… Brad… herbi Pilo… <NA>                14.4       2.2       0.767
 5 North… Call… carni Carn… vu                   8.7       1.4       0.383
 6 Dog    Canis carni Carn… domesticated        10.1       2.9       0.333
 7 Roe d… Capr… herbi Arti… lc                   3        NA        NA    
 8 Goat   Capri herbi Arti… lc                   5.3       0.6      NA    
 9 Guine… Cavis herbi Rode… domesticated         9.4       0.8       0.217
10 Chinc… Chin… herbi Rode… domesticated        12.5       1.5       0.117
# ... with 46 more rows, and 3 more variables: awake <dbl>, brainwt <dbl>,
#   bodywt <dbl>

Maintenant que toutes ces particularités sont derrière nous, on peut passer à la seconde opération sur les bases de données, le tri.

Trier

Par défaut, le tri se fait en ordre croissant

arrange(msleep, bodywt)
# A tibble: 83 x 11
   name   genus vore  order conservation sleep_total sleep_rem sleep_cycle
   <chr>  <chr> <chr> <chr> <chr>              <dbl>     <dbl>       <dbl>
 1 Lesse… Cryp… omni  Sori… lc                   9.1       1.4       0.15
 2 Littl… Myot… inse… Chir… <NA>                19.9       2         0.2  
 3 Great… Blar… omni  Sori… lc                  14.9       2.3       0.133
 4 Deer … Pero… <NA>  Rode… <NA>                11.5      NA        NA    
 5 House… Mus   herbi Rode… nt                  12.5       1.4       0.183
 6 Big b… Epte… inse… Chir… lc                  19.7       3.9       0.117
 7 North… Onyc… carni Rode… lc                  14.5      NA        NA    
 8 "Vole… Micr… herbi Rode… <NA>                12.8      NA        NA    
 9 Afric… Rhab… omni  Rode… <NA>                 8.7      NA        NA    
10 Vespe… Calo… <NA>  Rode… <NA>                 7        NA        NA    
# ... with 73 more rows, and 3 more variables: awake <dbl>, brainwt <dbl>,
#   bodywt <dbl>

Il faut utiliser une modificateur pour inverser l’ordre

arrange(msleep, desc(bodywt))
# A tibble: 83 x 11
   name   genus vore  order conservation sleep_total sleep_rem sleep_cycle
   <chr>  <chr> <chr> <chr> <chr>              <dbl>     <dbl>       <dbl>
 1 Afric… Loxo… herbi Prob… vu                   3.3      NA        NA    
 2 Asian… Elep… herbi Prob… en                   3.9      NA        NA    
 3 Giraf… Gira… herbi Arti… cd                   1.9       0.4      NA    
 4 Pilot… Glob… carni Ceta… cd                   2.7       0.1      NA    
 5 Cow    Bos   herbi Arti… domesticated         4         0.7       0.667
 6 Horse  Equus herbi Peri… domesticated         2.9       0.6       1    
 7 Brazi… Tapi… herbi Peri… vu                   4.4       1         0.9  
 8 Donkey Equus herbi Peri… domesticated         3.1       0.4      NA    
 9 Bottl… Turs… carni Ceta… <NA>                 5.2      NA        NA    
10 Tiger  Pant… carni Carn… en                  15.8      NA        NA    
# ... with 73 more rows, and 3 more variables: awake <dbl>, brainwt <dbl>,
#   bodywt <dbl>

Premier exercice

Trouver en ordre croissant de poids du corps, la liste de tous les herbivores non domestiqués

Sélectionner certaines colonnes

select(msleep, vore, brainwt, bodywt)
# A tibble: 83 x 3
   vore   brainwt  bodywt
   <chr>    <dbl>   <dbl>
 1 carni NA        50    
 2 omni   0.0155    0.48
 3 herbi NA         1.35
 4 omni   0.00029   0.019
 5 herbi  0.423   600    
 6 herbi NA         3.85
 7 carni NA        20.5  
 8 <NA>  NA         0.045
 9 carni  0.07     14    
10 herbi  0.0982   14.8  
# ... with 73 more rows

Une série de colonnes en séquence

select(msleep, sleep_total:awake)
# A tibble: 83 x 4
   sleep_total sleep_rem sleep_cycle awake
         <dbl>     <dbl>       <dbl> <dbl>
 1        12.1      NA        NA      11.9
 2        17         1.8      NA       7  
 3        14.4       2.4      NA       9.6
 4        14.9       2.3       0.133   9.1
 5         4         0.7       0.667  20  
 6        14.4       2.2       0.767   9.6
 7         8.7       1.4       0.383  15.3
 8         7        NA        NA      17  
 9        10.1       2.9       0.333  13.9
10         3        NA        NA      21  
# ... with 73 more rows

ou tout sauf les colonnes de la séquence

select(msleep, -c(sleep_total:awake))
# A tibble: 83 x 7
   name              genus    vore  order    conservation  brainwt  bodywt
   <chr>             <chr>    <chr> <chr>    <chr>           <dbl>   <dbl>
 1 Cheetah           Acinonyx carni Carnivo… lc           NA        50    
 2 Owl monkey        Aotus    omni  Primates <NA>          0.0155    0.48
 3 Mountain beaver   Aplodon… herbi Rodentia nt           NA         1.35
 4 Greater short-ta… Blarina  omni  Soricom… lc            0.00029   0.019
 5 Cow               Bos      herbi Artioda… domesticated  0.423   600    
 6 Three-toed sloth  Bradypus herbi Pilosa   <NA>         NA         3.85
 7 Northern fur seal Callorh… carni Carnivo… vu           NA        20.5  
 8 Vesper mouse      Calomys  <NA>  Rodentia <NA>         NA         0.045
 9 Dog               Canis    carni Carnivo… domesticated  0.07     14    
10 Roe deer          Capreol… herbi Artioda… lc            0.0982   14.8  
# ... with 73 more rows

On peut aussi renommer certaines colonnes

rename(msleep, nom = name, genre = genus)
# A tibble: 83 x 11
   nom    genre vore  order conservation sleep_total sleep_rem sleep_cycle
   <chr>  <chr> <chr> <chr> <chr>              <dbl>     <dbl>       <dbl>
 1 Cheet… Acin… carni Carn… lc                  12.1      NA        NA    
 2 Owl m… Aotus omni  Prim… <NA>                17         1.8      NA    
 3 Mount… Aplo… herbi Rode… nt                  14.4       2.4      NA    
 4 Great… Blar… omni  Sori… lc                  14.9       2.3       0.133
 5 Cow    Bos   herbi Arti… domesticated         4         0.7       0.667
 6 Three… Brad… herbi Pilo… <NA>                14.4       2.2       0.767
 7 North… Call… carni Carn… vu                   8.7       1.4       0.383
 8 Vespe… Calo… <NA>  Rode… <NA>                 7        NA        NA    
 9 Dog    Canis carni Carn… domesticated        10.1       2.9       0.333
10 Roe d… Capr… herbi Arti… lc                   3        NA        NA    
# ... with 73 more rows, and 3 more variables: awake <dbl>, brainwt <dbl>,
#   bodywt <dbl>

Ajouter des colonnes

Puisque les colonnes sont ajoutées à la fin du jeu de données, nous allons créer un jeu de données simplifié pour bien voir ce que l’on fait

poids <- select(msleep,ends_with("wt"))
poids
# A tibble: 83 x 2
    brainwt  bodywt
      <dbl>   <dbl>
 1 NA        50    
 2  0.0155    0.48
 3 NA         1.35
 4  0.00029   0.019
 5  0.423   600    
 6 NA         3.85
 7 NA        20.5  
 8 NA         0.045
 9  0.07     14    
10  0.0982   14.8  
# ... with 73 more rows

Pour ajouter une colonne du poids du cerveau en grammes

mutate(poids, cerveau_g = brainwt*1000)
# A tibble: 83 x 3
    brainwt  bodywt cerveau_g
      <dbl>   <dbl>     <dbl>
 1 NA        50        NA    
 2  0.0155    0.48     15.5  
 3 NA         1.35     NA    
 4  0.00029   0.019     0.290
 5  0.423   600       423    
 6 NA         3.85     NA    
 7 NA        20.5      NA    
 8 NA         0.045    NA    
 9  0.07     14        70    
10  0.0982   14.8      98.2  
# ... with 73 more rows

On peut aussi utiliser plusieurs colonnes à la fois dans un calcul, p. ex. pour calculer la taille relative du cerveau :

mutate(poids, rel_brain = brainwt / bodywt)
# A tibble: 83 x 3
    brainwt  bodywt rel_brain
      <dbl>   <dbl>     <dbl>
 1 NA        50     NA       
 2  0.0155    0.48   0.0323  
 3 NA         1.35  NA       
 4  0.00029   0.019  0.0153  
 5  0.423   600      0.000705
 6 NA         3.85  NA       
 7 NA        20.5   NA       
 8 NA         0.045 NA       
 9  0.07     14      0.005   
10  0.0982   14.8    0.00664
# ... with 73 more rows

Combiner plusieurs opérations dans une chaîne

C’est ici que l’approche dplyr se démarque que l’approche classique!

Déjà, on peut faire beaucoup de choses avec les opérations vues précédemment, p. ex. obtenir le nom et le poids des 10 plus petits mammifères:

x <- arrange(msleep,bodywt) # trier du plus petit au plus grand
y <- mutate(x,rang = row_number())# ajouter une colonne de rang
z <- filter(y, rang <= 10)# garder les 10 plus petits
select(z,name,bodywt)# ne garder que les noms et les poids
# A tibble: 10 x 2
   name                       bodywt
   <chr>                       <dbl>
 1 Lesser short-tailed shrew   0.005
 2 Little brown bat            0.01
 3 Greater short-tailed shrew  0.019
 4 Deer mouse                  0.021
 5 House mouse                 0.022
 6 Big brown bat               0.023
 7 Northern grasshopper mouse  0.028
 8 "Vole "                     0.035
 9 African striped mouse       0.044
10 Vesper mouse                0.045

Ça nous fait beaucoup d’objets intermédiaires inutiles. On pourrait bêtement les éliminer

select(filter(mutate(arrange(msleep,bodywt),rang = row_number()), rang <= 10),name,bodywt)
# A tibble: 10 x 2
   name                       bodywt
   <chr>                       <dbl>
 1 Lesser short-tailed shrew   0.005
 2 Little brown bat            0.01
 3 Greater short-tailed shrew  0.019
 4 Deer mouse                  0.021
 5 House mouse                 0.022
 6 Big brown bat               0.023
 7 Northern grasshopper mouse  0.028
 8 "Vole "                     0.035
 9 African striped mouse       0.044
10 Vesper mouse                0.045

Mais ce faisant, on perd grandement en lisibilité. On pourrait indenter le code pour y voir plus clair :

select(
  filter(
    mutate(
      arrange(msleep,bodywt),
      rang = row_number()
    ),
    rang <= 10
  ),
  name,
  bodywt
)
# A tibble: 10 x 2
   name                       bodywt
   <chr>                       <dbl>
 1 Lesser short-tailed shrew   0.005
 2 Little brown bat            0.01
 3 Greater short-tailed shrew  0.019
 4 Deer mouse                  0.021
 5 House mouse                 0.022
 6 Big brown bat               0.023
 7 Northern grasshopper mouse  0.028
 8 "Vole "                     0.035
 9 African striped mouse       0.044
10 Vesper mouse                0.045

Mais on reste avec le problème que ce n’est pas simple au premier coup d’oeil de voir sur quel tableau de données on travaille. De plus, il faut lire du centre vers l’extérieur, ce qui est peu intuitif.

La solution, l’opérateur d’enchaînement (Pipe Operator), de la librairie magrittr : %>%

msleep %>%
  arrange(bodywt) %>%
  mutate(rang = row_number()) %>%
  filter(rang <= 10) %>%
  select(name, bodywt)
# A tibble: 10 x 2
   name                       bodywt
   <chr>                       <dbl>
 1 Lesser short-tailed shrew   0.005
 2 Little brown bat            0.01
 3 Greater short-tailed shrew  0.019
 4 Deer mouse                  0.021
 5 House mouse                 0.022
 6 Big brown bat               0.023
 7 Northern grasshopper mouse  0.028
 8 "Vole "                     0.035
 9 African striped mouse       0.044
10 Vesper mouse                0.045

%>% transforme notre version lisible en une version que l’ordinateur est prêt à exécuter…

Raccourci clavier : Ctrl+Shift+M

Toutes les librairies faisant partie du tidyverse de Hadley Wickham supportent l’opérateur d’enchaînement, sauf ggplot2. Ce dernier peut néanmoins être ajouté au bout d’une chaîne :

msleep %>%
  filter(bodywt > 0.200) %>%
  mutate(
    l_corps = log(bodywt),
    l_cerveau = log(brainwt)
  ) %>%
  ggplot(aes(x = l_corps,y = l_cerveau, col = vore)) +
  geom_point()
Warning: Removed 19 rows containing missing values (geom_point).

Il faut être super attentif, parce que toutes les autres fonctions s’enchaînent avec %>%, sauf les couches de ggplot qui s’attachent avec des +

Deuxième exercice

On refait le premier exercice, mais cette fois-ci, en prenant avantage de l’opérateur d’enchaînement :

Trouver en ordre croissant de poids du corps, la liste de tous les herbivores non domestiqués

Résumer les données

Dernière opération de base que l’on peut faire avec un jeu de données propres : le résumer.

On peut résumer plusieurs variables ou fonctions à la fois :

msleep %>%
  summarize(
    poids_moyen = mean(bodywt),
    ecart_type_poids = sd(bodywt)
  )
# A tibble: 1 x 2
  poids_moyen ecart_type_poids
        <dbl>            <dbl>
1        166.             787.

Cette opération devient beaucoup plus puissante si on utilise les regroupements :

msleep %>%
  group_by(vore) %>%
  summarize(
    poids_moyen = mean(bodywt),
    ecart_type_poids = sd(bodywt)
  )
# A tibble: 5 x 3
  vore    poids_moyen ecart_type_poids
  <chr>         <dbl>            <dbl>
1 carni        90.8             182.  
2 herbi       367.             1244.  
3 insecti      12.9              26.4
4 omni         12.7              24.7
5 <NA>          0.858             1.34

Nettoyage des données

Format long vs. format large

Parfois, le format dans lequel nous entrons nos données est très pratique lors de la saisie, mais ne correspond pas à la définition des données propres (une ligne par observations, une colonne par variable).

Souvent, on se retrouve avec des données qui ressemblent à ceci :

oiseaux <- tibble(
  Especes = c("Corneille", "Mésange"),
  "2001" = c(0,1),
  "2002" = c(2,1),
  "2003" = c(2,2)
)
oiseaux
# A tibble: 2 x 4
  Especes   `2001` `2002` `2003`
  <chr>      <dbl>  <dbl>  <dbl>
1 Corneille      0      2      2
2 Mésange        1      1      2

Comment tracerait-on le graphique de l’abondance des corneilles au fil des années?

La solution est de passer les données au format long plutôt que large :

library(tidyr)
o2 <-
  oiseaux %>%
    gather(
      key = Annee,
      value = Abondance,
      `2001`:`2003`,
      convert = TRUE
    )
o2
# A tibble: 6 x 3
  Especes   Annee Abondance
  <chr>     <int>     <dbl>
1 Corneille  2001         0
2 Mésange    2001         1
3 Corneille  2002         2
4 Mésange    2002         1
5 Corneille  2003         2
6 Mésange    2003         2

L’argument key correspond au nom de la colonne qui contiendra les anciens noms de colonnes.

L’argument valuecorrespond au nom de la colonne qui contiendra les valeurs dans l’ancien tableau.

Autrement dit, les deux questions à vous poser dans le passage du format large au format long sont :

Ensuite, on peut finalement tracer le graphique…

o2 %>%
  filter(Especes == "Corneille") %>%
  ggplot(aes(x = Annee, y = Abondance)) +
  geom_line()

Une autre application du format long : répéter une opération sur plusieur variables.

P. ex. pour obtenir l’histogramme de toutes les variables de sommeil dans le jeu de données msleep :

msleep_long <- msleep %>%
  gather(
    key = Variable,
    value = Valeur,
    sleep_total:awake
  )
msleep_long
# A tibble: 332 x 9
   name    genus vore  order conservation  brainwt  bodywt Variable Valeur
   <chr>   <chr> <chr> <chr> <chr>           <dbl>   <dbl> <chr>     <dbl>
 1 Cheetah Acin… carni Carn… lc           NA        50     sleep_t…   12.1
 2 Owl mo… Aotus omni  Prim… <NA>          0.0155    0.48  sleep_t…   17  
 3 Mounta… Aplo… herbi Rode… nt           NA         1.35  sleep_t…   14.4
 4 Greate… Blar… omni  Sori… lc            0.00029   0.019 sleep_t…   14.9
 5 Cow     Bos   herbi Arti… domesticated  0.423   600     sleep_t…    4  
 6 Three-… Brad… herbi Pilo… <NA>         NA         3.85  sleep_t…   14.4
 7 Northe… Call… carni Carn… vu           NA        20.5   sleep_t…    8.7
 8 Vesper… Calo… <NA>  Rode… <NA>         NA         0.045 sleep_t…    7  
 9 Dog     Canis carni Carn… domesticated  0.07     14     sleep_t…   10.1
10 Roe de… Capr… herbi Arti… lc            0.0982   14.8   sleep_t…    3  
# ... with 322 more rows
msleep_long %>%
  ggplot(aes(x = Valeur)) +
  geom_histogram() +
  facet_wrap(~Variable)
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
Warning: Removed 73 rows containing non-finite values (stat_bin).

Si à l’inverse, une même observation a été divisée sur plusieurs lignes, on peut la rassembler :

mesures <- data.frame(
  individu = c("A","A","B","B","C","C"),
  mesure = c("pH","O2", "pH","O2","pH","O2"),
  valeur = c(6,99,7,90, 6.5, 89)
)
mesures
  individu mesure valeur
1        A     pH    6.0
2        A     O2   99.0
3        B     pH    7.0
4        B     O2   90.0
5        C     pH    6.5
6        C     O2   89.0
mesures %>%
  spread(
    key = mesure,
    value = valeur
  )
  individu O2  pH
1        A 99 6.0
2        B 90 7.0
3        C 89 6.5

Et on est de retour à une variable par colonne et une obsevation par ligne

Pour la fonction spread, l’argument key correspond à la colonne contenant les noms des colonnes à créer, et l’argument value correspond à la colonne contenant les valeurs

VOIR SLIDES

Connecter deux bases de données ensemble

Pour l’exercice, nous allons faire comme si la base de données msleep nous avait été fournie en deux morceaux séparées. Un premier avec toute la méta-information, et l’autre avec les données de sommeil comme tel. Les deux tableaux ne sont pas dans le même ordre, et dans un, les carnivores étaient absents.

meta <- msleep %>%
  select(name:conservation) %>%
  arrange(name)

sommeil <- msleep %>%
  filter(vore != "carni") %>%
  select(name, sleep_total:awake)

meta
# A tibble: 83 x 5
   name                      genus        vore    order       conservation
   <chr>                     <chr>        <chr>   <chr>       <chr>       
 1 African elephant          Loxodonta    herbi   Proboscidea vu          
 2 African giant pouched rat Cricetomys   omni    Rodentia    <NA>        
 3 African striped mouse     Rhabdomys    omni    Rodentia    <NA>        
 4 Arctic fox                Vulpes       carni   Carnivora   <NA>        
 5 Arctic ground squirrel    Spermophilus herbi   Rodentia    lc          
 6 Asian elephant            Elephas      herbi   Proboscidea en          
 7 Baboon                    Papio        omni    Primates    <NA>        
 8 Big brown bat             Eptesicus    insecti Chiroptera  lc          
 9 Bottle-nosed dolphin      Tursiops     carni   Cetacea     <NA>        
10 Brazilian tapir           Tapirus      herbi   Perissodac… vu          
# ... with 73 more rows
sommeil
# A tibble: 57 x 5
   name                       sleep_total sleep_rem sleep_cycle awake
   <chr>                            <dbl>     <dbl>       <dbl> <dbl>
 1 Owl monkey                        17         1.8      NA       7  
 2 Mountain beaver                   14.4       2.4      NA       9.6
 3 Greater short-tailed shrew        14.9       2.3       0.133   9.1
 4 Cow                                4         0.7       0.667  20  
 5 Three-toed sloth                  14.4       2.2       0.767   9.6
 6 Roe deer                           3        NA        NA      21  
 7 Goat                               5.3       0.6      NA      18.7
 8 Guinea pig                         9.4       0.8       0.217  14.6
 9 Grivet                            10         0.7      NA      14  
10 Chinchilla                        12.5       1.5       0.117  11.5
# ... with 47 more rows

Comment faire un diagramme à moustache du temps de sommeil par statut de conservation (en faisant comme si msleep n’existait pas! )?

On ne peut pas simplement coller nos deux tableaux de données (bind_cols) parcequ’ils n’ont pas nécéssairement les mêmes lignes, le même ordre, etc. Il faut plutôt utiliser les fonction de la famille *_join du package tidyr.

VOIR SLIDES

Dans le cas qui nous intéresse, quelle est la clé unique connectant nos deux tableaux?

Dans notre cas, nous avons plusieurs possiblités pour connecter les deux tableaux, p. ex.

sommeil %>%
  left_join(meta)
Joining, by = "name"
# A tibble: 57 x 9
   name       sleep_total sleep_rem sleep_cycle awake genus   vore  order
   <chr>            <dbl>     <dbl>       <dbl> <dbl> <chr>   <chr> <chr>
 1 Owl monkey        17         1.8      NA       7   Aotus   omni  Prima…
 2 Mountain …        14.4       2.4      NA       9.6 Aplodo… herbi Roden…
 3 Greater s…        14.9       2.3       0.133   9.1 Blarina omni  Soric…
 4 Cow                4         0.7       0.667  20   Bos     herbi Artio…
 5 Three-toe…        14.4       2.2       0.767   9.6 Bradyp… herbi Pilosa
 6 Roe deer           3        NA        NA      21   Capreo… herbi Artio…
 7 Goat               5.3       0.6      NA      18.7 Capri   herbi Artio…
 8 Guinea pig         9.4       0.8       0.217  14.6 Cavis   herbi Roden…
 9 Grivet            10         0.7      NA      14   Cercop… omni  Prima…
10 Chinchilla        12.5       1.5       0.117  11.5 Chinch… herbi Roden…
# ... with 47 more rows, and 1 more variable: conservation <chr>

Autrement dit, d’ajouter les meta-informations à notre base de données sommeil.

Mais on aurait pu aussi faire l’inverse :

meta %>%
  left_join(sommeil)
Joining, by = "name"
# A tibble: 83 x 9
   name   genus vore  order conservation sleep_total sleep_rem sleep_cycle
   <chr>  <chr> <chr> <chr> <chr>              <dbl>     <dbl>       <dbl>
 1 Afric… Loxo… herbi Prob… vu                   3.3      NA        NA    
 2 Afric… Cric… omni  Rode… <NA>                 8.3       2        NA    
 3 Afric… Rhab… omni  Rode… <NA>                 8.7      NA        NA    
 4 Arcti… Vulp… carni Carn… <NA>                NA        NA        NA    
 5 Arcti… Sper… herbi Rode… lc                  16.6      NA        NA    
 6 Asian… Elep… herbi Prob… en                   3.9      NA        NA    
 7 Baboon Papio omni  Prim… <NA>                 9.4       1         0.667
 8 Big b… Epte… inse… Chir… lc                  19.7       3.9       0.117
 9 Bottl… Turs… carni Ceta… <NA>                NA        NA        NA    
10 Brazi… Tapi… herbi Peri… vu                   4.4       1         0.9  
# ... with 73 more rows, and 1 more variable: awake <dbl>

Dans ce cas, notre base de données est plus complète, mais contient des valeurs manquantes pour le sommeil de certains animaux

Importation de données

Au Québec, l’importation de données dans R à partir de fichiers CSV est particulièrement complexe :

VOIR SLIDES

La méthode à (presque) toute épreuve que je vous propose : charger vos données directement à partir de votre fichier Excel!

La raison : le représentation interne des données dans le fichier Excel est super constante et ne varie pas entre les langues ou les pays.

Les règles à suivre pour que les données s’importent bien à partir de votre fichier Excel :

library(readxl)
x <- read_excel("Exemple.xlsx")
x
# A tibble: 4 x 5
  Colonne1 Colonne2 Colonne3 Colonne4 Colonne5           
  <chr>    <chr>       <dbl>    <dbl> <dttm>             
1 A        4,3          4.12     4.12 2018-10-01 00:00:00
2 A        3,2          4.12     4.12 2018-01-10 00:00:00
3 B        1,1          4.12     4.12 2018-10-01 00:00:00
4 B        2,1          4.12     4.12 2018-11-01 00:00:00

On peut aussi passer des lignes ou aller lire une feuille spécifique plutôt que la première :

obs <- read_excel("Exemple.xlsx",sheet = 2, skip = 2)

Dernier exercice

Comment convertir le tableau obs dans un format approprié aux analyses de communautés, avec une ligne par site, et une colonne par espèce?

e.g. pour arriver à ceci :

# A tibble: 3 x 3
   Site Corneille `Geai bleu`
  <dbl>     <dbl>       <dbl>
1     1         1           2
2     2         2           5
3     3         1           0

Ces exercices ont été préparées avec…

sessionInfo()
R version 3.5.1 (2018-07-02)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS High Sierra 10.13.6

Matrix products: default
BLAS: /Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] readxl_1.1.0   tidyr_0.8.1    bindrcpp_0.2.2 dplyr_0.7.7   
[5] ggplot2_3.0.0

loaded via a namespace (and not attached):
 [1] Rcpp_0.12.17     pillar_1.2.3     compiler_3.5.1   cellranger_1.1.0
 [5] plyr_1.8.4       bindr_0.1.1      tools_3.5.1      digest_0.6.15   
 [9] evaluate_0.10.1  tibble_1.4.2     gtable_0.2.0     pkgconfig_2.0.1
[13] rlang_0.2.1      cli_1.0.0        yaml_2.1.19      withr_2.1.2     
[17] stringr_1.3.1    knitr_1.20       rprojroot_1.3-2  grid_3.5.1      
[21] tidyselect_0.2.4 glue_1.2.0       R6_2.2.2         rmarkdown_1.10  
[25] purrr_0.2.5      magrittr_1.5     backports_1.1.2  scales_0.5.0    
[29] htmltools_0.3.6  assertthat_0.2.0 colorspace_1.3-2 labeling_0.3    
[33] utf8_1.1.4       stringi_1.2.3    lazyeval_0.2.1   munsell_0.5.0   
[37] crayon_1.3.4