Manipulations avancées avec data.table
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)
<- as.data.table(iris)
iris2 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.
< 5] iris2[Sepal.Length
Sepal.Length Sepal.Width Petal.Length Petal.Width
1: 4.9 3.0 1.4 0.2
2: 4.7 3.2 1.3 0.2
3: 4.6 3.1 1.5 0.2
4: 4.6 3.4 1.4 0.3
5: 4.4 2.9 1.4 0.2
6: 4.9 3.1 1.5 0.1
7: 4.8 3.4 1.6 0.2
8: 4.8 3.0 1.4 0.1
9: 4.3 3.0 1.1 0.1
10: 4.6 3.6 1.0 0.2
11: 4.8 3.4 1.9 0.2
12: 4.7 3.2 1.6 0.2
13: 4.8 3.1 1.6 0.2
14: 4.9 3.1 1.5 0.2
15: 4.9 3.6 1.4 0.1
16: 4.4 3.0 1.3 0.2
17: 4.5 2.3 1.3 0.3
18: 4.4 3.2 1.3 0.2
19: 4.8 3.0 1.4 0.3
20: 4.6 3.2 1.4 0.2
21: 4.9 2.4 3.3 1.0
22: 4.9 2.5 4.5 1.7
Sepal.Length Sepal.Width Petal.Length Petal.Width
Species
1: setosa
2: setosa
3: setosa
4: setosa
5: setosa
6: setosa
7: setosa
8: setosa
9: setosa
10: setosa
11: setosa
12: setosa
13: setosa
14: setosa
15: setosa
16: setosa
17: setosa
18: setosa
19: setosa
20: setosa
21: versicolor
22: virginica
Species
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
).
list(Sepal.Length, Sepal.Width)] iris2[,
Sepal.Length Sepal.Width
1: 5.1 3.5
2: 4.9 3.0
3: 4.7 3.2
4: 4.6 3.1
5: 5.0 3.6
---
146: 6.7 3.0
147: 6.3 2.5
148: 6.5 3.0
149: 6.2 3.4
150: 5.9 3.0
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)]
Sepal.Length Sepal.Width
1: 5.1 3.5
2: 4.9 3.0
3: 4.7 3.2
4: 4.6 3.1
5: 5.0 3.6
---
146: 6.7 3.0
147: 6.3 2.5
148: 6.5 3.0
149: 6.2 3.4
150: 5.9 3.0
Il est possible de renommer une variable à la volée et même d’en calculer d’autres.
espece = Species, aire_petal = Petal.Length * Petal.Width)] iris2[, .(
espece aire_petal
1: setosa 0.28
2: setosa 0.28
3: setosa 0.26
4: setosa 0.30
5: setosa 0.28
---
146: virginica 11.96
147: virginica 9.50
148: virginica 10.40
149: virginica 12.42
150: virginica 9.18
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.
"Species", 3)] iris2[, .(
V1 V2
1: 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.
min_sepal_width = min(Sepal.Width), max_sepal_width = max(Sepal.Width), n_observations = .N)] iris2[, .(
min_sepal_width max_sepal_width n_observations
1: 2 4.4 150
Cela devient particulièrement intéressant en calculant ces mêmes valeurs par sous-groupe, grace au troisième paramètre : by
.
min_sepal_width = min(Sepal.Width), max_sepal_width = max(Sepal.Width), n_observations = .N), by = Species] iris2[, .(
Species min_sepal_width max_sepal_width
1: setosa 2.3 4.4
2: versicolor 2.0 3.4
3: virginica 2.2 3.8
n_observations
1: 50
2: 50
3: 50
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.
:= "A"]
iris2[, group == "virginica", group := "B"]
iris2[Species := .N, by = Species] iris2[, n_obs_per_species
iris2
Sepal.Length Sepal.Width Petal.Length Petal.Width
1: 5.1 3.5 1.4 0.2
2: 4.9 3.0 1.4 0.2
3: 4.7 3.2 1.3 0.2
4: 4.6 3.1 1.5 0.2
5: 5.0 3.6 1.4 0.2
---
146: 6.7 3.0 5.2 2.3
147: 6.3 2.5 5.0 1.9
148: 6.5 3.0 5.2 2.0
149: 6.2 3.4 5.4 2.3
150: 5.9 3.0 5.1 1.8
Species group n_obs_per_species
1: setosa A 50
2: setosa A 50
3: setosa A 50
4: setosa A 50
5: setosa A 50
---
146: virginica B 50
147: virginica B 50
148: virginica B 50
149: virginica B 50
150: virginica B 50
= group] iris2[, .N, by
group N
1: A 100
2: B 50
Enchaîner les opérations
Il est possible d’enchaîner les opérations avec une succession de crochets.
petal_area = Petal.Width * Petal.Length, Species)][, .(min_petal_area = min(petal_area)), by = Species] iris2[, .(
Species min_petal_area
1: setosa 0.11
2: versicolor 3.30
3: virginica 7.50
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
- Une introduction à data.table par Lino Galiana : https://linogaliana.netlify.app/post/datatable/datatable-intro/
- Un cours sur
data.table
: https://gitlab.com/linogaliana/bigr/-/blob/master/04-datatable.Rmd