mfpi() assesses whether the effect of a grouping
variable, such as treatment, varies with a continuous variable. The
treatment effect is modelled as a fractional polynomial (FP) function of
the continuous variable, so the variable does not need to be
categorised. The procedure has two stages: Stage 1 selects an adjustment
model with MFP, and Stage 2 tests each candidate interaction while
holding that adjustment model fixed.
This vignette shows how to run the analyses in the accompanying paper
(paper reference) and how to read the
output. For the methodology, the flexibility variants
(flex), the selection criteria and the interface, each
section points to the relevant part of the paper (Sections 2 and 3).
The data come from a randomised trial comparing placebo with
diethylstilboestrol (paper, Section 4). The outcome is overall survival
(survtime, cens), and the grouping variable is
the binary treatment rx. The data set already contains the
recoding described in the paper.
Converting rx to a factor with named levels gives
Placebo and Treatment labels in printed
output, predictions and plots. The first level is the reference group
(paper, Section 3.3).
data("advanced_prostate_cancer", package = "mfp2")
advanced_prostate_cancer$rx <- factor(
advanced_prostate_cancer$rx,
levels = c(0, 1), labels = c("Placebo", "Treatment")
)
table(advanced_prostate_cancer$rx)
#>
#> Placebo Treatment
#> 237 238
tapply(advanced_prostate_cancer$age, advanced_prostate_cancer$rx, range)
#> $Placebo
#> [1] 48 84
#>
#> $Treatment
#> [1] 49 89The question is whether the treatment effect varies with age. The overall treatment comparison, which ignores age, shows no clear difference (paper, Section 4.1):
survdiff(Surv(survtime, cens) ~ rx, data = advanced_prostate_cancer)
#> Call:
#> survdiff(formula = Surv(survtime, cens) ~ rx, data = advanced_prostate_cancer)
#>
#> N Observed Expected (O-E)^2/E (O-E)^2/V
#> rx=Placebo 237 180 165 1.33 2.66
#> rx=Treatment 238 158 173 1.27 2.66
#>
#> Chisq= 2.7 on 1 degrees of freedom, p= 0.1
cox <- coxph(Surv(survtime, cens) ~ rx, data = advanced_prostate_cancer)
summary(cox)$conf.int
#> exp(coef) exp(-coef) lower .95 upper .95
#> rxTreatment 0.8374787 1.19406 0.6762658 1.037123The hazard ratio is 0.84 (95% CI 0.68–1.04). A single overall estimate like this can hide an effect that changes with age.
All analyses below use flex = "flex1" (the same FP
powers in both groups) and ties = "breslow", as in the
paper. The package defaults are flex = "flex3" and
ties = "efron". The paper (Sections 2.5 and 4) explains
both choices and how Efron’s method changes the results.
fit_unadjusted <- mfpi(
Surv(survtime, cens) ~ rx + fp(age),
data = advanced_prostate_cancer,
family = "cox",
group_var = "rx",
interaction_vars = "age",
interaction_forms = c(age = "fp2"),
flex = "flex1",
ties = "breslow"
)
#>
#> ======================================================================
#> STEP 1: Selected Adjustment Model (MFP)
#> ======================================================================
#>
#> Model: Cox proportional hazards
#> Criterion: p-value
#>
#> df_initial select alpha searched_fp df_final power1
#> age 4 0.050 0.050 yes 2 3
#>
#>
#> Selected adjustment variables (1): age
#>
#> ======================================================================
#> STEP 2: Evaluating Interactions (flex = flex1)
#> ======================================================================
#>
#> Group variable: rx
#> Selection rule: p-value < 0.050
#> P-value adjustment: none
#>
#> ----------------------------------------------------------------------
#> [1/1] Variable: 'age' x 'rx'
#> Adjusting for: (none)
#> ----------------------------------------------------------------------
#> Type deviance_int deviance_diff df pvalue main_power int_power
#> FP2 3766.216 9.746 2 0.008 (3, 3) (3, 3), (3, 3)
#>
#>
#> ======================================================================
#> STEP 3: Interaction Summary
#> ======================================================================
#> Variable Type p-value selected
#> ----------------------------------------------------------------------
#> age FP2 0.008 Yes
#> ----------------------------------------------------------------------
#>
#> Done: 1 of 1 interactions selected.
print(fit_unadjusted)
#> =========================================
#> MFPI | n = 475 (events = 338) | flex1
#> =========================================
#>
#> Call:
#> mfpi(formula = Surv(survtime, cens) ~ rx + fp(age), data = advanced_prostate_cancer,
#> group_var = "rx", interaction_vars = "age", interaction_forms = c(age = "fp2"),
#> flex = "flex1", family = "cox", ties = "breslow")
#>
#> Model: Cox proportional hazards
#>
#>
#> Step 1 - Selected Adjustment Model (MFP):
#> -----------------------------------------
#> df_initial select alpha searched_fp df_final power1
#> age 4 0.050 0.050 yes 2 3
#>
#> Selected adjustment variables (1): age
#>
#> Step 2 - All Interaction Candidates:
#> ------------------------------------
#>
#> Grouping variable : rx
#> Selection criterion : p-value
#> Selection rule : p-value < 0.050
#> P-value adjustment : none
#>
#> variable type deviance_int deviance_diff df_int p-value fp_powers_main
#> age FP2 3766.216 9.746 2 0.008 (3, 3)
#>
#> FP powers by group level:
#> variable type Group Group level fp_powers_int
#> age FP2 rx Placebo (3, 3)
#> age FP2 rx Treatment (3, 3)
#>
#> Step 3 - Interaction Summary:
#> -----------------------------
#> variable type p-value selected
#> age FP2 0.008 YesWhat the arguments do:
fp(age) puts age in the Stage 1 adjustment model. It
does not set the form of the interaction.group_var names the grouping variable.interaction_vars lists the continuous variables tested
for interaction with group_var.interaction_forms = c(age = "fp2") models the
interaction with an FP2 function. All 36 FP2 power pairs are compared.
Variables not listed use FP1.flex = "flex1" selects the FP powers from the model
without interaction and uses them in both groups (paper, Section
2.5).How to read the output (paper, Section 4.2):
plot() returns a list with one element per variable.
Each element contains one plot per comparison with the reference
group.
Unadjusted analysis: fitted age functions by group (left) and the Treatment-minus-Placebo log hazard ratio with 95% CI (right); paper, Figure 2.
The right panel shows the log hazard ratio for Treatment vs Placebo as a function of age, the difference function \(\Delta\) (paper, Section 2.3). The dashed line is the constant log hazard ratio from the model without interaction. Here, the benefit of treatment is largest in younger patients and decreases with age. The confidence intervals do not account for the selection of FP powers.
type = "both" returns the fitted function for each group
and the difference between groups, all on the linear predictor
scale.
age_pred <- predict(
fit_unadjusted,
newdata = data.frame(age = c(50, 60, 70)),
terms = "age",
level = 0.95,
type = "both"
)
age_pred
#> =========================================================
#> MFPI prediction | term: age | scale: linear predictor
#> =========================================================
#>
#> Partial fitted functions:
#> age group estimate SE lower upper
#> 50 Placebo 0.460 0.322 -0.170 1.090
#> 60 Placebo -0.018 0.126 -0.266 0.230
#> 70 Placebo -0.154 0.080 -0.310 0.003
#> 50 Treatment -1.014 0.404 -1.805 -0.223
#> 60 Treatment -0.821 0.199 -1.212 -0.430
#> 70 Treatment -0.376 0.123 -0.618 -0.134
#>
#> Differences:
#> age contrast group reference estimate SE lower upper
#> 50 Treatment-Placebo Treatment Placebo -1.474 0.517 -2.488 -0.460
#> 60 Treatment-Placebo Treatment Placebo -0.802 0.235 -1.263 -0.342
#> 70 Treatment-Placebo Treatment Placebo -0.223 0.144 -0.505 0.060
#>
#> Note: Partial fitted functions comprise the intercept (when present), group main effect,
#> and group-specific FP function estimated from the adjusted interaction model.
#> Differences are comparison-group minus reference-group partial fitted functions.The differences are age-specific log hazard ratios. Exponentiating them gives hazard ratios of about 0.23, 0.45 and 0.80 at ages 50, 60 and 70 (paper, Section 4.2.2).
The other baseline factors are now candidates for the adjustment
model. Continuous variables enter through fp(); binary
variables are entered directly. Age is still the only variable tested
for interaction.
fit_adjusted <- mfpi(
Surv(survtime, cens) ~ rx + fp(age) + fp(wt) + fp(sbp) +
fp(dbp) + fp(sz) + fp(ap) + fp(hg) + fp(sg) +
pf + hx + bm + stage + ekg,
data = advanced_prostate_cancer,
family = "cox",
group_var = "rx",
interaction_vars = "age",
interaction_forms = c(age = "fp2"),
flex = "flex1",
ties = "breslow"
)
#>
#> ======================================================================
#> STEP 1: Selected Adjustment Model (MFP)
#> ======================================================================
#>
#> Model: Cox proportional hazards
#> Criterion: p-value
#>
#> df_initial select alpha searched_fp df_final power1 power2
#> hx 1 0.050 0.050 no 1 1 NA
#> sz 4 0.050 0.050 no 1 1 NA
#> age 4 0.050 0.050 yes 4 3 3
#> ekg 1 0.050 0.050 no 1 1 NA
#> wt 4 0.050 0.050 no 1 1 NA
#> pf 1 0.050 0.050 no 1 1 NA
#> stage 1 0.050 0.050 no 1 1 NA
#>
#>
#> Selected adjustment variables (7): hx, sz, age, ekg, wt, pf, stage
#>
#> ======================================================================
#> STEP 2: Evaluating Interactions (flex = flex1)
#> ======================================================================
#>
#> Group variable: rx
#> Selection rule: p-value < 0.050
#> P-value adjustment: none
#>
#> ----------------------------------------------------------------------
#> [1/1] Variable: 'age' x 'rx'
#> Adjusting for: hx, sz, ekg, wt, pf, stage
#> ----------------------------------------------------------------------
#> Type deviance_int deviance_diff df pvalue main_power int_power
#> FP2 3706.659 7.203 2 0.027 (3, 3) (3, 3), (3, 3)
#>
#>
#> ======================================================================
#> STEP 3: Interaction Summary
#> ======================================================================
#> Variable Type p-value selected
#> ----------------------------------------------------------------------
#> age FP2 0.027 Yes
#> ----------------------------------------------------------------------
#>
#> Done: 1 of 1 interactions selected.
print(fit_adjusted)
#> =========================================
#> MFPI | n = 475 (events = 338) | flex1
#> =========================================
#>
#> Call:
#> mfpi(formula = Surv(survtime, cens) ~ rx + fp(age) + fp(wt) +
#> fp(sbp) + fp(dbp) + fp(sz) + fp(ap) + fp(hg) + fp(sg) + pf +
#> hx + bm + stage + ekg, data = advanced_prostate_cancer, group_var = "rx",
#> interaction_vars = "age", interaction_forms = c(age = "fp2"),
#> flex = "flex1", family = "cox", ties = "breslow")
#>
#> Model: Cox proportional hazards
#>
#>
#> Step 1 - Selected Adjustment Model (MFP):
#> -----------------------------------------
#> df_initial select alpha searched_fp df_final power1 power2
#> hx 1 0.050 0.050 no 1 1 NA
#> sz 4 0.050 0.050 no 1 1 NA
#> age 4 0.050 0.050 yes 4 3 3
#> ekg 1 0.050 0.050 no 1 1 NA
#> wt 4 0.050 0.050 no 1 1 NA
#> pf 1 0.050 0.050 no 1 1 NA
#> stage 1 0.050 0.050 no 1 1 NA
#>
#> Selected adjustment variables (7): hx, sz, age, ekg, wt, pf, stage
#>
#> Step 2 - All Interaction Candidates:
#> ------------------------------------
#>
#> Grouping variable : rx
#> Selection criterion : p-value
#> Selection rule : p-value < 0.050
#> P-value adjustment : none
#>
#> variable type deviance_int deviance_diff df_int p-value fp_powers_main
#> age FP2 3706.659 7.203 2 0.027 (3, 3)
#>
#> FP powers by group level:
#> variable type Group Group level fp_powers_int
#> age FP2 rx Placebo (3, 3)
#> age FP2 rx Treatment (3, 3)
#>
#> Step 3 - Interaction Summary:
#> -----------------------------
#> variable type p-value selected
#> age FP2 0.027 YesStep 1 now selects seven variables, with age as FP2 (3, 3). The interaction is weaker after adjustment but is still selected (p = 0.027; paper, Section 4.3). The plot is similar to the unadjusted one:
Adjusted analysis: fitted age functions by group (left) and the Treatment-minus-Placebo log hazard ratio with 95% CI (right); paper, Figure 3.
With criterion = "aic", AIC is used in both stages. The
interaction is selected when it lowers AIC by more than
min_improvement (default 2). criterion = "bic"
works the same way (paper, Section 2.6).
fit_aic <- mfpi(
Surv(survtime, cens) ~
rx + fp(age) + fp(wt) + fp(sbp) + fp(dbp) +
fp(sz) + fp(ap) + fp(hg) + fp(sg) +
pf + hx + bm + stage + ekg,
data = advanced_prostate_cancer,
family = "cox",
group_var = "rx",
interaction_vars = "age",
interaction_forms = c(age = "fp2"),
flex = "flex1",
ties = "breslow",
criterion = "aic",
min_improvement = 2
)
#>
#> ======================================================================
#> STEP 1: Selected Adjustment Model (MFP)
#> ======================================================================
#>
#> Model: Cox proportional hazards
#> Criterion: AIC
#>
#> df_initial searched_fp df_final power1 power2
#> hx 1 no 1 1 NA
#> sz 4 no 1 1 NA
#> age 4 yes 4 3 3
#> ekg 1 no 1 1 NA
#> hg 4 no 1 1 NA
#> wt 4 no 1 1 NA
#> sg 4 no 1 1 NA
#> pf 1 no 1 1 NA
#>
#>
#> Selected adjustment variables (8): hx, sz, age, ekg, hg, wt, sg, pf
#>
#> ======================================================================
#> STEP 2: Evaluating Interactions (flex = flex1)
#> ======================================================================
#>
#> Group variable: rx
#> Selection rule: AIC reduction > 2.000
#>
#> ----------------------------------------------------------------------
#> [1/1] Variable: 'age' x 'rx'
#> Adjusting for: hx, sz, ekg, hg, wt, sg, pf
#> ----------------------------------------------------------------------
#> Type AIC_main AIC_interaction dAIC main_power int_power
#> FP2 3715.930 3712.248 3.681 (3, 3) (3, 3), (3, 3)
#>
#>
#> ======================================================================
#> STEP 3: Interaction Summary
#> ======================================================================
#> Variable Type dAIC selected
#> ----------------------------------------------------------------------
#> age FP2 3.681 Yes
#> ----------------------------------------------------------------------
#>
#> Done: 1 of 1 interactions selected.
print(fit_aic)
#> =========================================
#> MFPI | n = 475 (events = 338) | flex1
#> =========================================
#>
#> Call:
#> mfpi(formula = Surv(survtime, cens) ~ rx + fp(age) + fp(wt) +
#> fp(sbp) + fp(dbp) + fp(sz) + fp(ap) + fp(hg) + fp(sg) + pf +
#> hx + bm + stage + ekg, data = advanced_prostate_cancer, group_var = "rx",
#> interaction_vars = "age", interaction_forms = c(age = "fp2"),
#> flex = "flex1", min_improvement = 2, family = "cox", criterion = "aic",
#> ties = "breslow")
#>
#> Model: Cox proportional hazards
#>
#>
#> Step 1 - Selected Adjustment Model (MFP):
#> -----------------------------------------
#> df_initial searched_fp df_final power1 power2
#> hx 1 no 1 1 NA
#> sz 4 no 1 1 NA
#> age 4 yes 4 3 3
#> ekg 1 no 1 1 NA
#> hg 4 no 1 1 NA
#> wt 4 no 1 1 NA
#> sg 4 no 1 1 NA
#> pf 1 no 1 1 NA
#>
#> Selected adjustment variables (8): hx, sz, age, ekg, hg, wt, sg, pf
#>
#> Step 2 - All Interaction Candidates:
#> ------------------------------------
#>
#> Grouping variable : rx
#> Selection criterion : AIC
#> Selection rule : AIC reduction > 2.000
#>
#> variable type AIC_main AIC_interaction dAIC fp_powers_main
#> age FP2 3715.930 3712.248 3.681 (3, 3)
#>
#> FP powers by group level:
#> variable type Group Group level fp_powers_int
#> age FP2 rx Placebo (3, 3)
#> age FP2 rx Treatment (3, 3)
#>
#> Step 3 - Interaction Summary:
#> -----------------------------
#> variable type dAIC selected
#> age FP2 3.681 YesAIC selects a slightly different adjustment model (eight variables) but the same interaction, with AIC lower by 3.681 (paper, Section 4.4).
Several continuous variables can be tested at once. Here age uses FP2
and the other six use the default FP1.
p_adjust_method = "holm" adjusts the p-values for the seven
tests (paper, Section 2.6).
fit_multiple_interactions <- mfpi(
Surv(survtime, cens) ~
rx + fp(age) + fp(wt) + fp(sbp) + fp(dbp) +
fp(sz) + fp(ap) + fp(hg) + fp(sg) +
pf + hx + bm + stage + ekg,
data = advanced_prostate_cancer,
family = "cox",
group_var = "rx",
interaction_vars = c("age", "wt", "sbp", "dbp", "sz", "ap", "hg"),
interaction_forms = c(age = "fp2"),
flex = "flex1",
ties = "breslow",
p_adjust_method = "holm",
verbose = FALSE
)
print(fit_multiple_interactions)
#> =========================================
#> MFPI | n = 475 (events = 338) | flex1
#> =========================================
#>
#> Call:
#> mfpi(formula = Surv(survtime, cens) ~ rx + fp(age) + fp(wt) +
#> fp(sbp) + fp(dbp) + fp(sz) + fp(ap) + fp(hg) + fp(sg) + pf +
#> hx + bm + stage + ekg, data = advanced_prostate_cancer, group_var = "rx",
#> interaction_vars = c("age", "wt", "sbp", "dbp", "sz", "ap",
#> "hg"), interaction_forms = c(age = "fp2"), flex = "flex1",
#> family = "cox", ties = "breslow", p_adjust_method = "holm",
#> verbose = FALSE)
#>
#> Model: Cox proportional hazards
#>
#>
#> Step 1 - Selected Adjustment Model (MFP):
#> -----------------------------------------
#> df_initial select alpha searched_fp df_final power1 power2
#> hx 1 0.050 0.050 no 1 1 NA
#> sz 4 0.050 0.050 no 1 1 NA
#> age 4 0.050 0.050 yes 4 3 3
#> ekg 1 0.050 0.050 no 1 1 NA
#> wt 4 0.050 0.050 no 1 1 NA
#> pf 1 0.050 0.050 no 1 1 NA
#> stage 1 0.050 0.050 no 1 1 NA
#>
#> Selected adjustment variables (7): hx, sz, age, ekg, wt, pf, stage
#>
#> Step 2 - All Interaction Candidates:
#> ------------------------------------
#>
#> Grouping variable : rx
#> Selection criterion : p-value
#> Selection rule : adjusted p-value < 0.050
#> P-value adjustment : holm
#>
#> variable type deviance_int deviance_diff df_int p-value fp_powers_main
#> age FP2 3706.659 7.203 2 0.027 (3, 3)
#> wt FP1 3712.079 0.190 1 0.663 (-2)
#> sbp FP1 3713.361 0.395 1 0.529 (3)
#> dbp FP1 3713.527 0.178 1 0.673 (-0.5)
#> sz FP1 3710.649 3.213 1 0.073 (1)
#> ap FP1 3706.813 3.877 1 0.049 (-2)
#> hg FP1 3706.688 0.955 1 0.329 (-2)
#>
#> FP powers by group level:
#> variable type Group Group level fp_powers_int
#> age FP2 rx Placebo (3, 3)
#> age FP2 rx Treatment (3, 3)
#> wt FP1 rx Placebo (-2)
#> wt FP1 rx Treatment (-2)
#> sbp FP1 rx Placebo (3)
#> sbp FP1 rx Treatment (3)
#> dbp FP1 rx Placebo (-0.5)
#> dbp FP1 rx Treatment (-0.5)
#> sz FP1 rx Placebo (1)
#> sz FP1 rx Treatment (1)
#> ap FP1 rx Placebo (-2)
#> ap FP1 rx Treatment (-2)
#> hg FP1 rx Placebo (-2)
#> hg FP1 rx Treatment (-2)
#>
#> Step 3 - Interaction Summary:
#> -----------------------------
#> variable type p_raw p_adjusted selected
#> age FP2 0.027 0.191 No
#> wt FP1 0.663 1.000 No
#> sbp FP1 0.529 1.000 No
#> dbp FP1 0.673 1.000 No
#> sz FP1 0.073 0.365 No
#> ap FP1 0.049 0.294 No
#> hg FP1 0.329 1.000 NoStep 3 now shows raw and adjusted p-values. Age (raw p = 0.027) and
ap (raw p = 0.049) are below 0.05 before adjustment, but no
interaction is selected after Holm adjustment (paper, Section 4.5).
The adjustment model can be prespecified instead of selected:
fp(x, df = 1) gives a linear adjustment effect, with no
FP power search.keep forces the named variables into the adjustment
model.fit_fixed_linear <- mfpi(
Surv(survtime, cens) ~
rx +
fp(age, df = 1) + fp(wt, df = 1) + fp(sbp, df = 1) +
fp(dbp, df = 1) + fp(sz, df = 1) + fp(ap, df = 1) +
fp(hg, df = 1) + fp(sg, df = 1) +
pf + hx + bm + stage + ekg,
data = advanced_prostate_cancer,
family = "cox",
group_var = "rx",
interaction_vars = "age",
interaction_forms = c(age = "fp2"),
flex = "flex1",
ties = "breslow",
criterion = "pvalue",
keep = c("age", "wt", "sbp", "dbp", "sz", "ap", "hg", "sg",
"pf", "hx", "bm", "stage", "ekg")
)
#>
#> ======================================================================
#> STEP 1: Selected Adjustment Model (MFP)
#> ======================================================================
#>
#> Model: Cox proportional hazards
#> Criterion: p-value
#>
#> df_initial select alpha searched_fp df_final power1
#> hx 1 1.000 0.050 no 1 1
#> sz 1 1.000 0.050 no 1 1
#> age 1 1.000 0.050 no 1 1
#> ekg 1 1.000 0.050 no 1 1
#> hg 1 1.000 0.050 no 1 1
#> wt 1 1.000 0.050 no 1 1
#> sg 1 1.000 0.050 no 1 1
#> pf 1 1.000 0.050 no 1 1
#> bm 1 1.000 0.050 no 1 1
#> ap 1 1.000 0.050 no 1 1
#> dbp 1 1.000 0.050 no 1 1
#> sbp 1 1.000 0.050 no 1 1
#> stage 1 1.000 0.050 no 1 1
#>
#>
#> Selected adjustment variables (13): hx, sz, age, ekg, hg, wt, sg, pf, bm, ap, dbp, sbp, stage
#>
#> ======================================================================
#> STEP 2: Evaluating Interactions (flex = flex1)
#> ======================================================================
#>
#> Group variable: rx
#> Selection rule: p-value < 0.050
#> P-value adjustment: none
#>
#> ----------------------------------------------------------------------
#> [1/1] Variable: 'age' x 'rx'
#> Adjusting for: hx, sz, ekg, hg, wt, sg, pf, bm, ap, dbp, sbp, stage
#> ----------------------------------------------------------------------
#> Type deviance_int deviance_diff df pvalue main_power int_power
#> FP2 3694.694 8.004 2 0.018 (3, 3) (3, 3), (3, 3)
#>
#>
#> ======================================================================
#> STEP 3: Interaction Summary
#> ======================================================================
#> Variable Type p-value selected
#> ----------------------------------------------------------------------
#> age FP2 0.018 Yes
#> ----------------------------------------------------------------------
#>
#> Done: 1 of 1 interactions selected.
print(fit_fixed_linear)
#> =========================================
#> MFPI | n = 475 (events = 338) | flex1
#> =========================================
#>
#> Call:
#> mfpi(formula = Surv(survtime, cens) ~ rx + fp(age, df = 1) +
#> fp(wt, df = 1) + fp(sbp, df = 1) + fp(dbp, df = 1) + fp(sz,
#> df = 1) + fp(ap, df = 1) + fp(hg, df = 1) + fp(sg, df = 1) +
#> pf + hx + bm + stage + ekg, data = advanced_prostate_cancer,
#> group_var = "rx", interaction_vars = "age", interaction_forms = c(age = "fp2"),
#> flex = "flex1", family = "cox", criterion = "pvalue", keep = c("age",
#> "wt", "sbp", "dbp", "sz", "ap", "hg", "sg", "pf", "hx",
#> "bm", "stage", "ekg"), ties = "breslow")
#>
#> Model: Cox proportional hazards
#>
#>
#> Step 1 - Selected Adjustment Model (MFP):
#> -----------------------------------------
#> df_initial select alpha searched_fp df_final power1
#> hx 1 1.000 0.050 no 1 1
#> sz 1 1.000 0.050 no 1 1
#> age 1 1.000 0.050 no 1 1
#> ekg 1 1.000 0.050 no 1 1
#> hg 1 1.000 0.050 no 1 1
#> wt 1 1.000 0.050 no 1 1
#> sg 1 1.000 0.050 no 1 1
#> pf 1 1.000 0.050 no 1 1
#> bm 1 1.000 0.050 no 1 1
#> ap 1 1.000 0.050 no 1 1
#> dbp 1 1.000 0.050 no 1 1
#> sbp 1 1.000 0.050 no 1 1
#> stage 1 1.000 0.050 no 1 1
#>
#> Selected adjustment variables (13): hx, sz, age, ekg, hg, wt, sg, pf, bm, ap, dbp, sbp, stage
#>
#> Step 2 - All Interaction Candidates:
#> ------------------------------------
#>
#> Grouping variable : rx
#> Selection criterion : p-value
#> Selection rule : p-value < 0.050
#> P-value adjustment : none
#>
#> variable type deviance_int deviance_diff df_int p-value fp_powers_main
#> age FP2 3694.694 8.004 2 0.018 (3, 3)
#>
#> FP powers by group level:
#> variable type Group Group level fp_powers_int
#> age FP2 rx Placebo (3, 3)
#> age FP2 rx Treatment (3, 3)
#>
#> Step 3 - Interaction Summary:
#> -----------------------------
#> variable type p-value selected
#> age FP2 0.018 Yesfp(age, df = 1) affects only the adjustment model. The
interaction with treatment is still FP2, as set by
interaction_forms. To use a known transformation in the
adjustment model (for example, log(x)), create the
transformed variable first and enter it with df = 1 (paper,
Section 4.6).
group_var may have more than two levels. Each
non-reference group is compared with the reference group, and
plot() and predict() return one comparison per
non-reference group (paper, Section 4.7).
mfpi() also accepts a predictor data frame
x and a response y instead of a formula.
Columns are matched by name, so their order does not matter (paper,
Appendix A).
y <- with(advanced_prostate_cancer, Surv(survtime, cens))
x <- advanced_prostate_cancer[, c(
"rx", "age", "wt", "sbp", "dbp", "sz", "ap", "hg", "sg",
"pf", "hx", "bm", "stage", "ekg"
)]The unadjusted analysis passes only rx and
age, and gives the same results as
fit_unadjusted:
fit_unadjusted_xy <- mfpi(
x = x[, c("rx", "age")],
y = y,
family = "cox",
group_var = "rx",
interaction_vars = "age",
interaction_forms = c(age = "fp2"),
flex = "flex1",
ties = "breslow"
)
#>
#> ======================================================================
#> STEP 1: Selected Adjustment Model (MFP)
#> ======================================================================
#>
#> Model: Cox proportional hazards
#> Criterion: p-value
#>
#> df_initial select alpha searched_fp df_final power1
#> age 4 0.050 0.050 yes 2 3
#>
#>
#> Selected adjustment variables (1): age
#>
#> ======================================================================
#> STEP 2: Evaluating Interactions (flex = flex1)
#> ======================================================================
#>
#> Group variable: rx
#> Selection rule: p-value < 0.050
#> P-value adjustment: none
#>
#> ----------------------------------------------------------------------
#> [1/1] Variable: 'age' x 'rx'
#> Adjusting for: (none)
#> ----------------------------------------------------------------------
#> Type deviance_int deviance_diff df pvalue main_power int_power
#> FP2 3766.216 9.746 2 0.008 (3, 3) (3, 3), (3, 3)
#>
#>
#> ======================================================================
#> STEP 3: Interaction Summary
#> ======================================================================
#> Variable Type p-value selected
#> ----------------------------------------------------------------------
#> age FP2 0.008 Yes
#> ----------------------------------------------------------------------
#>
#> Done: 1 of 1 interactions selected.
print(fit_unadjusted_xy)
#> =========================================
#> MFPI | n = 475 (events = 338) | flex1
#> =========================================
#>
#> Call:
#> mfpi(x = x[, c("rx", "age")], y = y, group_var = "rx", interaction_vars = "age",
#> interaction_forms = c(age = "fp2"), flex = "flex1", family = "cox",
#> ties = "breslow")
#>
#> Model: Cox proportional hazards
#>
#>
#> Step 1 - Selected Adjustment Model (MFP):
#> -----------------------------------------
#> df_initial select alpha searched_fp df_final power1
#> age 4 0.050 0.050 yes 2 3
#>
#> Selected adjustment variables (1): age
#>
#> Step 2 - All Interaction Candidates:
#> ------------------------------------
#>
#> Grouping variable : rx
#> Selection criterion : p-value
#> Selection rule : p-value < 0.050
#> P-value adjustment : none
#>
#> variable type deviance_int deviance_diff df_int p-value fp_powers_main
#> age FP2 3766.216 9.746 2 0.008 (3, 3)
#>
#> FP powers by group level:
#> variable type Group Group level fp_powers_int
#> age FP2 rx Placebo (3, 3)
#> age FP2 rx Treatment (3, 3)
#>
#> Step 3 - Interaction Summary:
#> -----------------------------
#> variable type p-value selected
#> age FP2 0.008 YesPassing all columns of x makes every other variable a
candidate for the adjustment model. The following calls give the same
results as the adjusted, AIC and multiple-candidate formula fits
above:
fit_adjusted_xy <- mfpi(
x = x, y = y, family = "cox",
group_var = "rx", interaction_vars = "age",
interaction_forms = c(age = "fp2"), flex = "flex1", ties = "breslow"
)
fit_aic_xy <- mfpi(
x = x, y = y, family = "cox",
group_var = "rx", interaction_vars = "age",
interaction_forms = c(age = "fp2"), flex = "flex1", ties = "breslow",
criterion = "aic", min_improvement = 2
)
fit_multiple_xy <- mfpi(
x = x, y = y, family = "cox",
group_var = "rx",
interaction_vars = c("age", "wt", "sbp", "dbp", "sz", "ap", "hg"),
interaction_forms = c(age = "fp2"), flex = "flex1", ties = "breslow",
p_adjust_method = "holm", verbose = FALSE
)Without a formula, settings that fp() would set are
given as named vectors. For the fixed linear adjustment model,
df sets each continuous variable to linear and
keep forces all variables in:
fit_fixed_linear_xy <- mfpi(
x = x, y = y, family = "cox",
group_var = "rx", interaction_vars = "age",
interaction_forms = c(age = "fp2"), flex = "flex1", ties = "breslow",
criterion = "pvalue",
df = c(age = 1, wt = 1, sbp = 1, dbp = 1,
sz = 1, ap = 1, hg = 1, sg = 1),
keep = c("age", "wt", "sbp", "dbp", "sz", "ap", "hg", "sg",
"pf", "hx", "bm", "stage", "ekg")
)Centering (center, center_type) works the
same in both interfaces. It does not change the fitted values, the
difference function, the selected powers or the p-values (paper,
Appendix A.7).
See ?mfpi, ?predict.mfpi and
?plot.mfpi for all arguments. The paper describes the MFPI
methodology (Section 2), the software interface and data requirements
(Section 3), the application in more detail (Section 4) and a comparison
with the Stata mfpi command (Section 5 and Appendix B).
Kipruto, E., Royston, P., and Sauerbrei, W. Multivariable Fractional Polynomial Interaction Models in R. Manuscript submitted to the Journal of Statistical Software.