From 5f3781e0f148ae16d71338fb3f3ac8f212a18df9 Mon Sep 17 00:00:00 2001 From: Christian Wia Date: Tue, 15 Jul 2025 20:49:51 +0200 Subject: [PATCH 01/17] Update datatable-joins.Rmd #7177 lines 120 442 substitue '>' by 'Note:' --- vignettes/fr/datatable-joins.Rmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vignettes/fr/datatable-joins.Rmd b/vignettes/fr/datatable-joins.Rmd index ef283c302c..78782f1d60 100644 --- a/vignettes/fr/datatable-joins.Rmd +++ b/vignettes/fr/datatable-joins.Rmd @@ -117,7 +117,7 @@ x[i, on, nomatch] \____ data.table secondaire ``` -> N'oubliez pas que l'ordre standard des arguments dans data.table est `dt[i, j, by]`. Pour les opérations de jointure, il est recommandé de passer les arguments `on` et `nomatch` par nom pour éviter d'utiliser `j` et `by` quand ce n'est pas nécessaire. +Note : n'oubliez pas que l'ordre standard des arguments dans data.table est `dt[i, j, by]`. Pour les opérations de jointure, il est recommandé de passer les arguments `on` et `nomatch` par nom pour éviter d'utiliser `j` et `by` quand ce n'est pas nécessaire. ## 3. Jointures équilibrées @@ -439,7 +439,7 @@ ProductReceived[ProductSales, allow.cartesian = TRUE] ``` -> `allow.cartesian` vaut par défaut FALSE car c'est ce que l'utilisateur a souhaité, et une telle jointure croisée peut conduire à un très grand nombre de lignes dans le résultat. Par exemple, si Table A possède 100 lignes et Table +Note : `allow.cartesian` vaut par défaut FALSE car c'est ce que l'utilisateur a souhaité, et une telle jointure croisée peut conduire à un très grand nombre de lignes dans le résultat. Par exemple, si Table A possède 100 lignes et Table B en a 50, leur produit cartésien sera de 5000 lignes (100 * 50). Ce qui peut rapidement accroître la mémoire occupée pour de grands ensembles de données. #### 3.6.1. Selection d'une seule correspondance From 9cc247de2801bcfaa0008a5e89ef1c1baf6ebb52 Mon Sep 17 00:00:00 2001 From: Christian Wia Date: Tue, 15 Jul 2025 20:54:39 +0200 Subject: [PATCH 02/17] Update datatable-joins.Rmd #7177 lines 120 442 substitue > by Notes: --- vignettes/datatable-joins.Rmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vignettes/datatable-joins.Rmd b/vignettes/datatable-joins.Rmd index fa1d84e89f..d50feba924 100644 --- a/vignettes/datatable-joins.Rmd +++ b/vignettes/datatable-joins.Rmd @@ -117,7 +117,7 @@ x[i, on, nomatch] \____ secondary data.table ``` -> Please keep in mind that the standard argument order in `data.table` is `dt[i, j, by]`. For join operations, it is recommended to pass the `on` and `nomatch` arguments by name to avoid using `j` and `by` when they are not needed. +Note: Please keep in mind that the standard argument order in `data.table` is `dt[i, j, by]`. For join operations, it is recommended to pass the `on` and `nomatch` arguments by name to avoid using `j` and `by` when they are not needed. ## 3. Equi joins @@ -439,7 +439,7 @@ ProductReceived[ProductSales, allow.cartesian = TRUE] ``` -> `allow.cartesian` is defaulted to FALSE as this is seldom what the user wants, and such a cross join can lead to a very large number of rows in the result. For example, if Table A has 100 rows and Table B has 50 rows, their Cartesian product would result in 5000 rows (100 * 50). This can quickly become memory-intensive for large datasets. +Note: `allow.cartesian` is defaulted to FALSE as this is seldom what the user wants, and such a cross join can lead to a very large number of rows in the result. For example, if Table A has 100 rows and Table B has 50 rows, their Cartesian product would result in 5000 rows (100 * 50). This can quickly become memory-intensive for large datasets. #### 3.6.1. Selecting one match From 30302a142251079cc30d5813cb482e417daabe5c Mon Sep 17 00:00:00 2001 From: Christian Wia Date: Tue, 15 Jul 2025 21:10:11 +0200 Subject: [PATCH 03/17] Update datatable-benchmarking.Rmd #7177 line 112 substitue > by Note: --- vignettes/datatable-benchmarking.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/datatable-benchmarking.Rmd b/vignettes/datatable-benchmarking.Rmd index 1a47d95303..86fb456c2e 100644 --- a/vignettes/datatable-benchmarking.Rmd +++ b/vignettes/datatable-benchmarking.Rmd @@ -109,7 +109,7 @@ Unless this is what you truly want to measure you should prepare input objects o Repeating a benchmark many times usually does not give the clearest picture for data processing tools. Of course, it makes perfect sense for more atomic calculations, but this is not a good representation of the most common way these tools will actually be used, namely for data processing tasks, which consist of batches of sequentially provided transformations, each run once. Matt once said: -> I'm very wary of benchmarks measured in anything under 1 second. Much prefer 10 seconds or more for a single run, achieved by increasing data size. A repetition count of 500 is setting off alarm bells. 3-5 runs should be enough to convince on larger data. Call overhead and time to GC affect inferences at this very small scale. +Note: I'm very wary of benchmarks measured in anything under 1 second. Much prefer 10 seconds or more for a single run, achieved by increasing data size. A repetition count of 500 is setting off alarm bells. 3-5 runs should be enough to convince on larger data. Call overhead and time to GC affect inferences at this very small scale. This is very valid. The smaller time measurement is the relatively bigger noise is. Noise generated by method dispatch, package/class initialization, etc. Main focus of benchmark should be on real use case scenarios. From a85099a34ab1d91ba43a01d98c233ffe166fc258 Mon Sep 17 00:00:00 2001 From: Christian Wia Date: Tue, 15 Jul 2025 21:14:47 +0200 Subject: [PATCH 04/17] Update datatable-faq.Rmd #7177 lines 112 118 324 466 substitue > by Note: --- vignettes/datatable-faq.Rmd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vignettes/datatable-faq.Rmd b/vignettes/datatable-faq.Rmd index 474c5b5790..1e19739f48 100644 --- a/vignettes/datatable-faq.Rmd +++ b/vignettes/datatable-faq.Rmd @@ -109,13 +109,13 @@ Furthermore, data.table _inherits_ from `data.frame`. It _is_ a `data.frame`, to We _have_ proposed enhancements to R wherever possible, too. One of these was accepted as a new feature in R 2.12.0: -> `unique()` and `match()` are now faster on character vectors where all elements are in the global CHARSXP cache and have unmarked encoding (ASCII). Thanks to Matt Dowle for suggesting improvements to the way the hash code is generated in unique.c. +Note: `unique()` and `match()` are now faster on character vectors where all elements are in the global CHARSXP cache and have unmarked encoding (ASCII). Thanks to Matt Dowle for suggesting improvements to the way the hash code is generated in unique.c. A second proposal was to use `memcpy` in duplicate.c, which is much faster than a for loop in C. This would improve the _way_ that R copies data internally (on some measures by 13 times). The thread on r-devel is [here](https://stat.ethz.ch/pipermail/r-devel/2010-April/057249.html). A third more significant proposal that was accepted is that R now uses data.table's radix sort code as from R 3.3.0: -> The radix sort algorithm and implementation from data.table (forder) replaces the previous radix (counting) sort and adds a new method for order(). Contributed by Matt Dowle and Arun Srinivasan, the new algorithm supports logical, integer (even with large values), real, and character vectors. It outperforms all other methods, but there are some caveats (see ?sort). +Note: The radix sort algorithm and implementation from data.table (forder) replaces the previous radix (counting) sort and adds a new method for order(). Contributed by Matt Dowle and Arun Srinivasan, the new algorithm supports logical, integer (even with large values), real, and character vectors. It outperforms all other methods, but there are some caveats (see ?sort). This was big event for us and we celebrated until the cows came home. (Not really.) @@ -321,7 +321,7 @@ A[c(1, 3), c(2, 3)] However, this returns the union of those rows and columns. To reference the cells, a 2-column matrix is required. `?Extract` says: -> When indexing arrays by `[` a single argument `i` can be a matrix with as many columns as there are dimensions of `x`; the result is then a vector with elements corresponding to the sets of indices in each row of `i`. +Note: When indexing arrays by `[` a single argument `i` can be a matrix with as many columns as there are dimensions of `x`; the result is then a vector with elements corresponding to the sets of indices in each row of `i`. Let's try again. @@ -463,7 +463,7 @@ This comes up quite a lot but it's really earth-shatteringly simple. A function You might now ask: where is this documented in R? Answer: it's quite clear, but, you need to first know to look in `?UseMethod` and _that_ help file contains: -> When a function calling `UseMethod('fun')` is applied to an object with class attribute `c('first', 'second')`, the system searches for a function called `fun.first` and, if it finds it, applies it to the object. If no such function is found a function called `fun.second` is tried. If no class name produces a suitable function, the function `fun.default` is used, if it exists, or an error results. +Note: When a function calling `UseMethod('fun')` is applied to an object with class attribute `c('first', 'second')`, the system searches for a function called `fun.first` and, if it finds it, applies it to the object. If no such function is found a function called `fun.second` is tried. If no class name produces a suitable function, the function `fun.default` is used, if it exists, or an error results. Happily, an internet search for "How does R method dispatch work" (at the time of this writing) returns the `?UseMethod` help page in the top few links. Admittedly, other links rapidly descend into the intricacies of S3 vs S4, internal generics and so on. From 7724e6c9890ee52068fa73d4e0a0319440334e8a Mon Sep 17 00:00:00 2001 From: Christian Wia Date: Tue, 15 Jul 2025 21:16:55 +0200 Subject: [PATCH 05/17] Update datatable-programming.Rmd #7177 line 102 substitue > by Note: --- vignettes/datatable-programming.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/datatable-programming.Rmd b/vignettes/datatable-programming.Rmd index 34c6d77fda..ee7d279ba9 100644 --- a/vignettes/datatable-programming.Rmd +++ b/vignettes/datatable-programming.Rmd @@ -99,7 +99,7 @@ We have to use `deparse(substitute(...))` to catch the actual names of objects p Martin Machler, R Project Core Developer, [once said](https://stackoverflow.com/a/40164111/2490497): -> Sorry but I don't understand why too many people even think a string was something that could be evaluated. You must change your mindset, really. Forget all connections between strings on one side and expressions, calls, evaluation on the other side. +Note: Sorry but I don't understand why too many people even think a string was something that could be evaluated. You must change your mindset, really. Forget all connections between strings on one side and expressions, calls, evaluation on the other side. The (possibly) only connection is via `parse(text = ....)` and all good R programmers should know that this is rarely an efficient or safe means to construct expressions (or calls). Rather learn more about `substitute()`, `quote()`, and possibly the power of using `do.call(substitute, ......)`. #### Computing on the language From be4ea92657968e01fbf4520289177c3f9c097f7a Mon Sep 17 00:00:00 2001 From: Christian Wia Date: Tue, 15 Jul 2025 21:21:08 +0200 Subject: [PATCH 06/17] Update datatable-benchmarking.Rmd #7177 line 102 substitue > by Note : --- vignettes/fr/datatable-benchmarking.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/fr/datatable-benchmarking.Rmd b/vignettes/fr/datatable-benchmarking.Rmd index 846f34c0b6..98369b72d2 100644 --- a/vignettes/fr/datatable-benchmarking.Rmd +++ b/vignettes/fr/datatable-benchmarking.Rmd @@ -99,7 +99,7 @@ Si ce n'est pas ce que vous voulez vraiment mesurer, vous devez préparer des ob Répéter un benchmark plusieurs fois ne donne généralement pas l'image la plus claire des outils de traitement des données. Bien sûr, c'est parfaitement logique pour les calculs plus atomiques, mais ce n'est pas une bonne représentation de la manière la plus courante dont ces outils seront utilisés, à savoir pour les tâches de traitement des données, qui consistent en des lots de transformations fournies de manière séquentielle, chacune exécutée une fois. Matt a dit un jour : -> Je me méfie beaucoup des benchmarks qui prennent moins d'une seconde. Je préfère de loin 10 secondes ou plus pour une seule exécution, obtenues en augmentant la taille des données. Un nombre de répétitions de 500 tire la sonnette d'alarme. 3 à 5 exécutions devraient suffire à convaincre sur des données plus importantes. Le coût des appels de fonctions et le temps nécessaire au GC affectent les calculs à une si petite échelle. +Note : je me méfie beaucoup des benchmarks qui prennent moins d'une seconde. Je préfère de loin 10 secondes ou plus pour une seule exécution, obtenues en augmentant la taille des données. Un nombre de répétitions de 500 tire la sonnette d'alarme. 3 à 5 exécutions devraient suffire à convaincre sur des données plus importantes. Le coût des appels de fonctions et le temps nécessaire au GC affectent les calculs à une si petite échelle. Ceci est tout à fait vrai. Plus la mesure du temps est petite, plus le bruit est important, de manière relative. Le bruit est généré par le dispatching des méthodes, l'initialisation de packages/classes, etc. Le benchmark devrait se concentrer sur des scénarios d'utilisation réelle. From 48221673240bd6dc5ae3150aedd199251123e391 Mon Sep 17 00:00:00 2001 From: Christian Wia Date: Tue, 15 Jul 2025 21:28:06 +0200 Subject: [PATCH 07/17] Update datatable-faq.Rmd #7177 lines 103 109 318 461 substitue > by Note : --- vignettes/fr/datatable-faq.Rmd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vignettes/fr/datatable-faq.Rmd b/vignettes/fr/datatable-faq.Rmd index a90bfc5cd1..c15aac4192 100644 --- a/vignettes/fr/datatable-faq.Rmd +++ b/vignettes/fr/datatable-faq.Rmd @@ -100,13 +100,13 @@ De plus, data.table *hérite* de `data.frame`. C'est aussi un `data.frame`. Un d Nous avons également proposé des améliorations à R chaque fois que cela était possible. L'une d'entre elles a été acceptée comme nouvelle fonctionnalité dans R 2.12.0 : -> `unique()` et `match()` sont maintenant plus rapides sur les vecteurs de caractères où tous les éléments sont dans le cache global CHARSXP et ont un encodage non marqué (ASCII). Merci à Matt Dowle pour avoir suggéré des améliorations dans la façon dont le code de hachage est généré dans unique.c. +Note : `unique()` et `match()` sont maintenant plus rapides sur les vecteurs de caractères où tous les éléments sont dans le cache global CHARSXP et ont un encodage non marqué (ASCII). Merci à Matt Dowle pour avoir suggéré des améliorations dans la façon dont le code de hachage est généré dans unique.c. Une deuxième proposition était d'utiliser `memcpy` dans duplicate.c, qui est beaucoup plus rapide qu'une boucle for en C. Cela améliorerait la *manière* dont R copie les données en interne (sur certaines mesures, de 13 fois). Le fil de discussion sur r-devel est [ici] (https://stat.ethz.ch/pipermail/r-devel/2010-April/057249.html). Une troisième proposition plus significative qui a été acceptée est que R utilise maintenant le code de tri par base (radix sort) de data.table à partir de R 3.3.0 : -> L'algorithme de tri par base (radix sort) et l'implémentation de data.table (forder) remplace l'ancien tri par base (comptage) et ajoute une nouvelle méthode pour order(). Proposé par Matt Dowle et Arun Srinivasan, le nouvel algorithme supporte les vecteurs de logiques, d’entiers (même avec de grandes valeurs), de réels et de caractères. Il est plus performant que toutes les autres méthodes, mais il y a quelques mises en garde (voir ?sort). +Note : l'algorithme de tri par base (radix sort) et l'implémentation de data.table (forder) remplace l'ancien tri par base (comptage) et ajoute une nouvelle méthode pour order(). Proposé par Matt Dowle et Arun Srinivasan, le nouvel algorithme supporte les vecteurs de logiques, d’entiers (même avec de grandes valeurs), de réels et de caractères. Il est plus performant que toutes les autres méthodes, mais il y a quelques mises en garde (voir ?sort). C'était un grand événement pour nous et nous l'avons fêté jusqu'à ce que les vaches rentrent à la maison. (Pas vraiment.) @@ -315,7 +315,7 @@ A[c(1, 3), c(2, 3)] Cependant, cette méthode renvoie l'union de ces lignes et de ces colonnes. Pour référencer les cellules, une matrice à 2 colonnes est nécessaire. `?Extract` dit : -> Lors de l'indexation des tableaux par `[`, un seul argument `i` peut être une matrice avec autant de colonnes qu'il y a de dimensions de `x` ; le résultat est alors un vecteur avec des éléments correspondant aux ensembles d'indices dans chaque ligne de `i`. +Note : lors de l'indexation des tableaux par `[`, un seul argument `i` peut être une matrice avec autant de colonnes qu'il y a de dimensions de `x` ; le résultat est alors un vecteur avec des éléments correspondant aux ensembles d'indices dans chaque ligne de `i`. Essayons encore une fois. @@ -458,7 +458,7 @@ On en parle souvent, mais c'est d'une simplicité déconcertante. Une fonction t Vous pouvez maintenant vous demander : où cela est-il documenté dans R ? Réponse : c'est assez clair, mais vous devez d'abord savoir qu'il faut chercher dans `?UseMethod` et *ce* fichier d'aide contient : -> Lorsqu'une fonction appelant `UseMethod('fun')` est appliquée à un objet avec l'attribut de classe `c('first', 'second')`, le système recherche une fonction appelée `fun.first` et, s'il la trouve, l'applique à l'objet. Si aucune fonction de ce type n'est trouvée, une fonction appelée `fun.second` est essayée. Si aucun nom de classe ne produit une fonction appropriée, la fonction `fun.default` est utilisée, si elle existe, ou une erreur se produit. +Note : lorsqu'une fonction appelant `UseMethod('fun')` est appliquée à un objet avec l'attribut de classe `c('first', 'second')`, le système recherche une fonction appelée `fun.first` et, s'il la trouve, l'applique à l'objet. Si aucune fonction de ce type n'est trouvée, une fonction appelée `fun.second` est essayée. Si aucun nom de classe ne produit une fonction appropriée, la fonction `fun.default` est utilisée, si elle existe, ou une erreur se produit. Heureusement, une recherche internet sur "How does R method dispatch work" (à l'heure où j'écris ces lignes) renvoie la page d'aide `?UseMethod` dans les premiers liens. Certes, les autres liens sortent rapidement dans les subtilités de S3 vs S4, les génériques internes et ainsi de suite. From d38139a352ddc1f79c417cc1cdd5e5de86dce164 Mon Sep 17 00:00:00 2001 From: Christian Wia Date: Tue, 15 Jul 2025 21:30:51 +0200 Subject: [PATCH 08/17] Update datatable-programming.Rmd #7177 line 102 subshitue > by Note : --- vignettes/fr/datatable-programming.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/fr/datatable-programming.Rmd b/vignettes/fr/datatable-programming.Rmd index 7249c2de23..26b08bfac4 100644 --- a/vignettes/fr/datatable-programming.Rmd +++ b/vignettes/fr/datatable-programming.Rmd @@ -99,7 +99,7 @@ Nous devons utiliser `deparse(substitute(...))` pour récupérer les noms réels Martin Machler, R Project Core Developer, [a dit](https://stackoverflow.com/a/40164111/2490497) : -> Désolé, mais je ne comprends pas pourquoi tant de gens pensent qu'une chaîne de caractères est quelque chose qui peut être évalué. Il faut vraiment changer d'état d'esprit. Oubliez toutes les connexions entre les chaînes d'un côté et les expressions, les appels, l'évaluation de l'autre côté. La (possible) seule connexion est via `parse(text = ....)` et tous les bons programmeurs R devraient savoir que c'est rarement un moyen efficace ou sûr de construire des expressions (ou des appels). Apprenez plutôt à connaître `substitute()`, `quote()`, et peut-être la puissance de l'utilisation de `do.call(substitute, ......)`. +Note : désolé, mais je ne comprends pas pourquoi tant de gens pensent qu'une chaîne de caractères est quelque chose qui peut être évalué. Il faut vraiment changer d'état d'esprit. Oubliez toutes les connexions entre les chaînes d'un côté et les expressions, les appels, l'évaluation de l'autre côté. La (possible) seule connexion est via `parse(text = ....)` et tous les bons programmeurs R devraient savoir que c'est rarement un moyen efficace ou sûr de construire des expressions (ou des appels). Apprenez plutôt à connaître `substitute()`, `quote()`, et peut-être la puissance de l'utilisation de `do.call(substitute, ......)`. #### Calculs sur le langage From 79841abf61ee995af9993bccfc5bc4b9c528aed1 Mon Sep 17 00:00:00 2001 From: Christian Wia Date: Tue, 15 Jul 2025 23:04:56 +0200 Subject: [PATCH 09/17] Update datatable-benchmarking.Rmd redtored line 102 --- vignettes/datatable-benchmarking.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/datatable-benchmarking.Rmd b/vignettes/datatable-benchmarking.Rmd index 86fb456c2e..1a47d95303 100644 --- a/vignettes/datatable-benchmarking.Rmd +++ b/vignettes/datatable-benchmarking.Rmd @@ -109,7 +109,7 @@ Unless this is what you truly want to measure you should prepare input objects o Repeating a benchmark many times usually does not give the clearest picture for data processing tools. Of course, it makes perfect sense for more atomic calculations, but this is not a good representation of the most common way these tools will actually be used, namely for data processing tasks, which consist of batches of sequentially provided transformations, each run once. Matt once said: -Note: I'm very wary of benchmarks measured in anything under 1 second. Much prefer 10 seconds or more for a single run, achieved by increasing data size. A repetition count of 500 is setting off alarm bells. 3-5 runs should be enough to convince on larger data. Call overhead and time to GC affect inferences at this very small scale. +> I'm very wary of benchmarks measured in anything under 1 second. Much prefer 10 seconds or more for a single run, achieved by increasing data size. A repetition count of 500 is setting off alarm bells. 3-5 runs should be enough to convince on larger data. Call overhead and time to GC affect inferences at this very small scale. This is very valid. The smaller time measurement is the relatively bigger noise is. Noise generated by method dispatch, package/class initialization, etc. Main focus of benchmark should be on real use case scenarios. From e524cd78d340ef2b504c82b0e79acdb7cc07e4b1 Mon Sep 17 00:00:00 2001 From: Christian Wia Date: Tue, 15 Jul 2025 23:07:27 +0200 Subject: [PATCH 10/17] Update datatable-benchmarking.Rmd restore line 102 --- vignettes/fr/datatable-benchmarking.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/fr/datatable-benchmarking.Rmd b/vignettes/fr/datatable-benchmarking.Rmd index 98369b72d2..846f34c0b6 100644 --- a/vignettes/fr/datatable-benchmarking.Rmd +++ b/vignettes/fr/datatable-benchmarking.Rmd @@ -99,7 +99,7 @@ Si ce n'est pas ce que vous voulez vraiment mesurer, vous devez préparer des ob Répéter un benchmark plusieurs fois ne donne généralement pas l'image la plus claire des outils de traitement des données. Bien sûr, c'est parfaitement logique pour les calculs plus atomiques, mais ce n'est pas une bonne représentation de la manière la plus courante dont ces outils seront utilisés, à savoir pour les tâches de traitement des données, qui consistent en des lots de transformations fournies de manière séquentielle, chacune exécutée une fois. Matt a dit un jour : -Note : je me méfie beaucoup des benchmarks qui prennent moins d'une seconde. Je préfère de loin 10 secondes ou plus pour une seule exécution, obtenues en augmentant la taille des données. Un nombre de répétitions de 500 tire la sonnette d'alarme. 3 à 5 exécutions devraient suffire à convaincre sur des données plus importantes. Le coût des appels de fonctions et le temps nécessaire au GC affectent les calculs à une si petite échelle. +> Je me méfie beaucoup des benchmarks qui prennent moins d'une seconde. Je préfère de loin 10 secondes ou plus pour une seule exécution, obtenues en augmentant la taille des données. Un nombre de répétitions de 500 tire la sonnette d'alarme. 3 à 5 exécutions devraient suffire à convaincre sur des données plus importantes. Le coût des appels de fonctions et le temps nécessaire au GC affectent les calculs à une si petite échelle. Ceci est tout à fait vrai. Plus la mesure du temps est petite, plus le bruit est important, de manière relative. Le bruit est généré par le dispatching des méthodes, l'initialisation de packages/classes, etc. Le benchmark devrait se concentrer sur des scénarios d'utilisation réelle. From 633400541e1532e0a5c04ee8770fcc69af347473 Mon Sep 17 00:00:00 2001 From: Christian Wia Date: Tue, 15 Jul 2025 23:09:26 +0200 Subject: [PATCH 11/17] Update datatable-programming.Rmd restore line 102 --- vignettes/datatable-programming.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/datatable-programming.Rmd b/vignettes/datatable-programming.Rmd index ee7d279ba9..34c6d77fda 100644 --- a/vignettes/datatable-programming.Rmd +++ b/vignettes/datatable-programming.Rmd @@ -99,7 +99,7 @@ We have to use `deparse(substitute(...))` to catch the actual names of objects p Martin Machler, R Project Core Developer, [once said](https://stackoverflow.com/a/40164111/2490497): -Note: Sorry but I don't understand why too many people even think a string was something that could be evaluated. You must change your mindset, really. Forget all connections between strings on one side and expressions, calls, evaluation on the other side. +> Sorry but I don't understand why too many people even think a string was something that could be evaluated. You must change your mindset, really. Forget all connections between strings on one side and expressions, calls, evaluation on the other side. The (possibly) only connection is via `parse(text = ....)` and all good R programmers should know that this is rarely an efficient or safe means to construct expressions (or calls). Rather learn more about `substitute()`, `quote()`, and possibly the power of using `do.call(substitute, ......)`. #### Computing on the language From 980b2d58eae36fb992eb4984481cecddb0fdb7f1 Mon Sep 17 00:00:00 2001 From: Christian Wia Date: Tue, 15 Jul 2025 23:11:18 +0200 Subject: [PATCH 12/17] Update datatable-programming.Rmd restore line 102 --- vignettes/fr/datatable-programming.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/fr/datatable-programming.Rmd b/vignettes/fr/datatable-programming.Rmd index 26b08bfac4..7249c2de23 100644 --- a/vignettes/fr/datatable-programming.Rmd +++ b/vignettes/fr/datatable-programming.Rmd @@ -99,7 +99,7 @@ Nous devons utiliser `deparse(substitute(...))` pour récupérer les noms réels Martin Machler, R Project Core Developer, [a dit](https://stackoverflow.com/a/40164111/2490497) : -Note : désolé, mais je ne comprends pas pourquoi tant de gens pensent qu'une chaîne de caractères est quelque chose qui peut être évalué. Il faut vraiment changer d'état d'esprit. Oubliez toutes les connexions entre les chaînes d'un côté et les expressions, les appels, l'évaluation de l'autre côté. La (possible) seule connexion est via `parse(text = ....)` et tous les bons programmeurs R devraient savoir que c'est rarement un moyen efficace ou sûr de construire des expressions (ou des appels). Apprenez plutôt à connaître `substitute()`, `quote()`, et peut-être la puissance de l'utilisation de `do.call(substitute, ......)`. +> Désolé, mais je ne comprends pas pourquoi tant de gens pensent qu'une chaîne de caractères est quelque chose qui peut être évalué. Il faut vraiment changer d'état d'esprit. Oubliez toutes les connexions entre les chaînes d'un côté et les expressions, les appels, l'évaluation de l'autre côté. La (possible) seule connexion est via `parse(text = ....)` et tous les bons programmeurs R devraient savoir que c'est rarement un moyen efficace ou sûr de construire des expressions (ou des appels). Apprenez plutôt à connaître `substitute()`, `quote()`, et peut-être la puissance de l'utilisation de `do.call(substitute, ......)`. #### Calculs sur le langage From 566196c3ac79bdbc95e9c641c3a45424166c840f Mon Sep 17 00:00:00 2001 From: Christian Wia Date: Tue, 15 Jul 2025 23:17:20 +0200 Subject: [PATCH 13/17] Update datatable-faq.Rmd restore lines 112 118 324 466 --- vignettes/datatable-faq.Rmd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vignettes/datatable-faq.Rmd b/vignettes/datatable-faq.Rmd index 1e19739f48..474c5b5790 100644 --- a/vignettes/datatable-faq.Rmd +++ b/vignettes/datatable-faq.Rmd @@ -109,13 +109,13 @@ Furthermore, data.table _inherits_ from `data.frame`. It _is_ a `data.frame`, to We _have_ proposed enhancements to R wherever possible, too. One of these was accepted as a new feature in R 2.12.0: -Note: `unique()` and `match()` are now faster on character vectors where all elements are in the global CHARSXP cache and have unmarked encoding (ASCII). Thanks to Matt Dowle for suggesting improvements to the way the hash code is generated in unique.c. +> `unique()` and `match()` are now faster on character vectors where all elements are in the global CHARSXP cache and have unmarked encoding (ASCII). Thanks to Matt Dowle for suggesting improvements to the way the hash code is generated in unique.c. A second proposal was to use `memcpy` in duplicate.c, which is much faster than a for loop in C. This would improve the _way_ that R copies data internally (on some measures by 13 times). The thread on r-devel is [here](https://stat.ethz.ch/pipermail/r-devel/2010-April/057249.html). A third more significant proposal that was accepted is that R now uses data.table's radix sort code as from R 3.3.0: -Note: The radix sort algorithm and implementation from data.table (forder) replaces the previous radix (counting) sort and adds a new method for order(). Contributed by Matt Dowle and Arun Srinivasan, the new algorithm supports logical, integer (even with large values), real, and character vectors. It outperforms all other methods, but there are some caveats (see ?sort). +> The radix sort algorithm and implementation from data.table (forder) replaces the previous radix (counting) sort and adds a new method for order(). Contributed by Matt Dowle and Arun Srinivasan, the new algorithm supports logical, integer (even with large values), real, and character vectors. It outperforms all other methods, but there are some caveats (see ?sort). This was big event for us and we celebrated until the cows came home. (Not really.) @@ -321,7 +321,7 @@ A[c(1, 3), c(2, 3)] However, this returns the union of those rows and columns. To reference the cells, a 2-column matrix is required. `?Extract` says: -Note: When indexing arrays by `[` a single argument `i` can be a matrix with as many columns as there are dimensions of `x`; the result is then a vector with elements corresponding to the sets of indices in each row of `i`. +> When indexing arrays by `[` a single argument `i` can be a matrix with as many columns as there are dimensions of `x`; the result is then a vector with elements corresponding to the sets of indices in each row of `i`. Let's try again. @@ -463,7 +463,7 @@ This comes up quite a lot but it's really earth-shatteringly simple. A function You might now ask: where is this documented in R? Answer: it's quite clear, but, you need to first know to look in `?UseMethod` and _that_ help file contains: -Note: When a function calling `UseMethod('fun')` is applied to an object with class attribute `c('first', 'second')`, the system searches for a function called `fun.first` and, if it finds it, applies it to the object. If no such function is found a function called `fun.second` is tried. If no class name produces a suitable function, the function `fun.default` is used, if it exists, or an error results. +> When a function calling `UseMethod('fun')` is applied to an object with class attribute `c('first', 'second')`, the system searches for a function called `fun.first` and, if it finds it, applies it to the object. If no such function is found a function called `fun.second` is tried. If no class name produces a suitable function, the function `fun.default` is used, if it exists, or an error results. Happily, an internet search for "How does R method dispatch work" (at the time of this writing) returns the `?UseMethod` help page in the top few links. Admittedly, other links rapidly descend into the intricacies of S3 vs S4, internal generics and so on. From 145a5100f037b5423bcb24830a7649b1ce065375 Mon Sep 17 00:00:00 2001 From: Christian Wia Date: Tue, 15 Jul 2025 23:20:43 +0200 Subject: [PATCH 14/17] Update datatable-faq.Rmd restore lineS 103 109 318 461 --- vignettes/fr/datatable-faq.Rmd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vignettes/fr/datatable-faq.Rmd b/vignettes/fr/datatable-faq.Rmd index c15aac4192..a90bfc5cd1 100644 --- a/vignettes/fr/datatable-faq.Rmd +++ b/vignettes/fr/datatable-faq.Rmd @@ -100,13 +100,13 @@ De plus, data.table *hérite* de `data.frame`. C'est aussi un `data.frame`. Un d Nous avons également proposé des améliorations à R chaque fois que cela était possible. L'une d'entre elles a été acceptée comme nouvelle fonctionnalité dans R 2.12.0 : -Note : `unique()` et `match()` sont maintenant plus rapides sur les vecteurs de caractères où tous les éléments sont dans le cache global CHARSXP et ont un encodage non marqué (ASCII). Merci à Matt Dowle pour avoir suggéré des améliorations dans la façon dont le code de hachage est généré dans unique.c. +> `unique()` et `match()` sont maintenant plus rapides sur les vecteurs de caractères où tous les éléments sont dans le cache global CHARSXP et ont un encodage non marqué (ASCII). Merci à Matt Dowle pour avoir suggéré des améliorations dans la façon dont le code de hachage est généré dans unique.c. Une deuxième proposition était d'utiliser `memcpy` dans duplicate.c, qui est beaucoup plus rapide qu'une boucle for en C. Cela améliorerait la *manière* dont R copie les données en interne (sur certaines mesures, de 13 fois). Le fil de discussion sur r-devel est [ici] (https://stat.ethz.ch/pipermail/r-devel/2010-April/057249.html). Une troisième proposition plus significative qui a été acceptée est que R utilise maintenant le code de tri par base (radix sort) de data.table à partir de R 3.3.0 : -Note : l'algorithme de tri par base (radix sort) et l'implémentation de data.table (forder) remplace l'ancien tri par base (comptage) et ajoute une nouvelle méthode pour order(). Proposé par Matt Dowle et Arun Srinivasan, le nouvel algorithme supporte les vecteurs de logiques, d’entiers (même avec de grandes valeurs), de réels et de caractères. Il est plus performant que toutes les autres méthodes, mais il y a quelques mises en garde (voir ?sort). +> L'algorithme de tri par base (radix sort) et l'implémentation de data.table (forder) remplace l'ancien tri par base (comptage) et ajoute une nouvelle méthode pour order(). Proposé par Matt Dowle et Arun Srinivasan, le nouvel algorithme supporte les vecteurs de logiques, d’entiers (même avec de grandes valeurs), de réels et de caractères. Il est plus performant que toutes les autres méthodes, mais il y a quelques mises en garde (voir ?sort). C'était un grand événement pour nous et nous l'avons fêté jusqu'à ce que les vaches rentrent à la maison. (Pas vraiment.) @@ -315,7 +315,7 @@ A[c(1, 3), c(2, 3)] Cependant, cette méthode renvoie l'union de ces lignes et de ces colonnes. Pour référencer les cellules, une matrice à 2 colonnes est nécessaire. `?Extract` dit : -Note : lors de l'indexation des tableaux par `[`, un seul argument `i` peut être une matrice avec autant de colonnes qu'il y a de dimensions de `x` ; le résultat est alors un vecteur avec des éléments correspondant aux ensembles d'indices dans chaque ligne de `i`. +> Lors de l'indexation des tableaux par `[`, un seul argument `i` peut être une matrice avec autant de colonnes qu'il y a de dimensions de `x` ; le résultat est alors un vecteur avec des éléments correspondant aux ensembles d'indices dans chaque ligne de `i`. Essayons encore une fois. @@ -458,7 +458,7 @@ On en parle souvent, mais c'est d'une simplicité déconcertante. Une fonction t Vous pouvez maintenant vous demander : où cela est-il documenté dans R ? Réponse : c'est assez clair, mais vous devez d'abord savoir qu'il faut chercher dans `?UseMethod` et *ce* fichier d'aide contient : -Note : lorsqu'une fonction appelant `UseMethod('fun')` est appliquée à un objet avec l'attribut de classe `c('first', 'second')`, le système recherche une fonction appelée `fun.first` et, s'il la trouve, l'applique à l'objet. Si aucune fonction de ce type n'est trouvée, une fonction appelée `fun.second` est essayée. Si aucun nom de classe ne produit une fonction appropriée, la fonction `fun.default` est utilisée, si elle existe, ou une erreur se produit. +> Lorsqu'une fonction appelant `UseMethod('fun')` est appliquée à un objet avec l'attribut de classe `c('first', 'second')`, le système recherche une fonction appelée `fun.first` et, s'il la trouve, l'applique à l'objet. Si aucune fonction de ce type n'est trouvée, une fonction appelée `fun.second` est essayée. Si aucun nom de classe ne produit une fonction appropriée, la fonction `fun.default` est utilisée, si elle existe, ou une erreur se produit. Heureusement, une recherche internet sur "How does R method dispatch work" (à l'heure où j'écris ces lignes) renvoie la page d'aide `?UseMethod` dans les premiers liens. Certes, les autres liens sortent rapidement dans les subtilités de S3 vs S4, les génériques internes et ainsi de suite. From 2d21c400f488bdd4ffc76b5eb86f9e78b3fa17f4 Mon Sep 17 00:00:00 2001 From: Christian Wia Date: Wed, 16 Jul 2025 09:01:26 +0200 Subject: [PATCH 15/17] Update datatable-joins.Rmd #7179 highlight Note on lines 120 442 --- vignettes/datatable-joins.Rmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vignettes/datatable-joins.Rmd b/vignettes/datatable-joins.Rmd index d50feba924..3d7cf8c5ce 100644 --- a/vignettes/datatable-joins.Rmd +++ b/vignettes/datatable-joins.Rmd @@ -117,7 +117,7 @@ x[i, on, nomatch] \____ secondary data.table ``` -Note: Please keep in mind that the standard argument order in `data.table` is `dt[i, j, by]`. For join operations, it is recommended to pass the `on` and `nomatch` arguments by name to avoid using `j` and `by` when they are not needed. +**Note**: Please keep in mind that the standard argument order in `data.table` is `dt[i, j, by]`. For join operations, it is recommended to pass the `on` and `nomatch` arguments by name to avoid using `j` and `by` when they are not needed. ## 3. Equi joins @@ -439,7 +439,7 @@ ProductReceived[ProductSales, allow.cartesian = TRUE] ``` -Note: `allow.cartesian` is defaulted to FALSE as this is seldom what the user wants, and such a cross join can lead to a very large number of rows in the result. For example, if Table A has 100 rows and Table B has 50 rows, their Cartesian product would result in 5000 rows (100 * 50). This can quickly become memory-intensive for large datasets. +**Note**: `allow.cartesian` is defaulted to FALSE as this is seldom what the user wants, and such a cross join can lead to a very large number of rows in the result. For example, if Table A has 100 rows and Table B has 50 rows, their Cartesian product would result in 5000 rows (100 * 50). This can quickly become memory-intensive for large datasets. #### 3.6.1. Selecting one match From 718d828a90ebe37bae5c1af124942a66c896feff Mon Sep 17 00:00:00 2001 From: Christian Wia Date: Wed, 16 Jul 2025 09:04:21 +0200 Subject: [PATCH 16/17] Update datatable-joins.Rmd #7179 higlight Note line 120 442 --- vignettes/fr/datatable-joins.Rmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vignettes/fr/datatable-joins.Rmd b/vignettes/fr/datatable-joins.Rmd index 78782f1d60..cc57b49a6e 100644 --- a/vignettes/fr/datatable-joins.Rmd +++ b/vignettes/fr/datatable-joins.Rmd @@ -117,7 +117,7 @@ x[i, on, nomatch] \____ data.table secondaire ``` -Note : n'oubliez pas que l'ordre standard des arguments dans data.table est `dt[i, j, by]`. Pour les opérations de jointure, il est recommandé de passer les arguments `on` et `nomatch` par nom pour éviter d'utiliser `j` et `by` quand ce n'est pas nécessaire. +**Note** : n'oubliez pas que l'ordre standard des arguments dans data.table est `dt[i, j, by]`. Pour les opérations de jointure, il est recommandé de passer les arguments `on` et `nomatch` par nom pour éviter d'utiliser `j` et `by` quand ce n'est pas nécessaire. ## 3. Jointures équilibrées @@ -439,7 +439,7 @@ ProductReceived[ProductSales, allow.cartesian = TRUE] ``` -Note : `allow.cartesian` vaut par défaut FALSE car c'est ce que l'utilisateur a souhaité, et une telle jointure croisée peut conduire à un très grand nombre de lignes dans le résultat. Par exemple, si Table A possède 100 lignes et Table +**Note** : `allow.cartesian` vaut par défaut FALSE car c'est ce que l'utilisateur a souhaité, et une telle jointure croisée peut conduire à un très grand nombre de lignes dans le résultat. Par exemple, si Table A possède 100 lignes et Table B en a 50, leur produit cartésien sera de 5000 lignes (100 * 50). Ce qui peut rapidement accroître la mémoire occupée pour de grands ensembles de données. #### 3.6.1. Selection d'une seule correspondance From c7603ef0153453bfd41313a76186344423700990 Mon Sep 17 00:00:00 2001 From: Michael Chirico Date: Tue, 22 Jul 2025 17:20:49 +0000 Subject: [PATCH 17/17] line endings --- vignettes/fr/datatable-joins.Rmd | 1484 +++++++++++++++--------------- 1 file changed, 742 insertions(+), 742 deletions(-) diff --git a/vignettes/fr/datatable-joins.Rmd b/vignettes/fr/datatable-joins.Rmd index cc57b49a6e..d2322603bb 100644 --- a/vignettes/fr/datatable-joins.Rmd +++ b/vignettes/fr/datatable-joins.Rmd @@ -1,742 +1,742 @@ ---- -title: "Jointures avec data.table" -date: "`r Sys.Date()`" -output: - markdown::html_format -vignette: > - %\VignetteIndexEntry{Jointures avec data.table} - %\VignetteEngine{knitr::knitr} - \usepackage[utf8]{inputenc} -editor_options: - chunk_output_type: console ---- - -```{r, echo = FALSE, message = FALSE} -require(data.table) -knitr::opts_chunk$set( - comment = "#", - error = FALSE, - tidy = FALSE, - cache = FALSE, - collapse = TRUE -) -``` - -```{r, echo=FALSE, file='../_translation_links.R'} -``` -`r .write.translation.links("Une traduction de ce document est disponible en : %s")` - -Dans cette vignette nous apprendrons à réaliser les opérations de jointure en utilisant les ressources de la syntaxe `data.table`. - -Cela suppose que vous êtes déjà familiarisé avec la syntaxe `data.table` . Si ce n'est pas le cas, reportez-vous aux vignettes suivantes : - -- [`vignette("datatable-intro", package="data.table")`](datatable-intro.html) -- [`vignette("datatable-reference-semantics", package="data.table")`](datatable-reference-semantics.html) -- [`vignette("datatable-keys-fast-subset", package="data.table")`](datatable-keys-fast-subset.html) - -*** - -## 1. Définissons nos données d'exemple - -Pour illustrer la méthode proposée avec des exemples de la vie courante, nous allons simuler une **base de données normalisée** à partir d'un petit supermarché en définissant les tables suivantes dans une base de données : - -1. `Products`, une table où chaque ligne donne les caractéristiques de différents produits. Pour montrer comment l'environnement réagit avec les ***valeurs absentes***, nous laissons un `id` à NA. - -```{r, define_products} -Products = rowwiseDT( - id=, name=, price=, unit=, type=, - 1L, "banana", 0.63, "unit", "natural", - 2L, "carrots", 0.89, "lb", "natural", - 3L, "popcorn", 2.99, "unit", "processed", - 4L, "soda", 1.49, "ounce", "processed", - NA, "toothpaste", 2.99, "unit", "processed" -) -``` - -2. `NewTax`, une table donnant le pourcentage des taxes à appliquer aux produits traités en fonction de leurs unités. - -```{r define_new_tax} -NewTax = data.table( - unit = c("unit", "ounce"), - type = "processed", - tax_prop = c(0.65, 0.20) -) - -NewTax -``` - - -3. `ProductReceived`, une table dont les lignes simulent l'inventaire des ventes hebdomadaires. - -```{r define_product_received} -set.seed(2156) - -# NB: le 8 janvier 2024 est un lundi. -receipt_dates = seq(from=as.IDate("2024-01-08"), length.out=10L, by="week") - -ProductReceived = data.table( - id=1:10, # identifiant unique pour suivre une transaction - date=receipt_dates, - product_id=sample(c(NA, 1:3, 6L), size=10L, replace=TRUE), # NB: le produit '6' n'existe pas dans Products. - count=sample(c(50L, 100L, 150L), size=10L, replace=TRUE) -) - -ProductReceived -``` - -4. `ProductSales`, une table dont les lignes simulent les transactions des clients. - -```{r define_product_sales} -set.seed(5415) - -# lundi-vendredi (4 jours plus tard) pour chaque semaine présente dans ProductReceived -possible_weekdays <- as.IDate(sapply(receipt_dates, `+`, 0:4)) - -ProductSales = data.table( - id = 1:10, - date = sort(sample(possible_weekdays, 10L)), - product_id = sample(c(1:3, 7L), size = 10L, replace = TRUE), # NB: product '7' is in neither Products nor ProductReceived. - count = sample(c(50L, 100L, 150L), size = 10L, replace = TRUE) -) - -ProductSales -``` - -## 2. Syntaxe de la jointure `data.table` - -Avant de voir les avantages de la syntaxe `data.table` pour faire des opérations de jointure nous devons savoir quels arguments peuvent nous aider à les réaliser avec succès. - -Le diagramme suivant affiche une description pour chaque argument de base. Dans les sections suivantes nous verrons progressivement comment les utiliser tout en y apportant un peu de complexité à chaque fois. - -``` -x[i, on, nomatch] -| | | | -| | | \__ Si NULL, renvoie seulement les lignes liées des tables x et i -| | \____ vecteur de caractères ou liste définissant la logique de correspondance -| \_____ data.table primaire, liste ou data.frame -\____ data.table secondaire -``` - -**Note** : n'oubliez pas que l'ordre standard des arguments dans data.table est `dt[i, j, by]`. Pour les opérations de jointure, il est recommandé de passer les arguments `on` et `nomatch` par nom pour éviter d'utiliser `j` et `by` quand ce n'est pas nécessaire. - -## 3. Jointures équilibrées - -C'est le cas le plus général et facile où il existe des éléments communs entre les tables à combiner. - -La relation entre les tables peut être : - -- **De un vers un** : lorsque chaque valeur sélectionnée est unique dans chaque table. -- **De un vers plusieurs** : lorsque certaines valeurs sélectionnées sont répétées dans une table et à la fois uniques dans l'autre. -- **De plusieurs vers plusieurs** : lorsque les valeurs sélectionnées sont répétées plusieurs fois dans chaque table. - -Dans la plupart des exemples suivants nous réaliserons des correspondances *de un vers plusieurs* , mais nous prendrons aussi le temps d'expliquer les ressources disponibles pour réaliser les correspondances *de plusieurs vers plusieurs* . - - -### 3.1. Jointure droite - -Utilisez cette méthode pour combiner les colonnes de deux tables en se basant sur une ou plusieurs références mais ***en gardant toutes les lignes présentes dans la table située à droite (entre crochets)***. - -Dans le contexte de notre supermarché nous pouvons réaliser une jointure droite pour avoir davantage de détails à propos des produits reçus car c'est une relation de *un vers plusieurs* en passant un vecteur dans l'argument `on`. - -```{r} -Products[ProductReceived, - on = c(id = "product_id")] -``` - -Comme beaucoup de choses ont changé, nous allons expliquer les nouvelles caractéristiques dans les groupes suivants : - -- **Niveau colonne** - - Le *premier groupe* de colonnes dans le nouveau data.table vient de la table `x` . - - Le *second groupe* de colonnes de la nouvelle data.table vient de la table `i`. - - Si l'opération de jointure fait apparaître un **conflit de nom** (quand les deux tables ont un même nom de colonne) le ***prefixe*** `i.` est ajouté aux noms des colonnes de la **table de droite** (table en position `i`). - -- **Niveau ligne** - - Le `product_id` qui est absent, mais présent dans la table `ProductReceived` à la ligne 1 a été sélectionné avec le `id` absent de la table `Products`, donc ***les valeurs `NA` sont traitées comme toute autre valeur***. - - Toutes les lignes de la table `i` ont été gardées y compris : - - Les lignes qui ne correspondent pas, comme celle avec `product_id = 6`. - - Lignes qui répètent le même `product_id` plusieurs fois. - -#### 3.1.1. Jointure avec un argument de liste - -Si vous suivez la vignette, vous avez pu voir que nous avons utilisé un vecteur pour définir les relations entre les tables dans l'argument `on`, ce qui réellement utile si vous **créez vos propres fonctions**, mais une autre alternative est d'utiliser une **liste** pour définir les colonnes à sélectionner. - -Pour utiliser cette possibilité, nous avons a disposition les alternatives suivantes : - -- Inclure les colonnes associées dans la fonction R de base `list` . - -```{r, eval=FALSE} -Products[ProductReceived, - on = list(id = product_id)] -``` - -- Inclure les colonnes associées dans l'alias `data.table` `list` : `.`. - -```{r, eval=FALSE} -Products[ProductReceived, - on = .(id = product_id)] -``` - -#### 3.1.2. Alternatives pour définir l'argument `on` - -Dans tous les exemples précédents, nous avons passé le nom des colonnes à sélectionner avec l'argument `on` mais `data.table` possède également des alternatives à cette syntaxe. - -- **Jointure naturelle** : sélectionne les colonnes pour réaliser la correspondance en fonction des noms des colonnes en commun. Pour illustrer cette méthode, modifions la colonne de la table `Products` de `id` en `product_id` et utilisons le mot clé `.NATURAL`. - -```{r} -ProductsChangedName = setnames(copy(Products), "id", "product_id") -ProductsChangedName - -ProductsChangedName[ProductReceived, on = .NATURAL] -``` - -- **Jointure par clé** : sélectionne les colonnes pour rechercher la correspondance en fonction des colonnes clé quelque soit leur nom. Pour illustrer cette méthode, nous devons définir les clés dans le même ordre pour les deux tables. - -```{r} -ProductsKeyed = setkey(copy(Products), id) -key(ProductsKeyed) - -ProductReceivedKeyed = setkey(copy(ProductReceived), product_id) -key(ProductReceivedKeyed) - -ProductsKeyed[ProductReceivedKeyed] -``` - -#### 3.1.3. Opérations après la jointure - -La plupart du temps après avoir terminé une jointure il faut faire des adaptations supplémentaires. Pour cela plusieurs alternatives vous sont proposées : - -- Chaîner une nouvelle instruction en ajoutant une paire de crochets `[]`. -- En passant comme argument `j` une liste des colonnes que l'on veut conserver ou créer. - -Notre recommendation est d'utiliser la seconde alternative si possible, car elle est **plus rapide** et demande **moins de mémoire** que la première. - -##### Gestion de la colonne partagée Names avec l'argument j - -L'argument `j` autorise plusieurs alternatives intéressantes pour gérer les jointures avec les tables en **partageant les mêmes noms pour plusieurs colonnes**. Par défaut toutes les colonnes prennent leur source dans la table `x`, mais nous pouvons aussi utiliser le préfixe `x.` pour clarifier la source et utiliser le préfixe `i.` pour toutes les colonnes de la table déclarée dans l'argument `i` de la table `x`. - -Si nous retournons au petit supermarché, après avoir mis à jour la table `ProductReceived` avec la table `Products` , supposez que l'on veuille appliquer les modifications suivantes : - -- Modifier le nom des colonnes de `id` en `product_id` et de `i.id` en `received_id`. -- Ajouter `total_value`. - -```{r} -Products[ - ProductReceived, - on = c("id" = "product_id"), - j = .(product_id = x.id, - name = x.name, - price, - received_id = i.id, - date = i.date, - count, - total_value = price * count) -] -``` - - -##### Résumer avec`on` dans `data.table` - -Nous pouvons aussi utiliser cette alternative pour renvoyer les résultats agrégés en fonction des colonnes présentes dans la table `x` . - -Par exemple on pourrait s'intéresser à la somme dépensée pour acheter chaque produit au fil des jours, quelque soient ces produits. - -```{r} -dt1 = ProductReceived[ - Products, - on = c("product_id" = "id"), - by = .EACHI, - j = .(total_value_received = sum(price * count)) -] - -# alternative using multiple [] queries -dt2 = ProductReceived[ - Products, - on = c("product_id" = "id"), -][, .(total_value_received = sum(price * count)), - by = "product_id" -] - -identical(dt1, dt2) -``` - -#### 3.1.4. Jointure basée sur plusieurs colonnes - -Jusqu'à présent, nous avons réalisé les jointures en se basant sur une colonne `data.table`, mais il est important de savoir que le package peut joindre des tables en prenant en compte plusieurs colonnes. - -Pour illustrer cela supposons que nous voulions ajouter `tax_prop` de `NewTax` pour **mettre à jour** la table `Products`. - -```{r} -NewTax[Products, on = c("unit", "type")] -``` - -### 3.2. Jointure interne - -Utilisez cette méthode pour combiner les colonnes de deux tables en se basant sur une ou plusieurs références mais ***en conservant seulement les lignes qui correspondent entre les deux tables***. - -Pour réaliser cette opération il suffit d'ajouter `nomatch = NULL` ou `nomatch = 0` à l'une quelconque des opérations de jointure précédentes pour renvoyer le même résultat. - -```{r} -# First Table -Products[ProductReceived, - on = c("id" = "product_id"), - nomatch = NULL] - -# Second Table -ProductReceived[Products, - on = .(product_id = id), - nomatch = NULL] -``` - -Même si les deux tables contiennent la même information, il existe quelques différences importantes : - -- L'ordre des colonnes est différent -- Elles comportent certaines différences de noms dans le nom des colonnes : - - La colonne `id` de la première table contient les mêmes informations que `product_id` de la seconde table. - - La colonne `i.id` de la première table a les mêmes informations que `id` de la seconde table. - -### 3.3. Anti-jointure - -Cette méthode **ne garde que les lignes qui ne correspondent pas aux lignes de la seconde table**. - -Pour appliquer cette technique il suffit d'utiliser la négation (`!`) sur la table qui se trouve dans l'argument `i` . - -```{r} -Products[!ProductReceived, - on = c("id" = "product_id")] -``` - -Comme on peut le voir, le résultat ne comporte que 'soda' car c'est le seul produit qui ne figure pas dans la table `ProductReceived`. - -```{r} -ProductReceived[!Products, - on = c("product_id" = "id")] -``` - -Dans ce cas l'opération renvoie la ligne de `product_id = 6,` car il ne figure pas dans la table `Products`. - -### 3.4. Semi jointure - -Cette méthode **ne garde que les lignes qui correspondent à une ligne de la seconde table** sans combiner les colonnes des tables. - -En tant que jointure ceci est très similaire aux sous-ensembles, mais comme cette fois nous passons une table complète dans `i` nous devons vérifier que : - -- Chaque ligne de la table `x` est dupliquée à cause de la duplication des lignes dans la table passée dans l'argument `i`. - -- Toutes les lignes renommées de `x` doivent conserver l'ordre originel des lignes. - - -Pour faire ceci, suivez les étapes ci-après : - -1. Réaliser une **jointure interne** avec `which = TRUE` pour sauvegarder les numéros de ligne liés à chaque ligne sélectionnée de la table `x` . - -```{r} -SubSetRows = Products[ - ProductReceived, - on = .(id = product_id), - nomatch = NULL, - which = TRUE -] - -SubSetRows -``` - -2. Sélectionner et trier les id uniques de lignes. - -```{r} -SubSetRowsSorted = sort(unique(SubSetRows)) - -SubSetRowsSorted -``` - - -3. Sélectionner les lignes `x` à garder. - -```{r} -Products[SubSetRowsSorted] -``` - - -### 3.5. Jointure gauche - -Utiliser cette méthode pour combiner les colonnes de deux tables en se basant sur une ou plusieurs références mais ***en gardant toutes les lignes présentes dans la table située à gauche***. - -Pour réaliser cette opération, il suffit d'**échanger l'ordre entre les deux tables** ainsi que le nom des colonnes dans l'argument `on`. - -```{r} -ProductReceived[Products, - on = list(product_id = id)] -``` - -Voici les éléments importants à prendre en compte : - -- **Niveau colonne** - - Le *premier groupe* de colonnes provient maintenant de la table `ProductReceived` car il est la table `x` . - - Le *second groupe* de colonnes provient maintenant de la table `Products` car il est la table `i` . - - Le préfixe `i.` n'a été ajouté à aucune colonne. - -- **Niveau ligne** - - Toutes les lignes de la table `i` ont été gardées : l'entrée soda de `Products` ne correspond à aucune ligne de `ProductReceived` et fait encore partie des résultats. - - La ligne concernant `product_id = 6` ne fait plus partie des résultats car elle n'est pas présente dans la table `Products`. - - -#### 3.5.1. Jointure après des opérations sur les chaînes - -Une des fonctionnalités clé de `data.table` est que l'on peut appliquer plusieurs opérations en chaînant les crochets, avant d'enregistrer le résultat final. - -```r -DT[ - ... -][ - ... -][ - ... -] -``` - -Jusqu'à présent, si après avoir exécuté toutes ces opérations **nous souhaitons joindre de nouvelles colonnes sans supprimer aucune ligne**, il faut arrêter le processus de chaînage, sauvegarder dans une table temporaire puis effectuer ultérieurement l'opération de jointure. - -Pour éviter cette situation, nous pouvons utiliser le symbole spécial `.SD`, pour appliquer une **jointure droite en fonction de la table modifiée**. - -```{r} -NewTax[Products, - on = c("unit", "type") -][, ProductReceived[.SD, - on = list(product_id = id)], - .SDcols = !c("unit", "type")] -``` - -### 3.6. Jointure de plusieurs à plusieurs - -Quelques fois nous voulons joindre des tables en se basant sur les colonnes ayant des **valeurs `id` dupliquées** pour faires des transformations ultérieurement. - -Pour illustrer cette situation, prenons par exemple le `product_id == 1L`, qui a quatre lignes dans notre table `ProductReceived` . - -```{r} -ProductReceived[product_id == 1L] -``` - -Et quatre lignes dans notre table `ProductSales`. - -```{r} -ProductSales[product_id == 1L] -``` - -Pour réaliser cette jointure il nous suffit de filtrer `product_id == 1L` dans la table `i` pour limiter la jointure uniquement à ce produit et déclarer l'argument `allow.cartesian = TRUE` pour permettre la combinaison de chaque ligne d'une table avec chaque ligne de l'autre table. - -```{r} -ProductReceived[ProductSales[list(1L), - on = "product_id", - nomatch = NULL], - on = "product_id", - allow.cartesian = TRUE] -``` - -Une fois que nous avons compris les résultats, nous pouvont appliquer le même processus à **tous les produits**. - -```{r} -ProductReceived[ProductSales, - on = "product_id", - allow.cartesian = TRUE] -``` - -**Note** : `allow.cartesian` vaut par défaut FALSE car c'est ce que l'utilisateur a souhaité, et une telle jointure croisée peut conduire à un très grand nombre de lignes dans le résultat. Par exemple, si Table A possède 100 lignes et Table -B en a 50, leur produit cartésien sera de 5000 lignes (100 * 50). Ce qui peut rapidement accroître la mémoire occupée pour de grands ensembles de données. - -#### 3.6.1. Selection d'une seule correspondance - -Après avoir fait la jointure de la table, nous pouvons voir que l'on peut utiliser une seule jointure pour extraire les informations nécessaires. Dans ce cas il y a deux alternatives : - -- Nous pouvons sélectionner la **première correspondance**, représentée dans l'exemple suivant par `id = 2`. - -```{r} -ProductReceived[ProductSales[product_id == 1L], - on = .(product_id), - allow.cartesian = TRUE, - mult = "first"] -``` - -- Nous pouvons sélectionner la **dernière correspondance**, représentée dans l'exemple suivant par `id = 9`. - -```{r} -ProductReceived[ProductSales[product_id == 1L], - on = .(product_id), - allow.cartesian = TRUE, - mult = "last"] -``` - -#### 3.6.2. Jointure croisée - -Si vous voulez obtenir **toutes les combinaisons possibles de lignes** quelque soit l'id de colonne, vous pouvez suivre le processus suivant : - -1. Créer une nouvelle colonne dans les deux tables avec une constante. - -```{r} -ProductsTempId = copy(Products)[, temp_id := 1L] -``` - -2. Joindre les deux tables en fonction de la nouvelle colonne et supprimer cette dernnière à la fin de la manipulation parce qu'il n'y a pas de raison de la garder. - -```{r} -AllProductsMix = - ProductsTempId[ProductsTempId, - on = "temp_id", - allow.cartesian = TRUE] - -AllProductsMix[, temp_id := NULL] - -# Removing type to make easier to see the result when printing the table -AllProductsMix[, !c("type", "i.type")] -``` - - -### 3.7. Jointure complète - -Utilisez cette méthode pour combiner les colonnes de deux tables en se basant sur une ou plusieurs références mais ***sans supprimer aucune ligne***. - -Comme vu dans la section précédente, toutes les opérations ci-avant peuvent conserver le `product_id = 6` absent et le **soda** (`product_id = 4`) comme faisant partie du résultat. - -Pour résoudre ce problème nous pouvons utiliser la fonction `merge` bien qu'elle soit moins préférable à l'utilisation de la syntaxe de jointure native de `data.table`. - -```{r} -merge(x = Products, - y = ProductReceived, - by.x = "id", - by.y = "product_id", - all = TRUE, - sort = FALSE) -``` - - -## 4. Jointure de non équivalence - -Une jointure de non équivalence est un type de jointure où la condition pour sélectionner les lignes n'est pas basée sur une égalité mais sur d'autres opérateurs de comparaison tels que <, >, <=, ou >=. Ceci permet des **critères plus flexibles de jointure**. Dans `data.table`, le jointures non équivalentes sont particulièrement utiles pour les opérations telles que : - -- Rechercher la correspondance la plus proche -- Comparer des intervalles de valeurs entre deux tables - -C'est une alternative intéressante si, après avoir fait une jointure droite ou interne : - -- Vous souhaitez diminuer le nombre de lignes renvoyées en fonction du résultat de la comparaison des colonnes numériques de tables différentes. -- Il n'est pas nécessaire de garder les colonnes de la table x *(data.table secondaire)* dans la table finale. - -Pour illustrer le fonctionnement, concentrons-nous sur les promotions et les réceptions de product 2. - -```{r} -ProductSalesProd2 = ProductSales[product_id == 2L] -ProductReceivedProd2 = ProductReceived[product_id == 2L] -``` - -Si l'on veut savoir par exemple si quelque chose a été reçu avant la date des promotions, nous pouvons utiliser le code suivant. - -```{r} -ProductReceivedProd2[ProductSalesProd2, - on = "product_id", - allow.cartesian = TRUE -][date < i.date] -``` - -Que se passe-t-il si nous appliquons simplement la même logique à la liste passée à `on` ? - -- Comme cette opération est encore une jointure droite, elle renvoie toutes les lignes de la table `i` , mais n'affiche que les valeurs de `id` et `count` lorsque les règles sont vérifiées. - -- La date correspondant à `ProductReceivedProd2` a été omise de cette nouvelle table. - -```{r} -ProductReceivedProd2[ProductSalesProd2, - on = list(product_id, date < date)] -``` - -Maintenant après avoir appliqué la jointure, nous pouvons limiter les résultats pour n'afficher que les cas qui satisfont tous les critères de jointure. - -```{r} -ProductReceivedProd2[ProductSalesProd2, - on = list(product_id, date < date), - nomatch = NULL] -``` - -### 4.1 Noms des colonnes de sortie dans les jointures non équivalentes - -Lorsque vous réalisez des jointures non équivalentes (`<`, `>`, `<=`, `>=`), le nom des colonnes est assigné ainsi : - -- L'opérande de gauche (`x` column) determine le nom de la colonne du résultat. -- L'opérande de droite (`i` column) contribue avec ses valeurs mais ne garde pas son nom d'origine. -- Par défaut, `data.table` ne conserve pas la colonne `i` utilisée dans la condition du join sauf si vous le demandez explicitement. - -Dans les jointures non équivalentes, le côté gauche de l'opérateur (comme `x_int` dans `x_int >= i_int`) doit être une colonne de `x`, alors que le côté droit (ici `i_int`) doit être une colonne de `i`. - -Les jointures non équivalentes ne reconnaissent pas actuellement les expressions arbitraires (mais voir [#1639](https://github.com/Rdatatable/data.table/issues/1639)). Par exemple, `on = .(x_int >= i_int)` est correct, mais `on = .(x_int >= i_int + 1L)` n'est pas accepté. Pour réaliser une telle jointure non équivalente, ajoutez d'abord l'expression en tant que nouvelle colonne, comme `i[, i_int_plus_one := i_int + 1L]`, puis faites `.on(x_int >= i_int_plus_one)`. - -```{r non_equi_join_example} -x <- data.table(x_int = 2:4, lower = letters[1:3]) -i <- data.table(i_int = c(2L, 4L, 5L), UPPER = LETTERS[1:3]) -x[i, on = .(x_int >= i_int)] -``` - -Remarques clé : - -- Le nom de la colonne de sortie (`x_int`) vient de `x`, mais les valeurs viennent de `i_int` dans `i`. -- La dernière ligne contient `NA` car aucune ligne de `x` ne correspond à la dernière ligne de `i` (`UPPER == "C"`). -- Les lignes multiples de `x` sont renvoyées pour correspondre à la première ligne de `i` pour laquelle `UPPER == "A"`. - -Si vous souhaitez conserver la colonne `i_int` de `i`, vous devez la sélectionner explicitement dans le résultat : - -```{r retain_i_column} -x[i, on = .(x_int >= i_int), .(i_int = i.i_int, x_int = x.x_int, lower, UPPER)] -``` - -Il n'est pas strictement nécessaire d'utiliser les préfixes (`x.` et `i.`) dans ce cas car les noms ne sont pas ambigüs, mais en les utilisant vous verrez clairement dans la sortie la distinction entre `i_int` (de `i`) et `x_int` (de `x`). - -Pour exclure les lignes qui ne correspondent pas (une _joointure interne_), utiliser `nomatch = NULL`: - -```{r retain_i_column_inner_join} -x[i, on = .(x_int >= i_int), .(i_int = i.i_int, x_int = x.x_int, lower, UPPER), nomatch = NULL] -``` - -## 5. Jointure glissante - -Les jointures glissantes sont particulièrement utiles si vous faites des analyses de données sur des séries temporelles. Elles permettent de **prendre en compte les lignes basées sur la valeur la plus proche** dans une colonne triée, typiquement une colonne avec une date ou un horodatage. - -C'est utile lorsque vous avez besoin d'aligner des données de sources différentes **qui n'ont pas forcément les mêmes horodatages**, ou si vous souhaitez continuer avec la valeur la plus récente. - -Par exemple, avec des données financières, vous pourriez utiliser une jointure glissante pour assigner la valeur la plus récente d'une action à chaque transaction, même si les mises à jour du prix et les transactions ne correspondent pas exactement aux mêmes instants. - - -Dans notre exemple de supermarché nous pouvons utiliser une jointure glissante pour correspondre aux promotions avec les informations de produit les plus récentes. - -Supposons que le prix des bananes et des carottes change le premier jour de chaque mois. - -```{r} -ProductPriceHistory = data.table( - product_id = rep(1:2, each = 3), - date = rep(as.IDate(c("2024-01-01", "2024-02-01", "2024-03-01")), 2), - price = c(0.59, 0.63, 0.65, # Banana prices - 0.79, 0.89, 0.99) # Carrot prices -) - -ProductPriceHistory -``` - -Maintenant nous pouvons réaliser une jointure droite en donnant un prix différent à chaque produit en fonction de la date de promotion. - -```{r} -ProductPriceHistory[ProductSales, - on = .(product_id, date), - roll = TRUE, - j = .(product_id, date, count, price)] -``` - -Si nous voulons simplement voir les cas de correspondance, il suffit d'ajouter l'argument `nomatch = NULL` pour réaliser une jointure glissante interne. - -```{r} -ProductPriceHistory[ProductSales, - on = .(product_id, date), - roll = TRUE, - nomatch = NULL, - j = .(product_id, date, count, price)] -``` - -## 7. Avantage de la vitesse de jointure - -### 7.1. Sous-ensembles en tant que jointures - -Comme nous venons de le voir, dans la section précédente la table `x` est filtrée par les valeurs de la table `i` . Actuellement cette méthode est plus rapide que de passer une expression booléenne dans l'argument `i`. - -Pour filtrer la table `x` rapidement nous ne passons pas la `data.table` entière, nous pouvons passer une `list()` de vecteurs avec les valeurs de la table originale que nous voulons garder ou omettre. - -Par exemple pour filtrer les dates auxquelles le marché a reçu 100 unités de bananes (`product_id = 1`) ou de popcorn (`product_id = 3`) nous pouvons utiliser ceci : - -```{r} -ProductReceived[list(c(1L, 3L), 100L), - on = c("product_id", "count")] -``` - -Comme à la fin nous filtrons sur la base d'une opération de jointure, le code a renvoyé une **ligne absente de la table d'origine**. Pour éviter ce comportement il est recommandé de toujours ajouter l'argument `nomatch = NULL`. - -```{r} -ProductReceived[list(c(1L, 3L), 100L), - on = c("product_id", "count"), - nomatch = NULL] -``` - - -Nous pouvons aussi utiliser cette technique pour filtrer toute combinaison de valeurs en les préfixant avec `!` pour obtenir la négation de l'expression dans l'argument `i` et en gardant le `nomatch` à sa valeur par défaut. Par exemple nous pouvons filtrer les deux lignes filtrées précédemment. - -```{r} -ProductReceived[!list(c(1L, 3L), 100L), - on = c("product_id", "count")] -``` - -Si vous voulez simplement filtrer une valeur pour une **colonne de caractères** seule, vous pouvez ne pas appeler la fonction `list()` et passer la valeur pour être filtrée dans l'argument `i`. - -```{r} -Products[c("banana","popcorn"), - on = "name", - nomatch = NULL] - -Products[!"popcorn", - on = "name"] -``` - -### 7.2. Mise à jour par référence - -L'opérateur `:=` de data.table est utilisé pour modifier des colonnes par référence (c'est à dire sans recopie) lors de la jointure. Syntaxe générale : `x[i, on=, (cols) := val]`. - -**Mise à jour Simple un à un** - -Mise à jour de `Products` avec les prix de `ProductPriceHistory` : - -```{r} -Products[ProductPriceHistory, - on = .(id = product_id), - price := i.price] - -Products -``` - -- `i.price` est le prix dans `ProductPriceHistory`. -- on modifie directement le contenu de `Products`. - -**Mises à jour groupées avec `.EACHI`** - -Obtenir le dernier prix et la date pour chaque produit : - -```{r Updating_with_the_Latest_Record} -Products[ProductPriceHistory, - on = .(id = product_id), - `:=`(price = last(i.price), last_updated = last(i.date)), - by = .EACHI] - -Products -``` - -- `by = .EACHI` groupe les lignes de i (un groupe ar ligne ProductPriceHistory). -- `last()` renvoie la dernière valeur - -**Mise à jour efficace par jointure droite** - -Ajouter les détails des produits dans `ProductPriceHistory` sans recopier : - -```{r} -cols <- setdiff(names(Products), "id") -ProductPriceHistory[, (cols) := - Products[.SD, on = .(id = product_id), .SD, .SDcols = cols]] -setnafill(ProductPriceHistory, fill=0, cols="price") # Handle missing values - -ProductPriceHistory -``` - -- Dans `i`, `.SD` référence `ProductPriceHistory`. -- Dans `j`, `.SD` référence `Products`. -- `:=` et `setnafill()` mettent ensemble à jour `ProductPriceHistory` par référence. - -## Référence - -- *Comprendre les jointures glissantes de data.table* : https://www.r-bloggers.com/2016/06/understanding-data-table-rolling-joins/ - -- *Demi-jointure avec data.table* : https://stackoverflow.com/questions/18969420/perform-a-semi-join-with-data-table - -- *Jointure croisée avec with data.table* : https://stackoverflow.com/questions/10600060/how-to-do-cross-join-in-r - -- *Comment réaliser une jointure complète à l'aide de data.table ?* : https://stackoverflow.com/questions/15170741/how-does-one-do-a-full-join-using-data-table - -- *data.frame étendu* : https://rdatatable.gitlab.io/data.table/reference/data.table.html +--- +title: "Jointures avec data.table" +date: "`r Sys.Date()`" +output: + markdown::html_format +vignette: > + %\VignetteIndexEntry{Jointures avec data.table} + %\VignetteEngine{knitr::knitr} + \usepackage[utf8]{inputenc} +editor_options: + chunk_output_type: console +--- + +```{r, echo = FALSE, message = FALSE} +require(data.table) +knitr::opts_chunk$set( + comment = "#", + error = FALSE, + tidy = FALSE, + cache = FALSE, + collapse = TRUE +) +``` + +```{r, echo=FALSE, file='../_translation_links.R'} +``` +`r .write.translation.links("Une traduction de ce document est disponible en : %s")` + +Dans cette vignette nous apprendrons à réaliser les opérations de jointure en utilisant les ressources de la syntaxe `data.table`. + +Cela suppose que vous êtes déjà familiarisé avec la syntaxe `data.table` . Si ce n'est pas le cas, reportez-vous aux vignettes suivantes : + +- [`vignette("datatable-intro", package="data.table")`](datatable-intro.html) +- [`vignette("datatable-reference-semantics", package="data.table")`](datatable-reference-semantics.html) +- [`vignette("datatable-keys-fast-subset", package="data.table")`](datatable-keys-fast-subset.html) + +*** + +## 1. Définissons nos données d'exemple + +Pour illustrer la méthode proposée avec des exemples de la vie courante, nous allons simuler une **base de données normalisée** à partir d'un petit supermarché en définissant les tables suivantes dans une base de données : + +1. `Products`, une table où chaque ligne donne les caractéristiques de différents produits. Pour montrer comment l'environnement réagit avec les ***valeurs absentes***, nous laissons un `id` à NA. + +```{r, define_products} +Products = rowwiseDT( + id=, name=, price=, unit=, type=, + 1L, "banana", 0.63, "unit", "natural", + 2L, "carrots", 0.89, "lb", "natural", + 3L, "popcorn", 2.99, "unit", "processed", + 4L, "soda", 1.49, "ounce", "processed", + NA, "toothpaste", 2.99, "unit", "processed" +) +``` + +2. `NewTax`, une table donnant le pourcentage des taxes à appliquer aux produits traités en fonction de leurs unités. + +```{r define_new_tax} +NewTax = data.table( + unit = c("unit", "ounce"), + type = "processed", + tax_prop = c(0.65, 0.20) +) + +NewTax +``` + + +3. `ProductReceived`, une table dont les lignes simulent l'inventaire des ventes hebdomadaires. + +```{r define_product_received} +set.seed(2156) + +# NB: le 8 janvier 2024 est un lundi. +receipt_dates = seq(from=as.IDate("2024-01-08"), length.out=10L, by="week") + +ProductReceived = data.table( + id=1:10, # identifiant unique pour suivre une transaction + date=receipt_dates, + product_id=sample(c(NA, 1:3, 6L), size=10L, replace=TRUE), # NB: le produit '6' n'existe pas dans Products. + count=sample(c(50L, 100L, 150L), size=10L, replace=TRUE) +) + +ProductReceived +``` + +4. `ProductSales`, une table dont les lignes simulent les transactions des clients. + +```{r define_product_sales} +set.seed(5415) + +# lundi-vendredi (4 jours plus tard) pour chaque semaine présente dans ProductReceived +possible_weekdays <- as.IDate(sapply(receipt_dates, `+`, 0:4)) + +ProductSales = data.table( + id = 1:10, + date = sort(sample(possible_weekdays, 10L)), + product_id = sample(c(1:3, 7L), size = 10L, replace = TRUE), # NB: product '7' is in neither Products nor ProductReceived. + count = sample(c(50L, 100L, 150L), size = 10L, replace = TRUE) +) + +ProductSales +``` + +## 2. Syntaxe de la jointure `data.table` + +Avant de voir les avantages de la syntaxe `data.table` pour faire des opérations de jointure nous devons savoir quels arguments peuvent nous aider à les réaliser avec succès. + +Le diagramme suivant affiche une description pour chaque argument de base. Dans les sections suivantes nous verrons progressivement comment les utiliser tout en y apportant un peu de complexité à chaque fois. + +``` +x[i, on, nomatch] +| | | | +| | | \__ Si NULL, renvoie seulement les lignes liées des tables x et i +| | \____ vecteur de caractères ou liste définissant la logique de correspondance +| \_____ data.table primaire, liste ou data.frame +\____ data.table secondaire +``` + +**Note** : n'oubliez pas que l'ordre standard des arguments dans data.table est `dt[i, j, by]`. Pour les opérations de jointure, il est recommandé de passer les arguments `on` et `nomatch` par nom pour éviter d'utiliser `j` et `by` quand ce n'est pas nécessaire. + +## 3. Jointures équilibrées + +C'est le cas le plus général et facile où il existe des éléments communs entre les tables à combiner. + +La relation entre les tables peut être : + +- **De un vers un** : lorsque chaque valeur sélectionnée est unique dans chaque table. +- **De un vers plusieurs** : lorsque certaines valeurs sélectionnées sont répétées dans une table et à la fois uniques dans l'autre. +- **De plusieurs vers plusieurs** : lorsque les valeurs sélectionnées sont répétées plusieurs fois dans chaque table. + +Dans la plupart des exemples suivants nous réaliserons des correspondances *de un vers plusieurs* , mais nous prendrons aussi le temps d'expliquer les ressources disponibles pour réaliser les correspondances *de plusieurs vers plusieurs* . + + +### 3.1. Jointure droite + +Utilisez cette méthode pour combiner les colonnes de deux tables en se basant sur une ou plusieurs références mais ***en gardant toutes les lignes présentes dans la table située à droite (entre crochets)***. + +Dans le contexte de notre supermarché nous pouvons réaliser une jointure droite pour avoir davantage de détails à propos des produits reçus car c'est une relation de *un vers plusieurs* en passant un vecteur dans l'argument `on`. + +```{r} +Products[ProductReceived, + on = c(id = "product_id")] +``` + +Comme beaucoup de choses ont changé, nous allons expliquer les nouvelles caractéristiques dans les groupes suivants : + +- **Niveau colonne** + - Le *premier groupe* de colonnes dans le nouveau data.table vient de la table `x` . + - Le *second groupe* de colonnes de la nouvelle data.table vient de la table `i`. + - Si l'opération de jointure fait apparaître un **conflit de nom** (quand les deux tables ont un même nom de colonne) le ***prefixe*** `i.` est ajouté aux noms des colonnes de la **table de droite** (table en position `i`). + +- **Niveau ligne** + - Le `product_id` qui est absent, mais présent dans la table `ProductReceived` à la ligne 1 a été sélectionné avec le `id` absent de la table `Products`, donc ***les valeurs `NA` sont traitées comme toute autre valeur***. + - Toutes les lignes de la table `i` ont été gardées y compris : + - Les lignes qui ne correspondent pas, comme celle avec `product_id = 6`. + - Lignes qui répètent le même `product_id` plusieurs fois. + +#### 3.1.1. Jointure avec un argument de liste + +Si vous suivez la vignette, vous avez pu voir que nous avons utilisé un vecteur pour définir les relations entre les tables dans l'argument `on`, ce qui réellement utile si vous **créez vos propres fonctions**, mais une autre alternative est d'utiliser une **liste** pour définir les colonnes à sélectionner. + +Pour utiliser cette possibilité, nous avons a disposition les alternatives suivantes : + +- Inclure les colonnes associées dans la fonction R de base `list` . + +```{r, eval=FALSE} +Products[ProductReceived, + on = list(id = product_id)] +``` + +- Inclure les colonnes associées dans l'alias `data.table` `list` : `.`. + +```{r, eval=FALSE} +Products[ProductReceived, + on = .(id = product_id)] +``` + +#### 3.1.2. Alternatives pour définir l'argument `on` + +Dans tous les exemples précédents, nous avons passé le nom des colonnes à sélectionner avec l'argument `on` mais `data.table` possède également des alternatives à cette syntaxe. + +- **Jointure naturelle** : sélectionne les colonnes pour réaliser la correspondance en fonction des noms des colonnes en commun. Pour illustrer cette méthode, modifions la colonne de la table `Products` de `id` en `product_id` et utilisons le mot clé `.NATURAL`. + +```{r} +ProductsChangedName = setnames(copy(Products), "id", "product_id") +ProductsChangedName + +ProductsChangedName[ProductReceived, on = .NATURAL] +``` + +- **Jointure par clé** : sélectionne les colonnes pour rechercher la correspondance en fonction des colonnes clé quelque soit leur nom. Pour illustrer cette méthode, nous devons définir les clés dans le même ordre pour les deux tables. + +```{r} +ProductsKeyed = setkey(copy(Products), id) +key(ProductsKeyed) + +ProductReceivedKeyed = setkey(copy(ProductReceived), product_id) +key(ProductReceivedKeyed) + +ProductsKeyed[ProductReceivedKeyed] +``` + +#### 3.1.3. Opérations après la jointure + +La plupart du temps après avoir terminé une jointure il faut faire des adaptations supplémentaires. Pour cela plusieurs alternatives vous sont proposées : + +- Chaîner une nouvelle instruction en ajoutant une paire de crochets `[]`. +- En passant comme argument `j` une liste des colonnes que l'on veut conserver ou créer. + +Notre recommendation est d'utiliser la seconde alternative si possible, car elle est **plus rapide** et demande **moins de mémoire** que la première. + +##### Gestion de la colonne partagée Names avec l'argument j + +L'argument `j` autorise plusieurs alternatives intéressantes pour gérer les jointures avec les tables en **partageant les mêmes noms pour plusieurs colonnes**. Par défaut toutes les colonnes prennent leur source dans la table `x`, mais nous pouvons aussi utiliser le préfixe `x.` pour clarifier la source et utiliser le préfixe `i.` pour toutes les colonnes de la table déclarée dans l'argument `i` de la table `x`. + +Si nous retournons au petit supermarché, après avoir mis à jour la table `ProductReceived` avec la table `Products` , supposez que l'on veuille appliquer les modifications suivantes : + +- Modifier le nom des colonnes de `id` en `product_id` et de `i.id` en `received_id`. +- Ajouter `total_value`. + +```{r} +Products[ + ProductReceived, + on = c("id" = "product_id"), + j = .(product_id = x.id, + name = x.name, + price, + received_id = i.id, + date = i.date, + count, + total_value = price * count) +] +``` + + +##### Résumer avec`on` dans `data.table` + +Nous pouvons aussi utiliser cette alternative pour renvoyer les résultats agrégés en fonction des colonnes présentes dans la table `x` . + +Par exemple on pourrait s'intéresser à la somme dépensée pour acheter chaque produit au fil des jours, quelque soient ces produits. + +```{r} +dt1 = ProductReceived[ + Products, + on = c("product_id" = "id"), + by = .EACHI, + j = .(total_value_received = sum(price * count)) +] + +# alternative using multiple [] queries +dt2 = ProductReceived[ + Products, + on = c("product_id" = "id"), +][, .(total_value_received = sum(price * count)), + by = "product_id" +] + +identical(dt1, dt2) +``` + +#### 3.1.4. Jointure basée sur plusieurs colonnes + +Jusqu'à présent, nous avons réalisé les jointures en se basant sur une colonne `data.table`, mais il est important de savoir que le package peut joindre des tables en prenant en compte plusieurs colonnes. + +Pour illustrer cela supposons que nous voulions ajouter `tax_prop` de `NewTax` pour **mettre à jour** la table `Products`. + +```{r} +NewTax[Products, on = c("unit", "type")] +``` + +### 3.2. Jointure interne + +Utilisez cette méthode pour combiner les colonnes de deux tables en se basant sur une ou plusieurs références mais ***en conservant seulement les lignes qui correspondent entre les deux tables***. + +Pour réaliser cette opération il suffit d'ajouter `nomatch = NULL` ou `nomatch = 0` à l'une quelconque des opérations de jointure précédentes pour renvoyer le même résultat. + +```{r} +# First Table +Products[ProductReceived, + on = c("id" = "product_id"), + nomatch = NULL] + +# Second Table +ProductReceived[Products, + on = .(product_id = id), + nomatch = NULL] +``` + +Même si les deux tables contiennent la même information, il existe quelques différences importantes : + +- L'ordre des colonnes est différent +- Elles comportent certaines différences de noms dans le nom des colonnes : + - La colonne `id` de la première table contient les mêmes informations que `product_id` de la seconde table. + - La colonne `i.id` de la première table a les mêmes informations que `id` de la seconde table. + +### 3.3. Anti-jointure + +Cette méthode **ne garde que les lignes qui ne correspondent pas aux lignes de la seconde table**. + +Pour appliquer cette technique il suffit d'utiliser la négation (`!`) sur la table qui se trouve dans l'argument `i` . + +```{r} +Products[!ProductReceived, + on = c("id" = "product_id")] +``` + +Comme on peut le voir, le résultat ne comporte que 'soda' car c'est le seul produit qui ne figure pas dans la table `ProductReceived`. + +```{r} +ProductReceived[!Products, + on = c("product_id" = "id")] +``` + +Dans ce cas l'opération renvoie la ligne de `product_id = 6,` car il ne figure pas dans la table `Products`. + +### 3.4. Semi jointure + +Cette méthode **ne garde que les lignes qui correspondent à une ligne de la seconde table** sans combiner les colonnes des tables. + +En tant que jointure ceci est très similaire aux sous-ensembles, mais comme cette fois nous passons une table complète dans `i` nous devons vérifier que : + +- Chaque ligne de la table `x` est dupliquée à cause de la duplication des lignes dans la table passée dans l'argument `i`. + +- Toutes les lignes renommées de `x` doivent conserver l'ordre originel des lignes. + + +Pour faire ceci, suivez les étapes ci-après : + +1. Réaliser une **jointure interne** avec `which = TRUE` pour sauvegarder les numéros de ligne liés à chaque ligne sélectionnée de la table `x` . + +```{r} +SubSetRows = Products[ + ProductReceived, + on = .(id = product_id), + nomatch = NULL, + which = TRUE +] + +SubSetRows +``` + +2. Sélectionner et trier les id uniques de lignes. + +```{r} +SubSetRowsSorted = sort(unique(SubSetRows)) + +SubSetRowsSorted +``` + + +3. Sélectionner les lignes `x` à garder. + +```{r} +Products[SubSetRowsSorted] +``` + + +### 3.5. Jointure gauche + +Utiliser cette méthode pour combiner les colonnes de deux tables en se basant sur une ou plusieurs références mais ***en gardant toutes les lignes présentes dans la table située à gauche***. + +Pour réaliser cette opération, il suffit d'**échanger l'ordre entre les deux tables** ainsi que le nom des colonnes dans l'argument `on`. + +```{r} +ProductReceived[Products, + on = list(product_id = id)] +``` + +Voici les éléments importants à prendre en compte : + +- **Niveau colonne** + - Le *premier groupe* de colonnes provient maintenant de la table `ProductReceived` car il est la table `x` . + - Le *second groupe* de colonnes provient maintenant de la table `Products` car il est la table `i` . + - Le préfixe `i.` n'a été ajouté à aucune colonne. + +- **Niveau ligne** + - Toutes les lignes de la table `i` ont été gardées : l'entrée soda de `Products` ne correspond à aucune ligne de `ProductReceived` et fait encore partie des résultats. + - La ligne concernant `product_id = 6` ne fait plus partie des résultats car elle n'est pas présente dans la table `Products`. + + +#### 3.5.1. Jointure après des opérations sur les chaînes + +Une des fonctionnalités clé de `data.table` est que l'on peut appliquer plusieurs opérations en chaînant les crochets, avant d'enregistrer le résultat final. + +```r +DT[ + ... +][ + ... +][ + ... +] +``` + +Jusqu'à présent, si après avoir exécuté toutes ces opérations **nous souhaitons joindre de nouvelles colonnes sans supprimer aucune ligne**, il faut arrêter le processus de chaînage, sauvegarder dans une table temporaire puis effectuer ultérieurement l'opération de jointure. + +Pour éviter cette situation, nous pouvons utiliser le symbole spécial `.SD`, pour appliquer une **jointure droite en fonction de la table modifiée**. + +```{r} +NewTax[Products, + on = c("unit", "type") +][, ProductReceived[.SD, + on = list(product_id = id)], + .SDcols = !c("unit", "type")] +``` + +### 3.6. Jointure de plusieurs à plusieurs + +Quelques fois nous voulons joindre des tables en se basant sur les colonnes ayant des **valeurs `id` dupliquées** pour faires des transformations ultérieurement. + +Pour illustrer cette situation, prenons par exemple le `product_id == 1L`, qui a quatre lignes dans notre table `ProductReceived` . + +```{r} +ProductReceived[product_id == 1L] +``` + +Et quatre lignes dans notre table `ProductSales`. + +```{r} +ProductSales[product_id == 1L] +``` + +Pour réaliser cette jointure il nous suffit de filtrer `product_id == 1L` dans la table `i` pour limiter la jointure uniquement à ce produit et déclarer l'argument `allow.cartesian = TRUE` pour permettre la combinaison de chaque ligne d'une table avec chaque ligne de l'autre table. + +```{r} +ProductReceived[ProductSales[list(1L), + on = "product_id", + nomatch = NULL], + on = "product_id", + allow.cartesian = TRUE] +``` + +Une fois que nous avons compris les résultats, nous pouvont appliquer le même processus à **tous les produits**. + +```{r} +ProductReceived[ProductSales, + on = "product_id", + allow.cartesian = TRUE] +``` + +**Note** : `allow.cartesian` vaut par défaut FALSE car c'est ce que l'utilisateur a souhaité, et une telle jointure croisée peut conduire à un très grand nombre de lignes dans le résultat. Par exemple, si Table A possède 100 lignes et Table +B en a 50, leur produit cartésien sera de 5000 lignes (100 * 50). Ce qui peut rapidement accroître la mémoire occupée pour de grands ensembles de données. + +#### 3.6.1. Selection d'une seule correspondance + +Après avoir fait la jointure de la table, nous pouvons voir que l'on peut utiliser une seule jointure pour extraire les informations nécessaires. Dans ce cas il y a deux alternatives : + +- Nous pouvons sélectionner la **première correspondance**, représentée dans l'exemple suivant par `id = 2`. + +```{r} +ProductReceived[ProductSales[product_id == 1L], + on = .(product_id), + allow.cartesian = TRUE, + mult = "first"] +``` + +- Nous pouvons sélectionner la **dernière correspondance**, représentée dans l'exemple suivant par `id = 9`. + +```{r} +ProductReceived[ProductSales[product_id == 1L], + on = .(product_id), + allow.cartesian = TRUE, + mult = "last"] +``` + +#### 3.6.2. Jointure croisée + +Si vous voulez obtenir **toutes les combinaisons possibles de lignes** quelque soit l'id de colonne, vous pouvez suivre le processus suivant : + +1. Créer une nouvelle colonne dans les deux tables avec une constante. + +```{r} +ProductsTempId = copy(Products)[, temp_id := 1L] +``` + +2. Joindre les deux tables en fonction de la nouvelle colonne et supprimer cette dernnière à la fin de la manipulation parce qu'il n'y a pas de raison de la garder. + +```{r} +AllProductsMix = + ProductsTempId[ProductsTempId, + on = "temp_id", + allow.cartesian = TRUE] + +AllProductsMix[, temp_id := NULL] + +# Removing type to make easier to see the result when printing the table +AllProductsMix[, !c("type", "i.type")] +``` + + +### 3.7. Jointure complète + +Utilisez cette méthode pour combiner les colonnes de deux tables en se basant sur une ou plusieurs références mais ***sans supprimer aucune ligne***. + +Comme vu dans la section précédente, toutes les opérations ci-avant peuvent conserver le `product_id = 6` absent et le **soda** (`product_id = 4`) comme faisant partie du résultat. + +Pour résoudre ce problème nous pouvons utiliser la fonction `merge` bien qu'elle soit moins préférable à l'utilisation de la syntaxe de jointure native de `data.table`. + +```{r} +merge(x = Products, + y = ProductReceived, + by.x = "id", + by.y = "product_id", + all = TRUE, + sort = FALSE) +``` + + +## 4. Jointure de non équivalence + +Une jointure de non équivalence est un type de jointure où la condition pour sélectionner les lignes n'est pas basée sur une égalité mais sur d'autres opérateurs de comparaison tels que <, >, <=, ou >=. Ceci permet des **critères plus flexibles de jointure**. Dans `data.table`, le jointures non équivalentes sont particulièrement utiles pour les opérations telles que : + +- Rechercher la correspondance la plus proche +- Comparer des intervalles de valeurs entre deux tables + +C'est une alternative intéressante si, après avoir fait une jointure droite ou interne : + +- Vous souhaitez diminuer le nombre de lignes renvoyées en fonction du résultat de la comparaison des colonnes numériques de tables différentes. +- Il n'est pas nécessaire de garder les colonnes de la table x *(data.table secondaire)* dans la table finale. + +Pour illustrer le fonctionnement, concentrons-nous sur les promotions et les réceptions de product 2. + +```{r} +ProductSalesProd2 = ProductSales[product_id == 2L] +ProductReceivedProd2 = ProductReceived[product_id == 2L] +``` + +Si l'on veut savoir par exemple si quelque chose a été reçu avant la date des promotions, nous pouvons utiliser le code suivant. + +```{r} +ProductReceivedProd2[ProductSalesProd2, + on = "product_id", + allow.cartesian = TRUE +][date < i.date] +``` + +Que se passe-t-il si nous appliquons simplement la même logique à la liste passée à `on` ? + +- Comme cette opération est encore une jointure droite, elle renvoie toutes les lignes de la table `i` , mais n'affiche que les valeurs de `id` et `count` lorsque les règles sont vérifiées. + +- La date correspondant à `ProductReceivedProd2` a été omise de cette nouvelle table. + +```{r} +ProductReceivedProd2[ProductSalesProd2, + on = list(product_id, date < date)] +``` + +Maintenant après avoir appliqué la jointure, nous pouvons limiter les résultats pour n'afficher que les cas qui satisfont tous les critères de jointure. + +```{r} +ProductReceivedProd2[ProductSalesProd2, + on = list(product_id, date < date), + nomatch = NULL] +``` + +### 4.1 Noms des colonnes de sortie dans les jointures non équivalentes + +Lorsque vous réalisez des jointures non équivalentes (`<`, `>`, `<=`, `>=`), le nom des colonnes est assigné ainsi : + +- L'opérande de gauche (`x` column) determine le nom de la colonne du résultat. +- L'opérande de droite (`i` column) contribue avec ses valeurs mais ne garde pas son nom d'origine. +- Par défaut, `data.table` ne conserve pas la colonne `i` utilisée dans la condition du join sauf si vous le demandez explicitement. + +Dans les jointures non équivalentes, le côté gauche de l'opérateur (comme `x_int` dans `x_int >= i_int`) doit être une colonne de `x`, alors que le côté droit (ici `i_int`) doit être une colonne de `i`. + +Les jointures non équivalentes ne reconnaissent pas actuellement les expressions arbitraires (mais voir [#1639](https://github.com/Rdatatable/data.table/issues/1639)). Par exemple, `on = .(x_int >= i_int)` est correct, mais `on = .(x_int >= i_int + 1L)` n'est pas accepté. Pour réaliser une telle jointure non équivalente, ajoutez d'abord l'expression en tant que nouvelle colonne, comme `i[, i_int_plus_one := i_int + 1L]`, puis faites `.on(x_int >= i_int_plus_one)`. + +```{r non_equi_join_example} +x <- data.table(x_int = 2:4, lower = letters[1:3]) +i <- data.table(i_int = c(2L, 4L, 5L), UPPER = LETTERS[1:3]) +x[i, on = .(x_int >= i_int)] +``` + +Remarques clé : + +- Le nom de la colonne de sortie (`x_int`) vient de `x`, mais les valeurs viennent de `i_int` dans `i`. +- La dernière ligne contient `NA` car aucune ligne de `x` ne correspond à la dernière ligne de `i` (`UPPER == "C"`). +- Les lignes multiples de `x` sont renvoyées pour correspondre à la première ligne de `i` pour laquelle `UPPER == "A"`. + +Si vous souhaitez conserver la colonne `i_int` de `i`, vous devez la sélectionner explicitement dans le résultat : + +```{r retain_i_column} +x[i, on = .(x_int >= i_int), .(i_int = i.i_int, x_int = x.x_int, lower, UPPER)] +``` + +Il n'est pas strictement nécessaire d'utiliser les préfixes (`x.` et `i.`) dans ce cas car les noms ne sont pas ambigüs, mais en les utilisant vous verrez clairement dans la sortie la distinction entre `i_int` (de `i`) et `x_int` (de `x`). + +Pour exclure les lignes qui ne correspondent pas (une _joointure interne_), utiliser `nomatch = NULL`: + +```{r retain_i_column_inner_join} +x[i, on = .(x_int >= i_int), .(i_int = i.i_int, x_int = x.x_int, lower, UPPER), nomatch = NULL] +``` + +## 5. Jointure glissante + +Les jointures glissantes sont particulièrement utiles si vous faites des analyses de données sur des séries temporelles. Elles permettent de **prendre en compte les lignes basées sur la valeur la plus proche** dans une colonne triée, typiquement une colonne avec une date ou un horodatage. + +C'est utile lorsque vous avez besoin d'aligner des données de sources différentes **qui n'ont pas forcément les mêmes horodatages**, ou si vous souhaitez continuer avec la valeur la plus récente. + +Par exemple, avec des données financières, vous pourriez utiliser une jointure glissante pour assigner la valeur la plus récente d'une action à chaque transaction, même si les mises à jour du prix et les transactions ne correspondent pas exactement aux mêmes instants. + + +Dans notre exemple de supermarché nous pouvons utiliser une jointure glissante pour correspondre aux promotions avec les informations de produit les plus récentes. + +Supposons que le prix des bananes et des carottes change le premier jour de chaque mois. + +```{r} +ProductPriceHistory = data.table( + product_id = rep(1:2, each = 3), + date = rep(as.IDate(c("2024-01-01", "2024-02-01", "2024-03-01")), 2), + price = c(0.59, 0.63, 0.65, # Banana prices + 0.79, 0.89, 0.99) # Carrot prices +) + +ProductPriceHistory +``` + +Maintenant nous pouvons réaliser une jointure droite en donnant un prix différent à chaque produit en fonction de la date de promotion. + +```{r} +ProductPriceHistory[ProductSales, + on = .(product_id, date), + roll = TRUE, + j = .(product_id, date, count, price)] +``` + +Si nous voulons simplement voir les cas de correspondance, il suffit d'ajouter l'argument `nomatch = NULL` pour réaliser une jointure glissante interne. + +```{r} +ProductPriceHistory[ProductSales, + on = .(product_id, date), + roll = TRUE, + nomatch = NULL, + j = .(product_id, date, count, price)] +``` + +## 7. Avantage de la vitesse de jointure + +### 7.1. Sous-ensembles en tant que jointures + +Comme nous venons de le voir, dans la section précédente la table `x` est filtrée par les valeurs de la table `i` . Actuellement cette méthode est plus rapide que de passer une expression booléenne dans l'argument `i`. + +Pour filtrer la table `x` rapidement nous ne passons pas la `data.table` entière, nous pouvons passer une `list()` de vecteurs avec les valeurs de la table originale que nous voulons garder ou omettre. + +Par exemple pour filtrer les dates auxquelles le marché a reçu 100 unités de bananes (`product_id = 1`) ou de popcorn (`product_id = 3`) nous pouvons utiliser ceci : + +```{r} +ProductReceived[list(c(1L, 3L), 100L), + on = c("product_id", "count")] +``` + +Comme à la fin nous filtrons sur la base d'une opération de jointure, le code a renvoyé une **ligne absente de la table d'origine**. Pour éviter ce comportement il est recommandé de toujours ajouter l'argument `nomatch = NULL`. + +```{r} +ProductReceived[list(c(1L, 3L), 100L), + on = c("product_id", "count"), + nomatch = NULL] +``` + + +Nous pouvons aussi utiliser cette technique pour filtrer toute combinaison de valeurs en les préfixant avec `!` pour obtenir la négation de l'expression dans l'argument `i` et en gardant le `nomatch` à sa valeur par défaut. Par exemple nous pouvons filtrer les deux lignes filtrées précédemment. + +```{r} +ProductReceived[!list(c(1L, 3L), 100L), + on = c("product_id", "count")] +``` + +Si vous voulez simplement filtrer une valeur pour une **colonne de caractères** seule, vous pouvez ne pas appeler la fonction `list()` et passer la valeur pour être filtrée dans l'argument `i`. + +```{r} +Products[c("banana","popcorn"), + on = "name", + nomatch = NULL] + +Products[!"popcorn", + on = "name"] +``` + +### 7.2. Mise à jour par référence + +L'opérateur `:=` de data.table est utilisé pour modifier des colonnes par référence (c'est à dire sans recopie) lors de la jointure. Syntaxe générale : `x[i, on=, (cols) := val]`. + +**Mise à jour Simple un à un** + +Mise à jour de `Products` avec les prix de `ProductPriceHistory` : + +```{r} +Products[ProductPriceHistory, + on = .(id = product_id), + price := i.price] + +Products +``` + +- `i.price` est le prix dans `ProductPriceHistory`. +- on modifie directement le contenu de `Products`. + +**Mises à jour groupées avec `.EACHI`** + +Obtenir le dernier prix et la date pour chaque produit : + +```{r Updating_with_the_Latest_Record} +Products[ProductPriceHistory, + on = .(id = product_id), + `:=`(price = last(i.price), last_updated = last(i.date)), + by = .EACHI] + +Products +``` + +- `by = .EACHI` groupe les lignes de i (un groupe ar ligne ProductPriceHistory). +- `last()` renvoie la dernière valeur + +**Mise à jour efficace par jointure droite** + +Ajouter les détails des produits dans `ProductPriceHistory` sans recopier : + +```{r} +cols <- setdiff(names(Products), "id") +ProductPriceHistory[, (cols) := + Products[.SD, on = .(id = product_id), .SD, .SDcols = cols]] +setnafill(ProductPriceHistory, fill=0, cols="price") # Handle missing values + +ProductPriceHistory +``` + +- Dans `i`, `.SD` référence `ProductPriceHistory`. +- Dans `j`, `.SD` référence `Products`. +- `:=` et `setnafill()` mettent ensemble à jour `ProductPriceHistory` par référence. + +## Référence + +- *Comprendre les jointures glissantes de data.table* : https://www.r-bloggers.com/2016/06/understanding-data-table-rolling-joins/ + +- *Demi-jointure avec data.table* : https://stackoverflow.com/questions/18969420/perform-a-semi-join-with-data-table + +- *Jointure croisée avec with data.table* : https://stackoverflow.com/questions/10600060/how-to-do-cross-join-in-r + +- *Comment réaliser une jointure complète à l'aide de data.table ?* : https://stackoverflow.com/questions/15170741/how-does-one-do-a-full-join-using-data-table + +- *data.frame étendu* : https://rdatatable.gitlab.io/data.table/reference/data.table.html