Skip to contents

Mettre en cache le résultat des calculs avec source_data()

source_data() est une fonction du package ofce qui permet de mettre en cache les résultats d’un script r et de ne pas avoir à ré-exécuter le code à chaque fois.

Les bénéfices sont nombreux :

  1. un gain de temps lorsque l’exécution du code est longue (accès à une API, téléchargement de grosses données, traitements importants). La lecture d’un fichier excel peut aussi être assez longue. Le temps d’accès aux données en cache dépend de leur taille, mais même pour des données volumineuses (et il n’y a pas de raisons qu’elles le soient tant que ça), l’ordre de grandeur est la milliseconde, grâce aux optimisations.
  2. le cache est transférable par github. Il se trouve dans un dossier (caché), mais enregistré dans le dossier de projet et commité par github. Le cache produit sur un poste est donc accessible par pull sur les autres postes.
  3. si le code source déclenche une erreur, on peut passer outre : En cas de package non installé, données absentes (par exemple un chemin absolu dans le code), ou une API qui bloque (comme celle de l’OCDE) alors source_data() essaye de prendre la dernière exécution résussie. Bien que cela puisse être problématique, c’est-à-dire une erreur non signalée, cela a l’énorme avantage de ne pas bloquer le processus et de permettre de traiter l’erreur en parallèle.
  4. source_data() cherche de façon astucieuse le fichier source dans le projet et exécute le code dans un environnement local, en changeant le répertoire de travail pour être celui où se trouve le code source. Cela permet d’appeler dans le code source (le script r mon_script.r passé en paramètre à source_data("mon_script.r"), des scripts r, des fichiers de données .csv ou .xlsx qui sont enregistré dans le même répertoire que le fichier mon_script.r. On peut donc réutiliser le code sans se soucier de modifier les chemins qui sont relatifs au dossier où se trouve mon_script.r.
  5. cela fournit un embryon de reproductibilité en désignant le script qui fabrique les données.

Comment l’utiliser ?

Première chose mettre son code de données dans un script et l’enregistrer là où est le qmd, dans cet exemple souverains.qmd et un dossier remplis de csv, scripts R, etc :

├── souverains
│   ├── ratings.R
│   ├── ratings.rds
│   ├── ratings_legende.rds
│   ├── ratings_taux.rds
│   ├── taux_souverains_historiques.R
│   └── taux_souverains_historiques.rds
└── souverains.qmd

Dans souverains.qmd on trouve le chunk suivant. source_data est capable de retrouver le dossier dans le même dossier que le qmd (ce n’est pas automatique, dans un projet commme la prévision, les chemins sont relatifs à la racine du projet, ici le qmd et ses fichiers peuvent être mis n’importe où grâce à cette heuristique). Le source est ensuite exécuté en changeant le dossier de travail à celui qui le contient (dans cet exemple, souverains) ce qui permet d’utiliser des chemins relatifs et donc de pouvoir transporter le code n’importe où.

```{r}
ofce::init_qmd(echo=TRUE) 

taux_souverains_historiques <- source_data("souverains/taux_souverains_historiques.R", lapse = "day")

ratings_taux <- source_data("souverains/ratings", lapse = "day")
```

Le script doit se terminer par un return qui renvoie les données calculées ou téléchargées. Ce sont ces données qui sont mises en cache.

``` {{r}}
...
télécharge et modifie, calcule, etc
...
return(taux_souverains_historiques)
```

source_data() dispose d’un cache (caché dans un dossier .data). Il repère le fichier source et détecte les changements faits au code source et invalide le cache. Si le cache est valide, les données sont renvoyées, sinon, le script est exécuté et les nouvelles données mises en cache.

Il existe d’autres moyens d’invalider le cache : il peut avoir une durée de vie maximale, comme avec l’argument lapse="day". Cette option dit que si le cache est plus vieux que 24h, il est renouvelé par une ré-exécution du script. Ce paramètre peut prendre plusieurs valeurs et des formes comme 2 hours ou 3 weeks. D’autres déclencheurs temporels seront ajoutés pour introduire des calendriers (comme 45 jours après la fin du trimestre). Attention, cependant, source_cache n’est pas capable d’aller vérifier que les données téléchargées ont un point de plus et donc de ne pas valider le cache si la donnée n’a pas encore été publiée.

Il est aussi possible de déclencher l’invalidation du cache si un fichier a été modifié. Il suffit de fournir une liste de fichiers (dont les chemins sont relatifs au script) qui seront suivis. Ces fichiers peuvent être des .csv ou des fichiers .xlsx (ou encore tout autre type de fichier) et donc sont utiles pour déclencher l’exécution du script quand on a fait une modification manuelle ou par un autre programme de ces fichiers. On peut en mettre autant qu’on veut.

On peut également forcer le déclenchement du script. Cela se fait par une option force_exec=TRUE. Cependant, il vaut mieux ne pas spécifier cette option, il existe d’autres moyens pour opérer un rafraîchissement du cache.

Il est possible de bloquer l’exécution du code par une option (prevent_exec) qui peut être définie comme une option globale (par options(ofce.source_data.prevent_exec=TRUE)). Dans ce cas, aucun script ne sera exécuté, ce qui peut servir lorsqu’on veut faire un rendu du site sans prendre le risque d’une erreur d’API ou d’un blocage.

Usage avancé : passer des paramètres

Il est possible de passer des paramètres avec source_data(), bien que ce soit plus pratique d’écrire une fonction. Les paramètres sont passés sous forme d’une liste (list(param1="1") par exemple) et sont disponibles dans le script (dans la variable args, donc pour avoir le paramètre param1 il faut écrire args$param1 dans le script. Changer les paramètres invalide le cache.

Notez que le script est toujours exécuté en “local” ce qui veut dire que toute variable créée ou tout package ouvert à l’intérieur du script n’est pas renvoyé (comme dans une fonction, en fait).

Usage avancé : récupérer les métadonnées

En utlisant l’option metadata=TRUE dans source_data() on peut récupérer des informations sur, par exmple, la date de téléchargement. C’est illustré sur quelques graphiques du cachier de graphique.

En code cela donne le chunk ci dessous. Les données sont accessibles par $data et la date de téléchargement par $date. Cela permet de construire la note (noter que glue::glue() est appliqué aux textes passés à ofce_caption()).

```{r}
transactions <- source_data("immo/data_transaction.r", metadata=TRUE)

trsc <- ggplot(transactions$data) + 
  aes(x=date, y=t*1000) +
  geom_line(alpha = 0.5, col = bluish) +
  geom_point_interactive(aes(tooltip = tooltip, data_id = date),
                         shape = 21, size = 1, stroke = 0.2, col = "white", 
                         fill = bluish,
                         hover_nearest = TRUE, show.legend = FALSE)+
  theme_ofce() +
  scale_y_log10(labels = scales::number_format(scale = 1/1000, suffix="k")) +
  scalex +
  ofce_caption(
    source = "IGEDD d'après DGFiP (MEDOC) et bases notariale", 
    dpt = transactions$data$date,
    note = "Transactions cumulées sur 12 mois, dans l'ancien, maisons et appartements, échelle log, données téléchargées le {date_jour(transactions$date)}",
    sub= "Nombre de transactions")

```

Quelques opérations sur le cache

Le package ofce fournit des outils pour s’occuper des caches. Le premier est source_data_status(). il scanne le répertoire et fournit la liste de tous les caches enregistrés et suivis. Il indique si les caches sont valides ou non et les principaux paramètres utilisés pour chaque script.

source_data_refresh() rafraîchit (en le forçant) tous les caches. On peut passer à source_data-refresh() un tibble comme celui renvoyé par souce_data_status() mais filtré pour ne rafraîchir que la liste voulue (attention passer toutes les colonnes). Cela sert lorsqu’on a un processus plus complexe d’invalidation du cache (en fonction d’un calendrier, en interrogeant une API, etc…) et qu’on déclenche en fonction de cette logique l’exécution des caches.

On peut également à partir de source_data_status() accéder aux données en cache. Elles sont enregistrées en .qs et donc se chargent avec un qs::qread().

On peut également nettoyer complètement le cache (ce qui provoquera sa ré exécution) avec clear_source_cache(). prune_source_cache() fait la même chose mais garde les dernières versions du cache (il nettoie l’historique donc).