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.
In tplyr2, a table is defined by its specification.
The tplyr_spec() function captures the full configuration –
column variables, filters, treatment groups, population data, and layers
– as a pure description of what you want. No data processing happens
until you call tplyr_build(). This vignette covers the
spec-level parameters that control the overall structure of your
table.
Every tplyr2 workflow follows two steps: define a
spec with tplyr_spec(), then build the
table with tplyr_build(spec, data). Let’s look at an
example using the included tplyr_adsl dataset.
spec <- tplyr_spec(
cols = "TRT01P",
layers = tplyr_layers(
group_count(target_var = "SEX")
)
)
result <- tplyr_build(spec, tplyr_adsl)
kable(result[, c("rowlabel1", grep("^res", names(result), value = TRUE))])| rowlabel1 | res1 | res2 | res3 |
|---|---|---|---|
| F | 53 (61.6%) | 40 (47.6%) | 50 (59.5%) |
| M | 33 (38.4%) | 44 (52.4%) | 34 (40.5%) |
Note how the cols parameter defines the column structure
of the output. Each unique value of TRT01P becomes a result
column, and the column labels automatically include the group count as
(N=xx).
The cols parameter accepts a character vector of one or
more variable names that define the columns of your output table. The
most common case is a single treatment variable:
spec <- tplyr_spec(
cols = "TRT01P",
layers = tplyr_layers(
group_count(target_var = "AGEGR1")
)
)
result <- tplyr_build(spec, tplyr_adsl)
kable(result[, c("rowlabel1", grep("^res", names(result), value = TRUE))])| rowlabel1 | res1 | res2 | res3 |
|---|---|---|---|
| <65 | 14 (16.3%) | 11 (13.1%) | 8 ( 9.5%) |
| 65-80 | 42 (48.8%) | 55 (65.5%) | 47 (56.0%) |
| >80 | 30 (34.9%) | 18 (21.4%) | 29 (34.5%) |
When you provide multiple variables, tplyr2 creates a cross of all combinations. This is useful when you need columns split by treatment and another variable.
spec <- tplyr_spec(
cols = c("TRT01P", "SEX"),
layers = tplyr_layers(
group_count(target_var = "AGEGR1")
)
)
result <- tplyr_build(spec, tplyr_adsl)
kable(result[, c("rowlabel1", grep("^res", names(result), value = TRUE))])| rowlabel1 | res1 | res2 | res3 | res4 | res5 | res6 |
|---|---|---|---|---|---|---|
| <65 | 9 (17.0%) | 5 (15.2%) | 5 (12.5%) | 6 (13.6%) | 5 (10.0%) | 3 ( 8.8%) |
| 65-80 | 22 (41.5%) | 20 (60.6%) | 28 (70.0%) | 27 (61.4%) | 28 (56.0%) | 19 (55.9%) |
| >80 | 22 (41.5%) | 8 (24.2%) | 7 (17.5%) | 11 (25.0%) | 17 (34.0%) | 12 (35.3%) |
Notice that the column labels use a " | " separator to
show the cross of treatment and sex, and each combination gets its own
N.
whereThe where parameter applies a filter to all data before
any layer processing begins. This is useful when records should be
excluded from the entire table.
spec <- tplyr_spec(
cols = "TRT01P",
where = SAFFL == "Y",
layers = tplyr_layers(
group_count(target_var = "AGEGR1", by = "Age Group"),
group_desc(
target_var = "AGE",
by = "Age (Years)",
settings = layer_settings(
format_strings = list(
"n" = f_str("xxx", "n"),
"Mean (SD)" = f_str("xx.x (xx.xx)", "mean", "sd"),
"Median" = f_str("xx.x", "median"),
"Min, Max" = f_str("xx, xx", "min", "max")
)
)
)
)
)
result <- tplyr_build(spec, tplyr_adsl)
kable(result[, c("rowlabel1", "rowlabel2", grep("^res", names(result), value = TRUE))])| rowlabel1 | rowlabel2 | res1 | res2 | res3 |
|---|---|---|---|---|
| Age Group | <65 | 14 (16.3%) | 11 (13.1%) | 8 ( 9.5%) |
| Age Group | 65-80 | 42 (48.8%) | 55 (65.5%) | 47 (56.0%) |
| Age Group | >80 | 30 (34.9%) | 18 (21.4%) | 29 (34.5%) |
| Age (Years) | n | 86 | 84 | 84 |
| Age (Years) | Mean (SD) | 75.2 ( 8.59) | 74.4 ( 7.89) | 75.7 ( 8.29) |
| Age (Years) | Median | 76.0 | 76.0 | 77.5 |
| Age (Years) | Min, Max | 52, 89 | 56, 88 | 51, 88 |
Both the count and descriptive statistics layers are computed on the
safety population. Individual layers can also have their own
where filters, which are applied in addition to the
table-level filter.
Clinical tables often need columns beyond the individual treatment arms. tplyr2 provides total groups and custom groups for this purpose.
A total group creates a synthetic column that includes all subjects by duplicating every row with the column variable set to the total group label.
spec <- tplyr_spec(
cols = "TRT01P",
total_groups = list(
total_group("TRT01P", label = "Total")
),
layers = tplyr_layers(
group_count(target_var = "SEX")
)
)
result <- tplyr_build(spec, tplyr_adsl)
kable(result[, c("rowlabel1", grep("^res", names(result), value = TRUE))])| rowlabel1 | res1 | res2 | res3 | res4 |
|---|---|---|---|---|
| F | 53 (61.6%) | 143 (56.3%) | 40 (47.6%) | 50 (59.5%) |
| M | 33 (38.4%) | 111 (43.7%) | 44 (52.4%) | 34 (40.5%) |
The “Total” column now appears alongside the individual treatment arms, with its N reflecting the sum of all subjects.
Custom groups combine specific treatment levels into a new group. For example, you might pool the two active dose groups together.
spec <- tplyr_spec(
cols = "TRT01P",
custom_groups = list(
custom_group(
"TRT01P",
"Xanomeline" = c("Xanomeline High Dose", "Xanomeline Low Dose")
)
),
layers = tplyr_layers(
group_count(target_var = "SEX")
)
)
result <- tplyr_build(spec, tplyr_adsl)
kable(result[, c("rowlabel1", grep("^res", names(result), value = TRUE))])| rowlabel1 | res1 | res2 | res3 | res4 |
|---|---|---|---|---|
| F | 53 (61.6%) | 90 (53.6%) | 40 (47.6%) | 50 (59.5%) |
| M | 33 (38.4%) | 78 (46.4%) | 44 (52.4%) | 34 (40.5%) |
The “Xanomeline” column includes all subjects from both dose groups, while the original dose-level columns are preserved.
You can use both together. Each synthetic column is built by duplicating the rows it needs: a custom group copies the rows of the levels it pools, and a total group copies the original rows. The total group deliberately ignores the custom group’s copies, so every subject is counted exactly once in the “Total” column – its N matches the study population, not the population plus the pooled arms.
spec <- tplyr_spec(
cols = "TRT01P",
custom_groups = list(
custom_group(
"TRT01P",
"Xanomeline" = c("Xanomeline High Dose", "Xanomeline Low Dose")
)
),
total_groups = list(
total_group("TRT01P", label = "Total")
),
layers = tplyr_layers(
group_count(target_var = "SEX")
)
)
result <- tplyr_build(spec, tplyr_adsl)
kable(result[, c("rowlabel1", grep("^res", names(result), value = TRUE))])| rowlabel1 | res1 | res2 | res3 | res4 | res5 |
|---|---|---|---|---|---|
| F | 53 (61.6%) | 143 (56.3%) | 90 (53.6%) | 40 (47.6%) | 50 (59.5%) |
| M | 33 (38.4%) | 111 (43.7%) | 78 (46.4%) | 44 (52.4%) | 34 (40.5%) |
In many clinical analyses, denominators and header Ns should come
from a different dataset than the analysis data. The classic example is
an adverse event table: ADAE only contains subjects who
experienced events, but percentages should reflect the full safety
population from ADSL.
The pop_data() configuration specifies how the
population dataset maps to the spec. The actual data is provided at
build time.
spec <- tplyr_spec(
cols = "TRTA",
pop_data = pop_data(cols = c("TRTA" = "TRT01A")),
layers = tplyr_layers(
group_count(
target_var = "AEBODSYS",
settings = layer_settings(
distinct_by = "USUBJID"
)
)
)
)
result <- tplyr_build(spec, tplyr_adae, pop_data = tplyr_adsl)
kable(head(result[, c("rowlabel1", grep("^res", names(result), value = TRUE))], 8))| rowlabel1 | res1 | res2 | res3 |
|---|---|---|---|
| CARDIAC DISORDERS | 5 ( 5.8%) | 6 ( 7.1%) | 6 ( 7.1%) |
| CONGENITAL, FAMILIAL AND GENETIC DISORDERS | 0 ( 0.0%) | 1 ( 1.2%) | 0 ( 0.0%) |
| GASTROINTESTINAL DISORDERS | 6 ( 7.0%) | 6 ( 7.1%) | 3 ( 3.6%) |
| GENERAL DISORDERS AND ADMINISTRATION SITE CONDITIONS | 11 (12.8%) | 21 (25.0%) | 21 (25.0%) |
| IMMUNE SYSTEM DISORDERS | 0 ( 0.0%) | 0 ( 0.0%) | 1 ( 1.2%) |
| INFECTIONS AND INFESTATIONS | 5 ( 5.8%) | 4 ( 4.8%) | 3 ( 3.6%) |
| INJURY, POISONING AND PROCEDURAL COMPLICATIONS | 2 ( 2.3%) | 2 ( 2.4%) | 2 ( 2.4%) |
| INVESTIGATIONS | 3 ( 3.5%) | 1 ( 1.2%) | 1 ( 1.2%) |
A few things to note:
cols = "TRTA" matches the treatment variable in
ADAE.pop_data(cols = c("TRTA" = "TRT01A")) maps
TRT01A in the population data to TRTA in the
analysis data (format:
c("analysis_name" = "pop_name")).distinct_by = "USUBJID" counts each subject once per
body system.tplyr_adsl population.After building a table with population data, you can extract the
header N values using tplyr_header_n():
| TRTA | .n |
|---|---|
| Placebo | 86 |
| Xanomeline High Dose | 84 |
| Xanomeline Low Dose | 84 |
This is useful when you need to programmatically construct column headers or integrate with other reporting tools.
The population data is not subject to the spec-level
where filter. It uses its own where clause,
specified in the pop_data() call:
spec <- tplyr_spec(
cols = "TRTA",
pop_data = pop_data(
cols = c("TRTA" = "TRT01A"),
where = SAFFL == "Y"
),
layers = tplyr_layers(
group_count(
target_var = "AEBODSYS",
settings = layer_settings(
distinct_by = "USUBJID"
)
)
)
)
result <- tplyr_build(spec, tplyr_adae, pop_data = tplyr_adsl)
kable(tplyr_header_n(result))| TRTA | .n |
|---|---|
| Placebo | 86 |
| Xanomeline High Dose | 84 |
| Xanomeline Low Dose | 84 |
This separation is intentional. The table-level where
controls which records are summarized, while pop_data
where controls which subjects contribute to denominators.
In practice these often differ – you might filter AE records to
treatment-emergent events while basing denominators on the full safety
population.
When building count layers, tplyr2 automatically completes all
combinations of factor levels and cross-variables. If a treatment group
has zero subjects with a given characteristic, a 0 (0.0%)
row still appears rather than being dropped.
spec <- tplyr_spec(
cols = "TRT01P",
layers = tplyr_layers(
group_count(target_var = "RACE")
)
)
result <- tplyr_build(spec, tplyr_adsl)
kable(result[, c("rowlabel1", grep("^res", names(result), value = TRUE))])| rowlabel1 | res1 | res2 | res3 |
|---|---|---|---|
| WHITE | 78 (90.7%) | 74 (88.1%) | 78 (92.9%) |
| BLACK OR AFRICAN AMERICAN | 8 ( 9.3%) | 9 (10.7%) | 6 ( 7.1%) |
| AMERICAN INDIAN OR ALASKA NATIVE | 0 ( 0.0%) | 1 ( 1.2%) | 0 ( 0.0%) |
Every race category appears for every treatment group, even when the count is zero.
limit_data_bySometimes completing all combinations is too aggressive. The
limit_data_by parameter in layer_settings()
restricts the completion grid to combinations that actually exist in the
data. This is essential for AE tables where preferred terms should only
appear under their actual body system:
spec <- tplyr_spec(
cols = "TRTA",
pop_data = pop_data(cols = c("TRTA" = "TRT01A")),
layers = tplyr_layers(
group_count(
target_var = "AEDECOD",
by = "AEBODSYS",
settings = layer_settings(
distinct_by = "USUBJID",
limit_data_by = c("AEBODSYS", "AEDECOD")
)
)
)
)
result <- tplyr_build(spec, tplyr_adae, pop_data = tplyr_adsl)
kable(head(result[, c("rowlabel1", "rowlabel2", grep("^res", names(result), value = TRUE))], 10))| rowlabel1 | rowlabel2 | res1 | res2 | res3 |
|---|---|---|---|---|
| CARDIAC DISORDERS | ATRIAL FIBRILLATION | 0 ( 0.0%) | 0 ( 0.0%) | 1 ( 1.2%) |
| CARDIAC DISORDERS | ATRIAL FLUTTER | 0 ( 0.0%) | 1 ( 1.2%) | 0 ( 0.0%) |
| CARDIAC DISORDERS | ATRIAL HYPERTROPHY | 1 ( 1.2%) | 0 ( 0.0%) | 0 ( 0.0%) |
| CARDIAC DISORDERS | BUNDLE BRANCH BLOCK RIGHT | 1 ( 1.2%) | 0 ( 0.0%) | 0 ( 0.0%) |
| CARDIAC DISORDERS | CARDIAC FAILURE CONGESTIVE | 1 ( 1.2%) | 0 ( 0.0%) | 0 ( 0.0%) |
| CARDIAC DISORDERS | MYOCARDIAL INFARCTION | 0 ( 0.0%) | 1 ( 1.2%) | 2 ( 2.4%) |
| CARDIAC DISORDERS | SINUS BRADYCARDIA | 0 ( 0.0%) | 3 ( 3.6%) | 1 ( 1.2%) |
| CARDIAC DISORDERS | SUPRAVENTRICULAR EXTRASYSTOLES | 1 ( 1.2%) | 0 ( 0.0%) | 1 ( 1.2%) |
| CARDIAC DISORDERS | SUPRAVENTRICULAR TACHYCARDIA | 0 ( 0.0%) | 0 ( 0.0%) | 1 ( 1.2%) |
| CARDIAC DISORDERS | TACHYCARDIA | 1 ( 1.2%) | 0 ( 0.0%) | 0 ( 0.0%) |
With limit_data_by = c("AEBODSYS", "AEDECOD"), tplyr2
only creates rows for body system/preferred term combinations that exist
in the data, while still filling in zeros for treatment groups with no
events for a given combination.
This vignette covered the table-level properties that control the overall structure of your tplyr2 output. For details on specific layer types and additional features, see:
vignette("count") – group_count() for
frequency tables, including nested counts, distinct subject counts,
population data, and missing value handling.vignette("denom") – denominator control in depth (this
vignette and the denominators vignette both cover pop_data;
that one goes deeper on denoms_by,
denom_where, and confidence intervals).vignette("adverse-events") – a full adverse event table
built end to end, the canonical use of population data.vignette("desc") – group_desc() for
summary statistics.vignette("shift") – group_shift() for
baseline-by-post-baseline cross-tabulations.vignette("sort") – how tplyr2 orders rows.vignette("options") – package-level options via
tplyr2_options().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.