First CRAN release.
tplyr2 builds clinical summary tables from a declarative specification. A
tplyr_spec() describes what to compute — count, descriptive, shift, and
custom analysis layers, with population-based denominators, formatting, sorting,
and traceability — and tplyr_build() executes it against data. The spec is
pure configuration, so it can be written, serialized to JSON or YAML, reviewed,
and re-run independently of any dataset.
tplyr2 is a ground-up successor to the
Tplyr package rather than a new
version of it. The two have different APIs and are intended to coexist while
users migrate; vignette("migration") maps one onto the other.
Versions before 0.2.0 were development releases available only from GitHub. The sections below record what changed since 0.1.0, and are relevant to anyone who installed the package that way.
Several settings that were previously accepted and silently ignored now error. Each of these produced a plausible-looking but wrong table, which is the wrong default for a clinical reporting package.
... overrides passed to tplyr_build() error instead of being
ignored (#73). tplyr_build(spec, adsl, wher = "SAFFL == 'Y'") used to build
on unfiltered data; it now errors and lists the valid override names.tplyr2_options() error (#74). A misspelled
IBMrounding used to set a dead option and leave the whole output package on
banker's rounding.result_order_var naming a statistic the layer does not compute errors
instead of falling back to "n", and ordering_cols matching none of the
observed column levels errors instead of zeroing every sort key (#78). A
partially unmatched ordering_cols warns and sorts on the levels that matched.denoms_by must name the layer's own grouping variables — the column
variables, by variables, and (for count and shift layers) the target
variable (#77). An unrecognized name used to shrink the join key set silently,
either multiplying table rows or attaching another group's denominator.missing_count keys error (#80).tplyr_stats_data() now returns the grouping columns plus the requested
statistic, as documented, rather than the entire layer frame (#79). Use
tplyr_numeric_data() for every statistic.0 (#76), matching what desc layers already did. A genuine
zero count against a real denominator still renders 0.0%.f_str keywords — ci_lower/ci_upper (from n/total) and
distinct_ci_lower/distinct_ci_upper (from distinct_n/distinct_total) —
are computed per column-by-target-level cell on the percentage scale, so an
incidence CI drops straight into a count-layer format string, e.g.
f_str("xx (xx.x%) [xx.x, xx.x]", "distinct_n", "distinct_pct", "distinct_ci_lower", "distinct_ci_upper").
Two new layer_settings() controls choose the method and coverage:
ci_method ("clopper_pearson" default / exact, matching SAS PROC FREQ EXACT and stats::binom.test(); plus "wilson", "wald",
"agresti_coull", "jeffreys") and ci_level (default 0.95). The bounds
are computed lazily (only when a format references a CI keyword) and appear on
Total/Missing rows just like pct. The underlying vectorized helper,
proportion_ci(), is also exported.apply_formats() gains na, width, and pad arguments (#41). na is a
string substituted for cells whose format-group inputs are all NA, used
instead of the blank-width fill (na = "" yields a truly empty cell, nchar
0; na = "NE" renders "NE"), letting apply_formats() replace hand-rolled
fixed-width formatters for externally row-bound statistics. width pads each
formatted token to a fixed total width (pad = "right"/"left"); when the
na substitution applies, it wins and the cell is not padded. The defaults
(NULL) preserve existing behavior.as_display() helper returning a display-ready frame — the rowlabel*,
res*, and rdiff* columns only, with the internal ord* (and row_id)
columns dropped, ready to hand to a table-rendering package (#36). Pass
labels = TRUE to rename the result columns to their column-group header
labels.n_records descriptive statistic keyword for group_desc() — the number
of records assessed (non-missing + missing), for tables that report an n of
records/subjects assessed rather than the non-missing analysis count (#34).
The existing n (non-missing) keyword is unchanged.denom_row setting for shift layers — emits the per-baseline-group
denominator (the shift_denom = "column" denominator) as an integer n row
above the shift-to rows, instead of forcing callers to recompute it (#35).
The label defaults to "n" (denom_row_label).assoc_test() for count and shift layers — an omnibus association-test
column (#37). It runs a caller-supplied function once per by group over the
raw source-data subset for that group (all cols levels), so a Fisher's
exact or CMH test can tabulate across the treatment columns, and lands the
formatted result as a single trailing pval1 column (one value per by-group,
on the group's first row). Attach via layer_settings(assoc_test = ...).assoc_test() gains a pairwise / per-level mode for count layers (#40).
Supplying comparisons (with an optional reference, defaulting to the first
cols level) compares the reference arm to each named arm and emits one
pval column per comparison, each with a value on every target-level row
(like risk_diff's rdiff columns) — the standard AE-by-SOC/PT layout. In
this mode the caller-supplied fn receives a 2x2 incidence matrix
matrix(c(n_ref, n_cmp, N_ref - n_ref, N_cmp - n_cmp), nrow = 2) (distinct
counts/denominators when distinct_by is set) and returns a scalar p-value,
so any test — fisher.test, and beyond — can be used. label may be a
per-comparison vector; the default is "<reference> vs <comparison>".assoc_test()'s fn may now return a character string, passed through
to the pval cell verbatim (#47); format applies only when fn returns a
numeric. This completes the arbitrary-fn design — the function that computes
the test can also supply the finished display, so conditional p-value
conventions (a significance flag like 0.031*, a >.99/<.0001
ceiling/floor, an "NE"/"N/A" sentinel, trailing-space alignment) all live
in the fn. Works in both omnibus and pairwise modes; NA (numeric or
character) still renders a blank. Existing numeric-returning fns are
unaffected.assoc_test() now works on nested count layers (#49), where it
was previously a no-op. It emits a pval column per comparison with a value
on every row of every level — each inner (e.g. preferred-term) row and each
outer (e.g. system-organ-class subtotal) row — building each row's 2x2 from
that row's own distinct counts and the pop_data denominators (the outer row
uses the level's "any event in that group" subject count). This is the
canonical AE-incidence-by-SOC/PT Fisher layout. A new total_row argument to
assoc_test() (default TRUE) also lands a p-value on the layer's total
("any event anywhere") row; set total_row = FALSE to leave it blank.
Missing rows are always blank. Combined with the character-return display
(#47), the fn can supply the exact * / >.99 / trailing-space / blank
cell text on every nested row. A zero-event arm is handled correctly: its
2x2 denominator is taken from pop_data (subjects at risk), so a sparse or
empty reference arm still yields a valid 0-vs-k test on every row
instead of blanking the column.assoc_test() now works on group_desc layers in omnibus mode (#51),
giving a continuous-variable comparison across arms — ANOVA, Kruskal-Wallis,
a t-test — a native home. Same contract as count/shift: fn receives the
by-group's raw source subset (all cols levels) and returns a scalar p
(formatted by format) or a verbatim character string (#47), placed on the
by-group's first statistic row (NA → blank). A demographics table can now
produce its comparison p-values — continuous and categorical characteristics
sharing one pval column — entirely through tplyr2 instead of a hand-rolled
aov/kruskal.test side pipeline. Pairwise/per-level mode remains count-layer
only; supplying comparisons on a desc layer is now a clear error rather than
silently ignored.assoc_test()'s fn may now return multiple values rendered into one
cell (#60). When format references more than one variable, fn returns a
numeric vector of matching length, mapped positionally onto the format — so a
procedure that emits a tuple (an odds ratio with its confidence interval, an
estimate with a p-value) lands as a single formatted cell, e.g.
f_str("xx.xx (xx.xx, xx.xx)", "or", "lo", "hi"). A scalar return with a
one-variable format is unchanged; an all-NA return or an arity mismatch
renders a blank; the character-return passthrough (#47) still wins for a
finished display string.shift_denom setting for shift layers (#18). shift_denom = "column"
computes percentages column-wise — out of each shift column group (the
"from"/baseline group) within the treatment arm — the standard
"% within the from group" shift display, and the header (N=) labels then
reflect those per-column-group denominators. The default "total" keeps the
arm-total denominator.pct_lt and pct_gt count-layer settings for the regulatory
"less-than / greater-than" percent conventions (#14). A cell with a nonzero
count whose percent would display below pct_lt renders as "<" + the
threshold (e.g. 1 ( <1%) instead of 1 ( 0%)); a percent below 100 that
would display above pct_gt renders as ">" + the threshold (e.g. >99).
The comparison is against the rounded display value, so a percent that
rounds up to the threshold keeps its number.zero_count_display count-layer setting (#14) controlling how cells
with a zero count render: "full" (default, unchanged), "count_only"
(just the count field, e.g. " 0"), or "blank" (empty string).stat_columns layer setting for count layers (#10). Passing a named
list of f_str() objects produces one result column per statistic per
column group — for example, a distinct-subject "n (%)" column beside a raw
event-count "E" column under each treatment arm. Column label attributes
follow the pattern "<column group> (N=n) | <stat name>" so renderers can
span the group over its stat sub-columns. Works with nested count layers,
by variables, total/missing rows, risk difference, cell metadata,
JSON/YAML serialization, and ARD conversion.tplyr_meta objects gain an optional statistic field recording which
statistic a cell displays (populated for stat_columns layers).missing_count gains denom_exclude (#80), which was previously accepted in
config and never implemented. With denom_exclude = TRUE the rows folded into
the Missing row (NA plus anything in missing_values) leave the layer's
denominator, so percentages are of the non-missing population. Every key
missing_count accepts is now documented.assoc_test functions still cannot abort a build and still
render NA as a blank cell, but the reasons are now collected and reported as
one warning per build, deduplicated and naming the summary or test and the
group affected. Previously a partial failure — real numbers everywhere and one
blank cell where the expression errored — was indistinguishable from data
legitimately missing. An assoc_test function whose return does not match its
format's variable count is reported as the caller bug it is.n > 0 against an NA or zero denominator warns, naming the layer and the
affected groups, and tplyr_build() warns when pop_data has no rows for a
column level present in the analysis data.A layer whose where clause left a column group with no rows emitted fewer
result columns than its sibling layers, and those columns were then aligned
positionally — putting that layer's values under the wrong treatment arm.
In tplyr_adae, for example, a where = AESEV == "SEVERE" layer alongside an
unfiltered one reported Xanomeline Low Dose's severe events under the Placebo
label. The column-variable level set is now captured from the table's full data
and pinned for every layer, so an empty column group completes with zeros in
its own position.
A total_group() combined with a custom_group() on the same column variable
double-counted the pooled subjects: the total duplicated the custom group's
copies as well as the originals, so a 254-subject study reported Total (N=422)
and a sex count of 233 where 143 was correct. Duplicated rows now record which
column variable they were created for, so a total group skips copies made on
its own variable while still spanning copies made for a different one.
A shift layer or a stats_as_columns desc layer combined with a standard layer
produced a table whose res columns meant different things in different row
blocks, keeping only the first layer's column labels. Those combinations are now
rejected by validate_spec() with a message pointing at separate specs. (This
replaces a silently mislabeled table, so a spec that "worked" before may now
error — it was not producing a correct table.)
tplyr_meta_subset() treated an empty filter set as "nothing matches" and
returned zero rows. A cell can legitimately have no filters — a total_group()
column crossed with a total row, or with a desc statistic in a layer that has no
by variable — and those cells describe the whole dataset. It now returns all
rows.
Cell metadata dropped the by filter when a by level was an empty string, and
aborted the entire metadata build with "missing value where TRUE/FALSE needed"
when a by level was NA. Both are real levels: an empty string now filters on
"" and NA filters with is.na(). A nested layer's structurally absent inner
label still contributes no filter, as before.
Cell metadata compared by values against the trimmed row label, so a by
variable whose values carry leading or trailing whitespace (common in
SAS-derived character data) produced filters matching zero rows. Filters now use
the untrimmed value.
generate_row_ids() silently produced duplicate IDs when row labels had been
blanked by apply_row_masks() or when a target level collided with a
total_row_label, so metadata lookups resolved to the wrong cell. It now warns.
A missing-subjects row built without distinct_by carried filters resolving to
the subjects that do appear — the exact complement of what the cell counts.
Row-level missing-subjects counting is a population-minus-target difference that
no filter set can express, so no metadata is emitted for it and the build warns.
Cell metadata for a stats_as_columns desc layer resolved to nothing. That
layout labels its result columns "<arm> (N=n) | <statistic>", the same grammar
count-layer stat_columns uses, but the trailing statistic segment was stripped
only for count layers — so every filter read TRT == "A | n" and matched zero
rows. tplyr_meta_subset() now returns the correct source rows.
A count layer's total row rendered a blank instead of 0 for any column
group with no rows in the analysis data, while the category rows above it
correctly showed 0 (#66). n is counted from the raw analysis rows, so an
empty column group never appears there; the denominator join now brings it in
from the completed category counts and zero-fills it. Nothing errored or
warned, so the value silently vanished from a delivered table.
A count layer's total row counted n by summing the category rows while
distinct_n counted from the raw data, so the two disagreed in the same cell
and total_row_count_missings had no effect on n. Category rows exclude NA
target values (data completion drops them) and any level folded into the Missing
row, so the sum silently omitted them. n is now counted from the raw rows with
the same missing handling as distinct_n, making n, distinct_n, and the
cell's metadata agree. A total row over data with missing target values will
change: with the default total_row_count_missings = TRUE it now includes
them, as documented.
tplyr_build(metadata = TRUE) now warns when a stats_as_columns desc layer
has no by variable. That layout names its result columns after the statistics
rather than res1, res2, ..., and cell metadata is keyed on res columns, so
none was produced — previously without any indication.
missing_count's missing_values no longer double-counts. Values named there
are folded into the Missing row, but they also kept their own category row, so
the same records were counted twice and the column summed past 100%. They are
now removed from the category rows, matching Tplyr v1's set_missing_count()
and the exclusion tplyr_meta() already assumed. On a nested count layer,
naming an outer-level value removes its inner rows along with it. This changes
the numbers in any table that used missing_values — previously those tables
were wrong.
f_str(empty = ) now honors its unnamed form. Only c(.overall = "...") was
implemented; an unnamed empty = "NA" was silently ignored. It now fills each
NA format group in place, right-justified to that group's field width, so
f_str("xx (xxx)", "n", "pct", empty = "NA") renders "NA ( NA)" and a
partially missing cell keeps its alignment. This restores v1 parity; .overall
is unchanged and still replaces the whole cell only when every group is NA.
Shift layers now compute the single-proportion confidence-interval keywords
(ci_lower, ci_upper, distinct_ci_lower, distinct_ci_upper). They were
accepted by validation but never computed, so a shift format referencing one
rendered an empty field with no warning. Bounds follow whichever denominator
shift_denom selects.
as_display() no longer discards the result columns of a stats_as_columns
desc layer built without a by variable. That layout names its columns after
the statistics rather than res1, res2, ..., and the whitelist dropped them,
returning row labels alone. It now removes the internal ord_layer_*/row_id
helpers and keeps everything else.
stats_as_columns with no by variable now orders its columns by
format-string order rather than alphabetically by statistic label.
collect_precision() now warns when precision_data does not cover every
precision_by group present in the data (those cells render blank), and when
precision_data omits the precision_by columns entirely (its widths are
applied to every group). Both were silent.
f_str() now warns when a format group requests parenthesis hugging (X/A)
but has no literal text in front of it — there is nothing to hug, and the
number is left-justified with trailing spaces instead.
Count-layer row ordering now honors the sort settings it advertised (#57).
order_count_method = "bycount" actually sorts by descending count (it
previously fell back to the default order); ordering_cols selects which
column's count drives that sort, and result_order_var which statistic;
outer_sort_position = "desc" reverses a nested layer's outer level. Any
explicit order_count_method (and the default) now also keeps by-groups
blocked instead of interleaving them, and the default respects the target
variable's factor levels (previously it ordered the target alphabetically
even when it was a factor). The target sort key is threaded through the cast
so all methods compose correctly with by-groups and special (total/missing)
rows.
order_count_method = "bycount" now also reaches the inner level of a
nested count layer (#64), sorting (e.g.) preferred terms by descending count
within each system organ class -- the AE-by-SOC/PT convention -- with
result_order_var/ordering_cols honored. Previously it only affected
single-level layers. The outer level keeps its own order (controlled by
outer_sort_position), so the useful "outer alphabetical, inner by descending
count" layout comes for free.
risk_diff on a nested count layer now errors instead of silently
emitting an all-blank column (#58). Risk difference is computed only on
single-level count layers; on a nested SOC/PT layer the setting previously
produced an empty rdiff column with no warning. The error points to
pairwise assoc_test(), which does compute a per-level comparison on nested
layers.
group_shift(denom_row = TRUE) no longer renders the literal string "NA"
for a baseline (shift-column) group that is absent within a by group (#55);
an absent group's denominator is zero, so the cell now reads 0 (consistent
with zero_count_display on the shift-to rows). A new denom_row_format
setting also lets the denominator row carry its own f_str width independent
of the n_counts cells (e.g. denom_row_format = f_str("xx", "n") for a plain
narrow integer) instead of inheriting their padding.
Omnibus assoc_test() no longer lets total_group() / custom_group()
duplicate rows leak into the fn's .data (#53). Those rows are a display
construct for the count columns; including them double-counted every subject
and silently returned a wrong p-value (no error, no warning). The synthetic
rows — and their now-unused factor levels (e.g. a phantom "Total" level that
made chisq.test() return NaN) — are dropped before fn runs, so it sees
only the real observations.
Omnibus assoc_test() now places its p-value on the layer's first display
row, not the arbitrary pre-sort (dcast) row (#54). The value was written
before the ord* reorder (e.g. order_count_method = "byfactor"), so it could
strand on the wrong category (landing on 65-80 instead of <65, etc.);
placement is now derived from the ordering columns, per by-group.
group_count() missing_count now always emits the Missing row when set,
zero-filling every column/by group that has no missing values, so the row
reads 0 ( 0%) throughout instead of being dropped (when the total missing
count is zero) or leaving empty cells (when only some columns have missings)
(#33). Matches classic Tplyr set_missing_count().
group_shift(shift_denom = "column") with a by variable now scopes the
column (from-group) denominator within each by-group instead of pooling it
across them (#28). A shift-by-visit table now gets per-visit percentages.
With a by variable the header (N=) reflects the arm total (the
per-column-group denominator varies by by-group, so no single header N can
represent it); the no-by behavior (from-group N in the header) is unchanged.
group_shift() now honors the zero_count_display, pct_lt, and pct_gt
layer settings, applying them the same way group_count() does (#31).
Previously a shift layer ignored them (e.g. a zero cell always rendered as
0 ( 0%) even with zero_count_display = "count_only").
Descriptive statistics that round to negative zero now display as 0.0
instead of -0.0, matching base R format() (#29).
Result and risk-difference columns are now ordered by their numeric suffix
when layers are combined and when metadata is built. Previously tables
with more than 9 result columns sorted them lexicographically
(res10 before res2), scrambling column order.
Count layers now order their res* columns by the cols variable's factor
levels, matching group_desc() (#13). Previously count layers ordered
result columns alphabetically by the cols value, so a spec mixing count
and desc layers (or any renderer assuming res1 is the first cols level)
could get inconsistent column order. Shift layers likewise order their
column dimension by the shift variable's factor levels.
group_count() now orders its by-group rows by the by variable's factor
levels (then a VARN companion, then alphabetically) instead of always
alphabetically (#24). Previously a factor by such as visits came out
mis-ordered (e.g. Week 12 before Week 2), matching group_shift() and
group_desc().
Fixed group_count() total/missing rows with a by variable (#24): each
by-group's Total (or Missing) row is now labelled with its by-group value
instead of a blank, and special rows now sort after the normal rows within
each group (previously e.g. Total was interleaved alphabetically among the
target values, and with a by variable the row label was dropped entirely).
group_count(order_count_method = "byfactor") now orders category rows by the
target variable's factor levels instead of alphabetically (#16). The target
column is coerced to character while counts are built, so the level order is
now recovered from the source data (compute_var_order()). Nested count
layers likewise order their outer and inner categories by factor levels
(previously they fell back to the dcast's alphabetical row order).
group_desc(stats_as_columns = TRUE) combined with a by variable no longer
drops the by-groups and returns only the last group's statistics (#20). It
now keeps the by-groups as rows and produces one result column per
treatment x statistic (labelled "<arm> | <stat>"). Behavior without a by
variable (treatment groups as rows, statistics as columns) is unchanged.
group_desc() now orders its by-group rows by the by variable's factor
levels (then a VARN companion, then alphabetically) instead of always
alphabetically (#20). Previously a factor by such as visits came out
mis-ordered (e.g. Week 12 before Week 2); this applies to both the
standard stats-as-rows output and stats_as_columns = TRUE.
Risk difference and pairwise assoc_test columns came out entirely blank
when by led with a string label (#72). Both merge functions assumed the by
data variables occupied the first rowlabel columns, so with
by = c("Age Group", "SEX") the join keyed the constant-label column against
SEX values and matched nothing. They now share one helper that offsets past
the label columns.
A where clause longer than about 60 characters could not be read back from a
spec file (#70). rlang::expr_deparse() wraps at that width, and the
resulting multi-element array was not something parse_expr() could accept —
so any realistic multi-condition ADaM filter broke the spec file in both
formats. Files written by the old code still read.
precision_cap was silently dropped on a spec-file round trip in both
formats (#69). Both writers dropped the names of the named numeric vector, and
apply_precision_cap() dispatches on those names, so a round-tripped spec
rendered different numbers than the original with no error or warning.
apply_precision_cap() now also warns when given a cap carrying neither an
int nor a dec name.
Multi-element character settings (denoms_by, keep_levels, precision_by,
and friends) deserialized as lists from JSON and broke the build (#68); YAML
was unaffected only because it auto-simplifies. A denoms_by list made
data.table's by= error out. One field-type table now restores every plain
setting's vector type, and a test asserts that no layer_settings() parameter
is missing from it.
A denom_where expression read back from a spec file was evaluated as a call
rather than stored, erroring on the first variable name it contained.
tplyr_from_ard() re-defaulted desc-layer format strings instead of sharing
get_desc_formats() with the build path (#71), reconstructing a 1-row table at
a different width than the 6-row build. Format-string rows also kept dcast's
alphabetical order rather than their declared order, which affected
ARD-reconstructed desc layers and analyze layers alike.
Unknown keys in a spec file are no longer dropped silently (#81). Hand-editing
spec files is supported, and a typo'd key such as total_rows built a table
without the requested behavior and said nothing. Unknown layer settings and
unknown top-level spec keys now warn, naming the layer.
A pop_data that renames the column variable — pop_data(c(TRTA = "TRT01P")) —
skipped total_group() and custom_group() on the population side, because
the rename ran after those were applied. The Total column had no population
rows, so every nonzero count in it displayed 0.0%.
compute_risk_diff() computed the plain difference inside the same
tryCatch() as the confidence interval, so a prop.test() failure blanked the
difference along with the CI even though the difference needs no test (#76). It
also now pre-validates that counts do not exceed their denominators instead of
letting prop.test()'s error be swallowed into an all-NA row.
The formatting vignettes have been reorganized. The two previous articles, "General String Formatting" and "Advanced Descriptive Statistics Formatting", are replaced by three organized around what the user is trying to do rather than by layer type:
vignette("format_strings") — the format string grammar, where format
strings attach per layer type, the complete statistic keyword reference for
count/shift/desc layers, rounding, missing-value handling, and standalone
apply_formats().vignette("precision_alignment") — auto-precision (a/A, +N,
precision_on, precision_by, precision_cap, precision_data) and
parenthesis hugging (X/A).vignette("display_conventions") — the display rules imposed by shells and
SAPs: pct_lt/pct_gt, zero_count_display, stat_columns and
stats_as_columns, keep_levels, missing_count, shift denominators, and
indenting/wrapping nested terms.The old vignettes duplicated auto-precision, hugging, and empty at similar
depth while disagreeing about what hugging does, and their four statistic
keyword lists contradicted each other and the source. The following were
previously documented in no vignette at all and are now covered: pct_lt,
pct_gt, zero_count_display, keep_levels, missing_count,
total_row_count_missings, stats_as_columns, shift_denom, denom_row,
denom_row_label, denom_row_format, the desc-layer total/pct keywords,
and apply_formats()'s na/width/pad/lt/gt arguments.
Corrected the description of parenthesis hugging. tplyr2 moves a hugged group's
leading spaces to just inside the trailing literal's last character
(12 (34.5% )); Tplyr v1 moved them to the left of the opening delimiter
(12 (34.5%)). Two vignettes described v1's behavior. The difference is now
stated explicitly for anyone reconciling output against v1.
Corrected the description of shift_denom = "column": it denominates by the
result column group (arm × post-baseline category), so each result column
sums to 100%. vignette("shift") now shows all three shift denominators,
including how to get row-wise percentages with denoms_by.
Documented that auto-precision (a/A) resolves against the data only in
group_desc() layers; elsewhere it degrades to a fixed width equal to the
number of characters written.
Documented several other scope limits that were previously unstated: the four
confidence-interval keywords are count-layer only (a shift layer accepts them
and renders them empty); the desc-layer total keyword is a record count,
so pct is a share of the arm only on one-row-per-subject data; keep_levels
filters after the denominators are computed, so the kept percentages do not
re-base; precision_data validates only max_int/max_dec, rendering a blank
cell for any group it fails to cover; pct_lt/pct_gt and
zero_count_display target the first matching format group; and str_indent_wrap()
charges an existing indent against width twice.
Documented that literal text in a format string cannot contain x, X, a,
or A — those characters are always parsed as format groups, so a template
like "xx days" silently gains a second group.
vignette("sort") now covers order_count_method = "bycount" on nested count
layers, including that it sorts the inner level only and that
outer_sort_position reverses the outer order rather than ranking it by count.
vignette("post_processing") now covers as_display(),
collapse_row_labels(nest = TRUE), and the apply_formats() na/width
arguments, and points at the declarative pct_lt/zero_count_display
settings before apply_conditional_format().
vignette("riskdiff") now states that risk_diff errors on nested count
layers and points to pairwise assoc_test().
Fixed the IBM-rounding example in vignette("options"), which showed two
identical tables under captions promising a difference.
Fixed a broken vignette("serialize") cross-reference in vignette("ard"),
and expanded the vignette indexes in the README and
vignette("tplyr2"), which listed 10 and 8 of the 19 articles respectively.
print() on an f_str object no longer runs its fields together on one line.
New vignette "Comparative Statistics and Binding External Results"
(vignette("binding-statistics")) — how to attach cross-arm comparisons
(assoc_test(), risk_diff, single-proportion CIs) and how to bind
externally-computed model results (MMRM/ANCOVA/Cox/logistic p-values,
LS-means, CIs) onto an assembled table via apply_formats() and
as_display(), plus where group_analyze() fits.
Clarified in ?layer_settings and the denominators vignette that denoms_by
replaces (does not augment) the default cols-based denominator
grouping, so you must include the cols variable(s) to get per-column
denominators (#19).