|
| 1 | +#' Recode and replace using logical conditions |
| 2 | +#' |
| 3 | +#' @description |
| 4 | +#' |
| 5 | +#' - `vec_case_when()` constructs an entirely new vector by recoding the `TRUE` |
| 6 | +#' `cases` to their corresponding `values`. If there are locations not matched |
| 7 | +#' by `cases`, then they are recoded to the `default` value. |
| 8 | +#' |
| 9 | +#' - `vec_replace_when()` updates an existing vector by replacing the values |
| 10 | +#' from `x` matched by the `TRUE` `cases` with their corresponding `values`. |
| 11 | +#' In this case, each element of `values` must have the same type as `x` and |
| 12 | +#' locations not matched by `cases` retain their original `x` value. |
| 13 | +#' |
| 14 | +#' `vec_case_when()` is often thought of as a way to vectorize multiple if-else |
| 15 | +#' statements, and is an R equivalent of the SQL "searched" `CASE WHEN` |
| 16 | +#' statement. |
| 17 | +#' |
| 18 | +#' @inheritParams rlang::args_dots_empty |
| 19 | +#' @inheritParams rlang::args_error_context |
| 20 | +#' |
| 21 | +#' @param x A vector. |
| 22 | +#' |
| 23 | +#' @param cases A list of logical vectors. |
| 24 | +#' |
| 25 | +#' For `vec_case_when()`, each vector should be the same size. |
| 26 | +#' |
| 27 | +#' For `vec_replace_when()`, each vector should be the same size as `x`. |
| 28 | +#' |
| 29 | +#' Where a value in `cases` is `TRUE`, the corresponding value in `values` |
| 30 | +#' will be assigned to the result. |
| 31 | +#' |
| 32 | +#' @param values A list of vectors. |
| 33 | +#' |
| 34 | +#' For `vec_case_when()`, each vector should be size 1 or the size implied by |
| 35 | +#' `cases`. The common type of `values` and `default` determine the output |
| 36 | +#' type, unless overridden by `ptype`. |
| 37 | +#' |
| 38 | +#' For `vec_replace_when()`, each vector should be size 1 or the same size |
| 39 | +#' as `x`. Each vector will be cast to the type of `x`. |
| 40 | +#' |
| 41 | +#' @param default Default value to use when `cases` does not match every |
| 42 | +#' location in the output. |
| 43 | +#' |
| 44 | +#' By default, a missing value is used as the default value. |
| 45 | +#' |
| 46 | +#' If supplied, `default` must be size 1 or the size implied by `cases`. |
| 47 | +#' |
| 48 | +#' Can only be set when `unmatched = "default"`. |
| 49 | +#' |
| 50 | +#' @param unmatched Handling of unmatched locations. |
| 51 | +#' |
| 52 | +#' One of: |
| 53 | +#' |
| 54 | +#' - `"default"` to use `default` in unmatched locations. |
| 55 | +#' |
| 56 | +#' - `"error"` to error when there are unmatched locations. |
| 57 | +#' |
| 58 | +#' @param ptype An optional override for the output type, which is usually |
| 59 | +#' computed as the common type of `values` and `default`. |
| 60 | +#' |
| 61 | +#' @param size An optional override for the output size, which is usually |
| 62 | +#' computed as the size of the first element of `cases`. |
| 63 | +#' |
| 64 | +#' Only useful for requiring a fixed size when `cases` is an empty list. |
| 65 | +#' |
| 66 | +#' @param x_arg,cases_arg,values_arg,default_arg Argument names used in error |
| 67 | +#' messages. |
| 68 | +#' |
| 69 | +#' @returns |
| 70 | +#' A vector. |
| 71 | +#' |
| 72 | +#' - For `vec_case_when()`, the type of the output is computed as the common |
| 73 | +#' type of `values` and `default`, unless overridden by `ptype`. The names of |
| 74 | +#' the output come from the names of `values` and `default`. The size of the |
| 75 | +#' output comes from the implied size from `cases`, unless overridden by |
| 76 | +#' `size`. |
| 77 | +#' |
| 78 | +#' - For `vec_replace_when()`, the type of the output will have the same type as |
| 79 | +#' `x`. The names of the output will be the same as the names of `x`. The size |
| 80 | +#' of the output will be the same size as `x`. |
| 81 | +#' |
| 82 | +#' @name vec-case-and-replace |
| 83 | +#' |
| 84 | +#' @examples |
| 85 | +#' # Note how the first `TRUE` is used in the output. |
| 86 | +#' # Also note how the `NA` falls through to `default`. |
| 87 | +#' x <- seq(-2L, 2L, by = 1L) |
| 88 | +#' x <- c(x, NA) |
| 89 | +#' cases <- list( |
| 90 | +#' x < 0, |
| 91 | +#' x < 1 |
| 92 | +#' ) |
| 93 | +#' values <- list( |
| 94 | +#' "<0", |
| 95 | +#' "<1" |
| 96 | +#' ) |
| 97 | +#' vec_case_when( |
| 98 | +#' cases, |
| 99 | +#' values, |
| 100 | +#' default = "other" |
| 101 | +#' ) |
| 102 | +#' |
| 103 | +#' # Missing values need to be handled with their own case |
| 104 | +#' # if you want them to have a special value |
| 105 | +#' cases <- list( |
| 106 | +#' x < 0, |
| 107 | +#' x < 1, |
| 108 | +#' is.na(x) |
| 109 | +#' ) |
| 110 | +#' values <- list( |
| 111 | +#' "<0", |
| 112 | +#' "<1", |
| 113 | +#' NA |
| 114 | +#' ) |
| 115 | +#' vec_case_when( |
| 116 | +#' cases, |
| 117 | +#' values, |
| 118 | +#' default = "other" |
| 119 | +#' ) |
| 120 | +#' |
| 121 | +#' # Both `values` and `default` are vectorized |
| 122 | +#' values <- list( |
| 123 | +#' x * 5, |
| 124 | +#' x * 10, |
| 125 | +#' NA |
| 126 | +#' ) |
| 127 | +#' vec_case_when( |
| 128 | +#' cases, |
| 129 | +#' values, |
| 130 | +#' default = x * 100 |
| 131 | +#' ) |
| 132 | +#' |
| 133 | +#' # Use `vec_replace_when()` if you need to update `x`, retaining |
| 134 | +#' # all previous values in locations that you don't match |
| 135 | +#' cases <- list( |
| 136 | +#' x < 0, |
| 137 | +#' x < 1 |
| 138 | +#' ) |
| 139 | +#' values <- list( |
| 140 | +#' 0, |
| 141 | +#' 1 |
| 142 | +#' ) |
| 143 | +#' out <- vec_replace_when( |
| 144 | +#' x, |
| 145 | +#' cases, |
| 146 | +#' values |
| 147 | +#' ) |
| 148 | +#' out |
| 149 | +#' |
| 150 | +#' # Note how `vec_replace_when()` is type stable on `x`, we retain the |
| 151 | +#' # integer type here even though `values` contained doubles |
| 152 | +#' typeof(out) |
| 153 | +#' |
| 154 | +#' # `vec_case_when()` creates a new vector, so names come from `values` |
| 155 | +#' # and `default`. `vec_replace_when()` modifies an existing vector, so |
| 156 | +#' # names come from `x` no matter what, just like `[<-` and `base::replace()` |
| 157 | +#' x <- c(a = 1, b = 2, c = 3) |
| 158 | +#' cases <- list(x == 1, x == 2) |
| 159 | +#' values <- list(c(x = 0), c(y = -1)) |
| 160 | +#' vec_case_when(cases, values) |
| 161 | +#' vec_replace_when(x, cases, values) |
| 162 | +#' |
| 163 | +#' # If you want to enforce that you've covered all of the locations in your |
| 164 | +#' # `cases`, use `unmatched = "error"` rather than providing a `default` |
| 165 | +#' x <- c(0, 1, 2) |
| 166 | +#' cases <- list(x == 1, x == 2) |
| 167 | +#' values <- list("a", "b") |
| 168 | +#' try(vec_case_when(cases, values, unmatched = "error")) |
| 169 | +NULL |
| 170 | + |
| 171 | +#' @rdname vec-case-and-replace |
| 172 | +#' @export |
| 173 | +vec_case_when <- function( |
| 174 | + cases, |
| 175 | + values, |
| 176 | + ..., |
| 177 | + default = NULL, |
| 178 | + unmatched = "default", |
| 179 | + ptype = NULL, |
| 180 | + size = NULL, |
| 181 | + cases_arg = "cases", |
| 182 | + values_arg = "values", |
| 183 | + default_arg = "default", |
| 184 | + error_call = current_env() |
| 185 | +) { |
| 186 | + check_dots_empty0(...) |
| 187 | + .Call( |
| 188 | + ffi_vec_case_when, |
| 189 | + cases, |
| 190 | + values, |
| 191 | + default, |
| 192 | + unmatched, |
| 193 | + ptype, |
| 194 | + size, |
| 195 | + environment() |
| 196 | + ) |
| 197 | +} |
| 198 | + |
| 199 | +#' @rdname vec-case-and-replace |
| 200 | +#' @export |
| 201 | +vec_replace_when <- function( |
| 202 | + x, |
| 203 | + cases, |
| 204 | + values, |
| 205 | + ..., |
| 206 | + x_arg = "x", |
| 207 | + cases_arg = "cases", |
| 208 | + values_arg = "values", |
| 209 | + error_call = current_env() |
| 210 | +) { |
| 211 | + check_dots_empty0(...) |
| 212 | + .Call( |
| 213 | + ffi_vec_replace_when, |
| 214 | + x, |
| 215 | + cases, |
| 216 | + values, |
| 217 | + environment() |
| 218 | + ) |
| 219 | +} |
0 commit comments