L’extension data.table permets d’étendre les tableaux de données. Elle modifie radicalement la syntaxe des crochets, permettant un code plus court et surtout plus puissant. Par ailleurs, elle est particulièrement rapide pour opérer des opérations sur les données et permets d’effectuer des opérations par assignation directe sans avoir à copier les objets en mémoire. Autrement dit, elle est particulièrement utile lorsque l’on travaille sur des gros fichiers de données.

Certes, l’apprentissage de cette nouvelle syntaxe peut faire peur au début, mais c’est un gain tellement notable une fois qu’on la maîtrise, qu’il est difficile de revenir en arrière.

Pour un tutoriel (en anglais et en ligne) écrit par les développeurs de data.table, voir https://www.datacamp.com/courses/data-table-data-manipulation-r-tutorial. On pourra aussi se référer au site officiel et ses différentes vignettes (en anglais) : https://rdatatable.gitlab.io/.

Convertir un data.frame en data.table

Il suffit d’avoir recours à la fonction as.data.table.

library(data.table)
iris2 <- as.data.table(iris)
class(iris2)
[1] "data.table" "data.frame"

Comme on le voit, cela ajoute plusieurs classes additionnelles au tableau de données, celui-ci restant malgré tout toujours un data.frame. Cependant, la syntaxe des crochets simples [] change radicalement, tandis que les crochets doubles [[]] restent inchangés. Par contre, comme il s’agit toujours d’un tableau de données classique, on pourra l’utiliser avec les fonctions des autres extensions de R. Si jamais vous rencontriez un problème, il est toujours possible de reconvertir en tableau de données classique avec setDF (voir ci-dessous).

setDT et setDF

Lors de l’utilisation de as.data.table, le tableau de données original a d’abord été copié en mémoire, converti puis il a fallu le sauvegarder dans un objet avec <-. Lorsqu’on l’on manipule de gros tableaux, cela est gourmand en ressources système et prend du temps.

C’est pour cela que data.table fournie plusieurs fonctions (commençant parle préfixe set) qui modifient directement l’objet sélectionné en mémoire, ce qu’on appelle modification par assignation. Ce type de fonction est beaucoup plus rapide et efficace en termes de ressources système. On notera également qu’il est inutile de stocker le résultats dans un objet puisque l’objet a été modifié directement en mémoire.

setDT converti un tableaux de données en data.table tandis que setDF fait l’opération opposée.

setDT(iris)
class(iris)
[1] "data.table" "data.frame"
setDF(iris)
class(iris)
[1] "data.frame"

dplyr et data.table

Pour ceux travaillant également avec les extension dplyr et tibble, il est possible de concilier tibble et data.table avec l’extension dtplyr. Cette extension a connu une profonde évolution en 2019. Pour plus d’informations, voir https://dtplyr.tidyverse.org/.

La syntaxe des crochets

La syntaxe des crochets change radicalement avec data.table. Elle est de la forme objet[i, j, by] (dans sa forme la plus simple, pour une présentation exhaustive, voir le fichier d’aide de data.table-package).

Sélectionner des observations

Cela se fait en indiquant une indiquant une condition au premier argument, à savoir i. Si l’on ne procède à une sélection en même temps sur les variables, il n’est pas nécessaire d’indiquer de virgule , dans les crochets.

iris2[Sepal.Length < 5]

On notera que les noms indiquer entre les crochets sont évalués en fonction du contexte, en l’occurence la liste des variables de l’objet considéré. Ainsi, les noms des variables peuvent être indiqués tels quels, sans utilisation du symbole $ ni des guillemets.

Une différence de taille : lorsqu’il y a des observations pour lesquelles la condition indiquée en i renvoie NA, elles ne sont pas sélectionnées par data.table tandis que, pour un data.frame classique cela renvoie des lignes manquantes.

Sélectionner des variables

Pour sélectionner une variable, il suffit d’indiquer son nom dans la seconde partie, à savoir j. Noter la virgule qui permets d’indiquer que c’est une condition sur j et non sur i.

iris2[, Sepal.Length]
  [1] 5.1 4.9 4.7 4.6 5.0 5.4 4.6 5.0 4.4 4.9 5.4 4.8 4.8
 [14] 4.3 5.8 5.7 5.4 5.1 5.7 5.1 5.4 5.1 4.6 5.1 4.8 5.0
 [27] 5.0 5.2 5.2 4.7 4.8 5.4 5.2 5.5 4.9 5.0 5.5 4.9 4.4
 [40] 5.1 5.0 4.5 4.4 5.0 5.1 4.8 5.1 4.6 5.3 5.0 7.0 6.4
 [53] 6.9 5.5 6.5 5.7 6.3 4.9 6.6 5.2 5.0 5.9 6.0 6.1 5.6
 [66] 6.7 5.6 5.8 6.2 5.6 5.9 6.1 6.3 6.1 6.4 6.6 6.8 6.7
 [79] 6.0 5.7 5.5 5.5 5.8 6.0 5.4 6.0 6.7 6.3 5.6 5.5 5.5
 [92] 6.1 5.8 5.0 5.6 5.7 5.7 6.2 5.1 5.7 6.3 5.8 7.1 6.3
[105] 6.5 7.6 4.9 7.3 6.7 7.2 6.5 6.4 6.8 5.7 5.8 6.4 6.5
[118] 7.7 7.7 6.0 6.9 5.6 7.7 6.3 6.7 7.2 6.2 6.1 6.4 7.2
[131] 7.4 7.9 6.4 6.3 6.1 7.7 6.3 6.4 6.0 6.9 6.7 6.9 5.8
[144] 6.8 6.7 6.7 6.3 6.5 6.2 5.9

Pour sélectionner plusieurs variables, on fournira une liste définie avec list (et non un vecteur défini avec c).

iris2[, list(Sepal.Length, Sepal.Width)]

data.table fourni un raccourci pour écrire une liste : .(). A l’intérieur des crochets (mais pas en dehors), .() sera compris comme list().

iris2[, .(Sepal.Length, Sepal.Width)]

Il est possible de renommer une variable à la volée et même d’en calculer d’autres.

iris2[, .(espece = Species, aire_petal = Petal.Length * Petal.Width)]

Seul le retour est ici affecté. Cela n’impacte pas le tableau d’origine. Nous verrons plus loin comment créer / modifier une variable.

Attention : on ne peut pas directement sélectionner une variable par sa position ou en indiquant une chaîne de caractères. En effet, une valeur numérique ou textuelle est comprise comme une constante.

iris2[, .("Species", 3)]

Grouper les résultats

Si en j on utilise des fonctions qui à partir d’un vecteur renvoient une valeur unique (telles que mean, median, min, max, first, last, nth, etc.), on peut ainsi obtenir un résumé. On pourra également utiliser .N pour obtenir le nombre d’observations.

iris2[, .(min_sepal_width = min(Sepal.Width), max_sepal_width = max(Sepal.Width), n_observations = .N)]

Cela devient particulièrement intéressant en calculant ces mêmes valeurs par sous-groupe, grace au troisième paramètre : by.

iris2[, .(min_sepal_width = min(Sepal.Width), max_sepal_width = max(Sepal.Width), n_observations = .N), by = Species]

Ajouter / Modifier / Supprimer une variable

data.table introduit un nouvel opérateur := permettant de modifier une variable par assignation directe. Cela signifie que la modification a lieu directement en mémoire dans le tableau de données, sans qu’il soit besoin réaffecter le résultat avec <-.

On peut également combiner := avec une sélection sur les observations en i pour ne modifier que certaines observations. De même, le recours à by permets des calculs par groupe.

iris2[, group := "A"]
iris2[Species == "virginica", group := "B"]
iris2[, n_obs_per_species := .N, by = Species]
iris2
iris2[, .N, by = group]

Enchaîner les opérations

Il est possible d’enchaîner les opérations avec une succession de crochets.

iris2[, .(petal_area = Petal.Width * Petal.Length, Species)][, .(min_petal_area = min(petal_area)), by = Species]

Réorganiser un tableau

L’extension data.table fournie également deux fonctions, melt et dcast, dédiée à la réorganisation d’un tableau de données (respectivement wide-to-long reshaping et long-to-wide reshaping).

Pour plus de détails, voir la vignette dédiée sur le site de l’extension : https://rdatatable.gitlab.io/data.table/articles/datatable-reshape.html

Ressources en ligne