The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
library(tabxplor)
# Pin the legend language: it defaults to "auto" = the ambient locale, so building this English
# vignette on a French machine silently renders French legends and captions (the -fr articles pin
# "fr" for the same reason). Output must not depend on where it is built.
options(tabxplor.lang = "en")
# The shape table a continuous predictor draws under the footer is not this vignette's subject.
options(tabxplor.shape_table = "no")
Sys.setenv(LANGUAGE = "en") # the test-summary / model-fit row labels go through gettext, not this option
library(dplyr)
# Tables render as tabxplor's real html tables (the recommended everyday setting); the shared
# stylesheet is emitted once by tab_css() below, and the hover tooltips are kept off here.
options(tabxplor.print = "html")
options(tabxplor.tab_kable_css = FALSE)
options(tabxplor.tab_kable_tooltips = FALSE)
# Console outputs (vectors, fields...) keep their terminal colors, turned to html by fansi.
options(cli.num_colors = 256)
set_color_palette(theme = "light")Une version française de ce document est disponible : Programmer avec tabxplor.
This vignette is for users who want to program with
tabxplor — write their own helpers, pull the underlying
numbers out, or reshape a table cell by cell. If you only want to make
and read tables, vignette("tabxplor") is the place to
start.
Every numeric column of a tabxplor table is a single
vector of class tabxplor_fmt (“formatted
number”). It is a vctrs
record: behind the one value you see printed, each cell stores
all the data needed to compute the displayed number, its format
and its colour — counts, percentages, differences, relative
risks, confidence-interval bounds, odds-ratios, and so on. Because it is
a proper vector, it survives every dplyr verb, and you can
read or rewrite any of its fields.
gss_simple <- gss_cat_data_formatting()
tabs <- tab(gss_simple, race, marital, pct = "row", color = "difference")The quickest way to recover the underlying numbers as ordinary
numeric vectors is get_num(), which extracts whatever field
is currently displayed:
| race | Married | Separated | Divorced | Widowed | Never married | NA | Total |
|---|---|---|---|---|---|---|---|
| White | 0.507227813357731 | 0.0266544678255566 | 0.163220494053065 | 0.0899664531869472 | 0.212137846904544 | 0.000792924672156145 | 1 |
| Black | 0.277724512623841 | 0.0626398210290828 | 0.158197507190796 | 0.0837328219878555 | 0.417066155321189 | 0.000639181847235539 | 1 |
| Other | 0.475752935171006 | 0.0561510974987238 | 0.108218478815722 | 0.035732516590097 | 0.32312404287902 | 0.00102092904543134 | 1 |
| Total | 0.470930503188568 | 0.0345854861983894 | 0.157473351021738 | 0.0841130195968906 | 0.252106316622446 | 0.000791323371968533 | 1 |
To get the character strings instead (formatted, but without
colours), use format():
Individual fields are read most simply with $ on the fmt
column (see “Reading and changing fields” below):
#> [1] 0.5072278 0.2777245 0.4757529 0.4709305
To hand the whole table to base R — a correspondence analysis,
chisq.test(), mosaicplot() —
as.matrix() and as.table() do it in one call.
They keep only the data cells: the total row, the total columns and the
display-time rows (the base count, add_pct, the p-values)
go, because an analysis run on a table’s own margins is wrong. Pass
totals = TRUE to keep them.
#> Married Separated Divorced Widowed Never married NA
#> White 8316 437 2676 1475 3478 13
#> Black 869 196 495 262 1305 2
#> Other 932 110 212 70 633 2
A tabxplor_fmt cell carries 21 fields
(many are NA when the relevant quantity was not requested).
The user-facing ones are:
| Field | Meaning |
|---|---|
n |
unweighted count (integer) |
wn |
weighted count |
pct |
percentage |
mean |
mean (numeric column variables) |
diff |
difference from the total / reference cell |
ratio |
ratio to the reference (relative risk, or a ratio of means) |
ci_inf, ci_sup |
confidence-interval bounds |
pvalue |
per-cell significance p-value (feeds the stars) |
or |
odds ratio / relative-risk ratio |
ctr |
contribution to the chi-squared
(color = "contrib") |
var |
variance (numeric columns; chi-squared variance with
pct) |
tot_n |
the cell’s own base — the count its percentage is computed on |
n_eff |
effective sample size used for the CI, with
options(tabxplor.design_effect = TRUE) or a
survey design |
obs |
tab_reg() only: the observed (crude)
effect the modelled one is compared to |
gap_se |
tab_reg() only: the standard error of the
gap between the estimate and obs |
digits |
number of decimals to display (per cell) |
display |
which field is shown (per cell) |
row_kind |
what kind of row the cell sits in: "data",
"total", or one of the synthetic display rows
"n" / "pct" / "pvalue" /
"gof" / "blank" |
in_tottab, in_refrow |
is the cell in a total table / a reference row (logical) |
(row_kind replaced a logical in_totrow
field in 2.0.0. x$in_totrow still returns that logical, and
is_totrow() / as_totrow() are unchanged.)
See the whole underlying data frame of a column with
vctrs::vec_data():
#> n display digits wn pct mean diff ratio ctr var ci_inf
#> 1 8316 pct 0 NA 0.5072278 NA 0.036297310 1.0770757 NA NA NA
#> 2 869 pct 0 NA 0.2777245 NA -0.193205991 0.5897357 NA NA NA
#> 3 932 pct 0 NA 0.4757529 NA 0.004822432 1.0102402 NA NA NA
#> 4 10117 pct 0 NA 0.4709305 NA 0.000000000 1.0000000 NA NA NA
#> ci_sup pvalue or tot_n n_eff obs gap_se row_kind in_tottab in_refrow
#> 1 NA NA 1 16395 NA NA NA data FALSE FALSE
#> 2 NA NA 1 3129 NA NA NA data FALSE FALSE
#> 3 NA NA 1 1959 NA NA NA data FALSE FALSE
#> 4 NA NA 1 21483 NA NA NA total FALSE FALSE
Read a field with $ (the friendliest way), or
vctrs::field():
tabs$Married$pct
tabs |> mutate(across(where(is_fmt), ~ .$pct))
tabs |> mutate(across(where(is_fmt), ~ vctrs::field(., "pct")))Confidence intervals are stored as their two bounds, the
ci_inf and ci_sup fields:
#> [1] 0.4995743 0.2623114 0.4537069 0.4642615
#> [1] 0.5148780 0.2936827 0.4978939 0.4776099
Switch the displayed field with set_display():
To change a field, the easiest route is dplyr::mutate()
on the fmt vector itself — a tabxplor_fmt
behaves like a little data frame of its fields. For example, round every
cell to two decimals:
A fuller example: turn a means table’s variance into a standard deviation and add it as a new, un-coloured column shown to one decimal:
set_display() chooses one field to show. To
show several fields in one cell, give it a
{} template instead of a bare field name —
the same template you can pass to tab(display =) when you
build the table. The template is plain text with {field}
tokens; each token is replaced by that field, formatted as usual:
| marital | |||||||
|---|---|---|---|---|---|---|---|
| race | Married | Separated | Divorced | Widowed | Never married | NA | Total |
| <row% (diff)> | <row% (n)> | ||||||
| White | 51% ( +4%) | 3% (-1%) | 16% (+1%) | 9% (+1%) | 21% ( -4%) | 0% (+0%) | 100% (16 395) |
| Black | 28% (-19%) | 6% (+3%) | 16% (+0%) | 8% (+0%) | 42% (+16%) | 0% (+0%) | 100% ( 3 129) |
| Other | 48% ( +0%) | 6% (+2%) | 11% (-5%) | 4% (-5%) | 32% ( +7%) | 0% (+0%) | 100% ( 1 959) |
| Total | 47% ( 0%) | 3% ( 0%) | 16% ( 0%) | 8% ( 0%) | 25% ( 0%) | 0% ( 0%) | 100% (21 483) |
|
Percentage points (risk) difference: cell ≥ the Total row +5; +10; +20; +30 points; cell ≤ the Total row -5; -10; -20; -30 points.
|
|||||||
The rules:
pct, n,
wn, mean, est, base,
diff, ratio, ci,
moe, or, ctr, var,
sd, cv, coef, resid,
obs, gap — the same set as the single-field
displays. Most name a stored field; a few are derived
and so read-only — resid (from the p-value and the sign of
ctr), sd and cv from
var, gap from est and
obs.{est} is whatever the
column estimates (a percentage, a difference, an odds ratio) and
{base} the level it sits on (a percentage, a mean, a
count). "{est} ({base})" reads the same on a crosstab and
on a regression."est",
"est_ci", "est_base", "base_est",
"base", "base_ci", "base_moe",
"mean_sd", "mean_cv". They are the same names
in tab(), in tab_reg() and in
set_display().get_num() returns, the
value Excel keeps, and the only part the colours paint. A template with
no token outside brackets has no primary, and renders whole as an
aside."{est} ({base:1})" — which beats the column’s
digits.set_display("ci") is exactly
set_display("{ci}").tab_html(), tab_md()): the stored
fields are untouched, so you can switch back at any time.{ci} field already prints its own
[…;…] brackets, so write "{pct} {ci}" —
not "{pct} [{ci}]", which would double
them.Because switching the display recomputes nothing — every field is
already stored in the cell — you can spin off a new column that
shows a different field. A percentage table has a reference by
default (the Total row), so its diff field is already
filled; a difference twin of every percentage column is then one
mutate():
tab(gss_simple, race, marital, pct = "row") |>
mutate(across(where(is_fmt), ~ set_display(., "diff"), .names = "{.col}_diff"))| marital | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| race | Married | Separated | Divorced | Widowed | Never married | NA | Total | Married_diff | Separated_diff | Divorced_diff | Widowed_diff |
Never married_ diff |
NA_diff | Total_diff |
| <row%> | <n> | <row%-diff> | <n> | |||||||||||
| White | 51% | 3% | 16% | 9% | 21% | 0% | (16 395) | +4% | -1% | +1% | +1% | -4% | +0% | (16 395) |
| Black | 28% | 6% | 16% | 8% | 42% | 0% | ( 3 129) | -19% | +3% | +0% | +0% | +16% | +0% | ( 3 129) |
| Other | 48% | 6% | 11% | 4% | 32% | 0% | ( 1 959) | +0% | +2% | -5% | -5% | +7% | +0% | ( 1 959) |
| Total | 47% | 3% | 16% | 8% | 25% | 0% | (21 483) | ref:47% | ref:3% | ref:16% | ref:8% | ref:25% | ref:0% | (21 483) |
.names = "{.col}_diff" keeps the original percentage
columns and adds a <name>_diff twin next to each. (If
a diff column comes out empty, the source table had no
reference cell — build it with ref = / comp =,
or with color = "difference".)
The same recipe puts an adjusted prediction beside a
regression effect. Two extra calls make the copy purely
descriptive: set_color("") drops its colour (the
ladder belongs to the effect, and the same ladder twice is noise), and
set_pvalue(NA_real_) drops its stars — the stored p-value
is their only source, so erasing it is the direct way to say “this copy
tests nothing”:
Helper predicates let you act on structural parts of a table:
is_totrow() / is_tottab() /
is_refrow() (cell-level, logical vectors), and
is_totcol() / is_refcol() (column-level). They
are what keeps totals in place when you re-order or when you write
conditional formatting:
Besides its per-cell fields, each fmt column carries a
few column-level attributes, read and set with
get_* / set_* (or is_* /
as_* for the logical ones):
scale — get_scale() /
set_scale(): what the column estimates. A
level ("level_n", "level_pct",
"level_mean"), a difference ("points" for
percentage points, "mean_diff", "raw_diff" for
a regression coefficient in the outcome’s own units), a ratio
("pct_ratio", "mean_ratio",
"odds_ratio") or a link-scale coefficient
("log_coef"). It says which field holds the estimate, what
its null value is, and which colour ladder it is read on.pct_type — get_pct_type() /
set_pct_type(): for a percentage, what it is a percentage
of — "row", "col",
"all", "all_tabs", or "none" for
counts, means and coefficients.ci_method — get_ci_method(): which
interval engine built this column’s bounds ("wilson",
"newcombe", "welch", "katz", …;
"" when the column carries no interval).color — get_color() /
set_color(): the colour measure of the column —
"", "no", or one of the measure names
"difference", "ratio",
"odds_ratio", "contrib" (plus
"adjustment" / "between_groups" on a
tab_reg() table). The discipline’s acronyms
("diff", "OR", "or",
"RR", "RD") are permanent shorthands you may
type, but a built table always stores — and its legend always
names — the full word.col_var — get_col_var(): the name of the
column variable (a table can hold several).col_group — get_col_group(): which
sub-population the column’s block belongs to, after a
spread_vars / tab_spread() pivot or a
tab_reg(tab_vars =) split ("" otherwise).
Together with col_var it identifies a column
block: two blocks can show the same variable for two
sub-populations, and exports head them on two lines.comp_all — get_comp_all(): with
tab_vars, is the comparison reference the sub-table
(FALSE) or the whole table (TRUE)?totcol / refcol — is_totcol()
/ is_refcol(): is this a total column / a reference
column?Generic code cannot assume the shape of the table it is handed. Three accessors answer that, so a function can branch on facts rather than guess from column names.
tab_structure() says what the object is
— a crosstab or a regression table, merged or not, grouped or not, and
which variables sit on each axis:
#> $container
#> [1] "table"
#>
#> $kind
#> [1] "crosstab"
#>
#> $merged
#> [1] FALSE
#>
#> $grouped
#> [1] FALSE
#>
#> $row_vars
#> [1] "race"
#>
#> $tab_vars
#> character(0)
#>
#> $col_vars
#> [1] "marital"
#>
#> $same_col_vars
#> [1] TRUE
#>
#> $same_tab_vars
#> [1] TRUE
tab_columns() is the column-level view: one row per
column, with everything the exporters and the colour engine read — what
it estimates, its reference, its confidence-interval method, its
role:
#> # A tibble: 7 × 6
#> column scale pct_type ref ci_method totcol
#> <chr> <chr> <chr> <chr> <chr> <lgl>
#> 1 Married points row tot newcombe FALSE
#> 2 Separated points row tot newcombe FALSE
#> 3 Divorced points row tot newcombe FALSE
#> 4 Widowed points row tot newcombe FALSE
#> 5 Never married points row tot newcombe FALSE
#> 6 NA points row tot newcombe FALSE
#> 7 Total points row tot newcombe TRUE
And fmt_attr() reads or writes any one of those
facts by name, which is the programmatic twin of the
get_* / set_* family above — useful when the
attribute is itself a variable:
#> [1] "points"
#> [1] "newcombe"
On a regression table, reg_measures(data, outcome) plays
the same role for the model: it lists the effect ×
measure combinations that outcome can be asked for, and why
the others are refused.
fmt() constructs an fmt vector directly
from numbers — handy for tests, or to add a hand-computed column.
Everything the display and colour engine needs can be supplied:
#> <fmt-%[3]>
#> [1] 10% 20% 70%
Sometimes the data already arrives cross-tabulated —
a counts table from a report, a table(), a matrix of
frequencies. tab_counts() builds the same coloured
tabxplor table as tab(), but from those counts
instead of the raw records; every calculation runs on the counts, so the
result is identical to what tab() would have produced from
individual records.
The commonest input is a tidy counts data frame (one row per combination, the count in a column):
counts <- dplyr::count(gss_simple, marital, race)
tab_counts(counts, marital, race, counts = n, pct = "row", color = TRUE)| race | ||||
|---|---|---|---|---|
| marital | White | Black | Other | Total |
| <row%> | <row% (n)> | |||
| Married | 82% | 9% | 9% | 100% (10 117) |
| Separated | 59% | 26% | 15% | 100% ( 743) |
| Divorced | 79% | 15% | 6% | 100% ( 3 383) |
| Widowed | 82% | 14% | 4% | 100% ( 1 807) |
| Never married | 64% | 24% | 12% | 100% ( 5 416) |
| NA | 76% | 12% | 12% | 100% ( 17) |
| Total | 76% | 15% | 9% | 100% (21 483) |
|
Percentage points (risk) difference: cell ≥ the Total row +5; +10; +20; +30 points; cell ≤ the Total row -5; -10; -20; -30 points. Background colour, relative
risk (ratio): cell ≥ the Total row ×1.5;
×2;
cell ≤ the Total row ÷2;
÷4.
|
||||
It also melts a table() / xtabs() / matrix
automatically, and reads a wide frame (one column per
column-variable level) via cols = /
col_name =:
tab_counts(table(gss_simple$marital, gss_simple$race), pct = "row", color = "difference")
wide <- tidyr::pivot_wider(counts, names_from = race, values_from = n)
tab_counts(wide, row_var = marital, cols = c(White, Black, Other),
col_name = "race", pct = "row", color = "difference")Weighting works as in tab(): pass the unweighted count
in counts and the weighted count in wt_counts
— estimates use the weighted count, confidence intervals and Chi-2 use
the unweighted N. (options(tabxplor.design_effect = TRUE)
needs the individual weights, which pre-aggregated counts no longer
carry, so it does not apply here — the CIs use the unweighted N, and the
table’s footer says so rather than claiming a correction it does not
have.) When the only figures available are non-whole numbers
(percentages × a base, or weighted-only counts), CIs and the Chi-2 test
are disabled with a message.
A grouped table (built with tab_vars)
stacks one sub-table per group. tab_spread() pivots a
grouping variable’s levels into side-by-side columns —
ideal to compare one measure across groups. Select the measure column
you want first, then spread:
tab(gss_simple, relig, marital, year, pct = "row", totaltab = "no", tot = "row") |>
dplyr::select(year, relig, Married) |>
tab_spread(year)| marital | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| relig | 2000 | 2002 | 2004 | 2006 | 2008 | 2010 | 2012 | 2014 | 2000 | 2002 | 2004 | 2006 | 2008 | 2010 | 2012 | 2014 |
| <row%> | <row%> | <row%> | <row%> | <row%> | <row%> | <row%> | <row%> | <n> | <n> | <n> | <n> | <n> | <n> | <n> | <n> | |
| 1-Protestant | 47% | 48% | 54% | 50% | 51% | 47% | 50% | 49% | 1 521 | 1 460 | 1 483 | 2 328 | 1 040 | 973 | 916 | 1 125 |
| 2-Catholic | 47% | 49% | 57% | 53% | 49% | 44% | 47% | 47% | 679 | 673 | 656 | 1 114 | 470 | 482 | 444 | 606 |
| 3-Other christian | 47% | 41% | 51% | 38% | 59% | 39% | 42% | 43% | 51 | 88 | 84 | 116 | 78 | 98 | 126 | 143 |
| 4-Jewish | 49% | 44% | 53% | 51% | 44% | 54% | 64% | 50% | 63 | 48 | 55 | 78 | 39 | 37 | 28 | 40 |
| 5-Buddhist/Hinduist | 40% | 41% | 65% | 50% | 59% | 50% | 60% | 51% | 25 | 27 | 26 | 42 | 22 | 22 | 15 | 39 |
| 6-Muslim | 42% | 38% | 69% | 71% | 31% | 36% | 62% | 67% | 12 | 13 | 16 | 17 | 13 | 11 | 13 | 9 |
| 7-Other | 30% | 48% | 43% | 30% | 25% | 33% | 42% | 39% | 64 | 58 | 77 | 50 | 20 | 45 | 38 | 36 |
| 8-None | 38% | 33% | 40% | 37% | 38% | 36% | 32% | 37% | 398 | 379 | 403 | 739 | 332 | 363 | 387 | 522 |
| NA | 75% | 37% | 67% | 38% | 67% | 31% | 29% | 50% | 4 | 19 | 12 | 26 | 9 | 13 | 7 | 18 |
| Total | 45% | 46% | 53% | 48% | 48% | 44% | 46% | 46% | 2 817 | 2 765 | 2 812 | 4 510 | 2 023 | 2 044 | 1 974 | 2 538 |
tab(..., spread_vars = year) does the same in one call.
tab_reg(..., tab_vars =) produces a grouped table you can
spread the same way — see vignette("tabxplor-reg").
tab() accepts vectors of arguments to
build several differently-shaped tables in one call
(output_list = TRUE keeps them as a list rather than
merging them). For a fully data-driven batch, purrr::pmap()
over a small specification table is the idiom — one row per table, one
column per argument:
Beyond the everyday defaults (vignette("tabxplor") →
Global R options), these options() tune the
exporters and the build. HTML / tab_html():
tabxplor.tab_kable_css — inline the stylesheet with
each table (TRUE); set FALSE in a many-table
document and call tab_css() once instead (this vignette
does).tabxplor.tab_kable_tooltips — the per-cell hover
tooltips (TRUE); set FALSE once per document
to switch them off everywhere.tabxplor.tab_kable_num_font — the number CSS font stack
(everything else is tab_css()’s business).tabxplor.kable_popover — click popovers instead of
hover tooltips.Excel / tab_export("xl"):
tabxplor.xl_font_text /
tabxplor.xl_font_num /
tabxplor.xl_font_num_stars — the label, number and
starred-number fonts.tabxplor.xl_ratio_cells — how a multiplicative cell
reaches Excel: "fold" (default, the signed fold as a
number), "raw" (the untransformed ratio) or
"text" (the exact display string).Console, stats and paths:
tabxplor.console_bold — embolden reference / total /
coloured cells (auto-detected per editor).tabxplor.stars — the significance ladder, as one named
vector:
options(tabxplor.stars = c("*" = 0.05, "**" = 0.01)).tabxplor.export_dir — the default export
directory.Performance and integration:
tabxplor.parallel — build one worker per row variable
on a background pool (needs mirai);
tabxplor.parallel_min sets the smallest row-variable count
worth dispatching. Release the pool with
tab_parallel_stop().See ?tabxplor-options for the full list and every
default.
vignette("tabxplor") — cross-tables and the colour
helpers.vignette("tabxplor-reg") — regression tables.vignette("tabxplor-weights") — weighted and survey
data.?fmt and ?tabxplor-vctrs for the cell
type, ?tabxplor-options for every option, ?tab
for the arguments and the display tokens.These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.