---
title: "Diagnostics and Model Selection"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Diagnostics and Model Selection}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

Before the final table goes into a manuscript, check the model. These helpers
keep diagnostics close to the regression workflow. They are screening aids:
interpret them with the study design, clinical or subject-matter judgement, and
the diagnostics from the fitted model.

```{r diag-setup, message=FALSE, warning=FALSE}
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")),
    ui = factor(ui, levels = c(0, 1), labels = c("No", "Yes")),
    low = factor(low, levels = c(0, 1), labels = c("Normal BW", "Low BW")),
    ptl_cat = factor(ifelse(ptl > 0, "Yes", "No"), levels = c("No", "Yes"))
  )

exposures <- c("age", "lwt", "race", "smoke", "ht", "ui", "ptl_cat")
```

## Convergence Screening

Use `check_convergence()` before interpreting model estimates, especially for
log-binomial and small or sparse binary-outcome models. A non-converged model is
a fitting warning, not a finding.

```{r diag-convergence, message=FALSE, warning=FALSE}
check_convergence(
  data = birthwt_data,
  exposures = exposures,
  outcome = low,
  approach = logit,
  multivariate = TRUE
)
```

For risk-ratio workflows, this same check helps users decide whether a
log-binomial model fitted cleanly or whether a robust Poisson approach may be a
more practical sensitivity analysis.

```{r diag-convergence-logbin, message=FALSE, warning=FALSE}
check_convergence(
  data = birthwt_data,
  exposures = c("smoke", "ht", "ui", "ptl_cat"),
  outcome = low,
  approach = logbinomial,
  multivariate = TRUE
)
```

## Collinearity Screening

`check_collinearity()` reports VIF-style diagnostics for multivariable models.
High VIF values are prompts to inspect coding, overlap between predictors, and
the scientific purpose of the model.

```{r diag-collinearity, message=FALSE, warning=FALSE}
birthwt_multi <- multi_reg(
  data = birthwt_data,
  outcome = low,
  exposures = exposures,
  approach = logit
)

check_collinearity(birthwt_multi, format = gt)
```

Adjusted-mode `multi_reg()` objects contain one model per exposure. The
collinearity output keeps that list structure so each model can be inspected
separately.

```{r diag-collinearity-adjusted, message=FALSE, warning=FALSE}
birthwt_adjusted <- multi_reg(
  data = birthwt_data,
  outcome = low,
  exposures = c("smoke", "ht", "ui", "ptl_cat"),
  adjust_for = c("age", "lwt", "race"),
  approach = logit
)

check_collinearity(birthwt_adjusted, format = tibble)
```

## Model Fit Plots

`plot_model_fit()` turns fitted models into quick diagnostic plots. It accepts
raw `lm()` and `glm()` objects, and it also works with models saved inside
`uni_reg()` and `multi_reg()` results.

For logistic regression, the calibration plot compares predicted probabilities
with observed event proportions. Points close to the diagonal line suggest that
the model predictions are reasonably aligned with the observed data. Calibration
is usually most useful for multivariable models because predicted probabilities
vary across many people.

```{r diag-fit-logistic, message=FALSE, warning=FALSE}
plot_model_fit(
  birthwt_multi,
  type = calibration,
  bins = 6
)
```

When a `uni_reg()` object contains several models, use `model_name` to choose
the exposure you want to inspect. For a simple binary exposure, calibration may
only show two points because the model has only two fitted probabilities; in
that situation, residual and influence plots are usually more useful.

```{r diag-fit-uni, message=FALSE, warning=FALSE}
birthwt_uni <- uni_reg(
  data = birthwt_data,
  outcome = low,
  exposures = c("age", "lwt", "smoke"),
  approach = logit
)

plot_model_fit(
  birthwt_uni,
  model_name = smoke,
  type = residual
)
```

For logistic models, residual plots often form two visible bands. That is a
normal consequence of a 0/1 outcome and should be interpreted as a screening
plot rather than a linear-model residual plot.

For linear regression, `type = all` shows the classic residual, Q-Q,
scale-location, and Cook's distance views.

```{r diag-fit-linear, message=FALSE, warning=FALSE}
fit_lm <- lm(bwt ~ age + lwt, data = birthwt_data)
plot_model_fit(fit_lm)
```

## Proportional Hazards Screening

For Cox models, use `check_ph()` before treating hazard ratios as final. It
reports Schoenfeld residual tests from `survival::cox.zph()`, including a global
test. Small p-values suggest possible non-proportional hazards and should be
reviewed with plots, follow-up pattern, and clinical judgement.

```{r diag-ph-setup, message=FALSE, warning=FALSE}
data("data_lungcancer", package = "gtregression")

lung_data <- data_lungcancer |>
  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")),
    celltype = factor(
      celltype,
      levels = c("squamous", "smallcell", "adeno", "large"),
      labels = c("Squamous", "Small cell", "Adenocarcinoma", "Large cell")
    )
  )

cox_fit <- cox_reg(
  data = lung_data,
  time = time,
  event = status,
  exposures = c(trt, celltype, prior),
  adjust_for = c(age, karno)
)
```

```{r diag-ph-table, message=FALSE, warning=FALSE}
check_ph(cox_fit, format = gt)
```

Use `format = tibble` when you want to inspect or filter the diagnostic results.

```{r diag-ph-tibble, message=FALSE, warning=FALSE}
check_ph(cox_fit, transform = rank, format = tibble)
```

## Stepwise Model Selection

`compare_models()` is for prespecified candidate models that have already been
fitted with gtregression. It answers a different question from stepwise
selection: "How do these planned models compare?" The inputs should be
`multi_reg()`, `cox_reg()`, or `surv_reg()` outputs, not raw `lm()`, `glm()`,
`coxph()`, or `survreg()` objects. This keeps the workflow consistent with the
publication-ready tables created by the package.

```{r diag-compare-logit, message=FALSE, warning=FALSE}
logit_m0 <- multi_reg(
  data = birthwt_data,
  outcome = low,
  exposures = smoke,
  approach = logit
)

logit_m1 <- multi_reg(
  data = birthwt_data,
  outcome = low,
  exposures = c(smoke, age, lwt),
  approach = logit
)

logit_m2 <- multi_reg(
  data = birthwt_data,
  outcome = low,
  exposures = c(smoke, age, lwt, race, ht, ui),
  approach = logit
)

logit_model_comparison <- compare_models(
  logit_m0,
  logit_m1,
  logit_m2,
  model_names = c(
    "Smoking only",
    "Add age and weight",
    "Full clinical model"
  ),
  primary_exposure = smoke,
  format = gt
)

logit_model_comparison$table
```

The table reports N, number of parameters, AIC, BIC, log-likelihood, and
likelihood-ratio comparisons when `nested = TRUE`. Lower AIC or BIC identifies
better relative fit among the compared models. When `primary_exposure` is
supplied, the table also tracks that effect estimate and the percentage change
across models.

`compare_models()` automatically checks whether the candidate models appear to
use the same analysis sample. It uses retained row identifiers when the fitted
model stores them; otherwise it compares N and event counts. If the models use
different complete-case samples, the table still displays AIC, BIC,
log-likelihood, and likelihood-ratio statistics for transparency, but the footer
warns that these values should not be interpreted as formal model-selection
criteria across different datasets. In that situation, use the primary exposure
estimate, percentage change, confidence intervals, and clinical or
epidemiological reasoning to judge robustness.

### Build Candidates in the App

The function API intentionally accepts fitted gtregression objects. In the
gtregression app, **Advanced > Compare models** provides a guided layer before
that API: choose the shared outcome and approach, name two to six candidates,
and select each candidate's exposures, adjustment variables, and optional
interaction. The app fits `multi_reg()`, `cox_reg()`, or `surv_reg()` objects and
then calls `compare_models()` with those objects.

For the birth weight models above, enter *Smoking only*, *Add age and weight*,
and *Full clinical model* as the candidate names, place `smoke` in the exposure
control, and add the corresponding variables under adjustment. Select `smoke`
as the primary exposure to reproduce the estimate-change columns. The generated
code records every fitted model and is suitable for direct use in an R script.

Candidate order is deliberate. Put the simplest or primary model first because
percentage change is calculated from that model and likelihood-ratio statistics
compare sequential pairs. If the fitted samples differ, or a sequential pair is
not nested, the rendered table displays only the warning relevant to that
comparison. See the **gtregression App** article for the complete ordinary,
Cox, and parametric survival walkthrough.

For Cox and parametric survival models, fit the candidate models with `cox_reg()`
or `surv_reg()` first. `compare_models()` then keeps survival-specific columns
such as events and Cox concordance.

```{r diag-compare-cox, message=FALSE, warning=FALSE}
cox_m0 <- cox_reg(
  data = lung_data,
  time = time,
  event = status,
  exposures = trt
)

cox_m1 <- cox_reg(
  data = lung_data,
  time = time,
  event = status,
  exposures = trt,
  adjust_for = c(age, karno)
)

cox_m2 <- cox_reg(
  data = lung_data,
  time = time,
  event = status,
  exposures = c(trt, age, karno, celltype, prior),
  multivariable = TRUE
)

cox_model_comparison <- compare_models(
  list(
    "Treatment only" = cox_m0,
    "Add age and performance" = cox_m1,
    "Full clinical model" = cox_m2
  ),
  primary_exposure = trt,
  format = gt
)

cox_model_comparison$table
```

`select_models()` compares candidate models step by step. It is useful for
exploration, teaching, and sensitivity checks. It should not replace a planned
model based on study design or a causal framework.

```{r diag-select, message=FALSE, warning=FALSE}
selected <- select_models(
  data = birthwt_data,
  outcome = low,
  exposures = exposures,
  approach = logit,
  direction = forward
)

selected
```

The selected direction is recorded in the formatted table footer. Backward and
both-direction searches are available using the same interface.

```{r diag-select-other-directions, message=FALSE, warning=FALSE}
select_models(
  data = birthwt_data,
  outcome = low,
  exposures = exposures,
  approach = logit,
  direction = backward,
  format = tibble
)$results_table

select_models(
  data = birthwt_data,
  outcome = low,
  exposures = exposures,
  approach = logit,
  direction = both,
  format = tibble
)$results_table
```

## What To Inspect

- `check_convergence()`: convergence status and maximum fitted probabilities.
  Use `format = gt` or `format = flextable` for viewing tables.
- `check_collinearity()`: VIF and interpretation. Nested model outputs keep
  their list structure when formatted.
- `plot_model_fit()`: residual, calibration, observed-versus-predicted, and
  influence plots for `lm`/`glm` models and stored `uni_reg()` / `multi_reg()`
  fitted models.
- `check_ph()`: Schoenfeld residual proportional hazards tests for Cox models,
  including term-level and global tests.
- `compare_models()`: AIC, BIC, log-likelihood, likelihood-ratio tests, sample
  size, events for survival models, and optional primary-exposure tracking for
  gtregression candidate models.
- `select_models()`: `$results_table`, `$best_model`, `$all_models`, and
  `$direction`; `$table` is added when `format = gt` or `format = flextable`.
