Skip to content

Commit c18cbd9

Browse files
committed
Implement vec_if_else()
1 parent 2ab2cf4 commit c18cbd9

File tree

17 files changed

+1859
-9
lines changed

17 files changed

+1859
-9
lines changed

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,7 @@ export(vec_group_id)
582582
export(vec_group_loc)
583583
export(vec_group_rle)
584584
export(vec_identify_runs)
585+
export(vec_if_else)
585586
export(vec_in)
586587
export(vec_init)
587588
export(vec_init_along)

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# vctrs (development version)
22

3+
* New `vec_if_else()` for performing a vectorized if-else. It is exactly the same as `dplyr::if_else()`, but much faster and memory efficient (#2030).
4+
35
* `vec_equal()` now efficiently internally recycles `x` and `y` elements of size 1 (#2028).
46

57
* `list_unchop()` now assigns names correctly when overlapping `indices` are involved (#2019).

R/if-else.R

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#' Vectorized if-else
2+
#'
3+
#' @description
4+
#' `vec_if_else()` is a vectorized [if-else][if]. Compared to the base R
5+
#' equivalent, [ifelse()], this function allows you to handle missing values in
6+
#' the `condition` with `missing` and always takes `true`, `false`, and
7+
#' `missing` into account when determining what the output type should be.
8+
#'
9+
#' @inheritParams rlang::args_dots_empty
10+
#' @inheritParams rlang::args_error_context
11+
#'
12+
#' @param condition A logical vector.
13+
#'
14+
#' @param true,false Vectors to use for `TRUE` and `FALSE` values of
15+
#' `condition`.
16+
#'
17+
#' Both `true` and `false` will be [recycled][theory-faq-recycling]
18+
#' to the size of `condition`.
19+
#'
20+
#' `true`, `false`, and `missing` (if used) will be cast to their common type.
21+
#'
22+
#' @param missing If not `NULL`, will be used as the value for `NA` values of
23+
#' `condition`. Follows the same size and type rules as `true` and `false`.
24+
#'
25+
#' @param ptype An optional prototype declaring the desired output type. If
26+
#' supplied, this overrides the common type of `true`, `false`, and `missing`.
27+
#'
28+
#' @param condition_arg,true_arg,false_arg,missing_arg Argument names used in
29+
#' error messages.
30+
#'
31+
#' @returns
32+
#' A vector with the same size as `condition` and the same type as the common
33+
#' type of `true`, `false`, and `missing`.
34+
#'
35+
#' Where `condition` is `TRUE`, the matching values from `true`, where it is
36+
#' `FALSE`, the matching values from `false`, and where it is `NA`, the matching
37+
#' values from `missing`, if provided, otherwise a missing value will be used.
38+
#'
39+
#' @export
40+
#' @examples
41+
#' x <- c(-5:5, NA)
42+
#' vec_if_else(x < 0, NA, x)
43+
#'
44+
#' # Explicitly handle `NA` values in the `condition` with `missing`
45+
#' vec_if_else(x < 0, "negative", "positive", missing = "missing")
46+
#'
47+
#' # Unlike `ifelse()`, `vec_if_else()` preserves types
48+
#' x <- factor(sample(letters[1:5], 10, replace = TRUE))
49+
#' ifelse(x %in% c("a", "b", "c"), x, NA)
50+
#' vec_if_else(x %in% c("a", "b", "c"), x, NA)
51+
#'
52+
#' # `vec_if_else()` also works with data frames
53+
#' condition <- c(TRUE, FALSE, NA, TRUE)
54+
#' true <- data_frame(x = 1:4, y = 5:8)
55+
#' false <- data_frame(x = 9:12, y = 13:16)
56+
#' vec_if_else(condition, true, false)
57+
vec_if_else <- function(
58+
condition,
59+
true,
60+
false,
61+
...,
62+
missing = NULL,
63+
ptype = NULL,
64+
condition_arg = "condition",
65+
true_arg = "true",
66+
false_arg = "false",
67+
missing_arg = "missing",
68+
error_call = current_env()
69+
) {
70+
check_dots_empty0(...)
71+
.Call(
72+
ffi_vec_if_else,
73+
condition,
74+
true,
75+
false,
76+
missing,
77+
ptype,
78+
environment()
79+
)
80+
}

_pkgdown.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ reference:
6464
- vec_assign
6565
- vec_fill_missing
6666

67+
- title: Recoding
68+
contents:
69+
- vec_if_else
70+
6771
- title: Equality and ordering
6872
contents:
6973
- vec_equal

man/vec_if_else.Rd

Lines changed: 79 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/bind.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ r_obj* vec_rbind(r_obj* xs,
7979
return new_data_frame(r_globals.empty_list, 0);
8080
}
8181
if (r_typeof(ptype) == R_TYPE_logical && !n_cols) {
82-
ptype = as_df_row_impl(vctrs_shared_na_lgl,
82+
ptype = as_df_row_impl(vctrs_shared_missing_lgl,
8383
name_repair,
8484
error_call);
8585
KEEP_N(ptype, &n_prot);

src/decl/if-else-decl.h

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
static
2+
r_obj* generic_if_else(
3+
r_obj* condition,
4+
r_obj* true_,
5+
r_obj* false_,
6+
r_obj* missing,
7+
r_obj* ptype,
8+
struct vctrs_arg* p_true_arg,
9+
struct vctrs_arg* p_false_arg,
10+
struct vctrs_arg* p_missing_arg,
11+
struct r_lazy error_call
12+
);
13+
14+
static
15+
r_obj* atomic_if_else(
16+
r_obj* condition,
17+
r_obj* true_,
18+
r_obj* false_,
19+
r_obj* missing,
20+
r_obj* ptype,
21+
struct vctrs_arg* p_true_arg,
22+
struct vctrs_arg* p_false_arg,
23+
struct vctrs_arg* p_missing_arg,
24+
struct r_lazy error_call,
25+
bool has_missing
26+
);
27+
28+
static
29+
r_obj* atomic_if_else_switch(
30+
enum r_type type,
31+
r_obj* condition,
32+
r_obj* true_,
33+
r_obj* false_,
34+
r_obj* missing,
35+
r_ssize size,
36+
r_ssize true_size,
37+
r_ssize false_size,
38+
r_ssize missing_size,
39+
r_obj* true_names,
40+
r_obj* false_names,
41+
r_obj* missing_names,
42+
bool has_missing,
43+
bool has_true_names,
44+
bool has_false_names,
45+
bool has_missing_names
46+
);
47+
48+
static
49+
bool ptype_is_atomic(r_obj* ptype);
50+
51+
static
52+
r_obj* ptype_finalize(
53+
r_obj* ptype,
54+
r_obj* true_,
55+
r_obj* false_,
56+
r_obj* missing,
57+
bool has_missing,
58+
struct vctrs_arg* p_true_arg,
59+
struct vctrs_arg* p_false_arg,
60+
struct vctrs_arg* p_missing_arg,
61+
struct r_lazy error_call
62+
);
63+
64+
static
65+
void check_logical(r_obj* x, struct vctrs_arg* p_x_arg, struct r_lazy error_call);

src/globals.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,22 @@ void vctrs_init_globals(r_obj* ns) {
4949

5050
// Symbols -----------------------------------------------------------
5151
syms.arg = r_sym("arg");
52+
syms.condition_arg = r_sym("condition_arg");
5253
syms.default_arg = r_sym("default_arg");
5354
syms.dot_arg = r_sym(".arg");
5455
syms.dot_call = r_sym(".call");
5556
syms.dot_error_arg = r_sym(".error_arg");
5657
syms.dot_error_call = r_sym(".error_call");
58+
syms.false_arg = r_sym("false_arg");
5759
syms.haystack_arg = r_sym("haystack_arg");
60+
syms.missing_arg = r_sym("missing_arg");
5861
syms.indices_arg = r_sym("indices_arg");
5962
syms.needles_arg = r_sym("needles_arg");
6063
syms.recurse = r_sym("recurse");
6164
syms.repair_arg = r_sym("repair_arg");
6265
syms.times_arg = r_sym("times_arg");
6366
syms.to_arg = r_sym("to_arg");
67+
syms.true_arg = r_sym("true_arg");
6468
syms.value_arg = r_sym("value_arg");
6569
syms.x_arg = r_sym("x_arg");
6670
syms.y_arg = r_sym("y_arg");

src/globals.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,22 @@
66

77
struct syms {
88
r_obj* arg;
9+
r_obj* condition_arg;
910
r_obj* default_arg;
1011
r_obj* dot_arg;
1112
r_obj* dot_call;
1213
r_obj* dot_error_arg;
1314
r_obj* dot_error_call;
15+
r_obj* false_arg;
1416
r_obj* haystack_arg;
17+
r_obj* missing_arg;
1518
r_obj* indices_arg;
1619
r_obj* needles_arg;
1720
r_obj* recurse;
1821
r_obj* repair_arg;
1922
r_obj* times_arg;
2023
r_obj* to_arg;
24+
r_obj* true_arg;
2125
r_obj* value_arg;
2226
r_obj* vec_default_cast;
2327
r_obj* vec_slice_dispatch_integer64;
@@ -93,8 +97,14 @@ extern r_obj* vctrs_shared_empty_date;
9397
extern r_obj* vctrs_shared_empty_uns;
9498

9599
extern Rcomplex vctrs_shared_na_cpl;
96-
extern r_obj* vctrs_shared_na_lgl;
97-
extern r_obj* vctrs_shared_na_list;
100+
101+
extern r_obj* vctrs_shared_missing_lgl;
102+
extern r_obj* vctrs_shared_missing_int;
103+
extern r_obj* vctrs_shared_missing_dbl;
104+
extern r_obj* vctrs_shared_missing_cpl;
105+
extern r_obj* vctrs_shared_missing_raw;
106+
extern r_obj* vctrs_shared_missing_chr;
107+
extern r_obj* vctrs_shared_missing_list;
98108

99109

100110
#endif

0 commit comments

Comments
 (0)