You recorded a binary outcome relapse (did the patient relapse, yes/no) and two continuous predictors, biomarker_level and age. The question is not just whether each one shifts the odds of relapse on its own, but whether the effect of biomarker_level changes across patient ages — a continuous-by-continuous interaction on a binary outcome. As an MCPower formula that is relapse ~ biomarker_level * age with family="logit", where * expands to both main effects plus their product term (biomarker_level + age + biomarker_level:age) and the outcome is fitted by a logistic GLM. The test that carries the moderation hypothesis is the interaction coefficient biomarker_level:age.

Variations

  • Test the whole model, not just the interaction. Swap target_test="biomarker_level:age" for target_test="all" to get power for each main effect, the interaction, and the omnibus test in one run.
  • Weaker or stronger moderation. The interaction effect is the uncertain one — re-run with biomarker_level:age=0.10 (small) or biomarker_level:age=0.25 (medium) to see how quickly the required sample size moves.
  • Correlated predictors. Biomarker level and age are rarely independent. Add set_correlations("corr(biomarker_level, age)=0.3") to see how collinearity erodes power for the product term.
  • Find the N instead of the power. Replace the find_power call with find_sample_size(target_test="biomarker_level:age", from_size=150, to_size=800, by=25) to sweep for the smallest sample that reaches 80% power on the interaction.
  • Same design, other fields:
    • germinated ~ soil_nitrogen * moisture — does the effect of soil nitrogen on germination depend on moisture level? (ecology)
    • employed ~ experience_years * tenure — does the effect of experience on employment depend on job tenure? (social science)

Not this setup?

If you'd rather have…

Copy-paste setup

from mcpower import MCPower

# Continuous-by-continuous moderation on a binary outcome: does the effect of
# biomarker_level on whether a patient relapses (yes/no) depend on their age?
# '*' expands to both main effects plus the product term
# (biomarker_level + age + biomarker_level:age). family="logit" makes relapse a
# binary (0/1) outcome fitted by a logistic GLM.
model = MCPower("relapse = biomarker_level * age", family="logit")

# family="logit" needs the event rate at the reference level (all predictors at
# their mean): here 30% of patients relapse. This fixes the logistic
# intercept, so it must be set before find_power.
model.set_baseline_probability(0.3)

# Standardised effects on the continuous benchmark scale (0.10 / 0.25 / 0.40).
# Both main effects are moderate; the interaction (the test of interest) is
# smaller, as moderation effects usually are.
model.set_effects("biomarker_level=0.30, age=0.25, biomarker_level:age=0.15")

# Power for the interaction term at N=300.
model.find_power(sample_size=300, target_test="biomarker_level:age")
suppressMessages(library(mcpower))

# Continuous-by-continuous moderation on a binary outcome: does the effect of
# biomarker_level on whether a patient relapses (yes/no) depend on their age?
# '*' expands to both main effects plus the product term
# (biomarker_level + age + biomarker_level:age). family="logit" makes relapse a
# binary (0/1) outcome fitted by a logistic GLM.
model <- MCPower$new("relapse ~ biomarker_level * age", family = "logit")

# family="logit" needs the event rate at the reference level (all predictors at
# their mean): here 30% of patients relapse. This fixes the logistic
# intercept, so it must be set before find_power.
model$set_baseline_probability(0.3)

# Standardised effects on the continuous benchmark scale (0.10 / 0.25 / 0.40).
# Both main effects are moderate; the interaction (the test of interest) is
# smaller, as moderation effects usually are.
model$set_effects("biomarker_level=0.30, age=0.25, biomarker_level:age=0.15")

# Power for the interaction term at N=300.
invisible(model$find_power(sample_size = 300, target_test = "biomarker_level:age"))