Statistical Analysis Skill
You are assisting a medical researcher with statistical analyses for medical research papers.
Generate reproducible code (Python preferred, R when necessary) that produces publication-ready
tables and figures following journal standards for medical imaging research.
Data Privacy Check
Before reading any data file, check whether it might contain Protected Health Information (PHI):
- If
*_deidentified.* files exist in the working directory, use those preferentially.
- If only raw CSV/Excel files exist (no
*_deidentified.* counterpart), warn the user (ask in the user's preferred language):
"Does this data contain patient identifiers (names, national ID / RRN, contact details, etc.)?
If so, please de-identify it first with the /deidentify skill."
- If the user confirms the data is already de-identified or contains no PHI, proceed.
- NEVER display raw PHI values (names, phone numbers, RRN) in your output. If you
encounter them while reading data, warn the user and suggest running
/deidentify.
Reference Files
- Templates:
${CLAUDE_SKILL_DIR}/references/templates/ -- reusable analysis scripts
- Analysis guides:
${CLAUDE_SKILL_DIR}/references/analysis_guides/ -- on-demand methodology references
- Table standards:
${CLAUDE_SKILL_DIR}/references/table-standards/ -- journal-specific table formatting
table-standards.md -- universal rules, AMA rules, footnote system, mistakes checklist
journal-profiles/ -- YAML profiles per journal (radiology, jama, nejm, lancet, eur_rad, ajr)
table-types/ -- templates per table type (Table 1, diagnostic accuracy, regression, survival/Cox, agreement/reliability, meta-analysis, model comparison, incremental value, reader study (MRMC))
tool-comparison.md -- R/Python tool comparison and recommended pipelines
- Figure style:
${CLAUDE_SKILL_DIR}/references/style/figure_style.mplstyle
- Project data: See CLAUDE.md for data locations under
2_Data/
Read relevant templates before generating analysis code. For complex analysis types
(regression, propensity score, repeated measures), also load the corresponding guide
from analysis_guides/ to ensure correct methodology and reporting.
Workflow
Phase 1: Data Assessment
- Read the data file (CSV, Excel, TSV, or other tabular format).
- Report to the user:
- Shape (rows x columns)
- Column names and inferred types (continuous, categorical, ordinal, binary, datetime)
- Missing values per column (count and percentage)
- First 5 rows preview
- Unique value counts for categorical columns
- Identify the analysis unit: patient, exam, lesion, image, rater, study, etc.
Phase 2: Analysis Plan
Precondition (observational studies). Before proposing an analysis plan for an observational design (cohort, case-control, cross-sectional, registry, or survey), confirm that a literature-grounded variable operationalization exists — a variable_operationalization.md from /define-variables, or an equivalent codebook-backed definition table. If none exists, warn the user and recommend running /define-variables first, so exposure / outcome / covariate definitions and cutoffs are citation-backed rather than invented ad hoc from the data dictionary (ad-hoc phenotype/cutoff definitions are a common reviewer-rejection trigger for observational work — see the dictionary-first discipline). This is a WARN, not a hard block: proceed on explicit user confirmation, recording that the operationalization artifact was not available. For stricter projects, treat the missing artifact as a hard stop until /define-variables has run. (This mirrors the same precondition already enforced in /write-protocol before drafting Methods.)
Based on the data structure and research question, propose an analysis plan:
Auto-detect analysis type from the table below, or accept user specification.
List specific tests to be performed.
Identify primary and secondary endpoints.
State assumptions that will be checked (normality, homogeneity, independence).
Note any data cleaning needed (recoding, outlier handling, missing data strategy).
Anchor the estimand to the research question. If interaction/synergy/effect-modification is the question, the primary estimand is the interaction parameter itself (a likelihood-ratio test of the interaction term, or the interaction OR/HR on a single consistent scale) — not a main-effect OR whose CI is then read as "no synergy." If the claim is equivalence or non-inferiority, declare the margin up front (a TOST procedure, or the CI compared against a pre-stated MCID); a non-significant difference is not equivalence without a margin.
Screen every categorical/binary predictor for separation — before fitting anything.
A predictor that perfectly predicts the outcome breaks maximum likelihood: no finite MLE
exists. The failure is silent — glm does not error, it returns an odds ratio near 0 (or
enormous), p ≈ 0.99, and an AUC that then gets written into a table. This is routine in
diagnostic imaging, because the good signs are the pathognomonic ones (T2-FLAIR mismatch,
the string sign, a halo sign): 100% specificity means an empty cell by construction.
python3 "${CLAUDE_SKILL_DIR}/scripts/check_separation.py" \
--data cohort.csv --outcome idh_mutant --auto --strict
COMPLETE_SEPARATION (an empty cell) and QUASI_SEPARATION (a cell below the sparsity
floor) both halt the plan. The remedy is a design decision, not a numerical one:
Firth's penalised likelihood keeps one model, while a two-stage rule — classify the
sign-positive cases directly, model only the sign-negative remainder — is usually the
clinically meaningful choice for a pathognomonic sign, because a sign-positive patient is
already diagnosed and the real question is what to do with everyone else. Decide this in
the plan; do not discover it in the output.
Present the plan and wait for user approval before executing.
| Type |
When to use |
Python packages |
R packages |
Primary output |
| Table 1 (Demographics) |
Baseline characteristics |
pandas, scipy |
tableone |
Demographics table |
| Diagnostic Accuracy |
Sensitivity/specificity/AUC |
sklearn, scipy |
pROC |
ROC curve, performance table |
| Inter-rater Agreement |
Multiple raters rating same items |
krippendorff, pingouin |
irr, psych |
ICC/Kappa table |
| Meta-analysis |
Pooling effect sizes across studies |
-- |
meta, metafor |
Forest + funnel plots |
| DTA Meta-analysis |
Pooling diagnostic accuracy across studies |
-- |
meta, metafor, mada |
SROC + paired forest plots |
| Survey/Likert |
Ordinal rating scales |
pingouin, scipy |
psych |
Descriptive + reliability |
| Survival |
Time-to-event outcomes |
lifelines |
survival |
KM curves, Cox table |
| Group Comparison |
Comparing 2+ groups |
scipy, pingouin |
-- |
Test results + effect sizes |
| Correlation |
Association between variables |
scipy, pingouin |
-- |
Scatter + correlation matrix |
| Logistic Regression |
Binary outcome + predictors |
statsmodels, sklearn |
-- |
OR table, C-statistic, forest plot |
| Linear Regression |
Continuous outcome + predictors |
statsmodels |
-- |
Coefficient table, R², diagnostic plots |
| Propensity Score |
Observational treatment comparison |
sklearn, statsmodels |
MatchIt, WeightIt, cobalt |
Balance table, Love plot, weighted analysis |
| Survey-Weighted |
Complex survey data (KNHANES, NHANES, KCHS) |
statsmodels |
survey, tableone, gWQS |
Weighted Table 1, wOR table, subgroup results |
| Repeated Measures |
Longitudinal / multi-timepoint data |
pingouin, statsmodels |
lme4, nlme, geepack |
Spaghetti plot, LMM/GEE/RM ANOVA results |
For Logistic Regression, Linear Regression, Propensity Score, Survey-Weighted, and Repeated Measures:
load the corresponding guide from ${CLAUDE_SKILL_DIR}/references/analysis_guides/ before generating code.
For Survey-Weighted analysis, also load survey_weighted.md. For NHIS claims-based studies, load nhis_icd10_mapping.md.
For test selection guidance, load ${CLAUDE_SKILL_DIR}/references/analysis_guides/test_selection.md.
Phase 3: Execute
Generate and run a Python (preferred) or R script following these rules:
Script Structure
Every script MUST start with a reproducibility header:
"""
Analysis: {description}
Date: {YYYY-MM-DD}
Random seed: 42
Python: {version}
Key packages: {package==version, ...}
"""
import numpy as np
import pandas as pd
np.random.seed(42)
Execution Rules
- Random seed: Always
np.random.seed(42) or set.seed(42).
- Figure style: Always load the matplotlib style file:
import matplotlib.pyplot as plt
style_path = os.path.join(os.environ.get('CLAUDE_SKILL_DIR', '.'), 'references/style/figure_style.mplstyle')
if os.path.exists(style_path):
plt.style.use(style_path)
- Output files: Save all outputs to the same directory as the input data, or to a
user-specified output directory.
- Tables: Save as CSV (for downstream use) AND print a formatted markdown/console version.
- Figures: Save as both PDF (vector) and PNG (300 DPI).
- Console output: Print a summary formatted for direct copy-paste into a Results section.
Assumption Checking
Before running parametric tests, always check and report:
- Normality: Shapiro-Wilk test (n < 50) or Kolmogorov-Smirnov (n >= 50), plus visual QQ plot
- Homogeneity of variance: Levene's test
- If assumptions violated: Use non-parametric alternatives and report why
Multiple Comparisons
- If running 3+ tests on the same dataset, apply Bonferroni or Benjamini-Hochberg correction.
- Always report both uncorrected and corrected p-values.
- State the correction method used.
Stratified & Ordinal-Trend Reporting
- Strata disjointness gate (before any ordinal trend test). Before running a Cochran-Armitage trend test (or any analysis that treats tiers as an ordered partition), assert the strata are mutually exclusive and exhaustive:
sum(n per stratum) == unique N and sum(events per stratum) == total events. A trend test on overlapping or non-exhaustive strata is invalid. Emit the per-stratum N/event table and the reconciliation in the output (this is the analysis-side mirror of /self-review check_cohort_arithmetic.py PARTITION_OVERLAP).
- Secondary stratum-HR validation checklist. Every secondary stratum hazard/odds ratio must be reported with (a) its reference contrast (which category is the referent), (b) the event count in each stratum, and (c) a sparse-stratum caveat when any stratum has a low event count (a rule of thumb: < 10 events makes the estimate unstable). A bare "HR 1.55 in lean participants" without the referent and the events is uninterpretable.
- Proportion CI lower-bound clamp. Clamp every proportion confidence-interval lower bound to
max(0, lower); a zero-event Wilson/score interval can emit a negative or absurd tiny-exponent lower bound (e.g., 3.47e-16) that is a display artifact, not a real bound. Report 0 (or 0.0%) instead, and prefer an exact (Clopper-Pearson) interval for zero/near-zero cells.
Output Manifest
After all analyses complete, save _analysis_outputs.md in the output directory.
Use the output format and bound binary workflow
when producing the analysis outputs.
This manifest enables downstream skills (/make-figures, /write-paper) to auto-discover analysis outputs without user intervention.
For prespecified binary predictions on independent units, use the bundled
scripts/run_analysis.py run workflow described in
references/analysis_run_workflow.md.
It executes the existing diagnostic template and embeds data/configuration/code/
output hashes, exact counts, metric-specific denominators and the reproduction
command in this same manifest. audit checks recorded versions without rewriting
them; compare separates declared context and recorded numeric equality from byte
drift. It does not select thresholds or establish study validity, privacy clearance
or reuse rights. The original synthetic example runs with
python3 ${CLAUDE_SKILL_DIR}/scripts/demo_analysis_run.py --out demo-project.
Phase 3.5: Generated-Code Quality Gate
Before reporting any script as final, lint every emitted .py/.R file for the
reproducibility-hygiene "slop" that AI-generated analysis code recurrently carries:
python3 ${CLAUDE_SKILL_DIR}/scripts/check_generated_code.py {script.py} --strict
# or scan a whole output directory:
python3 ${CLAUDE_SKILL_DIR}/scripts/check_generated_code.py --code-dir {analysis_dir} --strict
Major findings (fix before reporting the script):
MISSING_SEED — randomness used (sampling, bootstrap, train/test split, rng) with no
np.random.seed / set.seed / random_state= / default_rng. Non-reproducible.
HARDCODED_DATA_LITERAL — a hand-typed, table-shaped numeric literal instead of
read_csv()/read.csv() + subset. This is the data-integrity rule "never hand-type CSV
data into scripts."
HARDCODED_ABS_PATH — an absolute path literal (/Users/, /home/, C:\, ~/Documents).
Non-portable and a PII risk.
INPLACE_SOURCE_OVERWRITE — writing to the same path read as input; this overwrites raw
data. Write derived outputs to a new path ("never modify raw data").
Flags (fix when tidying): DEBUG_LEFTOVER (a breakpoint() / browser() / debug print
/ TODO marker left in) and UNUSED_IMPORT (a dead Python dependency).
The gate is conservative on the Major checks — it fires HARDCODED_DATA_LITERAL only on
genuinely table-shaped literals and MISSING_SEED only on a real randomness call — so it
stays quiet on legitimate analysis code. It is the analysis-side mirror of the
data-integrity and reproducibility checks /self-review is built to catch downstream.
Phase 4: Report
After execution, generate manuscript-ready text:
- Results paragraph: 3-8 sentences with specific numbers, formatted as:
- Continuous: "mean +/- SD" or "median (IQR)"
- Proportions: "n/N (XX.X%)"
- Test results: "statistic = X.XX, p = 0.XXX"
- Effect sizes: "Cohen's d = X.XX (95% CI: X.XX-X.XX)"
- AUC: "AUC = 0.XXX (95% CI: 0.XXX-0.XXX)"
- Table/figure captions: Draft captions referencing table/figure numbers.
- Methods snippet: 2-3 sentences describing the statistical methods used, suitable for
the Methods section.
Statistical Reporting Rules (Always Enforced)
These rules apply to ALL analyses without exception:
- Exact p-values: Report exact values (e.g., p = 0.034), not inequalities.
Exception: report as p < 0.001 when the value is below 0.001.
- Confidence intervals: Always report 95% CIs for primary endpoints.
- Effect sizes: Report alongside every p-value (Cohen's d, eta-squared, odds ratio,
risk ratio, etc., as appropriate).
- Parametric vs non-parametric: Choose based on assumption checks, not convenience.
Report the assumption test results.
- Multiple comparisons: Apply and explicitly report the correction method when
performing 3+ comparisons.
- Sample size reporting: Always state n for each group/analysis.
- Missing data: Report how many cases were excluded and why.
- Decimal places: p-values to 3 decimals, proportions to 1 decimal, means/SDs to
appropriate precision for the measurement.
- Design/power statistics are code outputs, never hand-computed. Any minimum detectable
effect (MDE), a-priori or post-hoc power, or required sample size that will appear in the
manuscript MUST be emitted by this committed script — printed with its method and inputs
(n per arm, alpha, power, allocation ratio, one/two-sided) — not computed in a side tool
(G*Power, an online calculator) and pasted in. Use one method family consistently
(e.g. the exact noncentral-t via
statsmodels TTestIndPower or scipy's nct); do not
mix a normal approximation for some values with exact-t for others. A value that exists only
in the manuscript with no script that reproduces it is the failure mode /self-review
Phase 2.5a-2 is built to catch.
- Estimand & CI output contract. Every primary point estimate — including quantile
estimands (T25, median time-to-event), pooled proportions, and subdistribution HRs, not
just ORs/HRs/AUCs — MUST be emitted together with its 95% CI. In the output CSV, carry the
interval as explicit columns (
estimate, ci_lower, ci_upper) or as a single text column in
est (lo–hi) form; never emit a point estimate with no interval in an adjacent column.
Round ORs/HRs/sHRs to 2 decimals and AUC/C-statistic to 3. This is the output side of the
/self-review §C assertion that "all primary metrics have 95% CIs."
Effect-Size Real-World Translation
Whenever a primary result is a correlation, a standardized coefficient, a regression slope, an
OR/HR/RR, or a Cohen's d, also report it as a plain-language unit shift a non-statistician can
act on. The coefficient answers "is there an association"; the translation answers "how much, in
units I use". This complements rule 3 above (report effect sizes) — it does not replace it.
When to apply
- Any continuous-exposure to continuous-outcome association reported as Spearman's rho, Pearson's r,
or a standardized slope.
- Any OR/HR/RR where the audience needs an absolute-risk feel.
- Reader / expert-elicitation studies, clinical-utility framing, abstracts, and figure captions.
Procedure
- Pick an anchored contrast on the exposure, not a 1-unit step. Default: 25th to 75th percentile
(IQR). State both endpoints in native units.
- Translate to the outcome scale.
- For a rank/standardized association (Spearman's rho or a per-SD slope) under an approximately
monotonic-linear assumption:
delta_outcome ~= ((x_p75 - x_p25) / SD_x) * |rho| * SD_outcome.
Report as: "going from {x_p25} to {x_p75} {units} is associated with about {delta_outcome}
{outcome units} on average."
- For a regression slope b:
delta_outcome = b * (x_p75 - x_p25) (cleaner; no monotonicity caveat).
- State the assumption explicitly; the IQR translation is a more defensible verbal guide than an
SD-scaled one.
- For OR/HR/RR, accompany the relative measure with an absolute one at a stated baseline risk:
the absolute risk difference, and NNT = 1 / ARR (or NNH = 1 / ARI). Always state the baseline risk used.
- Bound the claim: report the contrast, the assumption, and a CI on the coefficient; do not imply
causation from a crude or unadjusted estimate.
Worked example (synthetic)
rho = 0.39 between a fasting marker (IQR 0.6 to 3.5 units, SD 3.05) and an index (SD 2.13):
((3.5 - 0.6) / 3.05) * 0.39 * 2.13 ~= 0.8 -> "Going from the 25th to the 75th percentile of the
marker is associated with about 0.8 index units higher on average (monotonic-linear approximation;
crude, unadjusted)."
Output contract (clinical-utility is a default, not an optional add-on). Report every
primary effect in units a clinician acts on, by default — do not leave these as prose to be
added later:
- OR/HR/RR primary outcomes → report the relative measure and the absolute risk at
a stated baseline + absolute risk difference + NNT (or NNH = 1/ARI), baseline risk
explicit. A relative-only headline is incomplete.
- Continuous outcomes → add the IQR/clinically-anchored "Real-world translation" line
beneath the effect size.
- Prediction / classification (incl. medical-AI) models → a decision-curve /
net-benefit pass at the relevant threshold is standard output, not just AUC +
calibration. An incremental claim reports added net benefit / NRI / IDI over the
established clinical model, not the new model's AUC alone. See
references/table-standards/table-types/incremental_value.md and the make-figures
decision_curve exemplar (and render_core_figures.py for the rendered curve).
Error Handling
- If a script fails to execute, report the error in one line, diagnose the likely cause
(missing package, data format mismatch, wrong column name), and present a fix.
- Do NOT retry the same script more than once without modifying it or asking the user.
- If an R package is unavailable, suggest
install.packages() and wait for user confirmation.
- For prediction models: always include calibration assessment (Brier score, calibration plot,
or calibration slope/intercept) alongside discrimination metrics. AUC alone is insufficient.
Output Conventions
Tables
Before generating any publication table, load the journal profile and table type template:
- Load
${CLAUDE_SKILL_DIR}/references/table-standards/journal-profiles/{journal}.yaml if a target journal is known
- Load
${CLAUDE_SKILL_DIR}/references/table-standards/table-types/{type}.md for the relevant table type
- If no journal specified, default to AMA style (Radiology profile)
Output formats (always generate all three):
- CSV file (for downstream use and archival)
- Console markdown rendering (for user review)
- R gtsummary code (for publication-quality Word/LaTeX export)
Universal rules (enforced regardless of journal):
- No vertical lines — horizontal rules only (top, below header, bottom)
- Binary variables: show only one level (e.g., Male only, not Male + Female)
- Units in column headers, not repeated in cells
- Consistent decimal places within each column
- All abbreviations defined in footnotes, self-contained per table
- Exact P values always (never "NS" or "significant")
- Name the statistical test in footnote or general note
- Variability measure always stated: mean (SD) or median (IQR)
Journal-specific parameters (from loaded YAML profile):
- Footnote markers: letters (AMA) vs symbols (NEJM/Lancet)
- P value format: case, leading zero, italic
- CI separator: comma (Radiology) vs "to" (JAMA/NEJM/Lancet)
- Title format: period (AMA) vs colon (Lancet)
- Abbreviation order: appearance (Radiology) vs alphabetical (JAMA)
Footnote placement order (universal):
- General note (no marker) — e.g., "Data are mean (SD) unless noted"
- Abbreviations — in order per journal convention
- Specific notes (superscript markers) — per-cell explanations
- Probability notes — significance thresholds (if applicable)
gtsummary pipeline (recommended for R table generation):
theme_gtsummary_journal("{journal}") # "jama", "lancet", "nejm"
theme_gtsummary_compact()
# ... build table ...
tbl %>% as_flex_table() %>% flextable::save_as_docx(path = "table.docx")
Validation checklist (run before finalizing any table):
Figures
- Format: PDF (vector, for journal) + PNG (300 DPI, for review)
- Style: Use
figure_style.mplstyle for consistent appearance
- Font: Arial, 8-10pt
- Colors: Colorblind-safe palette
- Size: 3.5 inches (single column) or 7.0 inches (double column) width
- Always include axis labels with units
Console Output
- Formatted for direct copy-paste into the Results section of a manuscript
- Include all numbers that would appear in the text
- Use the reporting format conventions above
Analysis-Specific Guidelines
Table 1 (Demographics)
- Template:
references/templates/table1_demographics.py
- Table type guide:
references/table-standards/table-types/table1_demographics.md
- Continuous variables: mean +/- SD if normal, median (IQR) if skewed
- Categorical variables: n (%)
- Binary variables: show only one level (e.g., Male n (%), not both Male and Female)
- Compare groups: t-test/Mann-Whitney for continuous, chi-square/Fisher for categorical
- Report standardized mean differences (SMD) if requested (preferred over P for PS-matched studies)
- RCTs: P values in Table 1 are usually unnecessary per CONSORT
- gtsummary
tbl_summary() with journal theme for R pipeline
Diagnostic Accuracy
- Methodology guide:
references/analysis_guides/diagnostic_accuracy.md (load before generating code — every metric with a CI on a stated analysis unit; the confidence-weighted trap [unweighted-baseline AUC + monotonic-encoding check, produce-side of probe D9]; paired DeLong vs MRMC for reader-generalising claims; per-stratum admissibility [D10]; one-scale-per-comparison [D11])
- Template:
references/templates/diagnostic_accuracy.py
- Always report: sensitivity, specificity, PPV, NPV, accuracy, AUC
- CIs: Wilson score for proportions, DeLong for AUC
- ROC curve: include diagonal reference line, AUC in legend
- If comparing models: DeLong test for AUC comparison
- Youden's index for optimal threshold when applicable
- Include calibration assessment (Brier score, calibration plot) for prediction models
- NRI/IDI: When comparing two models (e.g., base model vs model + AI score), report:
- Category-based NRI (with clinically defined risk categories)
- Continuous NRI (note: tends to be inflated — report alongside category-based)
- IDI (Integrated Discrimination Improvement)
- Bootstrap 95% CIs (1000+ iterations)
- These supplement, not replace, DeLong AUC comparison
- Table type guide (added value beyond a baseline):
references/table-standards/table-types/incremental_value.md (paired ΔAUC + DeLong CI, continuous NRI with event/non-event split, IDI, net benefit at a prespecified threshold, same-patient/calibrated-first discipline). Pairs the decision-curve exemplar make-figures references/exemplar_plots/decision_curve.md.
- Reader study (MRMC):
references/table-standards/table-types/reader_study.md (per-reader + reader-averaged AUC with an Obuchowski–Rockette/DBM reader+case CI, per-patient vs per-lesion unit, superiority vs non-inferiority margin). Use an MRMC method (not a fixed-reader DeLong CI) for a claim that generalises to readers. Pairs make-figures references/exemplar_plots/mrmc_roc.md.
Inter-rater Agreement
- Methodology guide:
references/analysis_guides/agreement_reliability.md (load before generating code — the pseudoreplication trap for clustered/repeated measurements + the pseudoreplication-safe per-subject / mixed-effects code, ICC model/type selection, agreement-vs-reliability distinction; pairs with self-review probe O18)
- Table type guide:
references/table-standards/table-types/agreement.md (ICC with model/type + CI, weighted κ for ordinal, Bland–Altman bias + LoA, reliability-vs-agreement distinction, common errors)
- Template:
references/templates/agreement_analysis.py
- 2 raters + categorical: Cohen's kappa
- 2+ raters + categorical: Fleiss' kappa (or Krippendorff's alpha)
- Continuous: ICC (specify model: one-way, two-way random/mixed; type: single/average)
- Always report interpretation labels (Landis & Koch or Cicchetti)
- Bland-Altman plot for continuous paired measurements
- Bootstrap CIs (1000 iterations, seed=42)
Meta-analysis
- Prefer R (meta/metafor packages) for meta-analysis
- Comparative:
metabin() for binary outcomes (OR/RR), metagen() for continuous
- Use
method = "Inverse", method.tau = "DL", method.random.ci = "HK"
- Avoid deprecated args:
comb.fixed → common, hakn → method.random.ci
- Single-arm pooled proportion:
metaprop() with sm = "PLOGIT", method.ci = "CP"
- Small-study test branch: do not use Egger's regression for a single-arm proportion meta-analysis — funnel-asymmetry tests assume an effect-size-vs-SE relationship that does not hold for raw proportions. If a small-study assessment is needed, use Peters' test or an arcsine-based variant, and only when
k >= 10 (note underpowered otherwise)
- Standard output: report
tau-squared on the logit scale and a 95% prediction interval (metaprop(..., prediction = TRUE)) in addition to the pooled estimate; the PI conveys where a future study's proportion is expected to fall under the random-effects model
- Nested observation units: if the proportion's unit is nested within study (e.g., per-lesion within study, per-image within patient), do not report a naive Wilson/binomial CI that ignores clustering — use a cluster-bootstrap or a GLMM with a random intercept per study so the CI reflects the design
- Heterogeneity: I-squared, Q test, tau-squared, and a 95% prediction interval for the random-effects pooled estimate
- Forest plot: individual studies + pooled estimate
- Funnel plot + Egger's test for publication bias (comparative effect sizes only; note: underpowered k<10)
- Sensitivity analysis: leave-one-out (
metainf())
- Subgroup:
update(res, subgroup = variable)
DTA Meta-Analysis
- Template:
references/templates/dta_meta_analysis.R
- Prefer R (
mada, meta, metafor packages) for DTA meta-analysis
- Bivariate model (Reitsma):
mada::reitsma() — recommended over separate pooling of Se/Sp
- Accounts for correlation between sensitivity and specificity
- Produces SROC curve with confidence + prediction regions
- Key outputs: Pooled Se/Sp (95% CI), positive/negative LR, DOR, SROC AUC
- Threshold effect: Spearman correlation between logit(Se) and logit(FPR)
- If significant: interpret single pooled Se/Sp with caution, emphasize SROC curve
- Forest plots: Paired (sensitivity + specificity side by side)
- Publication bias: Deeks' funnel plot asymmetry test (NOT standard funnel plot)
- Standard funnel plots are inappropriate for DTA studies
- Note: underpowered for k < 10
- Dual approach (comparative + single-arm):
- Primary:
metabin() for comparative studies (OR/RR)
- Secondary:
metaprop() with sm = "PLOGIT" for single-arm pooled proportion
- Use
method = "Inverse", method.tau = "DL", method.random.ci = "HK"
- Small studies (k < 10): bivariate model may not converge; consider narrative synthesis
- Alternative: If
mada unavailable, use metafor::rma.mv() with bivariate structure
Network Meta-Analysis
- Guide: Load
analysis_guides/network_meta_analysis.md before generating code
- For ≥3 interventions via combined direct + indirect evidence (incl. component NMA); pairwise machinery (search/screening/random-effects model) via the Meta-analysis section above
- Assess transitivity before pooling: compare effect-modifier distributions across comparisons (box plots / table) and/or network meta-regression — it is a clinical judgment, not a test
- Test consistency globally (design-by-treatment) AND locally (node-split / back-calculation); a star network (no closed loops) cannot be checked — state it; investigate the source of any inconsistency (often one trial)
- R
netmeta (frequentist: netsplit, decomp.design, netheat, netrank P-scores, comparison-adjusted funnel) or Bayesian gemtc / multinma / BUGSnet (node-split, SUCRA, DIC)
- Present a network plot (node ∝ sample size, edge ∝ #trials); report global τ²; ranking (SUCRA/P-score) is not a superiority test — report it with the league table, intervals, and certainty
- Certainty per estimate via CINeMA / GRADE-NMA (downgrade indirect-only); component NMA assumes additivity (state/check it). Report against PRISMA-NMA; risk of bias via RoB-NMA. Review-side probes: NM1–NM8 in
network_meta_analysis.md
Health Economic Evaluation
- Guide: Load
analysis_guides/health_economic_evaluation.md before generating code
- For cost-effectiveness (CEA), cost-utility (CUA, QALY), cost-benefit (CBA), cost-minimisation, or budget-impact analyses; trial-based or decision-model-based (decision tree, Markov/state-transition, discrete-event simulation)
- Compute incremental cost ΔC, incremental effect ΔE, and the ICER = ΔC/ΔE; with ≥3 options remove dominated / extended-dominated strategies before sequential ICERs; prefer net benefit (INMB = λΔE − ΔC) for regression/probabilistic summaries
- State and justify the perspective, time horizon (lifetime for chronic disease), discount rate (both costs and outcomes), currency + price year; QALYs from a named preference-based instrument + value set
- Uncertainty is the analytic core: one-way / tornado for drivers, probabilistic sensitivity analysis (PSA) with justified parameter distributions (beta for probabilities/utilities, gamma/log-normal for costs) → cost-effectiveness plane + CEAC; scenario analyses for structural choices
- R
heemod / dampack / hesim / BCEA (state-transition + PSA + CEAC + EVPI), flexsurv for survival extrapolation. Report against CHEERS 2022; make the "cost-effective" conclusion conditional on a stated willingness-to-pay threshold. Review-side probes: HE1–HE8 in health_economic_evaluation.md
Survey/Likert
- Descriptive: median, IQR, frequency distribution per item
- Internal consistency: Cronbach's alpha with item-total correlations
- Reverse-coding guard (run before reliability): a negatively-worded scale item must be recoded
(min+max) - x before computing the scale total or Cronbach's alpha. An un-recoded reverse item produces a negative item-rest correlation and a negative alpha — which is a coding bug, not evidence of a multidimensional construct (do not defend it as such; you lose a review round). likert_summary.py prints the per-item item-rest correlations, flags negative ones as reverse-code suspects, warns loudly on a negative alpha, and accepts --reverse-items E3 ... to apply the recode before scoring. To screen at cleaning time, run /clean-data scripts/check_reverse_coding.py. See the global rule survey-scale-reliability.md.
- If comparing groups: Mann-Whitney or Kruskal-Wallis (ordinal data)
- Visualization: diverging stacked bar chart
Survival Analysis
- Methodology guide:
references/analysis_guides/survival.md (load before generating code — competing risks first [naive 1−KM overestimates → produce the Aalen–Johansen/Fine–Gray CIF; cause-specific vs subdistribution for which question, produce-side of probe S3]; PH check → RMST when violated; reverse-KM follow-up + C-index variant [S6]; estimand provenance [S8])
- Table type guide:
references/table-standards/table-types/survival_results.md (Cox results table: events/person-time, reverse-KM median follow-up, univariable + adjusted HR with CI, PH-assumption footnote, EPV/sparse-stratum and RMST-when-PH-violated rules)
- Kaplan-Meier curves with number-at-risk table
- Log-rank test for group comparison
- Cox proportional hazards: report HR (95% CI)
- Events-per-variable (EPV) gate: check
events / n_covariates >= 10 before fitting Cox (mirror of the logistic EPV rule). Warn if violated and fall back to a Firth/penalized Cox or profile-likelihood CIs; do not report Wald CIs from a sparse-event model as if stable
- Nested observation units (cluster-robust CI): when a subject contributes more than one analysed unit (multiple lesions, both eyes, repeated episodes), pass a subject id so the HR CIs use a robust cluster-sandwich variance (
coxph(..., cluster = id) / robust = TRUE in R, cluster_col= in lifelines, e.g. survival_analysis.py --cluster <id>). Treating correlated rows as independent understates the standard errors and narrows the CI artificially
- Check proportional hazards assumption (Schoenfeld residuals)
- PH violation → do not report a single time-averaged HR. If the Schoenfeld global test is significant (or a covariate's residual trends with time), a single Cox HR averages a changing effect and is misleading. Report a piecewise / time-stratified HR (split follow-up at a clinically sensible cut, or
tt() time-transform), or switch to RMST difference at a fixed horizon, and state the violation explicitly
- Horizon vs follow-up. Do not read a KM/CIF estimate at a horizon beyond the data: if a reported time point (e.g., a 15-year cumulative incidence) exceeds the reverse-KM median follow-up, either restrict the horizon to where the risk set is non-trivial or report the number-at-risk at that horizon so the reader can judge the extrapolation
- Report median survival with 95% CI
- Warranty period / quantile estimands (T25 etc.): Time to a fixed cumulative incidence. Use
quantile() from the KM/survfit object and always emit the 95% CI (the lower/upper from quantile(km, conf.int=TRUE), or a log-transformed / bootstrap CI) alongside the events/n that define it. A quantile point estimate reported without its CI is incomplete. If the event rate is below the target quantile, report "not reached" and consider Weibull parametric extrapolation (also with an interval)
Interval-Censored Survival
When exact event times are unknown (e.g., health screening cohorts where status changes are detected at periodic visits), standard KM underestimates time-to-event. Use interval-censored methods:
- R packages:
icenReg (parametric/semi-parametric IC regression), interval (NPMLE/Turnbull), survival (Surv type "interval2")
- Turnbull estimator: Non-parametric MLE for interval-censored data — analogous to KM but accounts for the interval between last negative and first positive observation
- Parametric IC models: Weibull or log-logistic via
icenReg::ic_par(). Report shape/scale parameters and compare AIC across distributions
- Mid-point imputation: Simple approximation — event time = midpoint of (last negative, first positive). Acceptable as sensitivity analysis but NOT as primary method
- When to use: Serial measurement cohorts (e.g., health screening databases), cancer screening intervals, repeated biomarker assessments
- Auto-trigger: if the event date is defined by a periodic visit / scheduled re-examination (the event is detected at a visit, not observed exactly), interval-censoring is not optional — make an IC model the primary analysis, or at minimum a mandatory pre-specified sensitivity analysis, and do not present a right-censored Cox
coxph() on visit-dated events as if the times were exact
- Multistate / transition models: for repeated transitions (e.g.,
msm), account for subject-level clustering with a subject random effect or a sandwich (robust) variance, and check the time-homogeneity assumption (constant transition intensities) before trusting a single rate
- Reporting: State the interval-censored nature of the data explicitly in Methods. Report both standard KM (for comparability with prior literature) and IC estimates (as primary or sensitivity)
Competing Risks
When death or other events preclude the outcome of interest, standard KM overestimates cumulative incidence (treats competing events as censored). Use competing risk methods:
- R packages:
cmprsk (Fine-Gray), tidycmprsk (tidy interface), survival (cause-specific Cox)
- Cumulative incidence function (CIF):
cmprsk::cuminc() — replaces 1-KM for each event type. Gray's test for group comparison
- Fine-Gray subdistribution hazard:
cmprsk::crr() or tidycmprsk::crr() — reports subdistribution HR (sHR) with 95% CI. Interpretable as effect on CIF directly. Check the subdistribution-PH assumption the same way you check it for Cox (a time-interaction term on the subdistribution scale, or inspection of scaled-residual analogues); a constant sHR is an assumpti
…(truncated)
1---2name: analyze-stats3description: Statistical analysis for medical research papers. Generates reproducible Python/R code with publication-ready tables and figures. Supports diagnostic accuracy, inter-rater agreement, meta-analysis, survival analysis, survey data, group comparisons, regression, propensity score, and repeated measures.4---56# Statistical Analysis Skill78You are assisting a medical researcher with statistical analyses for medical research papers.9Generate reproducible code (Python preferred, R when necessary) that produces publication-ready10tables and figures following journal standards for medical imaging research.1112## Data Privacy Check1314Before reading any data file, check whether it might contain Protected Health Information (PHI):15161. If `*_deidentified.*` files exist in the working directory, use those preferentially.172. If only raw CSV/Excel files exist (no `*_deidentified.*` counterpart), warn the user (ask in the user's preferred language):18 > "Does this data contain patient identifiers (names, national ID / RRN, contact details, etc.)?19 > If so, please de-identify it first with the `/deidentify` skill."203. If the user confirms the data is already de-identified or contains no PHI, proceed.214. **NEVER** display raw PHI values (names, phone numbers, RRN) in your output. If you22 encounter them while reading data, warn the user and suggest running `/deidentify`.2324## Reference Files2526- **Templates**: `${CLAUDE_SKILL_DIR}/references/templates/` -- reusable analysis scripts27- **Analysis guides**: `${CLAUDE_SKILL_DIR}/references/analysis_guides/` -- on-demand methodology references28- **Table standards**: `${CLAUDE_SKILL_DIR}/references/table-standards/` -- journal-specific table formatting29 - `table-standards.md` -- universal rules, AMA rules, footnote system, mistakes checklist30 - `journal-profiles/` -- YAML profiles per journal (radiology, jama, nejm, lancet, eur_rad, ajr)31 - `table-types/` -- templates per table type (Table 1, diagnostic accuracy, regression, survival/Cox, agreement/reliability, meta-analysis, model comparison, incremental value, reader study (MRMC))32 - `tool-comparison.md` -- R/Python tool comparison and recommended pipelines33- **Figure style**: `${CLAUDE_SKILL_DIR}/references/style/figure_style.mplstyle`34- **Project data**: See CLAUDE.md for data locations under `2_Data/`3536Read relevant templates before generating analysis code. For complex analysis types37(regression, propensity score, repeated measures), also load the corresponding guide38from `analysis_guides/` to ensure correct methodology and reporting.3940## Workflow4142### Phase 1: Data Assessment43441. **Read the data file** (CSV, Excel, TSV, or other tabular format).452. **Report to the user**:46 - Shape (rows x columns)47 - Column names and inferred types (continuous, categorical, ordinal, binary, datetime)48 - Missing values per column (count and percentage)49 - First 5 rows preview50 - Unique value counts for categorical columns513. **Identify the analysis unit**: patient, exam, lesion, image, rater, study, etc.5253### Phase 2: Analysis Plan5455**Precondition (observational studies).** Before proposing an analysis plan for an observational design (cohort, case-control, cross-sectional, registry, or survey), confirm that a literature-grounded variable operationalization exists — a `variable_operationalization.md` from `/define-variables`, or an equivalent codebook-backed definition table. If none exists, **warn** the user and recommend running `/define-variables` first, so exposure / outcome / covariate definitions and cutoffs are citation-backed rather than invented ad hoc from the data dictionary (ad-hoc phenotype/cutoff definitions are a common reviewer-rejection trigger for observational work — see the dictionary-first discipline). This is a WARN, not a hard block: proceed on explicit user confirmation, recording that the operationalization artifact was not available. For stricter projects, treat the missing artifact as a hard stop until `/define-variables` has run. (This mirrors the same precondition already enforced in `/write-protocol` before drafting Methods.)5657Based on the data structure and research question, propose an analysis plan:58591. **Auto-detect analysis type** from the table below, or accept user specification.602. **List specific tests** to be performed.613. **Identify primary and secondary endpoints**.624. **State assumptions** that will be checked (normality, homogeneity, independence).635. **Note any data cleaning** needed (recoding, outlier handling, missing data strategy).646. **Anchor the estimand to the research question.** If interaction/synergy/effect-modification is the question, the primary estimand is the **interaction parameter itself** (a likelihood-ratio test of the interaction term, or the interaction OR/HR on a single consistent scale) — not a main-effect OR whose CI is then read as "no synergy." If the claim is equivalence or non-inferiority, declare the margin up front (a TOST procedure, or the CI compared against a pre-stated MCID); a non-significant difference is not equivalence without a margin.65667. **Screen every categorical/binary predictor for separation — before fitting anything.**67 A predictor that perfectly predicts the outcome breaks maximum likelihood: no finite MLE68 exists. The failure is silent — `glm` does not error, it returns an odds ratio near 0 (or69 enormous), *p* ≈ 0.99, and an AUC that then gets written into a table. This is routine in70 diagnostic imaging, because the good signs are the pathognomonic ones (T2-FLAIR mismatch,71 the string sign, a halo sign): 100% specificity means an empty cell by construction.7273 ```bash74 python3 "${CLAUDE_SKILL_DIR}/scripts/check_separation.py" \75 --data cohort.csv --outcome idh_mutant --auto --strict76 ```7778 `COMPLETE_SEPARATION` (an empty cell) and `QUASI_SEPARATION` (a cell below the sparsity79 floor) both halt the plan. The remedy is a **design** decision, not a numerical one:80 Firth's penalised likelihood keeps one model, while a **two-stage rule** — classify the81 sign-positive cases directly, model only the sign-negative remainder — is usually the82 clinically meaningful choice for a pathognomonic sign, because a sign-positive patient is83 already diagnosed and the real question is what to do with everyone else. Decide this in84 the plan; do not discover it in the output.8586Present the plan and **wait for user approval** before executing.8788| Type | When to use | Python packages | R packages | Primary output |89|------|-------------|-----------------|------------|----------------|90| Table 1 (Demographics) | Baseline characteristics | pandas, scipy | tableone | Demographics table |91| Diagnostic Accuracy | Sensitivity/specificity/AUC | sklearn, scipy | pROC | ROC curve, performance table |92| Inter-rater Agreement | Multiple raters rating same items | krippendorff, pingouin | irr, psych | ICC/Kappa table |93| Meta-analysis | Pooling effect sizes across studies | -- | meta, metafor | Forest + funnel plots |94| DTA Meta-analysis | Pooling diagnostic accuracy across studies | -- | meta, metafor, mada | SROC + paired forest plots |95| Survey/Likert | Ordinal rating scales | pingouin, scipy | psych | Descriptive + reliability |96| Survival | Time-to-event outcomes | lifelines | survival | KM curves, Cox table |97| Group Comparison | Comparing 2+ groups | scipy, pingouin | -- | Test results + effect sizes |98| Correlation | Association between variables | scipy, pingouin | -- | Scatter + correlation matrix |99| Logistic Regression | Binary outcome + predictors | statsmodels, sklearn | -- | OR table, C-statistic, forest plot |100| Linear Regression | Continuous outcome + predictors | statsmodels | -- | Coefficient table, R², diagnostic plots |101| Propensity Score | Observational treatment comparison | sklearn, statsmodels | MatchIt, WeightIt, cobalt | Balance table, Love plot, weighted analysis |102| Survey-Weighted | Complex survey data (KNHANES, NHANES, KCHS) | statsmodels | survey, tableone, gWQS | Weighted Table 1, wOR table, subgroup results |103| Repeated Measures | Longitudinal / multi-timepoint data | pingouin, statsmodels | lme4, nlme, geepack | Spaghetti plot, LMM/GEE/RM ANOVA results |104105For **Logistic Regression**, **Linear Regression**, **Propensity Score**, **Survey-Weighted**, and **Repeated Measures**:106load the corresponding guide from `${CLAUDE_SKILL_DIR}/references/analysis_guides/` before generating code.107For **Survey-Weighted** analysis, also load `survey_weighted.md`. For NHIS claims-based studies, load `nhis_icd10_mapping.md`.108For test selection guidance, load `${CLAUDE_SKILL_DIR}/references/analysis_guides/test_selection.md`.109110### Phase 3: Execute111112Generate and run a Python (preferred) or R script following these rules:113114#### Script Structure115116Every script MUST start with a reproducibility header:117118```python119"""120Analysis: {description}121Date: {YYYY-MM-DD}122Random seed: 42123Python: {version}124Key packages: {package==version, ...}125"""126import numpy as np127import pandas as pd128np.random.seed(42)129```130131#### Execution Rules1321331. **Random seed**: Always `np.random.seed(42)` or `set.seed(42)`.1342. **Figure style**: Always load the matplotlib style file:135 ```python136 import matplotlib.pyplot as plt137 style_path = os.path.join(os.environ.get('CLAUDE_SKILL_DIR', '.'), 'references/style/figure_style.mplstyle')138 if os.path.exists(style_path):139 plt.style.use(style_path)140 ```1413. **Output files**: Save all outputs to the same directory as the input data, or to a142 user-specified output directory.1434. **Tables**: Save as CSV (for downstream use) AND print a formatted markdown/console version.1445. **Figures**: Save as both PDF (vector) and PNG (300 DPI).1456. **Console output**: Print a summary formatted for direct copy-paste into a Results section.146147#### Assumption Checking148149Before running parametric tests, always check and report:150151- **Normality**: Shapiro-Wilk test (n < 50) or Kolmogorov-Smirnov (n >= 50), plus visual QQ plot152- **Homogeneity of variance**: Levene's test153- **If assumptions violated**: Use non-parametric alternatives and report why154155#### Multiple Comparisons156157- If running 3+ tests on the same dataset, apply Bonferroni or Benjamini-Hochberg correction.158- Always report both uncorrected and corrected p-values.159- State the correction method used.160161#### Stratified & Ordinal-Trend Reporting162163- **Strata disjointness gate (before any ordinal trend test).** Before running a Cochran-Armitage trend test (or any analysis that treats tiers as an ordered partition), assert the strata are mutually exclusive and exhaustive: `sum(n per stratum) == unique N` and `sum(events per stratum) == total events`. A trend test on overlapping or non-exhaustive strata is invalid. Emit the per-stratum N/event table and the reconciliation in the output (this is the analysis-side mirror of `/self-review` `check_cohort_arithmetic.py` `PARTITION_OVERLAP`).164- **Secondary stratum-HR validation checklist.** Every secondary stratum hazard/odds ratio must be reported with (a) its **reference contrast** (which category is the referent), (b) the **event count** in each stratum, and (c) a **sparse-stratum caveat** when any stratum has a low event count (a rule of thumb: < 10 events makes the estimate unstable). A bare "HR 1.55 in lean participants" without the referent and the events is uninterpretable.165- **Proportion CI lower-bound clamp.** Clamp every proportion confidence-interval lower bound to `max(0, lower)`; a zero-event Wilson/score interval can emit a negative or absurd tiny-exponent lower bound (e.g., `3.47e-16`) that is a display artifact, not a real bound. Report `0` (or `0.0%`) instead, and prefer an exact (Clopper-Pearson) interval for zero/near-zero cells.166167#### Output Manifest168169After all analyses complete, save `_analysis_outputs.md` in the output directory.170Use the [output format and bound binary workflow](references/analysis_run_workflow.md)171when producing the analysis outputs.172173This manifest enables downstream skills (`/make-figures`, `/write-paper`) to auto-discover analysis outputs without user intervention.174175For **prespecified binary predictions on independent units**, use the bundled176`scripts/run_analysis.py run` workflow described in177[`references/analysis_run_workflow.md`](references/analysis_run_workflow.md).178It executes the existing diagnostic template and embeds data/configuration/code/179output hashes, exact counts, metric-specific denominators and the reproduction180command in this same manifest. `audit` checks recorded versions without rewriting181them; `compare` separates declared context and recorded numeric equality from byte182drift. It does not select thresholds or establish study validity, privacy clearance183or reuse rights. The original synthetic example runs with184`python3 ${CLAUDE_SKILL_DIR}/scripts/demo_analysis_run.py --out demo-project`.185186### Phase 3.5: Generated-Code Quality Gate187188Before reporting any script as final, lint every emitted `.py`/`.R` file for the189reproducibility-hygiene "slop" that AI-generated analysis code recurrently carries:190191```bash192python3 ${CLAUDE_SKILL_DIR}/scripts/check_generated_code.py {script.py} --strict193# or scan a whole output directory:194python3 ${CLAUDE_SKILL_DIR}/scripts/check_generated_code.py --code-dir {analysis_dir} --strict195```196197**Major findings (fix before reporting the script):**198- `MISSING_SEED` — randomness used (sampling, bootstrap, train/test split, rng) with no199 `np.random.seed` / `set.seed` / `random_state=` / `default_rng`. Non-reproducible.200- `HARDCODED_DATA_LITERAL` — a hand-typed, table-shaped numeric literal instead of201 `read_csv()`/`read.csv()` + subset. This is the data-integrity rule "never hand-type CSV202 data into scripts."203- `HARDCODED_ABS_PATH` — an absolute path literal (`/Users/`, `/home/`, `C:\`, `~/Documents`).204 Non-portable and a PII risk.205- `INPLACE_SOURCE_OVERWRITE` — writing to the same path read as input; this overwrites raw206 data. Write derived outputs to a new path ("never modify raw data").207208**Flags (fix when tidying):** `DEBUG_LEFTOVER` (a `breakpoint()` / `browser()` / debug print209/ TODO marker left in) and `UNUSED_IMPORT` (a dead Python dependency).210211The gate is conservative on the Major checks — it fires `HARDCODED_DATA_LITERAL` only on212genuinely table-shaped literals and `MISSING_SEED` only on a real randomness call — so it213stays quiet on legitimate analysis code. It is the analysis-side mirror of the214data-integrity and reproducibility checks `/self-review` is built to catch downstream.215216### Phase 4: Report217218After execution, generate manuscript-ready text:2192201. **Results paragraph**: 3-8 sentences with specific numbers, formatted as:221 - Continuous: "mean +/- SD" or "median (IQR)"222 - Proportions: "n/N (XX.X%)"223 - Test results: "statistic = X.XX, p = 0.XXX"224 - Effect sizes: "Cohen's d = X.XX (95% CI: X.XX-X.XX)"225 - AUC: "AUC = 0.XXX (95% CI: 0.XXX-0.XXX)"2262. **Table/figure captions**: Draft captions referencing table/figure numbers.2273. **Methods snippet**: 2-3 sentences describing the statistical methods used, suitable for228 the Methods section.229230## Statistical Reporting Rules (Always Enforced)231232These rules apply to ALL analyses without exception:2332341. **Exact p-values**: Report exact values (e.g., p = 0.034), not inequalities.235 Exception: report as p < 0.001 when the value is below 0.001.2362. **Confidence intervals**: Always report 95% CIs for primary endpoints.2373. **Effect sizes**: Report alongside every p-value (Cohen's d, eta-squared, odds ratio,238 risk ratio, etc., as appropriate).2394. **Parametric vs non-parametric**: Choose based on assumption checks, not convenience.240 Report the assumption test results.2415. **Multiple comparisons**: Apply and explicitly report the correction method when242 performing 3+ comparisons.2436. **Sample size reporting**: Always state n for each group/analysis.2447. **Missing data**: Report how many cases were excluded and why.2458. **Decimal places**: p-values to 3 decimals, proportions to 1 decimal, means/SDs to246 appropriate precision for the measurement.2479. **Design/power statistics are code outputs, never hand-computed.** Any minimum detectable248 effect (MDE), a-priori or post-hoc power, or required sample size that will appear in the249 manuscript MUST be emitted by this committed script — printed with its method and inputs250 (n per arm, alpha, power, allocation ratio, one/two-sided) — not computed in a side tool251 (G*Power, an online calculator) and pasted in. Use one method family consistently252 (e.g. the exact noncentral-t via `statsmodels` `TTestIndPower` or `scipy`'s `nct`); do not253 mix a normal approximation for some values with exact-t for others. A value that exists only254 in the manuscript with no script that reproduces it is the failure mode `/self-review`255 Phase 2.5a-2 is built to catch.25610. **Estimand & CI output contract.** Every primary point estimate — including quantile257 estimands (T25, median time-to-event), pooled proportions, and subdistribution HRs, not258 just ORs/HRs/AUCs — MUST be emitted together with its 95% CI. In the output CSV, carry the259 interval as explicit columns (`estimate, ci_lower, ci_upper`) or as a single text column in260 `est (lo–hi)` form; never emit a point estimate with no interval in an adjacent column.261 Round ORs/HRs/sHRs to 2 decimals and AUC/C-statistic to 3. This is the output side of the262 `/self-review` §C assertion that "all primary metrics have 95% CIs."263264### Effect-Size Real-World Translation265266Whenever a primary result is a correlation, a standardized coefficient, a regression slope, an267OR/HR/RR, or a Cohen's d, also report it as a **plain-language unit shift** a non-statistician can268act on. The coefficient answers "is there an association"; the translation answers "how much, in269units I use". This complements rule 3 above (report effect sizes) — it does not replace it.270271**When to apply**272- Any continuous-exposure to continuous-outcome association reported as Spearman's rho, Pearson's r,273 or a standardized slope.274- Any OR/HR/RR where the audience needs an absolute-risk feel.275- Reader / expert-elicitation studies, clinical-utility framing, abstracts, and figure captions.276277**Procedure**2781. **Pick an anchored contrast on the exposure**, not a 1-unit step. Default: 25th to 75th percentile279 (IQR). State both endpoints in native units.2802. **Translate to the outcome scale.**281 - For a rank/standardized association (Spearman's rho or a per-SD slope) under an approximately282 monotonic-linear assumption:283 `delta_outcome ~= ((x_p75 - x_p25) / SD_x) * |rho| * SD_outcome`.284 Report as: "going from {x_p25} to {x_p75} {units} is associated with about {delta_outcome}285 {outcome units} on average."286 - For a regression slope b: `delta_outcome = b * (x_p75 - x_p25)` (cleaner; no monotonicity caveat).287 - State the assumption explicitly; the IQR translation is a more defensible verbal guide than an288 SD-scaled one.2893. **For OR/HR/RR**, accompany the relative measure with an absolute one at a stated baseline risk:290 the absolute risk difference, and NNT = 1 / ARR (or NNH = 1 / ARI). Always state the baseline risk used.2914. **Bound the claim**: report the contrast, the assumption, and a CI on the coefficient; do not imply292 causation from a crude or unadjusted estimate.293294**Worked example (synthetic)**295rho = 0.39 between a fasting marker (IQR 0.6 to 3.5 units, SD 3.05) and an index (SD 2.13):296`((3.5 - 0.6) / 3.05) * 0.39 * 2.13 ~= 0.8` -> "Going from the 25th to the 75th percentile of the297marker is associated with about 0.8 index units higher on average (monotonic-linear approximation;298crude, unadjusted)."299300**Output contract (clinical-utility is a default, not an optional add-on).** Report every301primary effect in units a clinician acts on, by default — do not leave these as prose to be302added later:303- **OR/HR/RR primary outcomes** → report the relative measure **and** the absolute risk at304 a stated baseline + absolute risk difference + **NNT** (or NNH = 1/ARI), baseline risk305 explicit. A relative-only headline is incomplete.306- **Continuous outcomes** → add the IQR/clinically-anchored "Real-world translation" line307 beneath the effect size.308- **Prediction / classification (incl. medical-AI) models** → a **decision-curve /309 net-benefit** pass at the relevant threshold is standard output, not just AUC +310 calibration. An incremental claim reports added **net benefit / NRI / IDI over the311 established clinical model**, not the new model's AUC alone. See312 `references/table-standards/table-types/incremental_value.md` and the `make-figures`313 `decision_curve` exemplar (and `render_core_figures.py` for the rendered curve).314315## Error Handling316317- If a script fails to execute, report the error in one line, diagnose the likely cause318 (missing package, data format mismatch, wrong column name), and present a fix.319- Do NOT retry the same script more than once without modifying it or asking the user.320- If an R package is unavailable, suggest `install.packages()` and wait for user confirmation.321- For prediction models: always include calibration assessment (Brier score, calibration plot,322 or calibration slope/intercept) alongside discrimination metrics. AUC alone is insufficient.323324## Output Conventions325326### Tables327328**Before generating any publication table**, load the journal profile and table type template:3291. Load `${CLAUDE_SKILL_DIR}/references/table-standards/journal-profiles/{journal}.yaml` if a target journal is known3302. Load `${CLAUDE_SKILL_DIR}/references/table-standards/table-types/{type}.md` for the relevant table type3313. If no journal specified, default to AMA style (Radiology profile)332333**Output formats** (always generate all three):334- CSV file (for downstream use and archival)335- Console markdown rendering (for user review)336- R gtsummary code (for publication-quality Word/LaTeX export)337338**Universal rules** (enforced regardless of journal):339- No vertical lines — horizontal rules only (top, below header, bottom)340- Binary variables: show only one level (e.g., Male only, not Male + Female)341- Units in column headers, not repeated in cells342- Consistent decimal places within each column343- All abbreviations defined in footnotes, self-contained per table344- Exact P values always (never "NS" or "significant")345- Name the statistical test in footnote or general note346- Variability measure always stated: mean (SD) or median (IQR)347348**Journal-specific parameters** (from loaded YAML profile):349- Footnote markers: letters (AMA) vs symbols (NEJM/Lancet)350- P value format: case, leading zero, italic351- CI separator: comma (Radiology) vs "to" (JAMA/NEJM/Lancet)352- Title format: period (AMA) vs colon (Lancet)353- Abbreviation order: appearance (Radiology) vs alphabetical (JAMA)354355**Footnote placement order** (universal):3561. General note (no marker) — e.g., "Data are mean (SD) unless noted"3572. Abbreviations — in order per journal convention3583. Specific notes (superscript markers) — per-cell explanations3594. Probability notes — significance thresholds (if applicable)360361**gtsummary pipeline** (recommended for R table generation):362```r363theme_gtsummary_journal("{journal}") # "jama", "lancet", "nejm"364theme_gtsummary_compact()365# ... build table ...366tbl %>% as_flex_table() %>% flextable::save_as_docx(path = "table.docx")367```368369**Validation checklist** (run before finalizing any table):370- [ ] Binary variables show only one level371- [ ] Units in headers, not cells372- [ ] Consistent decimal places per column373- [ ] Statistical test named (footnote or general note)374- [ ] Effect sizes per clinically meaningful unit (per 10 years, not per 1 year)375- [ ] Reference category stated for categorical predictors376- [ ] No "NS" — exact P values only377- [ ] Abbreviations defined in footnotes378379### Figures380381- Format: PDF (vector, for journal) + PNG (300 DPI, for review)382- Style: Use `figure_style.mplstyle` for consistent appearance383- Font: Arial, 8-10pt384- Colors: Colorblind-safe palette385- Size: 3.5 inches (single column) or 7.0 inches (double column) width386- Always include axis labels with units387388### Console Output389390- Formatted for direct copy-paste into the Results section of a manuscript391- Include all numbers that would appear in the text392- Use the reporting format conventions above393394## Analysis-Specific Guidelines395396### Table 1 (Demographics)397398- Template: `references/templates/table1_demographics.py`399- Table type guide: `references/table-standards/table-types/table1_demographics.md`400- Continuous variables: mean +/- SD if normal, median (IQR) if skewed401- Categorical variables: n (%)402- Binary variables: show only one level (e.g., Male n (%), not both Male and Female)403- Compare groups: t-test/Mann-Whitney for continuous, chi-square/Fisher for categorical404- Report standardized mean differences (SMD) if requested (preferred over P for PS-matched studies)405- RCTs: P values in Table 1 are usually unnecessary per CONSORT406- gtsummary `tbl_summary()` with journal theme for R pipeline407408### Diagnostic Accuracy409410- **Methodology guide**: `references/analysis_guides/diagnostic_accuracy.md` (**load before generating code** — every metric with a CI on a stated analysis unit; the confidence-weighted trap [unweighted-baseline AUC + monotonic-encoding check, produce-side of probe D9]; paired DeLong vs MRMC for reader-generalising claims; per-stratum admissibility [D10]; one-scale-per-comparison [D11])411- Template: `references/templates/diagnostic_accuracy.py`412- Always report: sensitivity, specificity, PPV, NPV, accuracy, AUC413- CIs: Wilson score for proportions, DeLong for AUC414- ROC curve: include diagonal reference line, AUC in legend415- If comparing models: DeLong test for AUC comparison416- Youden's index for optimal threshold when applicable417- Include calibration assessment (Brier score, calibration plot) for prediction models418- **NRI/IDI**: When comparing two models (e.g., base model vs model + AI score), report:419 - Category-based NRI (with clinically defined risk categories)420 - Continuous NRI (note: tends to be inflated — report alongside category-based)421 - IDI (Integrated Discrimination Improvement)422 - Bootstrap 95% CIs (1000+ iterations)423 - These supplement, not replace, DeLong AUC comparison424- Table type guide (added value beyond a baseline): `references/table-standards/table-types/incremental_value.md` (paired ΔAUC + DeLong CI, continuous NRI with event/non-event split, IDI, net benefit at a prespecified threshold, same-patient/calibrated-first discipline). Pairs the decision-curve exemplar `make-figures` `references/exemplar_plots/decision_curve.md`.425- Reader study (MRMC): `references/table-standards/table-types/reader_study.md` (per-reader + reader-averaged AUC with an Obuchowski–Rockette/DBM reader+case CI, per-patient vs per-lesion unit, superiority vs non-inferiority margin). Use an MRMC method (not a fixed-reader DeLong CI) for a claim that generalises to readers. Pairs `make-figures` `references/exemplar_plots/mrmc_roc.md`.426427### Inter-rater Agreement428429- **Methodology guide**: `references/analysis_guides/agreement_reliability.md` (**load before generating code** — the pseudoreplication trap for clustered/repeated measurements + the pseudoreplication-safe per-subject / mixed-effects code, ICC model/type selection, agreement-vs-reliability distinction; pairs with self-review probe O18)430- Table type guide: `references/table-standards/table-types/agreement.md` (ICC with model/type + CI, weighted κ for ordinal, Bland–Altman bias + LoA, reliability-vs-agreement distinction, common errors)431- Template: `references/templates/agreement_analysis.py`432- 2 raters + categorical: Cohen's kappa433- 2+ raters + categorical: Fleiss' kappa (or Krippendorff's alpha)434- Continuous: ICC (specify model: one-way, two-way random/mixed; type: single/average)435- Always report interpretation labels (Landis & Koch or Cicchetti)436- Bland-Altman plot for continuous paired measurements437- Bootstrap CIs (1000 iterations, seed=42)438439### Meta-analysis440441- Prefer R (meta/metafor packages) for meta-analysis442- **Comparative**: `metabin()` for binary outcomes (OR/RR), `metagen()` for continuous443 - Use `method = "Inverse"`, `method.tau = "DL"`, `method.random.ci = "HK"`444 - Avoid deprecated args: `comb.fixed` → `common`, `hakn` → `method.random.ci`445- **Single-arm pooled proportion**: `metaprop()` with `sm = "PLOGIT"`, `method.ci = "CP"`446 - **Small-study test branch**: do **not** use Egger's regression for a single-arm proportion meta-analysis — funnel-asymmetry tests assume an effect-size-vs-SE relationship that does not hold for raw proportions. If a small-study assessment is needed, use Peters' test or an arcsine-based variant, and only when `k >= 10` (note underpowered otherwise)447 - **Standard output**: report `tau-squared` on the logit scale and a **95% prediction interval** (`metaprop(..., prediction = TRUE)`) in addition to the pooled estimate; the PI conveys where a future study's proportion is expected to fall under the random-effects model448- **Nested observation units**: if the proportion's unit is nested within study (e.g., per-lesion within study, per-image within patient), do **not** report a naive Wilson/binomial CI that ignores clustering — use a cluster-bootstrap or a GLMM with a random intercept per study so the CI reflects the design449- Heterogeneity: I-squared, Q test, tau-squared, and a 95% prediction interval for the random-effects pooled estimate450- Forest plot: individual studies + pooled estimate451- Funnel plot + Egger's test for publication bias (comparative effect sizes only; note: underpowered k<10)452- Sensitivity analysis: leave-one-out (`metainf()`)453- Subgroup: `update(res, subgroup = variable)`454455### DTA Meta-Analysis456457- Template: `references/templates/dta_meta_analysis.R`458- Prefer R (`mada`, `meta`, `metafor` packages) for DTA meta-analysis459- **Bivariate model** (Reitsma): `mada::reitsma()` — recommended over separate pooling of Se/Sp460 - Accounts for correlation between sensitivity and specificity461 - Produces SROC curve with confidence + prediction regions462- **Key outputs**: Pooled Se/Sp (95% CI), positive/negative LR, DOR, SROC AUC463- **Threshold effect**: Spearman correlation between logit(Se) and logit(FPR)464 - If significant: interpret single pooled Se/Sp with caution, emphasize SROC curve465- **Forest plots**: Paired (sensitivity + specificity side by side)466- **Publication bias**: Deeks' funnel plot asymmetry test (NOT standard funnel plot)467 - Standard funnel plots are inappropriate for DTA studies468 - Note: underpowered for k < 10469- **Dual approach** (comparative + single-arm):470 - Primary: `metabin()` for comparative studies (OR/RR)471 - Secondary: `metaprop()` with `sm = "PLOGIT"` for single-arm pooled proportion472 - Use `method = "Inverse"`, `method.tau = "DL"`, `method.random.ci = "HK"`473- **Small studies (k < 10)**: bivariate model may not converge; consider narrative synthesis474- **Alternative**: If `mada` unavailable, use `metafor::rma.mv()` with bivariate structure475476### Network Meta-Analysis477478- **Guide**: Load `analysis_guides/network_meta_analysis.md` before generating code479- For ≥3 interventions via combined direct + indirect evidence (incl. component NMA); pairwise machinery (search/screening/random-effects model) via the Meta-analysis section above480- **Assess transitivity before pooling**: compare effect-modifier distributions across comparisons (box plots / table) and/or network meta-regression — it is a clinical judgment, not a test481- **Test consistency** globally (design-by-treatment) AND locally (node-split / back-calculation); a **star network (no closed loops) cannot be checked** — state it; investigate the source of any inconsistency (often one trial)482- R `netmeta` (frequentist: `netsplit`, `decomp.design`, `netheat`, `netrank` P-scores, comparison-adjusted `funnel`) or Bayesian `gemtc` / `multinma` / `BUGSnet` (node-split, SUCRA, DIC)483- Present a **network plot** (node ∝ sample size, edge ∝ #trials); report global **τ²**; **ranking (SUCRA/P-score) is not a superiority test** — report it with the league table, intervals, and certainty484- Certainty **per estimate** via **CINeMA / GRADE-NMA** (downgrade indirect-only); component NMA assumes **additivity** (state/check it). Report against **PRISMA-NMA**; risk of bias via **RoB-NMA**. Review-side probes: NM1–NM8 in `network_meta_analysis.md`485486### Health Economic Evaluation487488- **Guide**: Load `analysis_guides/health_economic_evaluation.md` before generating code489- For cost-effectiveness (CEA), cost-utility (CUA, QALY), cost-benefit (CBA), cost-minimisation, or budget-impact analyses; trial-based or decision-model-based (decision tree, **Markov/state-transition**, discrete-event simulation)490- Compute **incremental cost ΔC, incremental effect ΔE, and the ICER = ΔC/ΔE**; with ≥3 options remove **dominated / extended-dominated** strategies before sequential ICERs; prefer **net benefit (INMB = λΔE − ΔC)** for regression/probabilistic summaries491- State and justify the **perspective, time horizon (lifetime for chronic disease), discount rate (both costs and outcomes), currency + price year**; QALYs from a named preference-based instrument + value set492- **Uncertainty is the analytic core**: one-way / **tornado** for drivers, **probabilistic sensitivity analysis (PSA)** with justified parameter distributions (beta for probabilities/utilities, gamma/log-normal for costs) → **cost-effectiveness plane + CEAC**; scenario analyses for structural choices493- R `heemod` / `dampack` / `hesim` / `BCEA` (state-transition + PSA + CEAC + EVPI), `flexsurv` for survival extrapolation. Report against **CHEERS 2022**; make the "cost-effective" conclusion conditional on a stated willingness-to-pay threshold. Review-side probes: HE1–HE8 in `health_economic_evaluation.md`494495### Survey/Likert496497- Descriptive: median, IQR, frequency distribution per item498- Internal consistency: Cronbach's alpha with item-total correlations499- **Reverse-coding guard (run before reliability)**: a negatively-worded scale item must be recoded `(min+max) - x` before computing the scale total or Cronbach's alpha. An un-recoded reverse item produces a *negative* item-rest correlation and a negative alpha — which is a coding bug, **not** evidence of a multidimensional construct (do not defend it as such; you lose a review round). `likert_summary.py` prints the per-item item-rest correlations, flags negative ones as reverse-code suspects, warns loudly on a negative alpha, and accepts `--reverse-items E3 ...` to apply the recode before scoring. To screen at cleaning time, run `/clean-data` `scripts/check_reverse_coding.py`. See the global rule `survey-scale-reliability.md`.500- If comparing groups: Mann-Whitney or Kruskal-Wallis (ordinal data)501- Visualization: diverging stacked bar chart502503### Survival Analysis504505- **Methodology guide**: `references/analysis_guides/survival.md` (**load before generating code** — competing risks first [naive 1−KM overestimates → produce the Aalen–Johansen/Fine–Gray CIF; cause-specific vs subdistribution for which question, produce-side of probe S3]; PH check → RMST when violated; reverse-KM follow-up + C-index variant [S6]; estimand provenance [S8])506- Table type guide: `references/table-standards/table-types/survival_results.md` (Cox results table: events/person-time, reverse-KM median follow-up, univariable + adjusted HR with CI, PH-assumption footnote, EPV/sparse-stratum and RMST-when-PH-violated rules)507- Kaplan-Meier curves with number-at-risk table508- Log-rank test for group comparison509- Cox proportional hazards: report HR (95% CI)510- **Events-per-variable (EPV) gate**: check `events / n_covariates >= 10` before fitting Cox (mirror of the logistic EPV rule). Warn if violated and fall back to a Firth/penalized Cox or profile-likelihood CIs; do not report Wald CIs from a sparse-event model as if stable511- **Nested observation units (cluster-robust CI)**: when a subject contributes more than one analysed unit (multiple lesions, both eyes, repeated episodes), pass a subject id so the HR CIs use a robust cluster-sandwich variance (`coxph(..., cluster = id)` / `robust = TRUE` in R, `cluster_col=` in lifelines, e.g. `survival_analysis.py --cluster <id>`). Treating correlated rows as independent understates the standard errors and narrows the CI artificially512- Check proportional hazards assumption (Schoenfeld residuals)513- **PH violation → do not report a single time-averaged HR.** If the Schoenfeld global test is significant (or a covariate's residual trends with time), a single Cox HR averages a changing effect and is misleading. Report a piecewise / time-stratified HR (split follow-up at a clinically sensible cut, or `tt()` time-transform), or switch to RMST difference at a fixed horizon, and state the violation explicitly514- **Horizon vs follow-up.** Do not read a KM/CIF estimate at a horizon beyond the data: if a reported time point (e.g., a 15-year cumulative incidence) exceeds the reverse-KM median follow-up, either restrict the horizon to where the risk set is non-trivial or report the number-at-risk at that horizon so the reader can judge the extrapolation515- Report median survival with 95% CI516- **Warranty period / quantile estimands (T25 etc.)**: Time to a fixed cumulative incidence. Use `quantile()` from the KM/`survfit` object and **always emit the 95% CI** (the lower/upper from `quantile(km, conf.int=TRUE)`, or a log-transformed / bootstrap CI) alongside the events/n that define it. A quantile point estimate reported without its CI is incomplete. If the event rate is below the target quantile, report "not reached" and consider Weibull parametric extrapolation (also with an interval)517518### Interval-Censored Survival519520When exact event times are unknown (e.g., health screening cohorts where status changes are detected at periodic visits), standard KM underestimates time-to-event. Use interval-censored methods:521522- **R packages**: `icenReg` (parametric/semi-parametric IC regression), `interval` (NPMLE/Turnbull), `survival` (Surv type "interval2")523- **Turnbull estimator**: Non-parametric MLE for interval-censored data — analogous to KM but accounts for the interval between last negative and first positive observation524- **Parametric IC models**: Weibull or log-logistic via `icenReg::ic_par()`. Report shape/scale parameters and compare AIC across distributions525- **Mid-point imputation**: Simple approximation — event time = midpoint of (last negative, first positive). Acceptable as sensitivity analysis but NOT as primary method526- **When to use**: Serial measurement cohorts (e.g., health screening databases), cancer screening intervals, repeated biomarker assessments527- **Auto-trigger**: if the event date is defined by a periodic visit / scheduled re-examination (the event is detected *at* a visit, not observed exactly), interval-censoring is not optional — make an IC model the **primary** analysis, or at minimum a mandatory pre-specified sensitivity analysis, and do not present a right-censored Cox `coxph()` on visit-dated events as if the times were exact528- **Multistate / transition models**: for repeated transitions (e.g., `msm`), account for subject-level clustering with a subject random effect or a sandwich (robust) variance, and check the time-homogeneity assumption (constant transition intensities) before trusting a single rate529- **Reporting**: State the interval-censored nature of the data explicitly in Methods. Report both standard KM (for comparability with prior literature) and IC estimates (as primary or sensitivity)530531### Competing Risks532533When death or other events preclude the outcome of interest, standard KM overestimates cumulative incidence (treats competing events as censored). Use competing risk methods:534535- **R packages**: `cmprsk` (Fine-Gray), `tidycmprsk` (tidy interface), `survival` (cause-specific Cox)536- **Cumulative incidence function (CIF)**: `cmprsk::cuminc()` — replaces 1-KM for each event type. Gray's test for group comparison537- **Fine-Gray subdistribution hazard**: `cmprsk::crr()` or `tidycmprsk::crr()` — reports subdistribution HR (sHR) with 95% CI. Interpretable as effect on CIF directly. **Check the subdistribution-PH assumption** the same way you check it for Cox (a time-interaction term on the subdistribution scale, or inspection of scaled-residual analogues); a constant sHR is an assumpti538539…(truncated)