Practice setting up rule-based, automated data validation checks, using the pointblank package, applied to the World Development Indicators (WDI) data from the week 3 lecture. This should take about 30 minutes.
This is the same tidying used in lecture – you don’t need to change anything here, just run it to get wdi (long, one row per country/year/series) and wdi2016 (a single cross-section).
SP.DYN.LE00.IN: life expectancy at birth, total (years)
SP.POP.TOTL: population, total
EN.ATM.CO2E.PC: CO2 emissions (metric tons per capita) – note this series stops being reported after 2014, so it will be entirely missing in wdi2016.
🧩 Tasks
1. Skim the pointblank introduction. In your own words, what is an agent, and why is interrogate() a separate step from adding the validation checks?
SolutionSolution
An agent is an object that holds a plan of validation checks (a set of rules) for a particular table, built up with create_agent() and then piped through validation functions like col_vals_not_null(). Nothing is actually checked against the data until you call interrogate(), which runs every rule and records the results. Separating the two means you can build up and re-use a validation plan (e.g. save it, apply it to next month’s data refresh) without re-running it every time you add a step.
2. Create a pointblank agent for wdi2016, and add two structural checks: country_code is never missing (col_vals_not_null), and country_code is unique (one row per country) (rows_distinct). interrogate() and check whether all_passed().
Both checks pass: every row has a country_code, and there’s exactly one row per country in this cross-section.
3. Now add two plausibility checks: life expectancy (SP.DYN.LE00.IN) should be between 0 and 100 years (col_vals_between), and population (SP.POP.TOTL) should not be negative (col_vals_gte). interrogate() again. Does the console output say every step is “OK”? Does all_passed() agree? If not, why not? (use get_agent_report here)
SolutionSolution
Code
agent <-create_agent(tbl = wdi2016,tbl_name ="wdi2016",label ="WDI 2016 cross-section checks") |>col_vals_not_null(columns =vars(country_code)) |>rows_distinct(columns =vars(country_code)) |>col_vals_between(columns =vars(SP.DYN.LE00.IN), left =0, right =100) |>col_vals_gte(columns =vars(SP.POP.TOTL), value =0) |>interrogate()agent
The printed summary shows a ✔ “OK” for every step, but all_passed() returns FALSE. That’s because the console summary only flags steps that breach a warn/stop threshold (none are set here), while all_passed() asks whether every single row passed. Looking at the detailed report shows why:
The life expectancy check only passes for 91.7% of rows (18 of 217 countries have a missing value in 2016), and the population check passes for 99.5% (1 country missing). By default, pointblank counts a missing value as a failing unit for col_vals_between()/col_vals_gte(), since it can’t confirm the value satisfies the rule.
4. We already know from the lecture that this data has missingness, and haven’t decided yet whether to treat it as a problem here. Re-run the same two plausibility checks with na_pass = TRUE, and explain what that argument changes – and when you would, or wouldn’t, want to use it.
SolutionSolution
Code
agent <-create_agent(tbl = wdi2016,tbl_name ="wdi2016",label ="WDI 2016 cross-section checks") |>col_vals_not_null(columns =vars(country_code)) |>rows_distinct(columns =vars(country_code)) |>col_vals_between(columns =vars(SP.DYN.LE00.IN), left =0, right =100, na_pass =TRUE) |>col_vals_gte(columns =vars(SP.POP.TOTL), value =0, na_pass =TRUE) |>interrogate()all_passed(agent)
[1] TRUE
na_pass = TRUE tells pointblank to excuseNA values from the check – a missing value is neither counted as a pass nor a fail, it’s simply skipped, so it can no longer drag the pass rate down. Now all_passed() is TRUE.
Use na_pass = TRUE when missingness is already known about and accepted (e.g. you documented it during IDA, and it doesn’t itself indicate a data quality problem for this particular check). Leave it FALSE (the default) when you want validation to catch missingness – for example, if a future data refresh should always be complete, and any new NA appearing would be a red flag worth investigating.
5. (Bonus, if time permits) Add a rule for EN.ATM.CO2E.PC (CO2 emissions per capita should not be negative) to the agent, using na_pass = TRUE, and interrogate. Does it pass? Is that reassuring?
# A tibble: 1 × 5
i columns units n_pass f_pass
<int> <chr> <dbl> <dbl> <dbl>
1 1 EN.ATM.CO2E.PC 217 217 1
It passes 100% – but not reassuringly so. EN.ATM.CO2E.PC stops being reported after 2014, so every value in wdi2016 is NA, and with na_pass = TRUE all of them are excused rather than checked. A validation step that always “passes” because there’s nothing left to check isn’t actually telling you anything useful; it’s worth checking the coverage of a rule (how many non-missing values it actually evaluated), not just whether it passed.