| name | tidy-itc-workflow |
| description | Master tidy modelling patterns for ITC analyses following TMwR principles. Covers workflow structure, consistent interfaces, reproducibility best practices, and data validation. Use when setting up ITC analysis projects or building pipelines. |
Tidy ITC Workflow
Apply tidy modelling principles from "Tidy Modeling with R" (TMwR) to indirect treatment comparison analyses for consistent, reproducible, and maintainable code.
When to Use This Skill
- Setting up a new ITC analysis project
- Building reproducible analysis pipelines
- Creating standardized interfaces across ITC methods
- Ensuring code quality and maintainability
- Reviewing code for tidy modelling compliance
Core Principles from TMwR
1. The "Pit of Success" Philosophy
- Software should facilitate proper usage by design
- Users should "fall into winning practices" naturally
- Interface must protect users from methodological errors
2. Workflow-Centric Architecture
Every ITC analysis follows this structure:
Data → Validation → Preparation → Analysis → Diagnostics → Reporting
3. Consistent Interfaces
All ITC functions should have predictable patterns:
itc_function(
data,
outcome_var,
treatment_var,
covariates = NULL,
method = "default",
alpha = 0.05,
seed = NULL,
verbose = TRUE,
...
)
list(
results = tibble(...),
diagnostics = list(...),
model = fitted_model,
data_summary = list(...),
call = match.call(),
parameters = list(...)
)
ITC Workflow Structure
Step 1: Project Setup
project/
├── R/
│ ├── 01_data_prep.R
│ ├── 02_analysis.R
│ ├── 03_sensitivity.R
│ └── 04_reporting.R
├── data/
│ ├── raw/
│ └── processed/
├── output/
│ ├── figures/
│ └── tables/
├── renv.lock
└── _targets.R
Step 2: Environment Setup
library(tidyverse)
library(meta)
library(netmeta)
library(maicplus)
library(multinma)
options(
dplyr.summarise.inform = FALSE,
mc.cores = parallel::detectCores() - 1
)
set.seed(12345)
Step 3: Data Validation
validate_ipd <- function(data, outcome_var, treatment_var, covariates = NULL) {
errors <- character()
warnings <- character()
required_cols <- c(outcome_var, treatment_var)
if (!is.null(covariates)) required_cols <- c(required_cols, covariates)
missing_cols <- setdiff(required_cols, names(data))
if (length(missing_cols) > 0) {
errors <- c(errors, paste("Missing columns:", paste(missing_cols, collapse = ", ")))
}
if (outcome_var %in% names(data)) {
outcome_vals <- unique(data[[outcome_var]])
if (all(outcome_vals %in% c(0, 1, NA))) {
message("Detected binary outcome")
} else if (is.numeric(data[[outcome_var]])) {
message("Detected continuous outcome")
}
}
if (treatment_var %in% names(data)) {
n_trt <- length(unique(data[[treatment_var]]))
if (n_trt < 2) {
errors <- c(errors, "Treatment variable must have at least 2 levels")
}
message(sprintf("Found %d treatment levels", n_trt))
}
if (any(is.na(data[required_cols]))) {
n_missing <- sum(!complete.cases(data[required_cols]))
warnings <- c(warnings, sprintf("%d observations with missing values", n_missing))
}
list(
valid = length(errors) == 0,
errors = errors,
warnings = warnings,
n_obs = nrow(data),
n_complete = sum(complete.cases(data[required_cols]))
)
}
Step 4: Data Preparation (Recipe Pattern)
create_itc_recipe <- function(data, outcome_var, treatment_var, covariates) {
recipe <- list(
handle_missing = function(d) {
d[complete.cases(d[c(outcome_var, treatment_var, covariates)]), ]
},
factor_treatment = function(d) {
d[[treatment_var]] <- factor(d[[treatment_var]])
d
},
center_covariates = function(d, centers = NULL) {
if (is.null(centers)) {
centers <- sapply(d[covariates], mean, na.rm = TRUE)
}
for (cov in covariates) {
d[[paste0(cov, "_centered")]] <- d[[cov]] - centers[[cov]]
}
attr(d, "covariate_centers") <- centers
d
}
)
class(recipe) <- c("itc_recipe", "list")
recipe
}
prep_itc_data <- function(data, recipe) {
result <- data
for (step_name in names(recipe)) {
result <- recipe[[step_name]](result)
}
result
}
Step 5: Analysis Workflow
run_itc_analysis <- function(
method = c("pairwise_ma", "nma", "maic", "stc", "ml_nmr"),
...
) {
method <- match.arg(method)
result <- switch(method,
pairwise_ma = run_pairwise_ma(...),
nma = run_nma(...),
maic = run_maic(...),
stc = run_stc(...),
ml_nmr = run_ml_nmr(...)
)
result$method <- method
result$timestamp <- Sys.time()
result$session_info <- sessionInfo()
class(result) <- c("itc_result", class(result))
result
}
Step 6: Result Standardization
standardize_itc_results <- function(result) {
tibble::tibble(
comparison = result$comparison,
effect_measure = result$effect_measure,
estimate = result$estimate,
ci_lower = result$ci_lower,
ci_upper = result$ci_upper,
se = result$se,
p_value = result$p_value,
method = result$method,
n_studies = result$n_studies %||% NA_integer_,
n_patients = result$n_patients %||% NA_integer_,
heterogeneity_i2 = result$i2 %||% NA_real_,
heterogeneity_tau2 = result$tau2 %||% NA_real_
)
}
Reproducibility Best Practices
1. Seed Management
ANALYSIS_SEED <- 12345
set.seed(ANALYSIS_SEED)
bootstrap_result <- boot::boot(..., R = 1000)
library(doRNG)
registerDoRNG(ANALYSIS_SEED)
2. Package Version Control
renv::init()
renv::snapshot()
cat("Package versions:\n")
packageVersion("meta")
packageVersion("netmeta")
packageVersion("maicplus")
3. Session Documentation
sink("session_info.txt")
sessionInfo()
sink()
writeLines(capture.output(devtools::session_info()), "session_info.txt")
Data Validation Patterns
Binary Outcomes
validate_binary_outcome <- function(data, outcome_var) {
vals <- data[[outcome_var]]
if (!all(vals %in% c(0, 1, NA))) {
stop("Binary outcome must contain only 0, 1, or NA")
}
if (all(vals == 0, na.rm = TRUE) || all(vals == 1, na.rm = TRUE)) {
warning("All outcomes are identical - check data")
}
invisible(TRUE)
}
Survival Outcomes
validate_survival_outcome <- function(data, time_var, event_var) {
if (any(data[[time_var]] < 0, na.rm = TRUE)) {
stop("Survival times must be non-negative")
}
if (!all(data[[event_var]] %in% c(0, 1, NA))) {
stop("Event indicator must be 0, 1, or NA")
}
invisible(TRUE)
}
Aggregate Data
validate_agd <- function(agd, required_fields) {
missing <- setdiff(required_fields, names(agd))
if (length(missing) > 0) {
stop(sprintf("Missing AgD fields: %s", paste(missing, collapse = ", ")))
}
numeric_fields <- c("n_total", "n_events", "mean", "sd")
for (field in intersect(numeric_fields, names(agd))) {
if (any(agd[[field]] < 0, na.rm = TRUE)) {
stop(sprintf("Field '%s' contains negative values", field))
}
}
invisible(TRUE)
}
Result Tibble Standards
All ITC results should return tibbles with consistent column naming:
| Column | Type | Description |
|---|
| comparison | character | "A vs B" format |
| effect_measure | character | "OR", "HR", "MD", etc. |
| estimate | numeric | Point estimate |
| ci_lower | numeric | Lower CI bound |
| ci_upper | numeric | Upper CI bound |
| se | numeric | Standard error |
| p_value | numeric | P-value |
| method | character | Analysis method |
Common Anti-Patterns to Avoid
1. Hardcoded Values
data <- data[data$age > 65, ]
AGE_THRESHOLD <- 65
data <- data[data$age > AGE_THRESHOLD, ]
2. Missing Validation
result <- maic_anchored(weights, ipd, pseudo_ipd)
stopifnot(inherits(weights, "maicplus_estimate_weights"))
stopifnot(nrow(ipd) > 0)
result <- maic_anchored(
weights_object = weights,
ipd = ipd,
pseudo_ipd = pseudo_ipd
)
3. Unreproducible Operations
bootstrap_ci <- boot::boot.ci(boot_result)
set.seed(12345)
boot_result <- boot::boot(data, statistic, R = 1000)
bootstrap_ci <- boot::boot.ci(boot_result)
Resources