Repository navigation
figures: pooled-mean column on the by-split heatmap, rows sorted by it - #262
Conversation
Jon (2026-10-20) asked for an aggregate column to sort the F1-by-split heatmap by. The column is the board's own pooled F1 (the headline number), read from the summary and asserted equal to the mean of the row's per-split cells. - Full-coverage legs (9/9 pooled cities) sort by it, descending; RampNet comes first by sorting, not by being pinned. - Partial-coverage legs form a separate group below a labelled rule, sorted by the mean over the pooled cities they ran, shown as e.g. "0.533 (5/9)". - The column sits at the right of the pooled block, framed and bold, before the gutter; group labels "Pooled US cities (nine)" / "Pooled mean" / "Held out" sit above the columns so the gutter reads as a boundary. - Same sequential ramp, values printed in every cell. by_split_rows() is the ordering logic, tested against the committed scoreboard.json. Only scoreboard_by_split.png changes; the other four figures regenerate byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s recorded as lost The partial-coverage group now sorts by pooled cities run, then by mean, so a one-city leg no longer sits above Opus 5 low's 8/9 (Jon, 2026-10-20). The gemini-3.1-pro-preview / gemini-3.6-flash manual_gold run did happen (2026-07-25 sweep, b148f71, counts kept in model_comparison.md) but its detections were searched for on 2026-10-20 on the Windows workstation, makelab2, klone and Jon's Mac and are not there. 0.568 / 0.540 are now marked historical; not re-run, by decision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jonfroehlich
left a comment
There was a problem hiding this comment.
Independent review (head 589a50c): Not ready, one factual fix
The code is sound. Verified:
scoreboard.py --checkexits 0.tests/test_scoreboard.py: 104 passed.- A fresh render of the figure is pixel-identical to the committed PNG.
- The pooled-mean tolerance has margin: the largest drift is 5.6e-7 against a 2e-6 tolerance.
- Edge cases render correctly: a 0/9 leg, a partial-only board, a full-only board, and Mask2Former forced to 9/9 (simulating #267).
- Nothing breaks when #267 or #259 regenerates the figure.
Blocker
docs/model_scoreboard.md, Gemini note. The note says "the sweep used klone'sckptpartition, so the likeliest home was purged scratch". The repo's own log contradicts this.model_comparison.md(manual_gold "Cost and runtime", from b148f71) says "Gemini needs no GPU at all — both models ran from a desktop against Vertex"; only the GPU legs used ckpt. The cache was therefore desktop-local, and the two desktops (the Windows workstation and the Mac) are both among the machines searched. Fix: cite that line and drop the scratch guess.
Should-fix
- The prose example
0.533 (5/9)will go stale. It is Mask2Former 1024, which #267 moves to full coverage. Use a leg that stays partial (0.568 (8/9)) or a generic form. - The new ordering test needs three changes:
mean_f1 == m["f1"]is tautological; drop it.- The
0.793pin is a data value that breaks whenever the pool changes; drop it or derive it from the board. - The None / 0-coverage path is untested. Add a synthetic-board test covering a partial leg with a higher mean but lower coverage, a 0/9 leg, and a tie in the full group, and check that
fig_by_splitrenders it.
Nits
- Coverage assert format. The assert hard-codes the
"n/N"coverage string format. Compare counts instead. - Fixed width. The figure width is fixed at 13 in. Scale it with the column count, as the height already scales with rows.
- Partial-only caption. On a partial-only board the caption says "not ranked against the rows above" when there are no rows above. Guard the clause.
- Held-out footer. It is still hard-coded and says "budapest" where the data says
budapest_district5. Generate it fromresult["held_out"]. [-0.0, width]→0.0.- A11y is fine. Every cell carries its number, and the frame and bold text mark the aggregate without using colour. One optional idea: give partial-row means a lighter fill.
Out of scope here, passed to #267: the fig_headline footer says "single-split legs", but the partial legs include 8/9 and 5/9 ones. And the scatter figures have no label nudge for Mask2Former 1024 once it goes full.
Fixes follow, then a re-review.
🤖 Generated with Claude Code — Opus 5.5, claude-opus-5-5
…d_out Fixes for the PR 262 review (5592994266): - model_scoreboard.md, "What is missing": the Gemini manual_gold cache was desktop-local. model_comparison.md (manual_gold "Cost and runtime", b148f71) says both Gemini models ran from a desktop against Vertex; only the GPU legs used ckpt. The klone-scratch guess is removed. Both desktops were searched. - Prose example 0.533 (5/9) -> 0.568 (8/9), a leg that stays partial. - Tests: dropped the tautological mean_f1 == f1 check and the 0.793 pin (RampNet's mean is now checked against the board's best full mean). Added a synthetic-board test: a partial leg with a higher mean but lower coverage sorts below a wider one, a 0/9 leg (f1 None) sorts last, a tie in the full group keeps board order, and fig_by_split renders the board and a partial-only board (Agg, tmp_path). Added a held-out footer test. - by_split_rows checks coverage by count (len(pooled_splits)), not the "n/N" string. - Figure width scales with the column count (3.0 + 0.59 in per column unit; 12.97 in at fourteen splits, as before). - The "not ranked against the rows above" clause appears only when there is a full-coverage group. - The dagger footer is generated from result["held_out"] (held_out_footer), so it names budapest_district5. - [-0.0, width] -> [0.0, width]. Only scoreboard_by_split.png changes on regeneration (the footer text). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review fixes pushed in fe76f90. Every finding in the review above is addressed. Checks:
Not done: the lighter fill for partial means. The fill encodes magnitude, so lightening it would misstate the number. Partial rows are already marked without colour, by their separate group and the coverage label. Next: a final independent re-review. 🤖 Generated with Claude Code — Opus 5.5, claude-opus-5-5 |
…ut note The synthetic board now lists rows out of order and has two one-city partial legs, so every ordering rule is exercised without the committed data. Drops the literal budapest_district5 pin (the loop covers it). The Gemini note says why a desktop search can come up empty: compare.py's cache lives inside the checkout it ran from. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Final re-review (head fe76f90): Ready to mergeEvery finding is verified as fixed:
Its three optional nits are applied in 10c93b4:
#267 also regenerates these figures. Whichever of the two merges second will merge 🤖 Generated with Claude Code — Opus 5.5, claude-opus-5-5 |
tests/test_scoreboard.py: both sides added tests at the same place; kept both. scoreboard_by_split.png regenerated from the merged board (y11x_tiles lands in the partial group at 0.667, 7/9). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#267 Conflicts, and how each was resolved: Generated artifacts (regenerated, never hand-merged): - analysis_out/scoreboard.json, docs/figures/scoreboard_by_split.png, docs/figures/scoreboard_f1.png, and the generated blocks of docs/model_scoreboard.md (partial table, by-split matrix) and docs/model_comparison.md (per-split "Also scored" lines): scoreboard.py. The board is 23 legs: 15 full-coverage (Vistas 1024 now among them, 0.519) and 8 partial (y11x_tiles 7/9 at 0.667, claude-opus-5 low 8/9, six one-split legs). The six y11x_tiles seed legs stay off_scoreboard. - analysis_out/eval_protocol_150/reproduction.json: eval_protocol_150.py. 233 scoreboard cells (main's 224 + the 9 Vistas cells), 23 legs, 2,330 fields; 30 benchmark_eval cells; 0 differing, 0 unchecked. - analysis_out/cascade_cost_35/summary.json: cascade_cost_35.py --summary. The gaps list now has both sides' "pair was not run" rows (Vistas 1024 on its new splits, and the seven y11x_tiles legs). Hand-written prose and tests, with counts recomputed from the regenerated board: - docs/model_scoreboard.md: "Eight legs have not run every pooled split - six of them only one" (main said nine with Vistas at 5/9; the PR said seven without y11x_tiles), in the section opener and the "What is missing" bullet. That bullet keeps the PR's wording (Vistas 1024 is in the headline) and main's y11x_tiles/seed-leg prose. The Vistas budapest sentence now quotes the four YOLO arms' 0.221-0.319, since y11x_tiles is at the top of that range. - docs/replication.md: 273 files, 31.3 MB (MiB, the doc's convention) in benchmark/model_detections/ (main's 264 + the 9 Vistas files). The breakdown table already sums to 273. - tests/test_scoreboard.py: kept both sides. The PR's test_the_vistas_parity_arm_is_pooled and test_partial_footer_counts_the_board, and main's by_split_rows/held_out_footer tests and test_seed_legs_are_named_but_not_scored. The coverage map had already auto-merged correctly (y11x_tiles 7/9, no Vistas 1024). Stale counts outside the conflicts, fixed the same way: - docs/eval_protocol_150.md: 224 -> 233 cells, 2,240 -> 2,330 fields, and the PR's update note says 233 (was 224) with 23 legs (it said 223 and 22). - docs/README.md: twenty-two -> twenty-three model legs. - docs/claude_newest_ids.md: Opus 5.5 is no longer "the only" full-coverage model besides the open detectors with pooled recall above RampNet's. Vistas 1024 (0.836) joined the pool. "Highest pooled recall apart from the two open detectors" in model_scoreboard.md still holds. - docs/vistas_transfer_126.md: the YOLO budapest range is 0.221-0.319. #262's Gemini manual_gold note in model_scoreboard.md is unchanged. Checks: scoreboard.py --check, eval_protocol_150.py --check, pytest (3360 passed, 7 skipped), check_all.py --ci (22 passed, 3 skipped). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
eval_protocol_150.md: keep both dated notes; the laurens_gsv note quotes the post-merge counts (224 cells, 13 op_cache cells). All checks re-run clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stacked on #261. Base is
claude-newest-ids; merge this after #261. (When #261 merges, GitHub retargets this tomain.)Jon asked (2026-10-20) for an aggregate column on the "F1 by model and split" heatmap (
docs/figures/scoreboard_by_split.png) that the whole table is sorted by.What changed
by_split_rows()asserts that it equals the mean of the row's per-split cells (tolerance 2e-6, which covers the six-place rounding inscoreboard.json). It prints three decimals to match the headline. Cells keep two.0.533 (5/9). So a one-city leg is never ranked against a nine-city mean.Row order, full group: RampNet 0.793, Claude Opus 5.5 0.712, Claude Fable 5.1 0.607, YOLO11l 0.583, Gemini 3.1 Pro 0.571, YOLO11x 0.551, YOLO26 0.547, Gemini 3.7 Flash 0.538, Gemini 3.6 Flash 0.525, Molmo2-8B 0.422, Qwen3-VL-32B 0.322, Qwen3-VL-8B 0.314, OWLv2 0.065, Grounding DINO 0.053.
Partial group: Claude Fable 5 0.611 (1/9), Claude Opus 5 low 0.568 (8/9), Mask2Former 1024 0.533 (5/9), Claude Opus 5 high 0.520 (1/9), Mask2Former curb cut 0.517 (1/9), Claude Sonnet 5 low 0.463 (1/9), Claude Sonnet 5 high 0.456 (1/9), Mask2Former +curb 0.210 (1/9).
Only the heatmap changes. The other four scoreboard figures regenerate byte-identical, and the markdown by-split table keeps its class-grouped order.
docs/model_scoreboard.mdgets a paragraph under the figure that explains the different row order.Tests
test_by_split_heatmap_rows_sort_by_pooled_mean_in_two_groupschecks the grouping, the sort within each group, that the aggregate equals the board's pooled F1, and that RampNet is first (0.793). It reads the committedanalysis_out/scoreboard.jsonand is CPU-only.test_by_split_rows_rejects_an_aggregate_that_disagrees_with_its_cellschecks that a pooled F1 that drifts from its cells fails the assert.python -m pytest -q: 3354 passed, 7 skipped.scripts/check_all.py --ci: 22 passed, 0 failed, 3 skipped.scoreboard.py --check: current.To regenerate:
python scripts/analysis/scoreboard.py🤖 Generated with Claude Code — Opus 5.5, claude-opus-5-5