1212# ' the predicate is `TRUE` for *any* of the selected columns, `if_all()`
1313# ' is `TRUE` when the predicate is `TRUE` for *all* selected columns.
1414# '
15+ # ' If you just need to select columns without applying a transformation to each
16+ # ' of them, then you probably want to use [pick()] instead.
17+ # '
1518# ' `across()` supersedes the family of "scoped variants" like
1619# ' `summarise_at()`, `summarise_if()`, and `summarise_all()`.
1720# '
2730# ' `list(mean = mean, n_miss = ~ sum(is.na(.x))`. Each function is applied
2831# ' to each column, and the output is named by combining the function name
2932# ' and the column name using the glue specification in `.names`.
30- # ' - `NULL`: the default, returns the selected columns in a data frame
31- # ' without applying a transformation. This is useful for when you want to
32- # ' use a function that takes a data frame.
3333# '
3434# ' Within these functions you can use [cur_column()] and [cur_group()]
3535# ' to access the current column and grouping keys respectively.
168168# '
169169# ' @export
170170# ' @seealso [c_across()] for a function that returns a vector
171- across <- function (.cols = everything() ,
172- .fns = NULL ,
171+ across <- function (.cols ,
172+ .fns ,
173173 ... ,
174174 .names = NULL ,
175175 .unpack = FALSE ) {
176176 mask <- peek_mask()
177177 caller_env <- caller_env()
178178
179+ .cols <- enquo(.cols )
180+
181+ if (quo_is_missing(.cols )) {
182+ across_missing_cols_deprecate_warn()
183+ .cols <- quo_set_expr(.cols , expr(everything()))
184+ }
185+ if (is_missing(.fns )) {
186+ # Silent restoration to old defaults of `.fns` for now.
187+ # TODO: Escalate this to formal deprecation.
188+ .fns <- NULL
189+ }
190+
179191 if (! is_bool(.unpack ) && ! is_string(.unpack )) {
180192 stop_input_type(.unpack , " `TRUE`, `FALSE`, or a single string" )
181193 }
@@ -188,7 +200,7 @@ across <- function(.cols = everything(),
188200 }
189201
190202 setup <- across_setup(
191- {{ .cols }} ,
203+ cols = !! .cols ,
192204 fns = .fns ,
193205 names = .names ,
194206 .caller_env = caller_env ,
@@ -221,6 +233,7 @@ across <- function(.cols = everything(),
221233 names <- setup $ names
222234
223235 if (is.null(fns )) {
236+ # TODO: Deprecate and remove the `.fns = NULL` path in favor of `pick()`
224237 data <- mask $ pick_current(vars )
225238
226239 if (is.null(names )) {
@@ -281,13 +294,13 @@ across <- function(.cols = everything(),
281294
282295# ' @rdname across
283296# ' @export
284- if_any <- function (.cols = everything() , .fns = NULL , ... , .names = NULL ) {
297+ if_any <- function (.cols , .fns , ... , .names = NULL ) {
285298 context_local(" across_if_fn" , " if_any" )
286299 if_across(`|` , across({{ .cols }}, .fns , ... , .names = .names ))
287300}
288301# ' @rdname across
289302# ' @export
290- if_all <- function (.cols = everything() , .fns = NULL , ... , .names = NULL ) {
303+ if_all <- function (.cols , .fns , ... , .names = NULL ) {
291304 context_local(" across_if_fn" , " if_all" )
292305 if_across(`&` , across({{ .cols }}, .fns , ... , .names = .names ))
293306}
@@ -330,13 +343,17 @@ if_across <- function(op, df) {
330343# ' mutate(
331344# ' sum = sum(c_across(w:z)),
332345# ' sd = sd(c_across(w:z))
333- # ' )
334- c_across <- function (cols = everything()) {
346+ # ' )
347+ c_across <- function (cols ) {
348+ mask <- peek_mask()
335349 cols <- enquo(cols )
336350
337- mask <- peek_mask()
338- vars <- c_across_setup(!! cols , mask = mask )
351+ if (quo_is_missing(cols )) {
352+ c_across_missing_cols_deprecate_warn()
353+ cols <- quo_set_expr(cols , expr(everything()))
354+ }
339355
356+ vars <- c_across_setup(!! cols , mask = mask )
340357
341358 cols <- mask $ current_cols(vars )
342359 vec_c(!!! cols , .name_spec = zap())
@@ -387,6 +404,7 @@ across_setup <- function(cols,
387404 vars <- names(data )[vars ]
388405
389406 if (is.null(fns )) {
407+ # TODO: Eventually deprecate and remove the `.fns = NULL` path in favor of `pick()`
390408 if (! is.null(names )) {
391409 glue_mask <- across_glue_mask(.caller_env , .col = names_vars , .fn = " 1" )
392410 names <- vec_as_names(
@@ -411,7 +429,7 @@ across_setup <- function(cols,
411429 }
412430
413431 if (! is.list(fns )) {
414- msg <- c(" `.fns` must be NULL, a function, a formula, or a list of functions/formulas." )
432+ msg <- c(" `.fns` must be a function, a formula, or a list of functions/formulas." )
415433 abort(msg , call = call(across_if_fn ))
416434 }
417435
@@ -614,13 +632,22 @@ expand_across <- function(quo) {
614632 if (" .cols" %in% names(expr )) {
615633 cols <- expr $ .cols
616634 } else {
617- cols <- quote(everything())
635+ across_missing_cols_deprecate_warn()
636+ cols <- expr(everything())
618637 }
619638 cols <- as_quosure(cols , env )
620639
640+ if (" .fns" %in% names(expr )) {
641+ fns <- eval_tidy(expr $ .fns , mask , env = env )
642+ } else {
643+ # In the missing case, silently restore the old default of `NULL`.
644+ # TODO: Escalate this to formal deprecation.
645+ fns <- NULL
646+ }
647+
621648 setup <- across_setup(
622649 !! cols ,
623- fns = eval_tidy( expr $ . fns, mask , env = env ) ,
650+ fns = fns ,
624651 names = eval_tidy(expr $ .names , mask , env = env ),
625652 .caller_env = env ,
626653 mask = dplyr_mask ,
@@ -639,6 +666,7 @@ expand_across <- function(quo) {
639666
640667 # No functions, so just return a list of symbols
641668 if (is.null(fns )) {
669+ # TODO: Deprecate and remove the `.fns = NULL` path in favor of `pick()`
642670 expressions <- pmap(list (vars , names , seq_along(vars )), function (var , name , k ) {
643671 quo <- new_quosure(sym(var ), empty_env())
644672 quo <- new_dplyr_quosure(
@@ -738,6 +766,45 @@ is_inlinable_formula <- function(x, mask) {
738766 }
739767}
740768
769+ across_missing_cols_deprecate_warn <- function () {
770+ across_if_fn <- context_peek_bare(" across_if_fn" ) %|| % " across"
771+
772+ # Passing the correct `user_env` through `expand_across()` to here is
773+ # complicated, so instead we force the global environment. This means users
774+ # won't ever see the "deprecated feature was likely used in the {pkg}"
775+ # message, but the warning will still fire and that is more important.
776+ user_env <- global_env()
777+
778+ cnd <- catch_cnd(classes = " lifecycle_warning_deprecated" , {
779+ lifecycle :: deprecate_warn(
780+ when = " 1.1.0" ,
781+ what = I(glue(" Using `{across_if_fn}()` without supplying `.cols`" )),
782+ details = " Please supply `.cols` instead." ,
783+ user_env = user_env
784+ )
785+ })
786+
787+ if (is_null(cnd )) {
788+ # Condition wasn't signaled
789+ return (NULL )
790+ }
791+
792+ # Subclassed so we can skip computing group context info when the warning
793+ # is thrown from `expand_across()` outside of any group
794+ class(cnd ) <- c(" dplyr:::warning_across_missing_cols_deprecated" , class(cnd ))
795+
796+ cnd_signal(cnd )
797+ }
798+
799+ c_across_missing_cols_deprecate_warn <- function (user_env = caller_env(2 )) {
800+ lifecycle :: deprecate_warn(
801+ when = " 1.1.0" ,
802+ what = I(" Using `c_across()` without supplying `cols`" ),
803+ details = " Please supply `cols` instead." ,
804+ user_env = user_env
805+ )
806+ }
807+
741808df_unpack <- function (x , spec , caller_env , error_call = caller_env()) {
742809 size <- vec_size(x )
743810
0 commit comments