-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Expand file tree
/
Copy pathgroup-by.R
More file actions
346 lines (319 loc) · 9.06 KB
/
Copy pathgroup-by.R
File metadata and controls
346 lines (319 loc) · 9.06 KB
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
#' Group by one or more variables
#'
#' @description
#' Most data operations are done on groups defined by variables.
#' `group_by()` takes an existing tbl and converts it into a grouped tbl
#' where operations are performed "by group". `ungroup()` removes grouping.
#'
#' @family grouping functions
#' @inheritParams arrange
#' @param ... <[`data-masking`][rlang::args_data_masking]> In `group_by()`,
#' variables or computations to group by. Computations are always done on the
#' ungrouped data frame. To perform computations on the grouped data, you need
#' to use a separate `mutate()` step before the `group_by()`.
#' Computations are not allowed in `nest_by()`.
#' In `ungroup()`, variables to remove from the grouping.
#' @param .add When `FALSE`, the default, `group_by()` will
#' override existing groups. To add to the existing groups, use
#' `.add = TRUE`.
#' @param .drop Drop groups formed by factor levels that don't appear in the
#' data? The default is `TRUE` except when `.data` has been previously
#' grouped with `.drop = FALSE`. See [group_by_drop_default()] for details.
#' @return A grouped data frame with class [`grouped_df`][grouped_df],
#' unless the combination of `...` and `add` yields a empty set of
#' grouping columns, in which case a tibble will be returned.
#' @section Methods:
#' These are S3 generics, which means that packages can provide
#' implementations (methods) for other classes. See the documentation of
#' individual methods for extra arguments and differences in behaviour.
#' The following methods are currently available in loaded packages:
#'
#' * `group_by()`: `Rd doclisting::methods_list("group_by")`
#' * `ungroup()`: `Rd doclisting::methods_list("ungroup")`
#'
#' @section Ordering:
#' Currently, `group_by()` internally orders the groups in ascending order. This
#' results in ordered output from functions that aggregate groups, such as
#' [summarise()].
#'
#' When used as grouping columns, character vectors are ordered in the C locale
#' for performance and reproducibility across R sessions. If the resulting
#' ordering of your grouped operation matters and is dependent on the locale,
#' you should follow up the grouped operation with an explicit call to
#' [arrange()] and set the `.locale` argument. For example:
#'
#' ```
#' data |>
#' group_by(chr) |>
#' summarise(avg = mean(x)) |>
#' arrange(chr, .locale = "en")
#' ```
#'
#' This is often useful as a preliminary step before generating content intended
#' for humans, such as an HTML table.
#'
#' ## Legacy behavior
#'
#' `r lifecycle::badge("deprecated")`
#'
#' Prior to dplyr 1.1.0, character vector grouping columns were ordered in the
#' system locale. Setting the global option `dplyr.legacy_locale` to `TRUE`
#' retains this legacy behavior, but this has been deprecated. Update existing
#' code to explicitly call `arrange(.locale = )` instead. Run
#' `Sys.getlocale("LC_COLLATE")` to determine your system locale, and compare
#' that against the list in [stringi::stri_locale_list()] to find an appropriate
#' value for `.locale`, i.e. for American English, `"en_US"`.
#'
#' @export
#' @examples
#' by_cyl <- mtcars |> group_by(cyl)
#'
#' # grouping doesn't change how the data looks (apart from listing
#' # how it's grouped):
#' by_cyl
#'
#' # It changes how it acts with the other dplyr verbs:
#' by_cyl |> summarise(
#' disp = mean(disp),
#' hp = mean(hp)
#' )
#' by_cyl |> filter(disp == max(disp))
#'
#' # Each call to summarise() removes a layer of grouping
#' by_vs_am <- mtcars |> group_by(vs, am)
#' by_vs <- by_vs_am |> summarise(n = n())
#' by_vs
#' by_vs |> summarise(n = sum(n))
#'
#' # To removing grouping, use ungroup
#' by_vs |>
#' ungroup() |>
#' summarise(n = sum(n))
#'
#' # By default, group_by() overrides existing grouping
#' by_cyl |>
#' group_by(vs, am) |>
#' group_vars()
#'
#' # Use add = TRUE to instead append
#' by_cyl |>
#' group_by(vs, am, .add = TRUE) |>
#' group_vars()
#'
#' # You can group by expressions: this is a short-hand
#' # for a mutate() followed by a group_by()
#' mtcars |>
#' group_by(vsam = vs + am)
#'
#' # The implicit mutate() step is always performed on the
#' # ungrouped data. Here we get 3 groups:
#' mtcars |>
#' group_by(vs) |>
#' group_by(hp_cut = cut(hp, 3))
#'
#' # If you want it to be performed by groups,
#' # you have to use an explicit mutate() call.
#' # Here we get 3 groups per value of vs
#' mtcars |>
#' group_by(vs) |>
#' mutate(hp_cut = cut(hp, 3)) |>
#' group_by(hp_cut)
#'
#' # when factors are involved and .drop = FALSE, groups can be empty
#' tbl <- tibble(
#' x = 1:10,
#' y = factor(rep(c("a", "c"), each = 5), levels = c("a", "b", "c"))
#' )
#' tbl |>
#' group_by(y, .drop = FALSE) |>
#' group_rows()
#'
group_by <- function(
.data,
...,
.add = FALSE,
.drop = group_by_drop_default(.data)
) {
UseMethod("group_by")
}
#' @export
group_by.data.frame <- function(
.data,
...,
.add = FALSE,
.drop = group_by_drop_default(.data)
) {
groups <- group_by_prepare(
.data,
...,
.add = .add,
error_call = current_env()
)
grouped_df(groups$data, groups$group_names, .drop)
}
#' @rdname group_by
#' @export
#' @param x A [tbl()]
ungroup <- function(x, ...) {
UseMethod("ungroup")
}
#' @export
ungroup.grouped_df <- function(x, ...) {
if (missing(...)) {
as_tibble(x)
} else {
old_groups <- group_vars(x)
to_remove <- tidyselect::eval_select(
expr = expr(c(...)),
data = x,
allow_rename = FALSE
)
to_remove <- names(to_remove)
new_groups <- setdiff(old_groups, to_remove)
group_by(x, !!!syms(new_groups))
}
}
#' @export
ungroup.rowwise_df <- function(x, ...) {
check_dots_empty()
as_tibble(x)
}
#' @export
ungroup.data.frame <- function(x, ...) {
check_dots_empty()
x
}
#' Prepare for grouping and other operations
#'
#' `*_prepare()` performs standard manipulation that is needed prior
#' to actual data processing. They are only be needed by packages
#' that implement dplyr backends.
#'
#' @return A list
#' \item{data}{Modified tbl}
#' \item{groups}{Modified groups}
#' @export
#' @keywords internal
group_by_prepare <- function(
.data,
...,
.add = FALSE,
.dots = deprecated(),
add = deprecated(),
error_call = caller_env()
) {
error_call <- dplyr_error_call(error_call)
if (!missing(add)) {
lifecycle::deprecate_stop("1.0.0", "group_by(add = )", "group_by(.add = )")
}
if (!missing(.dots)) {
lifecycle::deprecate_stop("1.0.0", "group_by(.dots = )")
}
new_groups <- enquos(..., .ignore_empty = "all")
# If any calls, use mutate to add new columns, then group by those
computed_columns <- add_computed_columns(
.data,
new_groups,
error_call = error_call
)
out <- computed_columns$data
group_names <- computed_columns$added_names
if (.add) {
group_names <- union(group_vars(.data), group_names)
}
unknown <- setdiff(group_names, tbl_vars(out))
if (length(unknown) > 0) {
bullets <- c(
"Must group by variables found in `.data`.",
x = glue("Column `{unknown}` is not found.")
)
abort(bullets, call = error_call)
}
list(
data = out,
groups = syms(group_names),
group_names = group_names
)
}
add_computed_columns <- function(.data, vars, error_call = caller_env()) {
is_symbol <- map_lgl(vars, quo_is_variable_reference)
needs_mutate <- have_name(vars) | !is_symbol
if (any(needs_mutate)) {
# TODO: use less of a hack
if (inherits(.data, "data.frame")) {
bare_data <- ungroup(.data)
by <- compute_by(by = NULL, data = bare_data)
cols <- mutate_cols(
bare_data,
dplyr_quosures(!!!vars),
by = by,
error_call = error_call
)
out <- dplyr_col_modify(.data, cols)
col_names <- names(cols)
} else {
out <- mutate(.data, !!!vars)
col_names <- names(exprs_auto_name(vars))
}
} else {
out <- .data
col_names <- names(exprs_auto_name(vars))
}
list(data = out, added_names = col_names)
}
quo_is_variable_reference <- function(quo) {
if (quo_is_symbol(quo)) {
return(TRUE)
}
if (quo_is_call(quo, n = 2)) {
expr <- quo_get_expr(quo)
if (is_call(expr, c("$", "[["))) {
if (!identical(expr[[2]], sym(".data"))) {
return(FALSE)
}
param <- expr[[3]]
if (is_symbol(param) || is_string(param)) {
return(TRUE)
}
}
}
FALSE
}
#' Default value for .drop argument of group_by
#'
#' @param .tbl A data frame
#'
#' @return `TRUE` unless `.tbl` is a grouped data frame that was previously
#' obtained by `group_by(.drop = FALSE)`
#'
#' @examples
#' group_by_drop_default(iris)
#'
#' iris |>
#' group_by(Species) |>
#' group_by_drop_default()
#'
#' iris |>
#' group_by(Species, .drop = FALSE) |>
#' group_by_drop_default()
#'
#' @keywords internal
#' @export
group_by_drop_default <- function(.tbl) {
UseMethod("group_by_drop_default")
}
#' @export
group_by_drop_default.default <- function(.tbl) {
TRUE
}
#' @export
group_by_drop_default.grouped_df <- function(.tbl) {
tryCatch(
{
!identical(attr(group_data(.tbl), ".drop"), FALSE)
},
error = function(e) {
TRUE
}
)
}