Skip to content

Commit 6d437e1

Browse files
committed
Move .by to the data frame method, remove .by handling, misc tweaks
1 parent c346ccb commit 6d437e1

File tree

5 files changed

+102
-54
lines changed

5 files changed

+102
-54
lines changed

NEWS.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# tidyr (development version)
22

3+
* `fill()` gains a `.by` argument as an alternative to `dplyr::group_by()` for
4+
applying the fill per group, similar to `nest(.by =)` and
5+
`dplyr::mutate(.by =)` (@olivroy, #1439).
6+
37
* `expand_grid()` gains a new `.vary` argument, allowing users to control
48
whether the first column varies fastest or slowest (#1543, @JamesHWade).
59

@@ -12,8 +16,6 @@
1216

1317
* tidyr now requires dplyr >=1.1.0 (#1568, @catalamarti).
1418

15-
* `fill()` gains `.by` similar to how `mutate()` works (@olivroy, #1439).
16-
1719
# tidyr 1.3.1
1820

1921
* `pivot_wider` now uses `.by` and `|>` syntax for the dplyr helper message to

R/fill.R

+32-25
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
#' @section Grouped data frames:
1010
#' With grouped data frames created by [dplyr::group_by()], `fill()` will be
1111
#' applied _within_ each group, meaning that it won't fill across group
12-
#' boundaries.
12+
#' boundaries. This can also be accomplished using the `.by` argument to
13+
#' `fill()`, which creates a temporary grouping for just this operation.
1314
#'
1415
#' @param data A data frame.
1516
#' @param ... <[`tidy-select`][tidyr_tidy_select]> Columns to fill.
16-
#' @inheritParams dplyr::mutate
1717
#' @param .direction Direction in which to fill missing values. Currently
1818
#' either "down" (the default), "up", "downup" (i.e. first down and then up)
1919
#' or "updown" (first up and then down).
@@ -83,27 +83,39 @@
8383
#' 3, "Danielle", "Observer", NA
8484
#' )
8585
#'
86-
#' # The values are inconsistently missing by position within the group
87-
#' # Use .direction = "downup" to fill missing values in both directions
86+
#' # The values are inconsistently missing by position within the `group`.
87+
#' # Use `.direction = "downup"` to fill missing values in both directions
88+
#' # and `.by = group` to apply the fill per group.
89+
#' squirrels %>%
90+
#' fill(n_squirrels, .direction = "downup", .by = group)
91+
#'
92+
#' # If you want, you can also supply a data frame grouped with `group_by()`,
93+
#' # but don't forget to `ungroup()`!
8894
#' squirrels %>%
8995
#' dplyr::group_by(group) %>%
9096
#' fill(n_squirrels, .direction = "downup") %>%
9197
#' dplyr::ungroup()
92-
#'
93-
#' # Using `.direction = "updown"` accomplishes the same goal in this example
94-
#'
95-
#' # Using .by simplifies this example.
96-
#' squirrels %>%
97-
#' fill(n_squirrels, .direction = "downup", .by = group)
98-
fill <- function(data, ..., .direction = c("down", "up", "downup", "updown"), .by = NULL) {
99-
check_dots_unnamed()
98+
fill <- function(data,
99+
...,
100+
.direction = c("down", "up", "downup", "updown")) {
100101
UseMethod("fill")
101102
}
102103

104+
#' @inheritParams dplyr::mutate
105+
#' @rdname fill
103106
#' @export
104-
fill.data.frame <- function(data, ..., .direction = c("down", "up", "downup", "updown"), .by = NULL) {
105-
vars <- names(tidyselect::eval_select(expr(c(...)), data = data, allow_rename = FALSE))
106-
by <- names(tidyselect::eval_select(enquo(.by), data = data, allow_rename = FALSE))
107+
fill.data.frame <- function(data,
108+
...,
109+
.direction = c("down", "up", "downup", "updown"),
110+
.by = NULL) {
111+
# Must be here rather than in the generic due to the placement of `.by`
112+
check_dots_unnamed()
113+
114+
vars <- names(tidyselect::eval_select(
115+
expr = expr(c(...)),
116+
data = data,
117+
allow_rename = FALSE
118+
))
107119

108120
.direction <- arg_match0(
109121
arg = .direction,
@@ -114,14 +126,9 @@ fill.data.frame <- function(data, ..., .direction = c("down", "up", "downup", "u
114126
vec_fill_missing(col, direction = .direction)
115127
}
116128

117-
if (dplyr::is_grouped_df(data)) {
118-
if (length(by) > 0) {
119-
cli::cli_abort(c(
120-
"Can't supply {.arg .by} when {.arg data} is a grouped data frame."
121-
))
122-
}
123-
dplyr::mutate(data, dplyr::across(any_of(vars), .fns = fn))
124-
} else {
125-
dplyr::mutate(data, dplyr::across(any_of(vars), .fns = fn), .by = all_of(by))
126-
}
129+
dplyr::mutate(
130+
data,
131+
dplyr::across(any_of(vars), .fns = fn),
132+
.by = {{ .by }}
133+
)
127134
}

man/fill.Rd

+14-10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/_snaps/fill.md

+19-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
# errors on named `...` inputs
2+
3+
Code
4+
fill(df, fooy = x)
5+
Condition
6+
Error in `fill()`:
7+
! Arguments in `...` must be passed by position, not name.
8+
x Problematic argument:
9+
* fooy = x
10+
111
# validates its inputs
212

313
Code
@@ -6,17 +16,20 @@
616
Error in `fill()`:
717
! `.direction` must be one of "down", "up", "downup", or "updown", not "foo".
818

9-
# fill works with .by
19+
# `.by` can't select columns that don't exist
1020

1121
Code
12-
df %>% fill(y, .by = z)
22+
fill(df, y, .by = z)
1323
Condition
14-
Error in `fill()`:
24+
Error in `dplyr::mutate()`:
1525
! Can't select columns that don't exist.
1626
x Column `z` doesn't exist.
27+
28+
# `.by` can't be used on a grouped data frame
29+
1730
Code
18-
gr_df %>% fill(y, .by = x)
31+
fill(df, y, .by = x)
1932
Condition
20-
Error in `fill()`:
21-
! Can't supply `.by` when `data` is a grouped data frame.
33+
Error in `dplyr::mutate()`:
34+
! Can't supply `.by` when `.data` is a grouped data frame.
2235

tests/testthat/test-fill.R

+33-11
Original file line numberDiff line numberDiff line change
@@ -106,37 +106,59 @@ test_that("fill preserves attributes", {
106106
expect_equal(attributes(out_u$x), attributes(df$x))
107107
})
108108

109-
test_that("fill respects grouping and `.by`", {
109+
test_that("fill respects existing grouping and `.by`", {
110110
df <- tibble(x = c(1, 1, 2), y = c(1, NA, NA))
111+
111112
out <- df %>%
112113
dplyr::group_by(x) %>%
113114
fill(y)
114-
expect_equal(out$y, c(1, 1, NA))
115+
expect_identical(out$y, c(1, 1, NA))
116+
expect_identical(dplyr::group_vars(out), "x")
117+
115118
out <- df %>%
116119
fill(y, .by = x)
117-
expect_equal(out$y, c(1, 1, NA))
118-
119-
120+
expect_identical(out$y, c(1, 1, NA))
121+
expect_identical(dplyr::group_vars(out), character())
120122
})
121123

122124
test_that("works when there is a column named `.direction` in the data (#1319)", {
123125
df <- tibble(x = c(1, NA, 2), .direction = 1:3)
124-
expect_no_error(out <- fill(df, x))
126+
out <- fill(df, x)
125127
expect_identical(out$x, c(1, 1, 2))
126128
})
127129

130+
test_that("errors on named `...` inputs", {
131+
df <- tibble(x = c(1, NA, 2))
132+
133+
expect_snapshot(error = TRUE, {
134+
fill(df, fooy = x)
135+
})
136+
})
137+
128138
test_that("validates its inputs", {
129139
df <- tibble(x = c(1, NA, 2))
130140
expect_snapshot(error = TRUE, {
131141
df %>% fill(x, .direction = "foo")
132142
})
133143
})
134144

135-
test_that("fill works with .by", {
136-
df <- tibble(x = c(1, 1, 2), y = c(1, NA, NA))
137-
gr_df <- dplyr::group_by(df, x)
145+
test_that("`.by` can't select columns that don't exist", {
146+
df <- tibble(x = 1, y = 2)
147+
148+
# This shows `mutate()` in the error, but we accept that to not have to handle
149+
# `.by` in any way.
150+
expect_snapshot(error = TRUE, {
151+
fill(df, y, .by = z)
152+
})
153+
})
154+
155+
test_that("`.by` can't be used on a grouped data frame", {
156+
df <- tibble(x = 1, y = 2)
157+
df <- dplyr::group_by(df, x)
158+
159+
# This shows `mutate()` in the error, but we accept that to not have to handle
160+
# `.by` in any way.
138161
expect_snapshot(error = TRUE, {
139-
df %>% fill(y, .by = z)
140-
gr_df %>% fill(y, .by = x)
162+
fill(df, y, .by = x)
141163
})
142164
})

0 commit comments

Comments
 (0)