# Regression Tables

Go from fitted models to publication-ready tables without hand-formatting effect estimates. `gtregression` supports logistic, log-binomial, Poisson, robust Poisson, negative binomial, Cox survival, parametric survival, and linear regression.

    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")),
        ftv_cat = factor(case_when(
          ftv == 0 ~ "None",
          ftv == 1 ~ "One",
          ftv >= 2 ~ "Two or more"
        ), levels = c("None", "One", "Two or more"))
      )

    birthwt_exposures <- c(
      "age", "lwt", "race", "smoke", "ht", "ui", "ptl_cat", "ftv_cat"
    )

    attr(birthwt_data$age, "label") <- "Maternal age"
    attr(birthwt_data$lwt, "label") <- "Maternal weight"
    attr(birthwt_data$race, "label") <- "Maternal race"
    attr(birthwt_data$smoke, "label") <- "Smoking during pregnancy"
    attr(birthwt_data$ht, "label") <- "Hypertension"
    attr(birthwt_data$ui, "label") <- "Uterine irritability"
    attr(birthwt_data$ptl_cat, "label") <- "Previous preterm labour"
    attr(birthwt_data$ftv_cat, "label") <- "First trimester visits"

## Univariable Models

`uni_reg()` fits one model per exposure and returns a table ready for reports. Variable labels set with `attr(x, "label")` or `labelled::var_label()` are used automatically in the displayed table, while raw column names remain in `$table_body`.

    birthwt_uni <- uni_reg(
      data = birthwt_data,
      outcome = "low",
      exposures = birthwt_exposures,
      approach = "logit",
      theme = clinical
    )

    birthwt_uni

    ## a flextable object.
    ## col_keys: `Characteristic`, `N`, `OR (95% CI)`, `p-value`
    ## header has 1 row(s)
    ## body has 22 row(s)
    ## original dataset sample:
    ## 'data.frame':    22 obs. of  4 variables:
    ##  $ Characteristic: chr  "Maternal age" "Maternal weight" "Maternal race" "White" ...
    ##  $ N             : chr  "189" "189" "189" "" ...
    ##  $ OR (95% CI)   : chr  "0.95 (0.89-1.01)" "0.99 (0.97-1.00)" "" "Ref." ...
    ##  $ p-value       : chr  "0.105" "0.023" "" "" ...

## Multivariable Models

With `adjust_for = NULL`, `multi_reg()` fits all supplied exposures in one multivariable model. This is the usual fully adjusted model when every exposure listed should appear in the same formula.

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

    birthwt_full

    ## a flextable object.
    ## col_keys: `Characteristic`, `Adjusted OR (95% CI)`, `p-value`
    ## header has 1 row(s)
    ## body has 15 row(s)
    ## original dataset sample:
    ## 'data.frame':    15 obs. of  3 variables:
    ##  $ Characteristic      : chr  "Maternal age" "Maternal weight" "Maternal race" "White" ...
    ##  $ Adjusted OR (95% CI): chr  "0.98 (0.92–1.05)" "0.98 (0.97–1.00)" "" "Ref." ...
    ##  $ p-value             : chr  "0.606" "0.018" "" "" ...

## Exposure-Specific Adjusted Models

Use `adjust_for` when you want one adjusted model per exposure, each adjusted for the same core covariate set. This is useful for screening several clinically important exposures while keeping the adjustment strategy explicit.

    birthwt_multi <- multi_reg(
      data = birthwt_data,
      outcome = low,
      exposures = c("smoke", "ht", "ui", "ptl_cat", "ftv_cat"),
      adjust_for = c("age", "lwt", "race"),
      approach = logit,
      theme = striped
    )

    birthwt_multi

    ## a flextable object.
    ## col_keys: `Characteristic`, `Adjusted OR (95% CI)`, `p-value`
    ## header has 1 row(s)
    ## body has 16 row(s)
    ## original dataset sample:
    ## 'data.frame':    16 obs. of  3 variables:
    ##  $ Characteristic      : chr  "Smoking during pregnancy" "No" "  Yes" "Hypertension" ...
    ##  $ Adjusted OR (95% CI): chr  "" "Ref." "2.87 (1.36–6.04)" "" ...
    ##  $ p-value             : chr  "" "" "0.006" "" ...

The adjustment variables are recorded in a compact table footnote, so the result is ready for manuscript-style reporting without making the table unnecessarily tall.

## Optional Model Statistics

Publication tables should stay readable. When you need model-fit information, set `model_stats = TRUE` and inspect the returned object’s `$model_stats` element. This keeps AIC, BIC, log-likelihood, deviance, pseudo R-squared, and linear-model R-squared values available without adding clutter to the main table.

    birthwt_uni_stats <- uni_reg(
      data = birthwt_data,
      outcome = low,
      exposures = birthwt_exposures,
      approach = logit,
      model_stats = TRUE
    )

    birthwt_uni_stats$model_stats

    ##     model      AIC      BIC    logLik deviance null_deviance  pseudo_r2
    ## 1     age 235.9120 242.3955 -115.9560 231.9120       234.672 0.01176126
    ## 2     lwt 232.6907 239.1742 -114.3453 228.6907       234.672 0.02548803
    ## 3    race 235.6616 245.3869 -114.8308 229.6616       234.672 0.02135051
    ## 4   smoke 233.8046 240.2881 -114.9023 229.8046       234.672 0.02074128
    ## 5      ht 234.6499 241.1334 -115.3249 230.6499       234.672 0.01713938
    ## 6      ui 233.5959 240.0794 -114.7979 229.5959       234.672 0.02163060
    ## 7 ptl_cat 225.8978 232.3812 -110.9489 221.8978       234.672 0.05443445
    ## 8 ftv_cat 238.0851 247.8103 -116.0425 232.0851       234.672 0.01102346
    ##   r_squared adj_r_squared   n
    ## 1        NA            NA 189
    ## 2        NA            NA 189
    ## 3        NA            NA 189
    ## 4        NA            NA 189
    ## 5        NA            NA 189
    ## 6        NA            NA 189
    ## 7        NA            NA 189
    ## 8        NA            NA 189

For adjusted mode, `multi_reg()` returns one row of statistics per adjusted exposure-specific model.

    birthwt_multi_stats <- multi_reg(
      data = birthwt_data,
      outcome = low,
      exposures = c("smoke", "ht", "ui", "ptl_cat"),
      adjust_for = c("age", "lwt", "race"),
      approach = logit,
      model_stats = TRUE
    )

    birthwt_multi_stats$model_stats

    ##     model      AIC      BIC    logLik deviance null_deviance  pseudo_r2
    ## 1   smoke 226.5772 246.0277 -107.2886 214.5772       234.672 0.08562914
    ## 2      ht 227.7487 247.1991 -107.8743 215.7487       234.672 0.08063739
    ## 3      ui 231.0194 250.4699 -109.5097 219.0194       234.672 0.06669985
    ## 4 ptl_cat 222.4421 241.8926 -105.2211 210.4421       234.672 0.10324998
    ##   r_squared adj_r_squared   n
    ## 1        NA            NA 189
    ## 2        NA            NA 189
    ## 3        NA            NA 189
    ## 4        NA            NA 189

## Other Effect Measures

Switch the `approach` to change the estimand.

    uni_reg(
      data = birthwt_data,
      outcome = low,
      exposures = c("smoke", "ht", "ui", "ptl_cat"),
      approach = logbinomial
    )

    ## a flextable object.
    ## col_keys: `Characteristic`, `N`, `RR (95% CI)`, `p-value`
    ## header has 1 row(s)
    ## body has 12 row(s)
    ## original dataset sample:
    ## 'data.frame':    12 obs. of  4 variables:
    ##  $ Characteristic: chr  "Smoking during pregnancy" "No" "  Yes" "Hypertension" ...
    ##  $ N             : chr  "189" "" "" "189" ...
    ##  $ RR (95% CI)   : chr  "" "Ref." "1.61 (1.06-2.44)" "" ...
    ##  $ p-value       : chr  "" "" "0.026" "" ...

Use `approach = firth` when a binary-outcome logistic model has sparse cells, very wide intervals, or separation concerns. The output remains an odds-ratio table, but the model is fitted with Firth penalized logistic regression.

The built-in `data_endometrial` dataset is a useful teaching example because neovascularization is completely absent among low-grade cases, a pattern that can make ordinary logistic regression unstable.

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

    endometrial_data <- data_endometrial |>
      mutate(
        HG = factor(HG, levels = c(0, 1),
                    labels = c("Low grade", "High grade")),
        NV = factor(NV, levels = c(0, 1), labels = c("Absent", "Present"))
      )

    multi_reg(
      data = endometrial_data,
      outcome = HG,
      exposures = c(NV, PI, EH),
      approach = firth
    )

    ## a flextable object.
    ## col_keys: `Characteristic`, `Adjusted OR (95% CI)`, `p-value`
    ## header has 1 row(s)
    ## body has 5 row(s)
    ## original dataset sample:
    ## 'data.frame':    5 obs. of  3 variables:
    ##  $ Characteristic      : chr  "NV" "Absent" "  Present" "PI" ...
    ##  $ Adjusted OR (95% CI): chr  "" "Ref." "18.71 (1.84–2,577.65)" "0.97 (0.88–1.04)" ...
    ##  $ p-value             : chr  "" "" "0.009" "0.387" ...

## Cox Survival Models

`cox_reg()` uses direct `time` and `event` arguments and returns hazard ratios. Without `adjust_for`, the table shows crude HRs. With `adjust_for`, the table shows adjusted HRs.

    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"))
      )

    attr(lung_data$trt, "label") <- "Treatment group"
    attr(lung_data$celltype, "label") <- "Cancer cell type"
    attr(lung_data$karno, "label") <- "Karnofsky performance score"
    attr(lung_data$age, "label") <- "Age"
    attr(lung_data$prior, "label") <- "Prior therapy"

    lung_hr <- cox_reg(
      data = lung_data,
      time = time,
      event = status,
      exposures = c("trt", "celltype", "karno", "age"),
      theme = clinical
    )

    lung_hr

    ## a flextable object.
    ## col_keys: `Characteristic`, `N`, `HR (95% CI)`, `p-value`
    ## header has 1 row(s)
    ## body has 10 row(s)
    ## original dataset sample:
    ## 'data.frame':    10 obs. of  4 variables:
    ##  $ Characteristic: chr  "Treatment group" "Standard treatment" "  Test treatment" "Cancer cell type" ...
    ##  $ N             : chr  "137" "" "" "137" ...
    ##  $ HR (95% CI)   : chr  "" "Ref." "1.02 (0.71–1.45)" "" ...
    ##  $ p-value       : chr  "" "" "0.922" "" ...

    lung_adj_hr <- cox_reg(
      data = lung_data,
      time = time,
      event = status,
      exposures = c(trt, celltype, prior),
      adjust_for = c(age, karno),
      model_stats = TRUE,
      theme = striped
    )

    lung_adj_hr

    ## a flextable object.
    ## col_keys: `Characteristic`, `Adjusted HR (95% CI)`, `p-value`
    ## header has 1 row(s)
    ## body has 11 row(s)
    ## original dataset sample:
    ## 'data.frame':    11 obs. of  3 variables:
    ##  $ Characteristic      : chr  "Treatment group" "Standard treatment" "  Test treatment" "Cancer cell type" ...
    ##  $ Adjusted HR (95% CI): chr  "" "Ref." "1.21 (0.84–1.74)" "" ...
    ##  $ p-value             : chr  "" "" "0.307" "" ...

    lung_adj_hr$model_stats

    ##      model      AIC      BIC    logLik concordance events   n
    ## 1      trt 973.7560 982.3121 -483.8780   0.7119491    128 137
    ## 2 celltype 961.0882 975.3484 -475.5441   0.7349500    128 137
    ## 3    prior 974.7459 983.3019 -484.3729   0.7134257    128 137

Use `interaction = exposure*modifier` for planned effect modification. In the default exposure-by-exposure workflow, provide one exposure.

    cox_interaction <- cox_reg(
      data = lung_data,
      time = time,
      event = status,
      exposures = trt,
      adjust_for = c(age, karno),
      interaction = trt*prior
    )

    cox_interaction

    ## a flextable object.
    ## col_keys: `Characteristic`, `Adjusted HR (95% CI)`, `p-value`
    ## header has 1 row(s)
    ## body has 4 row(s)
    ## original dataset sample:
    ## 'data.frame':    4 obs. of  3 variables:
    ##  $ Characteristic      : chr  "Treatment group" "Standard treatment" "  Test treatment" "  Treatment group: Test treatment x Prior therapy: Yes"
    ##  $ Adjusted HR (95% CI): chr  "" "Ref." "1.52 (0.98–2.36)" "0.47 (0.21–1.06)"
    ##  $ p-value             : chr  "" "" "0.061" "0.070"

## Parametric Survival Models

`surv_reg()` uses the same `time`, `event`, `exposures`, and `adjust_for` grammar as `cox_reg()`, but fits parametric survival models with `survival::survreg()`. The table reports time ratios rather than hazard ratios. A time ratio above 1 suggests longer survival time; below 1 suggests shorter survival time, conditional on the chosen distribution.

    lung_time_ratio <- surv_reg(
      data = lung_data,
      time = time,
      event = status,
      exposures = c("trt", "celltype", "karno", "age"),
      distribution = weibull,
      theme = clinical
    )

    lung_time_ratio

    ## a flextable object.
    ## col_keys: `Characteristic`, `N`, `Time Ratio (95% CI)`, `p-value`
    ## header has 1 row(s)
    ## body has 10 row(s)
    ## original dataset sample:
    ## 'data.frame':    10 obs. of  4 variables:
    ##  $ Characteristic     : chr  "Treatment group" "Standard treatment" "  Test treatment" "Cancer cell type" ...
    ##  $ N                  : chr  "137" "" "" "137" ...
    ##  $ Time Ratio (95% CI): chr  "" "Ref." "1.05 (0.70–1.58)" "" ...
    ##  $ p-value            : chr  "" "" "0.818" "" ...

    lung_adj_time_ratio <- surv_reg(
      data = lung_data,
      time = time,
      event = status,
      exposures = c(trt, celltype, prior),
      adjust_for = c(age, karno),
      distribution = lognormal,
      model_stats = TRUE,
      theme = striped
    )

    lung_adj_time_ratio

    ## a flextable object.
    ## col_keys: `Characteristic`, `Adjusted Time Ratio (95% CI)`, `p-value`
    ## header has 1 row(s)
    ## body has 11 row(s)
    ## original dataset sample:
    ## 'data.frame':    11 obs. of  3 variables:
    ##  $ Characteristic              : chr  "Treatment group" "Standard treatment" "  Test treatment" "Cancer cell type" ...
    ##  $ Adjusted Time Ratio (95% CI): chr  "" "Ref." "0.87 (0.60–1.27)" "" ...
    ##  $ p-value                     : chr  "" "" "0.468" "" ...

    lung_adj_time_ratio$model_stats

    ##      model distribution      AIC      BIC    logLik    scale events   n
    ## 1      trt    lognormal 1451.264 1465.864 -720.6318 1.110644    128 137
    ## 2 celltype    lognormal 1444.379 1464.819 -715.1894 1.064351    128 137
    ## 3    prior    lognormal 1451.768 1466.368 -720.8840 1.112741    128 137

    surv_interaction <- surv_reg(
      data = lung_data,
      time = time,
      event = status,
      exposures = trt,
      adjust_for = c(age, karno),
      interaction = trt*prior,
      distribution = weibull
    )

    surv_interaction

    ## a flextable object.
    ## col_keys: `Characteristic`, `Adjusted Time Ratio (95% CI)`, `p-value`
    ## header has 1 row(s)
    ## body has 4 row(s)
    ## original dataset sample:
    ## 'data.frame':    4 obs. of  3 variables:
    ##  $ Characteristic              : chr  "Treatment group" "Standard treatment" "  Test treatment" "  Treatment group: Test treatment x Prior therapy: Yes"
    ##  $ Adjusted Time Ratio (95% CI): chr  "" "Ref." "0.68 (0.44–1.04)" "2.29 (1.03–5.09)"
    ##  $ p-value                     : chr  "" "" "0.073" "0.042"

## Continuous Outcomes

Linear regression outputs beta coefficients and keeps diagnostics under `$reg_check`.

    birthwt_linear <- multi_reg(
      data = birthwt_data,
      outcome = bwt,
      exposures = c("age", "lwt", "race", "smoke", "ht", "ui"),
      approach = linear
    )

    birthwt_linear

    ## a flextable object.
    ## col_keys: `Characteristic`, `Adjusted Beta (95% CI)`, `p-value`
    ## header has 1 row(s)
    ## body has 15 row(s)
    ## original dataset sample:
    ## 'data.frame':    15 obs. of  3 variables:
    ##  $ Characteristic        : chr  "Maternal age" "Maternal weight" "Maternal race" "White" ...
    ##  $ Adjusted Beta (95% CI): chr  "-4.67 (-22.97–13.63)" "4.40 (1.05–7.74)" "" "Ref." ...
    ##  $ p-value               : chr  "0.617" "0.011" "" "" ...

## What To Inspect

- `$table`: publication-ready table.
- `$table_body`: numeric estimates behind the display.
- `$models`: fitted model objects.
- `$model_summaries`: model-level summaries.
- `$model_stats`: optional model-fit statistics when `model_stats = TRUE`.
- `$variable_labels`: labels used in publication output.
- `$reg_check`: diagnostics for linear models.