CommunityPesquisa e Análise de Dadosgithub.com

R + GNU Make Research Patterns

Padrões genéricos para orquestrar pipelines de análise de dados em R usando GNU Make com workflows baseados em arquivos e contratos de script padronizados

O que é R + GNU Make Research Patterns?

Esta skill para Claude, carregada com `/skill r-gnumake-orchestrator`, fornece padrões genéricos para construir pipelines de análise de dados reproduzíveis em R usando GNU Make. Ela se baseia em quatro princípios fundamentais: scripts agindo como funções puras (aceitam argumentos, leem entradas, escrevem uma saída e saem), Makefiles com alvos de arquivo (reconstroem apenas quando dependências mudam), configuração compartilhada centralizada (sem duplicação entre scripts), e o ecossistema Tidyverse com documentação roxygen2. O fluxo de trabalho garante que resultados sejam salvos exclusivamente em arquivos de dados versionados (CSV, RDS, PNG), sem impressão de saída para console, tornando todo o pipeline rastreável e reproduzível. A skill suporta desde análises exploratórias pequenas até grandes compêndios de pesquisa com dezenas de estágios e milhares de artefatos gerados, e inclui convenções detalhadas para organização de arquivos, formatação de código com `air`, validação incremental de scripts, e uso de regras de padrão no Makefile para tarefas repetitivas.

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

Installed? Explore more Pesquisa e Análise de Dados skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Padrões de Pesquisa Reprodutível com R + GNU Make

Esta é uma skill do Claude. Carregue com /skill r-gnumake-orchestrator no Claude para acessar padrões para construir pipelines de análise de dados reproduzíveis.

Um guia para orquestrar pipelines de análise em R usando GNU Make com workflows baseados em arquivos e um contrato de interface de script padronizado.

Visão Geral

Esta skill captura padrões genéricos para análise de dados reproduzível:

  • Scripts como funções puras: Aceitam argumentos, leem entradas, escrevem uma saída e saem
  • Makefiles com alvos de arquivo: Reconstroem apenas quando dependências mudam
  • Configuração compartilhada: Uma única fonte da verdade para todos os scripts
  • Ecossistema Tidyverse: Pacotes R modernos e consistentes
  • Documentação roxygen2: Cada função reutilizável é documentada
  • Sem impressão de saída: Resultados residem apenas em arquivos de dados versionados

Esses padrões escalam de análises exploratórias pequenas a grandes compêndios de pesquisa com dezenas de estágios e milhares de artefatos gerados.

Convenções de Scripts R e Makefile

Padrão de Script R

Todo script de pipeline define .args condicionalmente: padrões interativos se executado no console, commandArgs() se chamado pelo Make.

# Define argumentos: padrões para interativo, commandArgs quando chamado pelo Make
.args <- if (interactive()) c(
  "data/default1.rds",
  "data/default2.rds",
  "R/shared_inputs.R",
  "output/default.csv"
) else commandArgs(trailingOnly = TRUE)

# Extrai de .args por posição
input1 <- .args[1]
input2 <- .args[2]
helper <- .args[3]
output <- .args[4]

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

# Agora: leia entradas, compute, escreva saída, saia
data <- read_rds(input1)
results <- process(data)
write_csv(results, output)

No Make: Rscript $^ $@ passa todas as pré-requisitos em ordem, depois o alvo.
Interativamente: Rscript R/myscript.R usa os padrões de .args (nenhum argumento necessário).

Padrão de Makefile

Use alvos de arquivo (não regras phony). Reconstrua apenas quando as dependências mudarem.

# Criação de diretório (apenas ordem, não dispara reconstruções)
output output/figures output/tables:
	mkdir -p $@

# Entrada única → saída única
output/processed.rds: data/raw.csv R/process.R R/shared_inputs.R | output
	Rscript $^ $@

# Múltiplas entradas → saída única
output/scores.csv: output/model.rds data/test.rds R/score.R R/shared_inputs.R | output
	Rscript $^ $@

# Alvos phony apenas por conveniência (não rastreados pelo Make)
.PHONY: all
all: output/scores.csv

Sintaxe chave:

  • $^ = todas as pré-requisitos em ordem
  • $@ = nome do arquivo alvo
  • | output = pré-requisito apenas de ordem (diretório criado, mas timestamp ignorado)

Configuração Compartilhada

Um arquivo contém todo valor que dois ou mais scripts precisam. Sem duplicação 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

# Todo script carrega isso, nunca redefine
source("R/shared_inputs.R")

Organização de Arquivos

project/
├── Makefile                 # Regras de orquestração (alvos de arquivo)
├── R/
│   ├── shared_inputs.R      # Configuração (carregado por todos os scripts)
│   ├── process_data.R       # Scripts do pipeline
│   ├── fit_model.R          # (um trabalho por script)
│   ├── score_model.R
│   ├── helpers.R            # Funções reutilizáveis (sem prefixo de estágio)
│   ├── models.R             # Documentado com roxygen2
│   └── utils.R
├── data/
│   ├── raw/                 # Dados fonte
│   ├── processed/           # Gerados (gitignored ou rastreados por DVC)
│   └── features/
├── output/
│   ├── figures/             # Versionados (para o manuscrito renderizar no CI)
│   ├── tables/              # Arquivos .csv com todos os números
│   ├── models/              # Objetos de modelo ajustados (.rds)
│   └── predictions/         # .csv com saídas do modelo
└── paper/
    ├── paper.qmd            # Manuscrito (lê output/*.csv)
    └── bibliography.bib

Prefixos de estágio ordenam por ordem de execução:

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

Arquivos de funções não têm prefixo para que ferramentas de documentação possam distingui-los de scripts de pipeline.

Regras de Padrão do Make

Use regras de padrão (%) para alvos repetitivos. Defina uma vez, aplique a muitos.

# Padrão: processa cada arquivo de entrada
INPUTS := file1 file2 file3

# Regra genérica: transforma qualquer %.csv em output/processed_%.rds
output/processed_%.rds: input/%.csv R/process.R R/shared_inputs.R | output
	Rscript $^ $@

# Alvo de conveniência: todos os arquivos processados
all_processed: $(patsubst input/%.csv, output/processed_%.rds, $(wildcard input/*.csv))

# Sobrescrita para caso especial: file1 precisa de entrada diferente
output/processed_file1.rds: input/file1_v2.csv R/process.R R/shared_inputs.R | output
	Rscript $^ $@

Uma saída por regra: todo script escreve exatamente um arquivo, então toda regra tem exatamente um alvo. Se um estágio precisar de dois arquivos, escreva dois scripts e duas regras (veja "Uma Saída por Script" em Padrões Comuns).

Organização de Funções

Scripts de Pipeline

Um trabalho por script, e uma saída por script. Cada estágio lê o que o estágio anterior escreveu.

# R/fit_model.R
# Propósito: Ajustar um modelo a dados preparados
# Entradas: data/prepared.rds, R/shared_inputs.R
# Saída: 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)

Qualquer outra coisa derivada das mesmas entradas, como um resumo dos dados de treinamento, tem seu próprio script e sua própria saída:

# R/summarise_training.R
# Propósito: Resumir os dados usados para ajustar o modelo
# Entradas: data/prepared.rds, R/shared_inputs.R
# Saída: 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
)

Arquivos de Funções

Funções reutilizáveis vão em arquivos separados (sem prefixo de estágio). Documente com roxygen2.

# R/models.R

#' Ajusta um modelo a dados
#'
#' Treina uma floresta aleatória usando parâmetros especificados.
#'
#' @details
#' Suposições sobre as entradas:
#' - dados têm coluna `outcome` (binária: 0/1) e todos os preditores são numéricos
#' - seed é um inteiro para reprodutibilidade
#'
#' @param data Um tibble com colunas de resposta e preditores.
#' @param seed Inteiro para geração de números aleatórios.
#' @param mtry Inteiro; número de variáveis a amostrar por divisão.
#'
#' @return Um objeto de modelo ajustado (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
}

Execute make docs para regenerar man/ e NAMESPACE a partir dos blocos roxygen.

Configuração Compartilhada

Local central para parâmetros de todo o estudo. Todo script carrega.

# R/shared_inputs.R

# Design do estudo
COUNTRY <- "Country_Name"
SEED <- 42
ANALYSIS_YEARS <- 2017:2023

# Caminhos (computados, não hardcoded)
DATA_DIR <- "data"
OUTPUT_DIR <- "output"
HORIZON <- as.integer(Sys.getenv("HORIZON", "1"))

# Hiperparâmetros do modelo
RF_MTRY <- NULL  # sqrt(p)
RF_NTREES <- 500

# Não defina mais nada aqui.
# Valores específicos de estágio permanecem no script do estágio.

Diretrizes de Estilo

Stack Tidyverse

Padronize nestes pacotes:

library(tidyverse)   # core: dplyr, tidyr, ggplot2, readr, purrr
library(fs)          # operações de sistema de arquivos (dir_create, file_exists)
library(cli)         # sinalização de erro (cli_abort, cli_warn)
library(here)        # caminhos relativos (here("R", "script.R"))
library(scales)      # formatação de gráficos
library(yardstick)   # métricas de modelo

Nunca use:

  • Subsetting base ou aggregate() (use dplyr::filter(), dplyr::summarise())
  • map_dfr() (use map() + list_rbind())
  • group_by() + ungroup() (use argumento .by =)
  • gather(), spread() (use pivot_longer(), pivot_wider())
  • %>% (use pipe nativo |>)
  • function(x) (use lambda \(x))
  • Funções obsoletas (verifique o badge de ciclo de vida na página de ajuda)

Formatação de Código

Use o formatador air (impõe linhas de 80 caracteres, indentação de 2 espaços):

air format R/                  # formata diretório inteiro
air format R/myscript.R        # formata um arquivo

Configurações em air.toml na raiz do repositório (versionado, não pessoal).

Comentários

Comente por que o código faz algo, não o que (apenas onde a lógica não é óbvia pela leitura do código). Evite comentários que apenas repetem o código.

# Ruim: repete o que o código obviamente faz
# Ajusta uma floresta aleatória com hiperparâmetros padrão
model <- ranger(outcome ~ ., data = data)

# Bom: explica por que fizemos essa escolha
# Floresta aleatória em vez de CART aqui porque precisamos de intervalos de confiança de regressão quantílica
model <- ranger(outcome ~ ., data = data)

# Ruim: óbvio pelo código
# Filtra linhas onde ano está em ANALYSIS_YEARS
data <- data |> filter(year %in% ANALYSIS_YEARS)

# Bom: explica a lógica não óbvia
# Exclui ano de 2015 devido a cobertura de vigilância incompleta (veja Métodos)
data <- data |> filter(year >= 2016)

# Sem comentário necessário: o código é autoexplicativo
data <- data |>
  select(district, year, outcome) |>
  filter(!is.na(outcome))

Escreva comentários na seção de Métodos do manuscrito ao explicar uma escolha metodológica; use comentários de código apenas quando a decisão for inesperada ou fora do padrão.

Sem Impressão de Saída

Resultados residem apenas em arquivos CSV, RDS e PNG versionados. Nenhum message(), print(), cat(), ou sprintf() para saída informacional. Exceções: cli_abort() e cli_warn() (apenas fluxo de controle).

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

# Bom (escreva em arquivo em vez disso)
meta <- tibble(n_rows = nrow(data))
write_csv(meta, "output/metadata.csv")

O manuscrito lê output/metadata.csv no momento da renderização, então todo número aparece em arquivos versionados.

Fluxo de Validação

Ao escrever um novo script, teste progressivamente antes de adicionar regras ao Make.

Passo 1: Teste Interativamente (com padrões)

# Funciona sem argumentos?
Rscript R/myscript.R
# Verifique se os arquivos de saída foram criados
ls -lh output/

Passo 2: Teste com Argumentos (imita o Make)

# Passe argumentos como o Make faria
Rscript R/myscript.R \
  data/raw.csv \
  R/shared_inputs.R \
  output/result.rds
# Compare com a saída do Passo 1

Passo 3: Adicione Regra do Make e Teste

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

# Teste
make output/result.rds

# Verifique: modificar dados deve disparar reconstrução
touch data/raw.csv
make output/result.rds  # Deve re-executar

# Verifique: modificar output/result.rds (não dependência) não dispara reconstrução
touch output/result.rds
make output/result.rds  # Deve dizer "up to date"

Passo 4: Simulação

make --dry-run output/result.rds  # Mostra o que seria executado sem executar

Exemplo: Pipeline Simples de 3 Estágios

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)

Execute

make            # Constrói tudo em ordem (process → fit → score)
make output/model.rds --always-make  # Força reconstrução apenas desta etapa
make --dry-run  # Mostra o que seria executado

Lista de Verificação para Novos Scripts

Ao escrever um novo estágio de pipeline:

  • Script aceita argumentos em ordem: entradas → helper → saída
  • Script inclui padrões .args para teste interativo
  • Script carrega R/shared_inputs.R (nunca define valores de todo o estudo)
  • Script lê suas entradas, computa e escreve exatamente um arquivo de saída
  • Nenhum print(), message() ou cat() (use cli_abort(), cli_warn() para erros apenas)
  • Todos os resultados escritos em arquivos CSV/RDS/PNG versionados
  • Dependências listadas corretamente no Makefile (em ordem)
  • Pré-requisitos de diretório apenas de ordem usam sintaxe |
  • Testado interativamente, depois com argumentos, depois via Make
  • Regra do Makefile usa sintaxe $^ e $@
  • Código formatado com air format R/scriptname.R
  • Funções reutilizáveis extraídas para arquivos separados

Habilidades Relacionadas