| Title: | Manuscript-Compliant Table Construction for HVTI CORR Group |
|---|---|
| Description: | Turns 'gtsummary' table objects into 'MS Word' tables that follow the "Table Construction for Manuscripts" rules of the HVTI (Heart, Vascular and Thoracic Institute) CORR (Cardiovascular Outcomes, Registries and Research) group: footnotes and abbreviation keys as text below the table, no hidden spacer columns, and the house notation and rounding. Tables are drawn in a style named by its role: a report style, with flat headers, and a manuscript style, which lays a table out to compare groups or to describe cohorts side by side. |
| Authors: | John Ehrlinger [aut, cre] |
| Maintainer: | John Ehrlinger <[email protected]> |
| License: | GPL-3 |
| Version: | 1.0.10 |
| Built: | 2026-10-22 00:11:57 UTC |
| Source: | https://github.com/ehrlinger/hvtiRtables |
Read-only inspection of any Word document, whether written by this package or received from elsewhere. Reports the three structural failure patterns the canonical "Table Construction for Manuscripts" memo names, a table too wide for its page, and markup left as plain text, and never modifies the file.
hv_check_docx(path, style = NULL)hv_check_docx(path, style = NULL)
path |
Path to a |
style |
|
Six detectors run:
"layer" (structural, high confidence): paragraphs positioned with
w:framePr, or floating text boxes (w:txbxContent). Table content
belongs in the table, not in a floating layer over it.
"hidden_column" (structural, high confidence): a table column that
is both entirely empty and strictly narrower than 0.1 inch (a
w:gridCol width below 144 dxa; exactly 144 does not count). Both
conditions are required. The house's own JTCVS templates contain
all-empty 0.5-inch gutter columns between group pairs, which are
deliberate; width alone or emptiness alone would flag them. A cell
spanning several columns counts as filling every column it covers,
so a merged header never makes the columns beneath it look empty.
"wide_table" (structural, high confidence): a table whose grid is
wider than the text block of the page section it sits in (page width
less both margins), by more than 1 pt. Word clips what runs past the
margin, so the rightmost columns are lost from the page.
"embedded_footnote" (heuristic, lower confidence): a cell in a
table's last row whose text starts with a footnote marker (*, a
dagger or double dagger, a section or pilcrow sign, a lettered
a./b. marker, Key: or Abbreviation:) or whose first run is a
superscript number, as gtsummary writes a footnote, the pattern
flextable::footnote() produces when misused. This detector is
pattern-based rather than a guaranteed structural signature, so treat
its findings as prompts to look, not proof.
"merged_cell" (structural, high confidence): a row whose cells are
merged (w:gridSpan above 1, w:vMerge, or the legacy w:hMerge)
outside the header rows, unless the row is one cell spanning the
table. The header rows are the leading run of rows whose
w:tblHeader flag is on, as Word
reads it. With style = "report", any merged cell is a finding.
"markup" (heuristic, lower confidence): a paragraph whose baseline
text still holds ^...^ or ~...~ markup, such as "BSA (m^2^)".
Raised and lowered runs are skipped, as they are the renderer's own
output. hv_man_table() and hv_man_table_save() render the markup
as a superscript or subscript, so markup left in the text reached
Word another way, and would print as typed. It is a text pattern, so
a URL such as http://host/~a/~b also matches. Checked whatever
style is given.
A data.frame with one row per finding and columns type,
table (1-indexed table number, NA when document-level),
location (row/column detail where applicable, NA otherwise), and
detail (human-readable description). Zero rows means clean.
"path must be a single non-empty string." The argument is a
file path, not a flextable or an officer document. The saver
does not run this on its own output, so call it on the written .docx
afterwards.
Reading a hidden_column finding as a gutter column. It is not
one. The detector requires empty and narrower than 0.1 inch,
precisely so the deliberate half-inch gutter columns in the house
JTCVS templates do not trip it. A finding here is a real spacer.
Treating embedded_footnote or markup as proof. They are the two
heuristic detectors of the six. embedded_footnote matches a marker
character opening a cell in a table's last row, and a legitimate last
row that happens to start that way matches it too. markup matches a
pair of ^ or ~ characters, and a URL with two tildes matches it.
Read either as a prompt to look, not a verdict.
Expecting a repair. This is read-only and never modifies the
file. Fix what it reports in Word, or rebuild the table through
hv_man_table_save().
hv_man_table_save(), which writes the files this checks. It does
not run the check on its own
output, so call this on the path afterwards when you want it.
tbl <- hv_tbl_summary( gtsummary::trial, by = "trt", groups = list(Demography = "age"), continuous = "age", categorical = character(0), compare = "none" ) ft <- hv_man_table(tbl, style = "manuscript", layout = "describe") out <- tempfile(fileext = ".docx") hv_man_table_save(ft, out, caption = "Table 1. X") hv_check_docx(out) unlink(out)tbl <- hv_tbl_summary( gtsummary::trial, by = "trt", groups = list(Demography = "age"), continuous = "age", categorical = character(0), compare = "none" ) ft <- hv_man_table(tbl, style = "manuscript", layout = "describe") out <- tempfile(fileext = ".docx") hv_man_table_save(ft, out, caption = "Table 1. X") hv_check_docx(out) unlink(out)
The table half of the SAS correlation job
proc corr nosimple spearman pearson fisher(biasadj=no alpha=.32), with
the SpearmanCorr, FisherSpearmanCorr, PearsonCorr and
FisherPearsonCorr ODS tables folded into one data frame. The scatter-plot
matrix (plots=matrix) is hvtiPlotR::hv_correlation_matrix().
hv_correlation_table( data, vars, with = NULL, by = NULL, method = c("spearman", "pearson"), conf_level = 0.68, digits = 2 )hv_correlation_table( data, vars, with = NULL, by = NULL, method = c("spearman", "pearson"), conf_level = 0.68, digits = 2 )
data |
A data frame. |
vars |
Character vector of numeric columns ( |
with |
Character vector of numeric columns to correlate each of |
by |
Optional single column name to stratify on ( |
method |
One or both of |
conf_level |
Confidence level for the Fisher interval, strictly
between 0 and 1. Default |
digits |
Decimal places in |
The interval is Fisher's z without bias adjustment, SAS's biasadj=no:
z = atanh(r), se = 1 / sqrt(n - 3), back-transformed with tanh(). The
same transform is applied to Spearman's coefficient, as SAS does. The
p-value tests rho = 0 on the same z scale, so it is the Fisher p-value SAS
reports alongside the interval, not the t-test p-value of
stats::cor.test(). The interval, by contrast, is identical to
cor.test()'s for Pearson.
conf_level defaults to 0.68, the exemplar's alpha=.32: the house
one-standard-error convention, the same logic as the 15th and 85th
percentiles in hv_tbl_summary(). Pass 0.95 for a conventional interval.
Missing values are deleted pairwise, per pair and per stratum, which is
proc corr's default. Rows with a missing by value are dropped, where
SAS's BY would keep them as their own group.
A data frame, one row per stratum, pair and method, with columns
by (when given), variable, label, with, method, n,
estimate, conf.low, conf.high, p.value and display
("r (lcl, ucl)"). label describes variable only, not the pair.
display is NA whenever the interval is undefined: n <= 3, a
zero-variance column, or an empty stratum. The level used is stored as
attr(x, "conf_level").
hvtiPlotR::hv_correlation_matrix() for the plot.
hv_correlation_table( datasets::mtcars, vars = c("hp", "wt", "qsec"), with = "mpg", by = "am" )hv_correlation_table( datasets::mtcars, vars = c("hp", "wt", "qsec"), with = "mpg", by = "am" )
The house footnote block the macro emitted under PRINTFN=1.
hv_man_footnotes( continuous_stat = c("median", "mean", "both"), percentiles = c(15, 85), style = "report" )hv_man_footnotes( continuous_stat = c("median", "mean", "both"), percentiles = c(15, 85), style = "report" )
continuous_stat |
One of |
percentiles |
Numeric vector of length 2, the increasing whole-number
percentile pair named by the dagger. Default |
style |
|
Two footnotes are universal to the HVTI CORR "Table Construction for
Manuscripts" rules, not specific to any one study, so you don't have to
re-type them at every hv_man_table_save() call site: a * for the
non-missing-value count (house rule 8, exact wording required) and a
† explaining the statistic in each group column, e.g.
"No. (%) or Median [15th, 85th percentiles]." The categorical half is
part of it because the cells print 12 (39) with no % sign (house
rules 10/12), and the flat CORR header names only the group.
hv_man_table_save()'s footnotes parameter defaults to calling
this function, so every table gets both automatically. A table built by
hv_tbl_summary() and hv_man_table() carries its continuous_stat and
percentiles choices through to the saver, so its default dagger follows
what the table shows. Call this function directly with the same arguments
when composing footnotes yourself. Override with ordinary list operations,
no special sentinel values needed:
Suppress both: footnotes = NULL
Change one: modifyList(hv_man_footnotes(), list(...)) with
`†` = "custom text"
Add a study-specific one alongside: c(hv_man_footnotes(), list(...))
with `||` = "extra note". Avoid ‡, § and ¶ on a table from
hv_tbl_summary(): ‡ marks Fisher exact p-values there, and §
and ¶ mark the minority statistic when a table mixes mean and
median.
A named list with elements `*` and `†`, in the format
hv_man_table_save()'s footnotes parameter expects.
Passing the result to the JTCVS saver. These footnotes are
CORR-shaped, keyed by symbol, and belong to hv_man_table_save().
hv_man_table_save_jtcvs() takes an unrelated type keyed by row and
column, and rejects this one. Its equivalent,
hv_test_footnotes_jtcvs(), is deprecated along with that saver: the
describe layout of hv_man_table(style = "manuscript") names its tests
in its own notes, which hv_man_table_save() writes.
Calling this helper with settings different from the table. The
automatic hv_tbl_summary() -> hv_man_table() ->
hv_man_table_save() path stays synchronized. If you call this helper
yourself, pass the same continuous_stat and percentiles used to build
the table, or override the dagger explicitly.
hv_man_footnotes() hv_man_footnotes(continuous_stat = "mean") hv_man_footnotes(continuous_stat = "both", percentiles = c(16, 84)) modifyList(hv_man_footnotes(), list(`†` = "custom text"))hv_man_footnotes() hv_man_footnotes(continuous_stat = "mean") hv_man_footnotes(continuous_stat = "both", percentiles = c(16, 84)) modifyList(hv_man_footnotes(), list(`†` = "custom text"))
Stage 2 of the %summarytable split: shapes the table. The macro
controlled this with STYLE= and PAGE=. style = is the equivalent
of STYLE=: tables are drawn in a style named by its role, "report"
(the default, described below) or "manuscript". PAGE= has none;
pages are US Letter, turning landscape when the table is wide.
hv_man_table( tbl, font = "Times New Roman", font_size = 12, digits = 2, style = "report", layout = "compare", ... )hv_man_table( tbl, font = "Times New Roman", font_size = 12, digits = 2, style = "report", layout = "compare", ... )
tbl |
A |
font |
Font family. Default |
font_size |
Font size in points. Default |
digits |
Kept for interface symmetry with future callers; currently
unused (rounding is controlled upstream via |
style |
A style name, |
layout |
The manuscript style's layout, chosen by what the table
does: |
... |
Not used. Present so that |
The "manuscript" style lays out a comparison table as the journal
submissions did: a full grid, a two-row header with the grouping's name
spanning its levels, one overall n column, shaded section rows that
span the table, a bold header, and no font of its own, so the table
takes the body font of the document it is saved into. It needs a table
from hv_tbl_summary(), and hv_man_table_save() requires a caption
for it. See hv_table_style() for the properties of each style, and
the Table styles
article, which draws one table in each style and layout beside the
matrix of their properties and says which house rules each style
keeps.
The rest of this page describes the "report" style.
You give this a gtsummary::tbl_summary() (or any gtsummary table object
supporting gtsummary::as_flex_table()), and you get back a flextable
that already complies with HVTI CORR's "Table Construction for
Manuscripts" rules: a single, non-merged header row (no spanning parent
cells over grouped columns), no merged row-group section-header cells,
a left-justified label column, and Times New Roman at the house font
size. Section rows are shaded across every cell, unmerged, in the
style's fill (#CAEDFB by default), and the header is bold.
gtsummary::as_flex_table() already emits one header row per group with
self-contained labels (e.g. "B\nN = 45"), not a merged spanning header.
Any merged region is undone with flextable::merge_none(). That
satisfies the "format the table as flat as possible ... simple
non-merged column titles" rule.
The sections of an hv_tbl_summary() table (its groups names, such
as "Demography") become bold header rows in the label column, each
immediately above its first variable, rather than a column of their
own.
Rounding, %-free percentage cells, and ±-without-spaces formatting are
yours to control, via the statistic/digits arguments to
gtsummary::tbl_summary(); see the package README for a worked example
using gtsummary::style_sigfig() for 2-significant-figure rounding.
A manuscript table that describes cohorts or repeated measures side by
side, rather than comparing groups, takes layout = "describe": each
group spans its own count and statistic, so every group has its count.
Everything else is the comparison layout's. The JTCVS functions, whose
layout this is, are deprecated.
A table from hv_tbl_summary() works in every style and layout. That
function writes each cell as "{N_nonmiss} ||| {stat}", and this one
splits the two apart into a flat No. column immediately before the
statistic it counts. Those are the same two values the describe layout
puts under each group's spanning header, without the merge. House
rule 8 wants that non-missing count, and hv_man_footnotes()'s *
footnote describes it, so it is kept rather than discarded. A plain
gtsummary::tbl_summary() table carries no such convention and passes
through unchanged.
None of gtsummary's own footnotes reach the table: as_flex_table()
writes them as a footer row and a raised digit inside the table, and
house rule 13 puts notes below it. hv_tbl_summary() says what they
said in the notes hv_man_table_save() writes: the tests behind the
p-values in the dagger note, and CI in the key.
Superscripts and subscripts are written into the text as in Pandoc:
a label "BSA (m^2^)" raises the 2 and "HbA~1c~" lowers the 1c, in
labels, levels, the by levels and section names alike. A pair holds no
whitespace, so "~5 years" and "x ^ y" print as typed. The notes, the
abbreviation key and the caption hv_man_table_save() writes take the
same markup.
A flextable object, ready for hv_man_table_save(). In the
report style it has a single header row and no merged cells; in the
manuscript style a two-row header. Its hv_style attribute holds the
style, which the saver and hv_man_table_display() read. Input from
hv_tbl_summary() retains its hv_footnotes attribute so the
saver's default dagger follows the table's continuous statistic.
m^2 prints with a caret. A pair needs both markers, so an
unclosed m^2 prints as typed and nothing flags it. Write m^2^. A
pair runs to the next matching marker, so a^2+b^2 raises 2+b;
write a^2^+b^2^.
Cells read 98 ||| 46 (32, 63). You are on a version before the
split; upgrade. ||| is the internal separator hv_tbl_summary()
writes between the count and the statistic, never intended to reach a
rendered table.
"tbl was not built with the {N_nonmiss} ||| {stat} convention."
Only some cells carry the separator, which means statistic was set
for part of the table. Splitting would leave the rest blank, so it is
rejected. Either apply the convention to every variable or to none;
hv_tbl_summary() does the former for you.
"font_size must be 11 or 12." House rule 5. Use 11 only for
wide tables.
"font and font_size set the report style." You passed one with
style = "manuscript". That style sets no font, so the table takes the
font of the document it is saved into. Drop the argument, or use
style = "report".
The compare layout refuses a continuous_stat = "both" table.
That table has a column for the median and another for the mean and
SD, which the compare layout's one statistic column per group cannot
lay out. Build the table with "median" or "mean", or draw it with
layout = "describe" or the report style, which show both.
The manuscript style refuses a table not built by
hv_tbl_summary(). The layout reads the overall count and the
grouping's name that function records. Build the table with it.
"layout chooses between the manuscript style's layouts." You
named a layout with the report style, which has one. Drop layout, or
add style = "manuscript".
hv_man_table_save() to write the result to a compliant
.docx with footnotes and an abbreviation key. hv_table_style() for
each style's properties.
library(gtsummary) tbl <- tbl_summary(trial, by = trt, include = c(age, grade)) ft <- hv_man_table(tbl) ft2 <- hv_man_table( hv_tbl_summary( trial, by = "trt", groups = list(A = c("age", "grade")), continuous = "age", categorical = "grade" ), style = "manuscript", layout = "describe" )library(gtsummary) tbl <- tbl_summary(trial, by = trt, include = c(age, grade)) ft <- hv_man_table(tbl) ft2 <- hv_man_table( hv_tbl_summary( trial, by = "trt", groups = list(A = c("age", "grade")), continuous = "age", categorical = "grade" ), style = "manuscript", layout = "describe" )
Adds to a flextable from hv_man_table() what hv_man_table_save()
writes around it in Word, so a report can show the same table in HTML:
the footnote marks in the header (an asterisk on each No. column, a dagger
on the statistic columns), the footnotes, and the abbreviation key. In Word
the notes are paragraphs below the table; here they are footer lines,
since HTML has no page to put them on.
hv_man_table_display(ft, footnotes = NULL, abbreviations = NULL)hv_man_table_display(ft, footnotes = NULL, abbreviations = NULL)
ft |
A |
footnotes |
Optional named list, symbol -> footnote text, exactly
as for |
abbreviations |
Optional named character vector, abbreviation ->
definition, exactly as for |
The notes follow the table's style: a manuscript table's notes begin with the plain symbol and use the submissions' wording, as they do in the saved file. A manuscript table sets no font, so in HTML it takes the font and size of the page around it, as in Word it takes the document's.
The one deliberate difference is the "Variable" column
hv_tbl_summary()'s show_variable adds: the display keeps it, as the
report view, while the saved table never has it.
The marks, the note text and the key come from the same internal code
the saver uses, so the displayed table and the saved one cannot
disagree. footnotes resolves the same way too: left out, it is the
footnote block the table carries from hv_tbl_summary(), or
hv_man_footnotes() for any other flextable. A double dagger
hv_man_table() put on a Fisher exact p-value is already on its cell
and is footnoted, not marked again in the header.
The result is for display only. Save the flextable you passed in, not
the result: its notes are rows inside the table, which the Word layout
forbids, and hv_man_table_save() refuses it.
ft with the header marks and the footer lines added, for
display.
"ft is a display copy from hv_man_table_display() ..." You passed
the result to hv_man_table_save(). Keep the flextable from
hv_man_table() for saving and display a copy:
hv_man_table_save(ft, file) and hv_man_table_display(ft).
"The table marks cells with ..." The table has a Fisher exact
p-value marked with a double dagger, and footnotes leaves it
unexplained or files another note under it. Omit footnotes, or add
the entry the error names.
Different notes in the report and in Word. Pass the same
footnotes and abbreviations to both calls.
hv_man_table_save(), which writes the same marks and notes
to Word. hv_man_footnotes() for the default footnotes.
ft <- hv_man_table(hv_tbl_summary( gtsummary::trial, by = "trt", groups = list(Demography = c("age", "grade")), continuous = "age", categorical = "grade", compare = "none" )) hv_man_table_display(ft, abbreviations = c(SD = "standard deviation"))ft <- hv_man_table(hv_tbl_summary( gtsummary::trial, by = "trt", groups = list(Demography = c("age", "grade")), continuous = "age", categorical = "grade", compare = "none" )) hv_man_table_display(ft, abbreviations = c(SD = "standard deviation"))
Deprecated. Use
hv_man_table(tbl, style = "manuscript", layout = "describe"), the
manuscript style's describe layout, which this layout became. This
function warns on every call and goes in the next release.
hv_man_table_jtcvs( tbl, groups, trailing = NULL, stat_label = NULL, font = "Times New Roman", font_size = 12, ... )hv_man_table_jtcvs( tbl, groups, trailing = NULL, stat_label = NULL, font = "Times New Roman", font_size = 12, ... )
tbl |
A |
groups |
Named character vector, |
trailing |
Optional named character vector, existing
|
stat_label |
Sub-header text under each group's statistic column.
Default |
font |
Font family. Checked as a single non-empty string, as before, and ignored: the manuscript style sets no font, so the table takes the font of the document it is saved into. |
font_size |
Font size in points. Checked as before ( |
... |
Not used. Present so that |
Stage 2 of the %summarytable split: shapes the table. The macro
controlled this with STYLE= and PAGE=. This renderer is the
journal's one layout and takes neither; the CORR styles are chosen by
style = in hv_man_table(). Pages are US Letter.
Until it goes, it passes its arguments through to that layout: groups
and trailing choose the same group, Overall and comparison columns,
and stat_label names the statistic under each group. The table is
drawn in the manuscript style, not the JTCVS look it drew before: a
full grid, shaded section rows spanning the table, * and † notes,
and no font of its own. font and font_size are checked and set
nothing.
A flextable in the manuscript style's describe layout, ready
for hv_man_table_save().
"tbl was not built with the {N_nonmiss} ||| {stat} convention."
The table came from a plain gtsummary::tbl_summary() call. Build
it with hv_tbl_summary(), which applies the convention
automatically, or pass
statistic = list(all_continuous() ~ "{N_nonmiss} ||| {mean} ({sd})").
Before this check existed, such a table rendered every cell blank.
"groups names must be columns in tbl$table_body." Group
names are the stat_<k> columns gtsummary creates, one per level
of the by variable – not the group labels. Two groups give
stat_1 and stat_2.
"font_size must be 11 or 12." House rule 5. The value is checked
as before and then ignored: the manuscript style sets no font size, so
the table takes the size of the document it is saved into.
hv_man_table(style = "manuscript", layout = "describe"), the
layout that replaces it, and hv_man_table().
hv_man_table_save_jtcvs() to write the result to a compliant .docx.
# Instead of hv_man_table_jtcvs() and hv_man_table_save_jtcvs(): tbl <- hv_tbl_summary( gtsummary::trial, by = "trt", groups = list(Demography = c("age", "grade")), continuous = "age", categorical = "grade", compare = "none" ) ft <- hv_man_table(tbl, style = "manuscript", layout = "describe") out <- tempfile(fileext = ".docx") hv_man_table_save(ft, out, caption = "Table 2. Baseline Characteristics") unlink(out)# Instead of hv_man_table_jtcvs() and hv_man_table_save_jtcvs(): tbl <- hv_tbl_summary( gtsummary::trial, by = "trt", groups = list(Demography = c("age", "grade")), continuous = "age", categorical = "grade", compare = "none" ) ft <- hv_man_table(tbl, style = "manuscript", layout = "describe") out <- tempfile(fileext = ".docx") hv_man_table_save(ft, out, caption = "Table 2. Baseline Characteristics") unlink(out)
Stage 3 of the %summarytable split: writes the document. Replaces
the macro's RTFFILE=/PDFFILE= output block.
hv_man_table_save( ft, file, footnotes = NULL, abbreviations = NULL, caption = NULL, ... )hv_man_table_save( ft, file, footnotes = NULL, abbreviations = NULL, caption = NULL, ... )
ft |
A |
file |
Output |
footnotes |
Optional named list, symbol -> footnote text. When omitted,
uses the |
abbreviations |
Optional named character vector, |
caption |
The table's caption, written in bold on its own line
above the table. A manuscript table requires one, so it is written
with the table and not typed in later; a report table takes one when
given and writes none otherwise.
It may carry |
... |
Not used. Present so that |
You hand this a flextable (typically from hv_man_table()), and it
writes a Word document with footnotes and an abbreviation key rendered
as text below the table rather than embedded within it (house rules
13-14). flextable::footnote() cannot do this for you: it renders
footnote text as an extra row inside the table's own <w:tbl> block (a
"footer" table part), which is exactly the compliance violation this
function exists to avoid. Instead, a superscript reference symbol is
appended to the target header cell (a normal, compliant reference
mark), and the footnote text itself is written as a genuine document
paragraph after the table via officer::body_add_fpar(). The table
itself is inserted directly (no leading blank paragraph), which keeps
vertical cell alignment controllable if you later reformat the
surrounding document to 1.5- or double-spacing. House rule 2 concerns
the insertion point in the destination document, not this function's
output; see the package README for the paste-in workflow.
Every style is written on a US Letter page. A report table with three
or more statistic columns, such as an Overall column and two groups, is
written on a landscape page: with a No. column beside each, it does
not fit a portrait one. A manuscript table turns only on width, since
it has one overall n column: Overall, two groups and a P value come
to 6.5 inches and stay portrait. Any table wider than a portrait page's
text block (6.53 inches on US Letter) is written landscape. A table
wider than landscape's (9.0 inches), as a continuous_stat = "both"
table with an Overall column can be, has its columns narrowed in
proportion to fit; the font size is not changed.
In every style the saver drops the "Variable" column
hv_tbl_summary()'s show_variable adds for the report view before
the page is fitted, so it never reaches Word and never widens the page.
hv_man_table_display() keeps it, for the report.
Invisibly, the file path.
"A manuscript table needs caption ..." The table is in the
manuscript style, which requires a caption so the title is written with
the table. Pass
caption = "Table 1. ..."; a report table takes one optionally.
"footnotes must be a named list (symbol -> footnote text) ..."
You passed the deprecated JTCVS shape. The two savers' footnotes
arguments are unrelated types: this one is keyed by symbol,
list(`*` = "text"), and hv_man_table_save_jtcvs()'s is keyed
by position, list(list(row =, col =, text =)). So
hv_test_footnotes_jtcvs()'s output does not belong here;
hv_man_footnotes()'s does.
"Invalid footnote symbol(s): ..." Symbols are drawn from the
house set * † ‡ § ¶ ||. A letter or a digit is not
one of them; lettered markers are the JTCVS renderer's convention.
"Output directory does not exist: ..." The dirname() of file
is not created for you. Run
dir.create(dirname(file), recursive = TRUE) first.
A hand-built footnote that misstates the table. The automatic
hv_tbl_summary() -> hv_man_table() path keeps the default dagger in
sync. When supplying footnotes yourself, make its wording match the
statistic and percentile pair used to build the table.
hv_man_table() to build a compliant flextable first.
hv_man_footnotes() for details on the default footnotes.
library(gtsummary) tbl <- tbl_summary(trial, by = trt, include = c(age, grade)) ft <- hv_man_table(tbl) out <- tempfile(fileext = ".docx") hv_man_table_save(ft, out, abbreviations = c(N = "sample size")) unlink(out)library(gtsummary) tbl <- tbl_summary(trial, by = trt, include = c(age, grade)) ft <- hv_man_table(tbl) out <- tempfile(fileext = ".docx") hv_man_table_save(ft, out, abbreviations = c(N = "sample size")) unlink(out)
Deprecated. Use
hv_man_table_save(ft, file, caption = ) on a table from
hv_man_table(tbl, style = "manuscript", layout = "describe"). This
function warns on every call and goes in the next release.
hv_man_table_save_jtcvs( ft, file, caption, footnotes = NULL, abbreviations = NULL, ... )hv_man_table_save_jtcvs( ft, file, caption, footnotes = NULL, abbreviations = NULL, ... )
ft |
A |
file |
Output |
caption |
Full caption text, e.g. |
footnotes |
Optional list of |
abbreviations |
Optional named character vector, same as
|
... |
Not used. Present so that |
Stage 3 of the %summarytable split: writes the document. Replaces
the macro's RTFFILE=/PDFFILE= output block.
Until it goes, it writes the table as hv_man_table_save() does, with
caption above it and the table's own notes below. A test footnote
from hv_test_footnotes_jtcvs() is dropped when the table already
names that test, as a table from hv_tbl_summary() does in its dagger
note; every other footnotes entry, a plain gtsummary table's test
notes included, keeps a lettered superscript on its cells, lettered
from a, and its line after the table's own notes.
Invisibly, the file path.
"footnotes\[\[k\]\]$text must be a single non-empty string."
Every footnote needs all three of row, col, and text, and
text must be one non-empty string. Before this check existed, an
entry missing text wrote a document with a dangling superscript
marker and an empty footnote line.
"footnotes[[k]] must be a list of the form list(row =, col =,
text =)." footnotes is a list of footnotes, so a single one is
list(list(row = 1, col = "n_stat_1", text = "...")). Dropping the
outer list() previously failed with $ operator is invalid for atomic vectors.
"footnotes[[k]]$row must be whole numbers ..." Row indices
count the section-header rows hv_man_table_jtcvs() interleaves
into the body. hv_test_footnotes_jtcvs() computes them for you.
"file must be a single non-empty file path." Check the
argument order: it is (ft, file, caption).
"Output directory does not exist: ..." The directory part of
file (its dirname()) is not created for you; create it first
with dir.create(dirname(file), recursive = TRUE).
A wide table on a landscape page. Expected: a table wider than a
portrait page's text block (6.5 inches on US Letter), as a "both" table or
several trailing columns make it, is saved on a
landscape page. One wider than landscape's (9.0 inches) has its
columns narrowed in proportion to fit; the font size is not changed.
hv_man_table_jtcvs() to build a compliant flextable first.
# Instead of hv_man_table_jtcvs() and hv_man_table_save_jtcvs(): tbl <- hv_tbl_summary( gtsummary::trial, by = "trt", groups = list(Demography = c("age", "grade")), continuous = "age", categorical = "grade", compare = "none" ) ft <- hv_man_table(tbl, style = "manuscript", layout = "describe") out <- tempfile(fileext = ".docx") hv_man_table_save(ft, out, caption = "Table 2. Baseline Characteristics") unlink(out)# Instead of hv_man_table_jtcvs() and hv_man_table_save_jtcvs(): tbl <- hv_tbl_summary( gtsummary::trial, by = "trt", groups = list(Demography = c("age", "grade")), continuous = "age", categorical = "grade", compare = "none" ) ft <- hv_man_table(tbl, style = "manuscript", layout = "describe") out <- tempfile(fileext = ".docx") hv_man_table_save(ft, out, caption = "Table 2. Baseline Characteristics") unlink(out)
The properties a table is drawn with, from the style matrix of the
table-styles design: section rows, fill, borders, header, count
column, note markers and wording, font, caption, row height, page and
merged cells. A style is named for what the table is for, not for a
journal. "report" is the working table of an analysis report, as
hv_man_table() has always drawn it. "manuscript" is the table a
manuscript submits: a full grid, a two-row header with the grouping
spanning its levels, one overall n column, shaded section rows
spanning the table, a caption, and no font of its own, so it takes the
font of the document it is saved into.
hv_table_style(name = c("report", "manuscript"), fill = "#CAEDFB")hv_table_style(name = c("report", "manuscript"), fill = "#CAEDFB")
name |
|
fill |
The section rows' shading, one color written |
Both styles follow the same house notation (medians, P values, marks),
which hv_tbl_summary() writes, so a report reads like the manuscript
and only the layout differs.
A list of class "hv_table_style", one element per property,
for hv_man_table()'s style argument.
hv_man_table(), which draws a table in a style. The
Table styles
article prints every style's properties, read from this object.
hv_table_style("manuscript", fill = "#A5C9EB")$fillhv_table_style("manuscript", fill = "#A5C9EB")$fill
Adds one column to a table from hv_tbl_summary(), holding a value you
computed yourself for each variable: typically the standardized
difference %summarytable printed, which comes from the %stddiff
macro and is not the number hv_tbl_summary()'s compare = "smd"
gives. hvtiRpropensity::ps_stddiff() is the R port of %stddiff;
this function is how its output reaches the table, without hvtiRtables
depending on hvtiRpropensity.
hv_tbl_compare(tbl, values, label)hv_tbl_compare(tbl, values, label)
tbl |
A |
values |
Named character vector: variable name -> the text to show on that variable's first row. |
label |
The column header, e.g. |
Each value goes on its variable's first row, as %summarytable laid it
out: beside the label of a categorical variable, not on every level.
The column is placed rightmost, after any compare column, so
compare = "pvalue" followed by this function puts the p-value and
the standardized difference side by side. Call it again for a further
column. hv_man_table() shows the column directly;
The manuscript style shows it too, from the table's hv_trailing
attribute, which this function extends.
The values are shown exactly as given, so format them first, for
example sprintf("%.1f", 100 * d) for a difference as a percentage.
A variable missing from values is left blank.
tbl with the column added and its hv_trailing attribute
extended by the new column.
"values must be a character vector named by variable ..."
Numbers are not formatted for you. Convert them with sprintf() or
format(), and name each element by the variable's column name, the
name in groups, not its label.
"values names variable(s) not in the table: ..." A name that
matches no variable would silently show nothing. Usually a typo, or a
variable dropped from groups.
hv_tbl_summary(), whose compare argument computes a
p-value and gtsummary's generic standardized difference.
tbl <- hv_tbl_summary( gtsummary::trial, by = "trt", groups = list(Demography = c("age", "grade")), continuous = "age", categorical = "grade", compare = "pvalue", show_variable = FALSE ) # Standardized differences computed elsewhere, e.g. by # hvtiRpropensity::ps_stddiff(), as proportions. d <- c(age = 0.031, grade = 0.120) tbl <- hv_tbl_compare( tbl, stats::setNames(sprintf("%.1f", 100 * d), names(d)), label = "Std. Diff. (%)" ) hv_man_table(tbl)tbl <- hv_tbl_summary( gtsummary::trial, by = "trt", groups = list(Demography = c("age", "grade")), continuous = "age", categorical = "grade", compare = "pvalue", show_variable = FALSE ) # Standardized differences computed elsewhere, e.g. by # hvtiRpropensity::ps_stddiff(), as proportions. d <- c(age = 0.031, grade = 0.120) tbl <- hv_tbl_compare( tbl, stats::setNames(sprintf("%.1f", 100 * d), names(d)), label = "Std. Diff. (%)" ) hv_man_table(tbl)
Thin wrapper over gtsummary::tbl_summary() modeled on the interface
biostats team members already know from the %summarytable SAS macro:
a grouped, ordered variable list (groups, the macro's LIST=
equivalent) and variable-type buckets (continuous/binary/
categorical, the CON3=/CAT1=/CAT2= equivalents), rather than
gtsummary's own tidyselect-based interface. Returns a plain gtsummary
object, ready for hv_man_table().
hv_tbl_summary( data, by = NULL, groups, continuous = character(0), binary = character(0), categorical = character(0), compare = c("pvalue", "smd", "both", "none"), percentiles = c(15, 85), overall = TRUE, continuous_stat = c("median", "mean", "both"), normal = character(0), mean_sd = character(0), show_variable = TRUE, cutexact = "any", ... )hv_tbl_summary( data, by = NULL, groups, continuous = character(0), binary = character(0), categorical = character(0), compare = c("pvalue", "smd", "both", "none"), percentiles = c(15, 85), overall = TRUE, continuous_stat = c("median", "mean", "both"), normal = character(0), mean_sd = character(0), show_variable = TRUE, cutexact = "any", ... )
data |
A data frame. |
by |
Grouping variable name as a string ( |
groups |
Named list, section label -> variable names in display
order ( |
continuous |
Character vector of continuous variable names
( |
binary |
Character vector of 0/1 variable names ( |
categorical |
Character vector of multi-level variable names
( |
compare |
One of |
percentiles |
Numeric vector of length 2, the low/high percentile
pair for continuous summaries, as increasing whole numbers between 0
and 100. Default |
overall |
Single |
continuous_stat |
One of |
normal |
Character vector, a subset of |
mean_sd |
Character vector, a subset of |
show_variable |
Single |
cutexact |
When a binary or categorical variable is tested with
Fisher's exact test rather than Pearson's chi-square (no continuity
correction). The default, |
... |
Not used. Present so that |
Continuous variables are summarized as median [P<low>, P<high>] by
default, or as mean +/- SD, or both (continuous_stat), and tested
non-parametrically (Wilcoxon rank-sum for 2 groups, Kruskal-Wallis for
3+), the macro's CON3=. A variable you also list in normal is the
macro's CON1= instead: mean +/- SD, tested with one-way ANOVA.
percentiles defaults to the house convention documented in
hv_man_footnotes() (15th/85th), overridable per study
(%summarytable equivalent: PP=).
The house notation: each median and percentile is shown to 2 significant
digits, and a value of 10 or more to the units place (102 [63, 170],
0.31 [0.21, 0.39]), and a P value has no leading zero (.013,
<.001, >.9).
When rows of one table show different statistics, the header and dagger
name the statistic most rows use, and the cells of the other are marked
instead of the row labels (the macro's DESCLABEL=1 suffixes, such as
"Age, Mean±SD", are gone). When more rows show mean +/- SD than a
median, the mean is the default and a section sign (§) marks the
median cells; otherwise a pilcrow (¶) marks the mean cells. Each mark
has its own footnote. A tie leaves the median as the default.
Mean/SD output follows Eugene H. Blackstone's (EHB's) paired reporting
rule: the mean is rounded to the first-significant-digit place of the SD,
and the SD is rounded one place finer. When the SD begins with 1, both
retain one additional place. Exact ties round to even; values beyond a tie
round up. This applies to every group and Overall column when
continuous_stat = "mean", and to the mean row under "both". Medians and
percentiles follow the house notation above instead, and their exact ties
also round to even, as for mean +/- SD: 62.5 shows as 62, where gtsummary
and SAS showed 63. When an SD is zero or unavailable, no paired precision
can be inferred, so gtsummary's existing display is kept.
The returned object carries these renderer attributes:
hv_stat_label, the sub-header text naming the statistics shown
("No. (%) or Median [<low>, <high> percentiles]" by default, with
ordinal suffixes; it follows continuous_stat);
hv_trailing, a named character vector ready to pass as
naming the comparison columns the manuscript style shows, when compare
produced one (NULL when compare = "none"); and hv_footnotes,
the CORR footnote block that hv_man_table() carries into
hv_man_table_save() so the default dagger follows continuous_stat and
percentiles automatically. A mixed table also carries hv_cell_marks,
the symbol and variables whose cells hv_man_table() marks, and a table
built with continuous_stat = "both" carries hv_median_label.
A gtsummary object, ready for hv_man_table().
See Details for the hv_stat_label,
hv_trailing, and hv_footnotes renderer attributes. A table whose
rows mix mean and median also carries hv_cell_marks, a list with
symbol ("§" or "¶") and variables (the names whose cells
carry it); the attribute is NULL otherwise. The manuscript style
also reads hv_by_label, the grouping's name (the variable's label
when the data carry one), and the hidden hv_n column of
table_body, each variable's overall non-missing count.
"<var> appears in more than one of continuous, binary, and
categorical." Each variable is classified exactly once. A 0/1
variable is binary; a multi-level factor is categorical.
"continuous lists <var>, but <var> is factor data ..." The
bucket has to suit the data, not just be free of overlaps. A
non-numeric variable in continuous used to reach gtsummary,
which only messages about it, and produced a complete, correctly
styled table whose every statistic was NA.
"binary lists <var>, but <var> has ..." binary renders
one n (%) row, so it takes at most two distinct values and needs
an unambiguous event value: logical, 0/1, or Yes/No. A
three-level factor, or a two-level one like F/M, belongs in
categorical.
"Variable(s) in groups not classified ..." Every variable
listed in groups also needs a type bucket, and every classified
variable needs to appear in groups. The two lists must match.
"compare = "smd" requires exactly two groups." A standardized
mean difference is defined between two groups. Use
compare = "pvalue" for three or more. If by is a factor with an
unused level, droplevels() is usually what you want.
"by must not also be listed in groups." by is the
grouping variable being compared across, not a row to summarize.
Before this check existed, the combination reached gtsummary and
failed several calls later with "names must be NULL or a
character vector, not an empty integer vector.", a message that
never mentioned by. Remove the variable from groups.
hv_man_table() to render the result. hv_man_footnotes() for
the percentile-footnote house convention.
# A baseline-characteristics table of the kind a study manuscript # actually carries: demography and disease sections, compared # across treatment arms. hv_tbl_summary( gtsummary::trial, by = "trt", groups = list( Demography = c("age", "marker"), Disease = c("stage", "grade") ), continuous = c("age", "marker"), categorical = c("stage", "grade") )# A baseline-characteristics table of the kind a study manuscript # actually carries: demography and disease sections, compared # across treatment arms. hv_tbl_summary( gtsummary::trial, by = "trt", groups = list( Demography = c("age", "marker"), Disease = c("stage", "grade") ), continuous = c("age", "marker"), categorical = c("stage", "grade") )
Deprecated. A table from
hv_man_table(tbl, style = "manuscript", layout = "describe") names its
tests in its dagger note and marks Fisher's exact test on its P values,
so it needs no test footnotes. This function warns on every call and goes
in the next release.
hv_test_footnotes_jtcvs(tbl)hv_test_footnotes_jtcvs(tbl)
tbl |
A |
Reproduces the macro's lettered test footnotes. The letters differ by
design: the macro lettered by test (a=ANOVA, b=Kruskal-Wallis,
...), while here letters follow the order the tests appear. One-way
ANOVA appears only for variables listed in hv_tbl_summary()'s
normal.
The SAS %summarytable macro marks every p-value with a superscript
letter naming the test behind it. This builds those footnotes from the
test_name column gtsummary::add_p() records, in the format
hv_man_table_save_jtcvs()'s footnotes argument expects, so you do
not have to map test identifiers or count body rows by hand.
hv_man_table_save_jtcvs() drops the ones a describe table already names
itself.
Takes the gtsummary object rather than the flextable, because
hv_man_table_jtcvs() discards test_name when it reshapes the body.
The returned row indices are rendered body rows, counting the
section-header rows hv_man_table_jtcvs() interleaves, and are
therefore valid only against that renderer's output.
Letters follow a fixed order (Wilcoxon, Kruskal-Wallis, chi-square, Fisher), filtered to the tests actually used, so two tables in the same manuscript that use the same tests get the same letters regardless of variable order. Every row sharing a test collapses onto one letter.
Returns an empty list when there is nothing to mark: by = NULL,
compare = "none", or compare = "smd", since a standardized
difference is not a test. An empty list concatenates and renders
harmlessly, so no conditional is needed at the call site.
With compare = "both" the cell reads e.g. 0.7 (SMD -0.03) and the
letter marks the p-value portion of it. Note also that
gtsummary::add_difference() requires exactly two groups, so
compare = "smd" and compare = "both" are unavailable for a 3-group
table; compare = "pvalue" is.
An unrecognized test_name (only reachable from a hand-built object
that passed add_p(test = ...), since hv_tbl_summary() never sets
test) is kept, using the raw identifier as its footnote text, and
warned about. A marked cell whose letter has no definition would be
worse than an unpolished label.
A list of list(row =, col =, text =) entries, one per distinct
test, ready to pass as hv_man_table_save_jtcvs()'s footnotes
argument. list() when there is nothing to mark.
"tbl must be a gtsummary table object." You passed the
flextable. This needs the gtsummary object, because
hv_man_table_jtcvs() drops the test_name column when it reshapes
the body – by render time the test identities are gone. Pass the
same object you passed to the renderer.
An empty list back, and no footnotes in the document. Expected,
not a failure: nothing is marked when by = NULL,
compare = "none", or compare = "smd", since a standardized
difference names no test. An empty list concatenates and renders
harmlessly, so the call site needs no conditional.
Row indices that land on the wrong rows. They are computed from
tbl$table_body at the moment you call this, and they count the
section-header rows hv_man_table_jtcvs() interleaves, so they are
valid against that renderer's output and nothing else. Finish the
table first and call this last.
Passing the result to hv_man_table_save(). This is the JTCVS
shape. The CORR saver takes a symbol-keyed list and rejects it; its
equivalent is hv_man_footnotes().
hv_man_table_save_jtcvs(), which renders the result.
hv_tbl_summary(), which produces a suitable tbl.
hv_man_footnotes() for the house-universal footnotes, which are
CORR-shaped and belong to hv_man_table_save(), not to this
function's JTCVS output.
# Instead of hv_test_footnotes_jtcvs(): tbl <- hv_tbl_summary( gtsummary::trial, by = "trt", groups = list(Demography = c("age", "grade")), continuous = "age", categorical = "grade", compare = "pvalue" ) ft <- hv_man_table(tbl, style = "manuscript", layout = "describe") attr(ft, "hv_footnotes")[["\u2020"]]# Instead of hv_test_footnotes_jtcvs(): tbl <- hv_tbl_summary( gtsummary::trial, by = "trt", groups = list(Demography = c("age", "grade")), continuous = "age", categorical = "grade", compare = "pvalue" ) ft <- hv_man_table(tbl, style = "manuscript", layout = "describe") attr(ft, "hv_footnotes")[["\u2020"]]