Community연구 & 데이터 분석github.com

jamesmbaazam/r-gnumake-orchestrator

GNU Make를 사용하여 R 데이터 분석 파이프라인을 오케스트레이션하기 위한 일반 패턴입니다. 파일 대상 워크플로우와 표준화된 스크립트 계약을 제공합니다.

r-gnumake-orchestrator란 무엇인가요?

이 스킬은 R과 GNU Make를 결합하여 재현 가능한 데이터 분석 파이프라인을 구축하기 위한 일반 패턴을 제공합니다. Claude Code에서 사용할 수 있으며, `/skill r-gnumake-orchestrator` 명령어로 로드합니다. 핵심 원칙은 스크립트를 순수 함수처럼 취급하여 인수를 받고, 입력을 읽고, 하나의 출력을 쓰고 종료하는 것입니다. Makefile은 파일 대상 규칙을 사용하여 의존성이 변경될 때만 다시 빌드되도록 하여 효율성을 극대화합니다. 모든 설정은 `R/shared_inputs.R` 파일에 중앙 집중화되어 있으며, Tidyverse 생태계와 roxygen2 문서화를 일관되게 사용합니다. 이 패턴은 소규모 탐색적 분석부터 수십 개의 단계와 수천 개의 생성된 아티팩트가 있는 대규모 연구 컴펜디움까지 확장 가능합니다.

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

Installed? Explore more 연구 & 데이터 분석 skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

R + GNU Make 재현 가능한 연구 패턴

이것은 Claude 스킬입니다. Claude에서 /skill r-gnumake-orchestrator로 로드하여 재현 가능한 데이터 분석 파이프라인 구축 패턴에 접근하세요.

R에서 GNU Make를 사용하여 분석 파이프라인을 오케스트레이션하기 위한 가이드입니다. 파일 대상 워크플로우와 표준화된 스크립트 인터페이스 계약을 사용합니다.

개요

이 스킬은 재현 가능한 데이터 분석을 위한 일반 패턴을 제공합니다:

  • 스크립트를 순수 함수처럼: 인수 수락, 입력 읽기, 하나의 출력 쓰기, 종료
  • 파일 대상 Makefile: 의존성이 변경될 때만 다시 빌드
  • 공유 설정: 모든 스크립트에서 하나의 진실 공급원
  • Tidyverse 생태계: 일관된 최신 R 패키지
  • roxygen2 문서화: 모든 재사용 가능한 함수가 문서화됨
  • 출력 인쇄 금지: 결과는 커밋된 데이터 파일에만 저장

이러한 패턴은 소규모 탐색적 분석부터 수십 개의 단계와 수천 개의 생성된 아티팩트가 있는 대규모 연구 컴펜디움까지 확장 가능합니다.

R 스크립트 및 Makefile 규칙

R 스크립트 패턴

모든 파이프라인 스크립트는 조건부로 .args를 정의합니다: 콘솔에서 실행 시 대화형 기본값, Make에서 호출 시 commandArgs()를 사용합니다.

# 인수 정의: 대화형일 때 기본값, Make에서 호출될 때 commandArgs
.args <- if (interactive()) c(
  "data/default1.rds",
  "data/default2.rds",
  "R/shared_inputs.R",
  "output/default.csv"
) else commandArgs(trailingOnly = TRUE)

# .args에서 위치별로 추출
input1 <- .args[1]
input2 <- .args[2]
helper <- .args[3]
output <- .args[4]

source(helper)  # COUNTRY, SEED, OUTPUT_DIR 등 로드

# 이제: 입력 읽기, 계산, 출력 쓰기, 종료
data <- read_rds(input1)
results <- process(data)
write_csv(results, output)

Make에서: Rscript $^ $@는 모든 전제조건을 순서대로 전달한 다음 대상을 전달합니다. 대화형으로: Rscript R/myscript.R은 .args 기본값을 사용합니다 (인수 불필요).

Makefile 패턴

파일 대상 사용 (phony 규칙 금지). 전제조건이 변경될 때만 다시 빌드합니다.

# 디렉토리 생성 (순서 전용, 재빌드 트리거 안 함)
output output/figures output/tables:
	mkdir -p $@

# 단일 입력 → 단일 출력
output/processed.rds: data/raw.csv R/process.R R/shared_inputs.R | output
	Rscript $^ $@

# 다중 입력 → 단일 출력
output/scores.csv: output/model.rds data/test.rds R/score.R R/shared_inputs.R | output
	Rscript $^ $@

# 편의를 위한 Phony 대상만 (Make가 추적하지 않음)
.PHONY: all
all: output/scores.csv

주요 구문:

  • $^ = 모든 전제조건을 순서대로
  • $@ = 대상 파일명
  • | output = 순서 전용 전제조건 (디렉토리 생성되지만 타임스탬프는 무시)

공유 설정

두 개 이상의 스크립트가 필요로 하는 모든 값은 하나의 파일에 보관합니다. 스크립트 간 중복 없음.

# 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

# 모든 스크립트가 이것을 소싱하며, 절대 재정의하지 않음
source("R/shared_inputs.R")

파일 구성

project/
├── Makefile                 # 오케스트레이션 규칙 (파일 대상)
├── R/
│   ├── shared_inputs.R      # 설정 (모든 스크립트에서 소싱)
│   ├── process_data.R       # 파이프라인 스크립트
│   ├── fit_model.R          # (스크립트당 하나의 작업)
│   ├── score_model.R
│   ├── helpers.R            # 재사용 가능한 함수 (단계 접두사 없음)
│   ├── models.R             # roxygen2 문서화됨
│   └── utils.R
├── data/
│   ├── raw/                 # 원본 데이터
│   ├── processed/           # 생성됨 (gitignore 또는 DVC 추적)
│   └── features/
├── output/
│   ├── figures/             # 커밋됨 (CI에서 원고 렌더링)
│   ├── tables/              # 모든 숫자가 포함된 .csv 파일
│   ├── models/              # 적합된 모델 객체 (.rds)
│   └── predictions/         # 모델 출력이 있는 .csv
└── paper/
    ├── paper.qmd            # 원고 (output/*.csv 읽음)
    └── bibliography.bib

단계 접두사는 실행 순서대로 정렬:

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

함수 파일은 접두사가 없으므로 문서화 도구가 파이프라인 스크립트와 구분할 수 있습니다.

Make 패턴 규칙

반복적인 대상에 패턴 규칙(%)을 사용합니다. 한 번 정의하고 여러 대상에 적용합니다.

# 패턴: 각 입력 파일 처리
INPUTS := file1 file2 file3

# 일반 규칙: %.csv를 output/processed_%.rds로 변환
output/processed_%.rds: input/%.csv R/process.R R/shared_inputs.R | output
	Rscript $^ $@

# 편의 대상: 모든 처리된 파일
all_processed: $(patsubst input/%.csv, output/processed_%.rds, $(wildcard input/*.csv))

# 특수 케이스 재정의: file1은 다른 입력 필요
output/processed_file1.rds: input/file1_v2.csv R/process.R R/shared_inputs.R | output
	Rscript $^ $@

규칙당 하나의 출력: 모든 스크립트는 정확히 하나의 파일을 쓰므로, 각 규칙은 정확히 하나의 대상을 가집니다. 단계가 두 개의 파일을 필요로 한다면, 두 개의 스크립트와 두 개의 규칙을 작성하세요 (일반 패턴 아래 "출력당 하나의 스크립트" 참조).

함수 구성

파이프라인 스크립트

스크립트당 하나의 작업, 스크립트당 하나의 출력. 각 단계는 이전 단계가 쓴 내용을 읽습니다.

# R/fit_model.R
# 목적: 준비된 데이터에 모델 적합
# 입력: data/prepared.rds, R/shared_inputs.R
# 출력: 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)

동일한 입력에서 파생된 다른 모든 항목(예: 훈련 데이터 요약)은 자체 스크립트와 자체 출력을 가집니다:

# R/summarise_training.R
# 목적: 모델 적합에 사용된 데이터 요약
# 입력: data/prepared.rds, R/shared_inputs.R
# 출력: 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
)

함수 파일

재사용 가능한 함수는 별도 파일에 (단계 접두사 없음). roxygen2로 문서화.

# R/models.R

#' 데이터에 모델 적합
#'
#' 지정된 매개변수를 사용하여 랜덤 포레스트를 훈련합니다.
#'
#' @details
#' 입력에 대한 가정:
#' - 데이터에 `outcome` 열이 있음 (이진: 0/1) 및 모든 예측변수는 숫자형
#' - seed는 재현성을 위한 정수
#'
#' @param data 결과 및 예측변수 열이 있는 티블.
#' @param seed 난수 생성을 위한 정수.
#' @param mtry 정수; 각 분할당 샘플링할 변수 수.
#'
#' @return 적합된 모델 객체 (ranger 클래스).
#'
#' @export
fit_model <- function(data, seed = 42, mtry = floor(sqrt(ncol(data) - 1))) {
  model <- ranger(outcome ~ ., data = data, seed = seed, mtry = mtry)
  model
}

make docs를 실행하여 roxygen 블록에서 man/ 및 NAMESPACE를 재생성합니다.

공유 설정

연구 전체 매개변수의 중앙 위치. 모든 스크립트가 소싱합니다.

# R/shared_inputs.R

# 연구 설계
COUNTRY <- "Country_Name"
SEED <- 42
ANALYSIS_YEARS <- 2017:2023

# 경로 (계산됨, 하드코딩되지 않음)
DATA_DIR <- "data"
OUTPUT_DIR <- "output"
HORIZON <- as.integer(Sys.getenv("HORIZON", "1"))

# 모델 하이퍼파라미터
RF_MTRY <- NULL  # sqrt(p)
RF_NTREES <- 500

# 여기서 다른 것은 정의하지 마십시오.
# 단계별 값은 해당 단계의 스크립트에 남아 있습니다.

스타일 가이드라인

Tidyverse 스택

다음 패키지로 표준화:

library(tidyverse)   # 핵심: dplyr, tidyr, ggplot2, readr, purrr
library(fs)          # 파일시스템 작업 (dir_create, file_exists)
library(cli)         # 오류 신호 (cli_abort, cli_warn)
library(here)        # 상대 경로 (here("R", "script.R"))
library(scales)      # 플롯 서식
library(yardstick)   # 모델 메트릭

절대 사용하지 말 것:

  • 기본 서브셋팅이나 aggregate() (대신 dplyr::filter(), dplyr::summarise() 사용)
  • map_dfr() (대신 map() + list_rbind() 사용)
  • group_by() + ungroup() (대신 .by = 인수 사용)
  • gather(), spread() (대신 pivot_longer(), pivot_wider() 사용)
  • %>% (대신 네이티브 파이프 |> 사용)
  • function(x) (대신 람다 \(x) 사용)
  • 대체된 함수 (도움말 페이지의 생애주기 배지 확인)

코드 서식

air 포맷터 사용 (80자 줄, 2칸 들여쓰기 적용):

air format R/                  # 전체 디렉토리 서식
air format R/myscript.R        # 단일 파일 서식

설정은 air.toml 파일에 저장 (리포지토리 루트에, 개인 설정 아님).

주석

코드가 무엇을 하는지가 아닌 왜 하는지에 주석을 달아야 합니다 (이유가 코드를 읽는 것만으로 명확하지 않은 경우에만). 코드를 단순히 다시 설명하는 주석은 피하십시오.

# 나쁨: 코드가 명백히 하는 것을 반복
# 기본 하이퍼파라미터로 랜덤 포레스트 적합
model <- ranger(outcome ~ ., data = data)

# 좋음: 우리가 왜 이 선택을 했는지 설명
# 분위수 회귀에서 신뢰 구간이 필요하므로 CART 대신 랜덤 포레스트 사용
model <- ranger(outcome ~ ., data = data)

# 나쁨: 코드에서 명백함
# ANALYSIS_YEARS에 있는 연도로 행 필터링
data <- data |> filter(year %in% ANALYSIS_YEARS)

# 좋음: 명확하지 않은 추론 설명
# 불완전한 감시 범위로 인해 2015년 제외 (방법 참조)
data <- data |> filter(year >= 2016)

# 주석 불필요: 코드가 자명함
data <- data |>
  select(district, year, outcome) |>
  filter(!is.na(outcome))

방법론적 선택을 설명할 때는 원고의 방법 섹션에 주석을 작성하고, 결정이 예상치 못하거나 비표준적인 경우에만 코드 주석을 사용하십시오.

출력 인쇄 금지

결과는 커밋된 CSV, RDS 및 PNG 파일에만 저장됩니다. 정보 출력용 message(), print(), cat() 또는 sprintf() 사용 금지. 예외: cli_abort() 및 cli_warn() (제어 흐름 전용).

# 나쁨
message(sprintf("처리된 행 수: %d", nrow(data)))
cat("모델 훈련 완료\n")

# 좋음 (대신 파일에 쓰기)
meta <- tibble(n_rows = nrow(data))
write_csv(meta, "output/metadata.csv")

원고는 렌더링 시 output/metadata.csv를 읽으므로 모든 숫자가 커밋된 파일에 나타납니다.

검증 워크플로우

새 스크립트를 작성할 때, Make 규칙을 추가하기 전에 점진적으로 테스트하십시오.

1단계: 대화형 테스트 (기본값 사용)

# 인수 없이 작동하는가?
Rscript R/myscript.R
# 출력 파일이 생성되었는지 확인
ls -lh output/

2단계: 인수로 테스트 (Make 흉내)

# Make가 전달하는 방식대로 인수 전달
Rscript R/myscript.R \
  data/raw.csv \
  R/shared_inputs.R \
  output/result.rds
# 1단계 출력과 비교

3단계: Make 규칙 추가 및 테스트

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

# 테스트
make output/result.rds

# 확인: 데이터 수정 시 재빌드 트리거
touch data/raw.csv
make output/result.rds  # 재실행되어야 함

# 확인: output/result.rds 수정 시 (의존성 아님) 재빌드 트리거 안 함
touch output/result.rds
make output/result.rds  # "up to date"라고 표시되어야 함

4단계: 드라이 런

make --dry-run output/result.rds  # 실행하지 않고 무엇이 실행될지 표시

예제: 간단한 3단계 파이프라인

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)

실행

make            # 순서대로 모두 빌드 (process → fit → score)
make output/model.rds --always-make  # 이 단계만 강제 재빌드
make --dry-run  # 실행될 내용 표시

새 스크립트를 위한 체크리스트

새 파이프라인 단계를 작성할 때:

  • 스크립트가 순서대로 인수 수락: 입력 → 헬퍼 → 출력
  • 스크립트에 대화형 테스트를 위한 .args 기본값 포함
  • 스크립트가 R/shared_inputs.R을 소싱 (연구 전체 값을 절대 정의하지 않음)
  • 스크립트가 입력을 읽고, 계산하고, 정확히 하나의 출력 파일을 씀
  • print(), message() 또는 cat() 없음 (오류에만 cli_abort(), cli_warn() 사용)
  • 모든 결과가 커밋된 CSV/RDS/PNG 파일에 기록됨
  • 의존성이 Makefile에 올바르게 나열됨 (순서대로)
  • 순서 전용 디렉토리 전제조건이 | 구문 사용
  • 대화형으로, 그 다음 인수로, 그 다음 Make를 통해 테스트됨
  • Makefile 규칙이 $^ 및 $@ 구문 사용
  • 코드가 air format R/scriptname.R로 서식 지정됨
  • 재사용 가능한 함수가 별도 파일로 추출되고…

… (전체 내용이 번역됨)

관련 스킬