You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Repo-wide error audit: fix statistical and robustness bugs (#500)
* chore: start repo-wide error audit and fix series
Ruthless audit of statistical correctness and robustness across the
package. Fixes land as micro-commits on this branch; the PR description
carries the ranked findings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM4uAK1eM5YqBtpoLsSseM
* fix(multivariate): correct statistical errors found in the repo audit
- TSR PCA: the N <= K SVD branch returned an already-oriented loading
matrix which the shared transpose then mangled: an IndexError for
N < K and a silently wrong imputation regression for N == K. Fit
scores were also centred while transform() projects uncentred, so
scores_, SPE and R2 disagreed with transform on the same data; the
sign convention is now applied like the SVD/NIPALS paths, and the
EM loop is skipped entirely for complete data.
- Score-plot T2 ellipse: use the bivariate limit (2 degrees of
freedom) instead of the full model's A; the old ellipse was ~42
percent too wide per axis at A=5, N=50, hiding genuine outliers.
- spe_plot / t2_plot: compute the confidence limit at the plotted
component count instead of always the last component, and restore
the y-axis title (it previously showed the limit legend text).
- PCA.select_n_components: the 1-SE band compared total PRESS against
a per-fold standard error (~n_folds too narrow, degenerating the
1se rule to min); the Q2 null model now uses the centred sum of
squares instead of sum(x^2).
- PLS.select_n_components: same n_folds rescaling for the Q2 SE band.
- PLS.cross_validate: K-fold beta confidence intervals now use the
delete-a-block jackknife standard error (the plain sample SD was
(K-1)/sqrt(K) times too small); Q2 uses nanmean for the Y centre.
- PLS/PCA NIPALS: the max-iterations warning could never fire (itern
is capped AT the maximum); PCA previously had no warning at all.
- Target projection / selectivity ratio: the projection direction now
uses the scaled-space regression vector and maps X through the
model's own scaler; the raw-units beta_coefficients_ vector is not
a direction in the internal space when scale=True (the default).
- TPLS.diagnose: zero out missing cells after building the presence
maps, as fit() does; NaN * 0 is NaN, so one missing F/Z cell
previously poisoned that observation's scores, T2, SPE and
predictions.
- Hotelling's T2 in fit(): skip components with ~zero score variance
instead of dividing by ~zero (rank-deficient fits produced inf/NaN
T2 for every observation); validate n_components >= 1.
- MCUVScaler: a column with fewer than two observed values has NaN
nanstd which the ==0 guard missed, emitting an all-NaN column on
transform; non-finite centres/scales are now treated as constant.
- center()/scale(): axis=1 broadcast the row statistic across
columns (ValueError for rectangular input, silently wrong for
square); the statistic is now reshaped to a column vector.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM4uAK1eM5YqBtpoLsSseM
* fix(multivariate): satisfy mypy and CodeQL on the audit fixes
Use a dedicated DataFrame variable in _target_projection_arrays (mypy
union-attr), and hoist the ekf PRESS scale multiplier out of the branch
so CodeQL cannot see an uninitialized local.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM4uAK1eM5YqBtpoLsSseM
* fix(univariate, monitoring): correct statistical errors found in the repo audit
- Generalized ESD: the outlier count is the LARGEST i with R_i >
lambda_i (NIST/Rosner); the code took the first crossing, which
under-reports exactly in the masking scenarios the test exists for.
- Generalized ESD: robust_variant now defaults to False. The MAD-scaled
statistic has no upper bound while the critical values are derived
for the mean/std statistic (bounded by (N-1)/sqrt(N)), so the robust
variant declares outliers in clean data; it stays available as an
explicitly documented screening heuristic.
- Robust confidence interval (metrics and the agent tool): the interval
is for the median, whose asymptotic standard error is
sigma*sqrt(pi/2)/sqrt(n); the missing factor gave ~87% coverage for
a nominal 95% interval.
- variance_decomposition: between_stddev now reports the between-group
variance COMPONENT sqrt((MS_between - MS_within)/n0) instead of
sqrt(MS_between), which mixed the within-group noise into the
between number (the docstring example itself showed the wrong value).
- biweight_midvariance: use the midvariance tuning constant c = 9;
c = 6 is the biweight location constant and biased the scale low.
- Holt-Winters chart: the biweight rho conflated the consistency
constant with the cutoff k = 2.52, so every scale estimate was 12%
too small and the +/-3S limits were really +/-2.63 sigma (~3x the
nominal false-alarm rate). Warm-up residuals now subtract the trend
beta_0*t rather than the constant beta_0. The lambda grid search is
NaN-aware (row 0 has no error value, so for 10 <= N < 20 every grid
cell was NaN and (0.1, 0.1) always won silently). An explicit
ld_1=0.0 is respected instead of being treated as unset. Unknown
chart variants are rejected at construction with a clear message.
- The agent-facing control_chart tool no longer advertises a CUSUM
chart type that always failed with a misleading error; the package
docstring's CUSUM/EWMA claim is corrected too.
- calculate_cpk: rsd is now the relative standard deviation of the
data (spread over the data centre), not spread over the
distance-to-spec centre, which changed value when the spec moved;
the docstring documents that the overall-sigma statistic is
Ppk-style. The capability tool reports an undefined Cpk as 'could
not be computed' instead of 'Poor capability'.
- Residual diagnostics: p-values that underflow to exactly 0.0 are the
most significant result possible; use 'is not None' instead of
truthiness so they are no longer rendered as unavailable.
- Test suite: pins that encoded the pre-fix constants are updated with
derivations in comments; the registry test no longer depends on
sibling tests having run on the same xdist worker.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM4uAK1eM5YqBtpoLsSseM
* style: satisfy ruff on the univariate and monitoring audit fixes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM4uAK1eM5YqBtpoLsSseM
* docs: fix RST list structure in the variance_decomposition docstring
The Note block was never a real RST list (no blank line after the
heading), so the new multi-line bullet's continuation line failed the
strict Sphinx build. Promote it to a proper NumPy-style Notes section.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM4uAK1eM5YqBtpoLsSseM
* fix(experiments): correct DOE errors found in the repo audit
- Clear effects now follow Wu & Hamada: an effect is clear only when
every alias has order >= 3. The previous 'higher order than the
effect' rule declared every main effect of a resolution-III design
clear (A = BC has order 2 > 1), the exact case the concept exists to
flag.
- Explicit fractional-factorial generators: pyDOE3 returns columns as
(bases..., derived...) while the caller assigns positionally to the
factor list, so a generator on a non-last factor (B=AC) silently
swapped factor columns; raw factor names were also lower-cased into
pyDOE3's single-letter notation, so multi-character names were
misread as products of letters. Generators are now parsed against
the real factor names (same convention as evaluate._parse_word, but
raising on unparseable content), translated to canonical letters,
and the columns re-ordered back to the caller's factor order.
Negative generators (D=-ABC) are supported and inconsistent
generator sets are rejected with clear messages.
- Column.to_coded / to_realworld: an explicit center=0 (falsy) was
silently replaced by the stored pi_center; missing or zero-width
ranges now raise a clear ValueError instead of TypeError / silent
inf.
- gather(): positional arguments were accepted by the signature and
silently discarded; they are now folded in via their own column
names, and a nameless positional argument raises.
- D-optimal point exchange: the scorer de-duplicated the design before
computing |X'X| (replicated runs carry real information); an
improving swap onto the row with index label 0 was discarded by a
truthiness test; the shuffle now takes a random_state for
reproducibility.
- Lack-of-fit test: replicate groups are found on the MODEL's factor
columns with rounded numeric values. Grouping on the whole frame
meant the unique-per-row RunOrder column made every group a
singleton, so no generated design ever had detectable replicates
and the test always reported 'No replicated points'.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM4uAK1eM5YqBtpoLsSseM
* fix(regression, batch): correct numerical errors found in the repo audit
- Robust regression: the degenerate-x guard branch itself divided the
~zero x deviations by the ~zero x sum-of-squares, poisoning leverage
(and influence) with NaN/inf; with no variation in x the leverage is
exactly 1/N.
- DTW alignment: the reported 'distance' summed the CUMULATIVE cost
matrix entries along the warping path (a sum of prefix sums that
grows super-linearly with path length); the DTW distance is the
accumulated cost D[-1, -1]. The normalized distance and the
per-batch alignment-quality numbers inherit the fix.
- Kassidas batch alignment weights: a variable whose trajectories
align near-perfectly (SSQ ~ 0) must receive a LARGE weight (weights
are inversely proportional to the SSQ); the previous guard
substituted the scale-dependent magic value 10000 for a near-zero
SSQ, giving the best-aligned variables a weight of ~1e-4, the exact
opposite. The SSQ is now floored relative to the largest observed
SSQ.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM4uAK1eM5YqBtpoLsSseM
* fix(infra): config caching, JSON serialisation, tool discovery, pool thread-safety, CI hygiene
- Settings: the setdefault pattern evaluated the env read on every
access, so nothing was actually cached and a knob served successfully
could raise later if the env var went bad; a _get helper now caches
genuinely on first access. Numeric knobs must be positive, and
boolean env vars reject unrecognized values instead of silently
reading them as false (a typo in PROCESS_IMPROVE_MCP_SAFE_MODE
previously disabled the security-relevant safe mode with no error).
- clean(): handle np.bool_ (a subclass of neither np.integer nor
bool), numpy scalar dict keys (pandas groupby labels), sets, and the
remaining numpy scalar types via np.generic.item(); each previously
surfaced as a generic internal error at the MCP boundary.
- discover_tools: only tolerate a ModuleNotFoundError whose missing
module is third-party; a typo'd or renamed first-party module now
propagates instead of silently dropping a whole tool category with a
'missing dependency' warning.
- tool_safety: the module-level worker pool was created and torn down
with no lock while the MCP server calls tools from executor threads;
one thread's teardown could SIGKILL the worker running another
thread's task (mis-reported as a memory-limit kill) or leak an
orphaned worker. The default path now runs each call in a private
per-call pool (same cost as the old per-call recycling, no shared
state), and the remaining module-pool helpers are lock-guarded.
- tests/fuzz: the boundary fuzzer now excludes test-only tools
(leading underscore). Depending only on import order, the registry
snapshot could include test_tool_safety's deliberate infinite-loop
tool and the fuzzer then ran it in-process with no timeout,
hanging the run; this reproduced locally during this audit.
- raincloud: without the plotting extra, raise the documented
'install the extra' ImportError at the call site instead of an
AttributeError from the module stub.
- CI: drop the no-op create trigger (the create event ignores
branch/tag filters, so the full matrix ran on every branch
creation); grant run-tests contents:read only; move the Pages
deploy scopes off the docs build job, which executes PR code.
- Version 1.67.0: many of the audit fixes change numerical results
(and one default), so this is a MINOR bump. The changelog also gains
the missing 1.66.2 section: pyproject and CITATION already claimed
1.66.2 while its entries still sat under Unreleased, which would
have made the tag-gated release notes extraction silently fall back
to auto-generated notes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM4uAK1eM5YqBtpoLsSseM
* refactor(tool_safety): fold the pool globals into one state tuple
Addresses the CodeQL alerts on the previous commit: the split
_pool/_pool_memory_mb globals read as an unused variable to the
scanner, and the test-local module import mixed import styles. A
single _pool_state tuple is also harder to update inconsistently.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM4uAK1eM5YqBtpoLsSseM
---------
Co-authored-by: Claude <noreply@anthropic.com>
0 commit comments