Repository: tidyverse/tidyr

Issue #1397: Tidyselect support in complete and friends
Created: 2022-09-22T11:55:32Z
This is basically re-opening issue #1032, as suggested by #1032 (comment).
Question was how to use strings in complete, Hadley's answer was to use any_of() or all_of, though it doesn't seem to work, or is unclear how to use?
library(tibble)
library(tidyr)

df <- tibble(
  group = c(1:2, 1, 2),
  item_id = c(1:2, 2, 3),
  item_name = c("a", "a", "b", "b"),
  value1 = c(1, NA, 3, 4),
  value2 = 4:7
)

complete(df, group, item_id, item_name)
#> # A tibble: 12 × 5
#>    group item_id item_name value1 value2
#>    <dbl>   <dbl> <chr>      <dbl>  <int>
#>  1     1       1 a              1      4
#>  2     1       1 b             NA     NA
#>  3     1       2 a             NA     NA
#>  4     1       2 b              3      6
#>  5     1       3 a             NA     NA
#>  6     1       3 b             NA     NA
#>  7     2       1 a             NA     NA
#>  8     2       1 b             NA     NA
#>  9     2       2 a             NA      5
#> 10     2       2 b             NA     NA
#> 11     2       3 a             NA     NA
#> 12     2       3 b              4      7
complete(df, all_of(c("group", "item_id", "item_name")))
#> Error in `dplyr::full_join()`:
#> ! Join columns must be present in data.
#> ✖ Problem with `all_of(c("group", "item_id", "item_name"))`.

#> Backtrace:
#>     ▆
#>  1. ├─tidyr::complete(df, all_of(c("group", "item_id", "item_name")))
#>  2. └─tidyr:::complete.data.frame(...)
#>  3.   ├─dplyr::full_join(out, data, by = names)
#>  4.   └─dplyr:::full_join.data.frame(out, data, by = names)
#>  5.     └─dplyr:::join_mutate(...)
#>  6.       └─dplyr:::join_cols(...)
#>  7.         └─dplyr:::check_join_vars(by$y, y_names, by$condition, keep, error_call = error_call)
#>  8.           └─rlang::abort(bullets, call = error_call)
complete(df, any_of(c("group", "item_id", "item_name")))
#> Error:
#> ! `any_of()` must be used within a *selecting* function.
#> ℹ See <https://tidyselect.r-lib.org/reference/faq-selection-context.html>.

#> Backtrace:
#>      ▆
#>   1. ├─tidyr::complete(df, any_of(c("group", "item_id", "item_name")))
#>   2. ├─tidyr:::complete.data.frame(...)
#>   3. │ ├─tidyr::expand(data, ...)
#>   4. │ └─tidyr:::expand.data.frame(data, ...)
#>   5. │   └─tidyr:::grid_dots(..., `_data` = data)
#>   6. │     └─rlang::eval_tidy(dot, data = mask)
#>   7. └─tidyselect::any_of(c("group", "item_id", "item_name"))
#>   8.   ├─vars %||% peek_vars(fn = "any_of")
#>   9.   └─tidyselect::peek_vars(fn = "any_of")
#>  10.     └─rlang::abort(msg, call = NULL)
Created on 2022-09-22 with reprex v2.0.2

Duplicate issue #1523: Feature Request: Allow across() for column selection in complete()
Created: 2023-10-04T18:28:56Z
Because complete() can't act on grouping variables, we need to be able to work around this when necessary. One solution is to pull the grouping variables via group_vars() , then ungroup() the data  and supply those strings to complete(). However, complete does not accept string inputs via direct use of all_of( ) or any_of() or the indirect use inside of across() via  across(all_of( my_grp_vars )) solution. All of the other more nebulous tricks that I've found success in other scenarios, (e.g. {{ }}, !!ensym(), or !!enquo()  ) also don't work.
library(tidyverse)

### A simple toy dataset
df <- data.frame(
  group1=factor(c('A','B'), levels=c('A','B','C')),
  group2=factor(c('W','X'), levels=c('W','X')),
  value = 1:2)

### What we actually want
df |> 
  complete(group1, group2)
#> # A tibble: 6 × 3
#>   group1 group2 value
#>   <fct>  <fct>  <int>
#> 1 A      W          1
#> 2 A      X         NA
#> 3 B      W         NA
#> 4 B      X          2
#> 5 C      W         NA
#> 6 C      X         NA

### The grouping variables we want.
by = c('group1','group2')

### Unfortunately any_of() can't be directly used, which is common, but
### we can not use across() inside complete() (or expand() for that matter)
df |>
  complete( across(any_of(by)) )
#> Error in `across()`:
#> ! Must only be used inside data-masking verbs like `mutate()`,
#>   `filter()`, and `group_by()`.
#> Backtrace:
#>      ▆
#>   1. ├─tidyr::complete(df, across(any_of(by)))
#>   2. ├─tidyr:::complete.data.frame(df, across(any_of(by)))
#>   3. │ ├─tidyr::expand(data, ...)
#>   4. │ └─tidyr:::expand.data.frame(data, ...)
#>   5. │   └─tidyr:::grid_dots(..., `_data` = data)
#>   6. │     └─rlang::eval_tidy(dot, data = mask)
#>   7. └─dplyr::across(any_of(by))
#>   8.   └─dplyr:::peek_mask()
#>   9.     └─dplyr:::context_peek(...)
#>  10.       ├─context_peek_bare(name) %||% ...
#>  11.       └─rlang::abort(glue("Must only be used inside {location}."), call = call)
Created on 2023-10-04 with [reprex v2.0.2](https://reprex.tidyverse.org/)`

Closing comment (2023-11-01T20:58:34Z): Duplicate of #1397