Skip to content

Commit 1599df5

Browse files
authored
Add some missing grouped_df methods (#4710)
* Drop groups when subsetting. Fixes #4708 * Implement subset assignment methods for grouped_df. Fixes #4004 * Add news
1 parent b9a66d8 commit 1599df5

File tree

7 files changed

+93
-30
lines changed

7 files changed

+93
-30
lines changed

NAMESPACE

+3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
# Generated by roxygen2: do not edit by hand
22

3+
S3method("$<-",grouped_df)
34
S3method("[",fun_list)
45
S3method("[",grouped_df)
6+
S3method("[<-",grouped_df)
7+
S3method("[[<-",grouped_df)
58
S3method(anti_join,data.frame)
69
S3method(anti_join,tbl_df)
710
S3method(arrange,data.frame)

NEWS.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# dplyr 0.9.0 (in development)
22

3+
* Grouped data frames now have `[[<-`, `[<-` and `$<-` methods that will
4+
re-generate the underlying grouping. Note that modifying grouping variables
5+
in multiple steps (i.e. `df$grp1 <- 1; df$grp2 <- 1`) will be inefficient
6+
since the data frame will be regrouped after each modification.
7+
8+
* `[.grouped_df` now regroups to respect any grouping columns that have
9+
been removed (#4708).
310
* `as.tbl()` and `tbl_df()` have been formally deprecated.
411
Please use `as_tibble()` instead.
512

R/grouped-df.r

+33-7
Original file line numberDiff line numberDiff line change
@@ -204,20 +204,46 @@ as_tibble.grouped_df <- function(x, ...) {
204204
#' @importFrom tibble is_tibble
205205
#' @export
206206
`[.grouped_df` <- function(x, i, j, drop = FALSE) {
207-
y <- NextMethod()
207+
out <- NextMethod()
208208

209-
if (isTRUE(drop) && !is_tibble(y)) {
210-
return(y)
209+
if (!is.data.frame(out)) {
210+
return(out)
211211
}
212212

213-
group_names <- group_vars(x)
214-
if (!all(group_names %in% names(y))) {
215-
as_tibble(y)
213+
if (drop) {
214+
as_tibble(out)
216215
} else {
217-
grouped_df(y, group_names, group_by_drop_default(x))
216+
groups <- intersect(names(out), group_vars(x))
217+
if ((missing(i) || nargs() == 2) && identical(groups, group_vars(x))) {
218+
new_grouped_df(out, group_data(x))
219+
} else {
220+
grouped_df(out, groups, group_by_drop_default(x))
221+
}
222+
}
223+
}
224+
225+
#' @export
226+
`$<-.grouped_df` <- function(x, name, ..., value) {
227+
out <- NextMethod()
228+
if (name %in% group_vars(x)) {
229+
grouped_df(out, intersect(names(out), group_vars(x)), group_by_drop_default(x))
230+
} else {
231+
out
218232
}
219233
}
220234

235+
#' @export
236+
`[<-.grouped_df` <- function(x, i, j, ..., value) {
237+
out <- NextMethod()
238+
grouped_df(out, intersect(names(out), group_vars(x)), group_by_drop_default(x))
239+
}
240+
241+
#' @export
242+
`[[<-.grouped_df` <- function(x, ..., value) {
243+
out <- NextMethod()
244+
grouped_df(out, intersect(names(out), group_vars(x)), group_by_drop_default(x))
245+
}
246+
221247
#' @method rbind grouped_df
222248
#' @export
223249
rbind.grouped_df <- function(...) {

R/select.R

-10
Original file line numberDiff line numberDiff line change
@@ -148,16 +148,6 @@ select_impl <- function(.data, vars) {
148148
if (is_grouped_df(.data)) {
149149
# we might have to alter the names of the groups metadata
150150
groups <- attr(.data, "groups")
151-
152-
# check grouped metadata
153-
group_names <- names(groups)[seq_len(ncol(groups) - 1L)]
154-
if (any(test <- ! group_names %in% vars)) {
155-
abort(
156-
glue("{col} not found in groups metadata. Probably a corrupt grouped_df object.", col = group_names[test[1L]]),
157-
"dplyr_select_corrupt_grouped_df"
158-
)
159-
}
160-
161151
group_vars <- c(vars[vars %in% names(groups)], .rows = ".rows")
162152
groups <- select_impl(groups, group_vars)
163153

tests/testthat/test-group-by.r

-6
Original file line numberDiff line numberDiff line change
@@ -319,12 +319,6 @@ test_that("tbl_sum gets the right number of groups", {
319319
expect_equal(res, c("A tibble" = "4 x 1", "Groups" = "x [2]"))
320320
})
321321

322-
test_that("grouped data frames support drop=TRUE (#3714)", {
323-
expect_is(group_by(iris, Species)[ , "Sepal.Width", drop=TRUE], "numeric")
324-
325-
expect_is(group_by(iris, Species)[ , c("Species", "Sepal.Width"), drop=TRUE], "grouped_df")
326-
})
327-
328322
test_that("group_by ignores empty quosures (3780)", {
329323
empty <- quo()
330324
expect_equal(group_by(mtcars, cyl), group_by(mtcars, cyl, !!empty))

tests/testthat/test-grouped-df.r

+50
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,53 @@ test_that("can selectively ungroup", {
66
expect_equal(gf %>% ungroup(x) %>% group_vars(), "y")
77
expect_error(gf %>% ungroup(z) %>% group_vars(), "z")
88
})
9+
10+
11+
test_that("[ method can remove grouping vars", {
12+
df <- tibble(x = 1, y = 2, z = 3)
13+
gf <- group_by(df, x, y)
14+
15+
expect_equal(gf, gf)
16+
expect_equal(gf[1], group_by(df[1], x))
17+
expect_equal(gf[3], df[3])
18+
})
19+
20+
test_that("[ method reuses group_data() if possible", {
21+
df <- tibble(x = 1, y = 2, z = 3)
22+
gf <- group_by(df, x, y)
23+
24+
expect_reference(group_data(gf), group_data(gf[1:2]))
25+
expect_reference(group_data(gf), group_data(gf[, 1:2]))
26+
})
27+
28+
test_that("[ supports drop=TRUE (#3714)", {
29+
df <- tibble(x = 1, y = 2)
30+
gf <- group_by(df, x)
31+
32+
expect_type(gf[, "y", drop = TRUE], "double")
33+
expect_s3_class(gf[, c("x", "y"), drop = TRUE], "tbl_df")
34+
})
35+
36+
test_that("$<-, [[<-, and [<- update grouping data if needed", {
37+
df <- tibble(x = 1, y = 2)
38+
gf <- group_by(df, x)
39+
40+
expect_equal(group_data(`$<-`(gf, "x", 2))$x, 2)
41+
expect_equal(group_data(`$<-`(gf, "y", 2))$x, 1)
42+
43+
expect_equal(group_data({gf2 <- gf; gf2[[1]] <- 3; gf2})$x, 3)
44+
expect_equal(group_data(`[<-`(gf, 1, "x", 4))$x, 4)
45+
})
46+
47+
test_that("can remove grouping cols with subset assignment", {
48+
df <- tibble(x = 1, y = 2)
49+
gf1 <- gf2 <- gf3 <- group_by(df, x, y)
50+
51+
gf1$x <- NULL
52+
gf2[["x"]] <- NULL
53+
gf3[, "x"] <- NULL
54+
55+
expect_named(group_data(gf1), c("y", ".rows"))
56+
expect_named(group_data(gf2), c("y", ".rows"))
57+
expect_named(group_data(gf3), c("y", ".rows"))
58+
})

tests/testthat/test-select.r

-7
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,6 @@ test_that("select can be before group_by (#309)", {
6464
expect_equal(names(dfagg), c("id", "year", "var1"))
6565
})
6666

67-
test_that("rename errors with invalid grouped data frame (#640)", {
68-
df <- tibble(a = 1:3, b = 2:4, d = 3:5) %>% group_by(a, b)
69-
df$a <- NULL
70-
expect_error(df %>% rename(e = d))
71-
expect_error(df %>% rename(e = b))
72-
})
73-
7467
test_that("rename() handles data pronoun", {
7568
expect_identical(rename(tibble(x = 1), y = .data$x), tibble(y = 1))
7669
})

0 commit comments

Comments
 (0)