processbehavior 0.5.0__tar.gz → 0.6.0__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.
- {processbehavior-0.5.0 → processbehavior-0.6.0}/CHANGELOG.md +110 -1
- {processbehavior-0.5.0 → processbehavior-0.6.0}/CITATION.cff +1 -1
- {processbehavior-0.5.0 → processbehavior-0.6.0}/CONTRIBUTING.md +16 -8
- {processbehavior-0.5.0 → processbehavior-0.6.0}/PKG-INFO +9 -4
- {processbehavior-0.5.0 → processbehavior-0.6.0}/README.md +1 -1
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/__init__.py +1 -1
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/analysis.py +17 -17
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/analysis_dataset.py +7 -6
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/analysis_result.py +5 -4
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/capability.py +42 -14
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/exporters/excel_exporter.py +32 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/loss_function.py +96 -23
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/maximum_information.py +5 -5
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/plotting/effects_charts.py +3 -2
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/plotting/renderers.py +1 -1
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/process_behavior.py +98 -19
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/residual_calculator.py +21 -21
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/sds_detector.py +24 -11
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/signals/config.py +8 -5
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/signals/detector.py +1 -1
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/signals/detectors.py +17 -14
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/spc_constants.py +23 -23
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/study.py +58 -7
- {processbehavior-0.5.0 → processbehavior-0.6.0}/pyproject.toml +7 -5
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_ads3_charts.py +1 -1
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_capability.py +2 -2
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_capability_window.py +23 -0
- processbehavior-0.6.0/tests/test_design_report_diagnostics.py +87 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_documented_rule_coverage.py +19 -2
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_effects_plotting.py +12 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_excel_export.py +38 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_garbage_character_handling.py +57 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_loss_function.py +8 -8
- processbehavior-0.6.0/tests/test_loss_reference.py +79 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_maximum_information.py +1 -1
- processbehavior-0.6.0/tests/test_nested_in_time.py +94 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_phased_xbar_s.py +3 -3
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_r2_unavailable.py +1 -1
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_r6_effect.py +2 -2
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_recentering.py +1 -1
- processbehavior-0.6.0/tests/test_refresh_notebooks.py +104 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_residual_calculator.py +3 -3
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_sds.py +3 -3
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_sds_detector.py +2 -2
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_signal_detectors.py +40 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_signals.py +1 -1
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_spc_constants.py +1 -1
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_unequal_subgroups.py +5 -5
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_vas_chart_constants.py +3 -3
- processbehavior-0.5.0/examples/README.md +0 -10
- processbehavior-0.5.0/tests/test_bishop_reference.py +0 -82
- {processbehavior-0.5.0 → processbehavior-0.6.0}/.gitignore +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/CODE_OF_CONDUCT.md +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/LICENSE +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/SECURITY.md +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/calibration.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/data_preparation.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/datasets/__init__.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/datasets/data/coffee_shop_demo_long.csv +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/datasets/loaders.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/datasets/synthetic.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/derivations.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/effects_calculator.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/exceptions.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/exporters/__init__.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/focused_result.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/formulation_spec.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/plotting/__init__.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/plotting/_math.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/plotting/capability_chart.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/plotting/contracts.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/plotting/control_chart.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/plotting/lane_boundaries.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/plotting/limits.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/plotting/maximum_information_chart.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/plotting/plotter.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/plotting/report.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/plotting/residuals.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/plotting/run_rules_viz.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/plotting/stats_box.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/plotting/themes.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/plotting/x_axis_layout.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/plotting/zones.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/py.typed +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/result_signals.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/result_tabular.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/series_length.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/signals/__init__.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/signals/result.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/processbehavior/types.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/__init__.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/conftest.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/bishop_analyses/aco_medicare.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/bishop_analyses/known_effects_sds_1.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/bishop_analyses/pm_inert_sds_1.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/bishop_analyses/pm_inert_sds_2.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/bishop_analyses/pm_inert_sds_3.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/bishop_analyses/pm_inert_sds_4.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/bishop_analyses/pm_inert_sds_5.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/bishop_analyses/pm_inert_sds_6.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/bishop_analyses/pm_sds_1.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/bishop_analyses/pm_sds_2.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/bishop_analyses/pm_sds_3.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/bishop_analyses/pm_sds_4.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/bishop_analyses/pm_sds_5.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/bishop_analyses/pm_sds_6.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/coffee_capability_full_baseline.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/data/FILLWEIGHTDATA_800.csv +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/data/coffee_shop_demo_long.csv +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/data/wheeler_cavity_thickness.csv +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/missing_values/X_data.parquet +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/missing_values/X_metadata.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/missing_values/X_statistics.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/missing_values/mR_data.parquet +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/missing_values/mR_metadata.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/missing_values/mR_statistics.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/paired_xbar_s/S_data.parquet +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/paired_xbar_s/S_metadata.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/paired_xbar_s/S_statistics.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/paired_xbar_s/Xbar_data.parquet +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/paired_xbar_s/Xbar_metadata.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/paired_xbar_s/Xbar_statistics.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/paired_xmr_r/X_data.parquet +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/paired_xmr_r/X_metadata.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/paired_xmr_r/X_statistics.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/paired_xmr_r/mR_data.parquet +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/paired_xmr_r/mR_metadata.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/paired_xmr_r/mR_statistics.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/pathological_ordering/X_data.parquet +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/pathological_ordering/X_metadata.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/pathological_ordering/X_statistics.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/pathological_ordering/mR_data.parquet +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/pathological_ordering/mR_metadata.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/pathological_ordering/mR_statistics.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/phased_xbar_s/S_data.parquet +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/phased_xbar_s/S_metadata.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/phased_xbar_s/S_statistics.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/phased_xbar_s/Xbar_data.parquet +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/phased_xbar_s/Xbar_metadata.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/phased_xbar_s/Xbar_statistics.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/residual_chart/S_data.parquet +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/residual_chart/S_metadata.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/residual_chart/S_statistics.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/single_obs_strata/X_data.parquet +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/single_obs_strata/X_metadata.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/single_obs_strata/X_statistics.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/single_obs_strata/mR_data.parquet +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/single_obs_strata/mR_metadata.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/single_obs_strata/mR_statistics.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/stratified_balanced/S_data.parquet +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/stratified_balanced/S_metadata.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/stratified_balanced/S_statistics.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/stratified_balanced/Xbar_data.parquet +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/stratified_balanced/Xbar_metadata.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/stratified_balanced/Xbar_statistics.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/stratified_uneven/S_data.parquet +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/stratified_uneven/S_metadata.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/stratified_uneven/S_statistics.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/stratified_uneven/Xbar_data.parquet +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/stratified_uneven/Xbar_metadata.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/stratified_uneven/Xbar_statistics.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/unstratified_small/X_data.parquet +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/unstratified_small/X_metadata.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/unstratified_small/X_statistics.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/unstratified_small/mR_data.parquet +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/unstratified_small/mR_metadata.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/fixtures/golden_masters/unstratified_small/mR_statistics.json +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_analysis_bugs.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_analysis_dataset.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_analysis_golden_master.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_analysis_result_contract.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_analysis_specifications.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_api_reference.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_beyond_limits_vectorized.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_bin_extremes.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_by_parameter.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_by_permutations.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_calibration.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_capability_chart.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_chart_availability.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_chart_column_contract.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_chart_parsing.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_control_chart_figure.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_data_preparation.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_datasets.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_derivations.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_derivations_robustness.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_design_state_lineage.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_edge_cases.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_effects_calculator.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_encode_rsg_vectorized.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_execute_validation.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_focus.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_init_cleaning.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_lane_boundaries.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_lane_connectors.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_limits_vectorized.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_manual_citations.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_na_in_factor_columns.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_notebook_ergonomics.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_ordered_categorical_lane_order.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_p0_p2_regressions.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_performance.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_phased_limits.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_plotting.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_plotting_decorations.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_plotting_hardening.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_plotting_xaxis.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_process_behavior.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_recentered_mr_fix.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_reference_integrity.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_release_gate_0_1_0_checklist.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_residual_aliases.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_residual_labels.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_residual_scope.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_resolve_by_grouping.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_result_signals.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_result_tabular.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_sampling_plan.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_series_length.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_signal_result_surface.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_single_detection.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_solo_mr_residuals.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_srp_refactor.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_strata_companion.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_strata_sufficiency.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_stratified_insufficiency.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_supports_calibration.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_synthetic.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_types.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_validate_by_parameter.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_weco_integration.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_why_not.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_x_axis_layout.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_xbar_residual_center.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_xbar_s_helpers.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_xbar_s_options.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/tests/test_xbar_stratified.py +0 -0
- {processbehavior-0.5.0 → processbehavior-0.6.0}/validation/PBTESTDATABASE_T100.csv +0 -0
|
@@ -7,6 +7,114 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.0] - 2026-11-07
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **`validation/truth_report.py`.** It reports how closely the methods, as implemented, recover
|
|
14
|
+
answers known by construction: the PM INERT pure-noise data under all six missing-data
|
|
15
|
+
patterns, and Dr. Bishop's known-effects data. It is a report, not a gate.
|
|
16
|
+
- **Two design-report lines.** In ADS 3 the report says how many cells hold a single reading,
|
|
17
|
+
since one is enough to switch R2 for the whole study to the differencing method. When
|
|
18
|
+
conditions were observed in different periods, it says how far apart their average periods
|
|
19
|
+
are, since condition effects then average over different times.
|
|
20
|
+
- **`LossResult.reference`: what each loss part shows from noise alone.** The PDC, Time and
|
|
21
|
+
Interaction parts average squared deviations of estimated means, so with no effects each is
|
|
22
|
+
still above zero (about 14% interaction on the PM INERT pure-noise data). `reference` gives
|
|
23
|
+
the expected value of each under no effects for the study's own layout, exact for any
|
|
24
|
+
pattern of occupied cells and cell sizes, and the printed result shows it as a share of the
|
|
25
|
+
total. It is in `as_dict()` too. In ADS 2/3 the interaction is a remainder, so its reference
|
|
26
|
+
is `None`. Loss values are unchanged.
|
|
27
|
+
- **Windowed capability shows the window's own noise.** `CapabilityResult.window_sigma_r2` is
|
|
28
|
+
σ̂ from the window's R2 values, printed beside the full-study `sigma_hat_r2` that the
|
|
29
|
+
potential indices use. It appears only on windowed results; the default result is unchanged.
|
|
30
|
+
- **A Provenance sheet in Excel exports.** Written with the Summary sheet: library version,
|
|
31
|
+
time written, variables, input rows and rows analysed, design states, and a SHA-256
|
|
32
|
+
fingerprint of the analysed values. `AnalysisDataSet.n_input_rows` holds the input row count.
|
|
33
|
+
- **`validation/chance_signals.py`.** It reports how often charts signal on pure noise: X/mR
|
|
34
|
+
charts under the default rules (and mR with all eight), and the R3-R5 effect charts, whose
|
|
35
|
+
limits sit between about 2.2 and 4.4 standard deviations of the plotted points depending on
|
|
36
|
+
the chart and layout. A report, not a gate; its tables are in the WECO rules reference and
|
|
37
|
+
the residuals guide.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
- **Manual citations follow the 11-7 edition.** Dr. Bishop's manual of 2026-11-07 has 20 chapters
|
|
41
|
+
(chapters 7 and up are one lower than before) and dot-numbered captions; every equation and
|
|
42
|
+
chapter citation in the code, docs and tests now uses its numbering, in the hyphen form the
|
|
43
|
+
manual repository checks ("11-7 manual Eq 13-8"). Seven equations it removed are cited by
|
|
44
|
+
their replacements. No behaviour changes.
|
|
45
|
+
- **mR charts check Rule 1 only by default.** Wheeler reads the moving range chart for points
|
|
46
|
+
above its upper limit. The zone and run rules assume symmetric, independent points, and
|
|
47
|
+
successive moving ranges share a reading, so with all eight rules on, 95% of 100-point
|
|
48
|
+
pure-noise mR charts showed a signal (59% with Rule 1 alone). X charts still check all eight.
|
|
49
|
+
`rules='all'` restores the old behaviour. Signal counts on mR charts drop.
|
|
50
|
+
- **The validation page says what is compared with VAS.** It claimed "every analytical
|
|
51
|
+
output"; the gate compares chart centres and limits, capability indices and loss shares. The
|
|
52
|
+
page now lists what is not compared (residual values one by one, signals, staged charts,
|
|
53
|
+
effects tables, calibrations, capability windows, the design report) and points to the truth
|
|
54
|
+
report. The `detect_signals()` docstring no longer says Rules 1-4 are the default.
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
- **Tutorial outputs match the library again.** The docs site shows each notebook's saved outputs,
|
|
58
|
+
and eight tutorials had drifted: limits from before 0.4.0 in the Design-State-1 analysis, flat
|
|
59
|
+
limits where they now vary by subgroup size in the reference validation, older process means and
|
|
60
|
+
signal counts. All were re-executed. The Derived Variables tutorial, saved without outputs, now
|
|
61
|
+
shows them. Prose that the new outputs contradicted was corrected; the Bishop reference values in
|
|
62
|
+
the reference validation now quote his VAS rerun of 13 October 2026.
|
|
63
|
+
- **The factor x time interaction chart draws its lines in factor order on every machine.** It
|
|
64
|
+
sorted by time with an unstable sort, so the legend order, and the colour each line got, depended
|
|
65
|
+
on the numpy version.
|
|
66
|
+
- **Three signal rules now match their definitions.** Rule 5 (trend) fired only on 7 rising or
|
|
67
|
+
falling points, not 6; Rule 6 (oscillation) only on 15 alternating points, not 14; and Rule 2
|
|
68
|
+
counted only points in Zone A, leaving out points beyond 3σ, which Western Electric and Wheeler
|
|
69
|
+
count as "beyond 2σ". The docs, the rule descriptions and the minimum-observation table already
|
|
70
|
+
said 6, 14 and "Zone A or beyond". X charts may show slightly more signals; on 100 points of
|
|
71
|
+
pure noise, the share with a signal under the eight default rules rises from 70% to 79%.
|
|
72
|
+
- **The WECO rules page gives correct chance rates.** Six of its eight per-rule false-alarm rates
|
|
73
|
+
were wrong (for example 0.003% for 15 points hugging the centre, where it is 0.33%), and its
|
|
74
|
+
rule-set table had no source. The rates are now exact per-window figures, the table is measured
|
|
75
|
+
by `validation/chance_signals.py`, and the wording no longer says a signal is "almost certainly"
|
|
76
|
+
a special cause.
|
|
77
|
+
- **Commas in numbers stored as text are no longer misread.** A comma was removed wherever it
|
|
78
|
+
appeared, so a decimal comma such as `10,4` became 104; and a lone `10,4` in a column of plain
|
|
79
|
+
numbers silently became missing. Commas are now read one way per column: as decimal points
|
|
80
|
+
when the column has no `.` and some comma is not a thousands group, otherwise only as
|
|
81
|
+
separators between groups of three digits. Any other comma value becomes missing and is named.
|
|
82
|
+
- **The numeric-cleaning warning says what happened.** It counts the values actually reformatted
|
|
83
|
+
(it used to count every value in the column), gives examples, says when commas were read as
|
|
84
|
+
decimal points, and names the values that could not be read. A column of plain numeric strings
|
|
85
|
+
no longer warns.
|
|
86
|
+
- **A factor nested in time is named.** When every time period holds one level of a factor (a
|
|
87
|
+
stage, a before/after split), the design report says so beside the empty-cell count, and
|
|
88
|
+
`loss_function()` warns that the PDC and Time parts both contain that factor's effect, so the
|
|
89
|
+
decomposition counts it more than once. It points to staged charts instead. Loss values are
|
|
90
|
+
unchanged.
|
|
91
|
+
- **The design report no longer says "Complete structure" when there is nothing to compare.**
|
|
92
|
+
Without a sampling plan the structure line reads "Not checked (no sampling plan supplied)". It
|
|
93
|
+
used to say "Complete structure", even beside hundreds of empty cells.
|
|
94
|
+
- **Potential capability says why it is unavailable.** A study with no factors (such as one
|
|
95
|
+
stream of subgroups) or no time variable got "R2 residuals not available (ADS 1)". It now
|
|
96
|
+
says that Cp/Cpk use the VAS R2 residuals, which need at least one factor (or a time
|
|
97
|
+
variable), and that the study has none.
|
|
98
|
+
|
|
99
|
+
### Removed
|
|
100
|
+
- **`examples/` and `validation/effects_visualization.ipynb`.** These demos predated the 0.2.0
|
|
101
|
+
API (`ProcessDataFrame`, `study.sds`, moved data files) and no longer ran; nothing linked to
|
|
102
|
+
them. The maintained, CI-executed tutorials are in `docs/tutorials/`.
|
|
103
|
+
- **`tests/test_bishop_reference.py`.** Its one test compared residual values with a workbook
|
|
104
|
+
of Dr. Bishop's that is not in the repository, so it skipped on every run. Residual charts are
|
|
105
|
+
still compared with VAS through their limits.
|
|
106
|
+
|
|
107
|
+
### CI / Infrastructure
|
|
108
|
+
- **CI checks that docs notebooks match their saved outputs.** `scripts/refresh_notebooks.py`
|
|
109
|
+
re-executes the docs notebooks; with `--check` (in CI, replacing the nbmake run) it fails when a
|
|
110
|
+
notebook no longer runs or what a reader would see has changed, comparing printed text, tables
|
|
111
|
+
and each figure's data and labels. The `[test]` extra now needs nbclient, nbformat and ipykernel
|
|
112
|
+
instead of nbmake.
|
|
113
|
+
- **Python 3.14 is tested** on Ubuntu and Windows, in CI and in the release gate, and listed in
|
|
114
|
+
the package classifiers.
|
|
115
|
+
- **The truth report runs in CI.** `validation/truth_report.py` runs on every push (Ubuntu,
|
|
116
|
+
Python 3.13) and its output is kept as the `truth-report` artifact.
|
|
117
|
+
|
|
10
118
|
## [0.5.0] - 2026-10-29
|
|
11
119
|
|
|
12
120
|
This release adds staged (phased) Xbar and S charts, computed per stage as Dr. Bishop
|
|
@@ -1181,7 +1289,8 @@ and may change between minor versions until 1.0.
|
|
|
1181
1289
|
- `natsort >= 8.0`
|
|
1182
1290
|
- `plotly >= 5.18, < 7`
|
|
1183
1291
|
|
|
1184
|
-
[Unreleased]: https://github.com/cnicholas/processbehavior/compare/v0.
|
|
1292
|
+
[Unreleased]: https://github.com/cnicholas/processbehavior/compare/v0.6.0...HEAD
|
|
1293
|
+
[0.6.0]: https://github.com/cnicholas/processbehavior/compare/v0.5.0...v0.6.0
|
|
1185
1294
|
[0.5.0]: https://github.com/cnicholas/processbehavior/compare/v0.4.0...v0.5.0
|
|
1186
1295
|
[0.4.0]: https://github.com/cnicholas/processbehavior/compare/v0.3.3...v0.4.0
|
|
1187
1296
|
[0.3.3]: https://github.com/cnicholas/processbehavior/compare/v0.3.2...v0.3.3
|
|
@@ -17,7 +17,7 @@ authors:
|
|
|
17
17
|
repository-code: "https://github.com/cnicholas/processbehavior"
|
|
18
18
|
url: "https://cnicholas.github.io/processbehavior/"
|
|
19
19
|
license: Apache-2.0
|
|
20
|
-
version: 0.
|
|
20
|
+
version: 0.6.0
|
|
21
21
|
keywords:
|
|
22
22
|
- statistical process control
|
|
23
23
|
- process behavior charts
|
|
@@ -74,13 +74,7 @@ The full test suite is the gate: if it fails, the change is not ready.
|
|
|
74
74
|
The file `validation/PBTESTDATABASE_T100.csv` is ground-truth Minitab output
|
|
75
75
|
for DS 1–6 from Bishop's reference materials. Numerical changes that touch
|
|
76
76
|
residuals (R1–R5), DS detection, limit calculations, or variance
|
|
77
|
-
decomposition must keep the
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
pytest tests/test_bishop_reference.py -v # skips unless Bishop's residual workbooks are present
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
There is also a full end-to-end check that compares every chart center, control
|
|
77
|
+
decomposition must keep the end-to-end check green. It compares every chart center, control
|
|
84
78
|
limit, capability index and loss component against Bishop's VAS runs for the
|
|
85
79
|
10-12-2026 manual (PM SDS 1–6, PM INERT, his known-effects data and the Medicare data):
|
|
86
80
|
|
|
@@ -97,10 +91,24 @@ summary.
|
|
|
97
91
|
If a Bishop check fails after your change, the change is wrong — not the
|
|
98
92
|
reference.
|
|
99
93
|
|
|
94
|
+
## Docs notebooks
|
|
95
|
+
|
|
96
|
+
The docs site shows each notebook's saved outputs; it does not run them. After a change that
|
|
97
|
+
alters what a notebook prints or plots, refresh the saved outputs:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
python scripts/refresh_notebooks.py # every docs notebook
|
|
101
|
+
python scripts/refresh_notebooks.py docs/tutorials/signal-detection.ipynb
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
CI runs `python scripts/refresh_notebooks.py --check` and fails when a notebook no longer runs or
|
|
105
|
+
no longer matches its saved outputs. Then read the text around any changed output: the check
|
|
106
|
+
compares outputs, not prose.
|
|
107
|
+
|
|
100
108
|
## Citing the methodology
|
|
101
109
|
|
|
102
110
|
Code and docs cite Bishop's VAS documentation manual by edition and equation number:
|
|
103
|
-
`(
|
|
111
|
+
`(11-7 manual Eq 13-8)`, `Eqs 13-4..13-13`, `Eqs 10-16/17`. Name the edition once per file and use
|
|
104
112
|
the chapter-number form; `tests/test_manual_citations.py` checks both. The citations are read
|
|
105
113
|
against the manual itself, so a new edition reports every place in the library whose equation
|
|
106
114
|
changed or moved.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: processbehavior
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Python-native Process Behavior & Control Charts (spec-driven SPC with tidy outputs)
|
|
5
5
|
Project-URL: Homepage, https://github.com/cnicholas/processbehavior
|
|
6
6
|
Project-URL: Repository, https://github.com/cnicholas/processbehavior
|
|
@@ -21,6 +21,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.11
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
25
|
Classifier: Topic :: Scientific/Engineering
|
|
25
26
|
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
26
27
|
Classifier: Typing :: Typed
|
|
@@ -33,12 +34,14 @@ Requires-Dist: typing-extensions>=4.0; python_version < '3.11'
|
|
|
33
34
|
Provides-Extra: dev
|
|
34
35
|
Requires-Dist: filelock>=3.20.3; extra == 'dev'
|
|
35
36
|
Requires-Dist: ipykernel; extra == 'dev'
|
|
37
|
+
Requires-Dist: ipykernel>=6; extra == 'dev'
|
|
36
38
|
Requires-Dist: jupyter-book>=2.0; extra == 'dev'
|
|
37
39
|
Requires-Dist: kaleido>=0.2; extra == 'dev'
|
|
38
40
|
Requires-Dist: lxml>=6.1.0; extra == 'dev'
|
|
39
41
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
42
|
+
Requires-Dist: nbclient>=0.10; extra == 'dev'
|
|
40
43
|
Requires-Dist: nbconvert>=7.17.1; extra == 'dev'
|
|
41
|
-
Requires-Dist:
|
|
44
|
+
Requires-Dist: nbformat>=5.10; extra == 'dev'
|
|
42
45
|
Requires-Dist: openpyxl>=3.1; extra == 'dev'
|
|
43
46
|
Requires-Dist: orjson>=3.11.6; extra == 'dev'
|
|
44
47
|
Requires-Dist: pillow>=12.2.0; extra == 'dev'
|
|
@@ -73,7 +76,9 @@ Requires-Dist: pre-commit>=3.7; extra == 'lint'
|
|
|
73
76
|
Requires-Dist: ruff>=0.5; extra == 'lint'
|
|
74
77
|
Requires-Dist: virtualenv>=20.36.1; extra == 'lint'
|
|
75
78
|
Provides-Extra: test
|
|
76
|
-
Requires-Dist:
|
|
79
|
+
Requires-Dist: ipykernel>=6; extra == 'test'
|
|
80
|
+
Requires-Dist: nbclient>=0.10; extra == 'test'
|
|
81
|
+
Requires-Dist: nbformat>=5.10; extra == 'test'
|
|
77
82
|
Requires-Dist: openpyxl>=3.1; extra == 'test'
|
|
78
83
|
Requires-Dist: pyarrow>=14.0; extra == 'test'
|
|
79
84
|
Requires-Dist: pytest-cov>=5; extra == 'test'
|
|
@@ -282,7 +287,7 @@ The full report renders to `validation/e2e_bishop_report.html`.
|
|
|
282
287
|
- **Effects analysis**: Main effects, time effects, and interaction effects
|
|
283
288
|
- **Stratified charts**: Automatic per-stratum limits for grouped individual data
|
|
284
289
|
- **Staged charts**: `phased=True` gives each stage its own center and limits on X/mR and Xbar/S; `phase_statistics()` returns the numbers behind the steps
|
|
285
|
-
- **Signal detection**: all eight Western Electric rules on X
|
|
290
|
+
- **Signal detection**: all eight Western Electric rules on X charts; Rule 1 (3-sigma) on mR, Xbar and S, where the run- and zone-based rules don't fit: subgroup comparisons have no time order, and successive moving ranges share a reading
|
|
286
291
|
- **Beyond the charts**: capability indices (Cp/Cpk/Pp/Ppk), Taguchi loss decomposition, and maximum-information analysis — validated in the same Bishop-reference CI gate
|
|
287
292
|
- **IDE support**: Column auto-completion via `ProcessBehavior(df).cols`
|
|
288
293
|
- **Self-diagnostic errors**: Helpful messages that say what's available and how to fix it
|
|
@@ -199,7 +199,7 @@ The full report renders to `validation/e2e_bishop_report.html`.
|
|
|
199
199
|
- **Effects analysis**: Main effects, time effects, and interaction effects
|
|
200
200
|
- **Stratified charts**: Automatic per-stratum limits for grouped individual data
|
|
201
201
|
- **Staged charts**: `phased=True` gives each stage its own center and limits on X/mR and Xbar/S; `phase_statistics()` returns the numbers behind the steps
|
|
202
|
-
- **Signal detection**: all eight Western Electric rules on X
|
|
202
|
+
- **Signal detection**: all eight Western Electric rules on X charts; Rule 1 (3-sigma) on mR, Xbar and S, where the run- and zone-based rules don't fit: subgroup comparisons have no time order, and successive moving ranges share a reading
|
|
203
203
|
- **Beyond the charts**: capability indices (Cp/Cpk/Pp/Ppk), Taguchi loss decomposition, and maximum-information analysis — validated in the same Bishop-reference CI gate
|
|
204
204
|
- **IDE support**: Column auto-completion via `ProcessBehavior(df).cols`
|
|
205
205
|
- **Self-diagnostic errors**: Helpful messages that say what's available and how to fix it
|
|
@@ -47,7 +47,7 @@ its ``.sds`` field. Internally some modules still use the legacy "SDS"
|
|
|
47
47
|
prefix (e.g. ``sds_detector.py``); the model itself is three-state.
|
|
48
48
|
"""
|
|
49
49
|
|
|
50
|
-
__version__ = '0.
|
|
50
|
+
__version__ = '0.6.0'
|
|
51
51
|
|
|
52
52
|
# Result object
|
|
53
53
|
from processbehavior.analysis_result import AnalysisResult
|
|
@@ -254,7 +254,7 @@ def _mr_family_limits(mr_spec: _MRChartSpec, *, mean, mR) -> pd.DataFrame:
|
|
|
254
254
|
"""Limits for one MR-family chart per row of a per-stratum or per-phase table.
|
|
255
255
|
|
|
256
256
|
The array form of the scalar ``calculate_limits`` these sites used to call through
|
|
257
|
-
``DataFrame.apply(axis=1)``: the same formulae,
|
|
257
|
+
``DataFrame.apply(axis=1)``: the same formulae, 11-7 manual Eqs 11-8/11-9 (X) and
|
|
258
258
|
12-5 (mR). The scalar call built a ``pd.Series`` per stratum, about 0.1 ms each, so
|
|
259
259
|
it cost 30s per chart at 300,000 strata. ``mean`` is ignored for the mR chart.
|
|
260
260
|
"""
|
|
@@ -921,7 +921,7 @@ class Analysis:
|
|
|
921
921
|
"""Return the cell-grid columns at which to compute Bishop's grand mean
|
|
922
922
|
for an Xbar chart on this column.
|
|
923
923
|
|
|
924
|
-
Each re-centred residual is R + Ybar (
|
|
924
|
+
Each re-centred residual is R + Ybar (11-7 manual Eq 13-26; see
|
|
925
925
|
`AnalysisDataSet._calculate_centered_residuals` and
|
|
926
926
|
`residual_calculator.calculate_r6_residuals`). The canonical grand mean of an
|
|
927
927
|
RCRk chart averages over the grain its effect lives at — equal weight per
|
|
@@ -1216,10 +1216,10 @@ class Analysis:
|
|
|
1216
1216
|
def _xbar_limits(self, out: pd.DataFrame, s_bar: float, n_to_use: str) -> pd.DataFrame:
|
|
1217
1217
|
"""Xbar natural process limits for the subgroups in ``out`` (columns center, s, n, N).
|
|
1218
1218
|
|
|
1219
|
-
Equal subgroup sizes (or n_mode="average", Eq
|
|
1220
|
-
Unequal sizes: sigma_hat is the average of each subgroup's S_r / c4(N_r) (
|
|
1221
|
-
Eq
|
|
1222
|
-
(Eqs
|
|
1219
|
+
Equal subgroup sizes (or n_mode="average", Eq 10-20): S-bar / c4(N) for every row.
|
|
1220
|
+
Unequal sizes: sigma_hat is the average of each subgroup's S_r / c4(N_r) (11-7 manual
|
|
1221
|
+
Eq 10-5) and each subgroup's limits are center ± multiplier · sigma_hat / sqrt(N_r)
|
|
1222
|
+
(Eqs 10-16, 10-17), so they step with N_r, as VAS draws them. A one-reading subgroup
|
|
1223
1223
|
has no S_r: it takes no part in sigma_hat and is charted at center ± multiplier · sigma_hat.
|
|
1224
1224
|
"""
|
|
1225
1225
|
if n_to_use == 'n':
|
|
@@ -1283,12 +1283,12 @@ class Analysis:
|
|
|
1283
1283
|
|
|
1284
1284
|
Every subgroup with a value is charted. A one-reading subgroup has no S, so sigma-hat
|
|
1285
1285
|
comes from the subgroups with n >= 2, and its limits are center ± 3·sigma-hat (10-12
|
|
1286
|
-
manual Eqs
|
|
1286
|
+
manual Eqs 10-16, 10-17 with N_r = 1), as VAS draws them; the companion S chart keeps
|
|
1287
1287
|
only n >= 2. n counts the limits column, so a reading without one (R2's first value) is
|
|
1288
1288
|
not charted, as in VAS. Returns None when no subgroup has n >= 2; the caller decides
|
|
1289
1289
|
whether that raises (one chart) or is recorded (a stratum, a phase). Under
|
|
1290
1290
|
``n_mode='average'`` the ``N`` column becomes the mean fitted n and both charts take
|
|
1291
|
-
the constant-N path (Eq
|
|
1291
|
+
the constant-N path (Eq 10-20).
|
|
1292
1292
|
"""
|
|
1293
1293
|
out = out[out['n'].ge(1)].copy() # n=0: every charted value missing
|
|
1294
1294
|
fit = out['n'].ge(2)
|
|
@@ -2007,9 +2007,9 @@ class Analysis:
|
|
|
2007
2007
|
A phase is one combination of the grouping factors: a contiguous run of subgroups in
|
|
2008
2008
|
time order on the stage-by-stage layout that ``by=None`` already draws. Each phase is
|
|
2009
2009
|
computed as the per-condition chart of ``by=[time]`` computes one stratum — the
|
|
2010
|
-
unweighted mean of its subgroup means as centre (
|
|
2011
|
-
phase), its own S-bar and sigma-hat (Eq
|
|
2012
|
-
|
|
2010
|
+
unweighted mean of its subgroup means as centre (11-7 manual Figure 13.3 within the
|
|
2011
|
+
phase), its own S-bar and sigma-hat (Eq 10-5), its own limits (Eqs 10-8, 10-9, 10-16,
|
|
2012
|
+
10-17) — and the phases are concatenated in display order with the stepped columns.
|
|
2013
2013
|
Nothing is pooled across phases: Dr. Bishop (2026-10-18), "treat each stage separately
|
|
2014
2014
|
as if it were a single analysis".
|
|
2015
2015
|
|
|
@@ -2177,10 +2177,10 @@ class Analysis:
|
|
|
2177
2177
|
|
|
2178
2178
|
The X and mR charts share >85% of their calculation logic. The behavioral
|
|
2179
2179
|
differences are encoded in ``mr_spec`` — no boolean flags needed. The
|
|
2180
|
-
statistics are the
|
|
2181
|
-
consecutive ranges (Eq
|
|
2182
|
-
Y-bar ± 3·mR-bar/d2 (Eqs
|
|
2183
|
-
(Eq
|
|
2180
|
+
statistics are the 11-7 manual's Chapter 11: mR-bar over the T - 1
|
|
2181
|
+
consecutive ranges (Eq 11-1), X centre Y-bar (Eq 11-7), X limits
|
|
2182
|
+
Y-bar ± 3·mR-bar/d2 (Eqs 11-8, 11-9) and mR limits 0 … D4·mR-bar
|
|
2183
|
+
(Eq 11-5), within each stratum or phase when the chart is split.
|
|
2184
2184
|
|
|
2185
2185
|
Parameters
|
|
2186
2186
|
----------
|
|
@@ -2895,11 +2895,11 @@ class Analysis:
|
|
|
2895
2895
|
return result
|
|
2896
2896
|
|
|
2897
2897
|
# === Global limits path (unchanged) ===
|
|
2898
|
-
# Moving range: mR-bar over the T - 1 consecutive ranges (
|
|
2898
|
+
# Moving range: mR-bar over the T - 1 consecutive ranges (11-7 manual Eq 11-1). A re-centred
|
|
2899
2899
|
# R2 chart takes them over R2 (_resolve_mr_source_column), not the plotted RCR2.
|
|
2900
2900
|
out['mr'] = out[mr_source_col].diff().abs()
|
|
2901
2901
|
mR = out['mr'].mean()
|
|
2902
|
-
mean_ = out[value_col].mean() # Y-bar, the X chart's centre (
|
|
2902
|
+
mean_ = out[value_col].mean() # Y-bar, the X chart's centre (Eq 11-7)
|
|
2903
2903
|
|
|
2904
2904
|
# R chart: drop first observation
|
|
2905
2905
|
if mr_spec.drops_first_mr:
|
|
@@ -53,6 +53,7 @@ class AnalysisDataSet:
|
|
|
53
53
|
# Store inputs
|
|
54
54
|
self.spec = spec
|
|
55
55
|
self.observed_design_state: int = observed_sds
|
|
56
|
+
self.n_input_rows: int = len(df) # Rows given to formulate(), before tidying
|
|
56
57
|
|
|
57
58
|
# Initialize output containers
|
|
58
59
|
self.interactions = {}
|
|
@@ -171,7 +172,7 @@ class AnalysisDataSet:
|
|
|
171
172
|
def _check_r2_scale_factor(self, r2_method: str) -> None:
|
|
172
173
|
"""Record (and warn once) when the layout leaves the R2 scale factor undefined.
|
|
173
174
|
|
|
174
|
-
The scaled-difference R2 (
|
|
175
|
+
The scaled-difference R2 (11-7 manual Eqs 13-4..13-13) needs at least two process
|
|
175
176
|
design conditions and more than K + 1 observations; outside that it has no value.
|
|
176
177
|
"""
|
|
177
178
|
if r2_method != 'ma2':
|
|
@@ -243,7 +244,7 @@ class AnalysisDataSet:
|
|
|
243
244
|
|
|
244
245
|
# Compute n_per_cell and cell means once
|
|
245
246
|
self._n_per_cell = df.groupby('cell_key', observed=True)[y].transform('size')
|
|
246
|
-
self._ybar_kt = df.groupby('cell_key', observed=True)[y].transform('mean') # Ȳ_kt (
|
|
247
|
+
self._ybar_kt = df.groupby('cell_key', observed=True)[y].transform('mean') # Ȳ_kt (11-7 manual Eq 9-6)
|
|
247
248
|
|
|
248
249
|
# Handle edge case: empty data or all NaN
|
|
249
250
|
if len(self._n_per_cell) == 0 or self._n_per_cell.isna().all():
|
|
@@ -355,12 +356,12 @@ class AnalysisDataSet:
|
|
|
355
356
|
Calculate the re-centred residuals (RCR) and the R1 cell/marginal means (Rbar).
|
|
356
357
|
|
|
357
358
|
Re-centring puts a residual back on the measurement scale without changing what it
|
|
358
|
-
shows: RCR = R + Ȳ.. (Bishop's
|
|
359
|
+
shows: RCR = R + Ȳ.. (Bishop's 11-7 manual Eq 13-26, for R1, R3, R4, R5 and R6; RCR6
|
|
359
360
|
is built per request in residual_calculator.calculate_r6_residuals). An effect chart of
|
|
360
361
|
RCR5 by condition, RCR4 by period or RCR3 by subgroup therefore plots that effect plus
|
|
361
362
|
R2, centred on the grand mean.
|
|
362
363
|
|
|
363
|
-
RCR2 is not part of Eq
|
|
364
|
+
RCR2 is not part of Eq 13-26; it is kept as Ȳ_kt + R2 (which reconstructs Y in ADS 1).
|
|
364
365
|
|
|
365
366
|
Note: This method intentionally mutates self.analysis_dataset in-place
|
|
366
367
|
(adding columns directly to the DataFrame). This differs from
|
|
@@ -381,9 +382,9 @@ class AnalysisDataSet:
|
|
|
381
382
|
df['Rbar_k'] = df.groupby([self.spec.rsg_var_name], observed=True)['R1'].transform('mean')
|
|
382
383
|
df['Rbar_t'] = df.groupby([self.spec.time_var], observed=True)['R1'].transform('mean')
|
|
383
384
|
|
|
384
|
-
# Re-centred residuals, Eq
|
|
385
|
+
# Re-centred residuals, Eq 13-26: RCR = R + Ybar
|
|
385
386
|
df['RCR1'] = df['Ybar'] + df['R1']
|
|
386
|
-
df['RCR2'] = df['Ybar_kt'] + df['R2'] # not in Eq
|
|
387
|
+
df['RCR2'] = df['Ybar_kt'] + df['R2'] # not in Eq 13-26; reconstructs Y in ADS 1
|
|
387
388
|
df['RCR3'] = df['Ybar'] + df['R3']
|
|
388
389
|
df['RCR4'] = df['Ybar'] + df['R4']
|
|
389
390
|
df['RCR5'] = df['Ybar'] + df['R5']
|
|
@@ -1053,9 +1053,10 @@ class AnalysisResult:
|
|
|
1053
1053
|
Specific chart to analyze. If None, analyzes all charts.
|
|
1054
1054
|
rules : str, list, or RuleSet, optional
|
|
1055
1055
|
Rules to apply:
|
|
1056
|
-
-
|
|
1057
|
-
- '
|
|
1058
|
-
- '
|
|
1056
|
+
- None (default): Rules 1-8 on X charts; Rule 1 on mR, Xbar and S charts
|
|
1057
|
+
- 'standard': Rules 1-4, on every chart
|
|
1058
|
+
- 'extended': Rules 1-8, on every chart
|
|
1059
|
+
- 'all': Same as 'extended'
|
|
1059
1060
|
- List of rule names: ['rule_1', 'rule_2', ...]
|
|
1060
1061
|
- RuleSet: Fluent API configuration
|
|
1061
1062
|
config : SignalConfig, optional
|
|
@@ -1071,7 +1072,7 @@ class AnalysisResult:
|
|
|
1071
1072
|
|
|
1072
1073
|
Examples
|
|
1073
1074
|
--------
|
|
1074
|
-
Simple usage (
|
|
1075
|
+
Simple usage (default rules for each chart):
|
|
1075
1076
|
|
|
1076
1077
|
>>> signals = result.detect_signals()
|
|
1077
1078
|
>>> print(signals.summary)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Process Capability Analysis — Bishop Chapter
|
|
2
|
+
Process Capability Analysis — Bishop Chapter 15 (11-7 manual).
|
|
3
3
|
|
|
4
4
|
Computes capability indices given specification limits. Core logic lives as
|
|
5
5
|
pure module-level functions (composition pattern matching ResidualCalculator /
|
|
@@ -19,7 +19,7 @@ Potential Capability (achievable by removing assignable causes):
|
|
|
19
19
|
|
|
20
20
|
References
|
|
21
21
|
----------
|
|
22
|
-
Bishop, T. *Variation Analysis System* — Chapter
|
|
22
|
+
Bishop, T. *Variation Analysis System* — Chapter 15 (11-7 manual).
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
from __future__ import annotations
|
|
@@ -89,7 +89,7 @@ class SpecLimits:
|
|
|
89
89
|
@dataclass(frozen=True)
|
|
90
90
|
class CapabilityResult:
|
|
91
91
|
"""
|
|
92
|
-
Result of a process capability assessment (Bishop Ch.
|
|
92
|
+
Result of a process capability assessment (Bishop Ch. 15).
|
|
93
93
|
|
|
94
94
|
Raw ``float`` values are stored unrounded. Use :meth:`as_dict` for a
|
|
95
95
|
rounded dictionary or ``repr()`` for a formatted summary.
|
|
@@ -111,7 +111,7 @@ class CapabilityResult:
|
|
|
111
111
|
pp, ppk_lower, ppk_upper, ppk : float | None
|
|
112
112
|
Current capability indices (from overall sigma).
|
|
113
113
|
sigma_hat_r2 : float | None
|
|
114
|
-
Unbiased sigma from R2 residuals: S_R2 / c4(N_R2) (
|
|
114
|
+
Unbiased sigma from R2 residuals: S_R2 / c4(N_R2) (11-7 manual Eq 15-10).
|
|
115
115
|
cp, cpk_lower, cpk_upper, cpk : float | None
|
|
116
116
|
Potential capability indices (from R2 sigma), with CPL/CPU measured from
|
|
117
117
|
``potential_center``.
|
|
@@ -138,6 +138,11 @@ class CapabilityResult:
|
|
|
138
138
|
``y_bar + mean(R2)``. Equals ``y_bar`` when R2 averages to zero (ADS 1);
|
|
139
139
|
with the scaled-difference R2 of ADS 2/3 it differs by mean(R2), which is
|
|
140
140
|
small. None when potential capability is unavailable.
|
|
141
|
+
window_sigma_r2 : float | None
|
|
142
|
+
Windowed results only: σ̂ from the window's own R2 values, by the same
|
|
143
|
+
estimator as ``sigma_hat_r2``. The potential indices keep the full-study
|
|
144
|
+
``sigma_hat_r2``; this shows how the window's noise compares. None when not
|
|
145
|
+
windowed or potential capability is unavailable.
|
|
141
146
|
"""
|
|
142
147
|
|
|
143
148
|
# Input context
|
|
@@ -203,6 +208,9 @@ class CapabilityResult:
|
|
|
203
208
|
# Potential view centre: mean of (y_bar + R2). Last field so positional construction is unchanged.
|
|
204
209
|
potential_center: float | None = None
|
|
205
210
|
|
|
211
|
+
# Windowed only: sigma_hat from this window's own R2 values, beside the full-study one used above.
|
|
212
|
+
window_sigma_r2: float | None = None
|
|
213
|
+
|
|
206
214
|
# ------------------------------------------------------------------
|
|
207
215
|
# Visualization
|
|
208
216
|
# ------------------------------------------------------------------
|
|
@@ -319,6 +327,7 @@ class CapabilityResult:
|
|
|
319
327
|
out['time_var'] = self.time_var
|
|
320
328
|
out['n_total'] = self.n_total
|
|
321
329
|
out['window_warning'] = self.window_warning
|
|
330
|
+
out['window_sigma_r2'] = _r(self.window_sigma_r2)
|
|
322
331
|
return out
|
|
323
332
|
|
|
324
333
|
def __repr__(self) -> str:
|
|
@@ -339,6 +348,11 @@ class CapabilityResult:
|
|
|
339
348
|
lines.append(' Potential Capability (R2 sigma):')
|
|
340
349
|
lines.append(f' centre={d["potential_center"]}, sigma_hat_r2={d["sigma_hat_r2"]}')
|
|
341
350
|
lines.append(f' Cp={d["cp"]} Cpk={d["cpk"]} (lower={d["cpk_lower"]}, upper={d["cpk_upper"]})')
|
|
351
|
+
if self.window is not None and self.window_sigma_r2 is not None:
|
|
352
|
+
lines.append(
|
|
353
|
+
f' sigma_hat_r2 is from the full study (N={d["n_total"]}); '
|
|
354
|
+
f'this window\'s own is {d["window_sigma_r2"]}'
|
|
355
|
+
)
|
|
342
356
|
|
|
343
357
|
lines.append('')
|
|
344
358
|
lines.append(' Z-scores:')
|
|
@@ -389,9 +403,9 @@ def compute_sigma_hat(values: np.ndarray) -> tuple[float, float]:
|
|
|
389
403
|
|
|
390
404
|
Notes
|
|
391
405
|
-----
|
|
392
|
-
Bishop Ch.
|
|
406
|
+
Bishop Ch. 15 — ddof=1 is mandatory. The c4 correction
|
|
393
407
|
removes the small-sample bias inherent in the sample standard
|
|
394
|
-
deviation: sigma_hat = S / alpha_N (
|
|
408
|
+
deviation: sigma_hat = S / alpha_N (11-7 manual Eq 9-9).
|
|
395
409
|
"""
|
|
396
410
|
n = len(values)
|
|
397
411
|
s = float(np.std(values, ddof=1))
|
|
@@ -546,8 +560,8 @@ def _process_mean(frame: pd.DataFrame, response_var: str) -> float:
|
|
|
546
560
|
Each experimental condition counts once, whatever its cell size, as for the
|
|
547
561
|
Xbar centre line and the loss function's centring term. VAS prints this as the
|
|
548
562
|
capability charts' PROCESS MEAN (PM SDS 6: 237.86, where the plain average of
|
|
549
|
-
the readings is 237.834), and the
|
|
550
|
-
|
|
563
|
+
the readings is 237.834), and the 11-7 manual's one-sided indices (Eqs 15-5, 15-6,
|
|
564
|
+
15-12, 15-13) centre on it; on balanced data the two are equal.
|
|
551
565
|
A study without cells (no factors or time) has one cell: the plain average.
|
|
552
566
|
"""
|
|
553
567
|
valid = frame[frame[response_var].notna()]
|
|
@@ -690,6 +704,7 @@ def assess_capability(
|
|
|
690
704
|
potential_unavailable_reason = None
|
|
691
705
|
potential_values = None
|
|
692
706
|
potential_center = None
|
|
707
|
+
window_sigma_r2 = None
|
|
693
708
|
potential_outside = {}
|
|
694
709
|
|
|
695
710
|
if ads.r2_unavailable_reason is not None:
|
|
@@ -699,9 +714,9 @@ def assess_capability(
|
|
|
699
714
|
n_r2 = len(r2_values)
|
|
700
715
|
|
|
701
716
|
if n_r2 >= 2:
|
|
702
|
-
_, sigma_hat_r2 = compute_sigma_hat(r2_values) # Eq
|
|
717
|
+
_, sigma_hat_r2 = compute_sigma_hat(r2_values) # Eq 15-10
|
|
703
718
|
potential_values = y_bar + r2_values
|
|
704
|
-
# Centre the potential indices on the potential values themselves (Eqs
|
|
719
|
+
# Centre the potential indices on the potential values themselves (Eqs 15-11..15-13 as
|
|
705
720
|
# VAS computes them): y_bar + mean(R2). Tom's Medicare run of 10/3/2026 prints
|
|
706
721
|
# "PROCESS MEAN 10831.3" on the potential slide (y_bar 10832.4, mean R2 -1.15).
|
|
707
722
|
potential_center = float(np.mean(potential_values))
|
|
@@ -711,15 +726,27 @@ def assess_capability(
|
|
|
711
726
|
cpk_upper = pot['ppk_upper']
|
|
712
727
|
cpk = pot['ppk']
|
|
713
728
|
potential_outside = compute_pct_outside(potential_values, specs)
|
|
729
|
+
if window is not None:
|
|
730
|
+
window_r2 = frame['R2'].dropna().to_numpy(dtype=float)
|
|
731
|
+
if len(window_r2) >= 2:
|
|
732
|
+
_, window_sigma_r2 = compute_sigma_hat(window_r2)
|
|
714
733
|
else:
|
|
715
734
|
potential_unavailable_reason = (
|
|
716
735
|
f'Too few R2 residual values ({n_r2}) for potential capability; need at least 2'
|
|
717
736
|
)
|
|
718
737
|
else:
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
738
|
+
needs = (('at least one factor', ads.spec.rsg_vars_list), ('a time variable', ads.spec.time_var))
|
|
739
|
+
missing = [what for what, present in needs if not present]
|
|
740
|
+
if missing:
|
|
741
|
+
potential_unavailable_reason = (
|
|
742
|
+
f'Cp/Cpk use the VAS R2 residuals, which need {" and ".join(missing)}; '
|
|
743
|
+
f'this study has {"none" if len(missing) == 1 else "neither"}.'
|
|
744
|
+
)
|
|
745
|
+
else:
|
|
746
|
+
potential_unavailable_reason = (
|
|
747
|
+
f'R2 residuals not available (ADS {ads.analytical_design_state.sds}); '
|
|
748
|
+
f'Cp/Cpk require VAS residuals from a factorial+time design'
|
|
749
|
+
)
|
|
723
750
|
|
|
724
751
|
return CapabilityResult(
|
|
725
752
|
specs=specs,
|
|
@@ -757,5 +784,6 @@ def assess_capability(
|
|
|
757
784
|
n_total=n_total,
|
|
758
785
|
window_warning=window_warning,
|
|
759
786
|
potential_center=potential_center,
|
|
787
|
+
window_sigma_r2=window_sigma_r2,
|
|
760
788
|
observed_values=y_values,
|
|
761
789
|
)
|
|
@@ -73,6 +73,8 @@ class ExcelExporter:
|
|
|
73
73
|
|
|
74
74
|
Creates a multi-sheet Excel workbook with organized analysis results:
|
|
75
75
|
- Summary: Analysis metadata, SDS info, signal counts
|
|
76
|
+
- Provenance: library version, time written, variables, row counts,
|
|
77
|
+
design states and a fingerprint of the analysed data (with Summary)
|
|
76
78
|
- Charts: One tab per chart (Xbar, S, stratified X, etc.)
|
|
77
79
|
- Residuals: R1-R5 variance decomposition (if available)
|
|
78
80
|
- Effects: Main effects (if calculated)
|
|
@@ -148,6 +150,7 @@ class ExcelExporter:
|
|
|
148
150
|
# 1. Summary Tab
|
|
149
151
|
if include_summary:
|
|
150
152
|
self._write_summary_tab(writer, format_cells)
|
|
153
|
+
self._write_provenance_tab(writer, format_cells)
|
|
151
154
|
|
|
152
155
|
# 2. Chart Tabs
|
|
153
156
|
if include_charts:
|
|
@@ -236,6 +239,35 @@ class ExcelExporter:
|
|
|
236
239
|
ws = writer.sheets['Summary']
|
|
237
240
|
self._apply_formatting(ws)
|
|
238
241
|
|
|
242
|
+
def _write_provenance_tab(self, writer: pd.ExcelWriter, format_cells: bool) -> None:
|
|
243
|
+
"""Write what produced this workbook, so a reader can tell which data and version it came from."""
|
|
244
|
+
import hashlib
|
|
245
|
+
from datetime import datetime, timezone
|
|
246
|
+
|
|
247
|
+
from .. import __version__
|
|
248
|
+
|
|
249
|
+
ads = self.result._ads
|
|
250
|
+
spec = ads.spec
|
|
251
|
+
data = ads.analysis_dataset
|
|
252
|
+
columns = [spec.response_var, *spec.rsg_vars_list, *([spec.time_var] if spec.time_var else [])]
|
|
253
|
+
row_hashes = pd.util.hash_pandas_object(data[columns], index=False).to_numpy()
|
|
254
|
+
rows = [
|
|
255
|
+
('Library', f'processbehavior {__version__}'),
|
|
256
|
+
('Written (UTC)', datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')),
|
|
257
|
+
('Response', spec.response_var),
|
|
258
|
+
('Factors', ', '.join(spec.rsg_vars_list) or '(none)'),
|
|
259
|
+
('Time', spec.time_var or '(none)'),
|
|
260
|
+
('Analysis type', self.result.summary['analysis_type']),
|
|
261
|
+
('Input rows', ads.n_input_rows),
|
|
262
|
+
('Rows analysed', len(data)),
|
|
263
|
+
('Observed design state (ODS)', ads.observed_design_state),
|
|
264
|
+
('Analytical design state (ADS)', ads.analytical_design_state.sds),
|
|
265
|
+
('Data fingerprint (SHA-256)', hashlib.sha256(row_hashes.tobytes()).hexdigest()),
|
|
266
|
+
]
|
|
267
|
+
pd.DataFrame(rows, columns=['Item', 'Value']).to_excel(writer, sheet_name='Provenance', index=False)
|
|
268
|
+
if format_cells:
|
|
269
|
+
self._apply_formatting(writer.sheets['Provenance'])
|
|
270
|
+
|
|
239
271
|
def _write_chart_tabs(self, writer: pd.ExcelWriter, format_cells: bool) -> None:
|
|
240
272
|
"""Write tabs for each chart."""
|
|
241
273
|
|