10  Data Tables

Le package data.table a été conçu pour dépasser les limites des dataframes traditionnels, notamment dans le cadre de calculs intensifs ou de la manipulation de très grands volumes de données. Il est souvent préféré au tidyverse pour sa rapidité d’exécution, son efficacité en mémoire et sa syntaxe concise.

Ce que nous allons explorer constitue une première approche des bases. Pour aller plus loin et découvrir toutes les fonctionnalités de data.table, vous pouvez consulter la documentation officielle : https://rdatatable.gitlab.io/data.table.

10.1 Mise en route

La première étape pour utiliser data.table consiste à le charger

require(data.table)  # <-- Au préalable install.packages("data.table")

Nous allons ici poursuivre avec le même jeu de données que précédemment :

Taille <- c(167, 192, 173, 174, 172, 167, 171, 185, 163, 170)
Poids <- c(86, 74, 83, 50, 78, 66, 66, 51, 50, 55)
Prog <- c("Bac", "Bac", "Master", "Bac", "Bac", "Master", "Master", NA, "Bac", "Bac")
Sexe <- c("H", "H", "F", "H", "H", "H", "F", "H", "H", "H") |> factor()
df <- data.frame(Taille, Poids, Prog, Sexe)

Pour utiliser le package data.table, il faut d’abord convertir le dataframe df en un objet datatable à l’aide de la fonction as.data.table() :

dt <- as.data.table(df)
dt
    Taille Poids   Prog   Sexe
     <num> <num> <char> <fctr>
 1:    167    86    Bac      H
 2:    192    74    Bac      H
 3:    173    83 Master      F
 4:    174    50    Bac      H
 5:    172    78    Bac      H
 6:    167    66 Master      H
 7:    171    66 Master      F
 8:    185    51   <NA>      H
 9:    163    50    Bac      H
10:    170    55    Bac      H
str(dt)
Classes 'data.table' and 'data.frame':  10 obs. of  4 variables:
 $ Taille: num  167 192 173 174 172 167 171 185 163 170
 $ Poids : num  86 74 83 50 78 66 66 51 50 55
 $ Prog  : chr  "Bac" "Bac" "Master" "Bac" ...
 $ Sexe  : Factor w/ 2 levels "F","H": 2 2 1 2 2 2 1 2 2 2
 - attr(*, ".internal.selfref")=<externalptr> 

L’opération inverse — repasser d’une datatable à une dataframe classique — se fait simplement avec as.data.frame() :

df <- as.data.frame(dt)

Il est évidement possible de créer directement une datatable sans passer par un dataframe. Exemple :

dt <- data.table(Taille, Poids, Prog, Sexe)
# ou
dt <- data.table(
  Taille = c(167, 192, 173, 174, 172, 167, 171, 185, 163, 170),
  Poids = c(86, 74, 83, 50, 78, 66, 66, 51, 50, 55),
  Prog   = c("Bac", "Bac", "Master", "Bac", "Bac", "Master", "Master", NA, "Bac", "Bac"),
  Sexe   = c("H", "H", "F", "H", "H", "H", "F", "H", "H", "H") |> factor()
)

10.2 Syntaxe générale

La fonctionnement centrale du package data.table repose sur l’utilisation des crochets [...]. Dans un dataframe classique, ces crochets servent uniquement à sélectionner des lignes ou des colonnes. En revanche, appliqués à une datatable, ils permettent d’effectuer une large gamme d’opérations — filtrage, sélection, transformation, agrégation, création de variables — le tout dans une syntaxe compacte et performante. En pratique, presque toutes les manipulations courantes peuvent être réalisées directement à l’aide du syntaxe dt[...].

La forme générale de la syntaxe data.table est donnée par :

dt[i, j, by]

Cela peut se lire comme : “sur les lignes en i, j’applique les opérations en j, en regroupant par by”, où :

  • i : lignes à sélectionner (par numéro ou condition logique)
  • j : opérations à effectuer (sélection, calcul, transformation, etc)
  • by : groupes sur lesquels appliquer j.

Il est utile de noter qu’il n’est pas nécessaire d’utiliser le symbole $ à l’intérieur des crochets [...], ce qui permet d’écrire un code bien plus lisible.

Note

une datatable est, par définition, aussi une dataframe. Cela signifie que toutes les manipulations que nous avons vues jusqu’à présent — que ce soit avec les fonctionnalités de base en R ou celles du tidyverse — peuvent être appliquées aux datatables sans aucune adaptation particulière.

10.3 Sélectionner des lignes

Par numéro/position

dt[c(2, 3)]          # Sélectionner les lignes 2 et 3
dt[c(2, 3), ]        # Idem
dt[c(2, 3), , ]      # Idem

dt[-(4:10), ]        # Exclure les lignes de 4 à 10
Output
   Taille Poids   Prog   Sexe
    <num> <num> <char> <fctr>
1:    192    74    Bac      H
2:    173    83 Master      F
   Taille Poids   Prog   Sexe
    <num> <num> <char> <fctr>
1:    192    74    Bac      H
2:    173    83 Master      F
   Taille Poids   Prog   Sexe
    <num> <num> <char> <fctr>
1:    192    74    Bac      H
2:    173    83 Master      F
   Taille Poids   Prog   Sexe
    <num> <num> <char> <fctr>
1:    167    86    Bac      H
2:    192    74    Bac      H
3:    173    83 Master      F

Par condition logique (filtrage)

dt[Sexe == "H"]                            # Tous les hommes
dt[Poids > median(Poids)]                  # Individus avec un poids supérieur à la médiane
dt[Sexe == "H" & Poids > median(Poids)]    # Hommes avec poids > médiane
Output
   Taille Poids   Prog   Sexe
    <num> <num> <char> <fctr>
1:    167    86    Bac      H
2:    192    74    Bac      H
3:    174    50    Bac      H
4:    172    78    Bac      H
5:    167    66 Master      H
6:    185    51   <NA>      H
7:    163    50    Bac      H
8:    170    55    Bac      H
   Taille Poids   Prog   Sexe
    <num> <num> <char> <fctr>
1:    167    86    Bac      H
2:    192    74    Bac      H
3:    173    83 Master      F
4:    172    78    Bac      H
   Taille Poids   Prog   Sexe
    <num> <num> <char> <fctr>
1:    167    86    Bac      H
2:    192    74    Bac      H
3:    172    78    Bac      H

Quand on écrit dt[i], cela ne modifie pas l’objet dt. Cette syntaxe crée une copie temporaire contenant uniquement les lignes qui satisfont la condition i. Pour conserver cette sélection, il faut la réassigner à un nouvel objet ou à dt lui-même.

La composent i peut aussi etre utliser pour trier une datatable. Exemple :

# Trie par Taille croissante, puis Prog croissant
dt[order(Taille, Prog)]

# Trie par Taille croissante, puis par Prog décroissant (le signe - inverse l'ordre)
dt[order(Taille, -Prog)]
Output
    Taille Poids   Prog   Sexe
     <num> <num> <char> <fctr>
 1:    163    50    Bac      H
 2:    167    86    Bac      H
 3:    167    66 Master      H
 4:    170    55    Bac      H
 5:    171    66 Master      F
 6:    172    78    Bac      H
 7:    173    83 Master      F
 8:    174    50    Bac      H
 9:    185    51   <NA>      H
10:    192    74    Bac      H
    Taille Poids   Prog   Sexe
     <num> <num> <char> <fctr>
 1:    163    50    Bac      H
 2:    167    66 Master      H
 3:    167    86    Bac      H
 4:    170    55    Bac      H
 5:    171    66 Master      F
 6:    172    78    Bac      H
 7:    173    83 Master      F
 8:    174    50    Bac      H
 9:    185    51   <NA>      H
10:    192    74    Bac      H

10.4 Sélectionner des colonnes

Par numéro/position

dt[, c(2, 3)]       # Colonnes 2 et 3
dt[, -c(2, 3)]      # Toutes sauf les colonnes 2 et 3
Output
    Poids   Prog
    <num> <char>
 1:    86    Bac
 2:    74    Bac
 3:    83 Master
 4:    50    Bac
 5:    78    Bac
 6:    66 Master
 7:    66 Master
 8:    51   <NA>
 9:    50    Bac
10:    55    Bac
    Taille   Sexe
     <num> <fctr>
 1:    167      H
 2:    192      H
 3:    173      F
 4:    174      H
 5:    172      H
 6:    167      H
 7:    171      F
 8:    185      H
 9:    163      H
10:    170      H

Par nom

dt[, Poids]                       # Colonne Poids (résultat = vecteur)
dt[, .(Poids)]                    # Idem (résultat = datatable)

dt[, c("Poids", "Prog")]          # Plusieurs colonnes
dt[, .(Poids, Prog)]              # Idem (Syntaxe recommandée)

dt[, -c("Poids", "Prog")]         # Exclusion par nom
Output
 [1] 86 74 83 50 78 66 66 51 50 55
    Poids
    <num>
 1:    86
 2:    74
 3:    83
 4:    50
 5:    78
 6:    66
 7:    66
 8:    51
 9:    50
10:    55
    Poids   Prog
    <num> <char>
 1:    86    Bac
 2:    74    Bac
 3:    83 Master
 4:    50    Bac
 5:    78    Bac
 6:    66 Master
 7:    66 Master
 8:    51   <NA>
 9:    50    Bac
10:    55    Bac
    Poids   Prog
    <num> <char>
 1:    86    Bac
 2:    74    Bac
 3:    83 Master
 4:    50    Bac
 5:    78    Bac
 6:    66 Master
 7:    66 Master
 8:    51   <NA>
 9:    50    Bac
10:    55    Bac
    Taille   Sexe
     <num> <fctr>
 1:    167      H
 2:    192      H
 3:    173      F
 4:    174      H
 5:    172      H
 6:    167      H
 7:    171      F
 8:    185      H
 9:    163      H
10:    170      H

On peut évidemment combiner les deux opérations, càd sélectionner simultanément certaines lignes et certaines colonnes. Exemple :

# Poids des individus 2 et 3
dt[c(2, 3), .(Poids)]

# Tailles des hommes dont le poids est supérieur à la médiane
dt[Sexe == "H" & Poids > median(Poids), .(Taille)]

# Poids et Prog des hommes
dt[Sexe == "H", .(Poids, Prog)]
Output
   Poids
   <num>
1:    74
2:    83
   Taille
    <num>
1:    167
2:    192
3:    172
   Poids   Prog
   <num> <char>
1:    86    Bac
2:    74    Bac
3:    50    Bac
4:    78    Bac
5:    66 Master
6:    51   <NA>
7:    50    Bac
8:    55    Bac

10.5 Transformer des colonnes

La composante j d’une datatable ne sert pas uniquement à sélectionner des colonnes, mais permet aussi, contrairement aux dataframes classiques, d’effectuer des calculs sur celles-ci.

Une seule colonne :

On peut créer de nouvelles variables ou de transformer celles existantes via l’opérateur :=. Exemple :

dt[, V := 1:10]                    # Ajout de la colonne V
dt[, Prog := factor(Prog)]         # Transformer Prog en facteur
dt
    Taille Poids   Prog   Sexe     V
     <num> <num> <fctr> <fctr> <int>
 1:    167    86    Bac      H     1
 2:    192    74    Bac      H     2
 3:    173    83 Master      F     3
 4:    174    50    Bac      H     4
 5:    172    78    Bac      H     5
 6:    167    66 Master      H     6
 7:    171    66 Master      F     7
 8:    185    51   <NA>      H     8
 9:    163    50    Bac      H     9
10:    170    55    Bac      H    10

Contrairement à transform() ou dplyr::mutate(), qui retournent une copie modifiée du tableau, l’opérateur := de data.table modifie directement les données en mémoire, sans créer de copie intermédiaire. := ne renvoie aucun résultat, mais agit par référence directement sur l’objet original. Les changements sont donc permanents, sans qu’il soit nécessaire de réassigner le tableau. Cette approche est beaucoup plus rapide et efficace, notamment lorsqu’on manipule de très grands volumes de données.

Voici un autre exemple de l’utilisation de :=

# Ajouter "S = 1" dans les lignes où Poids > 70
dt[Poids > 70, S := 1]
dt
    Taille Poids   Prog   Sexe     V     S
     <num> <num> <fctr> <fctr> <int> <num>
 1:    167    86    Bac      H     1     1
 2:    192    74    Bac      H     2     1
 3:    173    83 Master      F     3     1
 4:    174    50    Bac      H     4    NA
 5:    172    78    Bac      H     5     1
 6:    167    66 Master      H     6    NA
 7:    171    66 Master      F     7    NA
 8:    185    51   <NA>      H     8    NA
 9:    163    50    Bac      H     9    NA
10:    170    55    Bac      H    10    NA

Plusieurs colonnes :

Pour affecter plusieurs colonnes à la fois, on utilise la syntaxe ':='(...). Exemple :

# Suppression + modification + création d'une nouvelle variable
dt[, ":="(
  S = NULL,
  V = -V,
  imc = Poids / (0.01 * Taille)^2
)]
dt
    Taille Poids   Prog   Sexe     V      imc
     <num> <num> <fctr> <fctr> <int>    <num>
 1:    167    86    Bac      H    -1 30.83653
 2:    192    74    Bac      H    -2 20.07378
 3:    173    83 Master      F    -3 27.73230
 4:    174    50    Bac      H    -4 16.51473
 5:    172    78    Bac      H    -5 26.36560
 6:    167    66 Master      H    -6 23.66524
 7:    171    66 Master      F    -7 22.57105
 8:    185    51   <NA>      H    -8 14.90139
 9:    163    50    Bac      H    -9 18.81892
10:    170    55    Bac      H   -10 19.03114

Les affectations multiples avec := sont évaluées simultanément, et non de manière séquentielle. Cela a pour conséquence qu’une variable nouvellement créée ne peut pas être utilisée immédiatement dans le calcul d’une autre au sein de la même instruction. Par exemple, le code suivant provoque une erreur :

dt[, ":="(
  S = 1,
  LS = log(S)
)]
Error in eval(jsub, SDenv, parent.frame()): object 'S' not found

10.6 Calculs et résumés sur les colonnes

Dans la composante j d’une datatable, il est possible d’utiliser toute expression R valide : calculs, affichages, graphiques, etc. Habituellement, on y place une seule instruction, mais si l’on souhaite en exécuter plusieurs à la suite, il faut les regrouper dans un bloc de code entouré d’accolades {}. Exemple :

dt[, {
  plot(Poids ~ Taille)    # Trace un graphique de Poids en fonction de Taille
  Poids + Taille          # La somme des colonnes Poids et Taille
}]
 [1] 253 266 256 224 250 233 237 236 213 225

Statistiques simples

# Moyenne de Poids
dt[, mean(Poids)]

# Moyenne et Poids et Taille
dt[, .(moyP = mean(Poids), moyT = mean(Taille))]

# Idem en utilisant la fontion lapply
1dt[, lapply(.(moyP = Poids, moyT = Taille), mean)]
1
La fonction lapply() fait partie de R de base. Elle permet d’appliquer une fonction (comme mean()) à chaque élément d’une liste ou d’un vecteur. Cette notion sera approfondie dans la partie dédiée à la programmation.
[1] 65.9
    moyP  moyT
   <num> <num>
1:  65.9 173.4
    moyP  moyT
   <num> <num>
1:  65.9 173.4

Statistiques agrégées via by

# Version 1 : Moyenne du poids par Sexe et Programme
dt[, .(mean = mean(Poids)), by = .(Sexe, Prog)]
     Sexe   Prog  mean
   <fctr> <fctr> <num>
1:      H    Bac  65.5
2:      F Master  74.5
3:      H Master  66.0
4:      H   <NA>  51.0
# Version 2 : Ajouter une colonne 'mean' contenant la moyenne du poids par sexe et programme
dt[, mean := mean(Poids), by = .(Sexe, Prog)]
dt
    Taille Poids   Prog   Sexe     V      imc  mean
     <num> <num> <fctr> <fctr> <int>    <num> <num>
 1:    167    86    Bac      H    -1 30.83653  65.5
 2:    192    74    Bac      H    -2 20.07378  65.5
 3:    173    83 Master      F    -3 27.73230  74.5
 4:    174    50    Bac      H    -4 16.51473  65.5
 5:    172    78    Bac      H    -5 26.36560  65.5
 6:    167    66 Master      H    -6 23.66524  66.0
 7:    171    66 Master      F    -7 22.57105  74.5
 8:    185    51   <NA>      H    -8 14.90139  51.0
 9:    163    50    Bac      H    -9 18.81892  65.5
10:    170    55    Bac      H   -10 19.03114  65.5

Dans by on peut aussi fournir une ou plusieurs conditions logiques. Exemple :

dt[, .(mean = mean(Poids)), by = (Pplus <- Poids > median(Poids))]
    Pplus     mean
   <lgcl>    <num>
1:   TRUE 80.25000
2:  FALSE 56.33333

10.7 Mots clés spéciaux de data.table

data.table met à disposition des mots clés spéciaux, appelés variables internes, utilisables à l’intérieur des crochets, qui facilitent les calculs et manipulations des données.

Par exemple, .N est une variable spéciale de data.table qui indique le nombre de lignes dans le tableau, ou dans chaque groupe lorsqu’un regroupement est effectué avec by. Exemple :

# Nombre total de lignes dans dt
dt[, .N]

# Dernière ligne
dt[.N]

# Lignes de la 8e à la dernière
dt[8:.N]

# Nombre de lignes par groupe de Sexe
dt[, .N, by = Sexe]

# Moyenne du Poids et effectif par combinaison de Sexe et Prog
dt[, .(mean = mean(Poids), effectif = .N), by = .(Sexe, Prog)]
[1] 10
   Taille Poids   Prog   Sexe     V      imc  mean
    <num> <num> <fctr> <fctr> <int>    <num> <num>
1:    170    55    Bac      H   -10 19.03114  65.5
   Taille Poids   Prog   Sexe     V      imc  mean
    <num> <num> <fctr> <fctr> <int>    <num> <num>
1:    185    51   <NA>      H    -8 14.90139  51.0
2:    163    50    Bac      H    -9 18.81892  65.5
3:    170    55    Bac      H   -10 19.03114  65.5
     Sexe     N
   <fctr> <int>
1:      H     8
2:      F     2
     Sexe   Prog  mean effectif
   <fctr> <fctr> <num>    <int>
1:      H    Bac  65.5        6
2:      F Master  74.5        2
3:      H Master  66.0        1
4:      H   <NA>  51.0        1

Une autre varaible interne fort utile est .SD souvant utlisée avec .SDcols. “SD” signifie “Subset of Data”. Par défaut, .SD contient toutes les colonnes à l’exception de celles dans by ou celles modifiées ou créées avec avec :=. .SDcols permet de spécifier explicitement quelles colonnes doivent être incluses dans .SD. Exemple :

# Toutes les colonnes
dt[, .SD]
    Taille Poids   Prog   Sexe     V      imc  mean
     <num> <num> <fctr> <fctr> <int>    <num> <num>
 1:    167    86    Bac      H    -1 30.83653  65.5
 2:    192    74    Bac      H    -2 20.07378  65.5
 3:    173    83 Master      F    -3 27.73230  74.5
 4:    174    50    Bac      H    -4 16.51473  65.5
 5:    172    78    Bac      H    -5 26.36560  65.5
 6:    167    66 Master      H    -6 23.66524  66.0
 7:    171    66 Master      F    -7 22.57105  74.5
 8:    185    51   <NA>      H    -8 14.90139  51.0
 9:    163    50    Bac      H    -9 18.81892  65.5
10:    170    55    Bac      H   -10 19.03114  65.5
# Sous-ensemble ciblé : Taille et Poids
dt[, .SD, .SDcols = c("Taille", "Poids")]
    Taille Poids
     <num> <num>
 1:    167    86
 2:    192    74
 3:    173    83
 4:    174    50
 5:    172    78
 6:    167    66
 7:    171    66
 8:    185    51
 9:    163    50
10:    170    55

Ces deux exemples sont donnés à titre purement illustratif et pédagogique. L’intérêt de .SD apparaît vraiment lorsqu’on souhaite appliquer des opérations sur plusieurs colonnes. Exemple :

# Moyenne de Taille et Poids
dt[, lapply(.SD, mean), .SDcols = c("Taille", "Poids")]

# Moyenne de toutes les colonnes numériques
dt[, lapply(.SD, mean), .SDcols = is.numeric]

# Moyenne de Taille et Poids par Sexe
dt[, lapply(.SD, mean), .SDcols = c("Taille", "Poids"), by = Sexe]

# Crée la colonne 'sum' contenant la somme ligne par ligne de Taille et Poids
dt[, sum := rowSums(.SD), .SDcols = c("Taille", "Poids")]
dt
Output
   Taille Poids
    <num> <num>
1:  173.4  65.9
   Taille Poids     V      imc  mean
    <num> <num> <num>    <num> <num>
1:  173.4  65.9  -5.5 22.05107  65.9
     Sexe Taille Poids
   <fctr>  <num> <num>
1:      H 173.75 63.75
2:      F 172.00 74.50
    Taille Poids   Prog   Sexe     V      imc  mean   sum
     <num> <num> <fctr> <fctr> <int>    <num> <num> <num>
 1:    167    86    Bac      H    -1 30.83653  65.5   253
 2:    192    74    Bac      H    -2 20.07378  65.5   266
 3:    173    83 Master      F    -3 27.73230  74.5   256
 4:    174    50    Bac      H    -4 16.51473  65.5   224
 5:    172    78    Bac      H    -5 26.36560  65.5   250
 6:    167    66 Master      H    -6 23.66524  66.0   233
 7:    171    66 Master      F    -7 22.57105  74.5   237
 8:    185    51   <NA>      H    -8 14.90139  51.0   236
 9:    163    50    Bac      H    -9 18.81892  65.5   213
10:    170    55    Bac      H   -10 19.03114  65.5   225

10.8 Enchaîner plusieurs opérations

il est possible d’enchaîner plusieurs opérations en utilisant les crochets [...] les uns après les autres selon la syntaxe :

dt[opération1][opération2][opération3]...

Chaque opération dans [] prend la datatable provenant de l’opération précédente et lui applique une nouvelle transformation. Le résultat final est le cumul de toutes ces transformations appliquées séquentiellement. Exemple :

dt[
1  Taille > median(Taille), .(Poids, Prog, Sexe)
][
2  , .(moyP = mean(Poids)), by = Sexe
][
3  order(Sexe)
]
1
Partir de dt, filtrer les lignes pour ne garder que les individus dont Taille est supérieure à sa médiane. On ne conserve que les colonnes Poids, Prog et Sexe.
2
Partir des données obtenues en (1), calculer le poids moyen par Sexe et enregistrer le résulta dans moyP.
3
Partir des données obtenues en (2), trier les lignes par ordre alphabétique de Sexe.
     Sexe  moyP
   <fctr> <num>
1:      F 83.00
2:      H 63.25