You have a classic factorial experiment — two crossed treatment factors (treatment arm and patient sex), each applied at two levels, in every combination — but you also measured each patient's systolic blood pressure before the manipulation. Running the plain two-way ANOVA on the post-treatment outcome leaves that baseline's variability in the error term, shrinking every F-test. Folding it in as a covariate soaks up between-subject noise and sharpens both main-effect and interaction tests: this is the two-way factorial ANCOVA, with all cells sharing one common slope on the covariate (parallel slopes).

The model is blood_pressure = treatment * sex + baseline_bp* expands to treatment + sex + treatment:sex, giving you both main effects plus the interaction, all read net of the continuous baseline and fit by OLS.

Variations

  • Power the interaction, not the main effects. Point target_test at treatment:sex when the cell-difference-of-differences is the hypothesis — interactions usually need a noticeably larger N than main effects to reach the same power.
  • More than two treatment levels. Swap treatment for a 3-level factor (e.g. treatment=(factor,3) for placebo, dose A, dose B); the page stays a two-way factorial ANCOVA, now with a multi-level omnibus test on treatment.
  • A weaker baseline covariate. Dial baseline_bp down to the 0.25 medium benchmark (or 0.10 small) when the baseline only loosely predicts the outcome — the adjustment buys you less, so the factor tests need more N to clear power.
  • Let the slopes diverge. Add a treatment:baseline_bp term to test whether the baseline adjustment differs across treatment arms — that turns the parallel-slopes assumption into something you test rather than assume.
  • Same design, other fields:
    • seed_yield = watering * habitat + soil_nitrogen — ecology: two binary crop factors adjusted for soil nitrogen baseline
    • hourly_wage = sector * gender + experience_years — social science: sector-by-gender wage model adjusted for experience

Not this setup?

If you'd rather have…

Copy-paste setup

from mcpower import MCPower

# Two-way factorial ANCOVA: blood pressure crossed by treatment and sex, adjusted
# for a continuous baseline_bp covariate. '*' expands treatment * sex to
# treatment + sex + treatment:sex, so both main effects and the interaction are
# fitted, each read net of baseline_bp (parallel slopes, common slope across all cells).
model = MCPower("blood_pressure = treatment * sex + baseline_bp")

# treatment and sex are two-level factors; baseline_bp stays continuous by default.
model.set_variable_type("treatment=binary, sex=binary")

# Effect sizes on the benchmark scales.
#   treatment=0.50          → medium main effect (factor benchmark 0.20/0.50/0.80).
#   sex=0.50                → medium main effect.
#   treatment:sex=0.50      → medium interaction (the cell-difference-of-differences).
#   baseline_bp=0.40        → strong continuous covariate (benchmark 0.10/0.25/0.40).
model.set_effects("treatment=0.50, sex=0.50, treatment:sex=0.50, baseline_bp=0.40")

model.set_seed(2137)

# Power for both main effects at N=160 (the adjusted factorial F-tests).
model.find_power(sample_size=160, target_test="treatment, sex")
suppressMessages(library(mcpower))

# Two-way factorial ANCOVA: blood pressure crossed by treatment and sex, adjusted
# for a continuous baseline_bp covariate. '*' expands treatment * sex to
# treatment + sex + treatment:sex, so both main effects and the interaction are
# fitted, each read net of baseline_bp (parallel slopes, common slope across all cells).
model <- MCPower$new("blood_pressure ~ treatment * sex + baseline_bp")

# treatment and sex are two-level factors; baseline_bp stays continuous by default.
model$set_variable_type("treatment=binary, sex=binary")

# Effect sizes on the benchmark scales.
#   treatment=0.50          -> medium main effect (factor benchmark 0.20/0.50/0.80).
#   sex=0.50                -> medium main effect.
#   treatment:sex=0.50      -> medium interaction (the cell-difference-of-differences).
#   baseline_bp=0.40        -> strong continuous covariate (benchmark 0.10/0.25/0.40).
model$set_effects("treatment=0.50, sex=0.50, treatment:sex=0.50, baseline_bp=0.40")

model$set_seed(2137)

# Power for both main effects at N=160 (the adjusted factorial F-tests).
invisible(model$find_power(sample_size = 160, target_test = "treatment, sex"))