R Programming / Evaluation, ownership, and reproducible results / Lesson 18
An auditable analysis pipeline
Combine typed inputs, explicit rejection policy, invariant checks, and a reviewable result boundary.
On this page
Lesson goals
- Write a complete input-to-summary contract.
- Choose strict rejection versus explicit row quarantine.
- Separate structural validation from substantive conclusions.
Begin with a small explicit analytical question
Suppose a synthetic workshop records one nonnegative duration per uniquely identified item. The task is to report count, total minutes, and mean minutes for supplied valid records. This is a descriptive calculation, not a prediction, causal analysis, or population inference. State which records are eligible and whether a missing or invalid value rejects the whole input or creates a separate rejected-row report. These choices determine the denominator and therefore the meaning of the average. Keep the pipeline small enough that a reviewer can follow every transformation and independently verify the fixture by hand.
Validate at the boundary where meaning is introduced
The first fixture imports supplied CSV text under declared column classes and checks exact names, unique nonempty ids, a nonempty table, finite measurements, and nonnegative durations. It then returns a named result. The code is intentionally strict and rejects incomplete measurements rather than guessing values. Parsing errors can occur before the explicit checks and should remain visible as unsupported input, not be replaced with zero. A reusable production-facing parser would need more detailed token diagnostics and compatibility tests; this fixture establishes one narrow contract without claiming to solve every CSV dialect or locale.
summarize_csv <- function(text) {
d <- read.csv(text = text, colClasses = c("character", "numeric"),
stringsAsFactors = FALSE, na.strings = "")
if (!identical(names(d), c("id", "minutes"))) stop("unexpected schema")
if (anyNA(d$id) || any(d$id == "") || anyDuplicated(d$id)) stop("invalid ids")
if (nrow(d) == 0L || anyNA(d$minutes) || !all(is.finite(d$minutes)) || any(d$minutes < 0)) stop("invalid minutes")
list(rows = nrow(d), total = sum(d$minutes), mean = mean(d$minutes))
}
result <- summarize_csv("id,minutes\na,3\nb,9")
cat("rows=", result$rows, "; total=", result$total, "; mean=", result$mean, "\n", sep = "")
rows=2; total=12; mean=6
A complete in-memory pipeline has a narrow declared schema, unique ids, nonnegative finite measurements, and an explicit summary result.
An alternative rejection policy is a different analysis
The second fixture demonstrates explicit row quarantine on already supplied vectors. It reports accepted and rejected positions and computes a total over accepted rows. This policy differs from strict whole-input rejection and must not be introduced silently after seeing a desired result. Preserve original ids, raw tokens, and rejection reasons when permitted, and check uniqueness separately if one record per item is still required. A rejected row is not a measured zero. For richer diagnostics, return one or more reason codes per row rather than only a Boolean, then test overlaps between reasons.
audit_rows <- function(ids, values) {
stopifnot(length(ids) == length(values))
valid <- !is.na(ids) & nzchar(ids) & !is.na(values) & is.finite(values) & values >= 0
list(accepted = which(valid), rejected = which(!valid),
total = sum(values[valid]))
}
result <- audit_rows(c("a", "b", "c"), c(3, NA_real_, -1))
cat("accepted=", paste(result$accepted, collapse = ","), "\n", sep = "")
cat("rejected=", paste(result$rejected, collapse = ","), "\n", sep = "")
cat("accepted total=", result$total, "\n", sep = "")
accepted=1
rejected=2,3
accepted total=3
This alternative policy retains explicit rejected row positions instead of silently removing invalid measurements; uniqueness remains a separate declared check.
Use independent invariants and worked fixtures
For the strict valid fixture, two rows with three and nine minutes imply a total of twelve and a mean of six. Check count, names, types, and values together. For a quarantining pipeline, accepted plus rejected counts should cover every supplied position exactly once, while retained identities must remain aligned with values. Add duplicate ids, a singleton, empty input, missingness, nonfinite values, negative values, and unexpected columns as focused cases. Invariants complement expected examples; a preserved total alone can miss a swapped identity or a wrongly converted unit.
Deliver the result with scope and execution evidence
Keep the script, schema, units, rejection policy, configuration, and environment record alongside the summary. A later model or chart should consume this validated result without changing the observation contract invisibly. State which tests were actually executed and which remain documented expectations. In this course no R runtime or parser was available, so these complete base-R examples remain illustrative and their outputs are derived. Before using a learnerโs local execution as evidence, compare all fixtures in a fresh supported session and investigate warnings. A clean synthetic pipeline supports local correctness reasoning, not a guarantee about an external dataset or decision.
Common errors and fixes
A strict pipeline starts dropping bad rows after a disappointing mean
- Why it happens
- The eligibility policy changed in response to the result.
- How to fix it
- Choose and document the policy before interpreting summaries.
- Prevent it next time
- Preserve rejected counts and the analysis revision history.
A correct total hides swapped ids
- Why it happens
- Only aggregate arithmetic was verified.
- How to fix it
- Check known identity-value pairs and aligned row operations.
- Prevent it next time
- Combine structural and numerical fixtures.
A derived output is reported as an executed test
- Why it happens
- Expected behavior was confused with runtime evidence.
- How to fix it
- Label the execution environment and actual checks precisely.
- Prevent it next time
- Keep separate records for derivation, parsing, and execution.
Check your understanding
1. What does the strict fixture return for durations three and nine, and why is missingness not silently ignored?
Show worked solution
It returns two rows, total twelve, and mean six. The declared contract requires every duration to be observed, finite, and nonnegative; missingness therefore rejects the input instead of changing the denominator. A different permitted policy would need its own explicit output and tests.
2. What extra evidence should accompany the quarantine fixture in a real review?
Show worked solution
Retain stable observation ids, raw-token provenance where permitted, reason codes for rejection, counts covering all input rows, and a uniqueness policy. Verify that accepted positions remain paired with their original values. Report the accepted-only scope rather than describing the result as a summary of all supplied measurements.
Key takeaways
- Eligibility and rejection policies define the denominator.
- Validate meaning, structure, and arithmetic at separate boundaries.
- State execution evidence and external limitations accurately.