-
Notifications
You must be signed in to change notification settings - Fork 16
/
desc.Rmd
322 lines (253 loc) · 16.1 KB
/
desc.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
---
title: "Descriptive Statistic Layers"
output:
rmarkdown::html_vignette:
toc: true
vignette: >
%\VignetteIndexEntry{desc_layers}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>"
)
```
```{r setup, include=FALSE}
library(tidyverse)
library(magrittr)
library(Tplyr)
library(knitr)
load("adlb.Rdata")
load("adsl.Rdata")
```
Descriptive statistics in 'Tplyr' are created using `group_desc()` function when creating a layer. While `group_desc()` allows you to set your target, by variables, and filter criteria, a great deal of the control of the layer comes from `set_format_strings()` where the actual summaries are declared.
```{r intro}
tplyr_table(adsl, TRT01P) %>%
add_layer(
group_desc(AGE, by = "Age (years)", where= SAFFL=="Y") %>%
set_format_strings(
"n" = f_str("xx", n),
"Mean (SD)"= f_str("xx.x (xx.xx)", mean, sd),
"Median" = f_str("xx.x", median),
"Q1, Q3" = f_str("xx, xx", q1, q3),
"Min, Max" = f_str("xx, xx", min, max),
"Missing" = f_str("xx", missing)
)
) %>%
build() %>%
kable()
```
Let's walk through this call to `set_format_strings` to understand in detail what's going on:
1) The quoted strings on the left of the '=' within `set_format_strings()` become the row label in the output. This allows you to define some custom text in `set_format_strings()` to explain the summary that is presented on the associated row. This text is fully in your control.
2) On the right side of each equals is a call to `f_str()`. As explained in the `vignette("Tplyr")`, this is an object that captures a lot of metadata to understand how the strings should be presented.
3) Within the `f_str()` call, you see x's in quotes. This defines how you'd like the numbers formatted from the resulting summaries. The number of x's you use on the left side of a decimal control the space allotted for an integer, and the right side controls the decimal precision. Decimals are rounded prior to string formatting - so no need to worry about that. Note that this forcefully sets the decimal and integer precision - 'Tplyr' can automatically determine this for you as well, but more on that later.
4) After the x's there are unquoted variable names. This is where you specify the actual summaries that will be performed. Notice that some `f_str()` calls have two summaries specified. This allows you to put two summaries in the same string and present them on the same line.
But where do these summary names come from? And which ones does 'Tplyr' have?
## Built-in Summaries
We've built a number of default summaries into 'Tplyr', which allows you to perform these summaries without having to specify the functions to calculate them yourself. The summaries built in to 'Tplyr' are listed below. In the second column are the names that you would use within an `f_str()` call to use them. In the third column, we have the syntax used to make the function call.
```{r varnames, echo=FALSE}
x <- data.frame(
Statistic = c('N', 'Mean', "Standard Deviation", "Median", "Variance", "Minimum",
"Maximum", "Interquartile Range", "Q1", "Q3", "Missing"),
`Variable Names` = c("n", "mean", "sd", "median", "variance", "min", "max", "iqr", "q1", "q3", "missing"),
`Function Call` = c("n()", "mean(.var, na.rm=TRUE)", "sd(.var, na.rm=TRUE)", "median(.var, na.rm=TRUE)",
"var(.var, na.rm=TRUE)", "min(.var, na.rm=TRUE)", "max(.var, na.rm=TRUE)",
"IQR(.var, na.rm=TRUE, type=getOption('tplyr.quantile_type')",
"quantile(.var, na.rm=TRUE, type=getOption('tplyr.quantile_type'))[[2]]",
"quantile(.var, na.rm=TRUE, type=getOption('tplyr.quantile_type'))[[4]]",
"sum(is.na(.var))")
)
x %>%
kable(align="ccl", col.names=c('Statistic', 'Variable Names', 'Function Call'))
```
### Notes About Built-in's
Note that the only non-default option being used in any of the function calls above is `na.rm=TRUE`. For most of the functions, this is likely fine - but with IQR, Q1, and Q3 note that there are several different quantile algorithms available in R. The default we chose to use is the R default of Type 7:
$$
m = 1-p. p[k] = (k - 1) / (n - 1). \textrm{In this case, } p[k] = mode[F(x[k])]. \textrm{This is used by S.}
$$
That said, we still want to offer some flexibility here, so you can change the quantile algorithm by switching the `tplyr.quantile_type` option. If you're intending to match the SAS definition, you can use Type 3. For more information, see the `stats::quantile()` documentation.
The example below demonstrates using the default quantile algorithm in R.
```{r quantile_types_default}
tplyr_table(adsl, TRT01P) %>%
add_layer(
group_desc(CUMDOSE) %>%
set_format_strings("Q1, Q3" = f_str('xxxxx, xxxxx', q1, q3))
) %>%
build() %>%
select(-starts_with("ord")) %>%
kable()
```
This next example demonstrates using quantile algorithm Type 3, which matches the SAS definition of:
$$
\textrm{Nearest even order statistic. γ = 0 if g = 0 and j is even, and 1 otherwise.}
$$
```{r quantile_types_sas}
options(tplyr.quantile_type = 3)
tplyr_table(adsl, TRT01P) %>%
add_layer(
group_desc(CUMDOSE) %>%
set_format_strings("Q1, Q3" = f_str('xxxxx, xxxxx', q1, q3))
) %>%
build() %>%
select(-starts_with("ord")) %>%
kable()
```
```{r echo=FALSE}
options(tplyr.quantile_type=7)
```
It's up to you to determine which algorithm you should use - but we found it necessary to provide you with the flexibility to change this within the default summaries.
But what if 'Tplyr' doesn't offer you the summaries that you need?
## Custom Summaries
We understand that our defaults may not cover every descriptive statistic that you'd like to see. That's why we've opened to door to creating custom summaries. Custom summaries allow you to provide any function you'd like into a desc layer. You can focus on the derivation and how to calculate the number you want to see. 'Tplyr' can consume this function, and use all the existing tools within 'Tplyr' to produce the string formatted result alongside any of the default summaries we provide as well.
Custom summaries may be provided in two ways:
- Through the `tplyr.custom_summaries` option set at your session level
- Through the function `set_custom_summaries()` at the layer level
As with any other setting in 'Tplyr', the layer setting will always take precedence over any other setting.
Let's look at an example.
```{r multi-custom}
tplyr_table(adsl, TRT01P) %>%
add_layer(
group_desc(vars(AGE, HEIGHTBL), by = "Sepal Length") %>%
set_custom_summaries(
geometric_mean = exp(sum(log(.var[.var > 0]), na.rm=TRUE) / length(.var))
) %>%
set_format_strings(
'Geometric Mean (SD)' = f_str('xx.xx (xx.xxx)', geometric_mean, sd)
)
) %>%
build() %>%
select(-starts_with("ord")) %>%
kable()
```
Here, a few important things are demonstrated:
- This is a multi-variable descriptive statistics layer. Both `AGE` and `HEIGHTBL` are being summarized in the same layer. `AGE` results go to the `var1_` variables and `HEIGHTBL` results go to the `var2_` variables.
- The parameter names in `set_custom_summaries()`, or names on the left side of the equals, flow into `set_format_strings()` in the `f_str()` calls. Just like the default summaries, `geometric_mean` becomes the name that you refer to in order to use the geometric mean derivation in a summary.
- In geometric mean, the target variable that you're summarizing is referred to as `.var`. This may not seem intuitive. The reason we have to use `.var` is so that, like in this example, the custom function can be applied to each of the separate target variables.
Another note about custom summaries is that you're able to overwrite the default summaries built into 'Tplyr' as well. Don't like the default summary functions that we provide? Use the `tplyr.custom_summaries` option to overwrite them in your session, and add any new ones that you would like to include.
For example, here we use the 'Tplyr' default mean.
```{r custom_options}
tplyr_table(adsl, TRT01P) %>%
add_layer(
group_desc(AGE) %>%
set_format_strings("Mean" = f_str('xx.xx', mean))
) %>%
build() %>%
kable()
```
But now, let's overwrite `mean` using a custom summary. Let's use a trimmed mean instead, taking 20% of observations off of both ends.
```{r custom_options_trimmed}
options(tplyr.custom_summaries =
rlang::quos(
mean = mean(.var, na.rm=TRUE, trim=0.4)
)
)
tplyr_table(adsl, TRT01P) %>%
add_layer(
group_desc(AGE) %>%
set_format_strings("Mean" = f_str('xx.xx', mean))
) %>%
build() %>%
kable()
```
Note that the table code used to produce the output is the same. Now 'Tplyr' used the custom summary function for `mean` as specified in the `tplyr.custom_summaries` option. Also note the use of `rlang::quos()`. We've done our best to mask this from the user everywhere possible and make the interfaces clean and intuitive, but a great deal of 'Tplyr' is built using 'rlang' and non-standard evaluation. Within this option is one of the very few instances where a user needs to concern themselves with the use of quosures. If you'd like to learn more about non-standard evaluation and quosures, we recommend [Section IV](https://adv-r.hadley.nz/metaprogramming.html) in Advanced R.
## Formatting
A lot of the nuance to formatting descriptive statistics layers has already been covered above, but there are a couple more tricks to getting the most out of 'Tplyr'. One of these tricks is filling empty values.
By default, if there is no available value for a summary in a particular observation, the result being presented will be blanked out.
```{r missing}
adlb_2 <- adlb %>%
filter(TRTA != "Placebo")
tplyr_table(adlb_2, TRTA) %>%
set_pop_data(adsl) %>%
set_pop_treat_var(TRT01P) %>%
add_layer(
group_desc(AVAL, by=PARAMCD) %>%
set_format_strings('Mean (SD)' = f_str('xxx (xxx)', mean, sd))
) %>%
build() %>%
head() %>%
select(-starts_with("ord")) %>%
kable()
```
Note how the entire example above has all records in `var1_Placebo` missing. 'Tplyr' gives you control over how you fill this space. Let's say that we wanted instead to make that space say "Missing". You can control this with the `f_str()` object using the `empty` parameter
```{r missing1}
adlb_2 <- adlb %>%
filter(TRTA != "Placebo")
tplyr_table(adlb_2, TRTA) %>%
set_pop_data(adsl) %>%
set_pop_treat_var(TRT01P) %>%
add_layer(
group_desc(AVAL, by=PARAMCD) %>%
set_format_strings('Mean (SD)' = f_str('xxx.xx (xxx.xxx)', mean, sd, empty=c(.overall="MISSING")))
) %>%
build() %>%
head() %>%
select(-starts_with("ord")) %>%
kable()
```
Look at the `empty` parameter above. Here, we use a named character vector, where the name is `.overall`. When this name is used, if all elements within the cell are missing, they will be filled with the specified text. Otherwise, the provided string will fill just the missing parameter. In some cases, this may not be what you'd like to see. Perhaps we want a string that fills each missing space.
```{r missing2}
adlb_2 <- adlb %>%
filter(TRTA != "Placebo")
tplyr_table(adlb_2, TRTA) %>%
set_pop_data(adsl) %>%
set_pop_treat_var(TRT01P) %>%
add_layer(
group_desc(AVAL, by=PARAMCD) %>%
set_format_strings('Mean (SD)' = f_str('xxx.xx (xxx.xxx)', mean, sd, empty=c("NA")))
) %>%
build() %>%
head() %>%
select(-starts_with("ord")) %>%
kable()
```
In the example above, instead of filling the whole space, the `empty` text of "NA" replaces the empty value for each element. So for 'Mean (SD)', we now have 'NA ( NA)'. Note that the proper padding was still used for 'NA' to make sure the parentheses still align with populated records.
### Auto Precision
You may have noticed that the approach to formatting covered so far leaves a lot to be desired. Consider analyzing lab results, where you may want precision to vary based on the collected precision of the tests. Furthermore, depending on the summary being presented, you may wish to increase the precision further. For example, you may want the mean to be at collected precision +1 decimal place, and for standard deviation +2.
'Tplyr' has this covered using auto-precision. Auto-precision allows you to format your numeric summaries based on the precision of the data collected. This has all been built into the format strings, because a natural place to specify your desired format is where you specify how you want your data presented. If you wish to use auto-precision, use `a` instead of `x` when creating your summaries. Note that only one `a` is needed on each side of a decimal. To use increased precision, use `a+n` where `n` is the number of additional spaces you wish to add.
```{r autoprecision1}
tplyr_table(adlb, TRTA) %>%
add_layer(
group_desc(AVAL, by = PARAMCD) %>%
set_format_strings(
'Mean (SD)' = f_str('a.a+1 (a.a+2)', mean, sd)
)
) %>%
build() %>%
head(20) %>%
kable()
```
As you can see, the decimal precision is now varying depending on the test being performed. Notice that both the integer and the decimal side of each number fluctuate as well. `Tpylr` collects both the integer and decimal precision, and you can specify both separately. For example, you could use `x`'s to specify a default number of spaces for your integers that are used consistently across by variables, but vary the decimal precision based on collected data. You can also increment the number of spaces for both integer and decimal separately.
But - this is kind of ugly, isn't it? Do we really need all 6 decimal places collected for CA? For this reason, you're able to set a cap on the precision that's displayed:
```{r autoprecision2}
tplyr_table(adlb, TRTA) %>%
add_layer(
group_desc(AVAL, by = PARAMCD) %>%
set_format_strings(
'Mean (SD)' = f_str('a.a+1 (a.a+2)', mean, sd),
cap = c(int=3, dec=2)
)
) %>%
build() %>%
head(20) %>%
kable()
```
Now that looks better. The `cap` argument is part of `set_format_strings()`. You need to specify the integer and decimal caps separately. Note that integer precision works slightly differently than decimal precision. Integer precision relates to the length allotted for the left side of a decimal, but integers will not truncate. When using 'x' formatting, if an integer exceeds the set length, it will push the number over. If the integer side of auto-precision is not capped, the necessary length for an integer in the associated by group will be as long as necessary. Decimals, on the other hand, round to the specified length. These caps apply to the length allotted for the "a" on either the integer or the decimal. So for example, if the decimal length is capped at 2 and the selected precision is "a+1", then 3 decimal places will be allotted.
This was a basic situation, but if you're paying close attention, you may have some questions. What if you have more by variables, like by visit AND test. Do we then calculate precision by visit and test? What if collected precision is different per visit and we don't want that? What about multiple summary variables? How do we determine precision then? We have modifier functions for this:
```{r precision3}
tplyr_table(adlb, TRTA) %>%
add_layer(
group_desc(vars(AVAL, CHG, BASE), by = PARAMCD) %>%
set_format_strings(
'Mean (SD)' = f_str('a.a+1 (a.a+2)', mean, sd, empty="NA"),
cap = c(int=3, dec=2)
) %>%
set_precision_on(AVAL) %>%
set_precision_by(PARAMCD)
) %>%
build() %>%
head() %>%
kable()
```
Three variables are being summarized here - AVAL, CHG, and BASE. So which should be used for precision? `set_precision_on()` allows you to specify this, where the `precision_on()` variable must be one of the variables within `target_var`. Similarly, `set_precision_by()` changes the `by` variables used to determine collected precision. If no `precision_on()` variable is specified, the first variable in `target_var` is used. If no `precision_by()` variables are specified, then the default `by` variables are used.