Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ Imports:
dplyr (>= 1.0.0),
pointblank (>= 0.12.3),
rlang (>= 0.4.0),
stats,
tidyselect (>= 1.0.0),
utils,
yaml (>= 2.2.0)
Expand Down
3 changes: 0 additions & 3 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ export(setup_pointblank_agent)
export(validate_row_counts)
export(write_rules_template)
importFrom(dplyr,"%>%")
importFrom(dplyr,across)
importFrom(dplyr,arrange)
importFrom(dplyr,collect)
importFrom(dplyr,left_join)
importFrom(pointblank,action_levels)
Expand All @@ -26,7 +24,6 @@ importFrom(pointblank,create_agent)
importFrom(pointblank,interrogate)
importFrom(rlang,":=")
importFrom(rlang,.data)
importFrom(stats,setNames)
importFrom(tidyselect,all_of)
importFrom(utils,modifyList)
importFrom(yaml,read_yaml)
Expand Down
20 changes: 20 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,26 @@
projection (`paste0(character(0), "__ok")` yields `"__ok"`) that `any_of()`
had been eating since 0.4.8 (issue #17).

## Chores

* Packaging and code cleanup (issue #34): `dev/` is excluded from the tarball
(`.Rbuildignore`); the orphan `inst/templates/rules_template.yaml`
(referenced nowhere) is gone; unused `@importFrom` entries dropped
(`dplyr::arrange`/`across`, `stats::setNames` replaced by a base
construction); the `numeric_abs` default reads `1e-9` instead of a
9-zero decimal literal; `is_non_local()` reuses `is_arrow()` instead of
duplicating the Arrow class list; `abs(ref_vals)` and the IEEE fp constant
are hoisted out of the kernels' expressions and loops;
`tol_col_counts()` counts failures without allocating intermediate
vectors; `format_key_examples()` truncates to 3 groups before formatting;
the lazy `preprocess_dataframe()` composes ONE `mutate()` for all
normalized columns instead of one or two layers per column (the dbplyr
query-construction anti-pattern eliminated elsewhere in 0.4.8); loop
variables no longer shadow `base::c`; the tolerance kernel documents the
integer-overflow caveat. Deliberately not changed: `print()` of a report
builds the gt object in non-interactive sessions too (printing IS the
render), and the `DBI` availability guard stays transitive via `duckdb`.

## Documentation and input friction

* Doc-vs-code drift resolved (issue #32): `@return` of
Expand Down
13 changes: 6 additions & 7 deletions R/compare_datasets_from_yaml.R
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
#' (same caveat as `date_equal_mode`)
#' @return The \code{path} to the written YAML file, returned invisibly.
#' @importFrom yaml write_yaml
#' @importFrom stats setNames
#' @importFrom dplyr collect
#' @export
#' @examples
Expand All @@ -43,7 +42,7 @@ write_rules_template <- function(data_reference,
key = NULL, label = NULL, path = "rules.yaml", version = 1L, na_equal_default = TRUE,
ignore_columns_default = character(0),
check_count_default = TRUE, expected_count_default = NULL, row_count_tolerance_default = 0,
numeric_abs = 0.000000001, numeric_rel = 0,
numeric_abs = 1e-9, numeric_rel = 0,
integer_abs = 0L,
character_equal_mode = "exact", character_case_insensitive = FALSE, character_trim = FALSE,
date_equal_mode = "exact",
Expand Down Expand Up @@ -108,7 +107,7 @@ write_rules_template <- function(data_reference,
datetime = list(equal_mode = datetime_equal_mode),
logical = list(equal_mode = logical_equal_mode)
),
by_name = setNames(replicate(length(types), list(), simplify = FALSE), names(types))
by_name = as.list(structure(replicate(length(types), list(), simplify = FALSE), names = names(types)))
)
write_yaml(x = y, file = path)
invisible(path)
Expand Down Expand Up @@ -363,7 +362,7 @@ validate_comparison_key <- function(key, ref_cols, cand_cols) {
#' the fast path skips the per-column agent.}
#' \item{summary}{Aggregate counts from \code{coverage} (n_checks, n_pass,
#' n_fail, n_rows_failed_total, all_passed).}
#' @importFrom dplyr arrange across left_join %>%
#' @importFrom dplyr left_join %>%
#' @importFrom pointblank interrogate
#' @importFrom dplyr collect
#' @export
Expand Down Expand Up @@ -874,9 +873,9 @@ compare_datasets_from_yaml <- function(data_reference,
# already carries __eq columns; col_vals_equal(na_pass = ...) alone cannot
# express these semantics.
if (!is_lazy && length(fail$eq) > 0) {
for (c in fail$eq) {
cmp_for_agent[[datadiff_eq_col(c)]] <- eq_col_bool(
cmp_for_agent, col = c, ref_suffix = ref_suffix, na_equal = na_equal
for (col_nm in fail$eq) {
cmp_for_agent[[datadiff_eq_col(col_nm)]] <- eq_col_bool(
cmp_for_agent, col = col_nm, ref_suffix = ref_suffix, na_equal = na_equal
)
}
}
Expand Down
30 changes: 16 additions & 14 deletions R/coverage.R
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
# matching col_vals_equal(..., na_pass = FALSE).
tol_col_counts <- function(tbl, col) {
ok <- tol_col_bool(tbl, col = col)
list(n = length(ok), n_failed = sum(is.na(ok) | !ok))
# length - sum(TRUE) counts FALSE and NA in one pass without allocating
# the two intermediate logical vectors of is.na(ok) | !ok
list(n = length(ok), n_failed = length(ok) - sum(ok, na.rm = TRUE))
}

# n / n_failed for an equality column (resolved boolean, no NA).
Expand Down Expand Up @@ -104,23 +106,23 @@ build_coverage <- function(tbl, tol_cols, eq_cols,
# Existence checks: every common (non-type-mismatch) column gets a col_exists
# check, distinct from its value check. These pass (the column is present in
# both datasets by construction), mirroring a full per-column pointblank run.
for (c in c(tol_cols, eq_cols)) {
add(c, "col_exists", 1L, 0L)
for (col_nm in c(tol_cols, eq_cols)) {
add(col_nm, "col_exists", 1L, 0L)
}
for (c in tol_cols) {
cnt <- counts[[c]] %||% tol_col_counts(tbl, col = c)
add(c, "tolerance", cnt$n, cnt$n_failed)
for (col_nm in tol_cols) {
cnt <- counts[[col_nm]] %||% tol_col_counts(tbl, col = col_nm)
add(col_nm, "tolerance", cnt$n, cnt$n_failed)
}
for (c in eq_cols) {
cnt <- counts[[c]] %||%
eq_col_counts(tbl, col = c, ref_suffix = ref_suffix, na_equal = na_equal)
add(c, "equality", cnt$n, cnt$n_failed)
for (col_nm in eq_cols) {
cnt <- counts[[col_nm]] %||%
eq_col_counts(tbl, col = col_nm, ref_suffix = ref_suffix, na_equal = na_equal)
add(col_nm, "equality", cnt$n, cnt$n_failed)
}
for (c in missing_in_candidate) {
add(c, "missing_column", 1L, 1L)
for (col_nm in missing_in_candidate) {
add(col_nm, "missing_column", 1L, 1L)
}
for (c in type_mismatch_cols) {
add(c, "type_mismatch", 1L, 1L)
for (col_nm in type_mismatch_cols) {
add(col_nm, "type_mismatch", 1L, 1L)
}
if (isTRUE(row_validation_info$check_count)) {
add("<row_count>", "row_count", 1L, if (isTRUE(row_count_ok)) 0L else 1L)
Expand Down
11 changes: 6 additions & 5 deletions R/duplicate_keys.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@
# tables keep the SQL-native count()/group_by (cheap inside the database).

format_key_examples <- function(uniq_keys, key) {
rows <- unname(apply(uniq_keys, 1, function(r) {
n_groups <- nrow(uniq_keys)
shown <- utils::head(uniq_keys, 3L)
rows <- unname(apply(shown, 1, function(r) {
paste(key, "=", r, collapse = ", ")
}))
if (length(rows) <= 3) {
rows
} else {
c(rows[1:3], "...")
if (n_groups > 3L) {
rows <- c(rows, "...")
}
rows
}

#' @importFrom rlang .data
Expand Down
38 changes: 19 additions & 19 deletions R/pointblank_setup.R
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ setup_pointblank_agent <- function(cmp, cols_reference = NULL, common_cols, tol_

# Add dummy columns for missing columns BEFORE creating the agent
# These columns are set to FALSE and we'll check they equal TRUE (will fail)
for (c in missing_in_candidate) {
dummy_col <- paste0(datadiff_prefix_missing_col, c)
for (col_nm in missing_in_candidate) {
dummy_col <- paste0(datadiff_prefix_missing_col, col_nm)
if (is_non_local(cmp)) {
cmp <- dplyr::mutate(cmp, !!dummy_col := FALSE)
} else {
Expand All @@ -82,8 +82,8 @@ setup_pointblank_agent <- function(cmp, cols_reference = NULL, common_cols, tol_

# Add dummy FALSE columns for type-mismatched columns.
# These will generate a dedicated failing validation step per column.
for (c in type_mismatch_cols) {
dummy_col <- paste0(datadiff_prefix_type_mismatch, c)
for (col_nm in type_mismatch_cols) {
dummy_col <- paste0(datadiff_prefix_type_mismatch, col_nm)
if (is_non_local(cmp)) {
cmp <- dplyr::mutate(cmp, !!dummy_col := FALSE)
} else {
Expand All @@ -98,11 +98,11 @@ setup_pointblank_agent <- function(cmp, cols_reference = NULL, common_cols, tol_
# express the one-sided/two-sided NA distinction).
eq_cols <- setdiff(x = common_cols, y = tol_cols)
if (!is_non_local(cmp)) {
for (c in eq_cols) {
eq_col <- datadiff_eq_col(c)
for (col_nm in eq_cols) {
eq_col <- datadiff_eq_col(col_nm)
if (!(eq_col %in% get_col_names(cmp))) {
cmp[[eq_col]] <- eq_col_bool(
cmp, col = c, ref_suffix = ref_suffix, na_equal = na_equal
cmp, col = col_nm, ref_suffix = ref_suffix, na_equal = na_equal
)
}
}
Expand All @@ -120,37 +120,37 @@ setup_pointblank_agent <- function(cmp, cols_reference = NULL, common_cols, tol_
# Skipped for the non-local path where cmp is a slim table containing only
# boolean validation columns (the original data columns are not present).
if (add_col_exists_steps) {
for (c in common_cols) {
agent <- agent %>% col_exists(columns = all_of(c))
for (col_nm in common_cols) {
agent <- agent %>% col_exists(columns = all_of(col_nm))
}
}

# For missing columns, add a validation that will fail
for (c in missing_in_candidate) {
dummy_col <- paste0(datadiff_prefix_missing_col, c)
for (col_nm in missing_in_candidate) {
dummy_col <- paste0(datadiff_prefix_missing_col, col_nm)
agent <- agent %>%
col_vals_equal(
columns = all_of(dummy_col),
value = TRUE,
na_pass = FALSE,
label = paste("col_exists:", c)
label = paste("col_exists:", col_nm)
)
}

# For type-mismatched columns, add a validation that will always fail.
for (c in type_mismatch_cols) {
dummy_col <- paste0(datadiff_prefix_type_mismatch, c)
for (col_nm in type_mismatch_cols) {
dummy_col <- paste0(datadiff_prefix_type_mismatch, col_nm)
agent <- agent %>%
col_vals_equal(
columns = all_of(dummy_col),
value = TRUE,
na_pass = FALSE,
label = paste("type_mismatch:", c)
label = paste("type_mismatch:", col_nm)
)
}

for (c in eq_cols) {
eq_col <- datadiff_eq_col(c)
for (col_nm in eq_cols) {
eq_col <- datadiff_eq_col(col_nm)
if (!(eq_col %in% col_names)) {
# Only reachable with a lazy cmp missing its precomputed boolean:
# the documented contract requires it (deriving it here would need
Expand All @@ -164,8 +164,8 @@ setup_pointblank_agent <- function(cmp, cols_reference = NULL, common_cols, tol_
col_vals_equal(columns = all_of(eq_col), value = TRUE, na_pass = FALSE)
}

for (c in tol_cols) {
ok_col <- datadiff_ok_col(c)
for (col_nm in tol_cols) {
ok_col <- datadiff_ok_col(col_nm)
agent <- agent %>% col_vals_equal(columns = all_of(ok_col), value = TRUE, na_pass = FALSE)
}

Expand Down
19 changes: 13 additions & 6 deletions R/preprocessing.R
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ normalize_text <- function(x, case_insensitive = FALSE, trim = FALSE) {
x <- trimws(x)
}
if (case_insensitive) {
x <- tolower(x = x)
x <- tolower(x)
}
x
}
Expand Down Expand Up @@ -63,6 +63,7 @@ preprocess_dataframe <- function(df, col_rules, schema = NULL) {
}
}
}
lazy_exprs <- list()
for (nm in names(col_rules)) {
cr <- col_rules[[nm]]
normalized <- identical(cr$equal_mode %||% "exact", "normalized")
Expand Down Expand Up @@ -93,23 +94,26 @@ preprocess_dataframe <- function(df, col_rules, schema = NULL) {

if (is_char && should_normalize) {
if (is_non_local(out)) {
# Non-local path: build transformations via dplyr::mutate()
nm_sym <- dplyr::sym(nm)
# Non-local path: compose ONE expression per column and accumulate;
# a single mutate() at the end keeps the dbplyr query-construction
# cost O(1) instead of one or two mutate() layers per column
expr <- dplyr::sym(nm)
if (trim) {
if (is_arrow(out)) {
# Arrow does not support trimws(); use stringr::str_trim() instead
if (!requireNamespace("stringr", quietly = TRUE)) {
stop("Package 'stringr' is required for trim on Arrow objects.")
}
out <- dplyr::mutate(out, !!nm := stringr::str_trim(!!nm_sym))
expr <- rlang::expr(stringr::str_trim(!!expr))
} else {
# SQL path: trimws() -> SQL TRIM() (translated by dbplyr)
out <- dplyr::mutate(out, !!nm := trimws(!!nm_sym))
expr <- rlang::expr(trimws(!!expr))
}
}
if (case_insensitive) {
out <- dplyr::mutate(out, !!nm := tolower(!!nm_sym))
expr <- rlang::expr(tolower(!!expr))
}
lazy_exprs[[nm]] <- expr
} else {
out[[nm]] <- normalize_text(
out[[nm]],
Expand All @@ -119,5 +123,8 @@ preprocess_dataframe <- function(df, col_rules, schema = NULL) {
}
}
}
if (length(lazy_exprs) > 0) {
out <- dplyr::mutate(out, !!!lazy_exprs)
}
out
}
Loading
Loading