Demographic Table
Author: Tingting Zhan

# Introduction

This vignette of package `DemographicTable` (CRAN, Github) presents an idiot-proof interface to create a summary table of simple statistics, often known as a demographic table.

Package `DemographicTable` `Imports` packages

- `cli` (Csárdi 2025, v3.6.5), for attractive command line interfaces
- 🗝 `flextable` (Gohel and Skintzos 2025, v0.9.9) and `officer` (Gohel, Moog, and Heckmann 2025, v0.6.10), key dependencies, for flexible table
- `scales` (Wickham, Pedersen, and Seidel 2025, v1.4.0), for colour pallete

## Prerequisite

Packages `DemographicTable` requires R version 4.5.0 (released 2025-04-11) or higher (macOS, Windows). An Integrated Development Environment (IDE), e.g., RStudio or Positron, is not required, but highly recommended. This vignette is created under R version 4.5.1 (2025-06-13) using packages `knitr` (Xie 2025, v1.50), `quarto` (Allaire and Dervieux 2025, v1.5.0 with Quarto v1.7.33) and `rmarkdown` (Allaire et al. 2024, v2.29).

Environment on author’s computer

    Sys.info()[c('sysname', 'release', 'machine')]
    #  sysname  release  machine
    # "Darwin" "24.6.0"  "arm64"
    R.version
    #                _
    # platform       aarch64-apple-darwin20
    # arch           aarch64
    # os             darwin20
    # system         aarch64, darwin20
    # status
    # major          4
    # minor          5.1
    # year           2025
    # month          06
    # day            13
    # svn rev        88306
    # language       R
    # version.string R version 4.5.1 (2025-06-13)
    # nickname       Great Square Root

Experimental (and maybe unstable) features are released extremely frequently to Github. Stable releases to CRAN are typically updated every 2 to 3 months.

    remotes::install_github('tingtingzhan/DemographicTable')

    utils::install.packages('DemographicTable')

## Getting Started

Examples in this vignette require that the `search` path has

    library(DemographicTable)
    library(flextable)

Users may remove the last pipe `|> as_flextable()` from all examples. This is required in the vignette to make `quarto` rendering work.

# Demographic Table

Data example `penguins` from package `datasets`

## Summary of all subjects

    datasets::penguins |>
      DemographicTable(include = c('species', 'island', 'bill_len')) |>
      as_flextable()

| datasets::penguins
--- | ---
n=344
bill_len. mean±sd median; IQR range | n*=342 43.9±5.5 44.5; 9.3 32.1~59.6
island: n (%). Biscoe Dream Torgersen | 168 (48.8%) 124 (36.0%) 52 (15.1%)
species: n (%). Adelie Chinstrap Gentoo | 152 (44.2%) 68 (19.8%) 124 (36.0%)
| n=344
datasets::penguins

## Summary by one `group`

Color of each individual `group` is determined by `scales::pal_hue()`, which is the default color pallete used in package `ggplot2`.

    datasets::penguins |>
      DemographicTable(groups = 'sex', include = c('species', 'bill_dep')) |>
      as_flextable()

| datasets::penguins
--- | ---
n=344 | sex n=11 (3.2%) missing
female n=165 (48.0%) | male n=168 (48.8%) | Signif
bill_dep. mean±sd median; IQR range | n*=342 17.2±2.0 17.3; 3.1 13.1~21.5 | 16.4±1.8 17.0; 3.3 13.1~20.7 | 17.9±1.9 18.4; 3.2 14.1~21.5 | 0.000★ Two-Sample t
species: n (%). Adelie Chinstrap Gentoo | 152 (44.2%) 68 (19.8%) 124 (36.0%) | 73 (44.2%) 34 (20.6%) 58 (35.2%) | 73 (43.5%) 34 (20.2%) 61 (36.3%) | 0.979 Fisher's Exact
| n=344 | sex n=11 (3.2%) missing
datasets::penguins

User may choose to hide the p-values with option `compare = FALSE`.

    datasets::penguins |>
      DemographicTable(groups = 'sex', include = c('species', 'bill_dep'), compare = FALSE) |>
      as_flextable()

| datasets::penguins
--- | ---
n=344 | sex n=11 (3.2%) missing
female n=165 (48.0%) | male n=168 (48.8%)
bill_dep. mean±sd median; IQR range | n*=342 17.2±2.0 17.3; 3.1 13.1~21.5 | 16.4±1.8 17.0; 3.3 13.1~20.7 | 17.9±1.9 18.4; 3.2 14.1~21.5
species: n (%). Adelie Chinstrap Gentoo | 152 (44.2%) 68 (19.8%) 124 (36.0%) | 73 (44.2%) 34 (20.6%) 58 (35.2%) | 73 (43.5%) 34 (20.2%) 61 (36.3%)
| n=344 | sex n=11 (3.2%) missing
datasets::penguins

## Summary by multiple `groups`

    datasets::penguins |>
      DemographicTable(groups = c('sex', 'island'), include = c('species', 'bill_dep'), compare = FALSE) |>
      as_flextable()

| datasets::penguins
--- | ---
n=344 | sex n=11 (3.2%) missing | island
female n=165 (48.0%) | male n=168 (48.8%) | Biscoe n=168 (48.8%) | Dream n=124 (36.0%) | Torgersen n=52 (15.1%)
bill_dep. mean±sd median; IQR range | n*=342 17.2±2.0 17.3; 3.1 13.1~21.5 | 16.4±1.8 17.0; 3.3 13.1~20.7 | 17.9±1.9 18.4; 3.2 14.1~21.5 | n*=167 15.9±1.8 15.5; 2.5 13.1~21.1 | 18.3±1.1 . 15.5~21.2 | n*=51 18.4±1.3 . 15.9~21.5
species: n (%). Adelie Chinstrap Gentoo | 152 (44.2%) 68 (19.8%) 124 (36.0%) | 73 (44.2%) 34 (20.6%) 58 (35.2%) | 73 (43.5%) 34 (20.2%) 61 (36.3%) | 44 (26.2%) - 124 (73.8%) | 56 (45.2%) 68 (54.8%) - | 52 (100.0%) - -
| n=344 | sex n=11 (3.2%) missing | island
datasets::penguins

# Combine multiple `DemographicTable`s

    tb1 = datasets::penguins |>
      subset(subset = (sex == 'male')) |>
      DemographicTable(groups = 'island', include = c('species', 'bill_dep'), data.name = 'Male Penguins', compare = FALSE)

    tb2 = datasets::penguins |>
      subset(subset = (sex == 'female')) |>
      DemographicTable(groups = 'island', include = c('species', 'bill_dep'), data.name = 'Female Penguins', compare = FALSE)

    c(tb1, tb2) |> as_flextable()

| Male Penguins | Female Penguins
--- | --- | ---
n=168 | island | n=165 | island
Biscoe n=83 (49.4%) | Dream n=62 (36.9%) | Torgersen n=23 (13.7%) | Biscoe n=80 (48.5%) | Dream n=61 (37.0%) | Torgersen n=24 (14.5%)
bill_dep. mean±sd median; IQR range | 17.9±1.9 18.4; 3.2 14.1~21.5 | 16.6±1.7 16.0; 2.3 14.1~21.1 | 19.1±0.9 . 17.0~21.2 | 19.4±1.1 . 17.6~21.5 | 16.4±1.8 17.0; 3.3 13.1~20.7 | 15.2±1.7 14.5; 2.3 13.1~20.7 | 17.6±0.8 . 15.5~19.4 | 17.6±0.9 . 15.9~19.3
species: n (%). Adelie Chinstrap Gentoo | 73 (43.5%) 34 (20.2%) 61 (36.3%) | 22 (26.5%) - 61 (73.5%) | 28 (45.2%) 34 (54.8%) - | 23 (100.0%) - - | 73 (44.2%) 34 (20.6%) 58 (35.2%) | 22 (27.5%) - 58 (72.5%) | 27 (44.3%) 34 (55.7%) - | 24 (100.0%) - -
| n=168 | island | n=165 | island
Male Penguins | Female Penguins

# Exception Handling

## Missing value in `groups`

    datasets::penguins |>
      DemographicTable(groups = c('sex'), include = c('body_mass', 'species')) |>
      as_flextable()

| datasets::penguins
--- | ---
n=344 | sex n=11 (3.2%) missing
female n=165 (48.0%) | male n=168 (48.8%) | Signif
body_mass. mean±sd median; IQR range | n*=342 4201.8±802.0 4050.0; 1200.0 2700.0~6300.0 | 3862.3±666.2 3650.0; 1200.0 2700.0~5200.0 | 4545.7±787.6 4300.0; 1412.5 3250.0~6300.0 | 0.000★ Two-Sample t
species: n (%). Adelie Chinstrap Gentoo | 152 (44.2%) 68 (19.8%) 124 (36.0%) | 73 (44.2%) 34 (20.6%) 58 (35.2%) | 73 (43.5%) 34 (20.2%) 61 (36.3%) | 0.979 Fisher's Exact
| n=344 | sex n=11 (3.2%) missing
datasets::penguins

## Use of `logical` values

Using `logical` values is discouraged, as this practice is proved confusing to scientists without a strong data background.

    mtc = datasets::mtcars |>
      within.data.frame(expr = {
        vs_straight = as.logical(vs)
        am_manual = as.logical(am)
      })

A warning message will be printed if `logical` variables are used in `groups` and/or `include`.

    tryCatch(DemographicTable(mtc, groups = 'am_manual', include = c('drat', 'vs_straight')), warning = identity)
    # <simpleWarning in DemographicTable(mtc, groups = "am_manual", include = c("drat",     "vs_straight")):
    # Some scientists do not understand logical value (e.g., arm_intervention being TRUE/FALSE)
    # Consider using 2-level factor (e.g., arm being intervention/control)>

Instead of using `logical` variables

    mtc |>
      DemographicTable(groups = 'am_manual', include = c('drat', 'vs_straight')) |>
      as_flextable()

| mtc
--- | ---
n=32 | am_manual
FALSE n=19 (59.4%) | TRUE n=13 (40.6%) | Signif
drat. mean±sd median; IQR range | 3.6±0.5 . 2.8~4.9 | 3.3±0.4 3.1; 0.6 2.8~3.9 | 4.0±0.4 . 3.5~4.9 | 0.000★ Wilcoxon- Mann-Whitney
vs_straight: n (%) | 14 (43.8%) | 7 (36.8%) | 7 (53.8%) | 0.556 χ² (chi-square)
| n=32 | am_manual
mtc

We recommend using 2-`level` `factor`s.

    mtcars |>
      within.data.frame(expr = {
        vs = ifelse(vs, yes = 'Straight', no = 'V-shaped')
        am = ifelse(am, yes = 'manual', no = 'automatic')
      }) |>
      DemographicTable(groups = 'am', include = c('drat', 'vs'), data.name = 'mtcars') |>
      as_flextable()

| mtcars
--- | ---
n=32 | am
automatic n=19 (59.4%) | manual n=13 (40.6%) | Signif
drat. mean±sd median; IQR range | 3.6±0.5 . 2.8~4.9 | 3.3±0.4 3.1; 0.6 2.8~3.9 | 4.0±0.4 . 3.5~4.9 | 0.000★ Wilcoxon- Mann-Whitney
vs: n (%). Straight V-shaped | 14 (43.8%) 18 (56.2%) | 7 (36.8%) 12 (63.2%) | 7 (53.8%) 6 (46.2%) | 0.473 Fisher's Exact
| n=32 | am
mtcars

Allaire, JJ, and Christophe Dervieux. 2025. quarto: R Interface to ’Quarto’ Markdown Publishing System. https://doi.org/10.32614/CRAN.package.quarto.
Allaire, JJ, Yihui Xie, Christophe Dervieux, Jonathan McPherson, Javier Luraschi, Kevin Ushey, Aron Atkins, et al. 2024. rmarkdown: Dynamic Documents for r. https://github.com/rstudio/rmarkdown.
Csárdi, Gábor. 2025. cli: Helpers for Developing Command Line Interfaces. https://doi.org/10.32614/CRAN.package.cli.
Gohel, David, Stefan Moog, and Mark Heckmann. 2025. officer: Manipulation of Microsoft Word and PowerPoint Documents. https://doi.org/10.32614/CRAN.package.officer.
Gohel, David, and Panagiotis Skintzos. 2025. flextable: Functions for Tabular Reporting. https://doi.org/10.32614/CRAN.package.flextable.
Wickham, Hadley, Thomas Lin Pedersen, and Dana Seidel. 2025. scales: Scale Functions for Visualization. https://doi.org/10.32614/CRAN.package.scales.
Xie, Yihui. 2025. knitr: A General-Purpose Package for Dynamic Report Generation in R. https://yihui.org/knitr/.