process-improve 1.2.0__tar.gz → 1.2.6__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 (92) hide show
  1. {process_improve-1.2.0 → process_improve-1.2.6}/PKG-INFO +16 -1
  2. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/batch/alignment_helpers.py +4 -2
  3. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/batch/data_input.py +15 -13
  4. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/batch/features.py +141 -55
  5. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/batch/plotting.py +34 -26
  6. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/batch/preprocessing.py +12 -8
  7. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/batch/tools.py +2 -2
  8. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/bivariate/methods.py +2 -2
  9. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/bivariate/tools.py +2 -2
  10. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/experiments/datasets.py +8 -6
  11. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/experiments/designs_factorial.py +2 -2
  12. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/experiments/models.py +27 -13
  13. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/experiments/optimization.py +1 -1
  14. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/experiments/simulations.py +9 -2
  15. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/experiments/structures.py +29 -22
  16. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/experiments/tools.py +5 -5
  17. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/mcp_server.py +2 -2
  18. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/monitoring/control_charts.py +9 -7
  19. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/monitoring/metrics.py +2 -2
  20. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/monitoring/tools.py +4 -4
  21. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/multivariate/methods.py +193 -233
  22. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/multivariate/tools.py +16 -13
  23. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/regression/methods.py +1 -1
  24. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/regression/tools.py +4 -4
  25. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/tool_spec.py +5 -6
  26. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/univariate/metrics.py +32 -19
  27. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/univariate/tools.py +1 -1
  28. process_improve-1.2.6/process_improve/visualization/__init__.py +1 -0
  29. {process_improve-1.2.0 → process_improve-1.2.6}/pyproject.toml +48 -10
  30. {process_improve-1.2.0 → process_improve-1.2.6}/README.md +0 -0
  31. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/__init__.py +0 -0
  32. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/batch/__init__.py +0 -0
  33. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/bivariate/__init__.py +0 -0
  34. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/batch/batch-fake-data.csv +0 -0
  35. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/batch/details.txt +0 -0
  36. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/batch/dryer.csv +0 -0
  37. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/batch/nylon.csv +0 -0
  38. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/experiments/test_doe1.csv +0 -0
  39. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/monitoring/batch-yield-and-purity.csv +0 -0
  40. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/monitoring/rubber-colour.csv +0 -0
  41. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/C.csv +0 -0
  42. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/Hotellings_T2_A3.csv +0 -0
  43. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/Hotellings_T2_A6.csv +0 -0
  44. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/LDPE.csv +0 -0
  45. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/P.csv +0 -0
  46. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/R.csv +0 -0
  47. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/T.csv +0 -0
  48. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/U.csv +0 -0
  49. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/W.csv +0 -0
  50. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/Yhat_A6.csv +0 -0
  51. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/kamyr.csv +0 -0
  52. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tablet-spectra.csv +0 -0
  53. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/SOURCE +0 -0
  54. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group1.csv +0 -0
  55. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group2.csv +0 -0
  56. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group3.csv +0 -0
  57. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group4.csv +0 -0
  58. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group5.csv +0 -0
  59. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/process_conditions.csv +0 -0
  60. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/properties_Group1.csv +0 -0
  61. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/properties_Group2.csv +0 -0
  62. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/properties_Group3.csv +0 -0
  63. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/properties_Group4.csv +0 -0
  64. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/properties_Group5.csv +0 -0
  65. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/quality_indicators.csv +0 -0
  66. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/docs/outline.txt +0 -0
  67. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/experiments/__init__.py +0 -0
  68. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/experiments/optimal.py +0 -0
  69. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/media/boilingpot.csv +0 -0
  70. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/media/distillate-flow.csv +0 -0
  71. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/media/oil-company-doe.csv +0 -0
  72. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/media/trade-off-table.html +0 -0
  73. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/media/trade-off-table.pdf +0 -0
  74. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/media/trade-off-table.png +0 -0
  75. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/monitoring/__init__.py +0 -0
  76. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/multivariate/__init__.py +0 -0
  77. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/multivariate/plots.py +0 -0
  78. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/Example notebook.ipynb +0 -0
  79. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/Tablets.xlsx +0 -0
  80. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/batch/__init__.py +0 -0
  81. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/batch/alignment-and-pca-example.ipynb +0 -0
  82. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/batch/batch-optimization.ipynb +0 -0
  83. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/batch/batch_llm.py +0 -0
  84. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/batch/batch_llm_multivariate.py +0 -0
  85. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/batch/batch_music.py +0 -0
  86. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/batch/demo-multiplots.ipynb +0 -0
  87. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/experiments/case-studies.py +0 -0
  88. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/least-squares-modelling/Misguided-reliance-on-R2-alone.ipynb +0 -0
  89. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/multivariate/pca_example.py +0 -0
  90. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/regression/__init__.py +0 -0
  91. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/univariate/__init__.py +0 -0
  92. {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/visualization/plots.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: process-improve
3
- Version: 1.2.0
3
+ Version: 1.2.6
4
4
  Summary: Designed Experiments; Latent Variables (PCA, PLS, multivariate methods with missing data); Process Monitoring; Batch data analysis.
5
5
  Keywords: Designed Experiments,Latent Variables,PCA,PLS,Multivariate Data Analysis,Batch data analysis
6
6
  Author: Kevin Dunn
@@ -20,11 +20,26 @@ Requires-Dist: scikit-learn>=1.7.2
20
20
  Requires-Dist: seaborn>=0.13.2
21
21
  Requires-Dist: statsmodels>=0.14.6
22
22
  Requires-Dist: tqdm>=4.67.1
23
+ Requires-Dist: coverage>=7.13.1 ; extra == 'dev'
24
+ Requires-Dist: matplotlib-stubs>=0.3.11 ; extra == 'dev'
25
+ Requires-Dist: mypy>=1.19.1 ; extra == 'dev'
26
+ Requires-Dist: pandas-stubs>=2.3.3.260113 ; extra == 'dev'
27
+ Requires-Dist: plotly-stubs>=0.0.6 ; extra == 'dev'
28
+ Requires-Dist: pre-commit>=4.5.1 ; extra == 'dev'
29
+ Requires-Dist: pytest>=9.0.2 ; extra == 'dev'
30
+ Requires-Dist: pytest-cov>=7.0.0 ; extra == 'dev'
31
+ Requires-Dist: pytest-xdist>=3.8.0 ; extra == 'dev'
32
+ Requires-Dist: pydata-sphinx-theme>=0.16.1 ; extra == 'dev'
33
+ Requires-Dist: ruff>=0.11.0 ; extra == 'dev'
34
+ Requires-Dist: sphinx>=8.1.3 ; extra == 'dev'
35
+ Requires-Dist: tqdm-stubs>=0.2.1 ; extra == 'dev'
36
+ Requires-Dist: watchdog>=6.0.0 ; extra == 'dev'
23
37
  Requires-Dist: mcp>=1.0 ; extra == 'mcp'
24
38
  Requires-Python: >=3.10
25
39
  Project-URL: Homepage, https://github.com/kgdunn/process_improve
26
40
  Project-URL: Repository, https://github.com/kgdunn/process_improve
27
41
  Project-URL: Issues, https://github.com/kgdunn/process_improve/issues
42
+ Provides-Extra: dev
28
43
  Provides-Extra: mcp
29
44
  Description-Content-Type: text/markdown
30
45
 
@@ -5,7 +5,8 @@ from numba import jit
5
5
 
6
6
 
7
7
  @jit(nopython=True)
8
- def distance_matrix(test: np.ndarray, ref: np.ndarray, weight_matrix: np.ndarray):
8
+ def distance_matrix(test: np.ndarray, ref: np.ndarray, weight_matrix: np.ndarray) -> np.ndarray:
9
+ """Compute the DTW distance matrix between test and reference batch trajectories."""
9
10
  # TODO: allow user to specify `band`. The code below assumes that `band` is fixed as shown
10
11
  # here, so therefore, if user provide `band`, the code needs to be adjusted.
11
12
  nt = test.shape[0] # 'test' data; will be align to the 'reference' data
@@ -36,7 +37,8 @@ def distance_matrix(test: np.ndarray, ref: np.ndarray, weight_matrix: np.ndarray
36
37
 
37
38
 
38
39
  @jit(nopython=True)
39
- def backtrack_optimal_path(D: np.ndarray):
40
+ def backtrack_optimal_path(D: np.ndarray) -> tuple[list, float]:
41
+ """Backtrack through the distance matrix to find the optimal warping path."""
40
42
  nr, nt = D.shape
41
43
  nr -= 1
42
44
  nt -= 1
@@ -36,12 +36,13 @@ Characteristics:
36
36
  - the multilevel column index has level 0 = column name, level 1 = aligned time
37
37
  - only makes sense if the data are aligned (same number of elements in each level-1 index)
38
38
  """
39
+ from __future__ import annotations
39
40
 
40
41
  import numpy as np
41
42
  import pandas as pd
42
43
 
43
44
 
44
- def check_valid_batch_dict(in_dict: dict, no_nan=False) -> bool:
45
+ def check_valid_batch_dict(in_dict: dict, no_nan: bool = False) -> bool:
45
46
  """Check if the incoming dictionary of batch data is a valid dictionary of data.
46
47
 
47
48
  Checks:
@@ -87,7 +88,11 @@ def check_valid_batch_dict(in_dict: dict, no_nan=False) -> bool:
87
88
  return bool(check)
88
89
 
89
90
 
90
- def dict_to_melted(in_df: pd.DataFrame, insert_batch_id_column=True, insert_sequence_column=False) -> pd.DataFrame:
91
+ def dict_to_melted(
92
+ in_df: pd.DataFrame,
93
+ insert_batch_id_column: bool = True,
94
+ insert_sequence_column: bool = False,
95
+ ) -> pd.DataFrame:
91
96
  """Reverse of `melted_to_dict`."""
92
97
  batch_id_col = "batch_id"
93
98
  all_batches = []
@@ -111,7 +116,7 @@ def dict_to_melted(in_df: pd.DataFrame, insert_batch_id_column=True, insert_sequ
111
116
  return pd.concat(all_batches)
112
117
 
113
118
 
114
- def dict_to_wide(in_df: dict, group_by_batch=False) -> pd.DataFrame:
119
+ def dict_to_wide(in_df: dict, group_by_batch: bool = False) -> pd.DataFrame:
115
120
  """
116
121
  Convert aligned batch data from dict to wide format.
117
122
 
@@ -131,22 +136,18 @@ def dict_to_wide(in_df: dict, group_by_batch=False) -> pd.DataFrame:
131
136
  return aligned_wide_df
132
137
 
133
138
 
134
- def melted_to_dict(in_df: pd.DataFrame, batch_id_col) -> dict:
139
+ def melted_to_dict(in_df: pd.DataFrame, batch_id_col: str) -> dict:
135
140
  """
136
141
  Load a "melted" data set, where one of the columns is the `batch_id_col`.
137
142
  The data are grouped along the unique values of `batch_id_col`, and each group is stored
138
143
  in a dictionary. The dictionary keys are the batch identifier, and the corresponding value
139
144
  is a Pandas dataframe of the batch data for that batch.
140
145
  """
141
- batches = {}
142
146
  assert batch_id_col in in_df, "The `batch_id_col` column does not exist in the incoming dataframe."
143
- for batch_id, batch in in_df.groupby(batch_id_col):
144
- batches[batch_id] = batch
147
+ return {batch_id: batch for batch_id, batch in in_df.groupby(batch_id_col)} # noqa: C416
145
148
 
146
- return batches
147
149
 
148
-
149
- def melted_to_wide(in_df: pd.DataFrame, batch_id_col) -> dict:
150
+ def melted_to_wide(in_df: pd.DataFrame, batch_id_col: str) -> dict:
150
151
  """Convert aligned melted data to wide format."""
151
152
  assert batch_id_col in in_df
152
153
  return {}
@@ -166,15 +167,16 @@ def melted_to_wide(in_df: pd.DataFrame, batch_id_col) -> dict:
166
167
 
167
168
 
168
169
  def wide_to_melted(in_df: pd.DataFrame) -> pd.DataFrame:
170
+ """Convert wide-format batch data to melted format. Not yet implemented."""
169
171
  # dict_to_melted(dict_to_wide(in_df))
170
172
  return pd.DataFrame()
171
173
 
172
174
 
173
- def wide_to_dict():
174
- pass
175
+ def wide_to_dict() -> None:
176
+ """Convert wide-format batch data to dict format. Not yet implemented."""
175
177
 
176
178
 
177
- def melt_df_to_series(in_df: pd.DataFrame, exclude_columns=None, name=None) -> pd.Series:
179
+ def melt_df_to_series(in_df: pd.DataFrame, exclude_columns: list | None = None, name: str | None = None) -> pd.Series:
178
180
  """Return a Series with a multilevel-index, melted from the DataFrame."""
179
181
  if exclude_columns is None:
180
182
  exclude_columns = ["batch_id"]
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import warnings
4
+
3
5
  import numpy as np
4
6
  import pandas as pd
5
7
  from scipy.stats import iqr, norm
@@ -11,7 +13,13 @@ from ..regression.methods import repeated_median_slope
11
13
  # ------------------------------------------
12
14
 
13
15
 
14
- def _prepare_data(df: pd.DataFrame, tags=None, batch_col=None, phase_col=None, age_col=None): # noqa: C901, PLR0912
16
+ def _prepare_data( # noqa: C901, PLR0912
17
+ df: pd.DataFrame,
18
+ tags: list[str] | None = None,
19
+ batch_col: str | None = None,
20
+ phase_col: str | None = None,
21
+ age_col: str | None = None,
22
+ ) -> tuple[pd.core.groupby.DataFrameGroupBy, list[str], pd.DataFrame, pd.DataFrame]:
15
23
  """
16
24
  General function, used for all feature extractions.
17
25
 
@@ -96,7 +104,12 @@ def _prepare_data(df: pd.DataFrame, tags=None, batch_col=None, phase_col=None, a
96
104
 
97
105
  # Location-based features
98
106
  # ------------------------------------------
99
- def f_mean(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
107
+ def f_mean(
108
+ data: pd.DataFrame,
109
+ tags: list[str] | None = None,
110
+ batch_col: str | None = None,
111
+ phase_col: str | None = None,
112
+ ) -> pd.DataFrame:
100
113
  """
101
114
  Feature: mean.
102
115
 
@@ -108,10 +121,15 @@ def f_mean(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
108
121
  prepared, tags, output, _ = _prepare_data(data, tags, batch_col, phase_col)
109
122
  f_names = [(tag + "_" + base_name) for tag in tags]
110
123
  output = prepared.mean()
111
- return output.rename(columns=dict(zip(tags, f_names)))[f_names]
124
+ return output.rename(columns=dict(zip(tags, f_names, strict=False)))[f_names]
112
125
 
113
126
 
114
- def f_median(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
127
+ def f_median(
128
+ data: pd.DataFrame,
129
+ tags: list[str] | None = None,
130
+ batch_col: str | None = None,
131
+ phase_col: str | None = None,
132
+ ) -> pd.DataFrame:
115
133
  """
116
134
  Feature: median.
117
135
 
@@ -119,7 +137,7 @@ def f_median(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
119
137
  indicator column, and within each unique phase, per batch, of the ``phase_col`` column.
120
138
  """
121
139
  base_name = "median"
122
- prepared, tags, output, df_out = _prepare_data(data, tags, batch_col, phase_col)
140
+ prepared, tags, output, _df_out = _prepare_data(data, tags, batch_col, phase_col)
123
141
 
124
142
  # If the feature is to be calculated within a specific range of time, then provide the
125
143
  # `slicer` input and the name of the `age_col` (column containing time evolution) which is
@@ -151,12 +169,17 @@ def f_median(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
151
169
 
152
170
  output = prepared.median()
153
171
  f_names = [(tag + "_" + base_name) for tag in tags]
154
- return output.rename(columns=dict(zip(tags, f_names)))[f_names]
172
+ return output.rename(columns=dict(zip(tags, f_names, strict=False)))[f_names]
155
173
 
156
174
 
157
175
  # Scale-based features
158
176
  # ------------------------------------------
159
- def f_std(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
177
+ def f_std(
178
+ data: pd.DataFrame,
179
+ tags: list[str] | None = None,
180
+ batch_col: str | None = None,
181
+ phase_col: str | None = None,
182
+ ) -> pd.DataFrame:
160
183
  """
161
184
  Feature: std.
162
185
 
@@ -170,10 +193,15 @@ def f_std(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
170
193
  prepared, tags, output, _ = _prepare_data(data, tags, batch_col, phase_col)
171
194
  f_names = [(tag + "_" + base_name) for tag in tags]
172
195
  output = prepared.std()
173
- return output.rename(columns=dict(zip(tags, f_names)))
196
+ return output.rename(columns=dict(zip(tags, f_names, strict=False)))
174
197
 
175
198
 
176
- def f_iqr(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
199
+ def f_iqr(
200
+ data: pd.DataFrame,
201
+ tags: list[str] | None = None,
202
+ batch_col: str | None = None,
203
+ phase_col: str | None = None,
204
+ ) -> pd.DataFrame:
177
205
  """
178
206
  Feature: iqr.
179
207
 
@@ -191,10 +219,15 @@ def f_iqr(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
191
219
  prepared, tags, output, _ = _prepare_data(data, tags, batch_col, phase_col)
192
220
  f_names = [(tag + "_" + base_name) for tag in tags]
193
221
  output = prepared.agg(iqr)
194
- return output.rename(columns=dict(zip(tags, f_names)))
222
+ return output.rename(columns=dict(zip(tags, f_names, strict=False)))
195
223
 
196
224
 
197
- def f_mad(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
225
+ def f_mad(
226
+ data: pd.DataFrame,
227
+ tags: list[str] | None = None,
228
+ batch_col: str | None = None,
229
+ phase_col: str | None = None,
230
+ ) -> pd.DataFrame:
198
231
  """
199
232
  Feature: mad.
200
233
 
@@ -216,10 +249,15 @@ def f_mad(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
216
249
  prepared, tags, output, _ = _prepare_data(data, tags, batch_col, phase_col)
217
250
  f_names = [(tag + "_" + base_name) for tag in tags]
218
251
  output = prepared.mad()
219
- return output.rename(columns=dict(zip(tags, f_names)))
252
+ return output.rename(columns=dict(zip(tags, f_names, strict=False)))
220
253
 
221
254
 
222
- def f_robust_mad(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
255
+ def f_robust_mad(
256
+ data: pd.DataFrame,
257
+ tags: list[str] | None = None,
258
+ batch_col: str | None = None,
259
+ phase_col: str | None = None,
260
+ ) -> pd.DataFrame:
223
261
  """
224
262
  Feature: mad.
225
263
 
@@ -252,12 +290,17 @@ def f_robust_mad(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
252
290
  raise AssertionError("This next line of code fails. Fix it.")
253
291
  output = (np.fabs(prepared - prepared.median())).median() / c_MAD_const
254
292
 
255
- return output.rename(columns=dict(zip(tags, f_names)))
293
+ return output.rename(columns=dict(zip(tags, f_names, strict=False)))
256
294
 
257
295
 
258
296
  # Cumulative features
259
297
  # ------------------------------------------
260
- def f_sum(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
298
+ def f_sum(
299
+ data: pd.DataFrame,
300
+ tags: list[str] | None = None,
301
+ batch_col: str | None = None,
302
+ phase_col: str | None = None,
303
+ ) -> pd.DataFrame:
261
304
  """
262
305
  Feature: sum.
263
306
 
@@ -274,10 +317,16 @@ def f_sum(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
274
317
  prepared, tags, output, _ = _prepare_data(data, tags, batch_col, phase_col)
275
318
  f_names = [(tag + "_" + base_name) for tag in tags]
276
319
  output = prepared.sum()
277
- return output.rename(columns=dict(zip(tags, f_names)))
320
+ return output.rename(columns=dict(zip(tags, f_names, strict=False)))
278
321
 
279
322
 
280
- def f_area(data: pd.DataFrame, time_tag, tags=None, batch_col=None, phase_col=None):
323
+ def f_area(
324
+ data: pd.DataFrame,
325
+ time_tag: str,
326
+ tags: list[str] | None = None,
327
+ batch_col: str | None = None,
328
+ phase_col: str | None = None,
329
+ ) -> pd.DataFrame:
281
330
  """
282
331
  Feature: area.
283
332
 
@@ -310,7 +359,9 @@ def f_area(data: pd.DataFrame, time_tag, tags=None, batch_col=None, phase_col=No
310
359
  # heights on the left and the right, multiplied by delta distance on the horizontal
311
360
  # index axis. Area = average(parallel lengths) * height
312
361
  # where height = delta distance on the horizontal axis.
313
- area = ((this_batch[tag].iloc[0:-1].values + this_batch[tag].iloc[1:].values) / 2 * half_base_factor).sum()
362
+ left_vals = this_batch[tag].iloc[0:-1].values
363
+ right_vals = this_batch[tag].iloc[1:].values
364
+ area = ((left_vals + right_vals) / 2 * half_base_factor).sum()
314
365
  output.loc[batch_id, tag] = area
315
366
 
316
367
  # TODO: check this out still
@@ -323,12 +374,17 @@ def f_area(data: pd.DataFrame, time_tag, tags=None, batch_col=None, phase_col=No
323
374
  # ) / 2
324
375
 
325
376
  # output.add_suffix('_' + base_name)
326
- return output.rename(columns=dict(zip(tags, f_names)))
377
+ return output.rename(columns=dict(zip(tags, f_names, strict=False)))
327
378
 
328
379
 
329
380
  # Breakpoint detection: rupture / breakpoint within a particular tag.
330
381
  # ------------------------------------------
331
- def f_rupture(data: pd.DataFrame, columns=None, batch_col=None, phase_col=None):
382
+ def f_rupture(
383
+ data: pd.DataFrame,
384
+ columns: list[str] | None = None,
385
+ batch_col: str | None = None,
386
+ phase_col: str | None = None,
387
+ ) -> None:
332
388
  """
333
389
  Feature: rupture.
334
390
 
@@ -361,7 +417,12 @@ def f_rupture(data: pd.DataFrame, columns=None, batch_col=None, phase_col=None):
361
417
 
362
418
  # Extreme features
363
419
  # ------------------------------------------
364
- def f_min(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
420
+ def f_min(
421
+ data: pd.DataFrame,
422
+ tags: list[str] | None = None,
423
+ batch_col: str | None = None,
424
+ phase_col: str | None = None,
425
+ ) -> pd.DataFrame:
365
426
  """
366
427
  Feature: min.
367
428
 
@@ -377,10 +438,15 @@ def f_min(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
377
438
  prepared, tags, output, _ = _prepare_data(data, tags, batch_col, phase_col)
378
439
  f_names = [(tag + "_" + base_name) for tag in tags]
379
440
  output = prepared.min()
380
- return output.rename(columns=dict(zip(tags, f_names)))
441
+ return output.rename(columns=dict(zip(tags, f_names, strict=False)))
381
442
 
382
443
 
383
- def f_max(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
444
+ def f_max(
445
+ data: pd.DataFrame,
446
+ tags: list[str] | None = None,
447
+ batch_col: str | None = None,
448
+ phase_col: str | None = None,
449
+ ) -> pd.DataFrame:
384
450
  """
385
451
  Feature: max.
386
452
 
@@ -396,20 +462,35 @@ def f_max(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
396
462
  prepared, tags, output, _ = _prepare_data(data, tags, batch_col, phase_col)
397
463
  f_names = [(tag + "_" + base_name) for tag in tags]
398
464
  output = prepared.max()
399
- return output.rename(columns=dict(zip(tags, f_names)))
465
+ return output.rename(columns=dict(zip(tags, f_names, strict=False)))
400
466
 
401
467
 
402
- def f_agemin(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
468
+ def f_agemin(
469
+ data: pd.DataFrame,
470
+ tags: list[str] | None = None,
471
+ batch_col: str | None = None,
472
+ phase_col: str | None = None,
473
+ ) -> None:
474
+ """Feature: age at minimum value. Not yet implemented."""
403
475
  # TODO
404
- pass
405
476
 
406
477
 
407
- def f_agemax(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
478
+ def f_agemax(
479
+ data: pd.DataFrame,
480
+ tags: list[str] | None = None,
481
+ batch_col: str | None = None,
482
+ phase_col: str | None = None,
483
+ ) -> None:
484
+ """Feature: age at maximum value. Not yet implemented."""
408
485
  # TODO
409
- pass
410
486
 
411
487
 
412
- def f_last(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
488
+ def f_last(
489
+ data: pd.DataFrame,
490
+ tags: list[str] | None = None,
491
+ batch_col: str | None = None,
492
+ phase_col: str | None = None,
493
+ ) -> pd.DataFrame:
413
494
  """
414
495
  Feature: endpoint.
415
496
 
@@ -426,10 +507,15 @@ def f_last(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
426
507
  prepared, tags, output, _ = _prepare_data(data, tags, batch_col, phase_col)
427
508
  f_names = [(tag + "_" + base_name) for tag in tags]
428
509
  output = prepared.last()
429
- return output.rename(columns=dict(zip(tags, f_names)))
510
+ return output.rename(columns=dict(zip(tags, f_names, strict=False)))
430
511
 
431
512
 
432
- def f_count(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
513
+ def f_count(
514
+ data: pd.DataFrame,
515
+ tags: list[str] | None = None,
516
+ batch_col: str | None = None,
517
+ phase_col: str | None = None,
518
+ ) -> pd.DataFrame:
433
519
  """
434
520
  Feature: count.
435
521
 
@@ -446,15 +532,15 @@ def f_count(data: pd.DataFrame, tags=None, batch_col=None, phase_col=None):
446
532
  prepared, tags, output, _ = _prepare_data(data, tags, batch_col, phase_col)
447
533
  f_names = [(tag + "_" + base_name) for tag in tags]
448
534
  output = prepared.count()
449
- return output.rename(columns=dict(zip(tags, f_names)))
535
+ return output.rename(columns=dict(zip(tags, f_names, strict=False)))
450
536
 
451
537
 
452
538
  # Shape-based features
453
539
  # ------------------------------------------
454
- def f_slope(
540
+ def f_slope( # noqa: PLR0913
455
541
  data: pd.DataFrame,
456
542
  x_axis_tag: str,
457
- tags=None,
543
+ tags: list[str] | None = None,
458
544
  batch_col: str | None = None,
459
545
  phase_col: str | None = None,
460
546
  age_col: str | None = None,
@@ -475,14 +561,14 @@ def f_slope(
475
561
 
476
562
  # We will overwrite all entries in this dataframe, one-by-one
477
563
  output = prepared.sum()
478
- for batch_id, this_batch in prepared:
479
- if x_axis_tag not in this_batch:
480
- this_batch = this_batch.reset_index()
564
+ for batch_id, batch_data in prepared:
565
+ if x_axis_tag not in batch_data:
566
+ batch_data = batch_data.reset_index() # noqa: PLW2901
481
567
  for tag in tags:
482
- X = this_batch[x_axis_tag]
483
- output.loc[batch_id, tag] = repeated_median_slope(X, this_batch[tag])
568
+ x_vals = batch_data[x_axis_tag]
569
+ output.loc[batch_id, tag] = repeated_median_slope(x_vals, batch_data[tag])
484
570
 
485
- return output.rename(columns=dict(zip(tags, f_names)))
571
+ return output.rename(columns=dict(zip(tags, f_names, strict=False)))
486
572
 
487
573
 
488
574
  def cross(
@@ -548,7 +634,7 @@ def cross(
548
634
  return x_crossings
549
635
 
550
636
 
551
- def f_crossing(
637
+ def f_crossing( # noqa: PLR0913
552
638
  data: pd.DataFrame,
553
639
  tag: str,
554
640
  time_tag: str,
@@ -584,7 +670,7 @@ def f_crossing(
584
670
  assert isinstance(tag, str)
585
671
  assert tag in data, f"Desired tag ['{tag}'] not found in the dataframe."
586
672
 
587
- prepared, tags, output, _ = _prepare_data(
673
+ prepared, _tags, output, _ = _prepare_data(
588
674
  data,
589
675
  [tag],
590
676
  batch_col,
@@ -594,15 +680,17 @@ def f_crossing(
594
680
 
595
681
  f_name = tag + "_" + base_name
596
682
 
597
- output = prepared.apply(lambda x: cross(x[tag], threshold, direction, only_index=only_index, first_point_only=True))
683
+ output = prepared.apply(
684
+ lambda x: cross(x[tag], threshold, direction, only_index=only_index, first_point_only=True)
685
+ )
598
686
 
599
687
  return pd.DataFrame(data={f_name: output})
600
688
 
601
689
 
602
- def f_elbow(
690
+ def f_elbow( # noqa: PLR0913
603
691
  data: pd.DataFrame,
604
692
  x_axis_tag: str,
605
- tags=None,
693
+ tags: list[str] | None = None,
606
694
  only_index: bool | None = False,
607
695
  batch_col: str | None = None,
608
696
  phase_col: str | None = None,
@@ -620,26 +708,24 @@ def f_elbow(
620
708
  want the *index* of the value, so you can also find the corresponding y-axis value. Use
621
709
  `only_index=True` for such cases.
622
710
  """
623
- import warnings
624
-
625
711
  base_name = "elbow"
626
712
  prepared, tags, output, _ = _prepare_data(data, tags, batch_col, phase_col, age_col=x_axis_tag)
627
713
  f_names = [(tag + "_" + base_name) for tag in tags]
628
714
 
629
715
  # We will overwrite all entries in this dataframe, one-by-one
630
716
  output = prepared.sum()
631
- for batch_id, this_batch in prepared:
632
- if x_axis_tag not in this_batch:
633
- this_batch = this_batch.reset_index()
717
+ for batch_id, batch_data in prepared:
718
+ if x_axis_tag not in batch_data:
719
+ batch_data = batch_data.reset_index() # noqa: PLW2901
634
720
  for tag in tags:
635
- subset = this_batch[[x_axis_tag, tag]]
721
+ subset = batch_data[[x_axis_tag, tag]]
636
722
  subset = subset.dropna()
637
- X = subset[x_axis_tag]
723
+ x_vals = subset[x_axis_tag]
638
724
 
639
725
  with warnings.catch_warnings():
640
726
  warnings.simplefilter("ignore")
641
727
 
642
- elbow_index = find_elbow_point(X, subset[tag])
728
+ elbow_index = find_elbow_point(x_vals, subset[tag])
643
729
  if elbow_index < 0:
644
730
  elbow_index = np.nan
645
731
 
@@ -648,6 +734,6 @@ def f_elbow(
648
734
  elif np.isnan(elbow_index):
649
735
  output.loc[batch_id][tag] = np.isnan
650
736
  else:
651
- output.loc[batch_id][tag] = X[elbow_index]
737
+ output.loc[batch_id][tag] = x_vals[elbow_index]
652
738
 
653
- return output.rename(columns=dict(zip(tags, f_names)))
739
+ return output.rename(columns=dict(zip(tags, f_names, strict=False)))