A column of negative revenue values can survive an entire ETL pipeline, a dashboard refresh and three weekly reports before anyone notices. In R shops the fix is almost always one of three packages: pointblank, validate or assertr — and teams routinely pick the wrong one, then fight it for a year. The mismatch is easy to explain: one is built for reporting validation results, one is built for storing validation rules as data, and one is built for stopping a pipeline the moment an assumption breaks. Those are three different jobs.
The code below uses the current releases: pointblank 0.12.4 (CRAN, 2026-07-21), validate (CRAN, actively maintained, 434 GitHub stars) and assertr (rOpenSci peer-reviewed, 484 stars).

TL;DR — pick by the question you are answering
- “Did this month’s data pass our quality rules, and can I show a manager?” → pointblank. Validation steps chain into an agent, thresholds trigger
warn/stopactions, and the output is a shareable report. - “Our validation rules are a business asset that must live outside the code.” → validate. Rules are first-class objects: you can import, export, annotate and diff them, and confront any dataset version against them.
- “If the input is wrong, kill the job before it writes garbage downstream.” → assertr. Assertions sit inside a
dplyrpipeline and raise an error immediately.
If you only remember one line: pointblank measures, validate encodes, assertr guards.
Three packages, three philosophies
| Dimension | pointblank | validate | assertr |
|---|---|---|---|
| Current release | 0.12.4 (CRAN, 2026-07-21) | CRAN, maintained (last push 2025-12) | CRAN, rOpenSci-reviewed (last push 2024-04) |
| GitHub stars | 495 | 434 | 484 |
| Mental model | Validation plan with report | Rules as data | Assertions in a pipeline |
| Typical trigger | interrogate() | confront() | %>% assert(...) |
| Failure behavior | Recorded, threshold-driven | Recorded, then summarised/plotted | Raises an error, stops execution |
| Aggregated results | Agent report (table + HTML) | summary(), barplot() | Error message with offending rows |
| Rule storage outside code | YAML (yaml_write) | YAML / R script, full export | Not a design goal |
| Native tidyverse feel | High | Medium (base-R friendly) | Very high |
| Works on database tables | Yes (DBI/dbplyr backends) | Data frames | Data frames |
| Report for stakeholders | Yes, best in class | Plots and summaries | No |
| Learning curve | Moderate | Moderate | Low |
That table hides the important part: these tools fail differently. A pointblank validation with a failing step still returns an object you can investigate. An assertr failure ends your R session’s useful work. Choosing wrongly means either silent bad data (you picked a reporter for a guard job) or a pipeline that dies on the first anomaly with no summary of the other 40 checks (you picked a guard for a reporting job).
pointblank — for teams that must show their work
pointblank is the most “product-like” of the three. You build a validation plan by chaining steps onto an agent, then execute it once. Each step becomes a row in a report with pass/fail counts and threshold state.
| |
The action_levels(warn_at = 0.05, stop_at = 0.10) argument is the feature that makes pointblank usable in production: failure rates per step are compared against thresholds, so one bad row in ten million does not page anyone, while 12% bad rows does. Steps that cross stop_at mark the whole validation as failing.
What you get for that structure:
- Step-level drill-down. Every failing step can return the offending rows as an extract table, which turns “validation failed” into “here are the 37 rows you need to fix”.
- A report object, not console text. The report is a table you can render to HTML and hand to someone who has never opened R.
- YAML plans.
yaml_write(agent, filename = "sales-validation.yml")serialises the plan;yaml_read()loads it back, so a data steward can review the rules in a pull request without reading R code. - Database pushdown. With a
tbl()connection to Postgres or adbplyrsource, checks execute in SQL instead of pulling a billion rows into memory.
The cost is conceptual overhead: an agent is a stateful object, column selection uses vars(), and thresholds are fractional failure rates rather than boolean pass/fail. Teams that expect stopifnot()-style behaviour are surprised the first time a validation “passes” with 4% bad rows because warn_at was set to 5%.

validate — rules as a first-class asset
validate starts from a different premise: a validation rule is a piece of knowledge about your domain, and knowledge should not be buried inside a script. Rules are objects that can be exported, imported, documented and compared.
| |
confront() does not stop at the first failure — it evaluates every rule and returns per-rule counts of passes, failures, missing values, errors and warnings. That makes it the right tool when the question is “how bad is this file, across all dimensions?” rather than “is this file acceptable?”.
The killer feature is portability. Rules can be exported and re-imported:
| |
Because the rules live in a text file, you can version them in Git, review them like schema migrations, and run the same rule set against last year’s data to see whether the problem is new or has always been there. validate also supports rule metadata (descriptions, origins) and confront results that you can subset per rule.
The trade-off: the reporting layer is thinner than pointblank’s. You get summary() tables and bar plots, not a self-contained HTML report with drill-down extracts. If your audience is regulators or analysts who read summary tables, that is fine. If your audience is a Slack channel that wants a link, pointblank wins.
assertr — assertions for pipeline hygiene
assertr is the smallest and most opinionated option. It provides five assertion verbs — verify, assert, insist, assert_rows, insist_rows — designed to be dropped into a dplyr pipeline right after data loading.
| |
Read that as executable documentation: the pipeline cannot continue unless the frame has the expected columns, is non-empty, has no missing order IDs, has non-negative revenue, contains only known statuses, and has no revenue values beyond four standard deviations. insist(within_n_sds(4), revenue) is the statistical outlier guard — useful when a single 10^9 typo would otherwise dominate every downstream average.
When something fails you get the offending rows and indices, not just a boolean:
| |
For exploratory work where you want all failures at once rather than fail-fast, wrap the chain in chain_start and chain_end — assertr then collects every violation and reports them together at the end. That is the closest assertr gets to a validation report, and it is deliberately terse.
What assertr does not do: store rules as data, produce stakeholder reports, or push checks down to a database. It is a guard rail, not a governance tool.
Decision matrix
| Situation | Pick | Reason |
|---|---|---|
| Monthly data quality report for management | pointblank | HTML report, step-level drill-down, threshold actions |
| Rules must be reviewed by non-programmers | validate | YAML round-trip, rules are objects with metadata |
| ETL job must never write bad rows | assertr | Fails inside the pipeline, before mutate/write |
| Validating a database table too large to pull | pointblank | SQL pushdown via dbplyr backends |
| Comparing rule outcomes across dataset versions | validate | Same rule object confronted against multiple frames |
| Quick sanity checks in an analysis script | assertr | Three lines, zero configuration |
| Regulated environment needing documented rule provenance | validate | Rules stored, annotated and exportable |
Pitfalls that cost real debugging time
1. Threshold semantics are fractional, not absolute. action_levels(warn_at = 0.05) means 5% of units in that step, so on a 20-row table a single bad row is 5% and trips the warning. Small datasets need explicit counts or warn_at = 1.
2. confront() returns missing values as their own category. A rule like revenue > 0 on a column containing NA yields nNA, not a failure. If you only read the fails column, missing data invisibly passes your quality gate.
3. assert needs a predicate function, verify needs an expression. Passing assert(revenue > 0) is a type error that only shows up at runtime; the correct forms are assert(within_bounds(0, Inf), revenue) or verify(revenue > 0).
4. vars() versus bare column names. pointblank’s R API expects columns = vars(revenue); tidyselect-style bare names belong to other tools. Copy-pasting from a dplyr example is the most common source of “column not found” errors.
5. Integer versus double comparisons. discount <= 0.6 behaves differently when the column arrives as a factor from read.csv() with stringsAsFactors surprises. Assert types before asserting ranges.
6. Rules drift from reality. A rule set that nobody updates becomes a rubber stamp. Review rules on the same cadence as schema migrations, and delete rules whose failure rate has been 0% for a year without anyone investigating — they are either redundant or wrong.
If you are building the surrounding infrastructure, our guide to reproducible reporting with Quarto, R Markdown and Bookdown covers how to publish these validation reports, and the self-hosted R Shiny deployment guide shows how to serve an interactive quality dashboard. Teams comparing cross-language options should read our Great Expectations vs Soda Core vs Deequ comparison and the data contract validation tooling roundup — the same architectural split (measure / encode / guard) reappears in every ecosystem.
FAQ
Can pointblank, validate and assertr be used together?
Yes, and mature teams often do: assertr at ingestion to prevent obviously broken frames, pointblank for the scheduled quality report, and validate for the rule catalogue that compliance reviews. Keep each role separate, otherwise you end up maintaining three copies of the same rule.
Which package works with database tables instead of data frames?
pointblank has the strongest story: through DBI/dbplyr backends it can validate tables in Postgres, MySQL, SQLite, BigQuery and Spark without materialising them locally. validate and assertr are data-frame oriented.
Does validate support rules loaded from a file?
Yes. validator(.file = "rules.yml") loads a rule set, and export_yaml() writes one. That round-trip is the main reason to choose validate over the other two when rules need review by people who do not write R.
Is assertr still maintained?
The package is stable and rOpenSci peer-reviewed, with its last repository push in April 2024 (484 stars). It does one job and does it correctly; the lack of recent commits reflects a finished design rather than abandonment. If you need active feature development, pointblank ships more frequently.
How many rules is too many?
If your suite has more than roughly 50 rules per table, split it by domain (orders, payments, customers) and run each set separately. A 200-rule validation whose report nobody reads is worse than 20 rules with named owners and action thresholds.
💰 想测试你的市场判断力?我用 Polymarket 做预测市场交易——这是全球最大的预测市场平台,从大选结果到技术监管时间线,什么都可以押注。和赌博不同,这是真正的信息市场:你懂的信息越多,胜率越高。我靠预测技术相关事件的走向已经赚了不少。用我的邀请链接注册:Polymarket.com