1
1
# ' Test two vctrs vectors for equality with some tolerance in some cases
2
2
# '
3
- # ' @param vec1,vec2 vctrs vectors (includes data frames)
4
- # ' @param abs_tol tolerance; will be used for bare numeric `vec1`, `vec2`, or
5
- # ' any such columns within `vec1`, `vec2` if they are data frames
3
+ # ' Similar to [`vctrs::vec_equal`]. Behavior may differ from `vec_equal` with
4
+ # ' non-`NA` `NaN`s involved, or for bare lists that contain named vectors, and
5
+ # ' the precise behavior in these cases may change and should not be relied upon.
6
+ # '
7
+ # ' @param vec1,vec2 vctrs vectors (includes data frames). Take care when using
8
+ # ' on named vectors or "keyed" data frames; [`vec_names()`] are largely
9
+ # ' ignored, and key columns are treated as normal value columns (when they
10
+ # ' should probably generate an error if they are not lined up correctly, or be
11
+ # ' tested for exact rather than approximate equality).
6
12
# ' @param na_equal should `NA`s be considered equal to each other? (In
7
13
# ' epiprocess, we usually want this to be `TRUE`, but that doesn't match the
8
14
# ' [`vctrs::vec_equal()`] default, so this is mandatory.)
9
- # ' @param .ptype as in [`vctrs::vec_equal()`]
10
- # ' @param inds1,inds2 optional (row) indices into vec1 and vec2; output should
11
- # ' be consistent with `vec_slice`-ing to these indices beforehand, but can
12
- # ' give faster computation if `vec1` and `vec2` are data frames.
15
+ # ' @param abs_tol absolute tolerance; will be used for bare numeric `vec1`,
16
+ # ' `vec2`, or any such columns within `vec1`, `vec2` if they are data frames.
17
+ # ' @param .ptype as in [`vctrs::vec_equal()`].
18
+ # ' @param inds1,inds2 optional (row) indices into vec1 and vec2 compatible with
19
+ # ' [`vctrs::vec_slice()`]; output should be consistent with `vec_slice`-ing to
20
+ # ' these indices beforehand, but can give faster computation if `vec1` and
21
+ # ' `vec2` are data frames.
13
22
# '
14
23
# ' @return logical vector, with length matching the result of recycling `vec1`
15
24
# ' (at `inds1` if provided) and `vec2` (at `inds2` if provided); entries
16
- # ' should all be `TRUE` or `FALSE` if `na_equal = TRUE`. Behavior may differ
17
- # ' from `vec_equal` with non-`NA` `NaN`s involved, or for bare lists that
18
- # ' contain named vectors.
19
- approx_equal <- function (vec1 , vec2 , abs_tol , na_equal , .ptype = NULL , inds1 = NULL , inds2 = NULL ) {
25
+ # ' should all be `TRUE` or `FALSE` if `na_equal = TRUE`.
26
+ # '
27
+ # ' @examples
28
+ # '
29
+ # ' # On numeric vectors:
30
+ # ' approx_equal(
31
+ # ' c(1, 2, 3, NA),
32
+ # ' c(1, 2 + 1e-10, NA, NA),
33
+ # ' na_equal = TRUE,
34
+ # ' abs_tol = 1e-8
35
+ # ' )
36
+ # '
37
+ # ' # On tibbles:
38
+ # ' tbl1 <- tibble(
39
+ # ' a = 1:5,
40
+ # ' b = list(1:5, 1:4, 1:3, 1:2, 1:1) %>% lapply(as.numeric),
41
+ # ' c = tibble(
42
+ # ' c1 = 1:5
43
+ # ' ),
44
+ # ' d = matrix(1:10, 5, 2)
45
+ # ' )
46
+ # ' tbl2 <- tbl1
47
+ # ' tbl2$a[[2]] <- tbl1$a[[2]] + 1e-10
48
+ # ' tbl2$b[[3]][[1]] <- tbl1$b[[3]][[1]] + 1e-10
49
+ # ' tbl2$c$c1[[4]] <- tbl1$c$c1[[4]] + 1e-10
50
+ # ' tbl2$d[[5, 2]] <- tbl1$d[[5, 2]] + 1e-10
51
+ # ' vctrs::vec_equal(tbl1, tbl2, na_equal = TRUE)
52
+ # ' approx_equal(tbl1, tbl2, na_equal = TRUE, abs_tol = 1e-12)
53
+ # ' approx_equal(tbl1, tbl2, na_equal = TRUE, abs_tol = 1e-8)
54
+ # '
55
+ # '
56
+ # '
57
+ # '
58
+ # '
59
+ # ' # Type comparison within lists is stricter, matching vctrs:
60
+ # ' vctrs::vec_equal(list(1:2), list(as.numeric(1:2)))
61
+ # ' approx_equal(list(1:2), list(as.numeric(1:2)), FALSE, abs_tol = 0)
62
+ # '
63
+ # ' @export
64
+ approx_equal <- function (vec1 , vec2 , na_equal , .ptype = NULL , ... , abs_tol , inds1 = NULL , inds2 = NULL ) {
65
+ if (! obj_is_vector(vec1 )) cli_abort(" `vec1` must be recognized by vctrs as a vector" )
66
+ if (! obj_is_vector(vec2 )) cli_abort(" `vec2` must be recognized by vctrs as a vector" )
67
+ # Leave vec size checking to vctrs recycling ops.
68
+ assert_logical(na_equal , any.missing = FALSE , len = 1L )
69
+ # Leave .ptype checks to cast operation.
70
+ check_dots_empty()
71
+ assert_numeric(abs_tol , lower = 0 , len = 1L )
72
+ assert(
73
+ check_null(inds1 ),
74
+ check_numeric(inds1 ),
75
+ check_logical(inds1 ),
76
+ check_character(inds1 )
77
+ )
78
+ assert(
79
+ check_null(inds2 ),
80
+ check_numeric(inds2 ),
81
+ check_logical(inds2 ),
82
+ check_character(inds2 )
83
+ )
84
+ # Leave heavier index validation to the vctrs recycling & indexing ops.
85
+
20
86
# Recycle inds if provided; vecs if not:
21
87
common_size <- vec_size_common(
22
88
if (is.null(inds1 )) vec1 else inds1 ,
@@ -33,13 +99,13 @@ approx_equal <- function(vec1, vec2, abs_tol, na_equal, .ptype = NULL, inds1 = N
33
99
inds2 <- vec_recycle(inds2 , common_size )
34
100
}
35
101
vecs <- vec_cast_common(vec1 , vec2 , .to = .ptype )
36
- approx_equal0(vecs [[1 ]], vecs [[2 ]], abs_tol , na_equal , inds1 , inds2 )
102
+ approx_equal0(vecs [[1 ]], vecs [[2 ]], na_equal , abs_tol , inds1 , inds2 )
37
103
}
38
104
39
105
# ' Helper for [`approx_equal`] for vecs guaranteed to have the same ptype and size
40
106
# '
41
107
# ' @keywords internal
42
- approx_equal0 <- function (vec1 , vec2 , abs_tol , na_equal , inds1 = NULL , inds2 = NULL ) {
108
+ approx_equal0 <- function (vec1 , vec2 , na_equal , abs_tol , inds1 = NULL , inds2 = NULL ) {
43
109
if (is_bare_numeric(vec1 ) && abs_tol != 0 ) {
44
110
# perf: since we're working with bare numerics and logicals: we can use `[`
45
111
# and `fifelse`. Matching vec_equal, we ignore names and other attributes.
@@ -66,7 +132,7 @@ approx_equal0 <- function(vec1, vec2, abs_tol, na_equal, inds1 = NULL, inds2 = N
66
132
rep(TRUE , nrow(vec1 ))
67
133
} else {
68
134
Reduce(`&` , lapply(seq_len(ncol(vec1 )), function (col_i ) {
69
- approx_equal0(vec1 [[col_i ]], vec2 [[col_i ]], abs_tol , na_equal , inds1 , inds2 )
135
+ approx_equal0(vec1 [[col_i ]], vec2 [[col_i ]], na_equal , abs_tol , inds1 , inds2 )
70
136
}))
71
137
}
72
138
} else if (is_bare_list(vec1 )) {
@@ -78,7 +144,7 @@ approx_equal0 <- function(vec1, vec2, abs_tol, na_equal, inds1 = NULL, inds2 = N
78
144
# consistently inconsistent, we avoid dispatching to vec_equal for bare
79
145
# lists even with abs_tol = 0:
80
146
identical(vec_ptype(entry1 ), vec_ptype(entry2 )) &&
81
- all(approx_equal0(entry1 , entry2 , abs_tol , na_equal ))
147
+ all(approx_equal0(entry1 , entry2 , na_equal , abs_tol ))
82
148
}, logical (1L ))
83
149
} else {
84
150
# XXX No special handling for any other types/situations. Makes sense for
@@ -201,8 +267,8 @@ tbl_diff2 <- function(earlier_snapshot, later_tbl,
201
267
combined_compactify_away [combined_ukey_is_repeat ] <-
202
268
approx_equal0(combined_vals ,
203
269
combined_vals ,
204
- abs_tol = compactify_abs_tol ,
205
270
na_equal = TRUE ,
271
+ abs_tol = compactify_abs_tol ,
206
272
inds1 = combined_ukey_is_repeat ,
207
273
inds2 = ukey_repeat_first_i
208
274
)
0 commit comments