Ce chapitre est en cours d’écriture.

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 à la vignette officielle https://cran.r-project.org/web/packages/data.table/vignettes/datatable-intro.html.

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 et sa fonction tbl_dt.

library(dtplyr)
iris_dt <- tbl_dt(iris)
class(iris_dt)
[1] "tbl_dt"     "tbl"        "data.table"
[4] "data.frame"

Le tableau de données est à la fois compatible avec data.table (et notamment sa syntaxe particulière des crochets) et les verbes de dplyr.

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]
    Sepal.Length Sepal.Width Petal.Length
 1:          4.9         3.0          1.4
 2:          4.7         3.2          1.3
 3:          4.6         3.1          1.5
 4:          4.6         3.4          1.4
 5:          4.4         2.9          1.4
 6:          4.9         3.1          1.5
 7:          4.8         3.4          1.6
 8:          4.8         3.0          1.4
 9:          4.3         3.0          1.1
10:          4.6         3.6          1.0
11:          4.8         3.4          1.9
12:          4.7         3.2          1.6
13:          4.8         3.1          1.6
14:          4.9         3.1          1.5
15:          4.9         3.6          1.4
16:          4.4         3.0          1.3
17:          4.5         2.3          1.3
18:          4.4         3.2          1.3
19:          4.8         3.0          1.4
20:          4.6         3.2          1.4
21:          4.9         2.4          3.3
22:          4.9         2.5          4.5
    Sepal.Length Sepal.Width Petal.Length
    Petal.Width    Species
 1:         0.2     setosa
 2:         0.2     setosa
 3:         0.2     setosa
 4:         0.3     setosa
 5:         0.2     setosa
 6:         0.1     setosa
 7:         0.2     setosa
 8:         0.1     setosa
 9:         0.1     setosa
10:         0.2     setosa
11:         0.2     setosa
12:         0.2     setosa
13:         0.2     setosa
14:         0.2     setosa
15:         0.1     setosa
16:         0.2     setosa
17:         0.3     setosa
18:         0.2     setosa
19:         0.3     setosa
20:         0.2     setosa
21:         1.0 versicolor
22:         1.7  virginica
    Petal.Width    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``{.pkg} 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
 [12] 4.8 4.8 4.3 5.8 5.7 5.4 5.1 5.7 5.1 5.4 5.1
 [23] 4.6 5.1 4.8 5.0 5.0 5.2 5.2 4.7 4.8 5.4 5.2
 [34] 5.5 4.9 5.0 5.5 4.9 4.4 5.1 5.0 4.5 4.4 5.0
 [45] 5.1 4.8 5.1 4.6 5.3 5.0 7.0 6.4 6.9 5.5 6.5
 [56] 5.7 6.3 4.9 6.6 5.2 5.0 5.9 6.0 6.1 5.6 6.7
 [67] 5.6 5.8 6.2 5.6 5.9 6.1 6.3 6.1 6.4 6.6 6.8
 [78] 6.7 6.0 5.7 5.5 5.5 5.8 6.0 5.4 6.0 6.7 6.3
 [89] 5.6 5.5 5.5 6.1 5.8 5.0 5.6 5.7 5.7 6.2 5.1
[100] 5.7 6.3 5.8 7.1 6.3 6.5 7.6 4.9 7.3 6.7 7.2
[111] 6.5 6.4 6.8 5.7 5.8 6.4 6.5 7.7 7.7 6.0 6.9
[122] 5.6 7.7 6.3 6.7 7.2 6.2 6.1 6.4 7.2 7.4 7.9
[133] 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)]
     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.

iris2[, .(espece = Species, aire_petal = Petal.Length * 
  Petal.Width)]
        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.

iris2[, .("Species", 3)]
        V1 V2
1: Species  3

Grouper les résults

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)]
   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.

iris2[, .(min_sepal_width = min(Sepal.Width), max_sepal_width = max(Sepal.Width), 
  n_observations = .N), by = Species]
      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.

iris2[, group := "A"]
iris2[Species == "virginica", group := "B"]
iris2[, n_obs_per_species := .N, by = Species]
iris2
     Sepal.Length Sepal.Width Petal.Length
  1:          5.1         3.5          1.4
  2:          4.9         3.0          1.4
  3:          4.7         3.2          1.3
  4:          4.6         3.1          1.5
  5:          5.0         3.6          1.4
 ---                                      
146:          6.7         3.0          5.2
147:          6.3         2.5          5.0
148:          6.5         3.0          5.2
149:          6.2         3.4          5.4
150:          5.9         3.0          5.1
     Petal.Width   Species group
  1:         0.2    setosa     A
  2:         0.2    setosa     A
  3:         0.2    setosa     A
  4:         0.2    setosa     A
  5:         0.2    setosa     A
 ---                            
146:         2.3 virginica     B
147:         1.9 virginica     B
148:         2.0 virginica     B
149:         2.3 virginica     B
150:         1.8 virginica     B
     n_obs_per_species
  1:                50
  2:                50
  3:                50
  4:                50
  5:                50
 ---                  
146:                50
147:                50
148:                50
149:                50
150:                50
iris2[, .N, by = group]
   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.

iris2[, .(petal_area = Petal.Width * Petal.Length, 
  Species)][, .(min_petal_area = min(petal_area)), 
  by = Species]
      Species min_petal_area
1:     setosa           0.11
2: versicolor           3.30
3:  virginica           7.50