Skip to content

Keep the subgroup label and n on every Xbar/S chart table (#143) - #148

Merged
cnicholas merged 1 commit into
mainfrom
fix/xbar-s-table-subgroup-n
Oct 18, 2026
Merged

cnicholas merged 1 commit into
mainfrom
fix/xbar-s-table-subgroup-n

Conversation

@cnicholas

Copy link
Copy Markdown
Owner

Closes #143

Summary

  • What: Xbar/S chart_table shows subgroup and n whatever the by=; Xbar/S chart data carries n.
  • Why: with a by= other than time alone (pooled subsets, all factors, factors without time, one factor) the table dropped both columns. A row couldn't be traced to its subgroup and stepped limits went unexplained. The app shows these tables directly.
  • Scope: the Xbar/S aggregation already counts each subgroup; it now keeps that n in the chart data instead of dropping it, and names its label column in metadata (group_col). The table reads both, and the per-cell re-join that could never size a pooled subgroup (_join_n_on_composite_group) is gone.

Contract / Invariants (must remain true)

  • Default behavior unchanged: limits, centres and statistics byte-identical on 104 Xbar/S requests over T100 PM SDS 1 and 3 (snapshot vs main)
  • Residuals unaffected: no residual or design-state change
  • Row/index alignment preserved: row count and order unchanged
  • Lane boundaries preserved: untouched
  • Output schema compatible: additive — Xbar/S chart data gains n; metadata gains group_col (CHANGELOG "Changed")

Behavior Changes (explicit)

  • New semantics:
    • get_chart('Xbar'/'S') has n after the subgroup label (after stratify_col, group_col on stratified charts); Excel chart sheets too
    • Every Xbar/S chart_table has subgroup, n; a pooled subgroup's n covers all its cells
    • n is the count the limits rest on. On residual charts whose limits come from R2 (R1, R3-R5) in a design with singletons, the stream's first cell now shows one fewer than its cell size, as its S and limits use (main showed the cell size there, which disagreed with its own limits; one row on PM SDS 3)

Key Design Decisions

  • n source: the aggregation's own count (Chris's call), not a re-join from per-cell sizes
  • Label column: declared by the chart (metadata['group_col']) rather than guessed from column names; stratified and X/mR tables keep the rsg fallback
  • NaN policy: n counts the limits column's non-missing values, as the aggregation always has
  • n_mode='average': n stays the actual sizes; the average remains in N / metadata['n_avg']

Risks & Mitigations

  • P0 risks: a limits change hiding behind the schema change
  • Mitigations: snapshot comparison against main (limits/stats identical, only n added); golden masters regenerated only after checking the diff was exactly the n column and group_col key

Tests

Added / Updated

  • TestXbarSSubgroupAndN (test_result_tabular.py) — 23 cases on T100: every by= variant has subgroup and an independently counted n (Xbar n >= 1, S n >= 2); solo S == companion S; default and stratified tables equal cell sizes; R5 n follows R2's count; n_mode='average' keeps actual sizes; n sits after the label in chart data
  • Golden masters for paired_xbar_s, residual_chart, stratified_balanced, stratified_uneven regenerated (n column + group_col only)

Regression Focus (must fail if broken)

  • 16 of the new cases fail on main's code; the 7 regression guards pass on both

Manual Verification

  • pytest tests/ — 2567 passed, 7 skipped
  • python validation/e2e_bishop_report.py — 1547 passed, 0 failed, 4 pending (14 runs); docs/reference/validation.md unchanged
  • App fast tests against the local library (PYTHONPATH) — 193 passed
  • Survey of by= variants on make_design(1): every Xbar/S table shows subgroup and n

Notes

  • Docs: chart_table docstring, api.md, CHANGELOG Fixed + Changed; stored outputs of the cells printing Xbar/S chart data refreshed (xbar-s-analysis cells 11, 12, 24, 25; quickstart cell 13)
  • Follow-ups (not in this PR): none

- **What:** Xbar/S `chart_table` shows `subgroup` and `n` whatever the `by=`; Xbar/S chart data carries `n`.
- **Why:** with a `by=` other than time alone (pooled subsets, all factors, factors without time, one factor) the table dropped both columns. A row couldn't be traced to its subgroup and stepped limits went unexplained. The app shows these tables directly.
- **Scope:** the Xbar/S aggregation already counts each subgroup; it now keeps that `n` in the chart data instead of dropping it, and names its label column in metadata (`group_col`). The table reads both, and the per-cell re-join that could never size a pooled subgroup (`_join_n_on_composite_group`) is gone.

- [x] **Default behavior unchanged:** limits, centres and statistics byte-identical on 104 Xbar/S requests over T100 PM SDS 1 and 3 (snapshot vs main)
- [x] **Residuals unaffected:** no residual or design-state change
- [x] **Row/index alignment preserved:** row count and order unchanged
- [x] **Lane boundaries preserved:** untouched
- [ ] **Output schema compatible:** additive — Xbar/S chart data gains `n`; metadata gains `group_col` (CHANGELOG "Changed")

- **New semantics:**
  - `get_chart('Xbar'/'S')` has `n` after the subgroup label (after `stratify_col`, `group_col` on stratified charts); Excel chart sheets too
  - Every Xbar/S `chart_table` has `subgroup, n`; a pooled subgroup's n covers all its cells
  - `n` is the count the limits rest on. On residual charts whose limits come from R2 (R1, R3-R5) in a design with singletons, the stream's first cell now shows one fewer than its cell size, as its S and limits use (main showed the cell size there, which disagreed with its own limits; one row on PM SDS 3)

- **n source:** the aggregation's own count (Chris's call), not a re-join from per-cell sizes
- **Label column:** declared by the chart (`metadata['group_col']`) rather than guessed from column names; stratified and X/mR tables keep the `rsg` fallback
- **NaN policy:** n counts the limits column's non-missing values, as the aggregation always has
- **n_mode='average':** `n` stays the actual sizes; the average remains in `N` / `metadata['n_avg']`

- **P0 risks:** a limits change hiding behind the schema change
- **Mitigations:** snapshot comparison against main (limits/stats identical, only `n` added); golden masters regenerated only after checking the diff was exactly the `n` column and `group_col` key

- [x] TestXbarSSubgroupAndN (test_result_tabular.py) — 23 cases on T100: every by= variant has subgroup and an independently counted n (Xbar n >= 1, S n >= 2); solo S == companion S; default and stratified tables equal cell sizes; R5 n follows R2's count; n_mode='average' keeps actual sizes; n sits after the label in chart data
- [x] Golden masters for paired_xbar_s, residual_chart, stratified_balanced, stratified_uneven regenerated (n column + group_col only)

- [x] 16 of the new cases fail on main's code; the 7 regression guards pass on both

- [x] `pytest tests/` — 2567 passed, 7 skipped
- [x] `python validation/e2e_bishop_report.py` — 1547 passed, 0 failed, 4 pending (14 runs); docs/reference/validation.md unchanged
- [x] App fast tests against the local library (PYTHONPATH) — 193 passed
- [x] Survey of by= variants on make_design(1): every Xbar/S table shows subgroup and n

- **Docs:** chart_table docstring, api.md, CHANGELOG Fixed + Changed; stored outputs of the cells printing Xbar/S chart data refreshed (xbar-s-analysis cells 11, 12, 24, 25; quickstart cell 13)
- **Follow-ups (not in this PR):** none
@cnicholas
cnicholas force-pushed the fix/xbar-s-table-subgroup-n branch from 7f32215 to 3e08ebb Compare October 18, 2026 22:21
@cnicholas
cnicholas merged commit f325f76 into main Oct 18, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pooled-subset Xbar/S chart_table drops the subgroup label and n

1 participant