/
custom_appearance.Rmd
551 lines (451 loc) · 16.3 KB
/
custom_appearance.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
---
title: "Customizing Appearance"
author: "Davide Garolini, Abinaya Yogasekaram, and Gabriel Becker"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Customizing Appearance}
%\VignetteEncoding{UTF-8}
%\VignetteEngine{knitr::rmarkdown}
editor_options:
chunk_output_type: console
---
```{r, echo=FALSE}
knitr::opts_chunk$set(comment = "#")
```
```{css, echo=FALSE}
.reveal .r code {
white-space: pre;
}
```
## Customizing Appearance
In this vignette, we describe the various ways we can modify and
customize the appearance of `rtables`.
Loading the package:
```{r, message=FALSE}
library(rtables)
library(dplyr)
```
### Rows and cell values alignments
It is possible to align the content by assigning `"left"`, `"center"`
(default), and `"right"` to `.aligns` and `align` arguments in
`in_rows()` and `rcell()`, respectively. It is also possible to use `decimal`,
`dec_right`, and `dec_left` for decimal alignments. The first takes all numerical
values and aligns the decimal character `.` in every value of the column that has
`align = "decimal"`. Also numeric without decimal values are aligned according to
an imaginary `.` if specified as such. `dec_left` and `dec_right` behave similarly,
with the difference that if the column present empty spaces at left or right, it
pushes values towards left or right taking the one value that has most decimal
characters, if right, or non-decimal values if left. For more details, please read
the related documentation page `help("decimal_align")`.
Please consider using `?in_rows` and `?rcell` for further
clarifications on the two arguments, and use `formatters::list_valid_aligns()` to see
all available alignment options.
In the following we show two simplified examples that use `align` and
`.aligns`, respectively.
```{r}
# In rcell we use align.
lyt <- basic_table() %>%
analyze("AGE", function(x) {
in_rows(
left = rcell("l", align = "left"),
right = rcell("r", align = "right"),
center = rcell("c", align = "center")
)
})
tbl <- build_table(lyt, DM)
tbl
```
```{r}
# In in_rows, we use .aligns. This can either set the general value or the
# single values (see NB).
lyt2 <- basic_table() %>%
analyze("AGE", function(x) {
in_rows(
left = rcell("l"),
right = rcell("r"),
center = rcell("c"),
.aligns = c("right")
) # NB: .aligns = c("right", "left", "center")
})
tbl2 <- build_table(lyt2, DM)
tbl2
```
These concepts can be well applied to any clinical table as shown in
the following, more complex, example.
```{r}
lyt3 <- basic_table() %>%
split_cols_by("ARM") %>%
split_rows_by("SEX") %>%
analyze(c("AGE", "STRATA1"), function(x) {
if (is.numeric(x)) {
in_rows(
"mean" = rcell(mean(x)),
"sd" = rcell(sd(x)),
.formats = c("xx.x"), .aligns = "left"
)
} else if (is.factor(x)) {
rcell(length(unique(x)), align = "right")
} else {
stop("Unsupported type")
}
}, show_labels = "visible", na_str = "NE")
tbl3 <- build_table(lyt3, ex_adsl)
tbl3
```
### Top-left Materials
The sequence of strings printed in the area between the column header
display and the first row label \emph{top left material} can be
modified during pre-processing using label position argument in row
splits `split_rows_by`, with the `append_topleft` function, and during
post-processing using the `top_left()` function. Note: Indenting is
automatically added \emph{only in the case of} `label_pos =
"topleft"`.
Within the layout initializer:
```{r}
lyt <- basic_table() %>%
split_cols_by("ARM") %>%
split_rows_by("STRATA1") %>%
analyze("AGE") %>%
append_topleft("New top_left material here")
build_table(lyt, DM)
```
Specify label position using the `split_rows` function. Notice the
position of `STRATA1` and `SEX`.
```{r}
lyt <- basic_table() %>%
split_cols_by("ARM") %>%
split_rows_by("STRATA1", label_pos = "topleft") %>%
split_rows_by("SEX", label_pos = "topleft") %>%
analyze("AGE")
build_table(lyt, DM)
```
Post-processing using the `top_left()` function:
```{r}
lyt <- basic_table() %>%
split_cols_by("ARM") %>%
split_rows_by("SEX") %>%
analyze(c("AGE", "STRATA1"), function(x) {
if (is.numeric(x)) {
in_rows(
"mean" = rcell(mean(x)),
"sd" = rcell(sd(x)),
.formats = c("xx.x"), .aligns = "left"
)
} else if (is.factor(x)) {
rcell(length(unique(x)), align = "right")
} else {
stop("Unsupported type")
}
}, show_labels = "visible", na_str = "NE") %>%
build_table(ex_adsl)
# Adding top-left material
top_left(lyt) <- "New top-left material here"
lyt
```
### Table Inset
Table title, table body, referential footnotes and and main footers
can be inset from the left alignment of the titles and provenance
footer materials. This can be modified within the layout initializer
`basic_table()` using the `inset` argument or during post-processing
with `table_inset()`.
Using the layout initializer:
```{r}
lyt <- basic_table(inset = 5) %>%
analyze("AGE")
build_table(lyt, DM)
```
Using the post-processing function:
Without inset -
```{r}
lyt <- basic_table() %>%
analyze("AGE")
tbl <- build_table(lyt, DM)
tbl
```
With an inset of 5 characters -
```{r}
table_inset(tbl) <- 5
tbl
```
Below is an example with a table produced for clinical data. Compare
the inset of the table and main footer between the two tables.
Without inset -
```{r}
analysisfun <- function(x, ...) {
in_rows(
row1 = 5,
row2 = c(1, 2),
.row_footnotes = list(row1 = "row 1 rfn"),
.cell_footnotes = list(row2 = "row 2 cfn")
)
}
lyt <- basic_table(
title = "Title says Whaaaat", subtitles = "Oh, ok.",
main_footer = "ha HA! Footer!", prov_footer = "provenaaaaance"
) %>%
split_cols_by("ARM") %>%
analyze("AGE", afun = analysisfun)
result <- build_table(lyt, ex_adsl)
result
```
With inset -
Notice, the inset does not apply to any title materials
(main title, subtitles, page titles), or provenance footer
materials. Inset settings is applied to top-left materials,
referential footnotes main footer materials and any horizontal
dividers.
```{r}
table_inset(result) <- 5
result
```
### Horizontal Separation
A character value can be specified to modify the horizontal separation
between column headers and the table. Horizontal separation applies
when:
1. separating title + subtitles from the column labels + top left
materials,
2. column labels + top left material from row labels + cells,
3. row labels + cells from footer content, and
4. Referential footnotes from main + provenance content \emph{only if}
there would be something on both sides of the divider.
Below, we replace the default line with "=".
```{r}
tbl <- basic_table() %>%
split_cols_by("Species") %>%
add_colcounts() %>%
analyze(c("Sepal.Length", "Petal.Width"), function(x) {
in_rows(
mean_sd = c(mean(x), sd(x)),
var = var(x),
min_max = range(x),
.formats = c("xx.xx (xx.xx)", "xx.xxx", "xx.x - xx.x"),
.labels = c("Mean (sd)", "Variance", "Min - Max")
)
}) %>%
build_table(iris, hsep = "=")
tbl
```
### Section Dividers
A character value can be specified as a section divider which succeed
every group defined by a split instruction. Note, a trailing divider
at the end of the table is never printed.
Below, a "+" is repeated and used as a section divider.
```{r}
lyt <- basic_table() %>%
split_cols_by("Species") %>%
analyze(head(names(iris), -1), afun = function(x) {
list(
"mean / sd" = rcell(c(mean(x), sd(x)), format = "xx.xx (xx.xx)"),
"range" = rcell(diff(range(x)), format = "xx.xx")
)
}, section_div = "+")
build_table(lyt, iris)
```
Section dividers can be set to " " to create a blank line.
```{r}
lyt <- basic_table() %>%
split_cols_by("Species") %>%
analyze(head(names(iris), -1), afun = function(x) {
list(
"mean / sd" = rcell(c(mean(x), sd(x)), format = "xx.xx (xx.xx)"),
"range" = rcell(diff(range(x)), format = "xx.xx")
)
}, section_div = " ")
build_table(lyt, iris)
```
Separation characters can be specified for different row
splits. However, only one will be printed if they "pile up" next to
each other.
```{r}
lyt <- basic_table() %>%
split_cols_by("ARM") %>%
split_rows_by("RACE", section_div = "=") %>%
split_rows_by("STRATA1", section_div = "~") %>%
analyze("AGE", mean, var_labels = "Age", format = "xx.xx")
build_table(lyt, DM)
```
### Indent Modifier
Tables by default have indenting at each level of splitting. A custom
indent value can be supplied with the `indent_mod` argument within a
split function to modify this default. Compare the indenting of the
tables below:
Default Indent -
```{r}
basic_table(
title = "Study XXXXXXXX",
subtitles = c("subtitle YYYYYYYYYY", "subtitle2 ZZZZZZZZZ"),
main_footer = "Analysis was done using cool methods that are correct",
prov_footer = "file: /path/to/stuff/that/lives/there HASH:1ac41b242a"
) %>%
split_cols_by("ARM") %>%
split_rows_by("SEX") %>%
split_rows_by("STRATA1") %>%
analyze("AGE", mean, format = "xx.x") %>%
build_table(DM)
```
Modified indent -
```{r}
basic_table(
title = "Study XXXXXXXX",
subtitles = c("subtitle YYYYYYYYYY", "subtitle2 ZZZZZZZZZ"),
main_footer = "Analysis was done using cool methods that are correct",
prov_footer = "file: /path/to/stuff/that/lives/there HASH:1ac41b242a"
) %>%
split_cols_by("ARM") %>%
split_rows_by("SEX", indent_mod = 3) %>%
split_rows_by("STRATA1", indent_mod = 5) %>%
analyze("AGE", mean, format = "xx.x") %>%
build_table(DM)
```
### Variable Label Visibility
With split instructions, visibility of the label for the variable
being split can be modified to `visible`, `hidden` and `topleft` with
the `show_labels` argument, `label_pos` argument, and `child_labels`
argument where applicable. Note: this is NOT the name of the levels
contained in the variable. For analyze calls, \code{default} indicates
that the variable should be visible only if multiple variables are
analyzed at the same level of nesting.
Visibility of labels for the groups generated by a split can also be
modified using the `child_label` argument with a split call. The
`child_label` argument can force labels to be visible in addition to
content rows but we cannot hide or move the content rows.
Notice the placement of the "AGE" label in this example:
```{r}
lyt <- basic_table(show_colcounts = TRUE) %>%
split_cols_by(var = "ARM") %>%
split_rows_by("SEX", split_fun = drop_split_levels, child_labels = "visible") %>%
split_rows_by("STRATA1") %>%
analyze("AGE", mean, show_labels = "default")
build_table(lyt, DM)
```
When set to default, the label `AGE` is not repeated since there is
only one variable being analyzed at the same level of
nesting. Override this by setting the `show_labels` argument as
"visible".
```{r}
lyt2 <- basic_table(show_colcounts = TRUE) %>%
split_cols_by(var = "ARM") %>%
split_rows_by("SEX", split_fun = drop_split_levels, child_labels = "hidden") %>%
split_rows_by("STRATA1") %>%
analyze("AGE", mean, show_labels = "visible")
build_table(lyt2, DM)
```
Below is an example using the `label_pos` argument for modifying label
visibility:
Label order will mirror the order of `split_rows_by` calls. If the
labels of any subgroups should be hidden, the `label_pos` argument
should be set to hidden.
"SEX" label position is hidden -
```{r}
basic_table(
title = "Study XXXXXXXX",
subtitles = c("subtitle YYYYYYYYYY", "subtitle2 ZZZZZZZZZ"),
main_footer = "Analysis was done using cool methods that are correct",
prov_footer = "file: /path/to/stuff/that/lives/there HASH:1ac41b242a"
) %>%
split_cols_by("ARM") %>%
split_rows_by("SEX", split_fun = drop_split_levels, label_pos = "visible") %>%
split_rows_by("STRATA1", label_pos = "hidden") %>%
analyze("AGE", mean, format = "xx.x") %>%
build_table(DM)
```
"SEX" label position is with the top-left materials -
```{r}
basic_table(
title = "Study XXXXXXXX",
subtitles = c("subtitle YYYYYYYYYY", "subtitle2 ZZZZZZZZZ"),
main_footer = "Analysis was done using cool methods that are correct",
prov_footer = "file: /path/to/stuff/that/lives/there HASH:1ac41b242a"
) %>%
split_cols_by("ARM") %>%
split_rows_by("SEX", split_fun = drop_split_levels, label_pos = "topleft") %>%
split_rows_by("STRATA1", label_pos = "hidden") %>%
analyze("AGE", mean, format = "xx.x") %>%
build_table(DM)
```
### Cell, Label, and Annotation Wrapping
An `rtable` can be rendered with a customized width by setting custom
rendering widths for cell contents, row labels, and titles/footers.
This is demonstrated using the sample data and table below. In this
section we aim to render this table with a reduced width since the
table has very wide contents in several cells, labels, and
titles/footers.
```{r}
trimmed_data <- ex_adsl %>%
filter(SEX %in% c("M", "F")) %>%
filter(RACE %in% levels(RACE)[1:2])
levels(trimmed_data$ARM)[1] <- "Incredibly long column name to be wrapped"
levels(trimmed_data$ARM)[2] <- "This_column_name_should_be_split_somewhere"
wide_tbl <- basic_table(
title = "Title that is too long and also needs to be wrapped to a smaller width",
subtitles = "Subtitle that is also long and also needs to be wrapped to a smaller width",
main_footer = "Footnote that is wider than expected for this table.",
prov_footer = "Provenance footer material that is also wider than expected for this table."
) %>%
split_cols_by("ARM") %>%
split_rows_by("RACE", split_fun = drop_split_levels) %>%
analyze(
c("AGE", "EOSDY"),
na_str = "Very long cell contents to_be_wrapped_and_splitted",
inclNAs = TRUE
) %>%
build_table(trimmed_data)
wide_tbl
```
In the following sections we will use the `toString()` function to
render the table in string form. This resulting string representation
is ready to be printed or written to a plain text file, but we will
use the `strsplit()` function in combination with the `matrix()`
function to preview the rendered wrapped table in matrix form within
this vignette.
#### Cell & Label Wrapping
The width of a rendered table can be customized by wrapping column
widths. This is done by setting custom width values via the `widths`
argument of the `toString()` function. The length of the vector passed
to the `widths` argument must be equal to the total number of columns
in the table, including the row labels column, with each value of the
vector corresponding to the maximum width (in characters) allowed in
each column, from left to right.
Similarly, wrapping can be applied when exporting a table via one of
the four `export_as_*` functions and when implementing pagination via
the `paginate_table()` function from the `rtables` package. In these
cases, the rendered column widths are set using the `colwidths`
argument which takes input in the same format as the `widths` argument
of `toString()`.
For example, `wide_tbl` has four columns (1 row label column and 3
content columns) which we will set the widths of below to use in the
rendered table. We set the width of the row label column to 10
characters and the widths of each of the 3 content columns to 8
characters. Any words longer than the specified width are broken and
continued on the following line. By default there are 3 spaces
separating each of the columns in the rendered table but this can be
customized via the `col_gap` argument to `toString()` if further width
customization is desired.
```{r}
result_wrap_cells <- toString(wide_tbl, widths = c(10, 8, 8, 8))
matrix_wrap_cells <- matrix(strsplit(result_wrap_cells, "\n")[[1]], ncol = 1)
matrix_wrap_cells
```
In the resulting output we can see that the table has been correctly
rendered using wrapping with a total width of 43 characters, but that
the titles and footers remain wider than the rendered table.
#### Title & Footer Wrapping
In addition to wrapping column widths, titles and footers can be
wrapped by setting `tf_wrap = TRUE` in `toString()` and setting the
`max_width` argument of `toString()` to the maximum width (in
characters) allowed for titles/footers. The four `export_as_*`
functions and `paginate_table()` can also wrap titles/footers by
setting the same two arguments. In the following code, we set
`max_width = 43` so that the rendered table and all of its annotations
have a maximum width of 43 characters.
```{r}
result_wrap_cells_tf <- toString(
wide_tbl,
widths = c(10, 8, 8, 8),
tf_wrap = TRUE,
max_width = 43
)
matrix_wrap_cells_tf <- matrix(strsplit(result_wrap_cells_tf, "\n")[[1]], ncol = 1)
matrix_wrap_cells_tf
```