process-improve 1.97.1__tar.gz → 1.97.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {process_improve-1.97.1 → process_improve-1.97.2}/PKG-INFO +2 -2
- {process_improve-1.97.1 → process_improve-1.97.2}/pyproject.toml +3 -3
- {process_improve-1.97.1 → process_improve-1.97.2}/pyproject.toml.orig +3 -3
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/_extras.py +34 -18
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/designs.py +4 -2
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/designs_response_surface.py +6 -2
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/monitoring/control_charts.py +214 -77
- {process_improve-1.97.1 → process_improve-1.97.2}/README.md +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/_linalg.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/_random.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/_remote_data.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/batch/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/batch/_batch_monitor.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/batch/_batch_pca.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/batch/_batch_plots.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/batch/_batch_pls.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/batch/_common.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/batch/_gaps.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/batch/_online.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/batch/alignment_helpers.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/batch/control.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/batch/data_input.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/batch/datasets.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/batch/features.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/batch/plotting.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/batch/preprocessing.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/batch/tools.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/bivariate/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/bivariate/_elbow_peak.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/bivariate/methods.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/bivariate/tools.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/chemistry/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/chemistry/preprocessing.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/config.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/batch/batch-fake-data.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/batch/details.txt +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/batch/dryer.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/batch/nylon.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/experiments/boilingpot.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/experiments/golf.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/experiments/pollutant.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/experiments/solar.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/experiments/test_doe1.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/monitoring/batch-yield-and-purity.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/monitoring/rubber-colour.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/LDPE/C.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/LDPE/Hotellings_T2_A3.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/LDPE/Hotellings_T2_A6.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/LDPE/LDPE.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/LDPE/P.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/LDPE/R.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/LDPE/T.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/LDPE/U.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/LDPE/W.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/LDPE/Yhat_A6.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/cheddar-cheese.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/dtu-pectin/NOTICE +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/dtu-pectin/extraction1.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/dtu-pectin/extraction2.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/dtu-pectin/extraction3.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/dtu-pectin/ftir1.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/dtu-pectin/ftir2.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/dtu-pectin/ftir3.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/kamyr.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/solvents.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/tablet-spectra.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/tpls-pyphi/SOURCE +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group1.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group2.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group3.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group4.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group5.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/tpls-pyphi/process_conditions.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/tpls-pyphi/properties_Group1.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/tpls-pyphi/properties_Group2.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/tpls-pyphi/properties_Group3.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/tpls-pyphi/properties_Group4.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/tpls-pyphi/properties_Group5.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/datasets/multivariate/tpls-pyphi/quality_indicators.csv +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_analyses/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_analyses/_shared.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_analyses/aliasing.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_analyses/box_cox.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_analyses/curvature.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_analyses/diagnostics.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_analyses/lack_of_fit.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_analyses/lenth.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_analyses/mixture.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_analyses/model_selection.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_analyses/ols_extractors.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_analyses/prediction.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_analyses/transforms.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_blocking.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_desirability.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_finite_fields.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_lm.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_moment_aberration.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_tools/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_tools/analyze_experiment.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_tools/augment_design.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_tools/create_factorial_design.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_tools/doe_knowledge.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_tools/evaluate_design.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_tools/fit_linear_model.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_tools/generate_design.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_tools/optimize_responses.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_tools/recommend_strategy.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_tools/trade_off_table.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_tools/visualize_doe.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/_uniform_sampling.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/analysis.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/augment.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/datasets.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/designs_constrained.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/designs_factorial.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/designs_mixture.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/designs_mixture_constrained.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/designs_omars.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/designs_omars_ilp.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/designs_optimal.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/designs_screening.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/designs_space_filling.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/designs_supersaturated.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/designs_utils.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/evaluate.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/factor.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/knowledge/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/knowledge/api.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/knowledge/data/concepts.yaml +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/knowledge/data/decision_rules.yaml +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/knowledge/data/design_types.yaml +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/knowledge/data/diagnostics.yaml +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/knowledge/engine.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/knowledge/models.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/models.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/omars.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/omars_trade_off.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/optimal.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/optimization.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/region.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/simulations.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/strategy/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/strategy/budget.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/strategy/domain_templates.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/strategy/engine.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/strategy/models.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/structures.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/tools.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/trade_off.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/visualization/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/visualization/api.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/visualization/plots/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/visualization/plots/cube_plot.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/visualization/plots/design_quality.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/visualization/plots/diagnostics.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/visualization/plots/effects.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/visualization/plots/optimization_plots.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/visualization/plots/registry.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/visualization/plots/significance.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/visualization/plots/square_plot.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/visualization/plots/surfaces.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/interactions.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/mcp_server.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/monitoring/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/monitoring/metrics.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/monitoring/tools.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_adaptive.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_asca.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_base.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_categorical.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_common.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_cv_criteria.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_diagnostics.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_impute.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_limits.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_mbpca.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_mbpls.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_nipals.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_null.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_opls.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_pca.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_pca_pls.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_pls.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_plsda.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_preprocessing.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_prm.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_procrustes.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_projection.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_resampling.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_robust.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/_tpls.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/methods.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/plots.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/multivariate/tools.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/py.typed +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/recipes.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/regression/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/regression/_robust_regression.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/regression/methods.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/regression/tools.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/sensory/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/sensory/analysis.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/sensory/designed.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/sensory/diagnostics.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/sensory/ingest.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/sensory/mam.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/sensory/panel.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/sensory/recipes.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/sensory/tools.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/sensory/validation.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/simulation/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/simulation/batch.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/simulation/context.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/simulation/latent.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/simulation/model.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/simulation/recipes.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/simulation/tools.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/tool_safety.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/tool_spec.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/univariate/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/univariate/metrics.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/univariate/tools.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/visualization/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/visualization/adapters/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/visualization/adapters/base.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/visualization/adapters/echarts_adapter.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/visualization/adapters/plotly_adapter.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/visualization/charts/__init__.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/visualization/charts/boxplot.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/visualization/colors.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/visualization/raincloud.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/visualization/spec.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/visualization/themes.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/visualization/tools.py +0 -0
- {process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/visualization/types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: process-improve
|
|
3
|
-
Version: 1.97.
|
|
3
|
+
Version: 1.97.2
|
|
4
4
|
Summary: Designed Experiments; Latent Variables (PCA, PLS, multivariate methods with missing data); Process Monitoring; Batch data analysis.
|
|
5
5
|
Keywords: Designed Experiments,Latent Variables,PCA,PLS,Multivariate Data Analysis,Batch data analysis
|
|
6
6
|
Author: Kevin Dunn
|
|
@@ -42,7 +42,7 @@ Requires-Dist: osqp>=1.0 ; extra == 'control'
|
|
|
42
42
|
Requires-Dist: coverage>=7.13.1 ; extra == 'dev'
|
|
43
43
|
Requires-Dist: hypothesis>=6.100 ; extra == 'dev'
|
|
44
44
|
Requires-Dist: matplotlib-stubs>=0.3.11 ; extra == 'dev'
|
|
45
|
-
Requires-Dist: mypy>=1.19.1,<2.
|
|
45
|
+
Requires-Dist: mypy>=1.19.1,<2.5 ; extra == 'dev'
|
|
46
46
|
Requires-Dist: pandas-stubs>=2.3.3.260113 ; extra == 'dev'
|
|
47
47
|
Requires-Dist: plotly-stubs>=0.0.6 ; extra == 'dev'
|
|
48
48
|
Requires-Dist: pre-commit>=4.5.1 ; extra == 'dev'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "process-improve"
|
|
3
|
-
version = "1.97.
|
|
3
|
+
version = "1.97.2"
|
|
4
4
|
description = "Designed Experiments; Latent Variables (PCA, PLS, multivariate methods with missing data); Process Monitoring; Batch data analysis."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -74,7 +74,7 @@ dev = [
|
|
|
74
74
|
"coverage>=7.13.1",
|
|
75
75
|
"hypothesis>=6.100",
|
|
76
76
|
"matplotlib-stubs>=0.3.11",
|
|
77
|
-
"mypy>=1.19.1,<2.
|
|
77
|
+
"mypy>=1.19.1,<2.5",
|
|
78
78
|
"pandas-stubs>=2.3.3.260113",
|
|
79
79
|
"plotly-stubs>=0.0.6",
|
|
80
80
|
"pre-commit>=4.5.1",
|
|
@@ -108,7 +108,7 @@ dev = [
|
|
|
108
108
|
"coverage>=7.13.1",
|
|
109
109
|
"hypothesis>=6.100",
|
|
110
110
|
"matplotlib-stubs>=0.3.11",
|
|
111
|
-
"mypy>=1.19.1,<2.
|
|
111
|
+
"mypy>=1.19.1,<2.5",
|
|
112
112
|
"pandas-stubs>=2.3.3.260113",
|
|
113
113
|
"plotly-stubs>=0.0.6",
|
|
114
114
|
"pre-commit>=4.5.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "process-improve"
|
|
3
|
-
version = "1.97.
|
|
3
|
+
version = "1.97.2"
|
|
4
4
|
description = 'Designed Experiments; Latent Variables (PCA, PLS, multivariate methods with missing data); Process Monitoring; Batch data analysis.'
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -122,7 +122,7 @@ dev = [
|
|
|
122
122
|
"coverage>=7.13.1",
|
|
123
123
|
"hypothesis>=6.100",
|
|
124
124
|
"matplotlib-stubs>=0.3.11",
|
|
125
|
-
"mypy>=1.19.1,<2.
|
|
125
|
+
"mypy>=1.19.1,<2.5",
|
|
126
126
|
"pandas-stubs>=2.3.3.260113",
|
|
127
127
|
"plotly-stubs>=0.0.6",
|
|
128
128
|
"pre-commit>=4.5.1",
|
|
@@ -157,7 +157,7 @@ dev = [
|
|
|
157
157
|
"coverage>=7.13.1",
|
|
158
158
|
"hypothesis>=6.100",
|
|
159
159
|
"matplotlib-stubs>=0.3.11",
|
|
160
|
-
"mypy>=1.19.1,<2.
|
|
160
|
+
"mypy>=1.19.1,<2.5",
|
|
161
161
|
"pandas-stubs>=2.3.3.260113",
|
|
162
162
|
"plotly-stubs>=0.0.6",
|
|
163
163
|
"pre-commit>=4.5.1",
|
|
@@ -58,11 +58,16 @@ def require_extra(missing: str, extra: str) -> ImportError:
|
|
|
58
58
|
class _MissingExtra:
|
|
59
59
|
"""Stand-in for an optional module that was not installed.
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
Using the stub in any way that needs the real package raises
|
|
62
|
+
:class:`ImportError` with the canonical "install the extra" message, so a
|
|
63
|
+
missing ``plotting`` extra fails the same way as a missing ``expt`` extra.
|
|
64
|
+
Attribute access returns a child stub that remembers the dotted path, so
|
|
65
|
+
``go.Figure(...)`` and ``go.layout.Template(...)`` raise ``ImportError`` at
|
|
66
|
+
the call, and indexing (``pio.templates[name]``) raises it too.
|
|
67
|
+
|
|
68
|
+
Dunder lookups (``__array__``, ``__deepcopy__``, ...) raise
|
|
69
|
+
:class:`AttributeError` instead, so protocol probes such as ``copy``,
|
|
70
|
+
``pickle`` and numpy see a missing attribute, per the Python data model.
|
|
66
71
|
|
|
67
72
|
Lets a module top-level write::
|
|
68
73
|
|
|
@@ -75,21 +80,32 @@ class _MissingExtra:
|
|
|
75
80
|
who never touch the optional surface.
|
|
76
81
|
"""
|
|
77
82
|
|
|
78
|
-
__slots__ = ("_extra", "_missing")
|
|
83
|
+
__slots__ = ("_extra", "_missing", "_path")
|
|
79
84
|
|
|
80
|
-
def __init__(self, missing: str, extra: str) -> None:
|
|
85
|
+
def __init__(self, missing: str, extra: str, path: str = "") -> None:
|
|
81
86
|
self._missing = missing
|
|
82
87
|
self._extra = extra
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
self._path = path
|
|
89
|
+
|
|
90
|
+
def _error(self) -> ImportError:
|
|
91
|
+
hint = f"\n(Attempted to use {self._path!r}.)" if self._path else ""
|
|
92
|
+
return ImportError(f"{_extra_message(self._missing, self._extra)}{hint}")
|
|
93
|
+
|
|
94
|
+
def __getattr__(self, name: str) -> _MissingExtra:
|
|
95
|
+
if name.startswith("__") and name.endswith("__"):
|
|
96
|
+
# __getattr__ must raise AttributeError for protocol lookups so
|
|
97
|
+
# ``hasattr`` and friends behave per the data model (CodeQL
|
|
98
|
+
# py/non-standard-exception-raised-in-special-method).
|
|
99
|
+
raise AttributeError(
|
|
100
|
+
f"{_extra_message(self._missing, self._extra)}\n(Attempted attribute access: {name!r}.)"
|
|
101
|
+
)
|
|
102
|
+
return _MissingExtra(self._missing, self._extra, f"{self._path}.{name}" if self._path else name)
|
|
90
103
|
|
|
91
104
|
def __call__(self, *_args: object, **_kwargs: object) -> object:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
raise
|
|
105
|
+
raise self._error()
|
|
106
|
+
|
|
107
|
+
def __getitem__(self, _key: object) -> object:
|
|
108
|
+
raise self._error()
|
|
109
|
+
|
|
110
|
+
def __setitem__(self, _key: object, _value: object) -> None:
|
|
111
|
+
raise self._error()
|
{process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/experiments/designs.py
RENAMED
|
@@ -678,7 +678,8 @@ def generate_design( # noqa: PLR0913
|
|
|
678
678
|
``Constraint(expression="3*T + 5*D <= 600")``; a dict of ``Constraint`` fields or
|
|
679
679
|
a bare expression string is accepted too. Honoured by the optimal
|
|
680
680
|
families (``"d_optimal"``, chosen automatically when constraints are given,
|
|
681
|
-
``"i_optimal"``, ``"a_optimal"
|
|
681
|
+
``"i_optimal"``, ``"a_optimal"``, ``"e_optimal"``, ``"g_optimal"`` and
|
|
682
|
+
``"k_optimal"``), whose runs are selected
|
|
682
683
|
from a candidate set of feasible points; by ``"mixture"`` (linear constraints
|
|
683
684
|
in the proportions); and by the ``"sobol"``, ``"halton"``, ``"maximin"`` and
|
|
684
685
|
``"maxpro"`` space-filling designs. Other design types do not enforce them and set
|
|
@@ -695,7 +696,8 @@ def generate_design( # noqa: PLR0913
|
|
|
695
696
|
Ignored by the classical (non-optimal) design families.
|
|
696
697
|
fixed_runs : pandas.DataFrame or None
|
|
697
698
|
Runs to hold fixed while the optimizer fills the rest (design augmentation), for the
|
|
698
|
-
optimal families only (``"d_optimal"``, ``"i_optimal"``, ``"a_optimal"``, ``"e_optimal"
|
|
699
|
+
optimal families only (``"d_optimal"``, ``"i_optimal"``, ``"a_optimal"``, ``"e_optimal"``,
|
|
700
|
+
``"g_optimal"``, ``"k_optimal"``). One row per
|
|
699
701
|
fixed run, one column per factor, in the same coding as the returned design: continuous
|
|
700
702
|
factors in coded ``[-1, 1]`` units, categorical factors as level labels.
|
|
701
703
|
The fixed runs occupy the first rows of the result and ``budget`` counts them, so
|
|
@@ -456,8 +456,12 @@ def dispatch_dsd(factors: list[Factor], budget: int | None = None) -> tuple[np.n
|
|
|
456
456
|
two zeros (one in each half of the foldover) become ``+z`` and ``-z``, and the
|
|
457
457
|
single centre run becomes two, with the categorical factors at ``+b`` and ``-b``.
|
|
458
458
|
The signs are chosen to maximise the determinant of the main-effects information
|
|
459
|
-
matrix. Every main effect stays orthogonal to every second-order effect
|
|
460
|
-
|
|
459
|
+
matrix. Every main effect stays orthogonal to every second-order effect, and the
|
|
460
|
+
continuous main effects stay orthogonal to each other. Resolving the zeros costs
|
|
461
|
+
some orthogonality between a categorical main effect and the other main effects:
|
|
462
|
+
it is slightly correlated with each continuous main effect (``|r|`` about 0.17 for
|
|
463
|
+
four continuous factors and one categorical factor in 14 runs) and with the other
|
|
464
|
+
categorical main effects.
|
|
461
465
|
|
|
462
466
|
A ``budget`` larger than the minimal design adds fake factors: the conference
|
|
463
467
|
matrix of the largest buildable order whose design fits the budget is used, and its
|
{process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/monitoring/control_charts.py
RENAMED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""Class for ControlChart: robust control charts with a balance between CUSUM and Shewhart properties."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from
|
|
4
|
+
from collections.abc import Mapping
|
|
5
|
+
from typing import Any, ClassVar
|
|
5
6
|
|
|
6
7
|
import numpy as np
|
|
7
8
|
import pandas as pd
|
|
@@ -18,6 +19,15 @@ logger = logging.getLogger(__name__)
|
|
|
18
19
|
#: (scipy.integrate.quad of rho_norm(z) * phi(z); E = 0.3061160, so
|
|
19
20
|
#: c_k = 3.266736). This makes E[rho(Z)] = 1, the condition for the scale
|
|
20
21
|
#: estimates built from rho to be consistent for sigma on Gaussian data.
|
|
22
|
+
#:
|
|
23
|
+
#: Departure from the paper, kept deliberately: equation (13) of Gelper, Fried and
|
|
24
|
+
#: Croux (2010, p 289) pairs the cutoff k = 2 with c_k = 2.52 ("for the common choice
|
|
25
|
+
#: of k = 2 we have c_k = 2.52"; numerically 1 / E[rho_norm(Z)] = 2.5153 for k = 2).
|
|
26
|
+
#: This module uses 2.52 as the cutoff instead, with the matching c_k above. Both pairs
|
|
27
|
+
#: give consistent scale estimates on Gaussian data, but this biweight saturates later
|
|
28
|
+
#: (at ``|x| > 2.52`` rather than ``|x| > 2``), so it down-weights large forecast errors
|
|
29
|
+
#: less than the paper's. The constants are left unchanged because existing results
|
|
30
|
+
#: and callers depend on them.
|
|
21
31
|
BIWEIGHT_RHO_CONSISTENCY = 3.266736
|
|
22
32
|
|
|
23
33
|
|
|
@@ -38,8 +48,9 @@ def rho(x: float, k: float = 2.52) -> float:
|
|
|
38
48
|
x : float
|
|
39
49
|
Value at which to evaluate the bi-weight rho function.
|
|
40
50
|
k : float, optional
|
|
41
|
-
Bi-weight cutoff. The default of 2.52 is
|
|
42
|
-
paper.
|
|
51
|
+
Bi-weight cutoff. The default of 2.52 is kept for compatibility. In the
|
|
52
|
+
paper (equation 13, p 289) 2.52 is instead the consistency constant c_k
|
|
53
|
+
for the cutoff k = 2; see the note on ``BIWEIGHT_RHO_CONSISTENCY``.
|
|
43
54
|
|
|
44
55
|
Returns
|
|
45
56
|
-------
|
|
@@ -55,12 +66,40 @@ def rho(x: float, k: float = 2.52) -> float:
|
|
|
55
66
|
return c_k if np.abs(x) > k else c_k * (1 - np.power(1 - np.power(x / k, 2), 3))
|
|
56
67
|
|
|
57
68
|
|
|
69
|
+
def _rho_array(x: np.ndarray | pd.Series, k: float = 2.52) -> np.ndarray:
|
|
70
|
+
"""
|
|
71
|
+
Evaluate the bi-weight :func:`rho` element-wise over an array.
|
|
72
|
+
|
|
73
|
+
Replaces ``np.vectorize(rho)``, which calls the scalar function once per element. The
|
|
74
|
+
polynomial branch is evaluated on ``x`` clipped to ``[-k, k]``: ``np.where`` evaluates both
|
|
75
|
+
branches, and an unclipped huge ``|x|`` would overflow in ``np.power`` and raise a
|
|
76
|
+
``RuntimeWarning`` from values that are then discarded. Saturated entries take ``c_k``
|
|
77
|
+
directly and NaN propagates, exactly as in :func:`rho`.
|
|
78
|
+
|
|
79
|
+
Parameters
|
|
80
|
+
----------
|
|
81
|
+
x : np.ndarray or pd.Series
|
|
82
|
+
Values at which to evaluate the bi-weight rho function.
|
|
83
|
+
k : float, optional
|
|
84
|
+
Bi-weight cutoff, as in :func:`rho`.
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
np.ndarray
|
|
89
|
+
``rho(x)`` for every element of ``x``, as float64.
|
|
90
|
+
"""
|
|
91
|
+
x = np.asarray(x, dtype=float)
|
|
92
|
+
c_k = BIWEIGHT_RHO_CONSISTENCY
|
|
93
|
+
inside = c_k * (1 - np.power(1 - np.power(np.clip(x, -k, k) / k, 2), 3))
|
|
94
|
+
return np.where(np.abs(x) > k, c_k, inside)
|
|
95
|
+
|
|
96
|
+
|
|
58
97
|
def _finite(values: pd.Series | np.ndarray) -> bool:
|
|
59
98
|
"""Return ``True`` if at least one entry of ``values`` is finite."""
|
|
60
99
|
return bool(np.isfinite(np.asarray(values, dtype=float)).any())
|
|
61
100
|
|
|
62
101
|
|
|
63
|
-
def _training_error_radicand(future_errors: pd.Series
|
|
102
|
+
def _training_error_radicand(future_errors: pd.Series) -> float:
|
|
64
103
|
"""
|
|
65
104
|
Return tau squared for the training-sample errors, or NaN when it is undefined.
|
|
66
105
|
|
|
@@ -74,8 +113,6 @@ def _training_error_radicand(future_errors: pd.Series, rho_func: np.vectorize) -
|
|
|
74
113
|
----------
|
|
75
114
|
future_errors : pd.Series
|
|
76
115
|
One-step-ahead errors over the training samples (those after the warm-up).
|
|
77
|
-
rho_func : np.vectorize
|
|
78
|
-
Element-wise bounded loss applied to the standardised errors.
|
|
79
116
|
|
|
80
117
|
Returns
|
|
81
118
|
-------
|
|
@@ -86,14 +123,17 @@ def _training_error_radicand(future_errors: pd.Series, rho_func: np.vectorize) -
|
|
|
86
123
|
if not _finite(future_errors):
|
|
87
124
|
return float("nan")
|
|
88
125
|
|
|
126
|
+
# s_T = 1.48 Med|r_t|, as printed below equation (16) of the paper: 1.48 is the Gaussian
|
|
127
|
+
# consistency factor of the median absolute error, 1 / Phi^-1(3/4) = 1.4826, rounded. The
|
|
128
|
+
# robust Shewhart chart below uses the unrounded value.
|
|
89
129
|
s_t_median_error = 1.48 * future_errors.abs().median()
|
|
90
130
|
if not np.isfinite(s_t_median_error) or s_t_median_error <= 0:
|
|
91
131
|
return float("nan")
|
|
92
132
|
|
|
93
|
-
return float(np.power(s_t_median_error, 2) * np.nanmean(
|
|
133
|
+
return float(np.power(s_t_median_error, 2) * np.nanmean(_rho_array(future_errors / s_t_median_error)))
|
|
94
134
|
|
|
95
135
|
|
|
96
|
-
def _tau_from_training_errors(future_errors: pd.Series
|
|
136
|
+
def _tau_from_training_errors(future_errors: pd.Series) -> float:
|
|
97
137
|
"""
|
|
98
138
|
Return the robust scale estimate (tau) for the training-sample errors.
|
|
99
139
|
|
|
@@ -108,8 +148,6 @@ def _tau_from_training_errors(future_errors: pd.Series, rho_func: np.vectorize)
|
|
|
108
148
|
----------
|
|
109
149
|
future_errors : pd.Series
|
|
110
150
|
One-step-ahead errors over the training samples (those after the warm-up).
|
|
111
|
-
rho_func : np.vectorize
|
|
112
|
-
Element-wise bounded loss applied to the standardised errors.
|
|
113
151
|
|
|
114
152
|
Returns
|
|
115
153
|
-------
|
|
@@ -123,16 +161,15 @@ def _tau_from_training_errors(future_errors: pd.Series, rho_func: np.vectorize)
|
|
|
123
161
|
or not positive (the errors carry no usable spread).
|
|
124
162
|
"""
|
|
125
163
|
if not _finite(future_errors):
|
|
126
|
-
#
|
|
127
|
-
#
|
|
164
|
+
# Every training error is NaN when the training period holds no finite observation
|
|
165
|
+
# to forecast against, or when the warm-up statistics themselves are not finite.
|
|
128
166
|
raise ValueError(
|
|
129
167
|
f"The control-chart scale estimate is undefined: none of the {future_errors.size} "
|
|
130
|
-
"training-sample errors is finite
|
|
131
|
-
"
|
|
132
|
-
"Supply a series without leading gaps, or pass an explicit positive 's'."
|
|
168
|
+
"training-sample errors is finite, so the series has no finite observation in its "
|
|
169
|
+
"training period to forecast against. Supply a series with more finite values."
|
|
133
170
|
)
|
|
134
171
|
|
|
135
|
-
resids = _training_error_radicand(future_errors
|
|
172
|
+
resids = _training_error_radicand(future_errors)
|
|
136
173
|
if not np.isfinite(resids) or resids <= 0:
|
|
137
174
|
raise ValueError(
|
|
138
175
|
f"The control-chart scale estimate is undefined (tau^2 = {resids}), so the control "
|
|
@@ -170,6 +207,105 @@ def psi(x: float, k: float = 2.0) -> float:
|
|
|
170
207
|
return x if abs(x) < k else k * np.sign(x)
|
|
171
208
|
|
|
172
209
|
|
|
210
|
+
def _holt_winters_recursion(
|
|
211
|
+
y: np.ndarray, warm_up: Mapping[str, Any], ld_1: float, ld_2: float, ld_s: float
|
|
212
|
+
) -> dict[str, np.ndarray]:
|
|
213
|
+
"""
|
|
214
|
+
Run the robust Holt-Winters recursion over ``y``, one time step per row.
|
|
215
|
+
|
|
216
|
+
Row ``i`` needs row ``i - 1``, so the recursion stays a loop, but over plain numpy arrays
|
|
217
|
+
that are read and filled in place, instead of several ``DataFrame`` lookups per row and a
|
|
218
|
+
``df.loc`` write of every row. The arithmetic is kept expression for expression, in the
|
|
219
|
+
same order, on the same ``np.float64`` scalars and through the same scalar :func:`rho` and
|
|
220
|
+
:func:`psi`, so the results are bit-for-bit those of the per-row pandas implementation it
|
|
221
|
+
replaces (pinned by ``tests/test_control_chart_hw_golden.py``).
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
----------
|
|
225
|
+
y : np.ndarray
|
|
226
|
+
The observations, as stored in ``ControlChart.df["y"]``.
|
|
227
|
+
warm_up : Mapping[str, Any]
|
|
228
|
+
``ControlChart.warm_up``. Row 0 is set from ``y_zero_robust`` (median of the warm-up
|
|
229
|
+
window) and the initial level ``alpha_0``, trend ``beta_0`` and strictly positive
|
|
230
|
+
scale ``sigma_0`` (p 290 of the paper).
|
|
231
|
+
ld_1 : float
|
|
232
|
+
Smoothing parameter for the level, lambda_1.
|
|
233
|
+
ld_2 : float
|
|
234
|
+
Smoothing parameter for the trend, lambda_2.
|
|
235
|
+
ld_s : float
|
|
236
|
+
Smoothing parameter for the scale, lambda_s.
|
|
237
|
+
|
|
238
|
+
Returns
|
|
239
|
+
-------
|
|
240
|
+
dict[str, np.ndarray]
|
|
241
|
+
Float64 arrays of length ``len(y)``, keyed by their ``ControlChart.df`` column:
|
|
242
|
+
``psi_input``, ``rho_input``, ``y_star``, ``alpha_hat``, ``beta_hat``, ``sigma_hat``
|
|
243
|
+
and ``error``. ``error[0]`` is NaN: row 0 has no one-step-ahead prediction.
|
|
244
|
+
"""
|
|
245
|
+
y_zero, alpha_0, beta_0, sigma_0 = (warm_up[key] for key in ("y_zero_robust", "alpha_0", "beta_0", "sigma_0"))
|
|
246
|
+
n = len(y)
|
|
247
|
+
psi_input, rho_input, y_star, alpha_hat, beta_hat, sigma_hat, error = (np.full(n, np.nan) for _ in range(7))
|
|
248
|
+
|
|
249
|
+
rho_input[0] = (y_zero - alpha_0 - beta_0) / sigma_0
|
|
250
|
+
psi_input[0] = rho_input[0]
|
|
251
|
+
y_star[0] = y_zero
|
|
252
|
+
alpha_hat[0] = alpha_0
|
|
253
|
+
beta_hat[0] = beta_0
|
|
254
|
+
sigma_hat[0] = sigma_0
|
|
255
|
+
|
|
256
|
+
for i in range(1, n):
|
|
257
|
+
# Cover the warm-up period, and the rest of the data set. We need that for the residual
|
|
258
|
+
# calculation later anyway. The previous state is read back from the float64 arrays,
|
|
259
|
+
# which yields the same np.float64 scalars the per-row DataFrame lookups returned.
|
|
260
|
+
alpha_prev, beta_prev, sigma_prev = alpha_hat[i - 1], beta_hat[i - 1], sigma_hat[i - 1]
|
|
261
|
+
|
|
262
|
+
# Error = observed - predicted. Predicted = one-step-ahead prediction
|
|
263
|
+
error_i = y[i] - (alpha_prev + beta_prev)
|
|
264
|
+
if np.isnan(error_i):
|
|
265
|
+
# If there is an error, replace it with the median of the last 10 error estimates
|
|
266
|
+
# or as many points as available, filtered to the finite ones so an all-NaN slice
|
|
267
|
+
# never reaches a median (which returns NaN but emits "Mean of empty slice"). (#557)
|
|
268
|
+
#
|
|
269
|
+
# Known limitation, kept for now: the imputed error is a median of ABSOLUTE errors,
|
|
270
|
+
# so it is never negative. Each missing row is treated as an observation above its
|
|
271
|
+
# prediction, which nudges the level up, and over a long gap the trend term
|
|
272
|
+
# compounds that into a drift (with lambdas 0.4 / 0.7, a 40-row gap in N(50, 2) noise
|
|
273
|
+
# took the level from about 49 to 550). Carrying the forecast forward for every
|
|
274
|
+
# missing row, as below, would remove the bias; later gaps keep the imputation so
|
|
275
|
+
# that their results do not change.
|
|
276
|
+
recent_errors = np.abs(error[max(i - 10, 0) : i])
|
|
277
|
+
recent_errors = recent_errors[np.isfinite(recent_errors)]
|
|
278
|
+
if not recent_errors.size:
|
|
279
|
+
# No finite error to impute from: a gap that starts at index 1, the first row
|
|
280
|
+
# with a one-step-ahead error. Imputing NaN used to spread through every later
|
|
281
|
+
# row and make the whole fit fail. Carry the forecast forward instead (the level
|
|
282
|
+
# follows the trend; trend and scale are held) and leave this row's error and
|
|
283
|
+
# cleaned value NaN, so nothing invented reaches the target or the scale.
|
|
284
|
+
alpha_hat[i], beta_hat[i], sigma_hat[i] = alpha_prev + beta_prev, beta_prev, sigma_prev
|
|
285
|
+
continue
|
|
286
|
+
error_i = float(np.median(recent_errors))
|
|
287
|
+
rho_i = error_i / sigma_prev
|
|
288
|
+
prior_variance = np.power(sigma_prev, 2)
|
|
289
|
+
sigma_i = np.sqrt(rho(rho_i) * ld_s * prior_variance + (1.0 - ld_s) * prior_variance)
|
|
290
|
+
psi_i = error_i / sigma_i
|
|
291
|
+
y_star_i = psi(psi_i) * sigma_i + alpha_prev + beta_prev
|
|
292
|
+
alpha_i = ld_1 * y_star_i + (1 - ld_1) * (alpha_prev + beta_prev)
|
|
293
|
+
beta_i = ld_2 * (alpha_i - alpha_prev) + (1 - ld_2) * beta_prev
|
|
294
|
+
|
|
295
|
+
psi_input[i], rho_input[i], y_star[i] = psi_i, rho_i, y_star_i
|
|
296
|
+
alpha_hat[i], beta_hat[i], sigma_hat[i], error[i] = alpha_i, beta_i, sigma_i, error_i
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
"psi_input": psi_input,
|
|
300
|
+
"rho_input": rho_input,
|
|
301
|
+
"y_star": y_star,
|
|
302
|
+
"alpha_hat": alpha_hat,
|
|
303
|
+
"beta_hat": beta_hat,
|
|
304
|
+
"sigma_hat": sigma_hat,
|
|
305
|
+
"error": error,
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
|
|
173
309
|
class ControlChart:
|
|
174
310
|
"""Create control chart instance objects."""
|
|
175
311
|
|
|
@@ -218,6 +354,21 @@ class ControlChart:
|
|
|
218
354
|
"CUSUM-style history with Shewhart behaviour.)"
|
|
219
355
|
)
|
|
220
356
|
|
|
357
|
+
self._reset_fit_state()
|
|
358
|
+
|
|
359
|
+
def _reset_fit_state(self) -> None:
|
|
360
|
+
"""
|
|
361
|
+
Forget everything an earlier fit set; only the constructor arguments survive.
|
|
362
|
+
|
|
363
|
+
``calculate_limits`` starts here, so a chart reused for a new series gives exactly
|
|
364
|
+
what a fresh chart would. It used to keep the previous series' ``target`` and ``s``
|
|
365
|
+
and treat them as given, keep its fitted lambdas instead of searching again, and
|
|
366
|
+
fail outright on a series of a different length.
|
|
367
|
+
"""
|
|
368
|
+
constructor_arguments = {"style": self.style, "variant": self.variant}
|
|
369
|
+
self.__dict__.clear()
|
|
370
|
+
self.__dict__.update(constructor_arguments)
|
|
371
|
+
|
|
221
372
|
# Will be calculated by the self.calculate_limits() function
|
|
222
373
|
self.target: float | None = None
|
|
223
374
|
self._given_target: float | None = None
|
|
@@ -261,7 +412,19 @@ class ControlChart:
|
|
|
261
412
|
`target` / `s` are ignored for that small-sample case.
|
|
262
413
|
Otherwise, if `target` and `s` are numeric, those values are used;
|
|
263
414
|
if not, they are estimated.
|
|
415
|
+
|
|
416
|
+
Missing values (NaN) in `y` are bridged by imputing a one-step-ahead error equal
|
|
417
|
+
to the median absolute error of the previous 10 rows. That error is never negative,
|
|
418
|
+
so a gap pulls the Holt-Winters level upward, increasingly so for long gaps; fill or
|
|
419
|
+
drop long runs of missing values before fitting. A gap that starts at index 1, the
|
|
420
|
+
first row with a one-step-ahead error, has no earlier error to impute from: those
|
|
421
|
+
rows carry the forecast forward instead (the level follows the trend, the trend and
|
|
422
|
+
scale are held), and their error and cleaned value stay NaN.
|
|
423
|
+
|
|
424
|
+
Every call starts afresh: nothing fitted by an earlier call on the same chart
|
|
425
|
+
(target, ``s``, lambdas, warm-up statistics) is carried into this one.
|
|
264
426
|
"""
|
|
427
|
+
self._reset_fit_state()
|
|
265
428
|
self._given_target = target
|
|
266
429
|
self._given_s = s
|
|
267
430
|
logger.debug(
|
|
@@ -285,7 +448,9 @@ class ControlChart:
|
|
|
285
448
|
self.df["y"] = y.ravel() if isinstance(y, np.ndarray) else pd.Series(y).values.ravel()
|
|
286
449
|
self.N = self.df.shape[0]
|
|
287
450
|
|
|
288
|
-
# Between M = 10 and 20 samples required to warm-up (calculate summary statistics)
|
|
451
|
+
# Between M = 10 and 20 samples required to warm-up (calculate summary statistics).
|
|
452
|
+
# The paper fixes its startup period at m = 10 (p 291); here it grows with N, to 10%
|
|
453
|
+
# of the series, capped at 20. Kept as is: existing results depend on it.
|
|
289
454
|
self.warm_up["M"] = self.warm_up_M = int(min(20, max(10, np.ceil(0.10 * self.N))))
|
|
290
455
|
|
|
291
456
|
if (self.warm_up_M > self.N) and self.variant.strip().lower() == "hw":
|
|
@@ -295,7 +460,7 @@ class ControlChart:
|
|
|
295
460
|
self.s = self._tau = self.df["y"].std()
|
|
296
461
|
self.df["y_star"] = self.df["y"].values
|
|
297
462
|
self.df["alpha_hat"] = self.target
|
|
298
|
-
self.df["beta_hat"] = 0
|
|
463
|
+
self.df["beta_hat"] = 0.0
|
|
299
464
|
self.df["sigma_hat"] = np.nan
|
|
300
465
|
self.df["error"] = np.nan
|
|
301
466
|
return
|
|
@@ -377,7 +542,6 @@ class ControlChart:
|
|
|
377
542
|
lambda_1 and lambda_2 values. This is done in a 5x5 grid in the code below.
|
|
378
543
|
"""
|
|
379
544
|
self.ld_s = ld_s = 0.2
|
|
380
|
-
rho_func = np.vectorize(rho)
|
|
381
545
|
|
|
382
546
|
# ``is not None``: an explicit ld_1=0.0 (or ld_2=0.0) is a legitimate
|
|
383
547
|
# user choice, but 0.0 is falsy and a plain truthiness test silently
|
|
@@ -398,9 +562,11 @@ class ControlChart:
|
|
|
398
562
|
ld_2_index = np.linspace(0.1, 0.9, num=5, endpoint=True)
|
|
399
563
|
residuals, _ = np.meshgrid(ld_1_index, ld_2_index)
|
|
400
564
|
|
|
565
|
+
# The warm-up statistics do not depend on the lambdas: estimate them once, not per cell.
|
|
566
|
+
self._holt_winters_warm_up_statistics()
|
|
401
567
|
for i, ld_1 in enumerate(ld_1_index):
|
|
402
568
|
for j, ld_2 in enumerate(ld_2_index):
|
|
403
|
-
self.
|
|
569
|
+
self._holt_winters_smooth(ld_1=ld_1, ld_2=ld_2, ld_s=ld_s)
|
|
404
570
|
|
|
405
571
|
# Apply equation 16 from the paper to the residuals in the 'training' period,
|
|
406
572
|
# that is the samples after the warm-up period. NaN-aware
|
|
@@ -414,7 +580,7 @@ class ControlChart:
|
|
|
414
580
|
# An unusable cell records NaN and cannot win the search; the
|
|
415
581
|
# `np.all(np.isnan(residuals))` check below still catches the case
|
|
416
582
|
# where every cell is unusable. (#557)
|
|
417
|
-
residuals[i, j] = _training_error_radicand(future_errors
|
|
583
|
+
residuals[i, j] = _training_error_radicand(future_errors)
|
|
418
584
|
|
|
419
585
|
if np.all(np.isnan(residuals)):
|
|
420
586
|
raise ValueError(
|
|
@@ -428,11 +594,11 @@ class ControlChart:
|
|
|
428
594
|
self.ld_1 = best_ld_1
|
|
429
595
|
self.ld_2 = best_ld_2
|
|
430
596
|
self._residuals_HW = residuals
|
|
431
|
-
self.
|
|
597
|
+
self._holt_winters_smooth(ld_1=best_ld_1, ld_2=best_ld_2, ld_s=ld_s)
|
|
432
598
|
|
|
433
599
|
# Common code for both branches of if-else above
|
|
434
600
|
future_errors = self.df["error"].iloc[np.asarray(self.train_samples, dtype=int)]
|
|
435
|
-
self._tau = _tau_from_training_errors(future_errors
|
|
601
|
+
self._tau = _tau_from_training_errors(future_errors)
|
|
436
602
|
if self.target is None:
|
|
437
603
|
# Estimate the target as the median of the y-star (cleaned) y-values
|
|
438
604
|
self.target = self.df["y_star"].median()
|
|
@@ -451,20 +617,34 @@ class ControlChart:
|
|
|
451
617
|
See paper: https://onlinelibrary.wiley.com/doi/abs/10.1002/for.1125.
|
|
452
618
|
|
|
453
619
|
Calculates the Holt-Winters fitting and control chart parameters, for given values of the
|
|
454
|
-
smoothing parameters lambda_1 (how
|
|
620
|
+
smoothing parameters lambda_1 (how much local history for the level is used, with values
|
|
455
621
|
approaching 1.0 implying that less history is used), and lambda_2 (history for the trend
|
|
456
622
|
that is used, with lambda_2 approaching 1.0 implying that historical data is less
|
|
457
623
|
interesting), and lambda_s, a similar parameter for the moving variance of the sequence.
|
|
458
624
|
|
|
459
625
|
lambda_1 = ld_1 = 0.5 (default): value must be between 0 <= ld_1 <= 1.0
|
|
460
626
|
lambda_2 = ld_2 = 0.8 (default): value must be between 0 <= ld_2 <= 1.0
|
|
461
|
-
lambda_s = ld_s = 0.2 (default): value must be between 0 <=
|
|
627
|
+
lambda_s = ld_s = 0.2 (default): value must be between 0 <= ld_s <= 1.0, based on values
|
|
462
628
|
used in the paper, recommended on page 291.
|
|
463
629
|
|
|
464
630
|
The ideal lambda values (ld_1, ld_2, ld_s) can be found from a grid search.
|
|
465
631
|
"""
|
|
466
|
-
|
|
467
|
-
|
|
632
|
+
self._holt_winters_warm_up_statistics()
|
|
633
|
+
self._holt_winters_smooth(ld_1=ld_1, ld_2=ld_2, ld_s=ld_s)
|
|
634
|
+
|
|
635
|
+
def _holt_winters_warm_up_statistics(self) -> None:
|
|
636
|
+
"""
|
|
637
|
+
Estimate the warm-up level, trend and scale (p 290 of the paper) into ``self.warm_up``.
|
|
638
|
+
|
|
639
|
+
They depend only on the warm-up window and on a given ``target`` / ``s``, not on the
|
|
640
|
+
smoothing lambdas, so the lambda grid search estimates them once instead of per cell.
|
|
641
|
+
|
|
642
|
+
Raises
|
|
643
|
+
------
|
|
644
|
+
ValueError
|
|
645
|
+
If the warm-up window has zero (or non-finite) variance.
|
|
646
|
+
"""
|
|
647
|
+
y_warm_up = self.df["y"].iloc[0 : self.warm_up_M]
|
|
468
648
|
self.warm_up["y_zero_robust"] = y_warm_up.median()
|
|
469
649
|
|
|
470
650
|
if isinstance(self.target, float):
|
|
@@ -513,53 +693,10 @@ class ControlChart:
|
|
|
513
693
|
"Supply more representative warm-up data, or pass a positive 's'."
|
|
514
694
|
)
|
|
515
695
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
df
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
df.loc[0, "sigma_hat"] = self.warm_up["sigma_0"]
|
|
524
|
-
|
|
525
|
-
for i in range(1, self.N):
|
|
526
|
-
# Cover the warm-up period, and the rest of the data set. We need that for the residual
|
|
527
|
-
# calculation later anyway.
|
|
528
|
-
|
|
529
|
-
# Error = observed - predicted. Predicted = one-step-ahead prediction
|
|
530
|
-
error_i = df["y"][i] - (df["alpha_hat"][i - 1] + df["beta_hat"][i - 1])
|
|
531
|
-
if np.isnan(error_i):
|
|
532
|
-
# If there is an error, replace it with the median of the last 10 error estimates
|
|
533
|
-
# or as many points as available. When the gap is at the very start of the series
|
|
534
|
-
# there is no finite history to take that median over, so `error_i` stays NaN and
|
|
535
|
-
# propagates through alpha_hat / beta_hat / sigma_hat for the rest of the
|
|
536
|
-
# recursion; `calculate_limits` raises on that downstream. Filter to the finite
|
|
537
|
-
# history explicitly rather than letting an all-NaN slice reach pandas' median,
|
|
538
|
-
# which returns NaN but emits "Mean of empty slice". (#557)
|
|
539
|
-
recent_errors = df["error"].iloc[max(i - 10, 0) : i].abs().to_numpy(dtype=float)
|
|
540
|
-
recent_errors = recent_errors[np.isfinite(recent_errors)]
|
|
541
|
-
error_i = float(np.median(recent_errors)) if recent_errors.size else np.nan
|
|
542
|
-
rho_i = error_i / df["sigma_hat"][i - 1]
|
|
543
|
-
prior_variance = np.power(df["sigma_hat"][i - 1], 2)
|
|
544
|
-
sigma_i = np.sqrt(rho(rho_i) * ld_s * prior_variance + (1.0 - ld_s) * prior_variance)
|
|
545
|
-
psi_i = error_i / sigma_i
|
|
546
|
-
y_star_i = psi(psi_i) * sigma_i + df["alpha_hat"][i - 1] + df["beta_hat"][i - 1]
|
|
547
|
-
alpha_i = ld_1 * y_star_i + (1 - ld_1) * (df["alpha_hat"][i - 1] + df["beta_hat"][i - 1])
|
|
548
|
-
beta_i = ld_2 * (alpha_i - df["alpha_hat"][i - 1]) + (1 - ld_2) * df["beta_hat"][i - 1]
|
|
549
|
-
df.loc[i] = [
|
|
550
|
-
df["y"][i],
|
|
551
|
-
psi_i,
|
|
552
|
-
rho_i,
|
|
553
|
-
y_star_i,
|
|
554
|
-
alpha_i,
|
|
555
|
-
beta_i,
|
|
556
|
-
sigma_i,
|
|
557
|
-
error_i,
|
|
558
|
-
]
|
|
559
|
-
|
|
560
|
-
# Checks: for algorithm debugging:
|
|
561
|
-
# future_errors = df["error"]
|
|
562
|
-
# S_T_median_error = 1.48 * future_errors.abs().median() # must handle NaNs!
|
|
563
|
-
# resids = np.power(S_T_median_error, 2) * \
|
|
564
|
-
# np.nanmean((future_errors / S_T_median_error).apply(rho))
|
|
565
|
-
# print(np.sqrt(max(0.0, resids)))
|
|
696
|
+
def _holt_winters_smooth(self, ld_1: float, ld_2: float, ld_s: float) -> None:
|
|
697
|
+
"""Run the Holt-Winters recursion from ``self.warm_up`` and write its columns into ``self.df``."""
|
|
698
|
+
df = self.df
|
|
699
|
+
columns = _holt_winters_recursion(df["y"].to_numpy(), self.warm_up, ld_1=ld_1, ld_2=ld_2, ld_s=ld_s)
|
|
700
|
+
# One write per column per fit; the "y" column, the column order and the index are untouched.
|
|
701
|
+
for name, values in columns.items():
|
|
702
|
+
df[name] = values
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{process_improve-1.97.1 → process_improve-1.97.2}/src/process_improve/batch/_batch_monitor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|