| Title: | Smart Data Frame Comparison in the Spirit of SAS PROC COMPARE |
|---|---|
| Description: | A tidyverse-native engine for comparing two data frames in the spirit of SAS 'PROC COMPARE', with extensions for clinical/pharma workflows: per-column tolerances (absolute, relative, and ULP), smart column matching, intelligent type reconciliation, SAS special-missing awareness, diff-pattern detection, and rich HTML/Excel reports. |
| Authors: | Igor Aleschenkov [aut, cre] |
| Maintainer: | Igor Aleschenkov <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.2.3 |
| Built: | 2026-07-06 14:52:43 UTC |
| Source: | https://github.com/al-garik/ksCompare |
arsenal::comparedf result into a ks_comparison
Lossy interop: takes the two source frames stored on a
arsenal::comparedf object and re-runs ks_compare() using the same
BY keys. Useful for teams migrating from arsenal::comparedf() to
ksCompare without rewriting their inputs.
as_ks_comparison(x, ...)as_ks_comparison(x, ...)
x |
An |
... |
Additional arguments forwarded to |
Note that ksCompare's diff is computed from scratch — it does not
attempt to translate comparedf's internal diff tables row-by-row.
A ks_comparison object.
Treats a ks_comparison as a pass/fail check. By default a
"passing" comparison has zero schema, key, and value differences;
callers can relax those expectations to allow a budget of known
acceptable diffs.
ks_assert_clean( x, max_value_diffs = 0L, max_schema_diffs = 0L, max_unmatched_rows = 0L ) ks_pointblank_step( agent, comparison, max_value_diffs = 0L, max_schema_diffs = 0L, max_unmatched_rows = 0L, label = "ksCompare clean" )ks_assert_clean( x, max_value_diffs = 0L, max_schema_diffs = 0L, max_unmatched_rows = 0L ) ks_pointblank_step( agent, comparison, max_value_diffs = 0L, max_schema_diffs = 0L, max_unmatched_rows = 0L, label = "ksCompare clean" )
x |
A |
max_value_diffs |
Integer. Maximum number of differing cells
allowed in matched rows. Default |
max_schema_diffs |
Integer. Maximum schema differences allowed,
counted as the sum of base-only columns, comp-only columns, and
matched columns whose |
max_unmatched_rows |
Integer. Maximum number of unmatched rows
allowed ( |
agent |
A |
comparison |
A |
label |
Optional label for the pointblank step. |
Two flavours are provided:
ks_assert_clean() throws a classed error
(ksCompare_assertion_failed) and halts a pipeline when
expectations are unmet. Returns the comparison invisibly on
success, so it can be dropped into a |> chain.
ks_pointblank_step() returns a pointblank step that wraps
ks_assert_clean() for use inside pointblank::create_agent() /
pointblank::action_levels() flows.
ks_assert_clean() returns x invisibly when expectations
are met. On failure, raises a condition of class
ksCompare_assertion_failed (catchable in CI with
tryCatch(..., ksCompare_assertion_failed = function(e) ...)).
ks_pointblank_step() returns a pointblank step.
a <- data.frame(id = 1:3, x = 1:3) ks_compare(a, a, by = "id") |> ks_assert_clean() # Allow a small budget of known diffs ## Not run: ks_compare(a, b, by = "id") |> ks_assert_clean(max_value_diffs = 5) ## End(Not run)a <- data.frame(id = 1:3, x = 1:3) ks_compare(a, a, by = "id") |> ks_assert_clean() # Allow a small budget of known diffs ## Not run: ks_compare(a, b, by = "id") |> ks_assert_clean(max_value_diffs = 5) ## End(Not run)
Aggregates the note column of cmp$value_diff into a tidy
taxonomy: one row per distinct cause, with counts and the columns
it affects. Helps answer "what kinds of discrepancies do we have?"
before drilling into individual cells.
ks_cause_summary(x)ks_cause_summary(x)
x |
A |
Compound notes (multiple cues joined by "; ") are split into
their elementary cues, so a single cell can contribute to several
causes. Cells with note = NA (a "plain" value change with no
recognised cue) are bucketed as "plain value change".
A tibble with columns:
cause: short label (e.g. "letter case differs").
n_cells: number of cells contributing this cue.
n_columns: number of distinct base columns affected.
columns: comma-separated sample of the affected columns
(capped at 5 names).
Sorted by n_cells descending. Zero-row tibble when there are no
value differences.
ks_compare(), ks_row_diff_summary().
a <- data.frame(id = 1:3, x = c("a", "b ", "c"), y = c(1, 2, 3)) b <- data.frame(id = 1:3, x = c("A", "b", "c"), y = c(1, 2, 4)) cmp <- ks_compare(a, b, by = "id") ks_cause_summary(cmp)a <- data.frame(id = 1:3, x = c("a", "b ", "c"), y = c(1, 2, 3)) b <- data.frame(id = 1:3, x = c("A", "b", "c"), y = c(1, 2, 4)) cmp <- ks_compare(a, b, by = "id") ks_cause_summary(cmp)
Builds an options object consumed by ks_compare() that controls
missing-value semantics, label / format comparison, string
normalisation, and time-zone handling.
ks_comp_options( na_equal = getOption("ksCompare.na_equal", TRUE), sas_special_missing = getOption("ksCompare.sas_special_missing", TRUE), compare_labels = getOption("ksCompare.compare_labels", TRUE), compare_formats = getOption("ksCompare.compare_formats", TRUE), str_trim = getOption("ksCompare.str_trim", FALSE), str_case = getOption("ksCompare.str_case", "sensitive"), str_norm = getOption("ksCompare.str_norm", "none"), tz = getOption("ksCompare.tz", "preserve"), path = getOption("ksCompare.path", NULL) )ks_comp_options( na_equal = getOption("ksCompare.na_equal", TRUE), sas_special_missing = getOption("ksCompare.sas_special_missing", TRUE), compare_labels = getOption("ksCompare.compare_labels", TRUE), compare_formats = getOption("ksCompare.compare_formats", TRUE), str_trim = getOption("ksCompare.str_trim", FALSE), str_case = getOption("ksCompare.str_case", "sensitive"), str_norm = getOption("ksCompare.str_norm", "none"), tz = getOption("ksCompare.tz", "preserve"), path = getOption("ksCompare.path", NULL) )
na_equal |
Treat two |
sas_special_missing |
Distinguish SAS special missings ( |
compare_labels |
Compare |
compare_formats |
Compare |
str_trim |
Trim leading/trailing whitespace with
|
str_case |
One of |
str_norm |
One of |
tz |
One of
|
path |
Optional output folder used by downstream writers
( |
A ks_comp_options S3 list.
options()
Each argument falls back to a global R option in the ksCompare.*
namespace, then to the package default. Precedence is:
explicit argument > getOption("ksCompare.<arg>") > built-in default.
That makes it possible to set project-wide defaults once and then
call ks_compare() without repeating yourself:
# In .Rprofile or at the top of a script
options(ksCompare.path = "out/qc",
ksCompare.str_case = "fold")
ks_compare(base, comp) |> ks_report_html() # honours the globals
ks_set_comp_options() is a small wrapper around base::options()
that takes the same argument names without the ksCompare. prefix.
Recognised options: ksCompare.na_equal, ksCompare.sas_special_missing,
ksCompare.compare_labels, ksCompare.compare_formats,
ksCompare.str_trim, ksCompare.str_case, ksCompare.str_norm,
ksCompare.tz, ksCompare.path.
ks_set_comp_options() for setting the ksCompare.*
globals from R code.
# Defaults: strict NAs, label & format comparison on ks_comp_options() # Loose strings: trim padding and ignore case ks_comp_options(str_trim = TRUE, str_case = "fold") # Suppress label/format drift, but keep cell-level strictness ks_comp_options(compare_labels = FALSE, compare_formats = FALSE) # Treat any NA as a difference (PROC COMPARE without NOMISS) ks_comp_options(na_equal = FALSE) # Pin every report from this run to a dedicated folder ks_comp_options(path = tempfile("ksCompare_")) # Pick up a project-wide global path withr::with_options( list(ksCompare.path = tempfile("ksCompare_")), ks_comp_options()$path )# Defaults: strict NAs, label & format comparison on ks_comp_options() # Loose strings: trim padding and ignore case ks_comp_options(str_trim = TRUE, str_case = "fold") # Suppress label/format drift, but keep cell-level strictness ks_comp_options(compare_labels = FALSE, compare_formats = FALSE) # Treat any NA as a difference (PROC COMPARE without NOMISS) ks_comp_options(na_equal = FALSE) # Pin every report from this run to a dedicated folder ks_comp_options(path = tempfile("ksCompare_")) # Pick up a project-wide global path withr::with_options( list(ksCompare.path = tempfile("ksCompare_")), ks_comp_options()$path )
ks_compare() is the single entry point for the package. It compares
base against comp and returns a ks_comparison S3 object containing
schema, key, row, and value differences plus a small QC manifest.
ks_compare( base, comp, by = NULL, mapping = NULL, tolerance = ks_tol(), coerce = c("safe", "strict", "lossy"), dup_keys = c("first", "last", "keep_all", "all_pairs", "error"), options = ks_comp_options(), base_name = NULL, comp_name = NULL, find_patterns = FALSE, n_first_last = 5L, max_unmatched_rows = 100L, loglevel = c("verdict", "verbose") )ks_compare( base, comp, by = NULL, mapping = NULL, tolerance = ks_tol(), coerce = c("safe", "strict", "lossy"), dup_keys = c("first", "last", "keep_all", "all_pairs", "error"), options = ks_comp_options(), base_name = NULL, comp_name = NULL, find_patterns = FALSE, n_first_last = 5L, max_unmatched_rows = 100L, loglevel = c("verdict", "verbose") )
base, comp
|
Either an in-memory data frame (or anything coercible
via
|
by |
Optional column name(s) to use as the matching key.
|
mapping |
Optional explicit column mapping for renamed columns.
Either a fully named character vector
( |
tolerance |
A |
coerce |
Type-coercion strictness. One of:
|
dup_keys |
Strategy for duplicate keys (per-side independently). One of:
|
options |
A |
base_name, comp_name
|
Optional display names for the two frames
(used in |
find_patterns |
Logical (default |
n_first_last |
Non-negative integer (default |
max_unmatched_rows |
Non-negative integer (default |
loglevel |
Controls what
|
A ks_comparison object with components:
meta: counts, keys, matching strategy, row-key lookup table.
schema_diff: one row per matched / unmatched column with kind
/ label / format comparison.
key_diff: counts of matched / base-only / comp-only rows.
row_diff: long table of key_id, base_row, comp_row,
status.
value_diff: long table of differing cells with column_base,
column_comp, kind, base, comp, diff, note.
pattern_summary: detected recurring shapes per column (only
populated when find_patterns = TRUE; otherwise an empty
tibble with the standard column layout).
unmatched_rows: full base-only / comp-only rows (capped by
max_unmatched_rows); see ks_unmatched_rows().
first_last_unequal: per-column first / last n_first_last
differing observations in key_id order (PROC COMPARE-style).
verdict: a list with headline (character), severity
("ok", "info", "warn", or "critical"), pct_match
(0–100 numeric), n_cells, and n_diffs; the same summary
shown by print().
options, tolerance: the inputs (echoed for the manifest).
manifest: input hashes + run metadata.
Inspect with print(), summary(), tibble::as_tibble(), or
render via ks_report_html() / ks_report_xlsx().
Schema alignment — columns are paired by an explicit mapping
first, then by exact name match. Unmatched columns become
base_only / comp_only and are reported in cmp$schema_diff.
Key resolution — by is taken literally if supplied,
auto-inferred from shared columns when by = "auto", or rows are
matched by row position when by = NULL.
Row matching — keyed-unique join, duplicate-key resolution
(dup_keys), or position-zip; details land in
cmp$meta$matching (also displayed by print() and in the HTML
report).
Cell diff — per-column equality respecting ks_tol() (abs /
rel / ULP) for numerics, ks_comp_options() for strings / NAs / SAS
special missings, and vctrs::vec_cast() for compatible types.
Pattern detection — recurring shapes across diffs (constant
offset, sign flip, trim-only, …) are scored and surfaced on
cmp$pattern_summary.
Manifest — xxhash64 digests of inputs / options / tolerance
plus a timestamp and package version, for QC traceability.
a <- data.frame(id = 1:3, x = c(1, 2, 3), y = c("a", "b", "c")) b <- data.frame(id = 1:3, x = c(1, 2, 4), y = c("a", "B", "c")) # Strict compare on `id` cmp <- ks_compare(a, b, by = "id") cmp tibble::as_tibble(cmp) # Tolerant numeric compare ks_compare( data.frame(id = 1, x = 1.0), data.frame(id = 1, x = 1.0001), by = "id", tolerance = ks_tol(abs = 1e-3) ) # Renamed key column ks_compare( data.frame(USUBJID = "S001", x = 1), data.frame(SUBJID = "S001", x = 1), by = c(USUBJID = "SUBJID") )a <- data.frame(id = 1:3, x = c(1, 2, 3), y = c("a", "b", "c")) b <- data.frame(id = 1:3, x = c(1, 2, 4), y = c("a", "B", "c")) # Strict compare on `id` cmp <- ks_compare(a, b, by = "id") cmp tibble::as_tibble(cmp) # Tolerant numeric compare ks_compare( data.frame(id = 1, x = 1.0), data.frame(id = 1, x = 1.0001), by = "id", tolerance = ks_tol(abs = 1e-3) ) # Renamed key column ks_compare( data.frame(USUBJID = "S001", x = 1), data.frame(SUBJID = "S001", x = 1), by = c(USUBJID = "SUBJID") )
Returns a one-row tibble with the same headline counts as
summary.ks_comparison(). Convenient for binding many comparisons
together in a QC loop.
ks_glance(x, ...)ks_glance(x, ...)
x |
A |
... |
Unused. |
A one-row tibble with columns n_base_rows, n_comp_rows,
n_matched_rows, n_base_only_rows, n_comp_only_rows,
n_matched_columns, n_value_diffs, n_columns_with_diffs.
cmp <- ks_compare( data.frame(id = 1:2, x = c(1, 2)), data.frame(id = 1:2, x = c(1, 3)), by = "id" ) ks_glance(cmp)cmp <- ks_compare( data.frame(id = 1:2, x = c(1, 2)), data.frame(id = 1:2, x = c(1, 3)), by = "id" ) ks_glance(cmp)
Computes a small set of metrics that flag suspicious matching: high diff density, many fully-different rows, duplicate keys resolved positionally, and similar patterns that often indicate the wrong key was used.
ks_match_health(x)ks_match_health(x)
x |
A |
A list with components:
diff_density: n_value_diffs / (n_matched_rows * n_matched_columns) (0-1).
n_fully_diff_rows: matched rows where every matched column
differs (or, more permissively, >= 80% of columns differ).
pct_fully_diff_rows: as proportion of matched rows.
n_value_to_na, n_na_to_value: structural NA transitions.
dup_keys: TRUE if either side had duplicate key values.
dup_positional: TRUE if duplicates were paired positionally
(keep_all) — the most error-prone path.
row_count_delta: n_comp_rows - n_base_rows (signed).
flags: character vector of human-readable warnings (possibly
empty).
severity: "ok", "info", "warn", or "critical".
ks_recommendations(), ks_compare().
a <- data.frame(id = c(1, 1, 2), x = c(1, 2, 3)) b <- data.frame(id = c(1, 1, 2), x = c(9, 9, 3)) cmp <- ks_compare(a, b, by = "id", dup_keys = "keep_all") ks_match_health(cmp)a <- data.frame(id = c(1, 1, 2), x = c(1, 2, 3)) b <- data.frame(id = c(1, 1, 2), x = c(9, 9, 3)) cmp <- ks_compare(a, b, by = "id", dup_keys = "keep_all") ks_match_health(cmp)
Synthesises ks_match_health() flags, ks_suggest_key() hints,
and a few other heuristics into a short list of user-facing
"what to look at first" recommendations.
ks_recommendations(x)ks_recommendations(x)
x |
A |
A tibble with one row per recommendation:
severity ("critical", "warn", "info", "ok")
title short label
message longer human-readable text
action suggested next step (may be NA)
ks_match_health(), ks_suggest_key().
Produces a polished, single-file HTML report summarising a
ks_comparison. The output uses htmltools for layout and reactable
for interactive tables. No internet access, no Quarto, and no Pandoc
are required.
ks_report_html( x, path = NULL, title = "ksCompare report", subtitle = NULL, max_rows = 100L, theme = c("default", "slate"), group_by_key = FALSE, max_groups = 200L )ks_report_html( x, path = NULL, title = "ksCompare report", subtitle = NULL, max_rows = 100L, theme = c("default", "slate"), group_by_key = FALSE, max_groups = 200L )
x |
A |
path |
File path for the report. Three behaviours:
|
title |
Title shown at the top of the report. |
subtitle |
Optional subtitle (e.g. study identifier or run id). |
max_rows |
Per-table row cap (default |
theme |
One of |
group_by_key |
Logical (default |
max_groups |
Maximum number of key-value blocks to render when
|
Layout: a left-hand sticky table of contents, a header with dataset
names, a status pill, KPI cards, then sections for schema, row diff,
value diff, patterns, and the run manifest. Tables use
friendly column labels, column groups, and tick/cross markers
instead of raw TRUE/FALSE.
Invisibly returns path (or the assembled tag list when
path = NULL).
## Not run: cmp <- ks_compare(iris, iris, by = "Species") # Explicit path ks_report_html(cmp, "report.html", title = "ADSL QC") # Auto-generated filename in working directory ks_report_html(cmp) # In-memory tagList for embedding tags <- ks_report_html(cmp, path = NA) ## End(Not run)## Not run: cmp <- ks_compare(iris, iris, by = "Species") # Explicit path ks_report_html(cmp, "report.html", title = "ADSL QC") # Auto-generated filename in working directory ks_report_html(cmp) # In-memory tagList for embedding tags <- ks_report_html(cmp, path = NA) ## End(Not run)
Writes a multi-sheet workbook summarising a ks_comparison. Sheets:
Summary, Schema, KeyDiff, Values, Patterns,
OUT_BASE, OUT_COMP, OUT_DIF, OUT_NOEQUAL, Manifest. Cells with
value differences are highlighted on the wide OUT_DIF sheet.
ks_report_xlsx(x, path = NULL, highlight = TRUE, threshold = 0)ks_report_xlsx(x, path = NULL, highlight = TRUE, threshold = 0)
x |
A |
path |
File path for the workbook. When |
highlight |
If |
threshold |
Numeric threshold for highlighting (default |
Invisibly returns path.
## Not run: cmp <- ks_compare(iris, iris, by = "Species") ks_report_xlsx(cmp, "report.xlsx") # Auto-named output in a pinned folder cmp2 <- ks_compare( iris, iris, by = "Species", options = ks_comp_options(path = tempfile("ksCompare_")) ) ks_report_xlsx(cmp2) ## End(Not run)## Not run: cmp <- ks_compare(iris, iris, by = "Species") ks_report_xlsx(cmp, "report.xlsx") # Auto-named output in a pinned folder cmp2 <- ks_compare( iris, iris, by = "Species", options = ks_comp_options(path = tempfile("ksCompare_")) ) ks_report_xlsx(cmp2) ## End(Not run)
Tallies the number of cell differences per matched observation, sorted descending. Useful for spotting rows where most/all columns disagree (often a sign of a wrong row match, a duplicated key resolved differently, or a systemic shift on a single record).
ks_row_diff_summary(x, n = NULL)ks_row_diff_summary(x, n = NULL)
x |
A |
n |
Optional cap on the number of rows returned (default
|
Only matched rows are reported; unmatched rows are available via
ks_unmatched_rows().
A tibble with one row per matched observation that has at least one cell diff:
key_id, base_row, comp_row, key_label
n_diffs: count of differing cells on this observation.
columns: comma-separated sample of the affected columns
(capped at 8 names).
Sorted by n_diffs descending. Zero-row tibble when there are no
value differences.
ks_compare(), ks_cause_summary().
a <- data.frame(id = 1:3, x = 1:3, y = 1:3) b <- data.frame(id = 1:3, x = c(1, 9, 9), y = c(1, 9, 3)) cmp <- ks_compare(a, b, by = "id") ks_row_diff_summary(cmp)a <- data.frame(id = 1:3, x = 1:3, y = 1:3) b <- data.frame(id = 1:3, x = c(1, 9, 9), y = c(1, 9, 3)) cmp <- ks_compare(a, b, by = "id") ks_row_diff_summary(cmp)
Thin wrapper around base::options() for the ksCompare.* namespace.
Accepts the same argument names as ks_comp_options() (without the
ksCompare. prefix) and forwards them into base::options(),
returning the previous values invisibly so the call can be unwound
with base::options() or withr::with_options().
ks_set_comp_options(...)ks_set_comp_options(...)
... |
Named arguments. Recognised names match the formals of
|
Globals set this way become the new defaults of
ks_comp_options() (and therefore of ks_compare() when no
options = argument is supplied).
Invisibly, a named list of the previous values (suitable for
do.call(options, prev) to restore).
old <- ks_set_comp_options(path = tempfile("ksCompare_"), str_case = "fold") ks_comp_options()$path do.call(options, old) # restoreold <- ks_set_comp_options(path = tempfile("ksCompare_"), str_case = "fold") ks_comp_options()$path do.call(options, old) # restore
Scans matched columns to find candidates whose combination with the
current by key would make the join key unique on at least one
side (and ideally both). Returns columns ranked by how much they
reduce duplicate-key cardinality. Useful when ks_compare() used
dup_keys = "keep_all" and the result looks suspicious.
ks_suggest_key(x, base = NULL, comp = NULL, top_n = 10L)ks_suggest_key(x, base = NULL, comp = NULL, top_n = 10L)
x |
A |
base, comp
|
Optional: the original input data frames used to
build |
top_n |
Maximum number of candidate columns to return. |
Because ks_compare() does not snapshot the input data, the
original base and comp frames must be passed in to inspect
candidate columns. When base / comp are not supplied (or do
not contain the original columns) an empty tibble is returned.
A tibble with columns column, pct_unique_base,
pct_unique_comp, would_make_unique. Zero rows when no key is
in use, no duplicates exist, or no improvement is possible.
ks_match_health(), ks_compare().
SAS's &SYSINFO macro variable encodes the result of PROC COMPARE as a
bitmask. ks_sysinfo() returns a compatible-style integer summarizing
the same kinds of facts, plus a named integer vector showing which bits
are set. Bit positions follow SAS documentation:
ks_sysinfo(x)ks_sysinfo(x)
x |
A |
| Bit | Mask | Meaning |
| 1 | 1 | Data set labels differ |
| 2 | 2 | Data set types differ |
| 3 | 4 | Variable has different informat |
| 4 | 8 | Variable has different format |
| 5 | 16 | Variable has different length |
| 6 | 32 | Variable has different label |
| 7 | 64 | Base data set has observation not in compare |
| 8 | 128 | Compare data set has observation not in base |
| 9 | 256 | Base data set has BY group not in compare |
| 10 | 512 | Compare data set has BY group not in base |
| 11 | 1024 | Base data set has variable not in compare |
| 12 | 2048 | Compare data set has variable not in base |
| 13 | 4096 | A value comparison was unequal |
| 14 | 8192 | Conflicting variable types |
| 15 | 16384 | BY variables do not match |
| 16 | 32768 | Fatal error - comparison not done |
Length and informat are not currently tracked; their bits are always
clear. haven does not surface SAS informats, and column "length"
is not a meaningful R-side concept for numerics.
An integer with class ks_sysinfo. Use as.integer() for the
raw value or print to see the named bits.
a <- data.frame(id = 1:3, x = c(1, 2, 3)) b <- data.frame(id = 1:3, x = c(1, 2, 4), z = 1:3) cmp <- ks_compare(a, b, by = "id") ks_sysinfo(cmp) as.integer(ks_sysinfo(cmp))a <- data.frame(id = 1:3, x = c(1, 2, 3)) b <- data.frame(id = 1:3, x = c(1, 2, 4), z = 1:3) cmp <- ks_compare(a, b, by = "id") ks_sysinfo(cmp) as.integer(ks_sysinfo(cmp))
Returns the long-format cell-level diff table. Equivalent to
as_tibble(cmp) and identical to cmp$value_diff when
include_unmatched = FALSE.
ks_tidy(x, ...) ## S3 method for class 'ks_comparison' ks_tidy(x, include_unmatched = FALSE, ...)ks_tidy(x, ...) ## S3 method for class 'ks_comparison' ks_tidy(x, include_unmatched = FALSE, ...)
x |
A |
... |
Unused. |
include_unmatched |
Logical (default |
When include_unmatched = TRUE, rows describing observations
present on only one side of the comparison are appended to the
result with kind = "base_only" / "comp_only" and column_base
/ column_comp set to NA. One row is added per unmatched
observation, capped at ks_compare(max_unmatched_rows = ...).
A tibble of value differences, optionally with appended
unmatched-row markers. Has columns key_id, base_row,
comp_row, column_base, column_comp, kind, base, comp,
diff, na_flow, note.
cmp <- ks_compare( data.frame(id = 1:2, x = c(1, 2)), data.frame(id = 1:3, x = c(1, 3, 4)), by = "id" ) ks_tidy(cmp) ks_tidy(cmp, include_unmatched = TRUE)cmp <- ks_compare( data.frame(id = 1:2, x = c(1, 2)), data.frame(id = 1:3, x = c(1, 3, 4)), by = "id" ) ks_tidy(cmp) ks_tidy(cmp, include_unmatched = TRUE)
Builds a tolerance object consumed by ks_compare(). Two numeric
values a and b are considered equal when any of the active
rules pass:
ks_tol(abs = 0, rel = 0, ulp = 0, per_column = NULL)ks_tol(abs = 0, rel = 0, ulp = 0, per_column = NULL)
abs |
Non-negative absolute tolerance (default |
rel |
Non-negative relative tolerance, scaled by
|
ulp |
Non-negative integer ULP tolerance. Typical values are
|
per_column |
Optional named list of per-column |
abs(a - b) <= abs
abs(a - b) <= rel * max(abs(a), abs(b))
ulp_distance(a, b) <= ulp — IEEE-754 units in the last place,
useful for catching floating-point round-trip noise without false
positives on genuine differences.
Per-column overrides may be supplied via per_column, a named list
whose names are base-side column names and whose values are
themselves the result of ks_tol(). Columns not listed fall back to
the top-level abs / rel / ulp.
A ks_tol S3 list.
ks_tol(abs = 1e-9) ks_tol(rel = 1e-6, per_column = list(price = ks_tol(abs = 0.005))) ks_tol(ulp = 4)ks_tol(abs = 1e-9) ks_tol(rel = 1e-6, per_column = list(price = ks_tol(abs = 0.005))) ks_tol(ulp = 4)
Returns the rows that are present on only one side of a comparison
("base_only" or "comp_only"), with their original data columns.
Mirrors the "Observations in PROC COMPARE.
ks_unmatched_rows(x, side = c("both", "base_only", "comp_only"))ks_unmatched_rows(x, side = c("both", "base_only", "comp_only"))
x |
A |
side |
One of |
The result is precomputed at ks_compare() time and capped by the
max_unmatched_rows argument (default 100). When the cap kicks
in, the truncated attribute is set and the full counts are
recorded on n_total.
A tibble with columns side, key_id, key_label,
base_row, comp_row, followed by the data columns from the
side that holds each row (base_row is NA for comp_only
rows and vice versa, making it trivial to look an observation
up in the original base / comp frame).
Empty (zero-row) tibble when there are no unmatched rows.
a <- data.frame(id = 1:3, x = c(1, 2, 3)) b <- data.frame(id = 2:4, x = c(2, 3, 4)) cmp <- ks_compare(a, b, by = "id") ks_unmatched_rows(cmp)a <- data.frame(id = 1:3, x = c(1, 2, 3)) b <- data.frame(id = 2:4, x = c(2, 3, 4)) cmp <- ks_compare(a, b, by = "id") ks_unmatched_rows(cmp)
These helpers reshape the long-format value_diff table inside a
ks_comparison into the four classic SAS PROC COMPARE output datasets:
as_outbase(x) as_outcomp(x) as_outdif(x) as_outnoequal(x)as_outbase(x) as_outcomp(x) as_outdif(x) as_outnoequal(x)
x |
A |
as_outbase() — values from the base frame, only for matched rows that
contain at least one differing column; one row per matched key, one
column per compared variable.
as_outcomp() — same shape as as_outbase() but values from the
compare frame.
as_outdif() — numeric difference (base - comp) for every matched
row that contains at least one differing column; non-numeric cells are
reported as NA with a .note column.
as_outnoequal() — only the rows where at least one matched cell
differs, in long format (one row per differing cell).
The shapes mirror SAS's OUTBASE=, OUTCOMP=, OUTDIF=, and
OUTNOEQUAL= datasets in spirit; we do not replicate the exact metadata
columns (_TYPE_, _OBS_, ...) but include key_id and the original
key columns so the rows can be related back to the source frames.
A tibble.
a <- data.frame(id = 1:3, x = c(1, 2, 3), y = c("a", "b", "c")) b <- data.frame(id = 1:3, x = c(1, 2, 4), y = c("a", "B", "c")) cmp <- ks_compare(a, b, by = "id") as_outbase(cmp) as_outcomp(cmp) as_outdif(cmp) as_outnoequal(cmp)a <- data.frame(id = 1:3, x = c(1, 2, 3), y = c("a", "b", "c")) b <- data.frame(id = 1:3, x = c(1, 2, 4), y = c("a", "B", "c")) cmp <- ks_compare(a, b, by = "id") as_outbase(cmp) as_outcomp(cmp) as_outdif(cmp) as_outnoequal(cmp)
Computes a small list of headline counts that drive the printed summary, the HTML report KPI cards, and CI gates.
## S3 method for class 'ks_comparison' summary(object, ...)## S3 method for class 'ks_comparison' summary(object, ...)
object |
A |
... |
Unused; present for S3 conformance. |
A ks_comparison_summary list (also pretty-printed via
print()) with components:
n_base_rows, n_comp_rows: input row counts.
n_matched_rows, n_base_only_rows, n_comp_only_rows: row
counts after matching.
n_matched_columns, n_base_only_columns,
n_comp_only_columns: schema-side counts.
n_value_diffs: number of differing cells in matched rows.
n_columns_with_diffs: how many distinct columns hold those
differences.
ks_glance() for a tibble version, ks_tidy() for the
long cell-level table.
cmp <- ks_compare( data.frame(id = 1:3, x = c(1, 2, 3)), data.frame(id = 1:3, x = c(1, 2, 4)), by = "id" ) summary(cmp)cmp <- ks_compare( data.frame(id = 1:3, x = c(1, 2, 3)), data.frame(id = 1:3, x = c(1, 2, 4)), by = "id" ) summary(cmp)