Skip to contents

Overview

color_table() produces a gt table object with cell backgrounds shaded by observed frequencies or Pearson residuals from an independence (or user-supplied) model. The goal is a “smart” tabular display that makes patterns of association or unusual cells immediately visible.

When used interactively in RStudio, the returned gt object renders directly in the Viewer panel — no extra steps needed. However, using color_table() in R Markdown (.Rmd) or Quarto (.qmd) documents requires some special consideration because gt tables render natively only in HTML output. For PDF or Word output, the table must be saved as a PNG image and included with knitr::include_graphics().

The knit_include() helper in this package handles that branching automatically. Piping a gt table through knit_include() returns it unchanged for HTML output, and saves a PNG fallback for all other formats — so the same chunk knits correctly regardless of the output format.


HTML output — return the gt object directly

For documents that are compiled to HTML, color_table() renders natively: just return the object from a chunk and knitr handles it via gt’s built-in knitr::knit_print() method. No filename argument or extra wrapper is needed.

The first example shades cells by Pearson residuals from the independence model (the default), making over- and under-represented Hair×Eye combinations immediately visible.

data(HairEyeColor)
HEC <- margin.table(HairEyeColor, 1:2)   # collapse over Sex

color_table(HEC, title = "Hair \u00d7 Eye Color (residual shading)")
## Shading based on residuals from model of independence,
##  X^2 = 138.29, df = 9, p = 2.325e-25
Hair × Eye Color (residual shading)
Eye
Total
Brown Blue Hazel Green
Black 68 20 15 5 108
Brown 119 84 54 29 286
Red 26 17 14 14 71
Blond 7 94 10 16 127
Total 220 215 93 64 592

For comparison, shading by raw frequencies (rather than residuals) gives a different picture — it highlights which combinations are simply most common, not which ones deviate from independence.

color_table(HEC, shade = "freq", title = "Hair \u00d7 Eye Color (frequency shading)")
Hair × Eye Color (frequency shading)
Eye
Total
Brown Blue Hazel Green
Black 68 20 15 5 108
Brown 119 84 54 29 286
Red 26 17 14 14 71
Blond 7 94 10 16 127
Total 220 215 93 64 592

For a three-way table, a formula argument controls the layout, following the col_vars ~ row_vars convention used by vcd::structable() and stats::ftable(): variable(s) on the left of ~ become the column category (or nested column spanners, if there’s more than one), and variable(s) on the right become the row category (or separate row-label columns, if there’s more than one). The legend note reproduces the chi-squared summary printed to the console.

Here Eye is the single column variable, and Hair and Sex are two row variables 014 shown as two separate stub columns instead of concatenated labels like “Black_Male”:

color_table(HairEyeColor,
            formula = Eye ~ Hair + Sex,
            legend  = TRUE,
            title   = "Hair \u00d7 Eye \u00d7 Sex (complete independence residuals)")
## Re-fitting to get frequencies and fitted values
## Shading based on residuals from model of complete independence, X^2 = 164.92, df = 24, p = 0
Hair × Eye × Sex (complete independence residuals)
Eye
Total
Brown Blue Hazel Green
Black Male 32 11 10 3 56
Female 36 9 5 2 52
Brown Male 53 50 25 15 143
Female 66 34 29 14 143
Red Male 10 10 7 7 34
Female 16 7 7 7 37
Blond Male 3 30 5 8 46
Female 4 64 5 8 81
Total 220 215 93 64 592
Shading based on residuals from model of complete independence, X^2 = 164.92, df = 24, p = 0

Swapping the formula around puts Hair and Sex on the columns instead, shown as nested spanner headings, with Eye as the single row variable:

color_table(HairEyeColor,
            formula = Hair + Sex ~ Eye,
            legend  = TRUE,
            title   = "Hair \u00d7 Sex as column spanners, Eye as rows")
## Re-fitting to get frequencies and fitted values
## Shading based on residuals from model of complete independence, X^2 = 164.92, df = 24, p = 0
Hair × Sex as column spanners, Eye as rows
Hair
Black
Brown
Red
Blond
Total
Male Female Male Female Male Female Male Female
Brown 32 36 53 66 10 16 3 4 220
Blue 11 9 50 34 10 7 30 64 215
Hazel 10 5 25 29 7 7 5 5 93
Green 3 2 15 14 7 7 8 8 64
Total 56 52 143 143 34 37 46 81 592
Shading based on residuals from model of complete independence, X^2 = 164.92, df = 24, p = 0

PDF / Word output — save image, then include it

For non-HTML output, save the table as a PNG and include with knitr::include_graphics(). Supported formats: .png, .pdf, .html, .rtf, .docx. The vwidth and vheight arguments control the image viewport in pixels.

The chunk below is shown for illustration only (eval=FALSE); the HTML approach above is sufficient when knitting to HTML.

color_table(HEC,
            title    = "Hair \u00d7 Eye Color",
            filename = "color_table_hec.png",
            vwidth   = 520,
            vheight  = 300)

knitr::include_graphics("color_table_hec.png")

Universal output — knit_include()

knit_include() eliminates the need to branch on output format by hand. Pipe the gt object through it and the same chunk works correctly whether you are knitting to HTML, PDF, or Word.

color_table(HEC,
            title = "Hair \u00d7 Eye Color") |>
  knit_include(width = 520, height = 300)
## Shading based on residuals from model of independence,
##  X^2 = 138.29, df = 9, p = 2.325e-25
Hair × Eye Color
Eye
Total
Brown Blue Hazel Green
Black 68 20 15 5 108
Brown 119 84 54 29 286
Red 26 17 14 14 71
Blond 7 94 10 16 127
Total 220 215 93 64 592

knit_include() also handles htmlwidget objects (plotly, DT, leaflet, …) the same way — HTML output gets the interactive widget, PDF/Word output gets a screenshot. Any other class is passed through unchanged, so it is safe to use in a pipe on any object.


More examples

Displaying residual values in cells

Setting values = "residuals" replaces the cell frequencies with the Pearson residual values themselves. This is useful when the exact magnitude of each deviation matters, not just its direction. Row and column totals are suppressed automatically because residuals do not have meaningful marginal sums.

color_table(HEC,
            values = "residuals",
            title  = "Hair \u00d7 Eye \u2014 Pearson residuals") |>
  knit_include(width = 520, height = 280)
## Shading based on residuals from model of independence,
##  X^2 = 138.29, df = 9, p = 2.325e-25
Hair × Eye — Pearson residuals
Eye
Brown Blue Hazel Green
Black 4.40 -3.07 -0.48 -1.95
Brown 1.23 -1.95 1.35 -0.35
Red -0.07 -1.73 0.85 2.28
Blond -5.85 7.05 -2.23 0.61

Multi-way table: PreSex data

The PreSex data cross-classifies four variables. Here MaritalStatus is the single column variable, and the two sexual-attitude variables (PremaritalSex, ExtramaritalSex) are row variables, shown as separate stub columns rather than concatenated labels — making the interaction structure easy to read. The legend note records the goodness-of-fit for the complete-independence model.

data(PreSex, package = "vcd")
color_table(PreSex,
            formula = MaritalStatus ~ PremaritalSex + ExtramaritalSex,
            legend  = TRUE,
            title   = "Pre/Extra-marital Sex by Marital Status") |>
  knit_include(width = 520, height = 300)
## Re-fitting to get frequencies and fitted values
## Shading based on residuals from model of complete independence, X^2 = 270.14, df = 11, p = 0
Pre/Extra-marital Sex by Marital Status
MaritalStatus
Total
Divorced Married
Yes Yes 45 15 60
No 114 67 181
No Yes 53 8 61
No 282 452 734
Total 494 542 1036
Shading based on residuals from model of complete independence, X^2 = 270.14, df = 11, p = 0

Row and column variables together: Titanic data

Column spanners and row-label columns can appear in the same table. Here Class and Sex (left of ~) form nested column spanners, while Age and Survived (right of ~) are shown as two separate row-stub columns:

data(Titanic)
color_table(Titanic,
            formula = Class + Sex ~ Age + Survived,
            legend  = TRUE,
            title   = "Titanic: Class × Sex as column spanners, Age × Survived as row stubs") |>
  knit_include(width = 620, height = 380)
## Re-fitting to get frequencies and fitted values
## Shading based on residuals from model of complete independence, X^2 = 1637.45, df = 25, p = 0
Titanic: Class × Sex as column spanners, Age × Survived as row stubs
Class
1st
2nd
3rd
Crew
Total
Male Female Male Female Male Female Male Female
Child No 0 0 0 0 35 17 0 0 52
Yes 5 1 11 13 13 14 0 0 57
Adult No 118 4 154 13 387 89 670 3 1438
Yes 57 140 14 80 75 76 192 20 654
Total 180 145 179 106 510 196 862 23 2201
Shading based on residuals from model of complete independence, X^2 = 1637.45, df = 25, p = 0