DaSPi 2.1.0__tar.gz → 2.2.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.
Files changed (174) hide show
  1. {daspi-2.1.0 → daspi-2.2.0}/PKG-INFO +1 -1
  2. {daspi-2.1.0 → daspi-2.2.0}/daspi/anova/model.py +113 -33
  3. {daspi-2.1.0 → daspi-2.2.0}/daspi/anova/tables.py +16 -2
  4. {daspi-2.1.0 → daspi-2.2.0}/daspi/constants.py +1 -1
  5. {daspi-2.1.0 → daspi-2.2.0}/daspi/plotlib/plotter.py +27 -13
  6. {daspi-2.1.0 → daspi-2.2.0}/pyproject.toml +1 -1
  7. {daspi-2.1.0 → daspi-2.2.0}/tests/anova_model_test.py +22 -2
  8. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Formatting_Features_Test_alignments_basic.png +0 -0
  9. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Formatting_Features_Test_all_alignments.png +0 -0
  10. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Formatting_Features_Test_angles_basic.png +0 -0
  11. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Formatting_Features_Test_callable_formatters.png +0 -0
  12. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Formatting_Features_Test_combined_all_features.png +0 -0
  13. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Formatting_Features_Test_extreme_angles.png +0 -0
  14. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Formatting_Features_Test_formatters_basic.png +0 -0
  15. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Formatting_Features_Test_formatters_scientific.png +0 -0
  16. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Formatting_Features_Test_multivariate_formatting.png +0 -0
  17. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Formatting_Features_Test_orientation_mapping.png +0 -0
  18. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Formatting_Features_Test_percentage_formatter.png +0 -0
  19. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/GaussianKDEContourUnivariate_comparison_with_violin.png +0 -0
  20. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/GaussianKDEContourUnivariate_custom_color.png +0 -0
  21. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/GaussianKDEContourUnivariate_custom_width.png +0 -0
  22. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/GaussianKDEContourUnivariate_joint_chart_mixed_plots.png +0 -0
  23. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/GaussianKDEContourUnivariate_multivariate_chart_facets.png +0 -0
  24. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/GaussianKDEContourUnivariate_resolution_high.png +0 -0
  25. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/GaussianKDEContourUnivariate_single_chart_horizontal.png +0 -0
  26. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/GaussianKDEContourUnivariate_single_chart_no_fade.png +0 -0
  27. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/GaussianKDEContourUnivariate_single_chart_no_fill.png +0 -0
  28. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/GaussianKDEContourUnivariate_single_chart_simple.png +0 -0
  29. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/GaussianKDEContourUnivariate_single_chart_with_hue.png +0 -0
  30. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/JointChart_bar_stacking.png +0 -0
  31. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/JointChart_pareto_marked.png +0 -0
  32. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/JointChart_probability_dists-prob.png +0 -0
  33. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/JointChart_regression_kde.png +0 -0
  34. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/MultivariateChart_full-stripes.png +0 -0
  35. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/MultivariateChart_full.png +0 -0
  36. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Precasts_bivariate-univariate-charts_contour-mean-test.png +0 -0
  37. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Precasts_bivariate-univariate-charts_mean-test.png +0 -0
  38. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Precasts_bivariate-univariate-charts_simple.png +0 -0
  39. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Precasts_bivariate-univariate-charts_smoothed.png +0 -0
  40. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Precasts_bivariate-univariate-charts_user.png +0 -0
  41. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Precasts_pair_comparison_charts_basic.png +0 -0
  42. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Precasts_parameter_relevance_charts_basic.png +0 -0
  43. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Precasts_process-capabity-analysis-charts_hue.png +0 -0
  44. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Precasts_residual_charts_basic.png +0 -0
  45. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_KDE_multiple.png +0 -0
  46. daspi-2.2.0/tests/charts/SingleChart_KDE_ridge.png +0 -0
  47. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_beeswarm_multiple.png +0 -0
  48. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_beeswarm_simple.png +0 -0
  49. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_blandaltman_simple.png +0 -0
  50. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_blandaltman_user.png +0 -0
  51. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_box_fill.png +0 -0
  52. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_box_multiple.png +0 -0
  53. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_box_simple.png +0 -0
  54. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_categorical-float-features_simple.png +0 -0
  55. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_errorbar_mean-test.png +0 -0
  56. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_errorbar_sem.png +0 -0
  57. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_errorbar_std-test.png +0 -0
  58. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_errorbar_var-test.png +0 -0
  59. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_jitter_multiple.png +0 -0
  60. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_jitter_simple.png +0 -0
  61. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_line_hue.png +0 -0
  62. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_line_hue_size.png +0 -0
  63. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_pareto_simple.png +0 -0
  64. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_pareto_transposed.png +0 -0
  65. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_quantiles_multiple.png +0 -0
  66. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_quantiles_simple.png +0 -0
  67. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_scatter_full.png +0 -0
  68. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_scatter_hue-shape.png +0 -0
  69. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_scatter_hue-size.png +0 -0
  70. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_scatter_hue.png +0 -0
  71. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_scatter_shape.png +0 -0
  72. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_scatter_size-shape.png +0 -0
  73. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_scatter_size.png +0 -0
  74. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_violin_mono.png +0 -0
  75. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_violin_multiple.png +0 -0
  76. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_violin_simple.png +0 -0
  77. {daspi-2.1.0 → daspi-2.2.0}/tests/plotlib_visual_test.py +32 -0
  78. {daspi-2.1.0 → daspi-2.2.0}/LICENSE +0 -0
  79. {daspi-2.1.0 → daspi-2.2.0}/README.md +0 -0
  80. {daspi-2.1.0 → daspi-2.2.0}/daspi/__init__.py +0 -0
  81. {daspi-2.1.0 → daspi-2.2.0}/daspi/_typing.py +0 -0
  82. {daspi-2.1.0 → daspi-2.2.0}/daspi/_version.py +0 -0
  83. {daspi-2.1.0 → daspi-2.2.0}/daspi/anova/__init__.py +0 -0
  84. {daspi-2.1.0 → daspi-2.2.0}/daspi/anova/convert.py +0 -0
  85. {daspi-2.1.0 → daspi-2.2.0}/daspi/config.py +0 -0
  86. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/__init__.py +0 -0
  87. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/ancova.csv +0 -0
  88. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/anova.csv +0 -0
  89. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/anova2.csv +0 -0
  90. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/anova2_unbalanced.csv +0 -0
  91. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/anova3.csv +0 -0
  92. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/anova3_unbalanced.csv +0 -0
  93. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/carpet_full-factorial.csv +0 -0
  94. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/covariate.csv +0 -0
  95. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/drop_card.csv +0 -0
  96. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/eeprom_full-factorial.csv +0 -0
  97. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/eeprom_partial-factorial.csv +0 -0
  98. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/grnr_adjustment.csv +0 -0
  99. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/grnr_layer_thickness.csv +0 -0
  100. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/grnr_spc.csv +0 -0
  101. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/iris.csv +0 -0
  102. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/mpg.csv +0 -0
  103. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/painkillers-dissolution.csv +0 -0
  104. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/partial_factorial.csv +0 -0
  105. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/penguins.csv +0 -0
  106. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/process_capability.mpx +0 -0
  107. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/salt_sticks.csv +0 -0
  108. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/shoe-sole.csv +0 -0
  109. {daspi-2.1.0 → daspi-2.2.0}/daspi/datasets/tips.csv +0 -0
  110. {daspi-2.1.0 → daspi-2.2.0}/daspi/doe/__init__.py +0 -0
  111. {daspi-2.1.0 → daspi-2.2.0}/daspi/doe/build.py +0 -0
  112. {daspi-2.1.0 → daspi-2.2.0}/daspi/plotlib/__init__.py +0 -0
  113. {daspi-2.1.0 → daspi-2.2.0}/daspi/plotlib/appearance.py +0 -0
  114. {daspi-2.1.0 → daspi-2.2.0}/daspi/plotlib/chart.py +0 -0
  115. {daspi-2.1.0 → daspi-2.2.0}/daspi/plotlib/classify.py +0 -0
  116. {daspi-2.1.0 → daspi-2.2.0}/daspi/plotlib/facets.py +0 -0
  117. {daspi-2.1.0 → daspi-2.2.0}/daspi/plotlib/precast.py +0 -0
  118. {daspi-2.1.0 → daspi-2.2.0}/daspi/plotlib/styles/daspi-dark.mplstyle +0 -0
  119. {daspi-2.1.0 → daspi-2.2.0}/daspi/plotlib/styles/daspi.mplstyle +0 -0
  120. {daspi-2.1.0 → daspi-2.2.0}/daspi/plotlib/styles/ggplot2.mplstyle +0 -0
  121. {daspi-2.1.0 → daspi-2.2.0}/daspi/plotlib/styles/seaborn.mplstyle +0 -0
  122. {daspi-2.1.0 → daspi-2.2.0}/daspi/requirements.txt +0 -0
  123. {daspi-2.1.0 → daspi-2.2.0}/daspi/statistics/__init__.py +0 -0
  124. {daspi-2.1.0 → daspi-2.2.0}/daspi/statistics/confidence.py +0 -0
  125. {daspi-2.1.0 → daspi-2.2.0}/daspi/statistics/estimation.py +0 -0
  126. {daspi-2.1.0 → daspi-2.2.0}/daspi/statistics/hypothesis.py +0 -0
  127. {daspi-2.1.0 → daspi-2.2.0}/daspi/statistics/montecarlo.py +0 -0
  128. {daspi-2.1.0 → daspi-2.2.0}/daspi/strings.py +0 -0
  129. {daspi-2.1.0 → daspi-2.2.0}/tests/ANOVA_SS_Fehler.mpx +0 -0
  130. {daspi-2.1.0 → daspi-2.2.0}/tests/ANOVA_SS_Fehler.mpx.bak +0 -0
  131. {daspi-2.1.0 → daspi-2.2.0}/tests/GageRnR.mpx +0 -0
  132. {daspi-2.1.0 → daspi-2.2.0}/tests/GageRnR.mpx.bak +0 -0
  133. {daspi-2.1.0 → daspi-2.2.0}/tests/__init__.py +0 -0
  134. {daspi-2.1.0 → daspi-2.2.0}/tests/anova_convert_test.py +0 -0
  135. {daspi-2.1.0 → daspi-2.2.0}/tests/anova_tables_test.py +0 -0
  136. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/JointChart_kdes_kde-mean.png +0 -0
  137. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/JointChart_kdes_mixed-kde.png +0 -0
  138. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/JointChart_probability_norm-prob.png +0 -0
  139. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Precasts_gage_rnr_model_basic.png +0 -0
  140. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/Precasts_gage_study_model_basic.png +0 -0
  141. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_KDE_simple.png +0 -0
  142. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_scatter_simple-stripes-65ci.png +0 -0
  143. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_scatter_simple-stripes-99ci.png +0 -0
  144. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_scatter_simple-stripes.png +0 -0
  145. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_scatter_simple.png +0 -0
  146. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_scatter_transposed.png +0 -0
  147. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_violine-contour_simple.png +0 -0
  148. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_violine_mono.png +0 -0
  149. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_violine_multiple.png +0 -0
  150. {daspi-2.1.0 → daspi-2.2.0}/tests/charts/SingleChart_violine_simple.png +0 -0
  151. {daspi-2.1.0 → daspi-2.2.0}/tests/config_test.py +0 -0
  152. {daspi-2.1.0 → daspi-2.2.0}/tests/data/anova3_result.csv +0 -0
  153. {daspi-2.1.0 → daspi-2.2.0}/tests/data/dists_10-samples.csv +0 -0
  154. {daspi-2.1.0 → daspi-2.2.0}/tests/data/dists_25-samples.csv +0 -0
  155. {daspi-2.1.0 → daspi-2.2.0}/tests/data/gage_rnr.csv +0 -0
  156. {daspi-2.1.0 → daspi-2.2.0}/tests/data/gage_study.csv +0 -0
  157. {daspi-2.1.0 → daspi-2.2.0}/tests/data/generate_dists.py +0 -0
  158. {daspi-2.1.0 → daspi-2.2.0}/tests/data/grnr_d-optimal.csv +0 -0
  159. {daspi-2.1.0 → daspi-2.2.0}/tests/data/uncertainties.csv +0 -0
  160. {daspi-2.1.0 → daspi-2.2.0}/tests/doe_build_test.py +0 -0
  161. {daspi-2.1.0 → daspi-2.2.0}/tests/plotlib_appearance_test.py +0 -0
  162. {daspi-2.1.0 → daspi-2.2.0}/tests/plotlib_chart_test.py +0 -0
  163. {daspi-2.1.0 → daspi-2.2.0}/tests/plotlib_classify_test.py +0 -0
  164. {daspi-2.1.0 → daspi-2.2.0}/tests/plotlib_facets_test.py +0 -0
  165. {daspi-2.1.0 → daspi-2.2.0}/tests/plotlib_plotter_test.py +0 -0
  166. {daspi-2.1.0 → daspi-2.2.0}/tests/plotlib_templates_test.py +0 -0
  167. {daspi-2.1.0 → daspi-2.2.0}/tests/r2_pred.mpx +0 -0
  168. {daspi-2.1.0 → daspi-2.2.0}/tests/r2_pred.mpx.bak +0 -0
  169. {daspi-2.1.0 → daspi-2.2.0}/tests/statistics_confidence_test.py +0 -0
  170. {daspi-2.1.0 → daspi-2.2.0}/tests/statistics_estimation_test.py +0 -0
  171. {daspi-2.1.0 → daspi-2.2.0}/tests/statistics_hypothesis_test.py +0 -0
  172. {daspi-2.1.0 → daspi-2.2.0}/tests/statistics_montecarlo_test.py +0 -0
  173. {daspi-2.1.0 → daspi-2.2.0}/tests/strings_test.py +0 -0
  174. {daspi-2.1.0 → daspi-2.2.0}/tests/test_full_factorial_design_builder.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: DaSPi
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: Visualize and analyze your data with DaSPi. This package is designed for users who want to find relevant influencing factors in processes and validate improvements.
5
5
  Author-Email: =?utf-8?q?Reto_J=C3=A4ggli?= <reto@jaeggli.email>
6
6
  License: MIT
@@ -76,7 +76,8 @@ import itertools
76
76
  import warnings
77
77
  from abc import ABC, abstractmethod
78
78
  from collections.abc import Generator
79
- from typing import Any, Literal, LiteralString, Self
79
+ from functools import lru_cache
80
+ from typing import Any, Literal, LiteralString, Self, overload
80
81
 
81
82
  import numpy as np
82
83
  import pandas as pd
@@ -110,19 +111,61 @@ __all__ = [
110
111
  'GageStudyModel',
111
112
  'GeneralizedLinearModel',
112
113
  'LinearModel',
113
- 'get_order',
114
114
  'hierarchical',
115
- 'is_main_parameter',]
115
+ 'interaction_order',
116
+ 'is_main_parameter',
117
+ ]
116
118
 
117
119
 
120
+ @overload
121
+ def _drop_intercept(data: DataFrame) -> DataFrame: ...
122
+ @overload
123
+ def _drop_intercept(data: Series) -> Series: ...
124
+ def _drop_intercept(data: DataFrame | Series) -> DataFrame | Series:
125
+ """Drop the intercept index from a DataFrame or Series if it exists.
126
+
127
+ Parameters
128
+ ----------
129
+ data : DataFrame or Series
130
+ The input data from which to drop the intercept.
131
+
132
+ Returns
133
+ -------
134
+ DataFrame or Series
135
+ The input data with the intercept dropped, if it was present.
136
+ """
137
+ mask = ~data.index.str.contains(ANOVA.INTERCEPT, regex=False)
138
+ if isinstance(data, Series):
139
+ data = data[mask]
140
+ else:
141
+ data = data.loc[mask, :]
142
+ return data.copy()
143
+
118
144
  def is_main_parameter(parameter: str) -> bool:
119
145
  """Check if given parameter is a main parameter (intercept is
120
146
  excluded)."""
121
147
  return parameter != ANOVA.INTERCEPT and ANOVA.SEP not in parameter
122
148
 
123
- def get_order(parameter: str) -> int:
149
+ def interaction_order(parameter: str) -> int:
124
150
  """Get the order of a parameter (i.e., how many interactions it
125
- contains)."""
151
+ contains).
152
+
153
+ Parameters
154
+ ----------
155
+ parameter : str
156
+ The parameter name to check.
157
+
158
+ Returns
159
+ -------
160
+ int
161
+ The interaction order of the parameter.
162
+
163
+ Examples
164
+ --------
165
+ ```python
166
+ interaction_order('x1') # Returns 1
167
+ interaction_order('x1:x2') # Returns 2
168
+ ```"""
126
169
  return parameter.count(ANOVA.SEP) + 1
127
170
 
128
171
  def hierarchical(parameters: list[str]) -> list[str]:
@@ -139,21 +182,28 @@ def hierarchical(parameters: list[str]) -> list[str]:
139
182
  -------
140
183
  h_parameters : list of str
141
184
  Sorted factors for hierarchical model"""
142
-
185
+ return list(_hierarchical_cached(tuple(parameters)))
186
+
187
+ @lru_cache(maxsize=128)
188
+ def _hierarchical_cached(parameters: tuple[str, ...]) -> tuple[str, ...]:
189
+ """Cached implementation of `hierarchical`. Repeated calls with the
190
+ same (unchanged) set of terms, e.g. from `is_hierarchical` during a
191
+ single fit, are then resolved without recomputing all interaction
192
+ subsets."""
143
193
  h_parameters = set(parameters)
144
194
  for parameter in parameters:
145
195
  split = parameter.split(ANOVA.SEP)
146
- n_splits = len(split)
147
- for s in split:
148
- h_parameters.add(s)
149
- if n_splits <= ANOVA.SMALLEST_INTERACTION:
196
+ _order = len(split)
197
+ h_parameters.update(split)
198
+ if _order <= ANOVA.SMALLEST_INTERACTION_ORDER:
150
199
  continue
151
200
 
152
- for i in range(ANOVA.SMALLEST_INTERACTION, n_splits):
153
- for combo in map(ANOVA.SEP.join, itertools.combinations(split, i)):
154
- h_parameters.add(combo)
201
+ h_parameters.update(
202
+ ANOVA.SEP.join(combo)
203
+ for i in range(ANOVA.SMALLEST_INTERACTION_ORDER, _order)
204
+ for combo in itertools.combinations(split, i))
155
205
 
156
- return sorted(sorted(h_parameters), key=get_order)
206
+ return tuple(sorted(h_parameters, key=lambda p: (interaction_order(p), p)))
157
207
 
158
208
 
159
209
  class BaseHTMLReprModel(ABC):
@@ -671,21 +721,55 @@ class LinearModel(BaseHTMLReprModel):
671
721
  """Check if current fitted model is hierarchical."""
672
722
  return all(term in self.terms for term in hierarchical(self.terms))
673
723
 
674
- def effects(self) -> Series:
724
+ def effects(self, include_intercept: bool = True) -> Series:
675
725
  """Calculates the impact of each term on the target. The
676
726
  effects are described as absolute number of the parameter
677
- coefficients devided by its standard error."""
727
+ coefficients devided by its standard error.
728
+
729
+ Parameters
730
+ ----------
731
+ include_intercept : bool, optional
732
+ If False, the intercept is dropped from the returned
733
+ Series, by default True.
734
+
735
+ Returns
736
+ -------
737
+ Series
738
+ A Pandas Series containing the effects of each term on the
739
+ target variable. The index of the Series corresponds to the
740
+ term names, and the values represent the calculated effects.
741
+ """
678
742
  if self._effects.empty:
679
743
  self._effects = terms_effect(self.model)
680
744
  effects = self._effects.copy().rename(index=self.term_map)
745
+ if not include_intercept:
746
+ effects = _drop_intercept(effects)
681
747
  return effects
682
748
 
683
- def p_values(self) -> 'Series[float]':
749
+ def p_values(self, include_intercept: bool = True) -> 'Series[float]':
684
750
  """Get P-value for significance of adding model terms using
685
- anova typ III table for current model."""
751
+ anova typ III table for current model.
752
+
753
+ Parameters
754
+ ----------
755
+ include_intercept : bool, optional
756
+ If False, the intercept is dropped from the returned
757
+ Series, by default True.
758
+
759
+ Returns
760
+ -------
761
+ Series
762
+ A Pandas Series containing the p-values of each term on the
763
+ target variable. The index of the Series corresponds to the
764
+ term names, and the values represent the calculated
765
+ p-values.
766
+ """
686
767
  if self._p_values.empty:
687
768
  self._p_values = terms_probability(self.model)
688
- return self._p_values.copy().rename(index=self.term_map)
769
+ p_values = self._p_values.copy().rename(index=self.term_map)
770
+ if not include_intercept:
771
+ p_values = _drop_intercept(p_values)
772
+ return p_values
689
773
 
690
774
  def least_parameter(self) -> str:
691
775
  """Get the parameter name with the least effect or the least
@@ -706,20 +790,15 @@ class LinearModel(BaseHTMLReprModel):
706
790
  the term name with the least p-value for the F-stats coming from
707
791
  current ANOVA table.
708
792
  """
709
- p_values = self.p_values().copy()
710
- has_intercept = ANOVA.INTERCEPT in p_values.index
793
+ include_intercept = not self.skip_intercept_as_least
794
+ p_values = self.p_values(include_intercept)
711
795
  if any(p_values.isna()):
712
- effects = self.effects().copy()
713
- if has_intercept and self.skip_intercept_as_least:
714
- effects = effects.drop(ANOVA.INTERCEPT)
715
- if any(effects.isna()):
716
- leasts = effects[effects.isna()]
717
- else:
718
- leasts = effects[effects == effects.min()]
719
- least = max(leasts.index, key=get_order)
796
+ effects = self.effects(include_intercept).copy()
797
+ effect_mask = effects.isna()
798
+ if not any(effect_mask):
799
+ effect_mask = effects == effects.min()
800
+ least = max(effects[effect_mask].index, key=interaction_order )
720
801
  else:
721
- if has_intercept and self.skip_intercept_as_least:
722
- p_values = p_values.drop(ANOVA.INTERCEPT)
723
802
  least = p_values.iloc[::-1].idxmax()
724
803
  return str(least)
725
804
 
@@ -945,7 +1024,8 @@ class LinearModel(BaseHTMLReprModel):
945
1024
  get_term_name(self.feature_map.get(x, x))
946
1025
  for x in parameter.split(ANOVA.SEP))
947
1026
  assert term in self.terms, (
948
- f'Given term {term} is not in model')
1027
+ f'Given term {term} (or "{parameter}") is not in model, '
1028
+ f'got: {self.terms}')
949
1029
 
950
1030
  self.excluded.add(term)
951
1031
  return self
@@ -985,7 +1065,7 @@ class LinearModel(BaseHTMLReprModel):
985
1065
  UserWarning)
986
1066
  self.excluded.discard(term)
987
1067
  return self
988
-
1068
+
989
1069
  def recursive_elimination(
990
1070
  self,
991
1071
  rsquared_max: float = 0.99,
@@ -97,9 +97,23 @@ def terms_effect(model: RegressionResultsWrapper) -> Series:
97
97
  Series
98
98
  A pandas Series containing the effects of each factor
99
99
  on the target variable.
100
+
101
+ Notes
102
+ -----
103
+ The effect size is calculated as the absolute value of the parameter
104
+ coefficient divided by its standard error. This is a measure of the
105
+ strength of the relationship between the predictor and the target
106
+ variable, with larger values indicating a stronger effect.
107
+
108
+ The index of the returned Series corresponds to the factor names in
109
+ the model, and the values represent the calculated effect sizes.
110
+
111
+ Only terms that are present in the model are included in the output,
112
+ which is determined by filtering the effects based on the unique
113
+ factor names derived from the model's design information.
100
114
  """
101
115
  params: Series = model.params
102
- se: Series = model.scale
116
+ terms = model.model.data.design_info.term_names
103
117
  names_map = {n: get_term_name(n) for n in params.index}
104
118
  se = (model.bse
105
119
  .rename(index=names_map)
@@ -112,7 +126,7 @@ def terms_effect(model: RegressionResultsWrapper) -> Series:
112
126
  .groupby(level=0)
113
127
  .sum())
114
128
  effects = params.abs() / se
115
- effects = effects[uniques(names_map.values())]
129
+ effects = effects[uniques(n for n in names_map.values() if n in terms)] # Filter out terms that are not in the model (this can happen when the model is imbalanced and some terms are dropped)
116
130
  effects.name = ANOVA.EFFECTS
117
131
  effects.index.name = ANOVA.FACTORS
118
132
  return effects
@@ -549,7 +549,7 @@ class _Anova_:
549
549
  """"Default name for source used in ANOVA table."""
550
550
  VIF: Literal['VIF'] = 'VIF'
551
551
  """"Default column name for VIF values in ANOVA table."""
552
- SMALLEST_INTERACTION: int = 2
552
+ SMALLEST_INTERACTION_ORDER: int = 2
553
553
  """Smallest possible interaction"""
554
554
  RESIDUAL: Literal['Residual'] = 'Residual'
555
555
  """Name in anova table for residual (not explained) values."""
@@ -2088,7 +2088,7 @@ class TransformPlotter(Plotter):
2088
2088
  self.skip_na = skip_na
2089
2089
 
2090
2090
  df = pd.concat(
2091
- [self.transform(f, t) for f, t in self.feature_grouped(source)],
2091
+ (self.transform(f, t) for f, t in self.feature_grouped(source)),
2092
2092
  axis=0,
2093
2093
  ignore_index=True)
2094
2094
 
@@ -2120,24 +2120,29 @@ class TransformPlotter(Plotter):
2120
2120
  target_data : pandas Series
2121
2121
  feature grouped target data used for transformation.
2122
2122
  """
2123
+ feature_data = self._f_base
2123
2124
  if self.feature and self.feature != PLOTTER.TRANSFORMED_FEATURE:
2124
- for i, (f_value, group) in enumerate(
2125
- source.groupby(self.feature, sort=True, observed=True),
2126
- start=DEFAULT.FEATURE_BASE):
2125
+ use_feature_value = any((
2126
+ pd.api.types.is_numeric_dtype(source[self.feature]),
2127
+ pd.api.types.is_datetime64_dtype(source[self.feature]),
2128
+ pd.api.types.is_timedelta64_dtype(source[self.feature])))
2129
+
2130
+ grouped = source.groupby(self.feature, sort=True, observed=True)
2131
+ for feature_value, group in grouped:
2127
2132
  target_data = group[self.target]
2128
2133
  if self.skip_na and getattr(target_data.isna(), self.skip_na)():
2129
2134
  continue
2130
2135
 
2131
- self._original_f_values = self._original_f_values + (f_value, )
2132
- if isinstance(f_value, (float, int, pd.Timestamp)):
2133
- feature_data = f_value
2136
+ self._original_f_values += (feature_value,)
2137
+ if use_feature_value:
2138
+ yield feature_value, target_data
2134
2139
  else:
2135
- feature_data = i
2136
- yield feature_data, target_data
2140
+ yield feature_data, target_data
2141
+ feature_data += 1
2142
+
2137
2143
  else:
2138
2144
  self.feature = PLOTTER.TRANSFORMED_FEATURE
2139
2145
  self._original_f_values = (self._f_base, )
2140
- feature_data = self._f_base
2141
2146
  target_data = source[self.target]
2142
2147
  yield feature_data, target_data
2143
2148
 
@@ -4294,6 +4299,13 @@ class GaussianKDE(SpreadOpacity, TransformPlotter):
4294
4299
  plot.
4295
4300
  target : str
4296
4301
  Column name of the target variable for the plot.
4302
+ feature : str, optional
4303
+ Column name of the feature variable for the plot, by default ''.
4304
+ Please note that this class is designed for univariate plots, so
4305
+ by default, all curves will be drawn on the same feature axis.
4306
+ If you want to draw curves on different feature axes, change the
4307
+ "ignore_feature" parameter to False and provide a feature column
4308
+ in the source DataFrame.
4297
4309
  stretch : float, optional
4298
4310
  Factor by which the curve was stretched in height,
4299
4311
  by default 1.
@@ -4446,10 +4458,11 @@ class GaussianKDE(SpreadOpacity, TransformPlotter):
4446
4458
  self,
4447
4459
  source: DataFrame,
4448
4460
  target: str,
4461
+ feature: str = '',
4449
4462
  stretch: float = 1,
4450
4463
  height: float | None = None,
4451
4464
  skip_na: Literal['all', 'any'] | None = None,
4452
- ignore_feature: bool = True,
4465
+ ignore_feature: bool = True, # TODO: This option seems not to work at all: fix it
4453
4466
  margin: float = 0,
4454
4467
  fill: bool = True,
4455
4468
  agreements: tuple[float, ...] | tuple[int, ...] = DEFAULT.AGREEMENTS,
@@ -4467,15 +4480,16 @@ class GaussianKDE(SpreadOpacity, TransformPlotter):
4467
4480
  self.n_points = n_points
4468
4481
  self.margin = margin
4469
4482
  self.fill = fill
4470
- if not ignore_feature and kwds.get('feature', False):
4483
+ if feature and not ignore_feature:
4471
4484
  height = height if height else CATEGORY.FEATURE_SPACE
4472
4485
  else:
4473
- kwds['feature'] = PLOTTER.TRANSFORMED_FEATURE
4486
+ feature = PLOTTER.TRANSFORMED_FEATURE
4474
4487
  self._height = height
4475
4488
  f_base = kwds.pop('f_base', DEFAULT.FEATURE_BASE)
4476
4489
  super().__init__(
4477
4490
  source=source,
4478
4491
  target=target,
4492
+ feature=feature,
4479
4493
  f_base=f_base,
4480
4494
  skip_na=skip_na,
4481
4495
  target_on_y=target_on_y,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "DaSPi"
3
- version = "2.1.0"
3
+ version = "2.2.0"
4
4
  description = "Visualize and analyze your data with DaSPi. This package is designed for users who want to find relevant influencing factors in processes and validate improvements."
5
5
  authors = [
6
6
  { name = "Reto Jäggli", email = "reto@jaeggli.email" },
@@ -216,6 +216,26 @@ class TestLinearModel:
216
216
  assert p_values.max() > 0.05
217
217
  assert lm.least_parameter() == 'A'
218
218
 
219
+ def test_effects_include_intercept(self, lm: LinearModel) -> None:
220
+ lm.fit()
221
+ effects = lm.effects()
222
+ assert 'Intercept' in effects.index
223
+ effects_no_intercept = lm.effects(include_intercept=False)
224
+ assert 'Intercept' not in effects_no_intercept.index
225
+ assert len(effects_no_intercept) == len(effects) - 1
226
+ pd.testing.assert_series_equal(
227
+ effects_no_intercept, effects.drop('Intercept'))
228
+
229
+ def test_p_values_include_intercept(self, lm: LinearModel) -> None:
230
+ lm.fit()
231
+ p_values = lm.p_values()
232
+ assert 'Intercept' in p_values.index
233
+ p_values_no_intercept = lm.p_values(include_intercept=False)
234
+ assert 'Intercept' not in p_values_no_intercept.index
235
+ assert len(p_values_no_intercept) == len(p_values) - 1
236
+ pd.testing.assert_series_equal(
237
+ p_values_no_intercept, p_values.drop('Intercept'))
238
+
219
239
  def test_main_parameters_property(self, lm2: LinearModel) -> None:
220
240
  lm2.fit()
221
241
  assert lm2.main_parameters == ['A', 'B', 'C', 'bad']
@@ -367,10 +387,10 @@ class TestLinearModel:
367
387
 
368
388
  def test_eliminate_invalid_term(self, lm3: LinearModel) -> None:
369
389
  lm3.fit()
370
- with pytest.raises(AssertionError, match=r'Given term Q is not in model'):
390
+ with pytest.raises(AssertionError, match=r'Given term Q .* is not in model'):
371
391
  lm3.eliminate('Q')
372
392
 
373
- with pytest.raises(AssertionError, match=r'Given term x0:x1:x2 is not in model'):
393
+ with pytest.raises(AssertionError, match=r'Given term x0:x1:x2 .* is not in model'):
374
394
  lm3.eliminate('A:B:C')
375
395
 
376
396
  def test_eliminate_encoded_term(self, lm3: LinearModel) -> None:
@@ -677,6 +677,38 @@ class TestSingleChart:
677
677
  assert STR.today in info_msg
678
678
  assert STR.username in info_msg
679
679
  assert self.info_msg not in info_msg
680
+
681
+ self.kind = 'ridge'
682
+ chart = SingleChart(
683
+ source=df_painkillers,
684
+ target=self.target,
685
+ feature=self.cat1,
686
+ target_on_y=True
687
+ ).plot(
688
+ GaussianKDE,
689
+ visible_spines='target',
690
+ hide_axis='feature',
691
+ agreements=(),
692
+ ignore_feature=False,
693
+ ).label(
694
+ fig_title=self.fig_title,
695
+ sub_title=self.sub_title,
696
+ feature_label=True,
697
+ target_label=True,
698
+ info=True
699
+ ).save(self.file_name
700
+ ).close()
701
+ texts = get_texts(chart)
702
+ info_msg = texts[-1].get_text()
703
+ assert self.file_name.is_file()
704
+ assert len(texts) == 5
705
+ assert texts[0].get_text() == self.fig_title
706
+ assert texts[1].get_text() == self.sub_title
707
+ assert texts[2].get_text() == self.target
708
+ assert texts[3].get_text() == self.cat1
709
+ assert STR.today in info_msg
710
+ assert STR.username in info_msg
711
+ assert self.info_msg not in info_msg
680
712
 
681
713
  def test_beeswarm_plot(self) -> None:
682
714
  self.base = f'{self.fig_title}_beeswarm'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes