processbehavior 0.3.1__tar.gz → 0.3.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.
Files changed (207) hide show
  1. {processbehavior-0.3.1 → processbehavior-0.3.2}/CHANGELOG.md +28 -1
  2. {processbehavior-0.3.1 → processbehavior-0.3.2}/PKG-INFO +1 -1
  3. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/__init__.py +1 -1
  4. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/derivations.py +12 -7
  5. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/plotting/renderers.py +53 -1
  6. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_derivations.py +13 -6
  7. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_derivations_robustness.py +16 -2
  8. processbehavior-0.3.2/tests/test_lane_connectors.py +103 -0
  9. {processbehavior-0.3.1 → processbehavior-0.3.2}/.gitignore +0 -0
  10. {processbehavior-0.3.1 → processbehavior-0.3.2}/CITATION.cff +0 -0
  11. {processbehavior-0.3.1 → processbehavior-0.3.2}/CODE_OF_CONDUCT.md +0 -0
  12. {processbehavior-0.3.1 → processbehavior-0.3.2}/CONTRIBUTING.md +0 -0
  13. {processbehavior-0.3.1 → processbehavior-0.3.2}/LICENSE +0 -0
  14. {processbehavior-0.3.1 → processbehavior-0.3.2}/README.md +0 -0
  15. {processbehavior-0.3.1 → processbehavior-0.3.2}/SECURITY.md +0 -0
  16. {processbehavior-0.3.1 → processbehavior-0.3.2}/examples/README.md +0 -0
  17. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/analysis.py +0 -0
  18. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/analysis_dataset.py +0 -0
  19. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/analysis_result.py +0 -0
  20. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/calibration.py +0 -0
  21. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/capability.py +0 -0
  22. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/data_preparation.py +0 -0
  23. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/datasets/__init__.py +0 -0
  24. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/datasets/data/coffee_shop_demo_long.csv +0 -0
  25. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/datasets/loaders.py +0 -0
  26. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/datasets/synthetic.py +0 -0
  27. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/effects_calculator.py +0 -0
  28. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/exceptions.py +0 -0
  29. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/exporters/__init__.py +0 -0
  30. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/exporters/excel_exporter.py +0 -0
  31. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/focused_result.py +0 -0
  32. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/formulation_spec.py +0 -0
  33. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/loss_function.py +0 -0
  34. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/maximum_information.py +0 -0
  35. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/plotting/__init__.py +0 -0
  36. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/plotting/_math.py +0 -0
  37. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/plotting/capability_chart.py +0 -0
  38. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/plotting/contracts.py +0 -0
  39. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/plotting/control_chart.py +0 -0
  40. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/plotting/effects_charts.py +0 -0
  41. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/plotting/lane_boundaries.py +0 -0
  42. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/plotting/limits.py +0 -0
  43. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/plotting/maximum_information_chart.py +0 -0
  44. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/plotting/plotter.py +0 -0
  45. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/plotting/report.py +0 -0
  46. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/plotting/residuals.py +0 -0
  47. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/plotting/run_rules_viz.py +0 -0
  48. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/plotting/stats_box.py +0 -0
  49. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/plotting/themes.py +0 -0
  50. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/plotting/x_axis_layout.py +0 -0
  51. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/plotting/zones.py +0 -0
  52. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/process_behavior.py +0 -0
  53. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/py.typed +0 -0
  54. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/residual_calculator.py +0 -0
  55. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/result_signals.py +0 -0
  56. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/result_tabular.py +0 -0
  57. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/sds_detector.py +0 -0
  58. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/series_length.py +0 -0
  59. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/signals/__init__.py +0 -0
  60. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/signals/config.py +0 -0
  61. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/signals/detector.py +0 -0
  62. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/signals/detectors.py +0 -0
  63. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/signals/result.py +0 -0
  64. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/spc_constants.py +0 -0
  65. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/study.py +0 -0
  66. {processbehavior-0.3.1 → processbehavior-0.3.2}/processbehavior/types.py +0 -0
  67. {processbehavior-0.3.1 → processbehavior-0.3.2}/pyproject.toml +0 -0
  68. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/__init__.py +0 -0
  69. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/conftest.py +0 -0
  70. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/bishop_analyses/vassds1analysis.json +0 -0
  71. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/bishop_analyses/vassds2analysis.json +0 -0
  72. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/bishop_analyses/vassds3analysis.json +0 -0
  73. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/coffee_capability_full_baseline.json +0 -0
  74. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/data/FILLWEIGHTDATA_800.csv +0 -0
  75. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/data/coffee_shop_demo_long.csv +0 -0
  76. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/missing_values/X_data.parquet +0 -0
  77. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/missing_values/X_metadata.json +0 -0
  78. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/missing_values/X_statistics.json +0 -0
  79. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/missing_values/mR_data.parquet +0 -0
  80. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/missing_values/mR_metadata.json +0 -0
  81. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/missing_values/mR_statistics.json +0 -0
  82. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/paired_xbar_s/S_data.parquet +0 -0
  83. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/paired_xbar_s/S_metadata.json +0 -0
  84. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/paired_xbar_s/S_statistics.json +0 -0
  85. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/paired_xbar_s/Xbar_data.parquet +0 -0
  86. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/paired_xbar_s/Xbar_metadata.json +0 -0
  87. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/paired_xbar_s/Xbar_statistics.json +0 -0
  88. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/paired_xmr_r/X_data.parquet +0 -0
  89. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/paired_xmr_r/X_metadata.json +0 -0
  90. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/paired_xmr_r/X_statistics.json +0 -0
  91. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/paired_xmr_r/mR_data.parquet +0 -0
  92. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/paired_xmr_r/mR_metadata.json +0 -0
  93. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/paired_xmr_r/mR_statistics.json +0 -0
  94. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/pathological_ordering/X_data.parquet +0 -0
  95. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/pathological_ordering/X_metadata.json +0 -0
  96. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/pathological_ordering/X_statistics.json +0 -0
  97. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/pathological_ordering/mR_data.parquet +0 -0
  98. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/pathological_ordering/mR_metadata.json +0 -0
  99. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/pathological_ordering/mR_statistics.json +0 -0
  100. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/residual_chart/S_data.parquet +0 -0
  101. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/residual_chart/S_metadata.json +0 -0
  102. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/residual_chart/S_statistics.json +0 -0
  103. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/single_obs_strata/X_data.parquet +0 -0
  104. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/single_obs_strata/X_metadata.json +0 -0
  105. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/single_obs_strata/X_statistics.json +0 -0
  106. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/single_obs_strata/mR_data.parquet +0 -0
  107. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/single_obs_strata/mR_metadata.json +0 -0
  108. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/single_obs_strata/mR_statistics.json +0 -0
  109. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/stratified_balanced/S_data.parquet +0 -0
  110. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/stratified_balanced/S_metadata.json +0 -0
  111. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/stratified_balanced/S_statistics.json +0 -0
  112. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/stratified_balanced/Xbar_data.parquet +0 -0
  113. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/stratified_balanced/Xbar_metadata.json +0 -0
  114. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/stratified_balanced/Xbar_statistics.json +0 -0
  115. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/stratified_uneven/S_data.parquet +0 -0
  116. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/stratified_uneven/S_metadata.json +0 -0
  117. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/stratified_uneven/S_statistics.json +0 -0
  118. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/stratified_uneven/Xbar_data.parquet +0 -0
  119. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/stratified_uneven/Xbar_metadata.json +0 -0
  120. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/stratified_uneven/Xbar_statistics.json +0 -0
  121. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/unstratified_small/X_data.parquet +0 -0
  122. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/unstratified_small/X_metadata.json +0 -0
  123. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/unstratified_small/X_statistics.json +0 -0
  124. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/unstratified_small/mR_data.parquet +0 -0
  125. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/unstratified_small/mR_metadata.json +0 -0
  126. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/fixtures/golden_masters/unstratified_small/mR_statistics.json +0 -0
  127. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_analysis_bugs.py +0 -0
  128. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_analysis_dataset.py +0 -0
  129. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_analysis_golden_master.py +0 -0
  130. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_analysis_result_contract.py +0 -0
  131. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_analysis_specifications.py +0 -0
  132. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_api_reference.py +0 -0
  133. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_beyond_limits_vectorized.py +0 -0
  134. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_bin_extremes.py +0 -0
  135. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_bishop_reference.py +0 -0
  136. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_by_parameter.py +0 -0
  137. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_by_permutations.py +0 -0
  138. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_calibration.py +0 -0
  139. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_capability.py +0 -0
  140. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_capability_chart.py +0 -0
  141. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_capability_window.py +0 -0
  142. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_chart_availability.py +0 -0
  143. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_chart_column_contract.py +0 -0
  144. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_chart_parsing.py +0 -0
  145. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_control_chart_figure.py +0 -0
  146. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_data_preparation.py +0 -0
  147. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_datasets.py +0 -0
  148. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_design_state_lineage.py +0 -0
  149. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_documented_rule_coverage.py +0 -0
  150. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_edge_cases.py +0 -0
  151. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_effects_calculator.py +0 -0
  152. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_effects_plotting.py +0 -0
  153. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_encode_rsg_vectorized.py +0 -0
  154. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_excel_export.py +0 -0
  155. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_execute_validation.py +0 -0
  156. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_focus.py +0 -0
  157. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_garbage_character_handling.py +0 -0
  158. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_init_cleaning.py +0 -0
  159. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_lane_boundaries.py +0 -0
  160. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_limits_vectorized.py +0 -0
  161. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_loss_function.py +0 -0
  162. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_maximum_information.py +0 -0
  163. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_na_in_factor_columns.py +0 -0
  164. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_notebook_ergonomics.py +0 -0
  165. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_p0_p2_regressions.py +0 -0
  166. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_performance.py +0 -0
  167. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_phased_limits.py +0 -0
  168. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_plotting.py +0 -0
  169. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_plotting_decorations.py +0 -0
  170. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_plotting_hardening.py +0 -0
  171. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_plotting_xaxis.py +0 -0
  172. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_process_behavior.py +0 -0
  173. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_recentered_mr_fix.py +0 -0
  174. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_reference_integrity.py +0 -0
  175. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_release_gate_0_1_0_checklist.py +0 -0
  176. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_residual_aliases.py +0 -0
  177. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_residual_calculator.py +0 -0
  178. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_residual_labels.py +0 -0
  179. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_residual_scope.py +0 -0
  180. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_resolve_by_grouping.py +0 -0
  181. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_result_signals.py +0 -0
  182. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_result_tabular.py +0 -0
  183. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_sampling_plan.py +0 -0
  184. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_sds.py +0 -0
  185. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_sds_detector.py +0 -0
  186. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_series_length.py +0 -0
  187. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_signal_detectors.py +0 -0
  188. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_signal_result_surface.py +0 -0
  189. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_signals.py +0 -0
  190. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_single_detection.py +0 -0
  191. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_solo_mr_residuals.py +0 -0
  192. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_spc_constants.py +0 -0
  193. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_srp_refactor.py +0 -0
  194. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_strata_companion.py +0 -0
  195. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_strata_sufficiency.py +0 -0
  196. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_stratified_insufficiency.py +0 -0
  197. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_supports_calibration.py +0 -0
  198. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_synthetic.py +0 -0
  199. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_types.py +0 -0
  200. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_validate_by_parameter.py +0 -0
  201. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_weco_integration.py +0 -0
  202. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_why_not.py +0 -0
  203. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_x_axis_layout.py +0 -0
  204. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_xbar_residual_center.py +0 -0
  205. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_xbar_s_options.py +0 -0
  206. {processbehavior-0.3.1 → processbehavior-0.3.2}/tests/test_xbar_stratified.py +0 -0
  207. {processbehavior-0.3.1 → processbehavior-0.3.2}/validation/PBTESTDATABASE_T100.csv +0 -0
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.2] - 2026-09-15
11
+
12
+ ### Fixed
13
+ - **Binning a heavily tied column no longer collapses to one bin.** 0.3.1 capped the
14
+ requested bin count at the number of distinct values *before* taking the quantiles, and on
15
+ a column of 3,000 ones and 1,000 twos the halved quantile positions both landed on 1, so
16
+ one bin fitted where 0.3.0 fitted two. When at least as many bins are requested as there
17
+ are distinct values, the fit now cuts at the midpoints between neighbouring values: one
18
+ bin per distinct value, every value in its own bin, with the message ``requested n bins,
19
+ ties produced k (one per distinct value)``. Requesting fewer bins than distinct values is
20
+ untouched (the original quantile / equal-width path). Found by the app's preview test.
21
+ - **The connecting line on a lane chart no longer crosses lane boundaries.** On an X or mR
22
+ chart with factors and ``by=[]`` (one chart, subgroups side by side), the last point of
23
+ each subgroup was joined to the first point of the next, so every boundary drew a steep
24
+ rise or drop that was only the sort order. The line is now its own trace with a gap at
25
+ each boundary, drawn under the markers; the markers trace keeps hover, legend and
26
+ signal highlighting exactly as before. Single-series and faceted charts are unchanged. #121
27
+
28
+ ### Added
29
+ - Docs: a user-guide page on series length and where sigma comes from (the position, the
30
+ n = 3..30 precision table from ``validation/short_series_bands.py`` with credit to
31
+ @rabujamra, the trending-series and not-one-process cautions, the formulation lesson from
32
+ #114, and the two-audiences position on run rules), and a user-guide page stating the
33
+ derived-variables contract (never raises, what counts as a violation, what bins promise,
34
+ what construction rejects).
35
+
10
36
  ## [0.3.1] - 2026-09-14
11
37
 
12
38
  ### Fixed
@@ -903,7 +929,8 @@ and may change between minor versions until 1.0.
903
929
  - `natsort >= 8.0`
904
930
  - `plotly >= 5.18, < 7`
905
931
 
906
- [Unreleased]: https://github.com/cnicholas/processbehavior/compare/v0.3.1...HEAD
932
+ [Unreleased]: https://github.com/cnicholas/processbehavior/compare/v0.3.2...HEAD
933
+ [0.3.2]: https://github.com/cnicholas/processbehavior/compare/v0.3.1...v0.3.2
907
934
  [0.3.1]: https://github.com/cnicholas/processbehavior/compare/v0.3.0...v0.3.1
908
935
  [0.3.0]: https://github.com/cnicholas/processbehavior/compare/v0.2.1...v0.3.0
909
936
  [0.2.1]: https://github.com/cnicholas/processbehavior/compare/v0.2.0...v0.2.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: processbehavior
3
- Version: 0.3.1
3
+ Version: 0.3.2
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
@@ -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.3.1'
50
+ __version__ = '0.3.2'
51
51
 
52
52
  # Result object
53
53
  from processbehavior.analysis_result import AnalysisResult
@@ -528,19 +528,24 @@ def _fit_edges(spec: Derivation, present_vals: pd.Series):
528
528
  edges = [-math.inf, mu - 2 * sigma, mu - sigma, mu + sigma, mu + 2 * sigma, math.inf]
529
529
  else:
530
530
  n = params['n']
531
- n_distinct = int(present_vals.nunique())
532
- if n_distinct and n > n_distinct:
533
- # More bins than distinct values can only produce empty bins.
534
- message = f'requested {n} bins, only {n_distinct} distinct values; fitted {n_distinct}'
535
- n = n_distinct
536
- if method == 'equal_width':
531
+ distinct = np.unique(present_vals.to_numpy(dtype=float))
532
+ if len(distinct) >= 2 and n >= len(distinct):
533
+ # More bins than distinct values: one bin per distinct value, cut at the
534
+ # midpoints between neighbours. Capping n and re-taking quantiles instead
535
+ # collapsed heavily tied columns (3,000 ones and 1,000 twos fitted one bin);
536
+ # the requested-n quantiles interpolated between the values and left mostly
537
+ # empty bins when n was large.
538
+ mids = (distinct[:-1] + distinct[1:]) / 2.0
539
+ edges = [float(distinct[0]), *[float(m) for m in mids], float(distinct[-1])]
540
+ message = f'requested {n} bins, ties produced {len(distinct)} (one per distinct value)'
541
+ elif method == 'equal_width':
537
542
  lo, hi = float(present_vals.min()), float(present_vals.max())
538
543
  edges = list(np.linspace(lo, hi, n + 1))
539
544
  else: # equal_freq
540
545
  qs = np.linspace(0.0, 1.0, n + 1)
541
546
  edges = list(np.unique(np.quantile(present_vals, qs)))
542
547
  if len(edges) - 1 != n:
543
- message = f'requested {params["n"]} bins, ties produced {len(edges) - 1}'
548
+ message = f'requested {n} bins, ties produced {len(edges) - 1}'
544
549
  return edges, message
545
550
 
546
551
 
@@ -35,6 +35,36 @@ if TYPE_CHECKING:
35
35
  # ---------------------------------------------------------------------------
36
36
 
37
37
 
38
+ def _lane_positions(lane_boundaries, chart_name: str, n_rows: int) -> list[int]:
39
+ """0-based row positions where a new lane starts (excluding row 0), for this chart.
40
+
41
+ Accepts the flat ``list[dict]`` form or the per-stratum ``dict`` form of the
42
+ ``lane_boundaries`` metadata; returns ``[]`` when there are no boundaries.
43
+ """
44
+ if not lane_boundaries:
45
+ return []
46
+ if isinstance(lane_boundaries, dict):
47
+ lane_boundaries = lane_boundaries.get(chart_name) or []
48
+ positions = sorted({int(b['position']) for b in lane_boundaries if 0 < int(b['position']) < n_rows})
49
+ return positions
50
+
51
+
52
+ def _with_gaps(values: list, positions: list[int], repeat: bool = False) -> list:
53
+ """Insert a gap entry before each position so Plotly breaks the line there.
54
+
55
+ For ``y`` the gap is ``None`` (a null y breaks the line). For ``x`` pass
56
+ ``repeat=True`` to insert the position's own x value instead, so the x array
57
+ stays numeric/categorical throughout and axis consumers never see a null.
58
+ """
59
+ out: list = []
60
+ cut = set(positions)
61
+ for i, v in enumerate(values):
62
+ if i in cut:
63
+ out.append(v if repeat else None)
64
+ out.append(v)
65
+ return out
66
+
67
+
38
68
  def _build_hover(
39
69
  data: pd.DataFrame,
40
70
  ctx: RenderContext,
@@ -136,10 +166,32 @@ def render_control_chart(
136
166
 
137
167
  # 2. Main data trace
138
168
  customdata, hovertemplate = _build_hover(data, ctx, value_col)
169
+ lane_positions = _lane_positions(spec.lane_boundaries, ctx.chart_name, len(data))
170
+ if lane_positions:
171
+ # Lane chart: the connecting line must not cross a lane boundary. The last point
172
+ # of one subgroup joined to the first of the next drew a steep "event" at every
173
+ # boundary that was only the sort order (#121). Draw the line as its own trace
174
+ # with a gap at each boundary, under the markers; the markers trace keeps the
175
+ # hover, customdata and legend exactly as before.
176
+ _add_trace(
177
+ fig,
178
+ go.Scatter(
179
+ x=_with_gaps(list(x_data), lane_positions, repeat=True),
180
+ y=_with_gaps(list(data[value_col]), lane_positions),
181
+ mode='lines',
182
+ line=dict(color=theme.data_color, width=ctx.line_width),
183
+ opacity=theme.data_opacity,
184
+ hoverinfo='skip',
185
+ showlegend=False,
186
+ name=f'{ctx.chart_name} (lanes)',
187
+ ),
188
+ row,
189
+ col,
190
+ )
139
191
  trace_kw = dict(
140
192
  x=x_data,
141
193
  y=data[value_col],
142
- mode='lines+markers',
194
+ mode='markers' if lane_positions else 'lines+markers',
143
195
  name=ctx.chart_name,
144
196
  marker=dict(size=ctx.marker_size, color=theme.data_color),
145
197
  line=dict(color=theme.data_color, width=ctx.line_width),
@@ -133,11 +133,18 @@ def test_equal_freq_labels_and_fitted_edges():
133
133
 
134
134
 
135
135
  def test_tie_drop_uses_fitted_count_labels_and_message():
136
+ """Fewer bins than distinct values, on a tied column: the quantile edges collide.
137
+
138
+ Requesting 4 bins on six 1s and one each of 2..5 puts three quartile edges on 1, so
139
+ only 2 bins fit. Labels are keyed on that fitted count, and the message says so.
140
+ (Requesting *at least* as many bins as distinct values now fits one per value; see
141
+ test_derivations_robustness.)
142
+ """
136
143
  tie = pd.Series([1, 1, 1, 1, 1, 1, 2, 3, 4, 5], dtype=float)
137
- r = evaluate(Derivation.bin('t', n=5, bin_labels='ordinal'), tie)
138
- assert r.fitted['n_bins'] == 3 # qcut dropped duplicate edges
139
- assert r.fitted['labels'] == ['Low', 'Medium', 'High'] # keyed on FITTED count, not 5
140
- assert 'requested 5 bins' in r.message and 'produced 3' in r.message
144
+ r = evaluate(Derivation.bin('t', n=4, bin_labels='ordinal'), tie)
145
+ assert r.fitted['n_bins'] == 2 # quantile edges collided on the tied value
146
+ assert r.fitted['labels'] == ['Low', 'High'] # keyed on FITTED count, not 4
147
+ assert 'requested 4 bins' in r.message and 'produced 2' in r.message
141
148
 
142
149
 
143
150
  @pytest.mark.parametrize('method', ['equal_freq', 'equal_width', 'sd'])
@@ -252,8 +259,8 @@ def test_validate_structured_results():
252
259
 
253
260
  def test_validate_label_count_against_fitted_not_requested():
254
261
  df = pd.DataFrame({'t': [1, 1, 1, 1, 1, 1, 2, 3, 4, 5]})
255
- # request 5 bins with 5 labels, but ties produce 3 bins -> mismatch caught
256
- res = validate(Derivation.bin('t', n=5, bin_labels=['a', 'b', 'c', 'd', 'e']), df)
262
+ # request 4 bins with 4 labels, but ties produce 2 bins -> mismatch caught
263
+ res = validate(Derivation.bin('t', n=4, bin_labels=['a', 'b', 'c', 'd']), df)
257
264
  assert res.ok is False
258
265
  assert any(i['code'] == 'label_count' for i in res.issues)
259
266
 
@@ -183,11 +183,25 @@ class TestBinRobustness:
183
183
  r = evaluate(B('x', method='sd'), pd.Series([-0.085, -0.085, -0.085, -0.085000001]))
184
184
  assert r.fitted['n_bins'] == 5 and r.values.notna().all()
185
185
 
186
- def test_more_bins_than_distinct_values_is_capped_with_a_message(self):
186
+ def test_more_bins_than_distinct_values_gives_one_bin_per_value(self):
187
187
  r = evaluate(B('x', n=1000), pd.Series(np.arange(1.0, 21.0)))
188
- assert r.fitted['n_bins'] == 20 and 'only 20 distinct values' in r.message
188
+ assert r.fitted['n_bins'] == 20 and 'ties produced 20 (one per distinct value)' in r.message
189
+ assert r.values.value_counts(sort=False).tolist() == [1] * 20
189
190
  r = evaluate(B('x', method='equal_width', n=9), pd.Series([1.0, 2.0, 3.0]))
190
191
  assert r.fitted['n_bins'] == 3 and r.values.notna().all()
192
+ assert r.fitted['edges'] == [1.0, 1.5, 2.5, 3.0]
193
+
194
+ def test_heavily_tied_column_keeps_one_bin_per_distinct_value(self):
195
+ """Regression: capping n before the quantiles collapsed 3,000 ones + 1,000 twos to one bin."""
196
+ tied = pd.Series([1.0] * 3000 + [2.0] * 1000)
197
+ r = evaluate(B('x', n=4), tied)
198
+ assert r.fitted['n_bins'] == 2 and 'ties' in r.message
199
+ assert r.values.value_counts(sort=False).tolist() == [3000, 1000]
200
+
201
+ def test_fewer_bins_than_distinct_values_is_untouched(self):
202
+ """n below the distinct count takes the original quantile / linspace path."""
203
+ r = evaluate(B('x', n=4), pd.Series(np.arange(1.0, 21.0)))
204
+ assert r.fitted['edges'] == [1.0, 5.75, 10.5, 15.25, 20.0] and r.message is None
191
205
 
192
206
  def test_ordinal_beyond_five_bins_says_so(self):
193
207
  r = evaluate(B('x', n=6, bin_labels='ordinal'), pd.Series(np.arange(1.0, 61.0)))
@@ -0,0 +1,103 @@
1
+ """On a lane chart the connecting line breaks at every lane boundary (#121).
2
+
3
+ One subgroup's last point used to be joined to the next subgroup's first point, so every
4
+ boundary showed a steep rise or drop that was only the sort order. The line is now its own
5
+ trace with a gap at each boundary; the markers trace keeps hover, customdata and legend.
6
+ """
7
+
8
+ import numpy as np
9
+ import pandas as pd
10
+
11
+ import processbehavior as pb
12
+
13
+
14
+ def _lane_study(n_lanes=3, T=4):
15
+ rng = np.random.default_rng(121)
16
+ rows = []
17
+ for k in range(n_lanes):
18
+ base = 100 * (k + 1)
19
+ for t in range(1, T + 1):
20
+ rows.append({'unit': f'U{k + 1}', 't': t, 'y': base + t + rng.normal(0, 0.1)})
21
+ return pb.formulate(pd.DataFrame(rows), response='y', factors=['unit'], time='t')
22
+
23
+
24
+ def _traces(fig):
25
+ return {t.name: t for t in fig.data}
26
+
27
+
28
+ def test_lane_chart_draws_the_line_with_one_gap_per_boundary():
29
+ fig = _lane_study(n_lanes=3, T=4).execute(chart='X', by=[], companion=True).plot(chart='X')
30
+ traces = _traces(fig)
31
+ line = traces['X (lanes)']
32
+ markers = traces['X']
33
+ assert line.mode == 'lines' and markers.mode == 'markers'
34
+ ys = list(line.y)
35
+ assert ys.count(None) == 2, 'three lanes -> two boundaries -> two gaps'
36
+ assert len([y for y in ys if y is not None]) == 12 and len(markers.y) == 12
37
+ # The gaps sit exactly at the lane starts (positions 4 and 8).
38
+ assert [i for i, y in enumerate(ys) if y is None] == [4, 9]
39
+
40
+
41
+ def test_lines_never_join_two_lanes():
42
+ """No drawn segment spans a boundary: every consecutive non-gap pair is within a lane."""
43
+ fig = _lane_study(n_lanes=4, T=3).execute(chart='X', by=[], companion=True).plot(chart='X')
44
+ line = _traces(fig)['X (lanes)']
45
+ ys = list(line.y)
46
+ # Lanes are at 100, 200, 300, 400 (+ small t); any segment crossing a lane would jump ~100.
47
+ for a, b in zip(ys, ys[1:], strict=False):
48
+ if a is not None and b is not None:
49
+ assert abs(a - b) < 10, (a, b)
50
+
51
+
52
+ def test_markers_keep_hover_and_the_line_has_none():
53
+ fig = _lane_study().execute(chart='X', by=[], companion=True).plot(chart='X')
54
+ traces = _traces(fig)
55
+ assert traces['X'].hovertemplate
56
+ assert traces['X (lanes)'].hoverinfo == 'skip' and traces['X (lanes)'].showlegend is False
57
+
58
+
59
+ def test_single_series_chart_is_unchanged():
60
+ """No lanes -> the original single lines+markers trace, no extra trace."""
61
+ df = pd.DataFrame({'t': range(1, 13), 'y': np.arange(12, dtype=float)})
62
+ fig = pb.formulate(df, response='y', time='t').execute(chart='X', companion=True).plot(chart='X')
63
+ names = [t.name for t in fig.data]
64
+ assert 'X (lanes)' not in names
65
+ assert _traces(fig)['X'].mode == 'lines+markers'
66
+
67
+
68
+ def test_faceted_charts_are_unchanged():
69
+ """by=['unit'] gives one panel per lane: no boundaries inside a panel, so no gaps."""
70
+ fig = _lane_study().execute(chart='X', by=['unit'], companion=True).plot(chart='X', facet=True, ncols=3)
71
+ assert not [t for t in fig.data if t.name and t.name.endswith('(lanes)')]
72
+
73
+
74
+ # ---------------------------------------------------------------------------
75
+ # The helpers, directly: both boundary shapes, out-of-range positions, both gap modes
76
+ # ---------------------------------------------------------------------------
77
+
78
+
79
+ def test_lane_positions_accepts_flat_and_per_stratum_boundaries():
80
+ from processbehavior.plotting.renderers import _lane_positions
81
+
82
+ flat = [{'position': 4, 'label': 'B'}, {'position': 8, 'label': 'C'}]
83
+ assert _lane_positions(flat, 'X', n_rows=12) == [4, 8]
84
+ per_stratum = {'X': flat, 'mR': [{'position': 3}]}
85
+ assert _lane_positions(per_stratum, 'X', n_rows=12) == [4, 8]
86
+ assert _lane_positions(per_stratum, 'mR', n_rows=12) == [3]
87
+ assert _lane_positions(per_stratum, 'Histogram', n_rows=12) == []
88
+ assert _lane_positions(None, 'X', n_rows=12) == [] and _lane_positions([], 'X', n_rows=12) == []
89
+
90
+
91
+ def test_lane_positions_ignores_row_zero_and_out_of_range():
92
+ from processbehavior.plotting.renderers import _lane_positions
93
+
94
+ raw = [{'position': 0}, {'position': 5}, {'position': 5}, {'position': 12}, {'position': 99}]
95
+ assert _lane_positions(raw, 'X', n_rows=12) == [5]
96
+
97
+
98
+ def test_with_gaps_inserts_none_for_y_and_repeats_x():
99
+ from processbehavior.plotting.renderers import _with_gaps
100
+
101
+ assert _with_gaps([10, 11, 12, 13], [2]) == [10, 11, None, 12, 13]
102
+ assert _with_gaps(['a', 'b', 'c', 'd'], [2], repeat=True) == ['a', 'b', 'c', 'c', 'd']
103
+ assert _with_gaps([1, 2, 3], []) == [1, 2, 3]
File without changes