Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
single stream, ADS 2, X/mR, sigma from N − 1 moving ranges. An NA response keeps its place as an
empty cell. The design report adds `Time: received order (no time column given)`. If the data
already has an `Observation` column, formulate asks you to pass it as `time=` or rename it (#129).
- **Phased limits on Xbar and S charts.** `execute(chart='Xbar', phased=True)` gives each factor
combination its own centre line and limits on one chart, drawn as stepped lines with a divider at
each change, the way phased X/mR already does for its lanes. Each phase is computed as a separate
Xbar/S analysis, exactly as `by=[time]` computes one chart per condition: the centre is the mean of
the phase's subgroup means, with its own S-bar and sigma-hat (10-12 manual Eqs 11-5, 11-8, 11-9,
11-16, 11-17). `by=['stage', time]` pools the other factors into the subgroup and phases on the
stage; the centre is then the mean of the pooled subgroup averages, as Dr. Bishop specified
(2026-10-18). On Wheeler's four-cavity thickness data the per-cavity centres and limits match
Minitab's staged Xbar-S chart. Residual values, calibrations and `by=[time]` are refused with
messages that say what to use instead; a phase with no replicated subgroup keeps its points without
limits and is named in `metadata['insufficient_phases']`. (#144)
- **`result.phase_statistics(chart, stratum=None)`.** One row per phase of a phased chart. Xbar/S:
`phase`, `subgroups`, `N`, `center`, `s_bar`, `sigma_hat`, `lpl`, `upl`. X/mR: `phase`, `points`,
`center`, `mR_bar`, `lpl`, `upl`, per stratum on a stratified chart (`stratum=`, or through
`focus()`). (#144)

### Changed
- **`formulate(response=..., factors=[])` with no time is the same study.** It used to treat the
Expand All @@ -23,6 +38,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Xbar and S chart data carry `n`.** `get_chart('Xbar')` / `get_chart('S')`, and the Excel chart
sheets, gain an `n` column after the subgroup label. Unstratified Xbar/S metadata names the label
column as `group_col` (#143).
- **Lane dividers on an Xbar or S chart are labelled with the phase.** These charts have no time
column and so no cell band under the axis, which is where X charts name their lanes. (#144)
- **Phased chart data and `chart_table` carry a `phase` column** (the lane label) after the subgroup
label, on X/mR as well as Xbar/S. Phased Xbar/S metadata carries `phase_by`, `lane_boundaries`,
`single_point_phases` and `insufficient_phases`; `n_avg` is not reported on a phased chart, whose
`n_mode='average'` is applied per phase, so the per-phase mean size is `phase_statistics(chart)['N']`.
(#144)

### Fixed
- **Xbar/S `chart_table` keeps the subgroup label and `n` on every `by=`.** With a `by=` other than
Expand All @@ -33,6 +55,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
(R1, R3, R4, R5) in a design with singletons: the stream's first cell, where R2 is undefined at
the first value, showed its cell size, not the one-fewer values its S and limits use. Limits,
centres and statistics are unchanged (#143).
- **`execute()` no longer documents `phased=True` as requiring `by=[]`.** Stratified phasing of X/mR
(`by=['factor 1'], phased=True`) has been accepted since 75bcdd1; the docstring never caught up. (#144)
- **The companion mR of a phased X chart reports `single_point_phases`.** Only the solo mR did. (#144)
- **X/mR lane order honours an ordered pandas Categorical factor.** A stage column with categories
`before < after` already ordered the Xbar/S subgroups that way, but the canonical row order sorted
the raw labels, so X/mR lanes, phases and `strata` came out `after, before`. The canonical sort now
uses an ordered Categorical's category codes; plain string and numeric factors sort as before. (#144)

## [0.4.0] - 2026-10-14

Expand Down
28 changes: 26 additions & 2 deletions docs/reference/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ result = study.execute(
recentered=False, # bool — recentered residual (RCR*) form
bins=None, # int | None — Histogram bins
companion=False, # bool — also compute the companion chart (see below)
phased=False, # bool — phase-wise limits
phased=False, # bool — a center line and limits per phase (X/mR and Xbar/S)
n_sigma=3.0, # float — limit width
n_mode='actual', # 'actual' | 'average' — subgroup-size handling
calibration=None, # Calibration | str | None — standards-given limits
Expand Down Expand Up @@ -276,7 +276,8 @@ Every chart's statistics dict carries exactly these keys:

When limits vary point-to-point (e.g. varying subgroup sizes with
`n_mode='actual'`), `N`, `lpl` and `upl` are `None` and the dict additionally has
`'limits_vary': True` — read per-point limits from `get_chart()`. Histogram adds
`'limits_vary': True` — read per-point limits from `get_chart()`. On a phased chart
`center` is `None` too. Histogram adds
`{'mean', 'std', 'n'}` and has `lpl`/`upl` of `None`. For a stratified result,
`get_statistics()` returns `{stratum: {...}}`; `statistics(chart, stratum=...)`
picks one.
Expand Down Expand Up @@ -326,6 +327,29 @@ result.focus(stratum) # -> FocusedAnalysisResult
`False`. (`FocusedAnalysisResult` is created by `focus()`, not constructed
directly, and is not a top-level export.)

### Phases

```python
result.phase_statistics(chart, stratum=None) # -> DataFrame, one row per phase
```

A chart executed with `phased=True` gives each phase — one factor combination, a
contiguous run of points in time order — its own center line and limits. The chart data
gains a `phase` column (the lane label) next to the subgroup label, and `chart_table`
shows it. `phase_statistics(chart)` returns the numbers behind the stepped lines. On
Xbar/S: `phase`, `subgroups`, `N` (the subgroup size the phase's limits assume; `NaN`
when sizes vary within it), `center`, `s_bar`, `sigma_hat`, `lpl`, `upl` (`NaN` when the
limits step within the phase). On X/mR: `phase`, `points`, `center`, `mR_bar`, `lpl`,
`upl`. A stratified phased X/mR chart (`by=['factor 1'], phased=True`) has phases per
stratum: pass `stratum=`, or call it on `focus(stratum)`. It raises `ValidationError` on a
chart that is not phased.

Phased metadata carries `phased=True`, `run_rules_applicable=False`, `phase_by` (the
factors a phase is one combination of), `lane_boundaries` (the divider positions),
`single_point_phases` and `insufficient_phases` (phases with no replicated subgroup,
charted without limits). Phased Xbar/S refuses residual values, calibrations and
`by=[time]`; see [Staged Xbar/S](../user-guide/chart-types.md#staged-xbars).

### Plotting, reporting, export

```python
Expand Down
51 changes: 51 additions & 0 deletions docs/user-guide/chart-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,51 @@ Charted](residuals.md#how-effect-residuals-are-charted).
2. **Then interpret Xbar** - Only meaningful if S is stable
3. **Investigate signals** - What makes that subgroup different?

### Staged Xbar/S

A staged study records the stage as a factor, and the default Xbar chart already lays the
subgroups out stage by stage: every subgroup of stage A, then every subgroup of stage B,
each in time order. `phased=True` gives each stage its own center line and limits on that
one chart, drawn as stepped lines with a divider at each stage change.

```python
study = pb.formulate(df, response='Thickness', time='Hour', factors=['Cavity'])
staged = study.execute(chart='Xbar', phased=True, companion=True)
staged.plot(chart='Xbar')
staged.phase_statistics('Xbar') # one row per stage: center, S-bar, sigma-hat, limits
```

Each stage is computed as a separate analysis, exactly as `by=['Hour']` computes one chart
per stage: its center is the unweighted mean of its subgroup means, its S̄ is the mean of its
subgroup standard deviations, and its σ̂ and limits follow from those (10-12 manual Eqs 11-5,
11-8, 11-9, 11-16, 11-17). Nothing is pooled across stages. On Wheeler's four-cavity
thickness data the per-cavity centers and limits match Minitab's staged Xbar-S chart to the
digit; the per-cavity charts of `by=['Hour']` give the same numbers, one chart at a time.

With more factors than the stage, `by=['Cavity', 'Hour']` pools the others into the
subgroup and phases on `Cavity`. The center is then the mean of the pooled subgroup
averages, per Dr. Bishop: ignore the other factor completely, group by stage and time, and
chart per the usual rules. That equals charting a study formulated without the pooled
factors.

What the phased chart reports:

- `get_chart('Xbar')` has a `phase` column and each row's own `center`, `lpl` and `upl`;
`chart_table` shows `phase` after `subgroup`.
- `get_statistics` returns `None` for `center`, `N`, `lpl` and `upl` with
`limits_vary=True`, since no single value describes the chart; `phase_statistics(chart)`
carries the per-stage numbers.
- `detect_signals` applies Rule 1 against each stage's own limits; the run rules are off,
as on phased X/mR, because the zones differ from stage to stage.
- A stage with a single replicated subgroup takes its σ̂ from that subgroup. A stage with
no replicated subgroup keeps its points but has no limits, and is named in
`metadata['insufficient_phases']`.

Stages are ordered as the factor's levels sort, so label them to sort (`1 before`,
`2 after`, or an ordered pandas Categorical). `by=[time]` with `phased=True` is refused:
that view is already one chart per stage. Residual values and calibrations are refused on
phased Xbar/S.

## Understanding X and mR Charts

### The X (Individual) Chart
Expand Down Expand Up @@ -314,6 +359,10 @@ phased = study.execute(chart='X', by=[], phased=True, companion=True)
phased.plot(chart='X')
```

The same view exists for subgrouped data: `study.execute(chart='Xbar', phased=True,
companion=True)` gives each factor combination its own center and limits on one Xbar/S
chart (see [Staged Xbar/S](#staged-xbars)).

**3. One subgroup at a time.** Full stratification: one chart per subgroup, each with
its own limits.

Expand Down Expand Up @@ -350,6 +399,7 @@ only the lane order, and reports which results move.
| "This transition is a signal" | `chart='mR', by=[]` | No. Which transitions are flagged depends on which lanes are adjacent. |
| "Subgroup *k* differs in level" | `chart='Xbar', by=['lane'], value='R5', recentered=True` | The points, yes: each is a subgroup mean. The limits, not entirely in DS 2 and 3: they are set from R2, which differences the condition-and-period-adjusted series along the same lane-major sequence. The level difference between lanes no longer enters R2, but which observations are paired still depends on lane order. In DS 1, R2 is within-cell and carries no order. |
| "Subgroup *k* is stable on its own" | `phased=True`, or `by=['lane']` | Yes. Only that subgroup's own ranges enter. |
| "Stage *k* is stable on its own" (subgrouped data) | `chart='Xbar', phased=True` | Yes. Only that stage's own subgroups enter its center, S̄ and limits. |

So a boundary signal should be read as evidence along the path, and claims about the
subgroups themselves should come from the charts that have no path: the R5 chart for
Expand Down Expand Up @@ -401,6 +451,7 @@ VAS residuals (R1–R6) need both factors and time.
|----------|-------|----------------|
| Is this one process? | `chart='X', by=[], companion=True` | Subgroups do not join into one stable stream |
| Each subgroup on its own? | `chart='X', by=[], phased=True` | Special cause within that subgroup |
| Each stage on its own, with subgroups? | `chart='Xbar', phased=True, companion=True` | Special cause within that stage |
| Are groups different? | `chart='Xbar'` | Group deviates from average |
| Is variation stable? | `chart='S'` | Group has unusual variation |
| Process over time? | `chart='X', by=[...]` | Special cause detected |
Expand Down
Loading
Loading