Skip to content

Commit 0e57934

Browse files
authored
Clarify that both character() and NULL are allowed for names_to (#1234)
* Clarify `names_to` handling in `build_longer_spec()` * Refresh `names_to` documentation to describe the 3 cases Explicitly allow `character()` or `NULL`, as both seem reasonable and we already do this in the CRAN version
1 parent a63a537 commit 0e57934

File tree

5 files changed

+111
-41
lines changed

5 files changed

+111
-41
lines changed

R/pivot-long.R

+44-23
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,25 @@
1616
#' @param data A data frame to pivot.
1717
#' @param cols <[`tidy-select`][tidyr_tidy_select]> Columns to pivot into
1818
#' longer format.
19-
#' @param names_to A string specifying the name of the column to create
20-
#' from the data stored in the column names of `data`.
19+
#' @param names_to A character vector specifying the new column or columns to
20+
#' create from the information stored in the column names of `data` specified
21+
#' by `cols`.
2122
#'
22-
#' Can be a character vector, creating multiple columns, if `names_sep`
23-
#' or `names_pattern` is provided. In this case, there are two special
24-
#' values you can take advantage of:
23+
#' * If length 0, or if `NULL` is supplied, no columns will be created.
2524
#'
26-
#' * `NA` will discard that component of the name.
27-
#' * `.value` indicates that component of the name defines the name of the
28-
#' column containing the cell values, overriding `values_to`.
25+
#' * If length 1, a single column will be created which will contain the
26+
#' column names specified by `cols`.
27+
#'
28+
#' * If length >1, multiple columns will be created. In this case, one of
29+
#' `names_sep` or `names_pattern` must be supplied to specify how the
30+
#' column names should be split. There are also two additional character
31+
#' values you can take advantage of:
32+
#'
33+
#' * `NA` will discard the corresponding component of the column name.
34+
#'
35+
#' * `".value"` indicates that the corresponding component of the column
36+
#' name defines the name of the output column containing the cell values,
37+
#' overriding `values_to` entirely.
2938
#' @param names_prefix A regular expression used to remove matching text
3039
#' from the start of each variable name.
3140
#' @param names_sep,names_pattern If `names_to` contains multiple values,
@@ -262,7 +271,8 @@ pivot_longer_spec <- function(data,
262271

263272
#' @rdname pivot_longer_spec
264273
#' @export
265-
build_longer_spec <- function(data, cols,
274+
build_longer_spec <- function(data,
275+
cols,
266276
names_to = "name",
267277
values_to = "value",
268278
names_prefix = NULL,
@@ -282,30 +292,41 @@ build_longer_spec <- function(data, cols,
282292
names <- gsub(vec_paste0("^", names_prefix), "", names(cols))
283293
}
284294

285-
if (length(names_to) > 1) {
286-
if (!xor(is.null(names_sep), is.null(names_pattern))) {
295+
if (is.null(names_to)) {
296+
names_to <- character(0L)
297+
}
298+
if (!is.character(names_to)) {
299+
abort("`names_to` must be a character vector or `NULL`.")
300+
}
301+
302+
n_names_to <- length(names_to)
303+
has_names_sep <- !is.null(names_sep)
304+
has_names_pattern <- !is.null(names_pattern)
305+
306+
if (n_names_to == 0L) {
307+
names <- tibble::new_tibble(x = list(), nrow = length(names))
308+
} else if (n_names_to == 1L) {
309+
if (has_names_sep) {
310+
abort("`names_sep` can't be used with a length 1 `names_to`.")
311+
}
312+
if (has_names_pattern) {
313+
names <- str_extract(names, names_to, regex = names_pattern)[[1]]
314+
}
315+
316+
names <- tibble(!!names_to := names)
317+
} else {
318+
if (!xor(has_names_sep, has_names_pattern)) {
287319
abort(glue::glue(
288320
"If you supply multiple names in `names_to` you must also supply one",
289321
" of `names_sep` or `names_pattern`."
290322
))
291323
}
292324

293-
if (!is.null(names_sep)) {
325+
if (has_names_sep) {
294326
names <- str_separate(names, names_to, sep = names_sep)
295327
} else {
296328
names <- str_extract(names, names_to, regex = names_pattern)
297329
}
298-
} else if (length(names_to) == 0) {
299-
names <- tibble::new_tibble(x = list(), nrow = length(names))
300-
} else {
301-
if (!is.null(names_sep)) {
302-
abort("`names_sep` can not be used with length-1 `names_to`")
303-
}
304-
if (!is.null(names_pattern)) {
305-
names <- str_extract(names, names_to, regex = names_pattern)[[1]]
306-
}
307-
308-
names <- tibble(!!names_to := names)
309330
}
310331

311332
if (".value" %in% names_to) {

man/pivot_longer.Rd

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

man/pivot_longer_spec.Rd

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

tests/testthat/_snaps/pivot-long.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# `names_to` is validated
2+
3+
Code
4+
(expect_error(build_longer_spec(df, x, names_to = 1)))
5+
Output
6+
<error/rlang_error>
7+
`names_to` must be a character vector or `NULL`.
8+
Code
9+
(expect_error(build_longer_spec(df, x, names_to = c("x", "y"))))
10+
Output
11+
<error/rlang_error>
12+
If you supply multiple names in `names_to` you must also supply one of `names_sep` or `names_pattern`.
13+
Code
14+
(expect_error(build_longer_spec(df, x, names_to = c("x", "y"), names_sep = "_",
15+
names_pattern = "x")))
16+
Output
17+
<error/rlang_error>
18+
If you supply multiple names in `names_to` you must also supply one of `names_sep` or `names_pattern`.
19+

tests/testthat/test-pivot-long.R

+18-2
Original file line numberDiff line numberDiff line change
@@ -170,12 +170,18 @@ test_that("validates inputs", {
170170
)
171171
})
172172

173-
test_that("no names doesn't generate names", {
173+
test_that("no names doesn't generate names (#1120)", {
174174
df <- tibble(x = 1)
175-
expect_equal(
175+
176+
expect_identical(
176177
colnames(build_longer_spec(df, x, names_to = character())),
177178
c(".name", ".value")
178179
)
180+
181+
expect_identical(
182+
colnames(build_longer_spec(df, x, names_to = NULL)),
183+
c(".name", ".value")
184+
)
179185
})
180186

181187
test_that("multiple names requires names_sep/names_pattern", {
@@ -245,3 +251,13 @@ test_that("can cast to custom type", {
245251
test_that("Error if the `col` can't be selected.", {
246252
expect_error(pivot_longer(iris, matches("foo")), "select at least one")
247253
})
254+
255+
test_that("`names_to` is validated", {
256+
df <- tibble(x = 1)
257+
258+
expect_snapshot({
259+
(expect_error(build_longer_spec(df, x, names_to = 1)))
260+
(expect_error(build_longer_spec(df, x, names_to = c("x", "y"))))
261+
(expect_error(build_longer_spec(df, x, names_to = c("x", "y"), names_sep = "_", names_pattern = "x")))
262+
})
263+
})

0 commit comments

Comments
 (0)