# Confounding and Interaction

Confounding asks whether adjustment changes the exposure effect. Interaction asks whether the exposure effect differs across another variable.

These checks are screening aids for viewing and organising results. Use DAGs, subject-matter knowledge, and study design to decide which variables are confounders or effect modifiers. Automated change-in-estimate and interaction checks should not be used as the sole basis for model adjustment.

## Which Function Should I Use?

Question | Use | Why
--- | --- | ---
Could these candidate variables be confounders or effect modifiers? | `identify_confounder()` | Screens crude, adjusted, Mantel-Haenszel, and interaction signals together.
Does this planned interaction term improve the model? | `interaction_models()` | Compares models with and without `exposure:effect_modifier` using LRT or Wald tests.
Which automatically generated candidate model has the strongest fit? | `select_models()` | Fits and ranks a defined set of candidate models using model-fit statistics.
How do my named, fitted gtregression models differ? | `compare_models()` | Compares supplied fitted models, checks analysis-sample consistency, and reports fit statistics transparently.
Could part of an exposure-outcome association operate through a mediator? | `mediation_analysis()` | Estimates direct, indirect, total, and proportion mediated effects with explicit causal caveats.
Do I need a Mantel-Haenszel estimate? | `identify_confounder(method = "mh")` or `identify_confounder(method = "both")` | MH is a stratified pooled estimate for eligible binary/categorical settings, not a formal interaction-term test.

A practical workflow is:

1. Use DAGs, prior literature, and study design to list important variables.
2. Use `identify_confounder()` to organise screening evidence for candidate confounders or effect modifiers.
3. Use `interaction_models()` when you have a planned interaction hypothesis.
4. Use `mediation_analysis()` when the mediator is part of a planned causal question and the temporal order is defensible.
5. If interaction is important, consider stratified reporting with `stratified_uni_reg()` or `stratified_multi_reg()`.

## How the Interaction Tests Differ

`identify_confounder()` is a candidate-by-candidate screening aid. For each exposure and potential confounder, it compares `outcome ~ exposure` with `outcome ~ exposure + potential_confounder`, examines stratum-specific estimates, and screens the candidate interaction:

    outcome ~ exposure + potential_confounder + exposure:potential_confounder

No other covariates are included in this interaction screen. Its interaction p-value helps organise early evidence; it is not a final covariate-adjusted interaction analysis.

Use `interaction_models()` for a planned interaction analysis. Its `covariates` are included in both nested models, preserving a common adjusted analysis sample. For example, `covariates = c(age, lwt)` fits:

    outcome ~ exposure + effect_modifier + age + lwt
    outcome ~ exposure + effect_modifier + age + lwt + exposure:effect_modifier

Choose covariates using the study design, a DAG, and subject-matter knowledge. Do not automatically adjust for mediators or colliders.

The formatted interaction table labels the effect modifier tested, displays the outcome, and records the two fitted model formulas in its table notes.

    library(gtregression)
    library(dplyr)

    data("data_birthwt", package = "gtregression")

    birthwt_data <- data_birthwt |>
      mutate(
        race = factor(race, levels = c(1, 2, 3),
                      labels = c("White", "Black", "Other")),
        smoke = factor(smoke, levels = c(0, 1), labels = c("No", "Yes")),
        ht = factor(ht, levels = c(0, 1), labels = c("No", "Yes")),
        low = factor(low, levels = c(0, 1), labels = c("Normal BW", "Low BW"))
      )

    attr(birthwt_data$race, "label") <- "Maternal race"
    attr(birthwt_data$smoke, "label") <- "Smoking during pregnancy"
    attr(birthwt_data$ht, "label") <- "Hypertension"

## Identify Confounders

Use `method = "change"` for the model-based change-in-estimate method. Use `method = "mh"` or `method = "both"` when Mantel-Haenszel is appropriate. The output is intentionally tidy and intended for viewing, not as a final publication table. Calling the object displays the formatted table; use `$summary` when you want the underlying tibble. Its table notes identify the outcome and the three candidate-level models: crude, adjusted for the potential confounder, and the interaction screen.

    confounder_check <- identify_confounder(
      data = birthwt_data,
      outcome = low,
      exposure = smoke,
      potential_confounder = c("race", "ht"),
      approach = logit,
      method = both,
      format = gt
    )

    confounder_check$table

Exposure | Potential confounder | Crude estimate | Adjusted estimate | MH estimate | % change model | % change MH | Confounder? | Interaction p | Effect modifier? | Decision | Recommendation
--- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | ---
smoke | race | 2.022 | 3.053 | 3.086 | 50.98 | 52.64 | Yes | 0.206 | No | Confounder | Adjust for race.
smoke | ht | 2.022 | 2.038 | 2.032 | 0.78 | 0.51 | No | 0.607 | No | No evidence | No clear statistical evidence to include ht as a confounder.
Outcome: low. For each exposure E and potential confounder C, models tested: low ~ E; low ~ E + C; and interaction screen: low ~ E + C + E:C (OR (95% CI) scale).
Screening aid only; use DAGs, subject-matter knowledge, and study design to decide confounding and effect modification.

The underlying summary remains available for inspection or further filtering.

    confounder_check$summary

    ## # A tibble: 2 × 13
    ##   exposure candidate crude_est adjusted_est mh_est percent_change
    ##   <chr>    <chr>         <dbl>        <dbl>  <dbl>          <dbl>
    ## 1 smoke    race           2.02         3.05   3.09          51.0
    ## 2 smoke    ht             2.02         2.04   2.03           0.78
    ## # ℹ 7 more variables: percent_change_model <dbl>, percent_change_mh <dbl>,
    ## #   is_confounder <lgl>, interaction_p <dbl>, is_effect_modifier <lgl>,
    ## #   decision <chr>, recommendation <chr>

## Mantel-Haenszel Estimate

Mantel-Haenszel is useful when the question is whether a stratified pooled estimate differs meaningfully from the crude estimate. It is available for eligible binary/categorical settings. It is not the same as fitting an interaction term.

    identify_confounder(
      data = birthwt_data,
      outcome = low,
      exposure = smoke,
      potential_confounder = race,
      approach = logit,
      method = mh,
      format = flextable
    )$table

Exposure | Potential confounder | Crude estimate | Adjusted estimate | MH estimate | % change model | % change MH | Confounder? | Interaction p | Effect modifier? | Decision | Recommendation
--- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | ---
smoke | race | 2.022 | 3.053 | 3.086 | 50.98 | 52.64 | Yes | 0.206 | No | Confounder | Adjust for race.
Outcome: low. For each exposure E and potential confounder C, models tested: low ~ E; low ~ E + C; and interaction screen: low ~ E + C + E:C (OR (95% CI) scale).
Screening aid only; use DAGs, subject-matter knowledge, and study design to decide confounding and effect modification.

## Test Interaction

`interaction_models()` compares models with and without the interaction term. It is deliberately model-based and uses `LRT` or `Wald`, not Mantel-Haenszel. Use it when the interaction term is planned or supported by clinical, causal, or subject-matter reasoning. Covariates are included in both models, so this is the appropriate function for an adjusted interaction test.

    interaction_check <- interaction_models(
      data = birthwt_data,
      outcome = low,
      exposure = smoke,
      effect_modifier = race,
      covariates = c("age", "lwt"),
      approach = logit,
      test = LRT,
      format = gt
    )

    interaction_check$table

Interaction screening
Outcome | Exposure | Effect modifier tested | Adjustment covariates | Approach | Test | p-value | Alpha | Interaction? | Decision | Interpretation
--- | --- | --- | --- | --- | --- | --- | --- | --- | --- | ---
low | smoke | race | age, lwt | logit | Likelihood Ratio Test | 0.319 | 0.050 | No | No interaction | No statistical evidence of interaction between smoke and race at alpha = 0.05.
Models tested: low ~ smoke + race + age + lwt; low ~ smoke + race + age + lwt + smoke:race.
Screening aid only; interaction decisions should be interpreted with subject-matter knowledge, study design, and stratum-specific estimates.

## Survival Confounding and Interaction

The same grammar works for survival models. For `cox` and `surv`/`survreg`, use `time` and `event` instead of `outcome`. Cox models report hazard-ratio style estimates; parametric survival models report time-ratio style estimates. Mantel-Haenszel screening is for binary outcome settings, so survival examples use model-based change-in-estimate and interaction checks.

    lung_data <- data_lungcancer |>
      dplyr::mutate(
        trt = factor(trt, levels = c(1, 2),
                     labels = c("Standard treatment", "Test treatment")),
        prior = factor(prior, levels = c(0, 10), labels = c("No", "Yes"))
      )

    survival_confounder <- identify_confounder(
      data = lung_data,
      time = time,
      event = status,
      exposure = trt,
      potential_confounder = prior,
      approach = cox,
      method = change,
      format = gt
    )

    survival_confounder$table

Exposure | Potential confounder | Crude estimate | Adjusted estimate | MH estimate | % change model | % change MH | Confounder? | Interaction p | Effect modifier? | Decision | Recommendation
--- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | ---
trt | prior | 1.018 | 1.026 |  | 0.84 |  | No |  | No | No evidence | No clear statistical evidence to include prior as a confounder.
Outcome: Surv(time, status). For each exposure E and potential confounder C, models tested: Surv(time, status) ~ E; Surv(time, status) ~ E + C; and interaction screen: Surv(time, status) ~ E + C + E:C (HR (95% CI) scale).
Screening aid only; use DAGs, subject-matter knowledge, and study design to decide confounding and effect modification.

    survival_interaction <- interaction_models(
      data = lung_data,
      time = time,
      event = status,
      exposure = trt,
      effect_modifier = prior,
      covariates = c(age, karno),
      approach = cox,
      test = LRT,
      format = gt
    )

    survival_interaction$table

Interaction screening
Outcome | Exposure | Effect modifier tested | Adjustment covariates | Approach | Test | p-value | Alpha | Interaction? | Decision | Interpretation
--- | --- | --- | --- | --- | --- | --- | --- | --- | --- | ---
time/status | trt | prior | age, karno | cox | Likelihood Ratio Test | 0.068 | 0.050 | No | No interaction | No statistical evidence of interaction between trt and prior at alpha = 0.05.
Models tested: survival::Surv(time, status) ~ trt + prior + age + karno; survival::Surv(time, status) ~ trt + prior + age + karno + trt * prior.
Screening aid only; interaction decisions should be interpreted with subject-matter knowledge, study design, and stratum-specific estimates.

## Causal Mediation

`mediation_analysis()` asks whether part of an exposure-outcome association may operate through a mediator. In this example, obesity is the exposure, plasma glucose is the mediator, and diabetes is the outcome. The question is not only “is obesity associated with diabetes?”, but also “how much of that association may operate through plasma glucose?”.

The default output is a publication-ready flextable. The table reports:

- Total effect: the overall exposure-outcome association.
- Direct effect: the part not operating through the mediator.
- Indirect effect: the part operating through the mediator.
- Proportion mediated: the share of the total effect attributed to the indirect pathway.

For logistic outcomes, effects are reported as predicted probability differences, not odds ratios. For example, an estimate of `0.068` means about a 6.8 percentage-point difference on the predicted probability scale.

Treat mediation output as causal only when the usual mediation assumptions are supported by study design, DAGs, temporality, and subject-matter knowledge. In particular, there should be no unmeasured exposure-outcome, exposure-mediator, or mediator-outcome confounding, and the mediator should occur before the outcome.

    data("data_diabetes_mediation", package = "gtregression")

    diabetes_med <- mediation_analysis(
      data = data_diabetes_mediation,
      exposure = obesity,
      mediator = glucose,
      outcome = diabetes,
      covariates = c(age, blood_pressure, pregnancies, diabetes_pedigree),
      outcome_approach = logit,
      sims = 100,
      seed = 123
    )

    diabetes_med

    ## a flextable object.
    ## col_keys: `Effect`, `Estimate`, `95% CI`, `p-value`, `Interpretation`
    ## header has 1 row(s)
    ## body has 4 row(s)
    ## original dataset sample:
    ## 'data.frame':    4 obs. of  5 variables:
    ##  $ Effect        : chr  "Total effect" "Direct effect" "Indirect effect" "Proportion mediated"
    ##  $ Estimate      : chr  "0.226" "0.176" "0.051" "0.224"
    ##  $ 95% CI        : chr  "0.163 to 0.291" "0.114 to 0.235" "0.019 to 0.087" "0.087 to 0.382"
    ##  $ p-value       : chr  "<0.001" "<0.001" "<0.001" "<0.001"
    ##  $ Interpretation: chr  "Overall exposure-outcome association" "Association not through the mediator" "Association through the mediator" "Share of total effect through the mediator"
    ##  - attr(*, "variable_labels")= Named chr [1:7] "Obesity" "Plasma glucose" "Diabetes" "Age" ...
    ##   ..- attr(*, "names")= chr [1:7] "obesity" "glucose" "diabetes" "age" ...

The underlying values remain available for audit, reporting, or custom formatting.

    diabetes_med$table_body

    ##                effect              Effect   estimate   conf.low conf.high
    ## total           total        Total effect 0.22624186 0.16258768 0.2907013
    ## direct         direct       Direct effect 0.17561583 0.11427535 0.2347698
    ## indirect     indirect     Indirect effect 0.05062603 0.01899140 0.0870712
    ## proportion proportion Proportion mediated 0.22376950 0.08741395 0.3821232
    ##            p.value                             Interpretation
    ## total            0       Overall exposure-outcome association
    ## direct           0       Association not through the mediator
    ## indirect         0           Association through the mediator
    ## proportion       0 Share of total effect through the mediator

Use `format = gt` when preparing HTML-first outputs such as websites or teaching pages.

    med_gt <- mediation_analysis(
      data = data_diabetes_mediation,
      exposure = obesity,
      mediator = glucose,
      outcome = diabetes,
      covariates = c(age, blood_pressure, pregnancies, diabetes_pedigree),
      outcome_approach = logit,
      format = gt,
      sims = 100,
      seed = 123
    )

    med_gt$table

Effect | Estimate | 95% CI | p-value | Interpretation
--- | --- | --- | --- | ---
Total effect | 0.226 | 0.163 to 0.291 | <0.001 | Overall exposure-outcome association
Direct effect | 0.176 | 0.114 to 0.235 | <0.001 | Association not through the mediator
Indirect effect | 0.051 | 0.019 to 0.087 | <0.001 | Association through the mediator
Proportion mediated | 0.224 | 0.087 to 0.382 | <0.001 | Share of total effect through the mediator
Effects are predicted probability differences from logistic outcome models.
Comparison: Obesity = Yes vs No; mediator = Plasma glucose; outcome = Diabetes. Bootstrap replicates = 100.
Adjusted for Age, Diastolic blood pressure, Number of pregnancies, Diabetes pedigree function.
Causal interpretation requires DAG-supported no-unmeasured-confounding and correct temporal-order assumptions.

`plot_mediation()` draws the same planned causal structure as a path diagram. It is useful for teaching, presentations, or checking that the exposure, mediator, and outcome have been specified as intended.

    plot_mediation(diabetes_med)

If the estimates make the plot too busy, hide them and use the diagram only to show the assumed path structure.

    plot_mediation(diabetes_med, show_estimates = FALSE)

## Overlap and Difference

Topic | `identify_confounder()` | `interaction_models()`
--- | --- | ---
Main purpose | Organises candidate confounder and effect-modifier screening signals. | Tests a planned exposure-by-modifier term.
Typical input | Exposure plus one or more candidate variables. | One exposure and one effect modifier.
Confounding | Crude vs adjusted change-in-estimate; optional Mantel-Haenszel comparison. | Not designed for confounder selection.
Effect modification | Screening signal from stratum-specific estimates and interaction p-value. | Model comparison using LRT or Wald test.
Best use | Early review of candidate variables, with DAGs and judgement. | Focused test of a clinically or biologically plausible interaction.
Output status | Viewing aid, not publication-ready evidence by itself. | Viewing aid; report with stratum-specific estimates when relevant.

`mediation_analysis()` is different from both functions. It decomposes one planned exposure-outcome relationship into direct and mediator-related components; it does not select confounders or test whether effects differ across strata.

## What To Inspect

- `identify_confounder()`: `$summary`, `$table`, `$details`, `$mh_estimate`, `$mh_status`, `$decision`, and `$recommendation`.
- `interaction_models()`: `$summary`, `$table`, `$p_value`, `$decision`, and fitted model objects.
- `mediation_analysis()`: `$table`, `$table_body`, `$models`, `$boot`, `$values`, `$variable_labels`, and `$complete_data`.
- Use subject-matter knowledge with these outputs. The functions support interpretation; they do not replace the study design.