jamesmbaazam/r-gnumake-orchestrator

Modèles génériques pour orchestrer des pipelines d'analyse de données en R avec GNU Make, utilisant des workflows ciblant les fichiers et des contrats de scripts standardisés.

Qu'est-ce que r-gnumake-orchestrator ?

Cette compétence Claude fournit des modèles éprouvés pour construire des pipelines d'analyse reproductibles en R en utilisant GNU Make. Elle impose une discipline stricte : chaque script est une fonction pure qui lit ses entrées, effectue un calcul unique et écrit exactement un fichier de sortie, sans effets secondaires (pas de `print()`, `message()` ou `cat()`). Les fichiers Makefile utilisent des cibles de fichiers (et non des règles factices) pour ne reconstruire que lorsque les dépendances changent, garantissant des exécutions rapides et reproductibles. La configuration partagée (dans `R/shared_inputs.R`) évite la duplication des paramètres à travers les scripts. La compétence fonctionne avec Claude Desktop et Claude Code, et s'intègre avec l'écosystème Tidyverse, roxygen2 pour la documentation, et le formateur `air` pour le style du code. Elle est idéale pour des projets allant de petites analyses exploratoires à de grands recueils de recherche comprenant des dizaines d'étapes et des milliers de fichiers générés.

Compatible avec✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/jamesmbaazam/r-gnumake-orchestrator/tree/HEAD

Installed? Explore more Recherche et analyse de données skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Modèles de Recherche Reproductible avec R + GNU Make

Ceci est une compétence Claude. Chargez-la avec /skill r-gnumake-orchestrator dans Claude pour accéder aux modèles de construction de pipelines d'analyse reproductibles.

Un guide pour orchestrer des pipelines d'analyse en R en utilisant GNU Make avec des workflows ciblant les fichiers et un contrat d'interface de script standardisé.

Aperçu

Cette compétence capture des modèles génériques pour l'analyse de données reproductible :

  • Les scripts comme des fonctions pures : Acceptent des arguments, lisent les entrées, écrivent une seule sortie, puis se terminent.
  • Makefiles avec des cibles de fichiers : Ne reconstruisent que lorsque les dépendances changent.
  • Configuration partagée : Une seule source de vérité pour tous les scripts.
  • Écosystème Tidyverse : Packages R modernes et cohérents.
  • Documentation roxygen2 : Chaque fonction réutilisable est documentée.
  • Pas d'affichage de sortie : Les résultats résident uniquement dans des fichiers de données versionnés.

Ces modèles s'adaptent des petites analyses exploratoires aux grands recueils de recherche avec des dizaines d'étapes et des milliers d'artefacts générés.

Conventions pour les Scripts R et les Makefiles

Modèle de Script R

Chaque script de pipeline définit .args conditionnellement : valeurs par défaut pour une utilisation interactive, commandArgs() s'il est appelé depuis Make.

# Définir les arguments : valeurs par défaut pour interactif, commandArgs lorsque appelé depuis Make
.args <- if (interactive()) c(
  "data/default1.rds",
  "data/default2.rds",
  "R/shared_inputs.R",
  "output/default.csv"
) else commandArgs(trailingOnly = TRUE)

# Extraire de .args par position
input1 <- .args[1]
input2 <- .args[2]
helper <- .args[3]
output <- .args[4]

source(helper)  # charge COUNTRY, SEED, OUTPUT_DIR, etc.

# Maintenant : lire les entrées, calculer, écrire la sortie, puis terminer
data <- read_rds(input1)
results <- process(data)
write_csv(results, output)

Dans Make : Rscript $^ $@ passe toutes les prérequises dans l'ordre, puis la cible. En interactif : Rscript R/myscript.R utilise les valeurs par défaut de .args (aucun argument nécessaire).

Modèle de Makefile

Utilisez des cibles de fichiers (pas de règles factices). Ne reconstruisez que lorsque les prérequises changent.

# Création de répertoire (ordre seul, ne déclenche pas de reconstructions)
output output/figures output/tables:
	mkdir -p $@

# Une entrée → une sortie
output/processed.rds: data/raw.csv R/process.R R/shared_inputs.R | output
	Rscript $^ $@

# Entrées multiples → une sortie
output/scores.csv: output/model.rds data/test.rds R/score.R R/shared_inputs.R | output
	Rscript $^ $@

# Cibles factices pour commodité uniquement (non suivies par Make)
.PHONY: all
all: output/scores.csv

Syntaxe clé :

  • $^ = toutes les prérequises dans l'ordre
  • $@ = nom de fichier de la cible
  • | output = prérequise d'ordre seul (répertoire créé, mais horodatage ignoré)

Configuration Partagée

Un seul fichier contient chaque valeur dont deux scripts ou plus ont besoin. Pas de duplication entre les scripts.

# R/shared_inputs.R
COUNTRY <- "Afghanistan"
HORIZON <- as.integer(Sys.getenv("HORIZON", "1"))
SEED <- 42
OUTPUT_DIR <- file.path("output", paste0("horizon_", HORIZON))
VALIDATION_YEARS <- 2017:2023

# Chaque script source ce fichier, ne le redéfinit jamais
source("R/shared_inputs.R")

Organisation des Fichiers

project/
├── Makefile                 # Règles d'orchestration (cibles de fichiers)
├── R/
│   ├── shared_inputs.R      # Configuration (sourcé par tous les scripts)
│   ├── process_data.R       # Scripts du pipeline
│   ├── fit_model.R          # (une tâche par script)
│   ├── score_model.R
│   ├── helpers.R            # Fonctions réutilisables (pas de préfixe d'étape)
│   ├── models.R             # Documenté avec roxygen2
│   └── utils.R
├── data/
│   ├── raw/                 # Données sources
│   ├── processed/           # Généré (gitignoré ou suivi par DVC)
│   └── features/
├── output/
│   ├── figures/             # Versionné (pour que le manuscrit s'affiche dans CI)
│   ├── tables/              # Fichiers .csv avec tous les nombres
│   ├── models/              # Objets de modèle ajustés (.rds)
│   └── predictions/         # .csv avec les sorties du modèle
└── paper/
    ├── paper.qmd            # Manuscrit (lit output/*.csv)
    └── bibliography.bib

Les préfixes d'étape trient par ordre d'exécution :

  • process_*.R → fit_*.R → score_*.R → visualise_*.R

Les fichiers de fonctions n'ont pas de préfixe, afin que les outils de documentation puissent les distinguer des scripts de pipeline.

Règles de Modèle Make

Utilisez des règles de modèle (%) pour les cibles répétitives. Définissez une fois, appliquez à beaucoup.

# Modèle : traiter chaque fichier d'entrée
INPUTS := file1 file2 file3

# Règle générique : transformer tout %.csv en output/processed_%.rds
output/processed_%.rds: input/%.csv R/process.R R/shared_inputs.R | output
	Rscript $^ $@

# Cible de commodité : tous les fichiers traités
all_processed: $(patsubst input/%.csv, output/processed_%.rds, $(wildcard input/*.csv))

# Surcharge pour un cas spécial : file1 nécessite une entrée différente
output/processed_file1.rds: input/file1_v2.csv R/process.R R/shared_inputs.R | output
	Rscript $^ $@

Une sortie par règle : chaque script écrit exactement un fichier, donc chaque règle a exactement une cible. Si une étape nécessite deux fichiers, écrivez deux scripts et deux règles (voir « Une sortie par script » dans Modèles Courants).

Organisation des Fonctions

Scripts de Pipeline

Une tâche par script, et une sortie par script. Chaque étape lit ce que l'étape précédente a écrit.

# R/fit_model.R
# Objectif : Ajuster un modèle à des données préparées
# Entrées : data/prepared.rds, R/shared_inputs.R
# Sortie : output/model.rds

.args <- if (interactive()) c(
  "data/prepared.rds",
  "R/shared_inputs.R",
  "output/model.rds"
) else commandArgs(trailingOnly = TRUE)

data_file <- .args[1]
helper <- .args[2]
model_file <- .args[3]

source(helper)

data <- read_rds(data_file)
model <- fit_model(data, seed = SEED)

write_rds(model, model_file)

Toute autre chose dérivée des mêmes entrées, comme un résumé des données d'entraînement, a son propre script et sa propre sortie :

# R/summarise_training.R
# Objectif : Résumer les données utilisées pour ajuster le modèle
# Entrées : data/prepared.rds, R/shared_inputs.R
# Sortie : output/model_meta.csv

.args <- if (interactive()) c(
  "data/prepared.rds",
  "R/shared_inputs.R",
  "output/model_meta.csv"
) else commandArgs(trailingOnly = TRUE)

data_file <- .args[1]
helper <- .args[2]
meta_file <- .args[3]

source(helper)

data <- read_rds(data_file)

write_csv(
  tibble(
    n_training = nrow(data),
    n_features = ncol(data) - 1
  ),
  meta_file
)

Fichiers de Fonctions

Les fonctions réutilisables vont dans des fichiers séparés (pas de préfixe d'étape). Documentez avec roxygen2.

# R/models.R

#' Ajuster un modèle à des données
#'
#' Entraîne une forêt aléatoire en utilisant des paramètres spécifiés.
#'
#' @details
#' Hypothèses sur les entrées :
#' - les données ont une colonne `outcome` (binaire : 0/1) et tous les prédicteurs sont numériques
#' - seed est un entier pour la reproductibilité
#'
#' @param data Un tibble avec les colonnes outcome et prédicteurs.
#' @param seed Entier pour la génération de nombres aléatoires.
#' @param mtry Entier ; nombre de variables à échantillonner par division.
#'
#' @return Un objet de modèle ajusté (classe ranger).
#'
#' @export
fit_model <- function(data, seed = 42, mtry = floor(sqrt(ncol(data) - 1))) {
  model <- ranger(outcome ~ ., data = data, seed = seed, mtry = mtry)
  model
}

Exécutez make docs pour régénérer man/ et NAMESPACE à partir des blocs roxygen.

Configuration Partagée

Emplacement central pour les paramètres de l'étude. Chaque script le source.

# R/shared_inputs.R

# Conception de l'étude
COUNTRY <- "Country_Name"
SEED <- 42
ANALYSIS_YEARS <- 2017:2023

# Chemins (calculés, pas codés en dur)
DATA_DIR <- "data"
OUTPUT_DIR <- "output"
HORIZON <- as.integer(Sys.getenv("HORIZON", "1"))

# Hyperparamètres du modèle
RF_MTRY <- NULL  # sqrt(p)
RF_NTREES <- 500

# Ne définissez rien d'autre ici.
# Les valeurs spécifiques à une étape restent dans le script de cette étape.

Règles de Style

Pile Tidyverse

Standardisez sur ces packages :

library(tidyverse)   # core : dplyr, tidyr, ggplot2, readr, purrr
library(fs)          # opérations sur le système de fichiers (dir_create, file_exists)
library(cli)         # signalement d'erreurs (cli_abort, cli_warn)
library(here)        # chemins relatifs (here("R", "script.R"))
library(scales)      # formatage des graphiques
library(yardstick)   # métriques de modèle

N'utilisez jamais :

  • Le sous-ensemble de base ou aggregate() (utilisez dplyr::filter(), dplyr::summarise())
  • map_dfr() (utilisez map() + list_rbind())
  • group_by() + ungroup() (utilisez l'argument .by =)
  • gather(), spread() (utilisez pivot_longer(), pivot_wider())
  • %>% (utilisez le pipe natif |>)
  • function(x) (utilisez la lambda \(x))
  • Les fonctions obsolètes (vérifiez le badge de cycle de vie sur la page d'aide)

Formatage du Code

Utilisez le formateur air (impose des lignes de 80 caractères, indentation de 2 espaces) :

air format R/                  # formater tout le répertoire
air format R/myscript.R        # formater un fichier

Paramètres dans air.toml à la racine du dépôt (versionné, pas personnel).

Commentaires

Commentez pourquoi le code fait quelque chose, pas quoi (uniquement là où la justification n'est pas évidente en lisant le code). Évitez les commentaires qui ne font que répéter le code.

# Mauvais : répète ce que le code fait évidemment
# Ajuster une forêt aléatoire avec les hyperparamètres par défaut
model <- ranger(outcome ~ ., data = data)

# Bon : explique pourquoi nous avons fait ce choix
# Forêt aléatoire plutôt que CART ici car nous avons besoin d'intervalles de confiance
# à partir de la régression quantile
model <- ranger(outcome ~ ., data = data)

# Mauvais : évident à partir du code
# Filtrer les lignes où l'année est dans ANALYSIS_YEARS
data <- data |> filter(year %in% ANALYSIS_YEARS)

# Bon : explique le raisonnement non évident
# Exclure l'année 2015 en raison d'une couverture de surveillance incomplète (voir Méthodes)
data <- data |> filter(year >= 2016)

# Pas de commentaire nécessaire : le code se suffit à lui-même
data <- data |>
  select(district, year, outcome) |>
  filter(!is.na(outcome))

Rédigez les commentaires dans la section Méthodes du manuscrit lorsque vous expliquez un choix méthodologique ; utilisez les commentaires de code uniquement lorsque la décision est inattendue ou non standard.

Pas d'Affichage de Sortie

Les résultats résident uniquement dans des fichiers CSV, RDS et PNG versionnés. Pas de message(), print(), cat(), ou sprintf() pour les sorties informationales. Exceptions : cli_abort() et cli_warn() (contrôle de flux uniquement).

# Mauvais
message(sprintf("Processed %d rows", nrow(data)))
cat("Model trained\n")

# Bon (écrire dans un fichier à la place)
meta <- tibble(n_rows = nrow(data))
write_csv(meta, "output/metadata.csv")

Le manuscrit lit output/metadata.csv au moment du rendu, donc chaque nombre apparaît dans les fichiers versionnés.

Workflow de Validation

Lorsque vous écrivez un nouveau script, testez progressivement avant d'ajouter des règles Make.

Étape 1 : Tester en Interactif (avec les valeurs par défaut)

# Est-ce que cela fonctionne sans arguments ?
Rscript R/myscript.R
# Vérifier que les fichiers de sortie ont été créés
ls -lh output/

Étape 2 : Tester avec des Arguments (imite Make)

# Passer les arguments comme Make le ferait
Rscript R/myscript.R \
  data/raw.csv \
  R/shared_inputs.R \
  output/result.rds
# Comparer avec la sortie de l'étape 1

Étape 3 : Ajouter une Règle Make et Tester

# Dans le Makefile :
output/result.rds: data/raw.csv R/myscript.R R/shared_inputs.R | output
	Rscript $^ $@

# Tester
make output/result.rds

# Vérifier : modifier les données devrait déclencher une reconstruction
touch data/raw.csv
make output/result.rds  # Devrait réexécuter

# Vérifier : modifier output/result.rds (pas une dépendance) ne déclenche pas de reconstruction
touch output/result.rds
make output/result.rds  # Devrait dire "up to date"

Étape 4 : Simulation

make --dry-run output/result.rds  # Affiche ce qui serait exécuté sans exécuter

Exemple : Pipeline Simple en 3 Étapes

Makefile

.PHONY: all
all: output/summary.csv

output:
	mkdir -p $@

output/processed.rds: data/raw.csv R/process.R R/shared_inputs.R | output
	Rscript $^ $@

output/model.rds: output/processed.rds R/fit.R R/shared_inputs.R | output
	Rscript $^ $@

output/summary.csv: output/model.rds output/processed.rds R/score.R R/shared_inputs.R | output
	Rscript $^ $@

R/process.R

.args <- if (interactive()) c(
  "data/raw.csv",
  "R/shared_inputs.R",
  "output/processed.rds"
) else commandArgs(trailingOnly = TRUE)

raw_file <- .args[1]
helper <- .args[2]
output <- .args[3]

source(helper)

data <- read_csv(raw_file) |>
  filter(year %in% ANALYSIS_YEARS) |>
  mutate(across(everything(), as.numeric))

write_rds(data, output)

R/fit.R

.args <- if (interactive()) c(
  "output/processed.rds",
  "R/shared_inputs.R",
  "output/model.rds"
) else commandArgs(trailingOnly = TRUE)

data_file <- .args[1]
helper <- .args[2]
output <- .args[3]

source(helper)

data <- read_rds(data_file)
model <- ranger(outcome ~ ., data = data, seed = SEED)
write_rds(model, output)

R/score.R

.args <- if (interactive()) c(
  "output/model.rds",
  "output/processed.rds",
  "R/shared_inputs.R",
  "output/summary.csv"
) else commandArgs(trailingOnly = TRUE)

model_file <- .args[1]
data_file <- .args[2]
helper <- .args[3]
output <- .args[4]

source(helper)

model <- read_rds(model_file)
data <- read_rds(data_file)
predictions <- predict(model, data)$predictions

scores <- tibble(
  auc = AUC::auc(data$outcome, predictions),
  n = nrow(data)
)

write_csv(scores, output)

Exécuter

make            # Construit tout dans l'ordre (process → fit → score)
make output/model.rds --always-make  # Forcer la reconstruction de cette étape uniquement
make --dry-run  # Afficher ce qui serait exécuté

Liste de Vérification pour les Nouveaux Scripts

Lors de l'écriture d'une nouvelle étape de pipeline :

  • Le script accepte des arguments dans l'ordre : entrées → helper → sortie
  • Le script inclut les valeurs par défaut de .args pour les tests interactifs
  • Le script source R/shared_inputs.R (ne définit jamais de valeurs à l'échelle de l'étude)
  • Le script lit ses entrées, calcule et écrit exactement un fichier de sortie
  • Pas de print(), message() ou cat() (utilisez cli_abort(), cli_warn() pour les erreurs uniquement)
  • Tous les résultats écrits dans des fichiers CSV/RDS/PNG versionnés
  • Les dépendances listées correctement dans le Makefile (dans l'ordre)
  • Les prérequises de répertoire d'ordre seul utilisent la syntaxe |
  • Testé en interactif, puis avec arguments, puis via Make
  • La règle du Makefile utilise la syntaxe $^ et $@
  • Code formaté avec air format R/scriptname.R
  • Les fonctions réutilisables extraites dans des fichiers séparés

…

[truncated for translation]

Skills associés