Skip to content

Commit

Permalink
Améliorer les formulations
Browse files Browse the repository at this point in the history
  • Loading branch information
oliviermeslin committed Jan 24, 2024
1 parent 1a6f671 commit 1cb91a7
Showing 1 changed file with 11 additions and 8 deletions.
19 changes: 11 additions & 8 deletions 03_Fiches_thematiques/Fiche_arrow.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,18 @@ Apprendre à utiliser `arrow` n'est pas difficile, car la syntaxe utilisée est

### Qu'est-ce qu'`arrow`?

Apache `arrow` est un projet *open-source* qui propose une représentation standardisée des données tabulaires en mémoire vive, qui est à la fois efficace (les traitements sont rapides), interopérable (différents langages de programmation peuvent accéder aux mêmes données, sans conversion des données dans un autre format) et indépendante du langage de programmation utilisé. Ce projet prend la forme d'une librairie C++ nommée `libarrow`. Un point important à retenir est donc que **`arrow` n'est pas un outil spécifique à `R`**, et il faut bien distinguer le projet `arrow` (et la librairie C++ `libarrow`) du *package* `R` `arrow`. Ce *package* propose simplement une interface qui permet d'utiliser la librairie `libarrow` avec `R`, et il existe d'autres interfaces pour se servir de `libarrow` avec d'autres langages: en Python, en Java, en Javascript, en Julia, etc.
Apache `arrow` est un projet *open-source* qui propose deux choses:

- une représentation standardisée des données tabulaires en mémoire vive appelée _Apache Arrow Columnar Format_, qui est à la fois efficace (les traitements sont rapides), interopérable (différents langages de programmation peuvent accéder aux mêmes données, sans conversion des données dans un autre format) et indépendante du langage de programmation utilisé.
- une implémentation de ce standard en C++, qui prend la forme d'une librairie C++ nommée `libarrow`. Il existe d'autres implémentations dans d'autres langages; l'implémentation en Rust est par exemple utilisée par le projet `polars`.

Un point important à retenir est donc que **`arrow` n'est pas un outil spécifique à `R`**, et il faut bien distinguer le projet `arrow` (et la librairie C++ `libarrow`) du *package* `R` `arrow`. Ce *package* propose simplement une interface qui permet d'utiliser la librairie `libarrow` avec `R`, et il existe d'autres interfaces pour se servir de `libarrow` avec d'autres langages: en Python, en Java, en Javascript, en Julia, etc.

### Spécificités de `arrow`

Le projet `arrow` présente cinq spécificités:

- __Représentation des données en mémoire__: `arrow` organise les données en colonnes plutôt qu'en lignes (on parle de *columnar format*). Concrètement, cela veut dire que dans la RAM toutes les valeurs de la première colonne sont stockées de façon contiguë, puis les valeurs de la deuxième colonne, etc. Cette structuration des données rend les traitements très efficaces: si l'on veut par exemple calculer la moyenne d'une variable, il est possible d'accéder directement au bloc de mémoire vive qui contient cette colonne (indépendamment des autres colonnes de la table de données), d'où un traitement très rapide.
- __Représentation des données en mémoire__: `arrow` organise les données en colonnes plutôt qu'en lignes (on parle de *columnar format*). Concrètement, cela veut dire que dans la RAM toutes les valeurs de la première colonne sont stockées de façon contiguë, puis les valeurs de la deuxième colonne, etc. Cette structuration des données rend les traitements très efficaces: si l'on veut par exemple calculer la moyenne d'une variable, il est possible d'accéder directement au bloc de mémoire vive qui contient l'intégralité de cette colonne (indépendamment des autres colonnes de la table de données), d'où un traitement très rapide.

- __Utilisation avec Parquet__: `arrow` est souvent utilisé pour manipuler des données stockées en format Parquet. Parquet est un format de stockage orienté colonne conçu pour être très rapide en lecture (voir la fiche [Importer des fichiers Parquet](#importparquet) pour plus de détails). `arrow` est optimisé pour travailler sur des fichiers Parquet, notamment lorsqu'ils contiennent des données très volumineuses.

Expand Down Expand Up @@ -126,7 +131,7 @@ View(extrait_bpe)

### Manipuler des `Arrow Table` avec la syntaxe `dplyr`

__Le _package_ `R` `arrow` a été écrit de façon à ce qu'un `Arrow Table` puisse être manipulé avec les fonctions de `dplyr` (`select`, `filter`, `mutate`, `left_join`, etc.), comme si cette table était un `data.frame` ou un `tibble` standard.__ Il est également possible d'utiliser sur un `Arrow Table` un certain nombre de fonctions des _packages_ du `tidyverse` (comme `stringr` et `lubridate`). Cela s'avère très commode en pratique, car lorsqu'on sait utiliser `dplyr` et le `tidyverse`, on peut commencer à utiliser `arrow` sans avoir à apprendre une nouvelle syntaxe de manipulation de données.
__Le _package_ `R` `arrow` a été écrit de façon à ce qu'un `Arrow Table` puisse être manipulé avec les fonctions de `dplyr` (`select`, `filter`, `mutate`, `left_join`, etc.), *comme si* cette table était un `data.frame` ou un `tibble` standard.__ Il est également possible d'utiliser sur un `Arrow Table` un certain nombre de fonctions des _packages_ du `tidyverse` (comme `stringr` et `lubridate`). Cela s'avère très commode en pratique, car lorsqu'on sait utiliser `dplyr` et le `tidyverse`, on peut commencer à utiliser `arrow` sans avoir à apprendre une nouvelle syntaxe de manipulation de données. Il y a néanmoins des subtilités à connaître, détaillées dans la suite de cette fiche.

Dans l'exemple suivant, on calcule le nombre d'équipements par région, à partir d'un `tibble` et à partir d'un `Arrow table`. La seule différence apparente entre les deux traitement est la présence de la fonction `collect()` à la fin des instructions; cette fonction indique que l'on souhaite que le résultat du traitement soit stocké sous la forme d'un `tibble`. La raison d'être de ce `collect()` est expliqué plus loin, dans le paragraphe sur l'évaluation différée.

Expand Down Expand Up @@ -168,12 +173,10 @@ Il y a une différence fondamentale entre manipuler un `data.frame` ou un `tibbl

__La grande différence entre manipuler un `tibble` et manipuler un `Arrow Table` tient au moteur d'exécution__: si on manipule un `tibble` avec la syntaxe de `dplyr`, alors c'est le moteur d'exécution de `dplyr` qui fait les calculs; si on manipule un `Arrow Table` avec la syntaxe de `dplyr`, alors c'est le moteur d'exécution d'`arrow` (nommé `acero`) qui fait les calculs. C'est justement parce que le moteur d'exécution d'`arrow` est beaucoup plus efficace que celui de `dplyr` qu'`arrow` est beaucoup plus rapide.

<!-- __Comprendre cette différence de moteurs d'exécution est essentiel pour bien utiliser `arrow` et résoudre les problèmes qui se présentent lorsqu'on découvre cet outil__. -->


<!-- __La grande différence entre `dplyr` et `arrow` tient au moteur d'exécution__: si on manipule un `tibble` avec la syntaxe de `dplyr`, alors c'est le moteur d'exécution de `dplyr` qui fait les calculs; si on manipule un `Arrow Table` avec la syntaxe de `dplyr`, alors les instructions sont automatiquement converties et envoyées au moteur d'exécution d'`arrow` (nommé `acero`), et c'est ce moteur qui fait les calculs. C'est justement parce que le moteur d'exécution d'`arrow` est beaucoup plus efficace que celui de `dplyr` qu'`arrow` est beaucoup plus rapide. __Comprendre cette différence de moteurs d'exécution est essentiel pour bien utiliser `arrow` et résoudre les problèmes qui se présentent lorsqu'on découvre cet outil__. -->

__Cette différence de moteurs d'exécution a une conséquence technique importante__: une fois que l'utilisateur a défini des instructions avec la syntaxe `dplyr`, il est nécessaire que celles-ci soient converties pour que le moteur `acero` (implémenté en C++ et non en `R`) puissent les exécuter. De façon générale, `arrow` fait cette conversion de façon automatique et invisible, car le _package_ `arrow` contient la traduction C++ de plusieurs centaines de fonctions du `tidyverse`. Par exemple, le _package_ `arrow` contient la traduction C++ de la fonction `filter()` de `dplyr`, ce qui fait que les instructions `filter()` écrites en syntaxe `tidyverse` sont converties de façon automatique et invisible en des instructions C++ équivalentes. La liste des fonctions du _tidyverse_ supportées par `acero` est disponible sur [cette page](https://arrow.apache.org/docs/dev/r/reference/acero.html). Il arrive toutefois qu'on veuille utiliser une fonction non supportée par `acero`. Cette situation est décrite dans le paragraphe "Comment utiliser une fonction non supportée par `acero`".
__Cette différence de moteurs d'exécution a une conséquence technique importante__: une fois que l'utilisateur a défini des instructions avec la syntaxe `dplyr`, il est nécessaire que celles-ci soient converties pour que le moteur `acero` (écrit en C++ et non en `R`) puissent les exécuter. De façon générale, `arrow` fait cette conversion de façon automatique et invisible, car le _package_ `arrow` contient la traduction C++ de plusieurs centaines de fonctions du `tidyverse`. Par exemple, le _package_ `arrow` contient la traduction C++ de la fonction `filter()` de `dplyr`, ce qui fait que les instructions `filter()` écrites en syntaxe `tidyverse` sont converties de façon automatique et invisible en des instructions C++ équivalentes. La liste des fonctions du _tidyverse_ supportées par `acero` est disponible sur [cette page](https://arrow.apache.org/docs/dev/r/reference/acero.html). Il arrive toutefois qu'on veuille utiliser une fonction non supportée par `acero`. Cette situation est décrite dans le paragraphe "Comment utiliser une fonction non supportée par `acero`".


### L'évaluation différée avec `arrow` (_lazy evaluation_) {#subsec-lazy}
Expand All @@ -197,7 +200,7 @@ resultats <- eq_dep |>
collect()
```

Dans cet exemple, on procède à un traitement en deux temps: on compte les équipements par département, puis on filtre sur le département. Il est important de souligner que la première étape ne réalise aucun calcul par elle-même, car elle ne comprend ni `collect()` ni `compute()`. L'objet `equipements_par_departement` n'est pas une table et ne contient pas de données, il contient simplement une requête (_query_) à appliquer à la table `bpe_ens_2018_arrow`.
Dans cet exemple, on procède à un traitement en deux temps: on compte les équipements par département, puis on filtre sur le département. Il est important de souligner que la première étape ne réalise aucun calcul par elle-même, car elle ne comprend ni `collect()` ni `compute()`. L'objet `equipements_par_departement` n'est pas une table et ne contient pas de données, il contient simplement une requête (_query_) décrivant les opérations à mener sur la table `bpe_ens_2018_arrow`.

On pourrait penser que, lorsqu'on exécute l'ensemble de ce traitement, `arrow` se contente d'exécuter les instructions les unes après les autres: compter les équipements par département, puis conserver uniquement le département 59. Mais en réalité `arrow` fait beaucoup mieux que cela: __`arrow` analyse la requête avant de l'exécuter, et optimise le traitement pour minimiser le travail__. Dans le cas présent, `arrow` repère que la requête ne porte en fait que sur le département 59, et commence donc par filtrer les données sur le département avant de compter les équipements, de façon à ne conserver que le minimum de données nécessaires et à ne réaliser que le minimum de calculs. Ce type d'optimisation s'avère très utile quand les données à traiter sont très volumineuses.

Expand All @@ -207,7 +210,7 @@ On pourrait penser que, lorsqu'on exécute l'ensemble de ce traitement, `arrow`

__Lorsqu'on manipule des données volumineuses, il est essentiel de manipuler uniquement des objets `Arrow Table`, plutôt que des `tibbles`__. Cela implique deux recommandations:

- __Importer les données directement dans des `Arrow Table`, ou à défaut convertir en `Arrow Table` avec la fonction `as_arrow_table()`.__ Par exemple, lorsqu'on importe un fichier Parquet avec la fonction `read_parquet()` ou un fichier csv avec la fonction `read_csv_arrow()`, il est recommandé d'utiliser l'option `as_data_frame = FALSE` pour que les données soient importées dans un `Arrow Table`.
- __Importer les données directement dans des `Arrow Table`, ou à défaut convertir en `Arrow Table` avec la fonction `as_arrow_table()`.__ Par exemple, lorsqu'on importe un fichier Parquet avec la fonction `read_parquet()` ou un fichier csv avec la fonction `read_csv_arrow()`, il est recommandé d'utiliser l'option `as_data_frame = FALSE` pour que les données soient importées sous forme de `Arrow Table`.
- __Utiliser systématiquement `compute()` plutôt que `collect()` dans les étapes de calcul intermédiaires.__ Cette recommandation est particulièrement importante. L'exemple suivant explique pourquoi il est préférable d'utiliser `compute()` dans les étapes intermédiaires:

- __Situation à éviter__: la première étape de traitement étant déclenchée par `collect()` (ligne 7), la table intermédiaire `res_intermediaire1` est un `tibble`. Par conséquent, c'est le moteur d'exécution de `dplyr` qui est utilisé pour manipuler `res_intermediaire1` lors de la seconde étape du traitement, ce qui peut dégrader fortement les performances, en particulier si les données sont volumineuses.
Expand Down

0 comments on commit 1cb91a7

Please sign in to comment.