jamesmbaazam/r-gnumake-orchestrator

Patrones genéricos para orquestar pipelines de análisis de datos en R usando GNU Make con flujos de trabajo basados en archivos destino y contratos de script estandarizados.

¿Qué es r-gnumake-orchestrator?

Esta habilidad proporciona un conjunto de patrones genéricos y reutilizables para construir pipelines de análisis de datos reproducibles en R utilizando GNU Make. Está diseñada para desarrolladores y científicos de datos que buscan automatizar flujos de trabajo complejos, garantizando que solo se reconstruyan las partes del pipeline que han cambiado. La habilidad se centra en un contrato de script estandarizado donde cada script actúa como una función pura: acepta argumentos, lee entradas, escribe una única salida y termina. Los Makefiles utilizan objetivos de archivo (no reglas phony), lo que permite que Make reconstruya solo los archivos destino cuyas dependencias han cambiado. La configuración compartida se centraliza en un solo archivo (`R/shared_inputs.R`) que todos los scripts importan, eliminando la duplicación. El skill es compatible con cualquier sistema que ejecute GNU Make y R, y se integra perfectamente con el ecosistema Tidyverse, el formateador de código `air` y la documentación con `roxygen2`. Los pipelines se organizan con prefijos de etapa (process_, fit_, score_, visualise_) para ordenar la ejecución y mantener separados los scripts de funciones reutilizables. El resultado final son archivos de datos comprometidos (CSV, RDS, PNG) que el manuscrito lee en tiempo de renderizado, asegurando que cada número en el artículo proviene de un archivo de datos versionado.

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

Installed? Explore more Investigación y análisis de datos skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Patrones de Investigación Reproducible con R + GNU Make

Esta es una habilidad de Claude. Cárgala con /skill r-gnumake-orchestrator en Claude para acceder a patrones para construir pipelines de análisis de datos reproducibles.

Una guía para orquestar pipelines de análisis en R usando GNU Make con flujos de trabajo basados en archivos destino y una interfaz de script estandarizada.

Resumen

Esta habilidad captura patrones genéricos para análisis de datos reproducibles:

  • Scripts como funciones puras: Aceptan argumentos, leen entradas, escriben una salida y terminan.
  • Makefiles con archivos destino: Solo se reconstruyen cuando las dependencias cambian.
  • Configuración compartida: Una sola fuente de verdad para todos los scripts.
  • Ecosistema Tidyverse: Paquetes de R modernos y consistentes.
  • Documentación roxygen2: Cada función reutilizable está documentada.
  • Sin impresión de salidas: Los resultados solo residen en archivos de datos comprometidos.

Estos patrones escalan desde análisis exploratorios pequeños hasta grandes compendios de investigación con docenas de etapas y miles de artefactos generados.

Convenciones de Scripts R y Makefile

Patrón de Script R

Cada script de pipeline define .args condicionalmente: valores predeterminados interactivos si se ejecuta en la consola, commandArgs() si se llama desde Make.

# Definir argumentos: valores predeterminados para interactivo, commandArgs cuando se llama desde Make
.args <- if (interactive()) c(
  "data/default1.rds",
  "data/default2.rds",
  "R/shared_inputs.R",
  "output/default.csv"
) else commandArgs(trailingOnly = TRUE)

# Extraer de .args por posición
input1 <- .args[1]
input2 <- .args[2]
helper <- .args[3]
output <- .args[4]

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

# Ahora: leer entradas, computar, escribir salida, terminar
data <- read_rds(input1)
results <- process(data)
write_csv(results, output)

En Make: Rscript $^ $@ pasa todos los prerrequisitos en orden, luego el destino. Interactivamente: Rscript R/myscript.R usa los valores predeterminados de .args (no se necesitan argumentos).

Patrón de Makefile

Usa archivos destino (no reglas phony). Solo reconstruye cuando los prerrequisitos cambian.

# Creación de directorios (orden-solo, no dispara reconstrucciones)
output output/figures output/tables:
	mkdir -p $@

# Una entrada → una salida
output/processed.rds: data/raw.csv R/process.R R/shared_inputs.R | output
	Rscript $^ $@

# Múltiples entradas → una salida
output/scores.csv: output/model.rds data/test.rds R/score.R R/shared_inputs.R | output
	Rscript $^ $@

# Destinos phony solo por conveniencia (no rastreados por Make)
.PHONY: all
all: output/scores.csv

Sintaxis clave:

  • $^ = todos los prerrequisitos en orden
  • $@ = nombre del archivo destino
  • | output = prerrequisito de orden-solo (directorio creado, pero la marca de tiempo se ignora)

Configuración Compartida

Un archivo contiene cada valor que necesitan dos o más scripts. Sin duplicación entre 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

# Cada script fuente importa esto, nunca lo redefine
source("R/shared_inputs.R")

Organización de Archivos

project/
├── Makefile                 # Reglas de orquestación (archivos destino)
├── R/
│   ├── shared_inputs.R      # Configuración (importada por todos los scripts)
│   ├── process_data.R       # Scripts del pipeline
│   ├── fit_model.R          # (un trabajo por script)
│   ├── score_model.R
│   ├── helpers.R            # Funciones reutilizables (sin prefijo de etapa)
│   ├── models.R             # Documentado con roxygen2
│   └── utils.R
├── data/
│   ├── raw/                 # Datos fuente
│   ├── processed/           # Generado (gitignorado o rastreado con DVC)
│   └── features/
├── output/
│   ├── figures/             # Comprometido (para que el manuscrito se renderice en CI)
│   ├── tables/              # Archivos .csv con todos los números
│   ├── models/              # Objetos de modelo ajustados (.rds)
│   └── predictions/         # .csv con salidas del modelo
└── paper/
    ├── paper.qmd            # Manuscrito (lee output/*.csv)
    └── bibliography.bib

Los prefijos de etapa ordenan en orden de ejecución:

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

Los archivos de funciones no tienen prefijo para que las herramientas de documentación puedan distinguirlos de los scripts del pipeline.

Reglas de Patrón Make

Usa reglas de patrón (%) para destinos repetitivos. Define una vez, aplica a muchos.

# Patrón: procesar cada archivo de entrada
INPUTS := file1 file2 file3

# Regla genérica: transformar cualquier %.csv a output/processed_%.rds
output/processed_%.rds: input/%.csv R/process.R R/shared_inputs.R | output
	Rscript $^ $@

# Destino de conveniencia: todos los archivos procesados
all_processed: $(patsubst input/%.csv, output/processed_%.rds, $(wildcard input/*.csv))

# Anulación para caso especial: file1 necesita una entrada diferente
output/processed_file1.rds: input/file1_v2.csv R/process.R R/shared_inputs.R | output
	Rscript $^ $@

Una salida por regla: cada script escribe exactamente un archivo, por lo que cada regla tiene exactamente un destino. Si una etapa necesita dos archivos, escribe dos scripts y dos reglas (ver "Una Salida por Script" en Patrones Comunes).

Organización de Funciones

Scripts del Pipeline

Un trabajo por script y una salida por script. Cada etapa lee lo que la anterior escribió.

# R/fit_model.R
# Propósito: Ajustar un modelo a datos preparados
# Entradas: data/prepared.rds, R/shared_inputs.R
# Salida: 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)

Cualquier otra cosa derivada de las mismas entradas, como un resumen de los datos de entrenamiento, tiene su propio script y su propia salida:

# R/summarise_training.R
# Propósito: Resumir los datos utilizados para ajustar el modelo
# Entradas: data/prepared.rds, R/shared_inputs.R
# Salida: 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
)

Archivos de Funciones

Las funciones reutilizables van en archivos separados (sin prefijo de etapa). Documentar con roxygen2.

# R/models.R

#' Ajustar un modelo a datos
#'
#' Entrena un bosque aleatorio usando parámetros especificados.
#'
#' @details
#' Suposiciones sobre las entradas:
#' - los datos tienen la columna `outcome` (binaria: 0/1) y todos los predictores son numéricos
#' - seed es un entero para reproducibilidad
#'
#' @param data Un tibble con columnas de outcome y predictores.
#' @param seed Entero para generación de números aleatorios.
#' @param mtry Entero; número de variables a muestrear por división.
#'
#' @return Un objeto de modelo ajustado (clase ranger).
#'
#' @export
fit_model <- function(data, seed = 42, mtry = floor(sqrt(ncol(data) - 1))) {
  model <- ranger(outcome ~ ., data = data, seed = seed, mtry = mtry)
  model
}

Ejecuta make docs para regenerar man/ y NAMESPACE a partir de bloques roxygen.

Configuración Compartida

Ubicación central para parámetros de todo el estudio. Cada script la importa.

# R/shared_inputs.R

# Diseño del estudio
COUNTRY <- "Country_Name"
SEED <- 42
ANALYSIS_YEARS <- 2017:2023

# Rutas (calculadas, no codificadas)
DATA_DIR <- "data"
OUTPUT_DIR <- "output"
HORIZON <- as.integer(Sys.getenv("HORIZON", "1"))

# Hiperparámetros del modelo
RF_MTRY <- NULL  # sqrt(p)
RF_NTREES <- 500

# No definir nada más aquí.
# Los valores específicos de cada etapa permanecen en el script de su etapa.

Guías de Estilo

Pila Tidyverse

Estandarizar en estos paquetes:

library(tidyverse)   # núcleo: dplyr, tidyr, ggplot2, readr, purrr
library(fs)          # operaciones del sistema de archivos (dir_create, file_exists)
library(cli)         # señalización de errores (cli_abort, cli_warn)
library(here)        # rutas relativas (here("R", "script.R"))
library(scales)      # formato de gráficos
library(yardstick)   # métricas de modelo

Nunca usar:

  • Subsetting base o aggregate() (usar dplyr::filter(), dplyr::summarise())
  • map_dfr() (usar map() + list_rbind())
  • group_by() + ungroup() (usar argumento .by =)
  • gather(), spread() (usar pivot_longer(), pivot_wider())
  • %>% (usar pipe nativo |>)
  • function(x) (usar lambda \(x))
  • Funciones obsoletas (verificar la insignia de ciclo de vida en la página de ayuda)

Formateo de Código

Usar el formateador air (impone líneas de 80 caracteres, sangría de 2 espacios):

air format R/                  # formatear todo el directorio
air format R/myscript.R        # formatear un archivo

Configuraciones en air.toml en la raíz del repositorio (rastreado, no personal).

Comentarios

Comentar por qué el código hace algo, no qué (solo donde la justificación no es obvia al leer el código). Evitar comentarios que solo repiten el código.

# Mal: repite lo que el código obviamente hace
# Ajustar un bosque aleatorio con hiperparámetros predeterminados
model <- ranger(outcome ~ ., data = data)

# Bien: explica por qué elegimos esto
# Bosque aleatorio en lugar de CART aquí porque necesitamos intervalos de confianza de regresión cuantílica
model <- ranger(outcome ~ ., data = data)

# Mal: obvio por el código
# Filtrar filas donde el año está en ANALYSIS_YEARS
data <- data |> filter(year %in% ANALYSIS_YEARS)

# Bien: explica el razonamiento no obvio
# Excluir el año 2015 debido a cobertura de vigilancia incompleta (ver Métodos)
data <- data |> filter(year >= 2016)

# No se necesita comentario: el código se explica por sí mismo
data <- data |>
  select(district, year, outcome) |>
  filter(!is.na(outcome))

Escribir comentarios en la sección de Métodos del manuscrito al explicar una elección metodológica; usar comentarios de código solo cuando la decisión es inesperada o no estándar.

Sin Impresión de Salidas

Los resultados solo residen en archivos CSV, RDS y PNG comprometidos. No usar message(), print(), cat(), o sprintf() para salida informativa. Excepciones: cli_abort() y cli_warn() (solo para flujo de control).

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

# Bien (escribir a un archivo en su lugar)
meta <- tibble(n_rows = nrow(data))
write_csv(meta, "output/metadata.csv")

El manuscrito lee output/metadata.csv al renderizar, por lo que cada número aparece en archivos comprometidos.

Flujo de Trabajo de Validación

Al escribir un nuevo script, probar progresivamente antes de agregar reglas Make.

Paso 1: Probar Interactivamente (con valores predeterminados)

# ¿Funciona sin argumentos?
Rscript R/myscript.R
# Verificar que los archivos de salida se crearon
ls -lh output/

Paso 2: Probar con Argumentos (imita a Make)

# Pasar argumentos como lo haría Make
Rscript R/myscript.R \
  data/raw.csv \
  R/shared_inputs.R \
  output/result.rds
# Comparar con la salida del Paso 1

Paso 3: Agregar Regla Make y Probar

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

# Probarlo
make output/result.rds

# Verificar: modificar datos debería disparar reconstrucción
touch data/raw.csv
make output/result.rds  # Debería re-ejecutar

# Verificar: modificar output/result.rds (no dependencia) no dispara reconstrucción
touch output/result.rds
make output/result.rds  # Debería decir "está actualizado"

Paso 4: Ejecución en Seco

make --dry-run output/result.rds  # Mostrar qué se ejecutaría sin ejecutarlo

Ejemplo: Pipeline Simple de 3 Etapas

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)

Ejecutarlo

make            # Construye todo en orden (process → fit → score)
make output/model.rds --always-make  # Forzar reconstrucción solo de este paso
make --dry-run  # Mostrar qué se ejecutaría

Lista de Verificación para Nuevos Scripts

Al escribir una nueva etapa del pipeline:

  • El script acepta argumentos en orden: entradas → helper → salida
  • El script incluye valores predeterminados de .args para pruebas interactivas
  • El script importa R/shared_inputs.R (nunca define valores de todo el estudio)
  • El script lee sus entradas, computa y escribe exactamente un archivo de salida
  • Sin print(), message(), o cat() (usar cli_abort(), cli_warn() solo para errores)
  • Todos los resultados escritos en archivos CSV/RDS/PNG comprometidos
  • Dependencias listadas correctamente en Makefile (en orden)
  • Los prerrequisitos de directorio de orden-solo usan sintaxis |
  • Probado interactivamente, luego con argumentos, luego vía Make
  • La regla Makefile usa sintaxis $^ y $@
  • Código formateado con air format R/scriptname.R
  • Funciones reutilizables extraídas a archivos separados y documentadas con roxygen2
  • Ninguna llamada a install.packages() o library() dentro de scripts del pipeline (hacer en Makefile o renv.lock)
  • No hay valores de ruta o parámetros codificados; todo viene de shared_inputs.R o variables de entorno

Skills relacionados