require(data.table)
knitr::opts_chunk$set(
  comment = "#",
    error = FALSE,
     tidy = FALSE,
    cache = FALSE,
 collapse = TRUE)
.old.th = setDTthreads(1)

Cette vignette traite de l'utilisation par défaut des fonctions de transformation melt (du format large au long) et dcast (du format long à large) pour les data.tables ainsi que des nouvelles fonctionnalités étendues de transformation melt et cast sur plusieurs colonnes disponibles depuis la version v1.9.6.


options(with = 100L)

Données

Nous chargerons les ensembles de données directement dans chaque section.

Introduction

Les fonctions melt et dcast pour data.table sont respectivement utilisées pour la restructuration de large en long et de long en large des données ; les implémentations sont spécifiquement conçues pour gérer de grandes quantités de données en mémoire (par exemple 10Go).

Dans cette vignette, nous allons

  1. Examiner brievement l'utilisation par défaut des fonctions de transformation melt et dcast sur les data.tables pour les convertir du format large au format long et vice versa

  2. Examiner des scénarios où les fonctionnalités actuelles deviennent fastidieuses et inefficaces.

  3. Enfin, explorer les nouvelles améliorations apportées aux méthodes melt et dcast pour les objets de type data.table afin de gérer plusieurs colonnes simultanément.

Les fonctionnalités étendues sont conformes à la philosophie de data.table qui consiste à effectuer des opérations de manière efficace et simple.

1. Fonctionnalité par défaut

a) Transformation (melt) des colonnes dans une data.table (format large vers long)

Supposons que nous ayons un data.table (données artificielles) comme indiqué ci-dessous :

s1 <- "family_id age_mother dob_child1 dob_child2 dob_child3
1         30 1998-11-26 2000-01-29         NA
2         27 1996-06-22         NA         NA
3         26 2002-07-11 2004-04-05 2007-09-02
4         32 2004-10-10 2009-08-27 2012-07-21
5         29 2000-12-05 2005-02-28         NA"
DT <- fread(s1)
DT

## dob signifie date de naissance.

str(DT)

- Convertir DT en format long où chaque dob est une observation séparée.

Nous pouvons réaliser ceci en utilisant melt() en spécifiant les arguments id.vars et measure.vars comme suit :

DT.m1 = melt(DT, id.vars = c("family_id", "age_mother"),
                measure.vars = c("dob_child1", "dob_child2", "dob_child3"))
DT.m1
str(DT.m1)

- Nommez les colonnes variable et value respectivement child et dob

DT.m1 = melt(DT, measure.vars = c("dob_child1", "dob_child2", "dob_child3"),
               variable.name = "child", value.name = "dob")
DT.m1

b) Transformation (dcast) des lignes (format long au large)

Dans la section précédente, nous avons vu comment passer de la forme large à la forme longue. Dans cette section, nous verrons l'opération inverse.

- Comment revenir à la table de données originale DT à partir de DT.m1 ?

En d'autres termes, nous aimerions collecter toutes les observations enfants correspondant à chaque family_id, age_mother dans la même ligne. Nous pouvons le faire en utilisant la fonction dcast comme suit :

dcast(DT.m1, family_id + age_mother ~ child, value.var = "dob")

- En partant de DT.m1, comment obtenir le nombre d'enfants dans chaque famille ?

Vous pouvez également passer une fonction d'agrégation dans dcast avec l'argument fun.aggregate. Ceci est particulièrement essentiel lorsque la formule fournie ne permet pas d'identifier une seule observation pour chaque cellule.

dcast(DT.m1, family_id ~ ., fun.agg = function(x) sum(!is.na(x)), value.var = "dob")

Voir ?dcast pour d'autres arguments utiles et des exemples supplémentaires.

2. Limitations des approches actuelles melt/dcast

Jusqu'à présent, nous avons vu des fonctionnalités de melt et dcast qui sont implémentées efficacement pour les objets data.table, en utilisant la machinerie interne de data.table (tri par base rapide, recherche binaire etc...).

Cependant, il existe des situations où l'opération souhaitée ne s'exprime pas de manière simple. Par exemple, considérons l'objet data.table présenté ci-dessous :

s2 <- "family_id age_mother dob_child1 dob_child2 dob_child3 gender_child1 gender_child2 gender_child3
1         30 1998-11-26 2000-01-29         NA             1             2            NA
2         27 1996-06-22         NA         NA             2            NA            NA
3         26 2002-07-11 2004-04-05 2007-09-02             2             2             1
4         32 2004-10-10 2009-08-27 2012-07-21             1             1             1
5         29 2000-12-05 2005-02-28         NA             2             1            NA"
DT <- fread(s2)
DT

## 1 = femme, 2 = homme

Et vous aimeriez combiner (avec melt) toutes les colonnes dob ensemble, ainsi que toutes les colonnes gender ensemble. Avec la fonctionnalité actuelle, nous pouvons faire quelque chose comme ceci :

DT.m1 = melt(DT, id = c("family_id", "age_mother"))
DT.m1[, c("variable", "child") := tstrsplit(variable, "_", fixed = TRUE)]
DT.c1 = dcast(DT.m1, family_id + age_mother + child ~ variable, value.var = "value")
DT.c1

str(DT.c1) ## la colonne 'gender' est un type de caractère maintenant !

Problèmes

  1. Ce que nous voulions faire était de combiner toutes les colonnes de type dob ensemble, et toutes les colonnes de type gender ensemble. Au lieu de cela, nous combinons tout, puis nous les scindons à nouveau. On voit aisément que c'est une approche détournée (et inefficace).

    Comme analogie, imaginez un placard avec quatre étagères de vêtements, et vous souhaitez rassembler les vêtements des étagères 1 et 2 (dans l'étagère 1), et ceux des étagères 3 et 4 (dans l'étagère 3). Ce que nous faisons, en quelque sorte, c'est de mélanger tous les vêtements ensemble, puis de les séparer à nouveau sur les étagères 1 et 3 !

  2. Les colonnes à transformer (melt) peuvent être de types différents, comme c'est le cas ici (types character et integer). En les transformant toutes ensemble avec melt, les colonnes seront forcées d'être du même type, comme l'explique le message d'avertissement ci-dessus, et on le voit dans la sortie de str(DT.c1), où la colonne gender a été convertie en type character.

  3. Nous générons une colonne supplémentaire en scindant la colonne variable en deux colonnes, dont l'utilité est plutôt obscure. Nous faisons cela parce que nous en avons besoin pour la transformation (cast) dans l'étape suivante.

  4. Enfin, nous transformons le jeu de données. Mais le problème est qu'il s'agit d'une opération beaucoup plus coûteuse en calcul que melt. En particulier, il faut calculer l'ordre des variables dans la formule, ce qui est coûteux.

En fait, stats::reshape est capable d'effectuer cette opération de manière très simple. C'est une fonction extrêmement utile et souvent sous-estimée. Vous devriez vraiment l'essayer !

3. (nouvelle) Fonctionnalité améliorée

a) melt améliorée

Puisque nous aimerions que data.table effectue cette opération de façon simple et efficace en utilisant la même interface, nous avons donc implémenté une fonctionnalité additionnelle, où nous pouvons appliquer la fonction melt sur plusieurs colonnes simultanément.

- Appliquer melt sur plusieurs colonnes simultanément

L'idée est assez simple. Nous passons une liste de colonnes à measure.vars, où chaque élément de la liste contient les colonnes qui doivent être combinées ensemble.

colA = paste0("dob_child", 1:3)
colB = paste0("gender_child", 1:3)
DT.m2 = melt(DT, measure = list(colA, colB), value.name = c("dob", "gender"))
DT.m2

str(DT.m2) ## le type de col est préservé

- Utilisation de patterns()

En général, dans ce type de problème, les colonnes que l'on souhaite transformer avec melt peuvent être distinguées par un motif commun. Nous pouvons utiliser la fonction patterns(), implémentée pour faciliter cette tâche, pour fournir des expressions régulières correspondant aux colonnes à combiner ensemble. L'opération ci-dessus peut alors être réécrite comme suit :

DT.m2 = melt(DT, measure = patterns("^dob", "^gender"), value.name = c("dob", "gender"))
DT.m2

- Utilisation de measure() pour spécifier measure.vars via un séparateur ou un motif

Si, comme dans les données ci-dessus, les colonnes d'entrée à transformer (melt) ont des noms réguliers, alors nous pouvons utiliser measure, qui permet de spécifier les colonnes à transformer via un séparateur ou une expression régulière. Par exemple, considérons les données iris,

(two.iris = data.table(datasets::iris)[c(1,150)])

Les données iris possèdent quatre colonnes numériques avec une structure régulière : d'abord la partie de la fleur, suivie d'un point, puis le type de mesure. Pour spécifier que nous voulons transformer (melt) ces quatre colonnes, nous pouvons utiliser measure avec sep="." ce qui signifie utiliser strsplit sur tous les noms de colonnes ; les colonnes qui résultent en un nombre maximum de groupes après division seront utilisées comme measure.vars :

melt(two.iris, measure.vars = measure(part, dim, sep="."))

Les deux premiers arguments de measure dans le code ci-dessus (part et dim) sont utilisés pour nommer les colonnes de sortie ; le nombre d'arguments doit être égal au nombre maximum de groupes après division avec sep.

Si nous voulons deux colonnes de valeurs, une pour chaque partie, nous pouvons utiliser le mot-clé spécial value.name, qui signifie produire une colonne de valeurs pour chaque nom unique trouvé dans ce groupe :

melt(two.iris, measure.vars = measure(value.name, dim, sep="."))

En utilisant le code ci-dessus, nous obtenons une colonne de valeurs par partie de fleur. Si nous voulons une colonne de valeurs pour chaque type de mesure, nous pouvons faire

melt(two.iris, measure.vars = measure(part, value.name, sep="."))

En revenant à l'exemple des données sur les familles et les enfants, nous pouvons voir une utilisation plus complexe de measure, impliquant une fonction utilisée pour convertir les valeurs de la chaîne child en entiers :

DT.m3 = melt(DT, measure = measure(value.name, child=as.integer, sep="_child"))
DT.m3

Dans le code ci-dessus, nous avons utilisé sep="_child", ce qui entraîne la transformation des colonnes uniquement si elle contiennent cette chaîne (six noms de colonnes séparés en deux groupes chacun). L'argument child=as.integer signifie que le second groupe donnera lieu à une colonne de sortie nommée child avec des valeurs définies en appliquant la fonction as.integer aux chaînes de caractères de ce groupe.

Enfin, nous considérons un exemple (emprunté au package tidyr) où nous devons définir les groupes à l'aide d'une expression régulière plutôt qu'un séparateur.

(who <- data.table(id=1, new_sp_m5564=2, newrel_f65=3))
melt(who, measure.vars = measure(
  diagnosis, gender, ages, pattern="new_?(.*)_(.)(.*)"))

Lorsque vous utilisez l'argument pattern, il doit s'agir d'une expression régulière compatible avec Perl contenant le même nombre de groupes de capture (sous-expressions entre parenthèses) que le nombre d'autres arguments (noms de groupes). Le code ci-dessous montre comment utiliser une expression régulière plus complexe avec cinq groupes, deux colonnes de sortie numériques et une fonction de conversion de type anonyme,

melt(who, measure.vars = measure(
  diagnosis, gender, age,
  ymin=as.numeric,
  ymax=function(y) ifelse(nzchar(y), as.numeric(y), Inf),
  pattern="new_?(.*)_(.)(([0-9]{2})([0-9]{0,2}))"
))

b) dcast améliorée

Parfait ! Nous pouvons maintenant transformer (melt) plusieurs colonnes simultanément. Maintenant, étant donné le jeu de données DT.m2, comment pouvons-nous revenir au même format que le jeu de données avec lequel nous avons commencé ?

Si nous utilisons la fonctionnalité actuelle de dcast, nous devrions effectuer la transformation via cast deux fois et combiner les résultats. Mais c'est une fois de plus verbeux, compliqué et inefficace.

- Transformation (cast) de plusieurs value.vars simultanément

Nous pouvons désormais fournir plusieurs colonnes value.var à dcast pour les objets data.table directement, de sorte que les opérations soient gérées en interne de manière efficace.

## nouvelle fonctionnalité 'cast' - plusieurs value.vars
DT.c2 = dcast(DT.m2, family_id + age_mother ~ variable, value.var = c("dob", "gender"))
DT.c2

Plusieurs fonctions pour fun.aggregate :

Vous pouvez également plusieurs fonctions à fun.aggregate dans dcast pour les data.tables. Consultez les exemples dans ?dcast qui illustrent cette fonctionnalité.

setDTthreads(.old.th)




Rdatatable/data.table documentation built on Nov. 20, 2024, 6:44 p.m.