Ateliers R du CSBQ

Cette série de 10 ateliers guide les participants à travers les étapes requises afin de maîtriser le logiciel R pour une grande variété d’analyses statistiques pertinentes en recherche en biologie et en écologie. Ces ateliers en libre accès ont été créés par des membres du CSBQ à la fois pour les membres du CSBQ et pour la grande communauté d’utilisateurs de R.

Le contenu de cet atelier a été révisé par plusieurs membres du CSBQ. Si vous souhaitez y apporter des modifications, veuillez SVP contacter les coordonnateurs actuels de la série, listés sur la page d'accueil

Atelier 2 : Charger et manipuler des données

Développé par : Johanna Bradie, Vincent Fugère, Thomas Lamy

Résumé : Durant cet atelier, vous apprendrez comment charger et visualiser des données dans R. Vous apprendrez certaines fonctions permettant d’examiner vos données et vous apprendrez comment corriger certaines erreurs communes se produisant lors du chargement des données. Finalement, vous verrez l’importance de sauvegarder votre travail dans un script R, soit un fichier texte contenant tous vos codes et permettant de refaire toutes vos analyses en appuyant sur une seule touche (ou deux, ou trois peut-être…). Ensuite, nous présenterons tidyr et dplyr pour vous aider à transformer et à gérer le format de vos données ainsi que pour appliquer des fonctions simples ou complexes sur des sous-groupes de vos jeux de données. Cet atelier s’avérera utile non seulement pour les participants suivant notre série d’ateliers, mais également pour ceux et celles d’entre vous connaissant déjà le langage R et cherchant à maîtriser des outils plus avancés pour l'analyse et la visualisation de vos données.

Lien vers la nouvelle présentation Rmarkdown

S'il vous plaît essayez-la et dites aux coordonnateurs des ateliers R ce que vous en pensez!

Lien vers l'ancienne présentation Prezi

Téléchargez les scripts et les données requis pour cet atelier :

  1. Créer un projet RStudio
  2. Écrire un script dans R
  3. Charger, explorer et enregister des données
  4. Manipuler des jeux de données avec tidyr, dplyr, maggritr

Qu-est-ce qu'un projet RStudio?

  • Les projets RStudio permettent l'organisation de son travail et l'accès facile à tous les fichiers requis pour une analyse.
  • Tout fichier, script, et documentation utilisé pour une analyse sont reliés et regroupés ensemble dans un même projet.

L'utilisation de projets RStudio facilite aussi la reproducibilité et le partage de données, de scripts, et de leur documentation.

Pour créer un projet dans RStudio, allez dans le menu Fichier puis sélectionnez “Nouveau projet”.

Un projet = un dossier!

  • Vous devriez enregistrez les fichiers à importer dans R en tant que fichiers “comma separated values” (.csv)
  • Les fichiers .csv peuvent être créés par presque toutes les applications (Excel, GoogleDocs, LibreOffice, etc.)
  • Fichier → Enregistrer sous: .csv

Bien nommer les fichiers

  • Oui:
    • rawDatasetAgo2017.csv
    • co2_concentrations_QB.csv
    • 01_figIntro.R
  • Non:
    • final.csv (Pas informatif!)
    • safnnejs.csv (C'est n'importe quoi!)
    • 1-4.csv (Éviter d'utiliser des chiffres!)
    • Dont.separate.names.with.dots.csv (Peut causer des erreurs de lecture de fichier!)

Bien nommer les variables

  • Utilisez des noms de variables courts et informatifs (i.e. “Temps_1” au lieu de “Temps de la premiere mesure”)
    • Oui: “Mesure”, “NomEspece”, “Site”
  • Les valeurs des colonnes doivent correspondre à l'usage prévu

Conseils pour la préparation de jeux de données

  • Pas de texte dans les colonnes de mode numérique (ceci inclut les espaces !)
  • Identifiez les valeurs manquantes par NA (“not available”) - notez le majuscule!
  • Faites attention aux erreurs typographiques!
  • Évitez les valeurs numériques pour les variables n'ayant pas vraiment de valeur numérique (i.e. individu, réplicat, traitement)
    • Par exemple, identifiez vos individus par “A,B,C” ou “ind1,ind2,ind3” au lieu de “1,2,3”
  • Utilisez un format uniforme pour les dates, les chiffres, etc.
  • N'ajoutez pas de notes, d'en-têtes supplémentaires, ou de cellules fusionnées!
  • Seulement une variable par colonne!

Exemples de mauvaises habitudes

Il est possible de faire toute la préparation des données dans R. Les avantages sont :

  • On économise du temps pour les grosses bases de données
  • On préserve les données d'origine
  • On peut basculer entre les modes “long” et “large” très facilement (plus de détails plus tard)
  • Pour des informations complémentaires, consultez la page suivante : https://www.zoology.ubc.ca/~schluter/R/data/

Un script R est un fichier texte contenant toutes les commandes nécessaires pour réaliser un projet. Une fois écrit et enregistré, votre script R vous permettra d'apporter des changements et de refaire des analyses avec un minimum d'effort.

Afin d'utiliser un script dans RStudio, il suffi simplement de surligner une commande et appuyer sur “Run” ou appuyer sur “command-enter” (Mac) ou “ctrl-enter” (PC).

Commandes & Commentaires

Utilisez le symbole '#' pour insérer des commentaires au sein d'un script. Ceci indique à R d'ignorer tout ce qui se trouve à la suite du symbole '#' lors de l'exécution de commandes.

Puisque les commentaires sont ignorés par R lors de l'exécution de scripts, il est possible de se laisser des notes ou d'indiquer à des collaborateurs ce qui a été fait. Un script enrichi de commentaires est une bonne façon de collaborer à une science reproductible et permet de comprendre comment R fonctionne.

# Ceci est un commentaire, pas une commande !

En-têtes

Il est recommandé d'ajouter des commentaires au début de chaque script sous forme d'en-tête en indiquant quelques informations essentielles : nom du projet, auteur, date et version de R.

Exemple d'en-tête
## Atelier R du CSBQ ##
## Atelier 2 - Charger et manipuler des données
## Auteur: Centre de la science de la biodiversité du Québec
## Date: Automne 2014
## R version 2.15.0

En-têtes de section

Vous pouvez utiliser quatre symboles '#' de suite pour créer un en-tête de section. C'est utile pour organiser un script.

Par exemple :

#### Chargement du fichier de données ####

RStudio affiche une petite flèche juste à côté du numéro de ligne où l'en-tête de section a été créé. Si vous cliquez sur la flèche, cette partie du script sera cachée.

Vous pouvez aussi naviguer entre sections en utilisant le menu trouvé en bas de la fenêtre du script.

Nettoyage

C'est une bonne habitude de maintenir son espace de travail propre en effaçant la mémoire de R ! À l'aide d'une simple commande, il est possible d'éviter plusieurs erreurs telles qu'utiliser un vieux jeu de données qui serait resté dans l'espace de travail. La commande rm(list=ls()) efface la mémoire.

| Nettoyage
rm(list=ls())  # Efface ce qui se trouve dans l'espace de travail
?rm
?ls

On peut tester cette commande en ajoutant des objets dans l'espace de travail pour mieux comprendre comment rm(list=ls()) fonctionne.

|
A<-"Test"     # On crée un objet "A".
A <- "Test"   # C'est préférable de mettre des espaces avant et après "<-".
A = "Test"    # = peut aussi être utilisé comme opérateur d'assignation
 
#Tel que vu au premier atelier, il est préférable d'utiliser "<-" comme opérateur d'assignation plutôt que "=".
 
A
rm(list=ls())
A

Petit rappel important

  1. R est prêt à exécuter une commande lorsque vous voyez le chevron > affiché dans la console. Si le chevron n'apparaît pas, c'est que la commande est incomplète : R attend que vous complétiez la commande. Appuyez sur 'ESC' pour sortir de cette commande.
  2. R est sensible à la casse, i.e. “A” est différent de “a”.
|
a<-10  
A<-5
a
A
 
rm(list=ls())  # On nettoie l'espace de travail à nouveau !

Répertoire de travail

Vous devez indiquer à R le répertoire où se trouvent les fichiers de données afin de les charger. Vous pouvez voir quel répertoire R utilise avec la fonction getwd()

|
getwd() # Cette commande vous indique le répertoire dans lequel vous vous trouvez.

Lorsque vous chargez un script, R définit le répertoire de travail comme étant le dossier qui contient le script.

Afin de spécifier le chemin d'accès du répertoire avec la fonction setwd(), utilisez le ”/“ pour séparer les dossiers, sous-dossiers et noms de fichiers. Vous pouvez aussi cliquer sur session / Définir le répertoire / Choisir le répertoire.

Afficher le contenu du répertoire de travail

La fonction dir() affiche le contenu du répertoire de travail.

|
dir() # Cette commande vous indique le contenu du répertoire dans lequel vous vous trouvez.

Vous pouvez vérifier:

  • Si le fichier que vous voulez ouvrir se trouve dans le répertoire de travail ou non
  • L'orthographe du nom du fichier (e.g. 'monfichier.csv' au lieu de 'MonFichier.csv')

Importer un jeu de données

Utilisez la fonction read.csv() pour importer des données provenant d'un fichier .csv dans R.

|
CO2<-read.csv("CO2_good.csv")
# Cette commande crée un objet nommé CO2 en chargeant les données
# du fichier nommé "CO2_good.csv".

Cette commande spécifie que vous créez un objet nommé “CO2” en lisant un fichier .csv nommé “CO2_good.csv”. Ce fichier doit se trouver dans votre répertoire de travail.

Il est aussi possible d'utiliser la fonction file.choose() pour charger un fichier de manière interactive.

|
CO2<-read.csv(file.choose()) 

Rappelez-vous que le point d'interrogation peut être utilisé pour trouver de l'aide sur une fonction.

|
?read.csv # Ceci fera apparaître la page d'aide pour la fonction read.csv().  

La page d'aide permet de voir qu'il y a un argument header qui permet de spécifier si la première ligne du fichier contient le nom des colonnes. Inscrire header=TRUE indique à R que la première ligne du tableau contient les noms des colonnes.

|
CO2<-read.csv("CO2_good.csv", header = TRUE) 

NOTE : Si vous utilisez un système d'exploitation en français ou un éditeur CSV, il est possible que vous deviez utiliser la fonction read.csv2() au lieu de la fonction read.csv() pour importer correctement un fichier.

Prenez note que RStudio montre maintenant le jeu de donné CO2 dans ton espace de travail. L'espace de travail inclut tous les objets créés pendant ta session R.

Explorer le jeu de données

Le jeu de données “CO2” contient des mesures répétées d'absorption de CO2 prises sur six plantes du Québec et six plantes du Mississippi à différentes concentrations de CO2 ambiant. La moitié des plantes de chaque région a subi un traitement de refroidissement la veille du début de l'expérience.

Voici quelques fonctions utiles pour visualiser le jeu de données importé :

CO2 Affiche le tableau de données complet dans la console
head(CO2) Affiche les premières lignes du tableau de données
names(CO2) Affiche le nom des colonnes du tableau de données
attributes(CO2) Affiche les attributs du tableau de données
ncol(CO2) Affiche le nombre de colonnes du tableau de données
nrow(CO2) Affiche le nombre de lignes du tableau de données
summary(CO2) Calcule quelques statistiques de base sur les variables
str(CO2) Affiche la structure du tableau de données

La fonction str() est très utile pour identifier le type/mode de chaque colonne (e.g. afin de vérifier que les facteurs sont enregistrés comme facteurs et que les données numériques sont de type numérique ou nombre entier). Plusieurs problèmes sont fréquents lors de l'importation des données :

  • Les facteurs apparaissent comme des chaînes de caractères (et vice versa)
  • Les facteurs ont trop de niveaux à cause d'une erreur de frappe
  • Les données numériques sont stockées sous forme de chaînes de caractères à cause d'une erreur de frappe (e.g. un espace de trop ou utiliser la virgule au lieu du point pour indiquer les décimales)

Exercice

Chargez les données de nouveau en utilisant le script suivant :

|
CO2<-read.csv("CO2_good.csv",header=FALSE)

Vérifiez la structure des données avec la fonction str(). Quel est le problème ? Chargez les données de nouveau, mais cette fois-ci avec l'argument header=TRUE avant de continuer.

Rappel de l'atelier 1 : Accéder aux données

Les données peuvent être extraites d'un jeu de données de plusieurs façons. Considerons un jeu de données nommé mydata. Les crochets permettent d'extraire le contenu d'une cellule.

|
mydata[2,3] # extrait le contenu de la deuxième ligne / troisième colonne

Si le numéro de la colonne est omis, toute la ligne est extraite.

|
mydata[1,] # extrait le contenu de la première ligne

Si le numéro de la ligne est omis, toute la colonne est extraite. De même, le symbôle $ suivi d'un nom de colonne peut être utilisé.

|
mydata[,1] # extrait le contenu de la première colonne
mydata$Variable1 # extrait le contenu de la colonne "Variable1"

Changer les noms de variables

On peut changer le nom de variables (colonnes) dans R.

|
# Créer une copie du jeu de données qu'on pourra modifier
CO2copy <- CO2
# names() donne les noms des variables présents dans le jeu de données  
names(CO2copy)
 
# Changer des noms anglais à des noms français
names(CO2copy) <- c("Plante","Categorie", "Traitement", "conc","absortion")

Créer des nouvelles variables

On peut facilement créer et produire des nouvelles variables. Par exemple, la fonction paste() permet la concaténation de chaînes de caractères et de variables.

|
# Créer un ID unique pour les échantillons avec la fonction ''paste()''
# Consultez ?paste et ?paste0
# N'oubliez pas d'utiliser "" pour les chaînes de caractères
CO2copy$uniqueID <- paste0(CO2copy$Plante,"_", CO2copy$Categorie, "_", CO2copy$Traitement)
 
# Observer les résultats 
head(CO2copy$uniqueID)

On peut aussi créer des nouvelles variables à partir de chiffres et d'opérations mathématiques!

|
# Standardizer la variable "absortion" en valeurs relatives
CO2copy$absortionRel = CO2copy$absortion/max(CO2copy$absortion) 
 
# Observer les résultats
head(CO2copy$absortionRel)

Extraire un sous-ensemble d'un jeu de données

Il y a plusieurs façons d'extraire un sous-ensemble d'un jeu de données.

|
# Continuons avec notre trame de données "CO2copy"
 
## Extraire un sous-ensemble par un nom de variable 
CO2copy[,c("Plante", "absortionRel")] # Extrait seulement les colonnes nommées "Plante" et "absortionRel". (N'oubliez pas ","!)
 
## Extraire un sous-ensemble de rangées 
CO2copy[1:50,] # Extraite les rangées de 1 à 50
 
### Extraire un sous-ensemble correspondant à un facteur 
CO2copy[CO2copy$Traitement == "nonchilled",] # Extraire les observations correspondant seulement au traitement "nonchilled". 
 
### Extraire un sous-ensemble correspondant à une condition numérique 
CO2copy[CO2copy$absortion >= 20, ] # Extraire les observations ayant une absortion plus grande ou égale à 20  
 
### Les conditions peuvent être complémentaires en utilisant "&"
CO2copy[CO2copy$Traitement == "nonchilled" & CO2copy$absortion >= 20, ]
 
# Nous avons fini de modifier la copie du jeu de données CO2copy. On peut alors l'effacer.
CO2copy <- NULL

Consultez here pour voir les opérateurs logiques pouvant être utilisés pour extraire des sous-ensembles de données dans R.

Explorer les données

Pour faire une première exploration de données, on peut rapidement calculer des statistiques de base avec la fonction summary().

|
summary(CO2) # calculer les statistiques sommaires du jeu de données

Plusieurs fonctions intégrées à R vous permettent d'obtenir de l'information supplémentaire sur vos données. Les fonctions mean(), sd(), hist(), et print() sont couramment utilisées.

|
# Calculer la moyenne et l'écart type des données dans la colonne "conc" de l'objet CO2
meanConc<-mean(CO2$conc) # Calcule la moyenne de la colonne "conc" de l'objet "CO2"
sdConc<-sd(CO2$conc) # Calcule l'écart-type de la colonne "conc" 

# print() sort une valeure dans la console R print(paste(“la concentration moyenne est:”, meanConc)) print(paste(“l'écart type de la concentration est:”, sdConc))

# Produisez un histogramme to explore the distribution of “uptake” hist(CO2$uptake)

# Augmenter le nombre de classes pour mieux observer la distribution hist(CO2$uptake, breaks = 40) </code>

La fonction apply() est utilisée pour appliquer une fonction à plusieurs colonnes en même temps. Utilisez ?apply pour en apprendre plus.

|
?apply

Pour utiliser la fonction apply(), trois arguments doivent être spécifiés. Le premier argument est le jeu de données sur lequel on veut appliquer une fonction. Le deuxième argument spécifie si la fonction est appliquée aux lignes (MARGIN = 1) ou aux colonnes (MARGIN = 2). Le dernier argument spécifie la fonction à appliquer. Par exemple :

|
apply(CO2[,4:5], MARGIN = 2, FUN = mean) # Calcule la moyenne des colonnes 4 et 5 du jeu de données CO2

Enregistrer son espace de travail

En enregistrant votre espace de travail, vous pouvez enregistrer les scripts et les objets qui sont actuellement chargés dans R. Si vous enregistrez votre espace de travail, vous pouvez chargez tous les objets même si vous avez utilisé la commande rm(list=ls()) afin de nettoyer tout ce qui se trouve dans l'espace de travail.

Utilisez la fonction save.image() pour enregistrer l'espace de travail :

|
save.image(file="CO2_project_Data.RData") # L'espace de travail est enregistré
 
rm(list=ls())  # Nettoyer l'espace de travail
 
load("CO2_project_Data.RData") # Charger tout ce qui se trouvait dans l'espace de travail
 
head(CO2) # Ça fonctionne! :)

Exporter le jeu de données

Si vous voulez enregistrer des données que vous avez créées ou modifiées dans R, utilisez la fonction write.csv(). Le fichier ainsi créé sera enregistré dans le répertoire de travail.

|
write.csv(CO2,file="CO2_new.csv") # L'objet CO2 est enregistré dans le fichier nommé CO2_new.csv

DÉFI: Utilisez vos propres données

Essayez de charger, explorer, et enregistrer vos propres données dans R. Est-ce que le chargement fonctionne ? Si ce n'est pas le cas, essayez d'arranger vos données dans Excel. Enregistrez vos données “arrangées” et essayez de les ouvrir dans R.

Réparer un jeu de données

Les jeux de données peuvent être désordonnés et incompatibles entre certains systèmes (Mac, Windows) ou entre ordinateurs.

Pratiquons-nous à réparer certains problèmes communs.

DÉFI: Réparer un jeu de données "endommagé"

Importez un jeu de données “endommagé” dans R et trouvez les problèmes.

|
CO2<-read.csv("CO2_broken.csv") # On remplace l'ancien objet CO2 avec le contenu d'un nouveau jeu de données
head(CO2) # C'est le désordre!
CO2
  • Vos données ressemblent probablement à ça !
  • Réparez le jeu de données dans R
  • Faites un essai avant de regarder la solution
  • Entraidez-vous entre voisins et amusez-vous :)

Voici quelques fonctions qui peuvent vous aider :

  • read.csv()
  • head()
  • str()
  • class()
  • unique()
  • levels()
  • which()
  • droplevels()

Note : Vous devez mettre le nom de l'objet entre parenthèses pour utiliser ces fonctions (i.e. head(CO2)). Rappelez-vous que vous pouvez obtenir de l'aide en utilisant ”?“ (e.g. ?str).

Indice : Il y a quatre problèmes avec ce jeu de données!

Réponse #1

Click to display ⇲

Click to hide ⇱

Click to hide ⇱

Problème #1 : On dirait que les données sont contenues dans une seule colonne
Solution :

Click to display ⇲

Click to hide ⇱

Click to hide ⇱

Importez les données de nouveau, mais en spécifiant comment chaque valeur est séparée. L'argument “sep” indique à R quel type de caractère sépare les valeurs sur chaque ligne. Ici, une tabulation sépare les valeurs au lieu d'une virgule.

|
CO2 <- read.csv("CO2_broken.csv",sep = "")
?read.csv

Réponse #2

Click to display ⇲

Click to hide ⇱

Click to hide ⇱

Problème #2 : Les données ne commencent pas avant la troisième ligne. Les en-têtes de colonnes sont remplacés par des notes.

|
head(CO2) # La fonction head() permet de voir que les données n'ont pas été lues avec les bons en-têtes

Solution :

Click to display ⇲

Click to hide ⇱

Click to hide ⇱

Pour régler ce problème, vous devez indiquer à R de sauter les deux premières lignes avec l'argument “skip”.

|
CO2<-read.csv("CO2_broken.csv",sep = "",skip=2)  # En ajoutant l'argument skip, R ignore les deux premières lignes.
head(CO2) # Le tableau a maintenant des en-têtes appropriés.

Réponse #3

Click to display ⇲

Click to hide ⇱

Click to hide ⇱

Problème #3 : Les variables “conc” et “uptake” sont considérées commes des facteurs au lieu de nombres, car il y a du texte dans ces colonnes.

|
str(CO2) # La fonction str() montre que les colonnes 'conc' et 'uptake' sont considérées comme facteurs.
class(CO2$conc)
unique(CO2$conc) # En examinant les valeurs uniques de cette colonne,
                 # on voit que ces deux colonnes contiennent la valeur "cannot_read_notes".
unique(CO2$uptake) 
?unique

Solution :

Click to display ⇲

Click to hide ⇱

Click to hide ⇱

|
?read.csv

|
CO2 <- read.csv("CO2_broken.csv",sep = "",skip = 2,na.strings = c("NA","na","cannot_read_notes")) 

En identifiant “cannot_read_notes” comme valeur manquante (i.e. NA), R peut maintenant ces colonnes correctement. Rappelez-vous que NA signifie “Not available”.

|
head(CO2)
str(CO2) # La variable 'conc' est maintenant de type 'integer' (nombre entier) et la variable 'uptake' est maintenant de type numérique.

Réponse #4

Click to display ⇲

Click to hide ⇱

Click to hide ⇱

Problème #4 : En réalité, il n'y a seulement que deux traitements (chilled & non chilled), mais des erreurs d'orthographe créent deux autres traitements.

|
str(CO2) # Vous voyez qu'il y a quatre niveaux de traitement
levels(CO2$Treatment)
unique(CO2$Treatment) # Les quatre différents traitements sont "nonchilled", "nnchilled", "chilled" et "chiled"

Solution :

Click to display ⇲

Click to hide ⇱

Click to hide ⇱

|
# Vous pouvez utiliser la fonction which() afin de trouver les lignes avec l'erreur "nnchilled"
which(CO2$Treatment=="nnchilled") # Cette commande nous indique que l'erreur se trouve à la ligne 10
# Vous pouvez corriger l'erreur par indexation (voir atelier 1) :
CO2$Treatment[10] <- "nonchilled"
# Vous pouvez faire ces deux étapes en une seule commande :
CO2$Treatment[which(CO2$Treatment=="nnchilled")] <- "nonchilled"
# Faisons la même chose pour "chiled" :
CO2$Treatment[which(CO2$Treatment=="chiled")] <- "chilled" 

Est-ce que les problèmes sont réglés ?

|
str(CO2)  # La fonction str() identifie toujours quatre niveaux de facteur pour la variable traitement
unique(CO2$Treatment) # Cependant, la fonction unique() indique que seulement deux niveaux sont utilisés
CO2<-droplevels(CO2) # La fonction droplevels() supprime les niveaux inutilisés de tous les facteurs du tableau de données
str(CO2) # C'est réparé !

Pourquoi réorganiser ses données?

La réorganisation permet de modifier la structure des données tout en préservant l'information contenue dans le jeu de données. Plusieurs fonctions dans R exigent ou fonctionnent mieux avec une structure de données qui n'est pas nécessairement propice à la lecture par l'oeil humain.

En comparaison à l'agrégation, où plusieurs cellules sont regroupées pour créer un nouveau jeu de données (e.g. tableau des moyennes de plusieurs variables), la réorganisation maintient le même nombre de cellules. Le paquet tidyr permet de réorganiser nos jeu de données dans un format idéal pour nos tâches en aval, et ce à l'aide d'une syntaxe simple et logique.

Commençons par installer et charger le paquet tidyr :

|
if(!require(tidyr)){install.packages("tidyr")}
library(tidyr)

Données en longueur vs. en largeur

Un jeu de données en largeur contient une colonne pour chaque variable ou facteur inclus dans les données. Une rangée peut alors comprendre plusieurs observations différentes.

Un jeu de données en longueur contient une colonne par variable, où chaque rangée s'agit d'une observation unique. Le format “long” est plus “propre” (tidy en anglais), parce que les données sont plus facilement interprétées par R pour nos visualisations et nos analyses.

Le format de vos données dépend de vos besoins d'analyse et de visualisation, mais certains paquets et fonctions, incluant dplyr, lm(), glm(), et gam(), nécessitent des données en longueur. Le paquet ggplot2 peut utiliser des données en largeur pour des visualisations simples, mais fonctionne mieux avec des données en longueur pour des visualisations plus complexes (exemple à venir).

De plus, les données en format long peuvent être agrégées et réorganisées en format large afin de produire des résumés ou de vérifier si un jeu de données est équilibré (i.e. avec le même nombre d'observations par traitement).

On peut utiliser le paquet tidyr pour manipuler la structure d'un jeu de données en préservant toutes les informations d'origine à l'aide des fonctions suivantes:

  • 1. “rassembler” les données (gather data en anglais; large –> long)
  • 2. “disperser” les données (spread data en anglais; long –> large)

Supposons que vous envoyez votre assistant de terrain pour faire la collecte de données des dimensions de plusieurs arbres sur un site de recherche, soit le diamètre à la hauteur de la poitrine (DHP) et la hauteur. Il/elle revient avec un jeu de donnés en format “dégât” (format large):

|
> large <- data.frame(Species = c("Chêne", "Orme", "Frêne"),
                          DHP = c(12, 20, 13),
                         Haut = c(56, 85, 55))
> large
  Species DHP Haut
1   Chêne  12   56
2    Orme  20   85
3   Frêne  13   55

gather(): Rassembler les données en longueur

|
?gather

La plupart des paquets dans le “Hadleyverse” requièrent un format long, c'est-à-dire où chaque rangée est une observation unique, et chaque colonne est une variable. Utilisons la fonction gather() pour “rassembler” nos données en longueur. gather() prend plusieurs colonnes et les empile dans deux colonnes: une colonne spécifiant la variable mesurée, et l'autre spécifiant la mesure associée.

La fonction gather() prend au moins 3 arguments:

  • data: le jeu de données (e.g. “large”)
  • key: le nom de la nouvelle colonne spécifiant la variable mesurée (e.g. “dimension”)
  • value: le nom de la nouvelle colonne spécifiant la mesure associée (e.g. “cm”)
  • : les colonnes qu'on veut empiler dans le jeu de données (e.g. “DBH”, “Haut”)

Voici une démonstration avec le jeu de données large utilisé précédemment, contenant les dimensions d'arbres:

|
# Rassembler les colonnes en rangées d'observations uniques 
 
> long <- gather(large, dimension, cm, DHP, Haut)
> long
  Species dimension cm
1   Chêne       DHP 12
2    Orme       DHP 20
3   Frêne       DHP 13
4   Chêne      Haut 56
5    Orme      Haut 85
6   Frêne      Haut 55

Essayons également avec le jeu de données CO2. On peut “rassembler” les colonnes contant la concentration de CO2 (conc) et le CO2 absorbé (uptake) :

|
CO2.long <- gather(CO2, response, value, conc, uptake)  
head(CO2) 
head(CO2.long) 
tail(CO2.long)

spread(): Disperser les données en largeur

|
?spread

La fonction spread() est l'inverse de gather(): elle nous permet de transformer nos données en format large, en utilisant la même syntaxe. spread() prend 3 arguments principaux:

  • data: le jeu de données (e.g. “long”)
  • key: nom de la colonne contenant les noms des variables (e.g. “dimension”)
  • value: nom de la colonne contenant les mesures associées aux variables (e.g. “cm”)
|
> large2 <- spread(long, dimension, cm)
> large2
  Species DHP Haut
1   Chêne  12   56
2   Frêne  13   55
3    Orme  20   85

separate(): séparer une colonne en deux (ou plusieurs) colonnes

Il arrive parfois qu'un jeu de données contient plusieurs variables réunies dans une même colonne. Dans ces situations, la fonction separate() sépare la colonne pour que chacune des variables ait sa propre colonne.

La fonction separate() prend 4 arguments, et sépare une colonne à partir d'une chaîne de caractères:

  • data: le jeu de données (e.g. “long”)
  • col: nom de la colonne que nous voulons séparer
  • into: nom des nouvelles colonnes crées par la séparation
  • sep: le caractère ou la chaîne de caractères identifiant où séparer le contenu (e.g. ” “)

À titre d'exemple, créons un jeu de données fictif sur les poissons et le zooplancton qui est mal organisé:

|
set.seed(8)
degat <- data.frame(id = 1:4,
                    trt = sample(rep(c('controle', 'culture'), each = 2)),
         zooplancton.T1 = runif(4),
             poisson.T1 = runif(4),
         zooplancton.T2 = runif(4),
             poisson.T2 = runif(4))

Dans ce cas, on commence par “rassembler” ce jeu de données en largeur en format long.

|
> degat.long <- gather(degat, taxa, count, -id, -trt)
> head(degat.long)

Petit rappel rapide: ici, nous utilisons -id et -trt, qui est interprété par la fonction comme étant TOUTES les colonnes sauf id et trt. Nous pourrions également énumérer chaque colonne comme ceci: zooplancton.T1, poisson.T1, zooplancton.T2, poisson.T2.

Le défi principal de separate() est de formuler une expression qui identifie le caractère sur lequel séparer la colonne. Dans notre exemple, nous voulons séparer le contenu de la colonne “taxa” au point (.). Il faut donc utiliser un petit truc provenant des outils d'expressions régulières pour travailler avec les chaînes de caractères (strings en anglais):

|
> degat.long.sep <- separate(degat.long, taxa, into = c("especes", "temps"), sep = "\\.")
> head(degat.long.sep)
  id      trt     especes temps     count
1  1 controle zooplancton    T1 0.3215092
2  2 controle zooplancton    T1 0.7189275
3  3  culture zooplancton    T1 0.2908734
4  4  culture zooplancton    T1 0.9322698
5  1 controle     poisson    T1 0.7691470
6  2 controle     poisson    T1 0.6444911

Ici, notons que la syntaxe \\. est requise parce qu'un point seul (.) est une carte frime (un joker) pour plusieurs fonctions dans le langage R. Les \\ servent donc à indiquer qu'on fait référence au caractère ”.“.

Récapitulatif: tidyr

tidyr est un paquet qui réorganise la structure de jeux de données.

  • Convertir de format large en format long à l'aide de gather()
  • Convertir de format long en format large à l'aide de spread()
  • Séparer et regrouper des colonnes à l'aide de separate() et de son inverse, unite()

Voici un aide-mémoire (en anglais) pour faciliter la manipulation de jeux de données avec tidyr et dplyr: https://www.rstudio.com/wp-content/uploads/2015/02/data-wrangling-cheatsheet.pdf

DÉFI: Utiliser tidyr pour convertir le format long en format large

Réorganisez le jeu de données airquality en format long (en rassemblant toutes les colonnes sauf “Month” et “Day”). Ensuite, dispersez-le en format large pour retrouver le format original de airquality:

Solution


Introduction à dplyr

Souvent, on doit faire appel à une gamme d'outils complexes pour manipuler nos jeux de données. La mission de dplyr est de simplifier nos tâches de manipulation en regroupant toutes les opérations communes sous un même toit. Le résultat est une collection de fonctions ayant une syntaxe simple qu'on peut exécuter à l'aide de “verbes” (ou fonctions) intuitifs.

En plus d'être facile à utiliser, le paquet dplyr est exceptionnel pour les raisons suivantes:

  • il peut traiter des grands jeux de données rapidement (écrit en langage Cpp)
  • sa fonctionnalité est bien intégrée dans RStudio et dans les autres paquets du Hadleyverse
  • il peut établir des connections à des bases de données SQL, et traduire votre code en requête SQL
  • c'est le Batman des paquets R (maîtrise la peur des données et adopte des technologies avancées)

Certaines fonctions de base dans R ressemblent à des fonctions dans dplyr, incluant: split(), subset(), apply(), sapply(), lapply(), tapply() and aggregate().

Commençons par installer et charger le paquet dplyr:

|
if(!require(dplyr)){install.packages("dplyr")}
library(dplyr)

Au coeur du paquet dplyr, on retrouve des “verbes” essentiels qui nous permettent d'accomplir la manipulation de données. Voici 4 verbes qui exécutent les opérations les plus communes:

  • select() : sélectionne des colonnes dans un jeu de données
  • filter() : filtre des rangées suivant les critères spécifiés
  • arrange() : trie les données d'une colonne en ordre croissant ou décroissant
  • mutate() : crée une nouvelle colonne de données (ou transforme une colonne existante)

Dans les exemples et les défis qui suivent, nous utiliserons les jeux de données airquality et ChickWeight, qui sont inclus dans R:

|
?airquality
data(airquality)
?ChickWeight
data(ChickWeight)

select(): Sélection de colonnes

La syntaxe de la fonction select() est: select(données, colonne1, colonne2, …). La majorité de fonctions dplyr suivent cette même syntaxe. select() prend alors au moins 2 arguments:

  • data: le jeu de données
  • : noms ou positions de colonnes, ou expressions complexes (séparés par des virgules pour désigner les colonnes que l'on veut sélectionner

Par exemple:

|
select(données, colonne1, colonne2) # sélectionne colonne1 et colonne2
select(données, c(2:4,6) # sélectionne les colonnes 2 à 4, plus la 6ème colonne
select(données, -colonne1) # sélectionne toutes les colonnes sauf la 1ère 
select(données, start_with(x.)) # sélectionne les colonnes ayant un nom qui commence par "x."

Voici d'autres arguments utiles pour select():

À titre d'exemple, supposons qu'on s'intéresse à la variation de la variable Ozone avec le temps. À l'aide de la fonction select(), on peut retenir uniquement les colonnes requises pour nos analyses :

|
> ozone <- select(airquality, Ozone, Month, Day)
> head(ozone)
  Ozone Month Day
1    41     5   1
2    36     5   2
3    12     5   3
4    18     5   4
5    NA     5   5
6    28     5   6

filter(): Sélection de rangées

Souvent, on s'intéresse seulement à un sous-ensemble de notre jeu de données. Pour extraire un sous-ensemble de rangées selon une condition, on peut utiliser la fonction filter() avec la syntaxe suivante: filter(dataframe, proposition logique 1, proposition logique 2, …).

On se rappelle que la réponse à une proposition logique est VRAI ou FAUX (TRUE or FALSE). La fonction filter() retient uniquement les rangées pour lesquelles la réponse à la proposition logique est VRAI. On peut aussi utiliser cette fonction sur des chaînes de caractères (strings) et des facteurs (factors).

Voici un petit rappel du fonctionnement des propositions logiques dans R:

Par exemple, supposons qu'on s'intéresse aux périodes de canicules du mois d'août dans le jeu de données airquality :

|
> aout <- filter(airquality, Month == 8, Temp >= 90)
> head(aout)
  Ozone Solar.R Wind Temp Month Day
1    89     229 10.3   90     8   8
2   110     207  8.0   90     8   9
3    NA     222  8.6   92     8  10
4    76     203  9.7   97     8  28
5   118     225  2.3   94     8  29
6    84     237  6.3   96     8  30

arrange(): Triage de rangées

Parfois, on travaille avec des jeux de données qui doivent être analysés et/ou visualisés dans un ordre en particulier (e.g. série temporelle).

La fonction arrange() permet de trier les rangées d'un jeu de données selon une ou plusieurs colonnes, suivant cette syntaxe: arrange(données, variable1, variable2, …).

Par défaut, les rangées sont réordonnées en ordre croissant (alphabétiquement ou numériquement). On peut également réordonner les rangées en ordre décroissant en enveloppant la variable d'intérêt par la fonction desc() au sein de la fonction arrange(), comme ceci: arrange(données, variable1, desc(variable2), …).

Commençons par créer une version désordonnée de airquality:

|
> air_degat <- sample_frac(airquality, 1)
> head(air_degat)
    Ozone Solar.R Wind Temp Month Day
21      1       8  9.7   59     5  21
42     NA     259 10.9   93     6  11
151    14     191 14.3   75     9  28
108    22      71 10.3   77     8  16
8      19      99 13.8   59     5   8
104    44     192 11.5   86     8  12

Maintenant, voici comment réarranger le jeu de données en ordre chronologique, soit en ordre croissant de Month et ensuite en ordre croissant de Day :

|
> air_chron <- arrange(air_degat, Month, Day)
> head(air_chron)
  Ozone Solar.R Wind Temp Month Day
1    41     190  7.4   67     5   1
2    36     118  8.0   72     5   2
3    12     149 12.6   74     5   3
4    18     313 11.5   62     5   4
5    NA      NA 14.3   56     5   5
6    28      NA 14.9   66     5   6

Quelle est la différence quand on change l'ordre des colonnes ciblées par la fonction?

|
arrange(air_mess, Day, Month)

mutate(): Créer des nouvelles colonnes

Avant même de passer au triage ou à l'extraction d'un sous-ensemble de données, il faut souvent générer des nouvelles variables ou transformer des variables existantes. On peut utiliser la fonction mutate() pour créer et transformer des variables.

La fonction mutate() suit la syntaxe suivante: mutate(données, newVar1 = expression1, newVar2 = expression2, …).

Utilisons mutate() pour créer une nouvelle colonne. Par exemple, la température est en degrés Fahrenheit dans le jeu de données airquality, et nous voulons transformer la variable Temp en degrés Celsius:

|
> airquality_C <- mutate(airquality, Temp_C = (Temp-32)*(5/9))
> head(airquality_C)
  Ozone Solar.R Wind Temp Month Day   Temp_C
1    41     190  7.4   67     5   1 19.44444
2    36     118  8.0   72     5   2 22.22222
3    12     149 12.6   74     5   3 23.33333
4    18     313 11.5   62     5   4 16.66667
5    NA      NA 14.3   56     5   5 13.33333
6    28      NA 14.9   66     5   6 18.88889

Notez que dans notre exemple, la syntaxe est très simple. Cependant, à l'intérieur d'un seul appel de la fonction mutate() on peut:

  • remplacer le contenu de colonnes existantes (en conservant le même nom)
  • créer plusieurs nouvelles colonnes
  • créer des nouvelles colonnes calculées à partir de variables créés en amont

Le paquet magrittr introduit un nouvel outil commun dans plusieurs langages informatiques : le pipe. Le “pipe” (un tuyau en français) permet de relier nos fonctions en redirigeant la sortie (output) d'une fonction en amont vers l'entrée (input) d'une fonction en aval. Le “pipe” fournit par le paquet magrittr s'écrit come ceci : %>%. Lorsqu'on l'utilise en combo avec dplyr, le “pipe” de magrittr nous permet d'accéder à la flexibilité et à toutes les fonctionnalités intéressantes du paquet. Pour vous familiariser avec ce type de flux de travail, nous utiliserons dplyr avec magrittr pour le restant de l'atelier.

Commençons par installer et charger le paquet :

|
if(!require(magrittr)){install.packages("magrittr")}
library(magrittr)

L'utilisation de magrittr est très intuitif. Nous allons démontrer son fonctionnement en combinant les exemples ci-haut. Supposons qu'on veut créer un sous-ensemble de airquality pour le mois de juin, et ensuite convertir la variable de la température en degrés Celsius. Si on procède sans le “pipe”, soit une étape à la fois, notre code lirait comme suit :

|
juin_C <- mutate(filter(airquality, Month == 6), Temp_C = (Temp-32)*(5/9))

Ce code est plus difficile à lire parce que l'ordre des opérations exécutées commence au centre et se lit vers l'extérieur, jusqu'à la fonction enveloppant le tout. Plus on ajoute des opérations, plus le code devient illisible. Au lieu d'envelopper tous les fonctions, on peut écrire les opérations en ordre d'exécutions et les relier à l'aide du “pipe” %>% :

|
juin_C <- airquality %>% 
    filter(Month == 6) %>%
    mutate(Temp_C = (Temp-32)*(5/9))

Notez qu'au lieu de spécifier le jeu de données au sein de chaque fonction, on inscrit le jeu de données avant les opérations et on le “pipe” jusqu'à la prochaine fonction à l'aide de %>%.

En utilisant le “pipe”, le code est moins redondant. De plus, il se lit et s'écrit dans le même ordre que l'exécution des opérations, ce qui facilite et accélère la traduction de nos pensées en code, ainsi que la lecture et la compréhension du code écrit par nos collègues. Lorsque nos opérations de manipulation de données deviennent plus complexes et requièrent plusieurs étapes, on s'aperçoit rapidement que magrittr offre une approche puissante et élégante pour la rédaction de notre code dplyr.

BONUS : Dans RStudio, on peut insérer le “pipe” rapidement avec la combinaison des touches suivantes: Ctrl (or Cmd sur Mac) +Shift+M.

Les verbes dplyr que nous avons utilisés dans cet atelier sont utiles individuellement, mais deviennent particulièrement puissants quand ils sont reliés par le “pipe” (%>%), et lorsqu'on les applique sur des sous-ensembles de données. Les fonctions dplyr suivantes nous permettent de séparer nos jeu de données en groupes distincts sur lesquels on peut exécuter des opérations individuelles, comme des fonctions d'aggrégation et de sommaire:

  • group_by(): regrouper le jeu de données par un facteur pour les opérations en aval (comme summarise)
  • summarise(): créer un sommaire de variables au sein de groupes distincts dans un jeu de données en utilisant des fonctions d'aggrégation (e.g. min(), max(), mean(), etc…)

Ces deux verbes fournissent la structure requise pour la stratégie Séparer-Appliquer-Combiner (“Split-Apply-Combine”) originalement introduite dans le paquet plyr, l'ancêtre de dplyr.

Utilisons ces deux fonctions pour générer un sommaire du jeu de données airquality qui montre la température moyenne et l'écart type pour chaque mois:

|
> mois_moy <- airquality %>% 
      group_by(Month) %>% 
      summarise(mean_temp = mean(Temp),
                sd_temp = sd(Temp)) 
mois_moy
Source: local data frame [5 x 3]
 
  Month mean_temp  sd_temp
  (int)     (dbl)    (dbl)
1     5  65.54839 6.854870
2     6  79.10000 6.598589
3     7  83.90323 4.315513
4     8  83.96774 6.585256
5     9  76.90000 8.355671

DÉFI: dplyr et magrittr

En utilisant le jeu de données ChickWeight, créez un tableau sommaire dans lequel on retrouve la différence de masse entre le maximum et le minimum de la masse enregistré pour chaque poussin dans l'étude. Utilisez les verbes dplyr et le “pipe” %>%.

|
?ChickWeight
data(ChickWeight)

Solution

Indice pour les ninjas

Notez qu'on peut regrouper un jeu de données par plus qu'un facteur, en suivant la syntaxe suivante :

group_by(groupe1, groupe2, …)

Au sein de “group_by()”, on peut regrouper par plusieurs variables pour créer un “oignon” de sous-groupes. Chaque fois qu'on utilise la fonction summarise(), elle élimine une couche de l'oignon en commençant par la couche extérieure, donc la dernière variable dans group_by() qui s'agit de groupe2 dans cet exemple. Ainsi, dans l'exemple ci-haut, si on utilise la fonction summarise() une seule fois en aval:

group_by(groupe1, groupe2) %>% summarise(x = mean(y))

Le groupe2 est éliminé, et le jeu de données devient ensuite groupé par groupe1.

NINJA DÉFI: dplyr & magrittr

En utilisant le jeu de données ChickWeight, créez un tableau sommaire qui indique pour chaque Diet, la moyenne de la différence de masse entre la fin et le début de l'étude pour chaque poussin. Utilisez les verbes dplyr et le pipe %>%. (Indice : les fonctions first() et last() pourraient s'avérer utiles).

Solution

En plus des fonctions que nous avons explorées aujourd'hui, dplyr offre d'autres fonctions forts utiles nous permettant de fusionner des tableau de données, avec une syntaxe relativement simple :

  • left_join()
  • right_join()
  • inner_join()
  • anti_join()

Ces fonctions vont au-delà du matériel d'introduction dans cet atelier, mais ils offrent des fonctionnalités pouvant être très utiles pour des manipulations de données plus complexes.