ksCompare adds several conveniences on top of plain
PROC COMPARE-style diffing.
ULP tolerance
ks_tol(ulp = N) accepts pairs whose IEEE-754 binary64
representations are within N units in the last place.
x <- 1.0
y <- x + .Machine$double.eps
ks_compare(
data.frame(id = 1, x = x),
data.frame(id = 1, x = y),
by = "id",
tolerance = ks_tol(ulp = 4)
)
#> ✔ data.frame(id = 1, x = x) vs data.frame(id = 1, x = y)
#> — identicalAuto-key inference
df <- data.frame(study = "S1", id = 1:3, x = 1:3)
suppressMessages(ks_compare(df, df, by = "auto"))by = "auto" searches for the smallest combination of
shared columns whose values are unique on both sides.
Diff triage helpers
ks_cause_summary() groups value diffs by likely cause,
while ks_row_diff_summary() shows which matched
observations changed the most.
a <- data.frame(id = 1:4, x = c("A", "b ", "c", "d"), y = c(1, 2, 3, 4))
b <- data.frame(id = 1:4, x = c("a", "b", "c", "d"), y = c(1, 9, 3, 9))
cmp <- ks_compare(a, b, by = "id")
#> ✖ a vs b — 4 value diffs across 2 columns
ks_cause_summary(cmp)
#> # A tibble: 3 × 4
#> cause n_cells n_columns columns
#> <chr> <int> <int> <chr>
#> 1 plain value change 2 1 y
#> 2 letter case differs 1 1 x
#> 3 whitespace padding on base 1 1 x
ks_row_diff_summary(cmp)
#> # A tibble: 3 × 6
#> key_id base_row comp_row key_label n_diffs columns
#> <int> <int> <int> <chr> <int> <chr>
#> 1 2 2 2 id = 2 2 x, y
#> 2 1 1 1 id = 1 1 x
#> 3 4 4 4 id = 4 1 yFirst / last unequal observations
For SAS-style review tables, ks_compare() also stores
the first and last differing observations per matched column.
ks_compare(a, b, by = "id", n_first_last = 1)$first_last_unequal
#> ✖ a vs b — 4 value diffs across 2 columns
#> # A tibble: 4 × 13
#> column_base column_comp position rank key_id base_row comp_row kind base
#> <chr> <chr> <chr> <int> <int> <int> <int> <chr> <chr>
#> 1 x x first 1 1 1 1 charact… "A"
#> 2 x x last 1 2 2 2 charact… "b "
#> 3 y y first 1 2 2 2 double "2"
#> 4 y y last 1 4 4 4 double "4"
#> # ℹ 4 more variables: comp <chr>, diff <dbl>, na_flow <chr>, note <chr>Duplicate-key strategies
a <- data.frame(id = c(1, 1, 2), x = c(10, 99, 20))
b <- data.frame(id = c(1, 2), x = c(10, 20))
ks_compare(a, b, by = "id", dup_keys = "first")
#> ✔ a vs b — identical
ks_compare(a, b, by = "id", dup_keys = "last")
#> ⚠ a vs b — 1 value diff across 1 column
ks_compare(a, b, by = "id", dup_keys = "all_pairs")
#> Warning in ks_match_rows_dup(bkey, ckey, dup_keys, uniq_check): `dup_keys` = "all_pairs": cardinality mismatch on 1 key.
#> ℹ Cartesian pairing inflates row counts; consider "keep_all" for positional
#> within-group pairing.
#> ⚠ a vs b — 1 value diff across 1 columndup_keys = "keep_all" pairs duplicate rows positionally
within each key group (1<->1, 2<->2, …). Leftover rows on
the longer side become base- or compare-only, and
cmp$row_diff carries a pair_rank /
pair_total column so you can tell which pair each diff
belongs to. The HTML report shows a Pair column when this
strategy is active.
SAS format normalisation
Format comparison is trailing-dot- and case-tolerant, so
haven import quirks do not surface as false schema
diffs:
a <- data.frame(d = as.Date("2024-01-01"))
attr(a$d, "format.sas") <- "DATE9."
b <- a
attr(b$d, "format.sas") <- "date9" # no trailing dot, lowercase
ks_compare(a, b)$schema_diff # format_match == TRUEEncoding-safe string comparison
Strings imported from mixed Latin1 / UTF-8 sources are converted to
UTF-8 before equality testing, so the same character in two encodings
does not register as a diff. Combine with str_norm = "NFC"
for fully Unicode-normalised comparison.
ks_compare(
base, comp, by = "id",
options = ks_comp_options(str_norm = "NFC")
)Pattern detection
Pattern detection is opt-in because the detectors scan every column
with at least one diff. When many cells differ in the same way (constant
offset, sign flip, trim-only string differences, etc.), set
find_patterns = TRUE to populate
cmp$pattern_summary:
a <- data.frame(id = 1:5, x = c(1, 2, 3, 4, 5))
b <- data.frame(id = 1:5, x = c(2, 3, 4, 5, 6))
ks_compare(a, b, by = "id", find_patterns = TRUE)$pattern_summary
#> ✖ a vs b — 5 value diffs across 1 column
#> # A tibble: 1 × 4
#> column pattern coverage detail
#> <chr> <chr> <dbl> <chr>
#> 1 x constant_offset 1 -1Common labels include constant_offset,
constant_scale, sign_flip,
integer_round, trim_only,
case_only, epoch_swap, and
factor_recoded, depending on the column type.
