cog_peer_compare() summary_* rows are per-category quantiles, not per-government totals, and this is undocumented #14
Closed
opened 2026-07-29 00:05:28 -04:00 by jared
·
2 comments
No Branch/Tag Specified
main
ci/mirror-canonical-tags
chore/release-47-badges-mirror-pr
docs/readme-perf-remeasure-56
feat/pagination-search-balances-57
feat/duckdb-threads-60
feat/cohort-predicates-58
fix/windows-backslash-paths
ci/mirror-to-github
ci/github-actions-matrix
feat/public-release-0.3.0
chore/fixture-sb203
ci/apt-https
fix/pushdown-pagination
feat/all-categories-37
fix/partial-coverage-signposting-9
fix/schema-v7
fix/cog-categories-balance-subtype
feat/cog-balances-25
feat/revenue-concepts-12
feat/expenditure-concepts-11
feat/coverage-disclosure-13
feat/complete-argument-18
fix/kodor-batch-14-15-16
fix/all-scoped-series-breaks-19
fix/regen-fixture-corpus-18
test/walkthrough-findings
feat/expenditure-concept
fix/3-url-trailing-slash
feat/phase-r3-signposting
fix/fixture-option-b-aggregates
feat/phase-r2-harmonization
feat/phase-r1-forward
feat/cog-gov-search-basket-mode
v0.4.0
Labels
Clear labels
kodor
kodor/feature-proposal
kodor/fix
kodor/needs-review
kodor/triaged
madison-walkthrough
severity/high
severity/low
severity/medium
south-guide
verdict/defect
verdict/definitional
kodor
kodor/feature-proposal
kodor/fix
kodor/needs-review
kodor/triaged
Kodor should process this issue
Kodor has written a feature proposal
Kodor should implement a fix (assigned to Kodor)
Kodor's work or failure needs Jared's review
Kodor has already triaged this issue (skip)
Surfaced while building the client-facing Southern API guide
needs
human
Cannot move without a person -- a decision, a check an agent cannot make, something outside the repo
origin
client
Came from a client ask
origin
obligation
Created by a change elsewhere
origin
review
Came from human review
origin
roborev
Promoted from a roborev finding
type
chore
Maintenance with no behaviour change
type
debt
Owed work -- docs, tests, cleanup a change obligated
type
decision
Needs a decision before work can proceed
type
defect
Something is wrong
type
feature
New capability
ws
api
Query verbs and results
ws
corpus
Corpus, mirror, provenance
ws
docs
Vignettes and guides
Assign a task to kodor
Kodor thinks this needs a feature.
Kodor should fix this
Kodor thinks the user is ready to review this.
Kodor is done with this issue.
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: Civilytics/uscogdata#14
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Filed from the Madison walkthrough audit (
cog_explorer/docs/walkthroughs/FINDINGS.md, 2026-07-28). Verdict: definitional — the function does exactly what its per-cell computation implies; the documented shape just isn't documented.Root cause
.peer_summary_rows()(R/peers.R) computesstats::quantile()separately inside each(year, spend_subtype, category)cell across the peer set. So asummary_p50row is "the median peer's value in that one category", not "the value of the median peer's total."cog_peer_compare()'s roxygen says the summary rows exist "so the result can be faceted byrolein a single ggplot call" and that they retain the same columns as everything else — includingspend_subtypeandcategory— but never states that the quantile is computed per cell, and never warns that the rows are not additive acrosscategory.Findings resolved
cog_peer_compare()'s built-insummary_p25/summary_p50/summary_p75rows are quantiles within each (year, spend_subtype, category) cell, not quantiles of each peer government's grand total — summing them naively misrepresents a "total spending" band by -33% to +251%Reproduction (verbatim from FINDINGS.md, verified against the live corpus)
The mechanism reproduces on the bundled fixture corpus (Madison, 10 peers,
category = NULL, FY2020, nominal per-capita): naivesum(summary_p50)= $6,180 against a correct per-government median of $2,043 — a +202% overstatement, the same direction and rough magnitude as the live corpus's post-FY2012 range.Why it matters
Wrong in every one of the 24 years tested (2000-2023): -20.5% to -32.7% before FY2012, then a sign flip to +189% to +251% from FY2012 onward. That exceeds the 7.1% understatement in the
direct-excludes-interest finding, which is itself rated high. And the trap is not exotic — it bites exactly the caller who wants a single "peer median total spending" line and infers from the column names alone that summing the summary rows gets it. The documented primary use case (faceting byroleandcategory) is internally consistent and unaffected; the failure is entirely in what a reader can reasonably infer from the return shape.Definition of done
cog_peer_compare()'s@returndocumentation states explicitly thatsummary_p25/summary_p50/summary_p75rows are quantiles computed within each(year, spend_subtype, category)cell — per-category quantiles — and are not additive acrosscategoryinto a total-spending quantile band.notesvalue on everysummary_*row — so the warning travels with the object rather than living only in?cog_peer_compare. The API surface needs exactly this; see the cross-reference.tests/testthat/test-peer-summary-scope.R→test_that("cog_peer_compare() documents that summary_* rows are per-category quantiles", ...). It asserts the rendered help (man/cog_peer_compare.Rd) contains a statement matching/per-category|not additive|within each/inext to thesummary_rows, and pins the mechanism numerically against the fixture (naive sum $6,180 vs. correct median $2,043 for Madison FY2020) so a future refactor that quietly changes the quantile grouping fails here. Remove theskip()on line 1 of the test body to activate.Cross-reference
cog-apicarries the same rows with the same silence, and rates it a defect there rather than definitional:/governments/{govid}/peer-comparisonrequirescategoryand accepts exactly one value, so a caller wanting a multi-category comparison has no option but to make N calls and combine per-category quantile rows — the API pushes every such caller straight into this trap. Tracked there as finding F-033.Severity: high. Verdict: definitional.
Taking this over. No kodor activity since assignment on 2026-07-29 — no branch, no PR, no comment — so I am picking it up rather than leaving it queued. Reassign back if kodor is simply slow and you would rather it land there; I will not push until this repo is otherwise quiet.
Being fixed together with the other two
kodor/fixissues in one PR: they are all single-file, all have committed acceptance tests, and two of them touch the same documentation surfaces.Fixed and merged in PR #22 (
8db944eonmain). Closing manually: that PR said "Closes #16, #15, #14", and Gitea only parsed the first reference, so #16 auto-closed while this one stayed open. The keyword needs repeating per issue.Verified present on
main, not just claimed.