« Organiser des données avec tidyr » : différence entre les versions
Aucun résumé des modifications |
|||
(33 versions intermédiaires par le même utilisateur non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
{{ En construction }} | {{En construction }} | ||
{{tutoriel | |||
|fait_partie_du_cours=Pensée computationnelle avec R | |||
|module_suivant= | |||
|pas_afficher_sous-page=Non | |||
|page_suivante= | |||
|statut=à finaliser | |||
|difficulté=débutant | |||
|cat tutoriels=R | |||
}} | |||
== Introduction == | == Introduction == | ||
Ligne 14 : | Ligne 24 : | ||
L'article nécessite de connaissances de base de [[R]], notamment au niveau des structures de données de type <code>data.frame</code> ou <code>tibble</code> (i.e. organisées en lignes et colonnes). La lecture préalable de l'article [[Introduction à Tidyverse]] est également recommandée. | L'article nécessite de connaissances de base de [[R]], notamment au niveau des structures de données de type <code>data.frame</code> ou <code>tibble</code> (i.e. organisées en lignes et colonnes). La lecture préalable de l'article [[Introduction à Tidyverse]] est également recommandée. | ||
=== Relation étroite avec dplyr === | |||
Le paquet '''tidyr''' met à disposition certaines fonctionnalités qui sont bien complétées par '''dplyr''', un autre paquet de l'écosystème [[introduction à Tidyverse|Tidyverse]]. Il existe un article consacré à ce paquet dans ce wiki : | |||
{{Goblock | [[Manipuler des données avec dplyr]] }} | |||
== Installation et chargement == | == Installation et chargement == | ||
Ligne 59 : | Ligne 75 : | ||
Voir [[Introduction à Tidyverse]] pour plus de détails. | Voir [[Introduction à Tidyverse]] pour plus de détails. | ||
== ''Tidy data'' == | == Structures des données et ''Tidy data'' == | ||
Dans l'[[introduction à Tidyverse]], les principes à la base du concept de ''tidy data'' (Wickham, 2014) ont déjà été abordés de manière conceptuelle. Dans cette section, les données ''tidy'' sont abordées de manière plus pragmatique. | Dans l'[[introduction à Tidyverse]], les principes à la base du concept de ''tidy data'' (Wickham, 2014) ont déjà été abordés de manière conceptuelle. Dans cette section, les données ''tidy'' sont abordées, de manière plus pragmatique, en fonction de différentes structures de données possibles et aux avantages de pouvoir passer entre structures. | ||
=== Organisation de données ''tidy'' === | === Organisation de données ''tidy'' === | ||
Ligne 105 : | Ligne 121 : | ||
# Format ''à plat'' ou ''emboîté'' | # Format ''à plat'' ou ''emboîté'' | ||
# Format ''large'' ou ''long'' | # Format ''large'' ou ''long'' | ||
''tidyr'' propose des fonctions qui permettent une grande flexibilité pour passer entre les différents formats et le reste de l'article sera organisé avec des exemples qui se réfèrent, entre autre, aux passages entre ces formats que nous présentons donc d'abord de manière conceptuelle et succincte. | |||
==== Format ''à plat'' ou ''emboîté'' ==== | ==== Format ''à plat'' ou ''emboîté'' ==== | ||
Un format ''à plat'' correspond au format ''classique'', souvent appelé '''tabulaire''' ou '''rectangulaire''', des jeux de données qu'on peut notamment trouver dans les applications de type ''spreadsheet'' (Excel et équivalent). Structurellement, ces jeux de données ne sont pas forcément dans le format ''tidy'', mais sont en général plus simple à visualiser et à manipuler, car ils maintiennent les deux dimensions ''classiques'' des colonnes et lignes, qu'on peut donc transformer respectivement en variables et observations. | |||
Un format ''emboîté'', ou ''nested'' en anglais, au contraire, organise les données sur plusieurs niveaux, comme par exemple dans les structures de type liste en [[r]], dans le format [[JSON]], ou encore dans une [[base de données]] NoSQL. Ce format est, d'une part, plus difficile à visualiser et manipuler ; mais de l'autre, permet parfois plus de flexibilité et de puissance. Cependant, la plupart des fonctions disponibles en [[R]] fonctionnemnt avec des structures rectangulaires, donc ''à plat'', et il arrive donc assez souvent de devoir ''aplatir'' une structure emboîtée afin de pouvoir la visualiser, modéliser, ou afficher dans un document. Le processus inverse est également possible, ce qui permet notamment l'automatisation de certains processus sur des larges données, mais en général il s'agit de passages plus complexes qui dépassent le niveau introductif de cet article. | |||
==== Format ''large'' ou ''long'' ==== | ==== Format ''large'' ou ''long'' ==== | ||
La distinction entre format ''large'' et format ''long'' est métaphoriquement simple à saisir, mais conceptuellement plus compliquée. Il s'agit en effet de deux manières différents de penser aux données. La distinction s'applique également aux données ''à plat'' ou ''emboîtées'', mais la distinction est souvent plus importante dans les données rectangulaires (''à plat''). | |||
Dans l'[[introduction à Tidyverse]], cette distinction à été montrée avec un jeu de données complet que ce soit en format ''large'' ou en format ''long''. Ici nous montrons un cas différent, qui est assez fréquent avec les données, c'est-à-dire dans lequel il n'y a pas des données pour toutes les combinaisons entre variables/colonnes et observations/lignes. Le jeu de données suivant affiche les notes obtenus par 10 étudiant-es à trois cours différents ('''math'''ématique, '''hist'''oire et '''fran'''çais), où chaque étudiant-e n'a suivi qu'un seul cours (e.g. un cours à option entre les trois). | |||
Dans le format ''large'', ou chaque cours représente une variable/colonne, donc, il existe plusieurs données manquantes (représentée par '''NA''' en [[R]]) : | |||
{| class="wikitable" | |||
! etu | |||
! align="right" | math | |||
! align="right" | fran | |||
! align="right" | hist | |||
|- | |||
| Etu1 | |||
| align="right" | 4.75 | |||
| align="right" | NA | |||
| align="right" | NA | |||
|- | |||
| Etu2 | |||
| align="right" | NA | |||
| align="right" | 5.75 | |||
| align="right" | NA | |||
|- | |||
| Etu3 | |||
| align="right" | 4.00 | |||
| align="right" | NA | |||
| align="right" | NA | |||
|- | |||
| Etu4 | |||
| align="right" | 5.25 | |||
| align="right" | NA | |||
| align="right" | NA | |||
|- | |||
| Etu5 | |||
| align="right" | NA | |||
| align="right" | 3.25 | |||
| align="right" | NA | |||
|- | |||
| Etu6 | |||
| align="right" | NA | |||
| align="right" | NA | |||
| align="right" | 3.25 | |||
|- | |||
| Etu7 | |||
| align="right" | NA | |||
| align="right" | 5.00 | |||
| align="right" | NA | |||
|- | |||
| Etu8 | |||
| align="right" | 5.75 | |||
| align="right" | NA | |||
| align="right" | NA | |||
|- | |||
| Etu9 | |||
| align="right" | NA | |||
| align="right" | 3.25 | |||
| align="right" | NA | |||
|- | |||
| Etu10 | |||
| align="right" | 4.25 | |||
| align="right" | NA | |||
| align="right" | NA | |||
|} | |||
Avec les mêmes données en format ''long'', les trois observations/colonnes de chaque matière sont condensées dans une variable '''cours''', et par conséquent chaque observation affiche l'étudiant-e, le cours spécifique, et la note obtenue : | |||
{| class="wikitable" | |||
! etu | |||
! cours | |||
! align="right" | note | |||
|- | |||
| Etu1 | |||
| math | |||
| align="right" | 4.75 | |||
|- | |||
| Etu2 | |||
| fran | |||
| align="right" | 5.75 | |||
|- | |||
| Etu3 | |||
| math | |||
| align="right" | 4.00 | |||
|- | |||
| Etu4 | |||
| math | |||
| align="right" | 5.25 | |||
|- | |||
| Etu5 | |||
| fran | |||
| align="right" | 3.25 | |||
|- | |||
| Etu6 | |||
| hist | |||
| align="right" | 3.25 | |||
|- | |||
| Etu7 | |||
| fran | |||
| align="right" | 5.00 | |||
|- | |||
| Etu8 | |||
| math | |||
| align="right" | 5.75 | |||
|- | |||
| Etu9 | |||
| fran | |||
| align="right" | 3.25 | |||
|- | |||
| Etu10 | |||
| math | |||
| align="right" | 4.25 | |||
|} | |||
Les deux formats ont avantages et désavantages, surtout dans des contextes plus complexes par rapport au simple exemple illustrés ici. Indépendamment de la ''qualité intrinsèque'' de la structure, de plus, au niveau pragmatique il y a des fonctions de [[R]] qui requièrent, ou sont plus adaptées, avec l'un ou l'autre format. Donc la possibilité de passer de l'un à l'autre peut se révéler une nécessite et pas ''seulement'' une question de structuration optimale. | |||
== Organisation des données ''à plat'' == | |||
Les données ''à plat'' (aussi appelées rectangulaires ou tabulaires) représentent à présent le format le plus fréquent en sciences sociales, grâce à une organisation en ligne et colonnes qui est assez intuitive à la fois pour la visualisation et la modification de jeux de données de petite/moyenne taille. | |||
Cette section propose une série d'exemple d'instructions pour améliorer la structure de données ''à plat'', ou pour passer entre les formats ''large'' et ''long'' décrits plus haut dans l'article. Les exemples utilisent parfois des jeux de données qui sont disponibles directement dans le paquet '''tidyr'''. | |||
=== Écarter les données manquantes === | |||
Une opération très fréquente dans l'organisation des données consiste à écarter les données manquantes. On utilise ici le terme ''écarter'' plutôt que ''supprimer'', car les données manquantes peuvent souvent apporter de l'information qui est importante à retenir. Si, par exemple, dans un questionnaire/échelle qui nécessite les réponses à toutes les questions pour calculer une sorte d'indice agrégé, la plupart des participant-es ne répondent pas à la quatrième question, il y a probablement un problème avec cet item. ''Supprimer'' tout simplement les données manquantes véhicule une image irréversible. Au contraire, on peut les écarter de manière instrumentale au type d'analyse qu'on veut mener (e.g. utiliser l'indice agrégé), mais les maintenir quand même dans les données originales pour d'autres analyses ou juste pour rappel intrinsèque dans les données de la problématique avec l'item. | |||
'''tidyr''' met à disposition plusieurs fonctions pour traiter des données manquantes. Nous verrons ici seulement les suivantes : | |||
* <code>drop_na()</code> qui permet d'écarter les observations/lignes qui ont des valeurs manquantes | |||
* <code>replace_na()</code> qui permet de substituer les valeurs '''NA''' de [[R]] avec des alternatives | |||
==== drop_na() ==== | |||
La fonction <code>drop_na()</code> permet d'écarter des données manquantes selon des critères de sélection. L'exemple suivant illustre le fonctionnement de cette fonction. | |||
Créons d'abord un petit jeu de données qui représente les résultats d'un test qui se compose de trois questions auxquelles les participant-es peuvent répondre "Oui" ou "Non". Le jeu de données propose également un identifiant du participant et l'âge. Pour créer ce jeu de données, nous utilisons la fonction <code>tribble()</code> de Tidyverse qui permet de créer des jeux de données par lignes plutôt que par colonnes (avec <code>tibble()</code> : | |||
<source lang="R"> | |||
results_test <- tribble( | |||
~participant, ~age, ~item1, ~item2, ~item3, | |||
"P1", 35, "Oui", "Non", "Oui", | |||
"P2", NA, "Oui", "Oui", "Oui", | |||
"P3", 22, "Non", "Non", NA, | |||
"P4", 27, "Oui", NA, "Non", | |||
"P5", NA, "Non", "Non", "Oui" | |||
) | |||
</source> | |||
Pour faciliter la visualisation, voici les données dans une table [[Mediawiki]] : | |||
{| class="wikitable" | |||
! participant | |||
! align="right" | age | |||
! item1 | |||
! item2 | |||
! item3 | |||
|- | |||
| P1 | |||
| align="right" | 35 | |||
| Oui | |||
| Non | |||
| Oui | |||
|- | |||
| P2 | |||
| align="right" | NA | |||
| Oui | |||
| Oui | |||
| Oui | |||
|- | |||
| P3 | |||
| align="right" | 22 | |||
| Non | |||
| Non | |||
| NA | |||
|- | |||
| P4 | |||
| align="right" | 27 | |||
| Oui | |||
| NA | |||
| Non | |||
|- | |||
| P5 | |||
| align="right" | NA | |||
| Non | |||
| Non | |||
| Oui | |||
|} | |||
On peut utiliser la fonction <code>drop_na()</code>, sans aucun argument, pour écarter les observations qui présente '''au moins''' une valeur manquante dans toutes les variables du jeu de données : | |||
<source lang="R"> | |||
results_test %>% | |||
drop_na() | |||
</source> | |||
Cette instruction résulte dans un jeu de données qui se limite à une seule observation, celle du participant "P1", le seule à avoir répondu aux trois items et fourni son âge. | |||
Cependant, les valeurs manquantes n'ont souvent pas toutes le même ''poids''. Dans cet exemple, notamment, certains participants ont répondu aux items, mais n'ont pas fourni leur âge : une information qui peut-être considérée moins importante par les chercheurs. Par conséquent, on peut passer à la fonction <code>drop_na()</code> des critères de sélection des variables en argument, comme dans l'exemple suivant : | |||
<source lang="R"> | |||
results_test %>% | |||
drop_na(item1:item3) | |||
</source> | |||
La notation '''item1:item3''' équivaut à sélectionner toutes les variables/colonnes qui se trouvent entre item1 et item3, qui sont comprises dans la sélection. Dans cet exemple, il s'agit donc des variables item1, item2, et item3. Avec ce critère de sélection plus permissif, on peut retenir les observations des participants "P1", "P2" et "P5". On peut notamment utiliser une nouvelle référence symbolique pour ensuite agir seulement sur ces observations considérées ''complètes'' aux finalités des chercheurs : | |||
<source lang="R"> | |||
complete_test <- results_test %>% | |||
drop_na(item1:item3) | |||
</source> | |||
Les critères de sélection des variables/colonnes sont communs à plusieurs paquets de [[Introduction à Tidyverse|Tidyverse]] et sont traités de manière plus approfondie dans l'article de ce wiki sur le paquet '''dplyr''': | |||
{{Goblock | [[Manipuler des données avec dplyr]] }} | |||
==== replace_na() ==== | |||
Une autre opération assez fréquente avec les données manquantes consiste à les remplacer avec d'autres valeurs, par exemple 0 dans le cas de variables quantitatifs, ou avec des textes alternatifs, notamment pour des publications. Il faut néanmoins faire bien attention à ce type de substitution, car [[R]] est très sensible à la présence/absence de données, et la manière la plus sûre de stocker des valeurs manquantes est de maintenir le '''NA'''. Des conventions, comme par exemple insérer 9999 pour des données manquantes, sont donc en général à éviter, car elles risquent de fausser des calculs ou des manipulations ultérieures. D'ailleurs, insérer une valeur différente de '''NA''' risque également de modifier le type de données associés à la variable/colonne (par exemple d'un chiffre à une suite de caractères) | |||
En tout cas, si on est certains de vouloir remplacer les '''NA''' avec d'autres éléments, alors la fonction <code>replace_na()</code> accepte une liste avec la syntaxe suivante : | |||
<source lang="R"> | |||
data %>% | |||
replace_na(list(variable = nouvelle_valeur, autre_variable = nouvelle_valeur, ...)) | |||
</source> | |||
Si on reprend l'exemple du jeu de données utilisé avec la fonction <code>drop_na()</code>, on peut s'imaginer que pour afficher le jeu des données dans une contribution, les chercheurs veulent remplacer '''NA''' avec ''Non dévoilée'' dans la variable '''age'''. Le code pour effectuer cette transformation est le suivant : | |||
<source lang="R"> | |||
results_test <- tribble( | |||
~participant, ~age, ~item1, ~item2, ~item3, | |||
"P1", 35, "Oui", "Non", "Oui", | |||
"P2", NA, "Oui", "Oui", "Oui", | |||
"P3", 22, "Non", "Non", NA, | |||
"P4", 27, "Oui", NA, "Non", | |||
"P5", NA, "Non", "Non", "Oui" | |||
) | |||
results_test %>% | |||
replace_na(list(age = "Non dévoilée")) | |||
</source> | |||
Cette exemple illustre d'ailleurs le mécanisme de changement du type de données, car la variable sur l'âge a été convertie en suite de caractères (char) au lieu de chiffre. | |||
Il existe des moyens pour modifier tous les '''NA''' d'un jeu de données sans spécifier à chaque fois la variable/colonne, mais ces instructions sont déconseillées pour éviter des résultats inattendus. | |||
=== Séparer ou condenser des variables/colonnes === | |||
Une autre type d'opération qui peut se révéler utile dans l'organisation des données consiste à séparer deux ou plusieurs variables/colonnes, si une valeur propose différentes informations ; ou le contraire, c'est-à-dire condenser deux ou plusieurs variables/colonnes. '''tidyr''' met à disposition respectivement les fonctions : | |||
* <code>separate()</code> pour créer deux ou plusieurs colonnes à partir d'une colonne | |||
* <code>unit()</code> pour condenser deux ou plusieurs colonnes dans une seule | |||
==== separate() ==== | |||
Parfois, les jeux de données affiche dans une seule variable/colonne l'équivalente de deux ou plusieurs informations distincts. À ce moment, il est utile de pouvoir ''étaler'' le contenu sur deux ou plusieurs colonnes. La fonction <code>separate()</code> permet cette opération en spécifiant des arguments qui permettent d'effectuer cette opération de manière assez simple. | |||
Voici un exemple de jeu de données qui simule une série d'association de mots effectuée pour un participant. On peut imaginer que la personne qui a récolté les associations les ait mise dans une seule colonne d'un ''spreadsheet'' en les séparant par un trait d'union. Voici le code pour créer quelques exemples de ce type : | |||
<source lang="R"> | |||
associations_mots <- tribble( | |||
~participant, ~association, | |||
"P1", "maison-vacance", | |||
"P1", "voiture-rouge", | |||
"P1", "pays-village", | |||
"P1", "programmation-pensée computationnelle", | |||
"P1", "émotion-vice-versa" | |||
) | |||
</source> | |||
Il est plus utile est intéressant de pouvoir disposer du mot ''stimulus'' et de la réponse du participant dans deux colonnes différentes. La séparation peut se faire facilement pour les quatre première observations/lignes, mais la cinquième pose un problème car le trait d'union figure une fois en tant qu'élément séparateur, mais une autre fois en tant que caractère de la réponse "vice-versa". Le code suivant permet de séparer les variables en tenant compte de la ''double'' présence du trait d'union : | |||
<source lang="R"> | |||
associations_mots %>% | |||
separate(col = association, into = c("stimulus", "reponse"), sep = "-", extra = "merge") | |||
</source> | |||
L'instruction s'explique de la manière suivante : | |||
* '''col''' identifie quelle variable/colonne contient plusieurs informations à séparer | |||
* '''into''' utile un vecteur pour déterminer les nouveaux noms des colonnes à créer | |||
* '''sep''' identifie le caractère de séparation en fonction du quel diviser le contenu | |||
* '''merge''' permet de garder le deuxième trait d'union comme élément du contenu. Vous pouvez essayez de modifier le code avec les options '''warn''' ou '''drop''' pour voir la différence sur le résultat final | |||
La fonction met à disposition d'autres mécanismes pour gérer des cas plus complexes. Vous pouvez consultez la [https://tidyr.tidyverse.org/reference/separate.html référence directe de la fonction] pour plus de détails. | |||
==== unite() ==== | |||
La fonction <code>unite()</code> permet de condenser deux ou plusieurs variables/colonnes en une seule. Un exemple classique concerne le stockage d'une date dans trois colonnes différentes : une pour le jour, une pour le mois, et une pour l'année. Le code suivant crée un petit jeu de données qui simule un journal de bord : | |||
<source lang="R"> | |||
journal_bord <- tribble( | |||
~jour, ~mois, ~annee, ~situation, | |||
12, 9, 2020, "Tout va bien", | |||
18, 10, 2020, "Il pleut", | |||
25, 11, 2020, "Rien à signaler" | |||
) | |||
</source> | |||
Avec la fonction <code>unite()</code>, on peut condenser les trois relatives à la date dans une seule : | |||
<source lang="R"> | |||
journal_bord %>% | |||
unite(col = date, c(jour, mois, annee), sep = "-") | |||
</source> | |||
Le code effectue les passages suivants : | |||
# D'abord on définir le nom de la nouvelle colonne avec l'attribut '''col''' = date | |||
# Ensuite on spécifie le vecteur des variables/colonnes qui sont intéressées par l'union | |||
# Enfin on définit si appliquer un séparateur, et dans ce cas quel(s) caractères utiliser | |||
Ce type d'opération peut être ensuite complété avec la fonction <code>mutate()</code> de '''[[manipuler des données avec dplyr|dplyr]]''' pour convertir la colonne dans le format des dates en [[R]] : | |||
<source lang="R"> | |||
# Ce code utilise la fonction mutate() de dplyr | |||
journal_bord %>% | |||
unite(col = date, c(jour, mois, annee), sep = "-") %>% | |||
mutate( | |||
date = as.Date(date, format="%d-%m-%Y") | |||
) | |||
</source> | |||
=== Passer du format ''large'' au format ''long'' et vice-versa === | |||
Le passage du format ''large'' au format ''long'' et vice-versa sont deux opérations qui peuvent se révéler très utiles, voire indispensables, en [[introduction à Tidyverse|tidyverse]], et plus en général en [[R]]. En effet, selon le type de manipulations ultérieurs qu'on souhaite effectuer sur les données, il se peut que les fonctions spécifiques acceptent exclusivement, ou puissent bénéficier davantage, d'un format plutôt que de l'autre. | |||
Le passage entre les deux formats n'est cependant pas seulement un stratagème technique. Il s'agit aussi d'un changement de structure, et par conséquent de perspective sur les données. À cet effet, surtout le passage du format ''large'' au format ''long'' peut se révéler plus difficile à imaginer, surtout au début. | |||
Les deux fonctions que '''tidyr''' met à disposition pour la conversion des formats sont : | |||
* <code>pivot_longer()</code> pour le passage du format ''large'' au format ''long''. Cette fonction remplace la fonction <code>gather()</code> qu'on peut encore trouver dans quelques tutoriels ou code source plus datés | |||
* <code>pivot_wider()</code> pour le passage du format ''long'' au format ''large''. Cette fonction remplace la fonction <code>spread()</code> qu'on peut également encore retrouver à certains endroits. | |||
==== pivot_longer() ==== | |||
La fonction <code>pivot_longer()</code> permet de passer du format large ''classique'' au format long qui peut être très utile, voire indispensable, pour des [[Introduction à la modélisation des données avec R|modélisation]] ou des [[Introduction à la visualisation des données avec R|représentation graphiques]] plus articulées. | |||
Imaginons d'avoir des données qui sortent d'un logiciel de sondage comme [[Qualtrics]] ou [[Limesurvey]] dans lequel chaque participant a dû évaluer deux dimensions d'une formation en ligne : l'intérêt et la motivation. Le questionnaire lui demande d'évaluer chacune de dimension pour (1) soi-même, (2) pour le reste de la classe, et (3) pour l'enseignant-e. | |||
Nous allons simuler ces données afin de pouvoir les manier et adapter selon vos éventuels besoins : | |||
<source lang="R"> | |||
library(tidyverse) | |||
# Déterminer le nombre d'observations | |||
n_observations <- 20 | |||
# Simuler les données qui pourraient sortir d'un logiciel de sondage | |||
format_large <- tibble( | |||
id = paste0("P", 1:n_observations), | |||
group = sample(c("Groupe A", "Groupe B"), n_observations, replace = TRUE), | |||
learner_interest = sample(1:7, n_observations, replace = TRUE), | |||
learner_motivation = sample(1:7, n_observations, replace = TRUE), | |||
others_interest = sample(1:7, n_observations, replace = TRUE), | |||
others_motivation = sample(1:7, n_observations, replace = TRUE), | |||
teacher_interest = sample(1:7, n_observations, replace = TRUE), | |||
teacher_motivation = sample(1:7, n_observations, replace = TRUE) | |||
) | |||
</source> | |||
En affichant seulement les 4 premiers lignes, vous allez obtenir des données similaires à celles-ci : | |||
{| class="wikitable" | |||
! width="2%" | id | |||
! width="7%" | group | |||
! width="14%" align="right" | learner_interest | |||
! width="16%" align="right" | learner_motivation | |||
! width="13%" align="right" | others_interest | |||
! width="15%" align="right" | others_motivation | |||
! width="14%" align="right" | teacher_interest | |||
! width="16%" align="right" | teacher_motivation | |||
|- | |||
| P1 | |||
| Groupe A | |||
| align="right" | 4 | |||
| align="right" | 5 | |||
| align="right" | 4 | |||
| align="right" | 7 | |||
| align="right" | 5 | |||
| align="right" | 2 | |||
|- | |||
| P2 | |||
| Groupe A | |||
| align="right" | 3 | |||
| align="right" | 2 | |||
| align="right" | 2 | |||
| align="right" | 5 | |||
| align="right" | 2 | |||
| align="right" | 1 | |||
|- | |||
| P3 | |||
| Groupe A | |||
| align="right" | 6 | |||
| align="right" | 7 | |||
| align="right" | 7 | |||
| align="right" | 3 | |||
| align="right" | 3 | |||
| align="right" | 2 | |||
|- | |||
| P4 | |||
| Groupe A | |||
| align="right" | 6 | |||
| align="right" | 1 | |||
| align="right" | 2 | |||
| align="right" | 5 | |||
| align="right" | 7 | |||
| align="right" | 4 | |||
|} | |||
Pour passer au format long, il faut d'abord décider quelles colonnes sont indispensables. Dans ce cas, elles sont toutes utiles, mais souvent lorsque vous exportez depuis un logiciel de sondage vous avez plusieurs colonnes qui vous n'intéressent pas. Vous pouvez à ce moment utiliser la fonction <code>select()</code> du [[Manipuler des données avec dplyr|paquet '''dplyr''']] pour retenir seulement les colonnes d'intérêt. | |||
Une fois déterminé les colonnes à maintenir, il faut décider quelles sont les colonnes qu'on veut ''faire tourner'', c'est-à-dire faire passer dans le format long. Dans ce cas, nous voulons avoir pour chaque ligne : | |||
# Le participant | |||
# Le group | |||
# La dimension évalué | |||
À ces trois colonnes, nous voulons en ajouter une quatrième qui consiste dans l'extraction de l'évaluation pour chaque dimension. Par conséquent, les dimensions que nous voulons ''pivoter'' sont les six dimensions qui vont de ''learner_interest'' à ''teacher_motivation''. Ces colonnes sont d'ailleurs celles qui contiennent les valeurs qui nous intéressent. Pour pivoter ces colonnes le code est le suivant : | |||
<source lang="R" line="" highlight="3""> | |||
# Transformation simple | |||
format_long_simple <- format_large |> | |||
pivot_longer(learner_interest:teacher_motivation) | |||
</source> | |||
Le code qui permet de faire la transformation se trouve ici à la ligne 3. Dans la fonction <code>pivot_longer()</code> on passe un argument avec les colonnes que nous voulons faire ''pivoter''. On utilise ici la notation colonne_initiale:colonne_finale pour ne pas avoir à les nommer individuellement. Le résultat de cette opération est un nouveau ''data.frame'' qui ressemble à celui-ci (limité au 12 premières lignes) : | |||
{| class="wikitable" | |||
! id | |||
! group | |||
! name | |||
! align="right" | value | |||
|- | |||
| P1 | |||
| Groupe A | |||
| learner_interest | |||
| align="right" | 4 | |||
|- | |||
| P1 | |||
| Groupe A | |||
| learner_motivation | |||
| align="right" | 5 | |||
|- | |||
| P1 | |||
| Groupe A | |||
| others_interest | |||
| align="right" | 4 | |||
|- | |||
| P1 | |||
| Groupe A | |||
| others_motivation | |||
| align="right" | 7 | |||
|- | |||
| P1 | |||
| Groupe A | |||
| teacher_interest | |||
| align="right" | 5 | |||
|- | |||
| P1 | |||
| Groupe A | |||
| teacher_motivation | |||
| align="right" | 2 | |||
|- | |||
| P2 | |||
| Groupe A | |||
| learner_interest | |||
| align="right" | 3 | |||
|- | |||
| P2 | |||
| Groupe A | |||
| learner_motivation | |||
| align="right" | 2 | |||
|- | |||
| P2 | |||
| Groupe A | |||
| others_interest | |||
| align="right" | 2 | |||
|- | |||
| P2 | |||
| Groupe A | |||
| others_motivation | |||
| align="right" | 5 | |||
|- | |||
| P2 | |||
| Groupe A | |||
| teacher_interest | |||
| align="right" | 2 | |||
|- | |||
| P2 | |||
| Groupe A | |||
| teacher_motivation | |||
| align="right" | 1 | |||
|} | |||
En regardant le code, on peut s'apercevoir que les noms des colonnes/variables qui viennent d'être créées véhiculent en réalité deux informations différentes : | |||
# L'''agent'' qui est visé (l'apprenant lui-même, les autres collègues, ou l'enseignante) | |||
# La dimension | |||
On peut exploiter les différents arguments mis à disposition par <code>pivot_longer()</code> pour améliorer le passage au format long. On peut en effet exploiter la régularité des patterns des noms des colonnes ''agent_dimension'' pour séparer en deux colonnes/variables : | |||
<source lang="R"> | |||
# Transformation plus complexe | |||
format_long_plus <- format_large |> | |||
pivot_longer( | |||
learner_interest:teacher_motivation, | |||
names_sep = "_", | |||
names_to = c("who", "dimension"), | |||
values_to = "rating" | |||
) | |||
</source> | |||
Le résultat se différencie du data.frame précédent justement par la division des colonnes, ainsi que pour le nom explicite de la colonne qui contient le valeur. Si on ne spécifie rien, cette colonne prend automatiquement le nom ''value''. Ici, nous spécifions le nom ''rating'' : | |||
{| class="wikitable" | |||
! id | |||
! group | |||
! who | |||
! dimension | |||
! align="right" | rating | |||
|- | |||
| P1 | |||
| Groupe A | |||
| learner | |||
| interest | |||
| align="right" | 4 | |||
|- | |||
| P1 | |||
| Groupe A | |||
| learner | |||
| motivation | |||
| align="right" | 5 | |||
|- | |||
| P1 | |||
| Groupe A | |||
| others | |||
| interest | |||
| align="right" | 4 | |||
|- | |||
| P1 | |||
| Groupe A | |||
| others | |||
| motivation | |||
| align="right" | 7 | |||
|- | |||
| P1 | |||
| Groupe A | |||
| teacher | |||
| interest | |||
| align="right" | 5 | |||
|- | |||
| P1 | |||
| Groupe A | |||
| teacher | |||
| motivation | |||
| align="right" | 2 | |||
|- | |||
| P2 | |||
| Groupe A | |||
| learner | |||
| interest | |||
| align="right" | 3 | |||
|- | |||
| P2 | |||
| Groupe A | |||
| learner | |||
| motivation | |||
| align="right" | 2 | |||
|- | |||
| P2 | |||
| Groupe A | |||
| others | |||
| interest | |||
| align="right" | 2 | |||
|- | |||
| P2 | |||
| Groupe A | |||
| others | |||
| motivation | |||
| align="right" | 5 | |||
|- | |||
| P2 | |||
| Groupe A | |||
| teacher | |||
| interest | |||
| align="right" | 2 | |||
|- | |||
| P2 | |||
| Groupe A | |||
| teacher | |||
| motivation | |||
| align="right" | 1 | |||
|} | |||
L'avantage de ce format peut être illustré par exemple dans la création d'une représentation graphique avec ggplot2, dont le code est le suivant : | |||
<source lang="R"> | |||
ggplot(format_long_plus, aes(x = group, y = rating, color = group)) + | |||
geom_jitter() + | |||
facet_grid(dimension ~ who) + | |||
theme(legend.position = "none") | |||
</source> | |||
Le résultat du graphique sera similaire à celui-ci (cela dépend aussi du thème qui est appliqué à ggplot2) : | |||
[[Fichier:Ggplot2 exemple format long.svg|500px|vignette|néant|Exemple de graphique qu'on peut obtenir assez ''facilement'' grâce au format long]] | |||
Le format long est très souvent utilisé dans des modélisations/analyses statistiques qui utilisent des modèles multi-niveaux, par exemple pour des mesures repétées ou des structures hiérarchiques (étudiant-es dans des classes, etc.). | |||
==== pivot_wider() ==== | |||
La fonction <code>pivot_wider()</code> fait le contraire de la fonction <code>pivot_longer()</code> illustrée plus haut : elle permet de construire un jeu de données ''classique'' à partir du format long. Le format classique est utilisé par quelques fonctions de [[R]], mais surtout par d'autres logiciels d'analyses statistiques comme [[SPSS]] ou [[Jamovi]]. De ce fait, il est parfois utile de mettre à disposition les données en ce format, même si un format long serait plus adapté au principes du ''Tidy data''. | |||
Le code suivant illustre l'utilisation de <code>pivot_wider()</code> : | |||
<source lang="R" line highlight="14"> | |||
library(tidyverse) | |||
format_long <- tribble( | |||
~participant, ~group, ~task, ~time, | |||
"P1", "Groupe A", "T1", 2.345, | |||
"P1", "Groupe A", "T2", 4.567, | |||
"P2", "Groupe B", "T1", 8.765, | |||
"P2", "Groupe B", "T2", 5.432, | |||
"P3", "Groupe A", "T1", 1.987, | |||
"P3", "Groupe A", "T2", 2.876 | |||
) | |||
format_large <-format_long |> | |||
pivot_wider(names_from = task, values_from = time) | |||
</source> | |||
Dans ce cas, les nouvelles colonnes sont nommées à partir de la variable/colonne ''task'' du format long. Les valeurs sont extrapolées depuis la colonne/variable ''time''. Le résultat est le suivant : | |||
{| class="wikitable" | |||
! participant | |||
! group | |||
! align="right" | T1 | |||
! align="right" | T2 | |||
|- | |||
| P1 | |||
| Groupe A | |||
| align="right" | 2.345 | |||
| align="right" | 4.567 | |||
|- | |||
| P2 | |||
| Groupe B | |||
| align="right" | 8.765 | |||
| align="right" | 5.432 | |||
|- | |||
| P3 | |||
| Groupe A | |||
| align="right" | 1.987 | |||
| align="right" | 2.876 | |||
|} | |||
== Organisation des données ''emboîtées'' == | |||
== Conclusion == | |||
== Ressources == | |||
[[Catégorie: R]] | [[Catégorie: R]] |
Dernière version du 11 janvier 2023 à 14:21
Cet article est en construction: un auteur est en train de le modifier.
En principe, le ou les auteurs en question devraient bientôt présenter une meilleure version.
Pensée computationnelle avec R | |
---|---|
⚐ à finaliser | ☸ débutant |
⚒ 2023/01/11 | |
Catégorie: R |
Introduction
Tidyr est un paquet de R faisant partie de l'écosystème Tidyverse qui permet d'organiser des données afin de faciliter la manipulation, la visualisation, ou la modélisation. Dans cet article, le terme organisation des données est une traduction limitative du correspondant tidy en anglais. Par organisation il faut en effet entendre plus en général les différentes actions qui permettent de préparer un ou plusieurs jeu de données : nettoyer les données, structurer les variables/colonnes, exclure les données manquantes, etc.
Cette page intègre des éléments techniques du fonctionnement du paquet tidyr avec des éléments théoriques et pratiques sur les principes du tidy data (Wickham, 2014) selon une perspective liée au parcours pensée computationnelle avec R.
Note sur la version
Cette page se réfère à la version 1.1.x
de Tidyr (voir versionnage sémantique). Les informations contenus abordent cependant des principes fondamentaux du tidy data et devraient par conséquent rester valides pour des versions successives.
Prérequis
L'article nécessite de connaissances de base de R, notamment au niveau des structures de données de type data.frame
ou tibble
(i.e. organisées en lignes et colonnes). La lecture préalable de l'article Introduction à Tidyverse est également recommandée.
Relation étroite avec dplyr
Le paquet tidyr met à disposition certaines fonctionnalités qui sont bien complétées par dplyr, un autre paquet de l'écosystème Tidyverse. Il existe un article consacré à ce paquet dans ce wiki :
Installation et chargement
tidyr est l'un des paquets qui composent l'écosystème Tidyverse. Il peut donc être installé deux deux manières :
- Paquet individuel
- Paquet global Tidyverse
Paquet tidyr individuel
Pour installer seulement le paquet tidyr, la commande est la suivante :
# Installation individuelle
install.packages("tidyr")
Pour utiliser le paquet il faudra à ce moment le charger :
library(tidyr)
Paquet global Tidyverse
Si vous installez le paquet global Tidyverse, tidyrest installé automatiquement.
# Installation de Tidyverse
install.packages("tidyverse")
L'installation de l'écosystème Tidyverse est conseillée, car tidyr peut s'intégrer facilement avec d'autres paquets de l'écosystème Tidyverse comme dplyr pour manipuer des données ou ggplot2 pour les visualiser.
Pour utiliser le paquet vous pouvez à ce moment choisir si :
- Charger seulement tidyr
library(tidyr)
- Charger tous les paquets de Tidyverse
library(tidyverse)
Voir Introduction à Tidyverse pour plus de détails.
Structures des données et Tidy data
Dans l'introduction à Tidyverse, les principes à la base du concept de tidy data (Wickham, 2014) ont déjà été abordés de manière conceptuelle. Dans cette section, les données tidy sont abordées, de manière plus pragmatique, en fonction de différentes structures de données possibles et aux avantages de pouvoir passer entre structures.
Organisation de données tidy
De manière très concrète, un jeu de données tidy est basé sur trois principes :
- Chaque colonne représente une variable. En général, une variable est l'opérationalisation d'un concept, c'est-à-dire une représentation quantifiable ou qualifiable d'un concept théorique.
- Chaque ligne représente une observation. En général, une observation se compose de plusieurs variables qui partagent un élément commun, par exemple le même participant.
- Chaque cellule - résultant du croisement entre colonnes et lignes - représente une et une seule valeur, c'est-à-dire la quantité ou qualité de la variable/colonne pour l'observation/ligne données.
Selon Wickham (ibid), toute autre organisation de données corresponde à messy data, c'est-à-dire le contraire du tidy. Plusieurs exemples de jeu de données qui ne sont pas tidy, mais qui sont assez fréquents en data science, sont disponibles dans la page Tidy data de la documentation officielle du paquet.
Situations qui nécessitent l'organisation des données
Les principes du tidy data sont assez simple à la base, mais il arrive souvent que des jeux de données ne les respectent pas pour différentes raisons, par exemple :
- Le jeu de données est mis à disposition par d'autres entités qui ont un système de gestion de données non-tidy
- Les données sont extraites depuis un outil (e.g. Qualtrics ou Limesurvey) qui aplatit toutes les données indépendamment de la structure sémantique
- Les données sont extraite de manière automatisée ou semi-automatisée et peuvent donc avoir une structure qui n'est pas très bien définie au départ (e.g. avec du web scraping, notamment du web scraping avec R)
- Les donnés sont distribuées dans plusieurs outils ou modalités de récolte différentes, avec des structures de données différentes
- On se rend compte à posteriori qu'une meilleure manière de structurer les données (e.g. pour les partager avec d'autres chercheurs) est possible et utile
- Certaines fonctions de R nécessitent d'un format de données spécifique (voir format large et format long plus bas dans la page)
Depuis ces exemples, on peut extrapoler deux typologies de situations qui nécessitent d'une (ré)organisation des données :
- Sémantique : les données sont pensées ou récoltées d'une manière qui n'est pas optimale
- Pragmatique : dans des phases successives à l'importation et l'organisation, par exemple dans la visualisation, la modélisation, ou la communication des résultats, des mécanismes internes à R nécessitent d'un format spécifique des données
Avantages d'utiliser tidyr pour organiser les données
Utiliser un langage de programmation pour le traitement des données permet déjà à la base de :
- Maintenir les données originales et agir sur des références symboliques aux données, sans donc modifier les données originales ;
- Garder trace de toutes les manipulations effectuées afin de pouvoir créer ou récréer le même flux au besoin ;
- Stocker les mêmes données dans des formats alternatifs, sans dupliquer la source primaire des données (e.g. le fichier avec les données) ;
tidyr met à disposition des mécanismes flexibles qui permettent d'organiser ou ré-organiser des jeux des données à travers des manipulations sémantiques adaptées à la philosophie tidy data.
Différents formats de données
Même en respectant les principes tidy, il existe plusieurs manière pour organiser le même jeu de données. Cette section propose une catégorisation basée sur deux types de formats qui peuvent se combiner :
- Format à plat ou emboîté
- Format large ou long
tidyr propose des fonctions qui permettent une grande flexibilité pour passer entre les différents formats et le reste de l'article sera organisé avec des exemples qui se réfèrent, entre autre, aux passages entre ces formats que nous présentons donc d'abord de manière conceptuelle et succincte.
Format à plat ou emboîté
Un format à plat correspond au format classique, souvent appelé tabulaire ou rectangulaire, des jeux de données qu'on peut notamment trouver dans les applications de type spreadsheet (Excel et équivalent). Structurellement, ces jeux de données ne sont pas forcément dans le format tidy, mais sont en général plus simple à visualiser et à manipuler, car ils maintiennent les deux dimensions classiques des colonnes et lignes, qu'on peut donc transformer respectivement en variables et observations.
Un format emboîté, ou nested en anglais, au contraire, organise les données sur plusieurs niveaux, comme par exemple dans les structures de type liste en r, dans le format JSON, ou encore dans une base de données NoSQL. Ce format est, d'une part, plus difficile à visualiser et manipuler ; mais de l'autre, permet parfois plus de flexibilité et de puissance. Cependant, la plupart des fonctions disponibles en R fonctionnemnt avec des structures rectangulaires, donc à plat, et il arrive donc assez souvent de devoir aplatir une structure emboîtée afin de pouvoir la visualiser, modéliser, ou afficher dans un document. Le processus inverse est également possible, ce qui permet notamment l'automatisation de certains processus sur des larges données, mais en général il s'agit de passages plus complexes qui dépassent le niveau introductif de cet article.
Format large ou long
La distinction entre format large et format long est métaphoriquement simple à saisir, mais conceptuellement plus compliquée. Il s'agit en effet de deux manières différents de penser aux données. La distinction s'applique également aux données à plat ou emboîtées, mais la distinction est souvent plus importante dans les données rectangulaires (à plat).
Dans l'introduction à Tidyverse, cette distinction à été montrée avec un jeu de données complet que ce soit en format large ou en format long. Ici nous montrons un cas différent, qui est assez fréquent avec les données, c'est-à-dire dans lequel il n'y a pas des données pour toutes les combinaisons entre variables/colonnes et observations/lignes. Le jeu de données suivant affiche les notes obtenus par 10 étudiant-es à trois cours différents (mathématique, histoire et français), où chaque étudiant-e n'a suivi qu'un seul cours (e.g. un cours à option entre les trois).
Dans le format large, ou chaque cours représente une variable/colonne, donc, il existe plusieurs données manquantes (représentée par NA en R) :
etu | math | fran | hist |
---|---|---|---|
Etu1 | 4.75 | NA | NA |
Etu2 | NA | 5.75 | NA |
Etu3 | 4.00 | NA | NA |
Etu4 | 5.25 | NA | NA |
Etu5 | NA | 3.25 | NA |
Etu6 | NA | NA | 3.25 |
Etu7 | NA | 5.00 | NA |
Etu8 | 5.75 | NA | NA |
Etu9 | NA | 3.25 | NA |
Etu10 | 4.25 | NA | NA |
Avec les mêmes données en format long, les trois observations/colonnes de chaque matière sont condensées dans une variable cours, et par conséquent chaque observation affiche l'étudiant-e, le cours spécifique, et la note obtenue :
etu | cours | note |
---|---|---|
Etu1 | math | 4.75 |
Etu2 | fran | 5.75 |
Etu3 | math | 4.00 |
Etu4 | math | 5.25 |
Etu5 | fran | 3.25 |
Etu6 | hist | 3.25 |
Etu7 | fran | 5.00 |
Etu8 | math | 5.75 |
Etu9 | fran | 3.25 |
Etu10 | math | 4.25 |
Les deux formats ont avantages et désavantages, surtout dans des contextes plus complexes par rapport au simple exemple illustrés ici. Indépendamment de la qualité intrinsèque de la structure, de plus, au niveau pragmatique il y a des fonctions de R qui requièrent, ou sont plus adaptées, avec l'un ou l'autre format. Donc la possibilité de passer de l'un à l'autre peut se révéler une nécessite et pas seulement une question de structuration optimale.
Organisation des données à plat
Les données à plat (aussi appelées rectangulaires ou tabulaires) représentent à présent le format le plus fréquent en sciences sociales, grâce à une organisation en ligne et colonnes qui est assez intuitive à la fois pour la visualisation et la modification de jeux de données de petite/moyenne taille.
Cette section propose une série d'exemple d'instructions pour améliorer la structure de données à plat, ou pour passer entre les formats large et long décrits plus haut dans l'article. Les exemples utilisent parfois des jeux de données qui sont disponibles directement dans le paquet tidyr.
Écarter les données manquantes
Une opération très fréquente dans l'organisation des données consiste à écarter les données manquantes. On utilise ici le terme écarter plutôt que supprimer, car les données manquantes peuvent souvent apporter de l'information qui est importante à retenir. Si, par exemple, dans un questionnaire/échelle qui nécessite les réponses à toutes les questions pour calculer une sorte d'indice agrégé, la plupart des participant-es ne répondent pas à la quatrième question, il y a probablement un problème avec cet item. Supprimer tout simplement les données manquantes véhicule une image irréversible. Au contraire, on peut les écarter de manière instrumentale au type d'analyse qu'on veut mener (e.g. utiliser l'indice agrégé), mais les maintenir quand même dans les données originales pour d'autres analyses ou juste pour rappel intrinsèque dans les données de la problématique avec l'item.
tidyr met à disposition plusieurs fonctions pour traiter des données manquantes. Nous verrons ici seulement les suivantes :
drop_na()
qui permet d'écarter les observations/lignes qui ont des valeurs manquantesreplace_na()
qui permet de substituer les valeurs NA de R avec des alternatives
drop_na()
La fonction drop_na()
permet d'écarter des données manquantes selon des critères de sélection. L'exemple suivant illustre le fonctionnement de cette fonction.
Créons d'abord un petit jeu de données qui représente les résultats d'un test qui se compose de trois questions auxquelles les participant-es peuvent répondre "Oui" ou "Non". Le jeu de données propose également un identifiant du participant et l'âge. Pour créer ce jeu de données, nous utilisons la fonction tribble()
de Tidyverse qui permet de créer des jeux de données par lignes plutôt que par colonnes (avec tibble()
:
results_test <- tribble(
~participant, ~age, ~item1, ~item2, ~item3,
"P1", 35, "Oui", "Non", "Oui",
"P2", NA, "Oui", "Oui", "Oui",
"P3", 22, "Non", "Non", NA,
"P4", 27, "Oui", NA, "Non",
"P5", NA, "Non", "Non", "Oui"
)
Pour faciliter la visualisation, voici les données dans une table Mediawiki :
participant | age | item1 | item2 | item3 |
---|---|---|---|---|
P1 | 35 | Oui | Non | Oui |
P2 | NA | Oui | Oui | Oui |
P3 | 22 | Non | Non | NA |
P4 | 27 | Oui | NA | Non |
P5 | NA | Non | Non | Oui |
On peut utiliser la fonction drop_na()
, sans aucun argument, pour écarter les observations qui présente au moins une valeur manquante dans toutes les variables du jeu de données :
results_test %>%
drop_na()
Cette instruction résulte dans un jeu de données qui se limite à une seule observation, celle du participant "P1", le seule à avoir répondu aux trois items et fourni son âge.
Cependant, les valeurs manquantes n'ont souvent pas toutes le même poids. Dans cet exemple, notamment, certains participants ont répondu aux items, mais n'ont pas fourni leur âge : une information qui peut-être considérée moins importante par les chercheurs. Par conséquent, on peut passer à la fonction drop_na()
des critères de sélection des variables en argument, comme dans l'exemple suivant :
results_test %>%
drop_na(item1:item3)
La notation item1:item3 équivaut à sélectionner toutes les variables/colonnes qui se trouvent entre item1 et item3, qui sont comprises dans la sélection. Dans cet exemple, il s'agit donc des variables item1, item2, et item3. Avec ce critère de sélection plus permissif, on peut retenir les observations des participants "P1", "P2" et "P5". On peut notamment utiliser une nouvelle référence symbolique pour ensuite agir seulement sur ces observations considérées complètes aux finalités des chercheurs :
complete_test <- results_test %>%
drop_na(item1:item3)
Les critères de sélection des variables/colonnes sont communs à plusieurs paquets de Tidyverse et sont traités de manière plus approfondie dans l'article de ce wiki sur le paquet dplyr:
replace_na()
Une autre opération assez fréquente avec les données manquantes consiste à les remplacer avec d'autres valeurs, par exemple 0 dans le cas de variables quantitatifs, ou avec des textes alternatifs, notamment pour des publications. Il faut néanmoins faire bien attention à ce type de substitution, car R est très sensible à la présence/absence de données, et la manière la plus sûre de stocker des valeurs manquantes est de maintenir le NA. Des conventions, comme par exemple insérer 9999 pour des données manquantes, sont donc en général à éviter, car elles risquent de fausser des calculs ou des manipulations ultérieures. D'ailleurs, insérer une valeur différente de NA risque également de modifier le type de données associés à la variable/colonne (par exemple d'un chiffre à une suite de caractères)
En tout cas, si on est certains de vouloir remplacer les NA avec d'autres éléments, alors la fonction replace_na()
accepte une liste avec la syntaxe suivante :
data %>%
replace_na(list(variable = nouvelle_valeur, autre_variable = nouvelle_valeur, ...))
Si on reprend l'exemple du jeu de données utilisé avec la fonction drop_na()
, on peut s'imaginer que pour afficher le jeu des données dans une contribution, les chercheurs veulent remplacer NA avec Non dévoilée dans la variable age. Le code pour effectuer cette transformation est le suivant :
results_test <- tribble(
~participant, ~age, ~item1, ~item2, ~item3,
"P1", 35, "Oui", "Non", "Oui",
"P2", NA, "Oui", "Oui", "Oui",
"P3", 22, "Non", "Non", NA,
"P4", 27, "Oui", NA, "Non",
"P5", NA, "Non", "Non", "Oui"
)
results_test %>%
replace_na(list(age = "Non dévoilée"))
Cette exemple illustre d'ailleurs le mécanisme de changement du type de données, car la variable sur l'âge a été convertie en suite de caractères (char) au lieu de chiffre.
Il existe des moyens pour modifier tous les NA d'un jeu de données sans spécifier à chaque fois la variable/colonne, mais ces instructions sont déconseillées pour éviter des résultats inattendus.
Séparer ou condenser des variables/colonnes
Une autre type d'opération qui peut se révéler utile dans l'organisation des données consiste à séparer deux ou plusieurs variables/colonnes, si une valeur propose différentes informations ; ou le contraire, c'est-à-dire condenser deux ou plusieurs variables/colonnes. tidyr met à disposition respectivement les fonctions :
separate()
pour créer deux ou plusieurs colonnes à partir d'une colonneunit()
pour condenser deux ou plusieurs colonnes dans une seule
separate()
Parfois, les jeux de données affiche dans une seule variable/colonne l'équivalente de deux ou plusieurs informations distincts. À ce moment, il est utile de pouvoir étaler le contenu sur deux ou plusieurs colonnes. La fonction separate()
permet cette opération en spécifiant des arguments qui permettent d'effectuer cette opération de manière assez simple.
Voici un exemple de jeu de données qui simule une série d'association de mots effectuée pour un participant. On peut imaginer que la personne qui a récolté les associations les ait mise dans une seule colonne d'un spreadsheet en les séparant par un trait d'union. Voici le code pour créer quelques exemples de ce type :
associations_mots <- tribble(
~participant, ~association,
"P1", "maison-vacance",
"P1", "voiture-rouge",
"P1", "pays-village",
"P1", "programmation-pensée computationnelle",
"P1", "émotion-vice-versa"
)
Il est plus utile est intéressant de pouvoir disposer du mot stimulus et de la réponse du participant dans deux colonnes différentes. La séparation peut se faire facilement pour les quatre première observations/lignes, mais la cinquième pose un problème car le trait d'union figure une fois en tant qu'élément séparateur, mais une autre fois en tant que caractère de la réponse "vice-versa". Le code suivant permet de séparer les variables en tenant compte de la double présence du trait d'union :
associations_mots %>%
separate(col = association, into = c("stimulus", "reponse"), sep = "-", extra = "merge")
L'instruction s'explique de la manière suivante :
- col identifie quelle variable/colonne contient plusieurs informations à séparer
- into utile un vecteur pour déterminer les nouveaux noms des colonnes à créer
- sep identifie le caractère de séparation en fonction du quel diviser le contenu
- merge permet de garder le deuxième trait d'union comme élément du contenu. Vous pouvez essayez de modifier le code avec les options warn ou drop pour voir la différence sur le résultat final
La fonction met à disposition d'autres mécanismes pour gérer des cas plus complexes. Vous pouvez consultez la référence directe de la fonction pour plus de détails.
unite()
La fonction unite()
permet de condenser deux ou plusieurs variables/colonnes en une seule. Un exemple classique concerne le stockage d'une date dans trois colonnes différentes : une pour le jour, une pour le mois, et une pour l'année. Le code suivant crée un petit jeu de données qui simule un journal de bord :
journal_bord <- tribble(
~jour, ~mois, ~annee, ~situation,
12, 9, 2020, "Tout va bien",
18, 10, 2020, "Il pleut",
25, 11, 2020, "Rien à signaler"
)
Avec la fonction unite()
, on peut condenser les trois relatives à la date dans une seule :
journal_bord %>%
unite(col = date, c(jour, mois, annee), sep = "-")
Le code effectue les passages suivants :
- D'abord on définir le nom de la nouvelle colonne avec l'attribut col = date
- Ensuite on spécifie le vecteur des variables/colonnes qui sont intéressées par l'union
- Enfin on définit si appliquer un séparateur, et dans ce cas quel(s) caractères utiliser
Ce type d'opération peut être ensuite complété avec la fonction mutate()
de dplyr pour convertir la colonne dans le format des dates en R :
# Ce code utilise la fonction mutate() de dplyr
journal_bord %>%
unite(col = date, c(jour, mois, annee), sep = "-") %>%
mutate(
date = as.Date(date, format="%d-%m-%Y")
)
Passer du format large au format long et vice-versa
Le passage du format large au format long et vice-versa sont deux opérations qui peuvent se révéler très utiles, voire indispensables, en tidyverse, et plus en général en R. En effet, selon le type de manipulations ultérieurs qu'on souhaite effectuer sur les données, il se peut que les fonctions spécifiques acceptent exclusivement, ou puissent bénéficier davantage, d'un format plutôt que de l'autre.
Le passage entre les deux formats n'est cependant pas seulement un stratagème technique. Il s'agit aussi d'un changement de structure, et par conséquent de perspective sur les données. À cet effet, surtout le passage du format large au format long peut se révéler plus difficile à imaginer, surtout au début.
Les deux fonctions que tidyr met à disposition pour la conversion des formats sont :
pivot_longer()
pour le passage du format large au format long. Cette fonction remplace la fonctiongather()
qu'on peut encore trouver dans quelques tutoriels ou code source plus datéspivot_wider()
pour le passage du format long au format large. Cette fonction remplace la fonctionspread()
qu'on peut également encore retrouver à certains endroits.
pivot_longer()
La fonction pivot_longer()
permet de passer du format large classique au format long qui peut être très utile, voire indispensable, pour des modélisation ou des représentation graphiques plus articulées.
Imaginons d'avoir des données qui sortent d'un logiciel de sondage comme Qualtrics ou Limesurvey dans lequel chaque participant a dû évaluer deux dimensions d'une formation en ligne : l'intérêt et la motivation. Le questionnaire lui demande d'évaluer chacune de dimension pour (1) soi-même, (2) pour le reste de la classe, et (3) pour l'enseignant-e.
Nous allons simuler ces données afin de pouvoir les manier et adapter selon vos éventuels besoins :
library(tidyverse)
# Déterminer le nombre d'observations
n_observations <- 20
# Simuler les données qui pourraient sortir d'un logiciel de sondage
format_large <- tibble(
id = paste0("P", 1:n_observations),
group = sample(c("Groupe A", "Groupe B"), n_observations, replace = TRUE),
learner_interest = sample(1:7, n_observations, replace = TRUE),
learner_motivation = sample(1:7, n_observations, replace = TRUE),
others_interest = sample(1:7, n_observations, replace = TRUE),
others_motivation = sample(1:7, n_observations, replace = TRUE),
teacher_interest = sample(1:7, n_observations, replace = TRUE),
teacher_motivation = sample(1:7, n_observations, replace = TRUE)
)
En affichant seulement les 4 premiers lignes, vous allez obtenir des données similaires à celles-ci :
id | group | learner_interest | learner_motivation | others_interest | others_motivation | teacher_interest | teacher_motivation |
---|---|---|---|---|---|---|---|
P1 | Groupe A | 4 | 5 | 4 | 7 | 5 | 2 |
P2 | Groupe A | 3 | 2 | 2 | 5 | 2 | 1 |
P3 | Groupe A | 6 | 7 | 7 | 3 | 3 | 2 |
P4 | Groupe A | 6 | 1 | 2 | 5 | 7 | 4 |
Pour passer au format long, il faut d'abord décider quelles colonnes sont indispensables. Dans ce cas, elles sont toutes utiles, mais souvent lorsque vous exportez depuis un logiciel de sondage vous avez plusieurs colonnes qui vous n'intéressent pas. Vous pouvez à ce moment utiliser la fonction select()
du paquet dplyr pour retenir seulement les colonnes d'intérêt.
Une fois déterminé les colonnes à maintenir, il faut décider quelles sont les colonnes qu'on veut faire tourner, c'est-à-dire faire passer dans le format long. Dans ce cas, nous voulons avoir pour chaque ligne :
- Le participant
- Le group
- La dimension évalué
À ces trois colonnes, nous voulons en ajouter une quatrième qui consiste dans l'extraction de l'évaluation pour chaque dimension. Par conséquent, les dimensions que nous voulons pivoter sont les six dimensions qui vont de learner_interest à teacher_motivation. Ces colonnes sont d'ailleurs celles qui contiennent les valeurs qui nous intéressent. Pour pivoter ces colonnes le code est le suivant :
# Transformation simple
format_long_simple <- format_large |>
pivot_longer(learner_interest:teacher_motivation)
Le code qui permet de faire la transformation se trouve ici à la ligne 3. Dans la fonction pivot_longer()
on passe un argument avec les colonnes que nous voulons faire pivoter. On utilise ici la notation colonne_initiale:colonne_finale pour ne pas avoir à les nommer individuellement. Le résultat de cette opération est un nouveau data.frame qui ressemble à celui-ci (limité au 12 premières lignes) :
id | group | name | value |
---|---|---|---|
P1 | Groupe A | learner_interest | 4 |
P1 | Groupe A | learner_motivation | 5 |
P1 | Groupe A | others_interest | 4 |
P1 | Groupe A | others_motivation | 7 |
P1 | Groupe A | teacher_interest | 5 |
P1 | Groupe A | teacher_motivation | 2 |
P2 | Groupe A | learner_interest | 3 |
P2 | Groupe A | learner_motivation | 2 |
P2 | Groupe A | others_interest | 2 |
P2 | Groupe A | others_motivation | 5 |
P2 | Groupe A | teacher_interest | 2 |
P2 | Groupe A | teacher_motivation | 1 |
En regardant le code, on peut s'apercevoir que les noms des colonnes/variables qui viennent d'être créées véhiculent en réalité deux informations différentes :
- L'agent qui est visé (l'apprenant lui-même, les autres collègues, ou l'enseignante)
- La dimension
On peut exploiter les différents arguments mis à disposition par pivot_longer()
pour améliorer le passage au format long. On peut en effet exploiter la régularité des patterns des noms des colonnes agent_dimension pour séparer en deux colonnes/variables :
# Transformation plus complexe
format_long_plus <- format_large |>
pivot_longer(
learner_interest:teacher_motivation,
names_sep = "_",
names_to = c("who", "dimension"),
values_to = "rating"
)
Le résultat se différencie du data.frame précédent justement par la division des colonnes, ainsi que pour le nom explicite de la colonne qui contient le valeur. Si on ne spécifie rien, cette colonne prend automatiquement le nom value. Ici, nous spécifions le nom rating :
id | group | who | dimension | rating |
---|---|---|---|---|
P1 | Groupe A | learner | interest | 4 |
P1 | Groupe A | learner | motivation | 5 |
P1 | Groupe A | others | interest | 4 |
P1 | Groupe A | others | motivation | 7 |
P1 | Groupe A | teacher | interest | 5 |
P1 | Groupe A | teacher | motivation | 2 |
P2 | Groupe A | learner | interest | 3 |
P2 | Groupe A | learner | motivation | 2 |
P2 | Groupe A | others | interest | 2 |
P2 | Groupe A | others | motivation | 5 |
P2 | Groupe A | teacher | interest | 2 |
P2 | Groupe A | teacher | motivation | 1 |
L'avantage de ce format peut être illustré par exemple dans la création d'une représentation graphique avec ggplot2, dont le code est le suivant :
ggplot(format_long_plus, aes(x = group, y = rating, color = group)) +
geom_jitter() +
facet_grid(dimension ~ who) +
theme(legend.position = "none")
Le résultat du graphique sera similaire à celui-ci (cela dépend aussi du thème qui est appliqué à ggplot2) :
Le format long est très souvent utilisé dans des modélisations/analyses statistiques qui utilisent des modèles multi-niveaux, par exemple pour des mesures repétées ou des structures hiérarchiques (étudiant-es dans des classes, etc.).
pivot_wider()
La fonction pivot_wider()
fait le contraire de la fonction pivot_longer()
illustrée plus haut : elle permet de construire un jeu de données classique à partir du format long. Le format classique est utilisé par quelques fonctions de R, mais surtout par d'autres logiciels d'analyses statistiques comme SPSS ou Jamovi. De ce fait, il est parfois utile de mettre à disposition les données en ce format, même si un format long serait plus adapté au principes du Tidy data.
Le code suivant illustre l'utilisation de pivot_wider()
:
library(tidyverse)
format_long <- tribble(
~participant, ~group, ~task, ~time,
"P1", "Groupe A", "T1", 2.345,
"P1", "Groupe A", "T2", 4.567,
"P2", "Groupe B", "T1", 8.765,
"P2", "Groupe B", "T2", 5.432,
"P3", "Groupe A", "T1", 1.987,
"P3", "Groupe A", "T2", 2.876
)
format_large <-format_long |>
pivot_wider(names_from = task, values_from = time)
Dans ce cas, les nouvelles colonnes sont nommées à partir de la variable/colonne task du format long. Les valeurs sont extrapolées depuis la colonne/variable time. Le résultat est le suivant :
participant | group | T1 | T2 |
---|---|---|---|
P1 | Groupe A | 2.345 | 4.567 |
P2 | Groupe B | 8.765 | 5.432 |
P3 | Groupe A | 1.987 | 2.876 |