Agent skill
network-meta-analysis
Network meta-analysis for comparing multiple treatments
Stars
163
Forks
31
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/development/network-meta-analysis
SKILL.md
Network Meta-Analysis Skill
Overview
Network meta-analysis (NMA) extends traditional pairwise meta-analysis to compare multiple treatments simultaneously, even when some treatments have never been directly compared in head-to-head trials.
When to Use
- Comparing 3+ treatment options
- Combining direct and indirect evidence
- Determining treatment rankings
- Creating treatment recommendations
Key Concepts
Network Geometry
- Nodes: Treatments being compared
- Edges: Direct comparisons from studies
- Thickness: Number of studies/participants
Consistency
- Direct evidence: From head-to-head trials
- Indirect evidence: Derived through common comparators
- Consistency assumption: Direct ≈ Indirect
Transitivity
For valid indirect comparisons, studies comparing A vs B must be similar to those comparing B vs C (effect modifiers must be balanced).
R Code Templates
Basic NMA Setup
r
library(netmeta)
library(meta)
# Load pairwise data
# Format: study, treat1, treat2, TE, seTE (or raw data)
data <- read.csv("{{INPUT}}")
# For binary outcomes - calculate OR
data$TE <- log((data$events1 / (data$n1 - data$events1)) /
(data$events2 / (data$n2 - data$events2)))
data$seTE <- sqrt(1/data$events1 + 1/(data$n1-data$events1) +
1/data$events2 + 1/(data$n2-data$events2))
# Run NMA
nma <- netmeta(
TE = TE,
seTE = seTE,
treat1 = treat1,
treat2 = treat2,
studlab = study,
data = data,
sm = "OR",
reference.group = "{{REFERENCE}}",
random = TRUE,
details.chkmultiarm = TRUE
)
summary(nma)
Network Plot
r
# Network geometry
png("{{OUTPUT}}/network_plot.png", width = 800, height = 800, res = 150)
netgraph(nma,
plastic = TRUE,
thickness = "number.of.studies",
number.of.studies = TRUE,
points = TRUE,
cex.points = 3,
col = "blue",
multiarm = TRUE)
dev.off()
# Alternative with ggplot
library(ggplot2)
library(ggraph)
# Create edge list
edges <- data.frame(
from = data$treat1,
to = data$treat2,
weight = 1
) %>%
group_by(from, to) %>%
summarise(n = n(), .groups = 'drop')
# Network visualization
graph <- igraph::graph_from_data_frame(edges)
ggraph(graph, layout = 'stress') +
geom_edge_link(aes(width = n), alpha = 0.5) +
geom_node_point(size = 8, color = 'steelblue') +
geom_node_text(aes(label = name), repel = TRUE) +
theme_void() +
labs(title = "Network of Treatment Comparisons")
ggsave("{{OUTPUT}}/network_ggplot.png", width = 10, height = 8)
Forest Plot vs Reference
r
# Forest plot comparing all treatments to reference
png("{{OUTPUT}}/forest_nma.png", width = 1000, height = 800, res = 150)
forest(nma,
reference.group = "{{REFERENCE}}",
sortvar = -TE,
smlab = "Odds Ratio vs {{REFERENCE}}",
drop.reference.group = TRUE)
dev.off()
League Table
r
# Generate league table
league <- netleague(nma,
digits = 2,
bracket = "(",
separator = " to ")
# Upper triangle: random effects estimates
# Lower triangle: direct evidence only
# Save as CSV
league_df <- as.data.frame(league$random)
write.csv(league_df, "{{OUTPUT}}/league_table.csv")
# Pretty print
print(league, digits = 2)
Treatment Rankings
r
# Calculate rankings
rank <- netrank(nma, small.values = "{{DIRECTION}}") # "good" or "bad"
# SUCRA/P-scores
print(rank)
# Rankogram
png("{{OUTPUT}}/rankogram.png", width = 1000, height = 600, res = 150)
plot(rank, cumulative = FALSE)
dev.off()
# Cumulative ranking
png("{{OUTPUT}}/sucra_plot.png", width = 1000, height = 600, res = 150)
plot(rank, cumulative = TRUE)
dev.off()
# P-scores (frequentist analog of SUCRA)
cat("\nP-scores (probability of being best):\n")
print(sort(rank$Pscore, decreasing = TRUE))
Consistency Assessment
r
# Check network connectivity
netconnection(data$treat1, data$treat2)
# Global inconsistency (design-by-treatment interaction)
decomp <- decomp.design(nma)
print(decomp)
# Q statistics
cat("\nInconsistency Q-statistic:\n")
cat(sprintf("Q = %.2f, df = %d, p = %.4f\n",
decomp$Q.inc.random, decomp$df.Q.inc, decomp$pval.Q.inc))
# Local inconsistency (node-splitting)
split <- netsplit(nma)
png("{{OUTPUT}}/nodesplit.png", width = 1200, height = 800, res = 150)
forest(split, show = "all")
dev.off()
# Summary
cat("\nNode-splitting results:\n")
print(split)
Comparison-Adjusted Funnel Plot
r
# Publication bias assessment
png("{{OUTPUT}}/funnel_nma.png", width = 800, height = 600, res = 150)
funnel(nma,
order = netrank(nma)$ranking,
pch = data$treat1)
dev.off()
Heat Plot
r
# Contribution matrix visualization
library(NMAoutlier) # or custom implementation
# Shows which studies contribute to each comparison
png("{{OUTPUT}}/heatplot.png", width = 1000, height = 800, res = 150)
netheat(nma, random = TRUE)
dev.off()
Data Preparation
From Arm-Level Data
r
# Convert arm-level to contrast-level
library(netmeta)
# Arm-level format
arm_data <- data.frame(
study = c("A", "A", "A", "B", "B"),
treatment = c("Drug1", "Drug2", "Placebo", "Drug1", "Drug3"),
events = c(10, 15, 20, 8, 5),
n = c(50, 50, 50, 40, 40)
)
# Convert to pairwise
pw <- pairwise(
treat = treatment,
event = events,
n = n,
studlab = study,
data = arm_data,
sm = "OR"
)
Handling Multi-Arm Studies
r
# Multi-arm studies require correlation adjustment
# netmeta handles this automatically if studlab properly identifies studies
# For gemtc (Bayesian NMA), use arm-level data directly
library(gemtc)
network <- mtc.network(
data.ab = arm_data # arm-based data
)
model <- mtc.model(network,
likelihood = "binom",
link = "logit")
results <- mtc.run(model, n.adapt = 5000, n.iter = 20000)
Interpretation Guidelines
Effect Estimates
- League table shows all pairwise comparisons
- Row treatment vs column treatment
- Values < 1 favor row treatment (for OR/RR)
Rankings
- SUCRA/P-score: Probability of being among the best
- 100% = always best, 0% = always worst
- Consider uncertainty in rankings
Consistency
- If p < 0.05 for inconsistency tests, interpret with caution
- May indicate effect modification or bias
- Consider splitting network or investigating sources
Common Issues
Disconnected Network
- Some treatments not connected to others
- Cannot make all comparisons
- May need to exclude some treatments
Sparse Network
- Few studies per comparison
- Wide confidence intervals
- Rankings unreliable
Inconsistency
- Direct ≠ indirect estimates
- Investigate effect modifiers
- Consider separate analyses
GRADE for NMA
Rate confidence for each comparison:
- Within-study bias (RoB of contributing studies)
- Publication bias (comparison-adjusted funnel)
- Indirectness (transitivity violations)
- Imprecision (CI width, ranking uncertainty)
- Heterogeneity (I² for direct comparisons)
- Incoherence (inconsistency tests)
Use CINeMA tool for systematic assessment.
Didn't find tool you were looking for?