1111# ' @param remove_na Logical. If `TRUE`, missing values are omitted from the
1212# ' grouping variable. If `FALSE` (default), missing values are included as a
1313# ' level in the grouping variable.
14+ # ' @param suffix Optional, suffixes to be added to the new variable names,
15+ # ' especially useful when a function returns several values (e.g. `quantile()`).
16+ # ' Can be:
17+ # ' * a character vector: all expressions in `...` must return the same number
18+ # ' of values as elements in `suffix`.
19+ # ' * a list of named character vectors: the names of elements in `suffix` must
20+ # ' match the names of the expressions. It is also allowed to specify suffixes
21+ # ' for selected expressions only.
22+ # '
23+ # ' The new column names are a combination of the left-hand side (i.e.,
24+ # ' the name) of the expression and the related suffixes. If `suffix = NULL` (the
25+ # ' default), and a summary expression returns multiple values, either the names
26+ # ' of the returned values (if any) or automatically numbered suffixes such as
27+ # ' `_1`, `_2`, etc. are used. See 'Examples'.
1428# ' @param ... One or more named expressions that define the new variable name
1529# ' and the function to compute the summary statistic. Example:
1630# ' `mean_sepal_width = mean(Sepal.Width)`. The expression can also be provided
5064# ' last = mpg[length(mpg)],
5165# ' by = c("am", "gear")
5266# ' )
67+ # '
68+ # ' # allow more than one-column-summaries for expressions
69+ # ' d <- data.frame(
70+ # ' x = rnorm(100, 1, 1),
71+ # ' y = rnorm(100, 2, 2),
72+ # ' groups = rep(1:4, each = 25)
73+ # ' )
74+ # '
75+ # ' # since we have multiple columns for one expression, the names of the
76+ # ' # returned summary results are used as suffix by default
77+ # ' data_summary(
78+ # ' d,
79+ # ' quant_x = quantile(x, c(0.25, 0.75)),
80+ # ' mean_x = mean(x),
81+ # ' quant_y = quantile(y, c(0.25, 0.5, 0.75))
82+ # ' )
83+ # '
84+ # ' # if a summary function, like `fivenum()`, returns no named vector, suffixes
85+ # ' # are automatically numbered
86+ # ' data_summary(
87+ # ' d,
88+ # ' quant_x = quantile(x, c(0.25, 0.75)),
89+ # ' mean_x = mean(x),
90+ # ' fivenum_y = fivenum(y)
91+ # ' )
92+ # '
93+ # ' # specify column suffix for expressions, matching by names
94+ # ' data_summary(
95+ # ' d,
96+ # ' quant_x = quantile(x, c(0.25, 0.75)),
97+ # ' mean_x = mean(x),
98+ # ' quant_y = quantile(y, c(0.25, 0.5, 0.75)),
99+ # ' suffix = list(quant_y = c("_Q1", "_Q2", "_Q3"))
100+ # ' )
101+ # '
102+ # ' # name multiple expression suffixes, grouped by variable
103+ # ' data_summary(
104+ # ' d,
105+ # ' quant_x = quantile(x, c(0.25, 0.75)),
106+ # ' mean_x = mean(x),
107+ # ' quant_y = quantile(y, c(0.25, 0.5, 0.75)),
108+ # ' suffix = list(quant_x = c("Q1", "Q3"), quant_y = c("_Q1", "_Q2", "_Q3")),
109+ # ' by = "groups"
110+ # ' )
111+ # '
53112# ' @export
54113data_summary <- function (x , ... ) {
55114 UseMethod(" data_summary" )
56115}
57116
58117
59118# ' @export
60- data_summary.matrix <- function (x , ... , by = NULL , remove_na = FALSE ) {
61- data_summary(as.data.frame(x ), ... , by = by , remove_na = remove_na )
119+ data_summary.matrix <- function (
120+ x ,
121+ ... ,
122+ by = NULL ,
123+ remove_na = FALSE ,
124+ suffix = NULL
125+ ) {
126+ data_summary(
127+ as.data.frame(x ),
128+ ... ,
129+ by = by ,
130+ remove_na = remove_na ,
131+ suffix = suffix
132+ )
62133}
63134
64135
@@ -72,7 +143,13 @@ data_summary.default <- function(x, ...) {
72143
73144# ' @rdname data_summary
74145# ' @export
75- data_summary.data.frame <- function (x , ... , by = NULL , remove_na = FALSE ) {
146+ data_summary.data.frame <- function (
147+ x ,
148+ ... ,
149+ by = NULL ,
150+ remove_na = FALSE ,
151+ suffix = NULL
152+ ) {
76153 dots <- eval(substitute(alist(... )))
77154
78155 # do we have any expression at all?
@@ -84,9 +161,10 @@ data_summary.data.frame <- function(x, ..., by = NULL, remove_na = FALSE) {
84161
85162 if (is.null(by )) {
86163 # when we have no grouping, just compute a one-row summary
87- summarise <- .process_datasummary_dots(dots , x )
88- out <- data.frame (summarise )
89- colnames(out ) <- vapply(summarise , names , character (1 ))
164+ summarise <- .process_datasummary_dots(dots , x , suffix )
165+ # coerce to data frame
166+ out <- as.data.frame(t(summarise ))
167+ colnames(out ) <- names(summarise )
90168 } else {
91169 # sanity check - is "by" a character string?
92170 if (! is.character(by )) {
@@ -122,15 +200,23 @@ data_summary.data.frame <- function(x, ..., by = NULL, remove_na = FALSE) {
122200 return (NULL )
123201 }
124202 # summarize data
125- summarise <- .process_datasummary_dots(dots , s )
203+ summarise <- .process_datasummary_dots(dots , s , suffix )
126204 # coerce to data frame
127- summarised_data <- data.frame (summarise )
205+ summarised_data <- as. data.frame(t( summarise ) )
128206 # bind grouping-variables and values
129207 summarised_data <- cbind(s [1 , by ], summarised_data )
130208 # make sure we have proper column names
131- colnames(summarised_data ) <- c(by , unlist(lapply( summarise , names ) ))
209+ colnames(summarised_data ) <- c(by , names( summarise ))
132210 summarised_data
133211 })
212+ # check for correct number of columns. If one expression returns different
213+ # number of values (which now means, we have different number of columns
214+ # to bind) for each group, tell user
215+ if (! all(lengths(out ) == lengths(out )[1 ])) {
216+ insight :: format_error(
217+ " Each expression must return the same number of values for each group. Some of the expressions seem to return varying numbers of values."
218+ )
219+ }
134220 out <- do.call(rbind , out )
135221 }
136222 # sort data
@@ -143,7 +229,13 @@ data_summary.data.frame <- function(x, ..., by = NULL, remove_na = FALSE) {
143229
144230
145231# ' @export
146- data_summary.grouped_df <- function (x , ... , by = NULL , remove_na = FALSE ) {
232+ data_summary.grouped_df <- function (
233+ x ,
234+ ... ,
235+ by = NULL ,
236+ remove_na = FALSE ,
237+ suffix = NULL
238+ ) {
147239 # extract group variables
148240 grps <- attr(x , " groups" , exact = TRUE )
149241 group_variables <- data_remove(grps , " .rows" )
@@ -154,13 +246,13 @@ data_summary.grouped_df <- function(x, ..., by = NULL, remove_na = FALSE) {
154246 # remove information specific to grouped df's
155247 attr(x , " groups" ) <- NULL
156248 class(x ) <- " data.frame"
157- data_summary(x , ... , by = by , remove_na = remove_na )
249+ data_summary(x , ... , by = by , remove_na = remove_na , suffix = suffix )
158250}
159251
160252
161253# helper -----------------------------------------------------------------------
162254
163- .process_datasummary_dots <- function (dots , data ) {
255+ .process_datasummary_dots <- function (dots , data , suffix = NULL ) {
164256 out <- NULL
165257 if (length(dots )) {
166258 # we check for character vector of expressions, in which case
@@ -199,39 +291,113 @@ data_summary.grouped_df <- function(x, ..., by = NULL, remove_na = FALSE) {
199291 }
200292 }
201293
294+ # sanity check: check the input for the `suffix` argument
295+ # `suffix` can be NULL, or must be a (named) list
296+ if (! is.null(suffix )) {
297+ # if `suffix` is a character vector, we transform it into a list,
298+ # matching the names of the expressions
299+ if (is.character(suffix )) {
300+ suffix <- rep(list (suffix ), length(dots ))
301+ names(suffix ) <- names(dots )
302+ }
303+ # no list? error
304+ if (! is.list(suffix )) {
305+ insight :: format_error(
306+ " Argument `suffix` must be a list of (named) character vectors, where the names match the names of the expressions, e.g.:" ,
307+ paste0(
308+ " `suffix = list(" ,
309+ names(dots )[1 ],
310+ " = c(\" _suffix1\" , \" _suffix2\" )`."
311+ )
312+ )
313+ }
314+ # not all elements named? error
315+ if (! length(which(nzchar(names(suffix ), keepNA = TRUE )))) {
316+ insight :: format_error(" All elements of `suffix` must have names." )
317+ }
318+ # names of suffix do not match names of expressions? error
319+ if (! all(names(suffix ) %in% names(dots ))) {
320+ wrong_name <- which(! names(suffix ) %in% names(dots ))[1 ]
321+ insight :: format_error(
322+ paste0(
323+ " Names of `suffix` must match the names of the expressions. Suffix `" ,
324+ names(suffix )[wrong_name ],
325+ " ` has no corresponding expression."
326+ )
327+ )
328+ }
329+ # identical suffixes for one expression? error
330+ identical_suffix <- vapply(
331+ suffix ,
332+ function (i ) insight :: n_unique(i ) != length(i ),
333+ logical (1 )
334+ )
335+ if (any(identical_suffix )) {
336+ insight :: format_error(
337+ paste0(
338+ " All suffixes for a single expression must be unique. Suffix for element `" ,
339+ names(identical_suffix )[which(identical_suffix )][1 ],
340+ " ` has duplicate values."
341+ )
342+ )
343+ }
344+ }
345+
202346 out <- lapply(seq_along(dots ), function (i ) {
203347 new_variable <- .get_new_dots_variable(dots , i , data )
348+ # check special case here - we want bayestestR::ci to work with
349+ # data summary, to easily create CIs for, say, posterior draws
204350 if (inherits(new_variable , c(" bayestestR_ci" , " bayestestR_eti" ))) {
205351 stats :: setNames(new_variable , c(" CI" , " CI_low" , " CI_high" ))
206352 } else {
207- stats :: setNames(new_variable , names(dots )[i ])
353+ # init
354+ current_suffix <- NULL
355+ # find matches and set use suffix if found
356+ matching_names <- which(names(suffix ) == names(dots )[i ])
357+ # either use suffixes based on matching names, or try to extract
358+ # names from the returned summary expression (saved in "new_variable"),
359+ # if the summary function returned a named vector
360+ if (length(matching_names ) > 0 ) {
361+ current_suffix <- suffix [[matching_names ]]
362+ } else if (
363+ length(new_variable ) > 1 &&
364+ all(nzchar(names(new_variable ), keepNA = TRUE ))
365+ ) {
366+ current_suffix <- names(new_variable )
367+ }
368+ # if we don't have suffixes for multiple columns, but expression
369+ # returns multiple columns, we get NA column names - we use
370+ # automatically numbered suffixes in this case
371+ if (is.null(current_suffix ) && length(new_variable ) > 1 ) {
372+ current_suffix <- paste0(" _" , seq_along(new_variable ))
373+ }
374+
375+ # if number of suffixes does not match the number of returned values
376+ # by the expression, error
377+ if (
378+ ! is.null(current_suffix ) &&
379+ length(current_suffix ) != length(new_variable )
380+ ) {
381+ insight :: format_error(
382+ paste0(
383+ " Argument `suffix` must have the same length as the result of the corresponding summary expression. `suffix` has " ,
384+ length(current_suffix ),
385+ " elements (" ,
386+ text_concatenate(current_suffix , enclose = " `" ),
387+ " ) for the expression `" ,
388+ insight :: safe_deparse(dots [[i ]]),
389+ " `, which returned " ,
390+ length(new_variable ),
391+ " values."
392+ )
393+ )
394+ }
395+ stats :: setNames(new_variable , paste0(names(dots )[i ], current_suffix ))
208396 }
209397 })
210398 }
211399
212- # check for correct length of output - must be a single value!
213- # Exception: bayestestR::ci()
214- wrong_length <- ! sapply(
215- out ,
216- inherits ,
217- what = c(" bayestestR_ci" , " bayestestR_eti" )
218- ) &
219- lengths(out ) != 1 # nolint
220- if (any(wrong_length )) {
221- insight :: format_error(
222- paste0(
223- " Each expression must return a single value. Following expression" ,
224- ifelse(sum(wrong_length ) > 1 , " s" , " " ),
225- " returned more than one value: " ,
226- text_concatenate(
227- vapply(dots [wrong_length ], insight :: safe_deparse , character (1 )),
228- enclose = " \" "
229- )
230- )
231- )
232- }
233-
234- out
400+ unlist(out )
235401}
236402
237403
0 commit comments