Manipuler des données avec data.table {#datatable}

Tâches concernées et recommandations

L'utilisateur souhaite manipuler des données stucturées sous forme de data.frame (sélectionner des variables, sélectionner des observations, créer des variables, joindre des tables).

::: {.recommandation} Pour des tables de données de taille petite et moyenne (inférieure à 1 Go ou moins d'un million d'observations), il est recommandé d'utiliser le package dplyr ; Pour des tables de données de grande taille (plus de 1 Go ou plus d'un million d'observations), il est recommandé d'utiliser le package data.table qui fait l'objet de la présente fiche. :::

::: {.conseil} L'utilisation du package data.table peut paraître plus déroutante pour les débutants que l'utilisation de dplyr. Toutefois, l'apprentissage de data.table est particulièrement recommandé si vous avez l'intention d'utiliser R avec des données volumineuses car data.table est beaucoup plus rapide et puissant que dplyr. Des remarques et conseils sont présents dans cette fiche pour vous aider à vous familiariser avec la syntaxe de data.table. :::

Présentation de data.table

Ne pas oublier de charger le package avec library(data.table).

library(data.table)

Principes structurants

Le package data.table propose une version améliorée du data.frame de base : le data.table. La principale différence visible est que la visualisation d'un objet data.table est meilleure que celle d'un data.frame standard : le data.table indique automatiquement le type des variables (sous le nom de variable), et donne le nombre total d'observations de la table.

dt <- data.table(x = c("A", "B", "C"),
                 y = 1:12,
                 z = 3:6)
dt

La fonction fondamentale de data.table est l'opérateur [...] (crochets). Lorsqu'on les applique à un objet data.frame de base, les crochets df[...] servent uniquement à sélectionner des lignes ou des colonnes. Dans un data.table, les crochets dt[...] permettent de faire beaucoup plus de choses (quasiment tout, en pratique). En fait, les instructions à l'intérieur des crochets peuvent être envisagées comme des requêtes SQL mises en forme différemment.

La forme générale de l'opérateur [...] est la suivante : DT[i, j, by]. Cette grammaire peut se lire comme ceci : "on part du data.table DT, on sélectionne certaines lignes avec i, puis on calcule j pour chaque groupe défini par by. Si on fait un parallèle avec SQL, i correspond au WHERE, j au SELECT et by au GROUP BY. La fonction [...] présente deux grands avantages :

Voici un exemple simple. A partir des données générées ci-dessus, on veut calculer la moyenne de y par groupe défini par x, uniquement sur les observations pour lesquelles x est supérieur à 3. Voici comment on peut réaliser cette opération avec Base R, dplyr et data.table. Vous pouvez juger vous-même de la concision du code.

dt <- dt[order(z)]
**`Base R`** wzxhzdk:3
**`dplyr`** wzxhzdk:4
**`data.table`** wzxhzdk:5

Quelles fonctions peut-on utiliser avec un data.table ?

Les data.tables sont simplement des data.frames particuliers, donc on peut normalement leur appliquer toutes les méthodes valables pour les data.frames. En particulier, on peut utiliser avec data.table toutes les fonctions des packages habituellement associés à dplyr : stringr pour le maniement de chaînes de caractères, lubridate pour les colonnes temporelles, forcats pour les colonnes de type factor, etc. Toutefois, il est utile de vérifier que le package data.table ne propose pas déjà une fonction adaptée. Par exemple, plutôt que d'utiliser la fonction str_split_fixed() du package stringr pour séparer une colonne en fonction d'un caractère, on utilisera tstrsplit() de data.table.

Enchaîner les opérations en data.table

Le principe est simple...

Il est facile d'enchaîner des opérations avec data.table : il suffit d'accoler les opérateurs []. Votre code data.table prendra alors la forme suivante : dt[opération 1][opération 2][opération 3][...]. Voici un exemple simple, dans lequel on calcule la moyenne d'une variable par groupe, puis on trie la table.

# En chaînant
ans <- dt[ , .(moyenne = mean(y, na.rm = TRUE)), by = x][order(moyenne)]
ans

... mais il faut que le code reste lisible...

Le problème avec l'enchaînement d'opérations multiples est qu'on aboutit rapidement à des lignes de codes extrèmement longues. C'est pourquoi il est préférable de revenir régulièrement à la ligne, de façon à garder un code qui reste lisible. Il y a évidemment plusieurs façons d'organiser le code. La seule obligation est que le crochet qui commence une nouvelle opération doit être accolé au crochet qui termine l'opération précédente (...][...). Voici deux organisations possibles, à vous de choisir celle qui vous paraît la plus claire et la plus adaptée à votre travail.

La première organisation enchaîne toutes les opérations en une seule fois :

resultat <- 
  dt[i = ...,
     j = ...,
     by = ...
     ][i = ...,
       j = ...,
       by = ...
       ]

La seconde organisation sépare les opérations en utilisant une table intermédiaire nommée resultat :

resultat <- dt[i = ...,
               j = ...,
               by = ...
               ]
resultat <- resultat[i = ...,
                     j = ...,
                     by = ...
                     ]

Comme indiqué précédemment, i, j et by ne sont pas forcément présents dans toutes les étapes. Voici ce que cette organisation du code donne sur un exemple légèrement plus complexe que le précédent :

dt[ , total := y + z]
resultat <- dt[ ,
                .(moyenne = mean(total, na.rm = TRUE)),
                by = x
                ][order(moyenne)]
resultat

... car on peut facilement faire des erreurs

L'enchaînement des opérations en data.table est puissant, mais peut aboutir à des résultats non désirés si on ne fait pas attention. Les exemples de ce paragraphe utilisent la fonction := ; si vous ne la connaissez pas encore, il est fortement conseillé de lire la section [La fonction d'assignation par référence (ou :=)] avant de poursuivre la lecture.

Voici deux exemples d'opérations enchaînées en data.table dont les codes sont très similaires et qui aboutissent à des résultats très différents. Le premier exemple ne conserve qu'une partie de la table dt puis crée une variable, tandis que le second crée une variable avec une valeur non manquante pour une partie de la table uniquement.

wzxhzdk:14 ### Mettre des données dans un `data.table` Il y a principalement deux méthodes pour mettre des données sous forme d'un `data.table` : - la fonction `fread()` importe un fichier plat comme les `.csv` (voir la fiche [Importer des fichiers plats (`.csv`, `.tsv`, `.txt`)]) ; - Les fonctions `setDT()` et `as.data.table()` convertissent un `data.frame` en `data.table`. Dans la suite de cette section, on va illustrer les opérations de base en `data.table` avec la base permanente des équipements (table `bpe_ens_2018`), qu'on transforme en `data.table`. wzxhzdk:15 ### Manipuler une seule table avec `data.table` #### Sélectionner des lignes **On peut sélectionner des lignes dans un `data.table` avec `dt[i]`.** Voici un exemple de code qui sélectionne les magasins de chaussures (`TYPEQU == "B304"`) dans le premier arrondissement de Paris (`DEPCOM == "75101"`) dans la table `bpe_ens_2018_dt` : wzxhzdk:16 ::: {.remarque} Voici une remarque très importante sur le fonctionnement de `data.table` : **lorsqu'on souhaite conserver toutes les lignes d'un `data.table`, il faut laisser vide l'emplacement pour `i`, sans oublier la virgule.** Par exemple, pour connaître le nombre de lignes de `iris_dt`, on écrit : `iris_dt[ , .N]`. Notez bien l'emplacement vide et la virgule après `[`. ::: #### Sélectionner des colonnes **On peut sélectionner des colonnes dans un `data.table` et renvoyer un `data.table` de plusieurs façons**. - La première consiste à indiquer les colonnes à conserver sous forme de liste. La notation `.()` est un alias pour `list()` qui est pratique et concis dans un code `data.table`. Le code suivant sélectionne le code commune, le type d’équipement et le nombre d’équipement dans la base permanente des équipements : wzxhzdk:17 - La seconde méthode consiste à utiliser un mot-clé de `data.table`, `.SD` qui signifie `Subset of Data`. On indique les colonnes qui seront aliasées par `.SD` avec la dimension `.SDcols`. wzxhzdk:18 ::: {.remarque} La seconde méthode peut vous sembler inutilement complexe. C'est vrai dans l'exemple donné ci-dessus, mais les fonctions `.SD` et `.SDcols` s'avèrent très puissantes dans un grand nombre de situations (notamment quand on veut programmer des fonctions qui font appel à `data.table`). ::: #### Trier un `data.table` **On peut trier un `data.table` avec la fonction `order()`.** Le code suivant trie la BPE selon le code commune et le type d’équipement. wzxhzdk:19 Il suffit d'ajouter un signe `-` devant une variable pour trier par ordre décroissant. Le code suivant trie la BPE par code commune croissant et type d’équipement décroissant. wzxhzdk:20 #### Calculer des statistiques **La méthode pour sélectionner des colonnes est également valable pour calculer des statistiques, car `data.table` accepte les expressions dans `j`.** Le code suivant calcule le nombre total d’équipements dans la BPE (`sum(NB_EQUIP, na.rm = TRUE`) : wzxhzdk:21 Il est possible de calculer plusieurs statistiques à la fois, et de donner des noms aux variables ; il suffit de séparer les formules par une virgule. Le code suivant calcule le nombre total d’équipements dans la BPE (`sum(NB_EQUIP, na.rm = TRUE)`), et le nombre total de boulangeries (`sum(NB_EQUIP * (TYPEQU == "B203"), na.rm = TRUE)`). wzxhzdk:22 **On peut évidemment combiner `i` et `j` pour calculer des statistiques sur un sous-ensemble d'observations.** Dans l'exemple suivant, on sélectionne les boulangeries avec `i` (`(TYPEQU == "B203")`), et on calcule le nombre total d'équipements avec `j` (`sum(NB_EQUIP, na.rm = TRUE)`). wzxhzdk:23 #### Les fonctions statistiques utiles de `data.table` Vous pouvez utiliser toutes les fonctions statistiques de `R` avec `data.table`. Le *package* `data.table` propose par ailleurs des fonctions optimisées qui peuvent vous être utiles. En voici quelques-unes : | Fonction | Opération | Exemple | |-----------------|---------------------------------------------------|-------------------------------------------------------------| |`.N` | Nombre d'observations | `dt[ , .N, by = 'group_var']` | |`uniqueN()` | Nombre d'observations uniques de la variable `x` | `dt[ , uniqueN(x), by = 'group_var']` | |`%in%` | Nombre dans la liste | `dt[x %in% 1:5]` | |`%chin%` | Chaîne de caractères dans la liste | `dt[x %chin% c("a", "b")]` | |`%between%` | Valeur entre deux nombres | `dt[x %between% c(5,13)]` | | `%like%` | Reconnaissance d'une chaîne de caractères | `dt[departement %like% "^Haute"]` #### Opérations par groupe **Toutes les opérations précédentes peuvent être réalisées par groupe.** il suffit d'ajouter le nom des variables de groupe dans `by` (c'est l'équivalent du `group_by()` du _package_ `dplyr`). Lorsqu'il y a plusieurs variables de groupe, on peut écrire l'argument `by` de deux façons : - soit `by = c("var1", "var2", "var3")` (attention aux guillemets) ; - soit `by = .(var1, var2, var3)` (attention à la notation `.()`). Le code suivant groupe les données de la BPE par département (`by = .(DEP)`) puis calcule le nombre total d’équipements (`sum(NB_EQUIP, na.rm = TRUE)`) et le nombre total de boulangeries (`sum(NB_EQUIP * (TYPEQU == "B203"), na.rm = TRUE)`). wzxhzdk:24 ::: {.remarque} L'argument `by` fonctionne également avec l'opérateur `:=`. Vous pouvez en apprendre davantage sur l'usage de cet opérateur dans la partie [La fonction d'assignation par référence (ou `:=`)]. ::: ### Joindre des tables avec `data.table` Pour joindre des données, `data.table` propose une fonction `merge()` plus rapide que la fonction de base. La syntaxe générale est `z <- merge(x, y, [options])`. Voici une liste des principales options (les autres options sont visibles avec `?data.table::merge`) : | **Option** | **Signification** | |-------------------------------------------|--------------------------------------------------------------------------| | `by = var_jointure` | Joindre sur la variable `var_jointure` (présente dans `x` et dans `y`) | | `by.x = "identx", by.y = "identy"` | Joindre sur la condition `identx == identy` | |`all.x = TRUE` | *Left join* (garder toutes les lignes de `x`) | |`all.y = TRUE` | *Right join* (garder toutes les lignes de `y`) | |`all = TRUE` | *Full join* (garder toutes les lignes de `x` et de `y`) | Enfin, il est possible de réalier des jointures plus sophistiquées avec `data.table`. Ces méthodes sont présentées dans la [vignette sur le sujet](https://rstudio-pubs-static.s3.amazonaws.com/52230_5ae0d25125b544caab32f75f0360e775.html). ### Indexer une table avec `data.table` **L'indexation est une fonctionalité très puissante pour accélérer les opérations sur les lignes (filtres, jointures, etc.) en `data.table`**. Pour indexer une table il faut déclarer les variables faisant office de clé (appelées `key`). C'est possible de la manière suivante : `setkey(dt, a)` ou `setkeyv(dt, "a")`. Le `data.table` sera réordonné en fonction de cette variable et l'algorithme de recherche sur les lignes sera ainsi beaucoup plus efficace. Lorsqu'il y a plusieurs variables-clé, on écrit `setkey(dt, a, b)` ou `setkeyv(dt, c("a","b"))`. Pour savoir si `un data.table` est déjà indexé, on peut exécuter la commande `key(dt)` qui renvoie le nom des clés s'il y en a, et `NULL` sinon. ::: {.conseil} **L'exécution de la fonction `data.table::setkey()` peut prendre un peu de temps** (parfois quelques minutes sur une table de plus de 10 millions de lignes), car `data.table` trie toute la table en fonction des variables-clé. Toutefois, c'est une étape vraiment utile car elle accélère considérablement les opérations ultérieures sur les lignes. Il est vivement recommandé de l'utiliser si une ou plusieurs variables vont régulièrement servir à filtrer ou combiner des données. Pour aller plus loin, voir cette [vignette](https://cran.r-project.org/web/packages/data.table/vignettes/datatable-keys-fast-subset.html). ::: ### Réorganiser les données en `data.table` **Le *package* `data.table` permet de réorganiser facilement une table de données avec les fonctions `dcast()` et `melt()`.** La fonction `melt()` réorganise les donnée dans un format `long`. La fonction `dcast()` réorganise les donnée dans un format `wide`. | **`melt()`** | **`dcast()`** | | :--------------------------------------------------: | :----------------------------------------------: | | Réorganiser les donnée dans un format `long` | Réorganise les donnée dans un format `wide` | |![](./pics/datatable/widetolong.png){width=55%} |![](./pics/datatable/longtowide.png){width=62%} | #### `melt` : transformer des colonnes en lignes La fonction `melt()` réorganise les donnée dans un format long. Elle prend les arguments suivants : * `data` : les données ; * `id.vars` : les variables qui identifient les lignes de table d'arrivée ; elles restent inchangées lors de l'utilisation de `melt()` ; * `measure.vars` : les variables qui sont transposées ; * `variable.name` : le nom de la nouvelle colonne qui contient le nom des variables transposées ; * `value.name` : le nom de la nouvelle colonne qui contient la valeur des variables transposées. Pour illustrer l'usage de cette fonction, nous allons utiliser les données du répertoire Filosofi 2016 agrégées au niveau des EPCI (table `filosofi_epci_2016`), et disponibles dans le package `doremifasolData`. On convertit cette table en `data.table` et on conserve uniquement certaines variables. wzxhzdk:25 Nous allons restructurer cette table pour obtenir une nouvelle table, avec une observation par EPCI et par tranche d’âge. Voici le code qui permet d’obtenir cette table : on indique dans `measure.vars` le nom des colonnes qui seront transposées, le nom des colonnes transposées sera indiqué dans la nouvelle colonne “tranche_age” (`variable.name = "tranche_age"`) et les valeurs des colonnes transposées seront stockées dans la colonne “taux_pauvrete” (`value.name = "taux_pauvrete"`). wzxhzdk:26 ::: {.conseil} **Il est recommandé de travailler avec des données en format `long` plutôt qu'en format `wide`, notamment lorsque vous voulez faire des graphiques.** En effet, le _package_ de visualisation graphique `ggplot2` est optimisé pour manipuler des données en format `long` (voir la fiche [Faire des graphiques avec `ggplot2`]). Ce conseil est particulièrement important si vous voulez représenter un graphique avec des groupes : il est préférable que les groupes soient empilés (format `long`) plutôt que juxtaposés (format `wide`), car le code est plus rapide et facile à écrire. ::: #### `dcast` : transformer des lignes en colonnes La fonction `dcast()` réorganise les donnée dans un format large. Elle prend les arguments suivants : * `data` : les données ; * `formula` : une formule de la forme `var_ligne ~ var_colonne` qui définit la structure de la nouvelle table ; - s'il y a plusieurs variables, la formule prend la forme `var1 + var2 ~ var3` ; - `dcast()` conserve une ligne par valeur de la partie gauche, et crée (au moins) une colonne par valeur de la partie droite ; * `fun.aggregate` : une liste contenant la ou les fonction(s) utilisées pour agréger les données le cas échéant ; exemple : `list(mean, sum, sd)` ; * `value.var` : un vecteur contenant le nom de la ou des colonne(s) dont les valeurs vont être transposées ; exemple : `c("var1", "var2")`. Dans l'exemple qui suit, on réorganise la table `bpe_ens_2018_dt` de façon à obtenir une table qui contient une ligne par type d'équipement et une colonne par région (`TYPEQU ~ REG`). Ces colonnes vont contenir la somme (`fun.aggregate = sum`) du nombre d'équipements (`value.var = "NB_EQUIP"`). wzxhzdk:27 **Il est possible d'utiliser `dcast()` avec plusieurs variables à transposer et plusieurs fonctions pour transposer.** Dans l'exemple qui suit, on obtient une ligne par type d'équipement, et une colonne par région et par fonction d'agrégation (`mean` et `sum`). wzxhzdk:28 ::: {.conseil} * La fonction `dcast()` crée une colonne par valeur des variables utilisées dans la partie droite de la formule (trois colonnes pour les trois valeurs de `cyl` dans l'exemple qui précède). **Il faut donc faire attention à ce que ces variables aient un nombre limité de valeurs**, pour ne pas obtenir une table *extrêmement* large. On peut éventuellement discrétiser les variables continues, ou regrouper les modalités avant d'utiliser `dcast()`. * On peut obtenir des **noms de colonnes peu significatifs** lorsqu'on utilise `dcast()` avec une fonction d'agrégation (`01`, `02`, `03`... dans l'exemple précédent). Il est conseillé de modifier légèrement la partie droite de la formule pour obtenir des noms plus significatifs. Voici un exemple où on ajoute le préfixe `resultat_region` : ```r bpe_ens_2018_wide2 <- dcast(bpe_ens_2018_dt, TYPEQU ~ paste0("resultat_region",REG), value.var = "NB_EQUIP", fun.aggregate = sum) head(bpe_ens_2018_wide2) ``` ::: ::: {.remarque} **Il est conseillé de bien réfléchir avant de restructurer des données en format *wide*, et de ne le faire que lorsque cela paraît indispensable**. En effet, s'il est tentant de restructurer les données sous format *wide* car ce format peut paraître plus intuitif, il est généralement plus simple et plus rigoureux de traiter les données en format *long*. Ceci dit, il existe des situations dans lesquelles il est indiqué de restructurer les données en format *wide*. Voici deux exemples : * produire un tableau synthétique de résultats, prêt à être diffusé, avec quelques colonnes donnant des indicateurs par catégorie (exemple : la table `filosofi_epci_2016` du _package_ `doremifasolData`) ; * produire une table avec une colonne par année, de façon à calculer facilement un taux d'évolution entre deux dates. ::: ## La fonction d'assignation par référence (ou `:=`) Jusqu'à présent, mous avons manipulé un `data.table` existant, mais nous ne lui avons pas ajouté de nouvelles colonnes. Pour ce faire, nous allons utiliser la fonction `:=` (qui s'appelle "*assignation par référence*" et qui peut également s'écrire `` `:=`() ``). Voici comment on crée une nouvelle colonne dans `bpe_ens_2018_dt` : wzxhzdk:29 ### La spécificité de `data.table` : la modification par référence **A première vue, on peut penser que la fonction `:=` est l'équivalent de la fonction `dplyr::mutate()` dans la grammaire `data.table`.** C'est vrai dans la mesure où elle permet de faire des choses similaires, mais il faut garder en tête que son fonctionnement est complètement différent de celui de `dplyr::mutate()`. En effet, **la grande spécificité de `data.table` par rapport à `dplyr` est que l'utilisation de la fonction `:=` modifie directement la table de données**, car `data.table` fonctionne sur le principe de la modification par référence (voir [ce lien](https://gitlab.com/linogaliana/bigr/-/blob/master/04-datatable.Rmd) pour plus de détails). Cela signifie en pratique qu'**il ne faut pas réassigner l'objet lorsqu'on modifie une de ses colonnes**. C'est ce comportement qui permet à `data.table` d'être très rapide et très économe en mémoire vive, rendant son usage approprié pour des données volumineuses. Pour créer une colonne, on écrit donc directement `dt[ , nouvelle_colonne := une_formule]` et non `dt <- dt[ , nouvelle_colonne := une_formule]`. Voici un exemple qui compare `dplyr` et `data.table` :
```{asis, eval=(knitr::is_html_output()), echo=TRUE} Exemple 1 wzxhzdk:10 wzxhzdk:11 wzxhzdk:12
```{asis, eval=(knitr::is_html_output()), echo=TRUE} **Signification** wzxhzdk:13 ```{asis, eval=(knitr::is_html_output()), echo=TRUE} Partir de `dt`, créer une nouvelle variable `newvar` qui vaut 1 pour les observations pour lesquelles `x > 3` et `NA` ailleurs
_Package_ Code Commentaire
```{asis echo=TRUE} `dplyr` wzxhzdk:30 Il faut utiliser une assignation (`<-`) pour modifier la table.
```{asis echo=TRUE} `data.table` wzxhzdk:31 Il ne faut pas d'assignation pour modifier la table, qui est modifiée par référence.
### Les usages de `:=` On peut se servir de la fonction `:=` de multiples façons, et avec plusieurs notations. #### Créer plusieurs variables à la fois Voici comment créer plusieurs variables à la fois avec `:=`, en utilisant une notation vectorielle : wzxhzdk:32 On peut faire exactement la même chose en utilisant la notation `` `:=`() ``. Voici le même exemple écrit avec `` `:=`() ``. wzxhzdk:33 ::: {.remarque} Si vous utilisez la notation`` `:=`() ``, alors il faut utiliser uniquement `=` à l'intérieur des parenthèses pour créer ou modifier des variables, et non `:=`. Exemple : wzxhzdk:34 ::: #### Supprimer une colonne On peut facilement supprimer une colonne en lui assignant la valeur `NULL` (c'est hyper rapide !). Voici un exemple : wzxhzdk:35 #### Faire un remplacement conditionnel La fonction `:=` peut être utilisée pour modifier une colonne pour certaines lignes seulement, en fonction d'une condition logique. C'est beaucoup plus efficace qu'un terme `dplyr::if_else()` ou `dplyr::case_when()`. Imaginons qu'on veuille créer une colonne `EQUIP_HORS_CHAUSS` égale au nombre d'équipements (`NB_EQUIP`) sauf pour les lignes correspondantes à des magasins de chaussures (`TYPEQU == "B304"`) où elle vaut `NA`. Dans ce cas, le code `dplyr` serait : wzxhzdk:36 et le code équivalent en `data.table`, nécessitant beaucoup moins de mémoire vive : wzxhzdk:37 ### Attention en utilisant `:=` L'utilisation de la fonction `:=` est déroutante lorsqu'on découvre `data.table`. Voici trois remarques qui vous feront gagner du temps : - **Vous pouvez faire appel à d'autres fonctions à l'intérieur de la fonction `:=`.** Par exemple, si on veut mettre la variable `name` en minuscules, on peut utiliser la fonction `tolower()`. On écrit alors : ```r dt[ , name_minuscule := tolower(name)] ``` - Lorsque l'on crée plusieurs variables avec la fonction `:=`, elles sont créées en même temps. **On ne peut donc pas faire appel dans une formule à une variable qu'on crée dans le même appel à la fonction `:=`.** Par exemple, le code suivant _ne fonctionne pas_ : ```r bpe_ens_2018_dt[ , `:=`(nouvelle_colonne1 = NB_EQUIP * 2, nouvelle_colonne2 = nouvelle_colonne1 + 3)] ``` En effet, au moment où la fonction `:=` est exécutée, la colonne `nouvelle_colonne1` n'existe pas encore, donc la formule `nouvelle_colonne2 = nouvelle_colonne1 + 3` n'a pas encore de sens. Si vous créez des variables en chaîne, il faut décomposer l'opération en plusieurs étapes enchaînées. Voici le code qui permet de créer les deux colonnes à la suite : ```r bpe_ens_2018_dt[ , `:=`(nouvelle_colonne1 := NB_EQUIP * 2 ][ , nouvelle_colonne2 := nouvelle_colonne1 + 3)] ``` - **Un mauvais usage de la fonction `:=` peut vous amener à écraser par erreur vos données.** En effet, si vous exécuter par erreur la commande `dt[ , ma_variable_importante := 0]`, vous écrasez la variable `ma_variable_importante`. Vous devez alors recharger vos données... Il faut donc bien réfléchir à ce que vous voulez faire avant de remplacer ou modifier une variable existante avec la fonction `:=`. Si vous modifiez un `data.table` dans une fonction, un filet de sécurité consiste à d'abord copier le `data.table` initial et ainsi faire les modifications sur le nouvel objet, de la manière suivante : ```r dt_copy <- data.table::copy(dt) dt_copy[, ma_variable_importante := "Nouvelle valeur"] ``` ## Programmer des fonctions avec `data.table` Une des forces de `data.table` est qu'il est relativement simple d'utiliser ce _package_ dans des fonctions. Pour illustrer l'usage des fonctions, nous allons utiliser la table `filosofi_com_2016` disponible dans le _package_ `doremifasolData`. Cette table donne des informations sur les revenus des ménages au niveau communal. Nous créons une variable donnant le numéro du département (`departement`) en extrayant les deux premiers caractères du code commune (`CODGEO`) avec la fonction `str_sub` du _package_ `stringr` (vous pouvez consulter la fiche [Manipuler des données textuelles pour en apprendre davantage sur `stringr`]). Enfin, nous utilisons la fonction `.SD` pour sélectionner uniquement quelques variables. wzxhzdk:38 ### Utiliser `.SD` et `lapply` Le mot clé `.SD` (*Subset of Data*) permet d'appliquer la même opération sur plusieurs colonnes. Les colonnes auxquelles l'opération s'applique sont contrôlées par l'argument `.SDcols` (par défaut, toutes les colonnes sont traitées). Le mot clé `.SD` est régulièrement utilisé en conjonction avec la fonction `lapply`. **Cette syntaxe, très puissante, permet également d'avoir des codes assez compacts, ce qui les rend plus lisible.** Un usage classique de ce duo `lapply`+`.SD` consiste à écrire des fonctions de statistiques descriptives. Par exemple, imaginons qu'on souhaite calculer la moyenne, l'écart-type et les quantiles (P25, P50 et P75) de nombreuses colonnes. On peut alors définir la fonction suivante : wzxhzdk:39 Voici comment on peut appliquer cette fonction aux colonnes `NBMENFISC16` (nombre de ménages fiscaux) et `NBPERSMENFISC16` (nombre de personnes dans les ménages fiscaux) de la table `filosofi_com_2016_dt` : wzxhzdk:40 **Il est également très simple d'effectuer des calculs par groupe avec la méthode `lapply`+`.SD`.** On peut par facilement adapter le code précédent pour calculer des statistiques descriptives par département (variable `departement`). wzxhzdk:41 ### Définir des fonctions modifiant un `data.table` **Il est très facile d'écrire avec `data.table` des fonctions génériques faisant appel à des noms de variables en arguments.** Pour déclarer à `data.table` qu'un nom fait référence à une colonne, la manière la plus simple est d'utiliser la fonction `get`. Dans l'exemple suivant, on définit la fonction `creation_var` qui crée dans la table `data` une nouvelle variable (dont le nom est l'argument `nouveau_nom`) égale à une autre variable incrémentée (dont le nom est l'argument `nom_variable`) de 1. L'utilisation de la fonction `get` permet d'indiquer à `data.table` que la chaîne de caractères `nom_variable` désigne une colonne de la table `data`. wzxhzdk:42 `c(nouveau_nom)` permet de s'assurer que `data.table` crée une nouvelle colonne dont le nom est défini en argument (et qui ne s'appelle donc pas `nouveau_nom`). ::: {.remarque} Il est particulièrement complexe d'écrire avec `dplyr` des fonctions génériques faisant appel à des noms de variables en arguments : il est nécessaire de faire appel à plusieurs fonctionnalités de `rlang` pour être en mesure d'effectuer une opération équivalente. Voici pour information la version `dplyr` de l'exemple précédent : wzxhzdk:43 C'est l'une des raisons pour lesquelles il est préférable d'utiliser `data.table` lorsqu'on souhaite manipuler fréquemment des données à l'aide de fonctions. Le lecteur intéressé par la question pourra consulter ce [post](https://linogaliana.netlify.com/post/datatable/datatable-nse/) sur le sujet. ::: ::: {.conseil} Lorsqu'on définit des fonctions pour effectuer des traitements génériques, il est conseillé de privilégier `data.table` sur `dplyr`. Une précaution est néanmoins nécessaire pour ne pas modifier les données en entrée de la fonction si l'opérateur `:=` est utilisée. Il est recommandé dans ce cas de créer une copie du dataframe en entrée (`data.table::copy(df)`) et d'effectuer les traitements sur cette copie. ::: ## Pour en savoir plus - la [documentation officielle](https://www.rdocumentation.org/packages/data.table) de `data.table` (en anglais) ; - les [vignettes](https://github.com/Rdatatable/data.table/wiki/Getting-started) de `data.table` : - [Introduction à `data.table`](https://cloud.r-project.org/web/packages/data.table/vignettes/datatable-intro.html) (en anglais) ; - [Modification par référence](https://cloud.r-project.org/web/packages/data.table/vignettes/datatable-reference-semantics.html) (en anglais) ; - [la foire aux questions de `data.table`](https://cran.r-project.org/web/packages/data.table/vignettes/datatable-faq.html) (en anglais) ; - une [_cheatsheet_](https://github.com/rstudio/cheatsheets/raw/master/datatable.pdf) sur `data.table` (en anglais) ; - une [introduction à l'utilisation de l'opérateur `[...]`](https://stt4230.rbind.io/tutoriels_etudiants/hiver_2017/data.table) (en français) ; - Ce [cours complet sur `data.table`](https://gitlab.com/linogaliana/bigr/-/blob/master/04-datatable.Rmd) (en français). - Cette [présentation des fonctionnalités du package](https://dreamrs.github.io/talks/20180528_RAddicts_datatable.pdf) (en français) ; - Ce [post sur les fonctions utilisant data.table](https://linogaliana.netlify.com/post/datatable/datatable-nse/).

InseeFrLab/utilitr-template documentation built on Oct. 21, 2022, 11:42 p.m.