Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,8 @@ export(union_all)
export(validate_grouped_df)
export(validate_rowwise_df)
export(vars)
export(when_all)
export(when_any)
export(where)
export(with_groups)
export(with_order)
Expand Down
10 changes: 10 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# dplyr (development version)

* New `when_any()` and `when_all()`, which are elementwise versions of `any()` and `all()`. Alternatively, you can think of them as performing repeated `|` and `&` on any number of inputs, for example:

* `when_any(x, y, z)` is equivalent to `x | y | z`.

* `when_all(x, y, z)` is equivalent to `x & y & z`.

`when_any()` is particularly useful within `filter()` and `filter_out()` to specify comma separated conditions combined with `|` rather than `&`.

This work is a result of [Tidyup 8: Expanding the `filter()` family](https://github.com/tidyverse/tidyups/pull/30).

* New experimental `filter_out()` companion to `filter()`.

* Use `filter()` when specifying rows to _keep_.
Expand Down
11 changes: 8 additions & 3 deletions R/filter.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
#' conditions evaluate to `TRUE`.
#'
#' Multiple conditions can be supplied separated by a comma. These will be
#' combined with the `&` operator.
#' combined with the `&` operator. To combine comma separated conditions using
#' `|` instead, wrap them in [when_any()].
#'
#' Both `filter()` and `filter_out()` treat `NA` like `FALSE`. This subtle
#' behavior can impact how you write your conditions when missing values are
Expand Down Expand Up @@ -113,6 +114,7 @@
#' * [`&`], [`|`], [`!`], [xor()]
#' * [is.na()]
#' * [between()], [near()]
#' * [when_any()], [when_all()]
#'
#' @section Grouped tibbles:
#'
Expand Down Expand Up @@ -157,8 +159,11 @@
#' filter(starwars, hair_color == "none" & eye_color == "black")
#' filter(starwars, hair_color == "none" | eye_color == "black")
#'
#' # When multiple expressions are used, they are combined using &
#' filter(starwars, hair_color == "none", eye_color == "black")
#' # Multiple comma separated expressions are combined using `&`
#' starwars |> filter(hair_color == "none", eye_color == "black")
#'
#' # To combine comma separated expressions using `|` instead, use `when_any()`
#' starwars |> filter(when_any(hair_color == "none", eye_color == "black"))
#'
#' # Filtering out to drop rows
#' filter_out(starwars, hair_color == "none")
Expand Down
162 changes: 162 additions & 0 deletions R/when.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#' Elementwise `any()` and `all()`
#'
#' @description
#' These functions are variants of [any()] and [all()] that work elementwise
#' across multiple inputs. You can also think of these functions as generalizing
#' [`|`] and [`&`] to any number of inputs, rather than just two, for example:
#'
#' - `when_any(x, y, z)` is equivalent to `x | y | z`.
#'
#' - `when_all(x, y, z)` is equivalent to `x & y & z`.
#'
#' `when_any()` is particularly useful within [filter()] and [filter_out()] to
#' specify comma separated conditions combined with `|` rather than `&`.
#'
#' @details
#' `when_any()` and `when_all()` are "parallel" versions of [any()] and [all()]
#' in the same way that [pmin()] and [pmax()] are "parallel" versions of [min()]
#' and [max()].
#'
#' @param ... Logical vectors of equal size.
#'
#' @param na_rm Missing value handling:
#'
#' - If `FALSE`, missing values are propagated according to the same rules as
#' `|` and `&`.
#'
#' - If `TRUE`, missing values are removed from the elementwise computation.
#'
#' @param size An optional output size. Only useful to specify if it is possible
#' for `...` to be empty, with no inputs provided.
#'
#' @name when-any-all
#'
#' @seealso [base::any()], [base::all()], [cumany()], [cumall()],
#' [base::pmin()], [base::pmax()]
#'
#' @examples
#' x <- c(TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, NA, NA, NA)
#' y <- c(TRUE, FALSE, NA, TRUE, FALSE, NA, TRUE, FALSE, NA)
#'
#' # `any()` and `all()` summarise down to 1 value
#' any(x, y)
#' all(x, y)
#'
#' # `when_any()` and `when_all()` work element by element across all inputs
#' # at the same time. Their defaults are equivalent to calling `|` or `&`.
#' when_any(x, y)
#' x | y
#'
#' when_all(x, y)
#' x & y
#'
#' # `na_rm = TRUE` is useful when you'd like to force these functions to
#' # return only `TRUE` or `FALSE`. This argument does so by removing any `NA`
#' # from the elementwise computation entirely.
#' tibble(
#' x = x,
#' y = y,
#' any_propagate = when_any(x, y),
#' any_remove = when_any(x, y, na_rm = TRUE),
#' all_propagate = when_all(x, y),
#' all_remove = when_all(x, y, na_rm = TRUE)
#' )
#'
#' # ---------------------------------------------------------------------------
#' # With `filter()` and `filter_out()`
#'
#' # `when_any()` is particularly useful inside of `filter()` and
#' # `filter_out()` as a way to combine comma separated conditions with `|`
#' # instead of with `&`.
#'
#' countries <- tibble(
#' name = c("US", "CA", "PR", "RU", "US", NA, "CA", "PR", "RU"),
#' score = c(200, 100, 150, NA, 50, 100, 300, 250, 120)
#' )
#' countries
#'
#' # Find rows where any of the following are true:
#' # - "US" and "CA" have a score between 200-300
#' # - "PR" and "RU" have a score between 100-200
#' countries |>
#' filter(
#' (name %in% c("US", "CA") & between(score, 200, 300)) |
#' (name %in% c("PR", "RU") & between(score, 100, 200))
#' )
#'
#' # With `when_any()`, you drop the explicit `|`, the extra `()`, and your
#' # conditions are all indented to the same level
#' countries |>
#' filter(when_any(
#' name %in% c("US", "CA") & between(score, 200, 300),
#' name %in% c("PR", "RU") & between(score, 100, 200)
#' ))
#'
#' # To drop these rows instead, use `filter_out()`
#' countries |>
#' filter_out(when_any(
#' name %in% c("US", "CA") & between(score, 200, 300),
#' name %in% c("PR", "RU") & between(score, 100, 200)
#' ))
#'
#' # ---------------------------------------------------------------------------
#' # Programming with `when_any()` and `when_all()`
#'
#' # When no inputs are provided, these functions are consistent with `any()`
#' # and `all()`
#' any()
#' when_any(size = 1)
#'
#' all()
#' when_all(size = 1)
#'
#' # The `size` argument is useful for making these functions size stable when
#' # you aren't sure how many inputs you're going to receive
#' size <- length(x)
#'
#' # Two inputs
#' inputs <- list(x, y)
#' when_all(!!!inputs, size = size)
#'
#' # One input
#' inputs <- list(x)
#' when_all(!!!inputs, size = size)
#'
#' # Zero inputs (without `size`, this would return `logical()`)
#' inputs <- list()
#' when_all(!!!inputs, size = size)
NULL

#' @rdname when-any-all
#' @export
when_any <- function(..., na_rm = FALSE, size = NULL) {
check_dots_unnamed()

check_bool(na_rm)
missing <- if (na_rm) FALSE else NA

vec_pany(
...,
.missing = missing,
.size = size,
.arg = "",
.error_call = current_env()
)
}

#' @rdname when-any-all
#' @export
when_all <- function(..., na_rm = FALSE, size = NULL) {
check_dots_unnamed()

check_bool(na_rm)
missing <- if (na_rm) TRUE else NA

vec_pall(
...,
.missing = missing,
.size = size,
.arg = "",
.error_call = current_env()
)
}
1 change: 1 addition & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ reference:
- order_by
- percent_rank
- row_number
- when_any

- title: Built in datasets
contents:
Expand Down
11 changes: 8 additions & 3 deletions man/filter.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

138 changes: 138 additions & 0 deletions man/when-any-all.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading