Batch case studies: DuPont batch PCA, SBR batch PLS, FMC multiblock batch PLS (#154, #155, #156) - #537
Merged
Conversation
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
… data The scores and loadings that the 2011 toolkit's unit test asserts, relabelled onto a (tag, sequence) layout, plus the script that regenerates them and writes the two-sheet workbook hosted on openmv.net. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
The urlopen-with-timeout helper moves from experiments.datasets into the private process_improve._remote_data module so the batch loaders can reuse it; read_remote_excel parses a workbook into one frame per sheet. Behaviour, timeouts and error messages are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
load_dupont, load_fmc and load_sbr download the openmv.net copies through the shared bounded reader, with a url override for mirrors and local copies. The FMC and SBR loaders return the multiblock layout (trajectories plus quality and initial-condition tables) as a Bunch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Forward the score, SPE and T2 plots, the limits and the three contribution functions to the inner PLS; expose predictions_ in quality units and r2_per_variable_ on the unfolded index; add predictions_vs_observed_plot aligned by batch id. Both batch classes gain unfold_and_scale, the public scaled matrix the contribution methods take, and BatchPCA gains score_contributions. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
…read any unfolded model The new plot draws one batch's whole contribution vector over the (tag, time) axis, one bar trace per tag with the tag names centred under their blocks, or summed into one bar per tag. Row validation is shared with contribution_at_time_plot. time_varying_loading_plot now accepts a BatchPLS (weights) or any multivariate model fitted on a dict_to_wide matrix, reading the tag and time order from the index when the model does not record them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
…test Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
…ive pages Three plain Python scripts under docs/user_guide/case_studies/batch, each a ladder of small functions that return the fitted models so tests can pin the numbers, with an RST page that quotes the script section by section and states what each section prints. DuPont: batch PCA and the exclude-and-rebuild outlier hunt (#155). SBR: batch PLS, the same injected fault at two onset times (#156). FMC: the eight-model multiblock ladder on the batch dryer, with the missing cells handled by the multivariate estimators (#154). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
The reference tests import the case-study scripts by path and assert on the objects their functions return, so the documented and tested numbers share one code path. TestFMCReference replaces the skipped placeholder. The SBR tests read the PROCESS_IMPROVE_SBR_URL override so they can run on a local copy of the workbook until it is hosted, and skip offline otherwise. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
…elog Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
CodeQL read the pytest.skip inside the except branch as an implicit fall-through; assigning first and returning at the end says the same thing with one explicit return. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
…t when the SBR fault appears CodeQL still read a pytest.skip call as a fall-through, so the download-or-skip helpers now raise pytest.skip.Exception explicitly. The loaders are exercised on local file:// copies of synthetic data, the openpyxl-missing branch and the plot figure-reuse branches are tested, and BatchPLS's initial-condition checks are covered. The SBR script reports per-fifth contribution shares and the first sample at which each tag leaves the band of the other batches, which shows the batch 34 fault appearing around samples 33 to 37, not midway. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
… page Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Share one forwarding helper between BatchPCA and BatchPLS; refuse a quality frame that lacks training batches in the parity plot; draw contiguous tag blocks in unfolded_contribution_plot for the (sequence, tag) layout; probe openpyxl before downloading a workbook; state the real DuPont tag order; qualify the r2_per_variable_ sentence for scale=False; use a Python 3.10 compatible timestamp in the fixture script; return the worst complete batch from the FMC script instead of recomputing it; make the DuPont and FMC end-to-end tests skip offline; tag the network tests slow; share the skip helper; restore the Simca-P placeholder that was dropped by mistake. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
…sing The "first sample more than 2 sd away" diagnostic on the SBR page reported samples 33 to 37 for the temperatures of batch 34, which are single noisy excursions; the temperatures and the energy released leave the band of the normal batches for good at samples 103 to 105, the middle of the batch, and conversion and latex density at 123 and 129. The helper now requires the tag to stay outside the band for twenty consecutive samples, the page quotes the resulting onsets, and the fault of batch 34 is described as starting midway, as the original course notes do. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
The NaN-padded scaled row of a running batch was built by hand in the mid-course corrector and, in a second form, in BatchPCA.predict_online_trace; the derivation of which unfolded column is an initial condition and which time sample each trajectory column belongs to existed in both as well. One private module, batch/_online.py, now holds those pieces and both callers use it, so the PLS mirror that follows is not a third copy. BatchPCA's online path is bitwise unchanged (checked against a saved baseline of scores, T2, SPE, condition numbers and monitor limits on the aligned nylon batches). predict_online gains three outputs the monitoring chapter needs: the per-cell residuals of the observed part, the SPE of the newest sample alone (the per-interval statistic of Nomikos and MacGregor), and the forecast of the remaining trajectories from the score estimate (Wold et al. 2009, Eq. 4), in engineering units. predict_online_trace gains the per-sample SPE. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
BatchPLS had no way to use a batch before it finished: unfold_and_scale rejects a truncated batch, and the missing-data projection the library already has for PCA was reachable for PLS only through the mid-course corrector. Three methods close that gap, mirroring BatchPCA. predict_online(batch, upto_k) scales the observed part of the batch, estimates the scores from those cells alone (trimmed score regression by default, or SCP or PMP) and maps them to a quality prediction in the original units; it also returns the residuals, the newest sample's SPE and the forecast of the rest of the batch. It accepts a truncated batch-so-far or a complete one. At upto_k equal to the batch length it reproduces predict() exactly, since the complete row takes the ordinary projection path. predict_online_trace(batch) does the same at every sample in one call, giving the evolving prediction of a batch as it would have looked in real time. online_rmse(X, Y) turns those traces into one error curve per quality attribute: the estimation error on the training batches, or the prediction error when the model was fitted without the batch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
BatchMonitor compared each sample's T2 against a limit built from the end-of-batch score covariance. Score estimates early in a batch are shrunk and noisy compared with those near its end, so that yardstick was wrong where it matters most: a normal SBR batch read T2 = 195 after four samples against a limit of 10. The T2 at sample k now uses the covariance of the reference batches' score estimates at that sample, with the F limit for the number of reference batches; the same normal batch reads 1.2. The reference batches' mean T2 is then A(N-1)/N at every sample, which the tests pin. The monitor also accepts a BatchPLS model, since both classes now expose predict_online_trace, and offers the SPE of the newest sample alone (spe_statistic="instantaneous") beside the cumulative statistic it charted before, which stays the default. The instantaneous statistic reacts in the sample a fault begins; on the SBR data it flags batch 34 after 105 samples, the cumulative one after 112. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Follow-ups from an adversarial read of the new on-line API. The reference score scatter in BatchMonitor is now taken about zero, the centre of the training scores, so the quadratic form and its normalisation agree and the reference batches' mean T2 is A (N - 1) / N at every sample; a rank check explains the case where fewer cells than components have been observed. observed_series matches tags by name rather than by position, rejects a batch that lacks a training tag, and handles the (sequence, tag) layout of group_by_batch=True. upto_k is coerced with operator.index; online_rmse rejects an empty X, a non-unique Y index, NaN quality rows and missing Z rows; the monitor forwards its ridge to every projection. The initial condition bookkeeping moved into its own helper to keep observed_series within the complexity budget. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
test_online_prediction.py checks exact identities on a thinned dryer set: predict_online agrees with predict at the end of a batch, a truncated batch-so-far equals the full batch cut at upto_k, the forecast reproduces the observed part, online_rmse ends at the fitted RMSE, both SPE statistics coincide at the first sample, and a model unfolded with group_by_batch=True projects the same batch to the same prediction, SPE, T2 and forecast. BatchMonitor is checked for the A (N - 1) / N mean-T2 invariant of a monitor fitted on its own training batches. The SBR case-study tests pin the first sustained alarm of each faulty batch (batch 37 on T2 after 23 samples, batch 34 on the instantaneous SPE after 105) and the reference batches' false-alarm rates. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Two sections on the SBR case-study page: the evolving prediction of the final quality from a batch-so-far (RMSEE per sample, and batch 4's particle size against its final prediction and its measured value), and the on-line monitoring of the two faulty batches against a reference model on the 51 normal batches, with the trajectory forecast of Wold et al. (2009). The companion script gains online_monitoring and the plotting helpers the tests call. The DuPont page's TempH-1 range now reports what the per-member contributions show on each component (-3.8 to +6.3 on t2, -3.1 to +2.1 on t3) instead of a range that reproduced on neither. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Minor bump for the new BatchPLS.predict_online, predict_online_trace and online_rmse methods, the residuals, spe_instantaneous and forecast keys on both batch models, BatchMonitor on BatchPLS with the spe_statistic option, and the per-sample T2 normalisation in BatchMonitor. CITATION.cff carries the same version and today's date. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
…low-up), keeping 1.80.0 Main took 1.79.0 for the select_n_components change (#539) and 1.79.1 for the cross-validation guide (#547) while this branch carried its own 1.79.0 entry for the case studies. The case-study entry is folded into this branch's 1.80.0 section, which now sits above main's 1.79.1, and the compare links follow. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
| """The fitted attributes a batch model must expose for the online helpers.""" | ||
|
|
||
| @property | ||
| def feature_columns_(self) -> pd.Index: ... |
| def feature_columns_(self) -> pd.Index: ... | ||
|
|
||
| @property | ||
| def center_(self) -> pd.Series: ... |
| def center_(self) -> pd.Series: ... | ||
|
|
||
| @property | ||
| def scale_(self) -> pd.Series: ... |
| def scale_(self) -> pd.Series: ... | ||
|
|
||
| @property | ||
| def tag_names_(self) -> Sequence[Hashable]: ... |
| def tag_names_(self) -> Sequence[Hashable]: ... | ||
|
|
||
| @property | ||
| def initial_condition_names_(self) -> Sequence[Hashable]: ... |
| def initial_condition_names_(self) -> Sequence[Hashable]: ... | ||
|
|
||
| @property | ||
| def n_initial_conditions_(self) -> int: ... |
| def n_initial_conditions_(self) -> int: ... | ||
|
|
||
| @property | ||
| def n_timesteps_(self) -> int: ... |
spe_window pools the reference SPE values of the samples on either side of sample k before that sample's chi-squared limit is fitted, the usual way to steady per-sample limits when few reference batches are available; the default of 0 keeps the per-sample fit and every existing limit. The mean trace is never pooled. On the SBR reference set the pooled limits leave every alarm sample unchanged: the unpooled limit moves by under 2% between neighbouring samples, so the reference batches' short alarm runs come from the autocorrelation of their SPE, not from a rough limit, and the case-study page says so. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Main took 1.80.0 for the consistency fixes surfaced by executing the book against the library, so this branch's section moves to 1.81.0, above main's 1.80.0 entry, with the compare links to match. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
The case-study script projects the 15 batches removed before model C onto it (the on-line projection at the last sample of a complete batch) and reports which lie above its 95% limits: all 15 above the SPE limit, the six score outliers and batch 37 above the T2 limit as well. The observability check prints one line instead of a table, the page's group-contribution passage is shorter and no longer repeats numbers the figure shows, and a test pins the projection. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
The SBR case-study page attributed the estimator to Garcia-Munoz, Kourti and MacGregor (2004), who compared it; the reference is added. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
…reading of the four anomalous batches The clock time at each aligned sample, which the workbook carries and the original study modelled, now enters the batch PCA, the batch PLS and the batch multiblock PLS beside the ten process tags, so the pace of each batch is part of what the models see. The pins move with it (batch PCA 0.231 / 0.376, batch PLS 0.266 / 0.410, batch MBPLS 0.370 / 0.472; the complete batch with the largest SPE is now 41), and the clock time joins the collector level as the leading contribution of batch 13. A new section places every batch, block by block, with the group whose average point is nearer in that block's score plot. Four batches classed good (2, 3, 6, 7) are placed with the abnormal batches by the trajectory block and with the good ones by both initial-condition blocks; the contribution from their nearest abnormal neighbours' average to theirs in the operating-condition block is carried by a longer cool-down, a shorter and steeper ramp and a shorter high-speed agitation, with the same peak set point. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
The simulated faults are the ones the 1994 AIChE paper describes: 30% more impurity from the start of one batch and 50% more from halfway through the other, not 30% in both; the feed flows and the feed temperature carry the simulation's noise rather than being constant. The per-sample T2 covariance is credited as the 1995 Technometrics paper states it (a need they note and set the individual score-chart limits by) and as Garcia-Munoz, Kourti and MacGregor (2004) compute it. The DuPont page says that the original reference set also left out the four poor-quality batches and had 36 batches with three components. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
spe_contributions, score_contributions and t2_contributions (and the PCA, PLS, BatchPCA and BatchPLS methods that forward to them) used to return an all-NaN row for any observation with a missing cell, because the scores were taken as X @ R. A batch with a gap in its record therefore had no contribution plot at all, even though NIPALS fitted it from its observed cells. The scores of an incomplete row are now estimated from its observed cells with the same missing-data operators as `project` (SCP by default, which reproduces the NIPALS fit; TSR and PMP available), and each contribution is that row-specific operator times the cell. Contributions are then defined at every observed cell and NaN only at the missing ones; complete rows take the exact complete-data path as before, so their results are bitwise unchanged. A row with no observed feature raises. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Batch 20 is the one batch above both limits of the batch PCA, and it is also one of the ten with missing samples, so its contribution plot could not be drawn and the page fell back to batch 41, the complete batch with the largest SPE. With contributions now defined for rows with missing cells, the script reports batch 20's share of the SPE per tag (half of it in the dryer pressure), the reference test pins that reading, and the page and changelog describe the change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
The attribute is documented as the cumulative per-variable R2 after each component (it replaced the old R2X_cum), and the complete-data path and PLS compute it that way. The NIPALS path, taken whenever X has missing cells, divided each column's residual by its sum of squares after the previous component instead of the original, so column a held the share of what was left that component a explained; only the first column was right. The TSR path never filled the attribute and returned zeros. Both now use the original column sums of squares. The FMC batch PCA of the case study exposed it: the collector-tank cells carry the largest first loading, and their second column read 0.07 where the cumulative value is 0.78. A test checks the three PCA paths and PLS against the direct definition. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Its missing cells are not a contiguous block of samples: five tags lack samples 95 to 109 and five more lack cells between samples 34 and 109. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Running every missing-data path on data without missing values showed one inconsistency: for a PLS model the single-component projection estimator projected each component onto the observed part of the loading vector, where NIPALS computes a score by projecting onto the weight vector and deflating with the loading. With nothing missing the SCP estimate differed from the model's own scores by up to a score standard deviation, and an incomplete row did not reproduce the score the fit had stored for it. PCA was unaffected (its weights are its loadings), and TSR, the default, reduces to the score map exactly. operator_for_pattern and project_rows take an optional x_weights, which PLS.project, projection_matrix, the contribution helpers and the mid-course corrector now pass. PMP is the least-squares fit onto the loading plane and, for PLS, does not reduce to the model's scores even with nothing missing; the docstrings say so. A new test module runs the NIPALS and TSR fit paths of PCA and the three score operators on complete data (the bundled dryer batches, the DuPont batches and the SBR batches) and checks each against the direct computation, and checks that SCP reproduces the NIPALS scores of incomplete rows for PLS as it already did for PCA. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
Every paper cited on the three case-study pages and in the projection module's references has an entry in the maintainer's literature database, so the links point there (the entries carry the DOI and an abstract) instead of at the DOI resolver, and the references that had no link get one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
… offline work only Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
docs/user_guide/case_studies/batch/. No notebooks: each script is a ladder of small functions that return the fitted models, and each page quotes the script section by section and states what it prints. The FMC script carries the clock time at each aligned sample as the eleventh trajectory of every batch model, as the original study did, and ends with a block-score reading of the four batches whose trajectories say off-specification while the product was on-specification.batch.load_dupont,batch.load_fmcandbatch.load_sbr(data served from openmv.net:polymerization.csv,batch-dryer.xlsxandsbr-batch-reactor.xlsx, all three live), a shared bounded-timeout readerprocess_improve._remote_datawith an Excel variant (the experiments loaders now use it, unchanged in behaviour), one plot helperbatch.unfolded_contribution_plot, and a small additive convenience layer:BatchPCA.score_contributions/unfold_and_scale, and onBatchPLSthe plot, limit and contribution forwards,predictions_,r2_per_variable_andpredictions_vs_observed_plot.time_varying_loading_plotnow also accepts aBatchPLSor any multivariate model fitted on adict_to_widematrix.BatchPLS:predict_online(scores, final-quality prediction, T2, cumulative and instantaneous SPE, residuals and the trajectory forecast of Wold et al. 2009 from a batch observed forupto_ksamples, using the missing-data score estimators the library already had),predict_online_trace(the same at every sample of a complete batch) andonline_rmse(the per-sample RMSE of the evolving prediction over a set of batches).BatchPCA.predict_onlinereturns the same new keys.BatchMonitornow accepts aBatchPLS, standardises Hotelling's T2 by the reference batches' score scatter at each sample (the time-varying covariance Nomikos and MacGregor 1995 note the T2 chart needs, computed as Garcia-Munoz, Kourti and MacGregor 2004 do; the fixed end-of-batch covariance gave a normal batch a T2 of 220 after four samples), takesspe_statistic="instantaneous"for the per-interval SPE, andspe_windowto pool the reference values of neighbouring samples into each per-sample SPE limit (default 0, the per-sample fit). The shared row-building and pattern-stacking code moved into a privatebatch/_online.py, with theBatchPCAtraces bitwise unchanged.spe_contributions,score_contributionsandt2_contributions(and thePCA,PLS,BatchPCAandBatchPLSmethods that forward to them) used to return an all-NaN row for any observation with a missing cell. The scores of such a row are now estimated from its observed cells with the same missing-data operators asproject(method="scp"by default, which reproduces the NIPALS fit;"tsr"and"pmp"available), and each contribution is that row-specific operator times the cell, so the contributions are defined at every observed cell and NaN only at the missing ones. Complete rows take the exact complete-data path and are bitwise unchanged; a row with no observed feature raises. The FMC page uses it for batch 20, the batch above both limits of the batch PCA, whose record has 205 missing cells.PCA.r2_per_variable_on the NIPALS path (taken wheneverXhas missing cells): it divided each column's residual by its sum of squares after the previous component, so columnaheld the share of what was left that componentaexplained rather than the cumulative R2 the docstring promises, and only the first column was right; the TSR path never filled the attribute and returned zeros. Found when the book's loading panels drew the R2 per cell (the collector-tank cells read 0.07 where the cumulative value is 0.78). A test checks the three PCA paths andPLSagainst the direct definition. The complete-data PCA path andPLSwere already correct."scp"score estimator for PLS models. Running every missing-data path on data without missing values showed that single-component projection projected onto the observed part of each loading vector, where NIPALS computes a score by projecting onto the weight vector and deflating with the loading: with nothing missing the SCP estimate differed from the model's own scores by up to a score standard deviation, and an incomplete row did not reproduce the score the fit had stored for it.operator_for_patternandproject_rowstake an optionalx_weights, passed byPLS.project,projection_matrix, the contribution helpers and the mid-course corrector; PCA is unchanged (its weights are its loadings) and TSR, the default, already reduced to the score map exactly. PMP is the least-squares fit onto the loading plane and, for PLS, does not reduce to the model's scores even with nothing missing; the docstrings now say so.tests/test_missing_data_paths_on_complete_data.pyruns the NIPALS and TSR fit paths of PCA and the three operators on complete data (the bundled dryer batches, the DuPont batches, the SBR batches) against the direct computation.tests/batch/test_case_studies.py), fills in the skippedTestFMCReferenceplaceholder, vendors the legacy MATLAB batch-PCA scores and loadings on the SBR data as a fixture (tests/fixtures/sbr_batch_pca/) with a sign-agnostic cross-check, and addstests/batch/test_online_prediction.py(exact identities of the on-line API on a thinned dryer set),tests/test_contributions_missing_cells.py(incomplete rows reproduce the stored diagnostics and theprojectestimates; complete rows bit-identical) andtests/test_r2_per_variable_cumulative.py, plus SBR alarm pins.Closes #154, closes #155, closes #156.
Reference numbers reproduced (two components, tag-major unfolding, MCUV scaling):
spe_window=2: 1.3%, batch 34 still at 105); particle-size RMSEE / sd falls from 2.84 after 10 samples to 0.60 at the end (leave-one-batch-out RMSEP 3.10 to 0.78)unit_tests.m)SBR-expected.matsign-agnosticallyprojecton complete rows equals the fit for every method; SCP reproduces the NIPALS scores of incomplete rows for PLS as it did for PCAConsistency with the Nomikos and MacGregor papers. The SBR pages now state the simulated faults as the 1994 AIChE paper describes them (30% more impurity from the start of one batch, 50% more from halfway through the other) and that the feed tags carry the simulation's noise rather than being constant; the per-sample T2 covariance is credited to the 1995 Technometrics paper as a need it states, and to the 2004 paper for computing it; the DuPont page says the original reference set also left out the four poor-quality batches (36 batches, three components).
SBR hosting. The workbook
sbr-batch-reactor.xlsx(sheetsX_batch,Y_quality, the layout ofbatch-dryer.xlsx) is produced bytests/fixtures/sbr_batch_pca/prepare_fixture.pyand is live athttps://openmv.net/file/sbr-batch-reactor.xlsx, byte-identical to the fixture's output, with its description athttps://openmv.net/info/sbr-batch-reactor.load_sbr()reads it in about two seconds, and the nine SBR reference tests, which skipped without aPROCESS_IMPROVE_SBR_URLoverride while the upload was pending, now run against it (the override stays for offline work).Companion pull requests. The book chapter is kgdunn/pid-book#273 (three pages in the Applications chapter, each figure preceded by the equivalent Plotly code, with the two on-line sections on the SBR page and the block-score section on the FMC page), and its matplotlib figures are kgdunn/figures#84 (merge the figures PR before the book PR). The figures repository's script checks fail on the three batch scripts until this PR is merged (they import
load_dupont,load_fmc,load_sbrandBatchMonitorfrom it) and, for the PyPI job, until a release carries it. The FMC loading-panel figure and the book's R2 echoes depend on ther2_per_variable_fix.Versioning note. Main took 1.79.0 (#539), 1.79.1 (#547) and 1.80.0 (#540) while this branch was open, so this branch's changelog section is 1.81.0, above main's 1.80.0. PR #541 also carries a 1.80.0 bump; if it merges before this one it needs a re-bump too.
Follow-ups, not in this PR. Converting the five existing case-study notebooks to the same script-plus-page form;
MCUVScalerandPLS(scale=True)dropping multi-level column labels (the scripts re-attachwide.columnsafter scaling).Test plan
uv run ruff check .anduv run ruff format --check .passuv run mypy src/process_improvepasses (163 files)tests/batch tests/test_multiblock_reference.pyafter each merge of main and after the clock-time change (299 passed), plus the coverage gateBatchPCAon-line traces compared bitwise with the pre-refactor baseline; complete-row contributions compared bitwise with the previous implementationuv run sphinx-build docs docs/_build/html -b html -Wsucceeds after this round (pandoc installed for the existing notebooks)Checklist
pyproject.toml(PATCH for fixes/docs/config, MINOR for new features): 1.80.0 to 1.81.0,CITATION.cffsyncedruff check .passesCHANGELOG.mdupdated🤖 Generated with Claude Code
https://claude.ai/code/session_01LXthGpHLQFfGubBiKFtGAE