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.
- {process_improve-1.2.0 → process_improve-1.2.6}/PKG-INFO +16 -1
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/batch/alignment_helpers.py +4 -2
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/batch/data_input.py +15 -13
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/batch/features.py +141 -55
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/batch/plotting.py +34 -26
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/batch/preprocessing.py +12 -8
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/batch/tools.py +2 -2
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/bivariate/methods.py +2 -2
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/bivariate/tools.py +2 -2
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/experiments/datasets.py +8 -6
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/experiments/designs_factorial.py +2 -2
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/experiments/models.py +27 -13
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/experiments/optimization.py +1 -1
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/experiments/simulations.py +9 -2
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/experiments/structures.py +29 -22
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/experiments/tools.py +5 -5
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/mcp_server.py +2 -2
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/monitoring/control_charts.py +9 -7
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/monitoring/metrics.py +2 -2
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/monitoring/tools.py +4 -4
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/multivariate/methods.py +193 -233
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/multivariate/tools.py +16 -13
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/regression/methods.py +1 -1
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/regression/tools.py +4 -4
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/tool_spec.py +5 -6
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/univariate/metrics.py +32 -19
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/univariate/tools.py +1 -1
- process_improve-1.2.6/process_improve/visualization/__init__.py +1 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/pyproject.toml +48 -10
- {process_improve-1.2.0 → process_improve-1.2.6}/README.md +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/__init__.py +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/batch/__init__.py +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/bivariate/__init__.py +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/batch/batch-fake-data.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/batch/details.txt +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/batch/dryer.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/batch/nylon.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/experiments/test_doe1.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/monitoring/batch-yield-and-purity.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/monitoring/rubber-colour.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/C.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/Hotellings_T2_A3.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/Hotellings_T2_A6.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/LDPE.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/P.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/R.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/T.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/U.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/W.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/LDPE/Yhat_A6.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/kamyr.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tablet-spectra.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/SOURCE +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group1.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group2.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group3.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group4.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group5.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/process_conditions.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/properties_Group1.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/properties_Group2.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/properties_Group3.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/properties_Group4.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/properties_Group5.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/datasets/multivariate/tpls-pyphi/quality_indicators.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/docs/outline.txt +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/experiments/__init__.py +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/experiments/optimal.py +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/media/boilingpot.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/media/distillate-flow.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/media/oil-company-doe.csv +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/media/trade-off-table.html +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/media/trade-off-table.pdf +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/media/trade-off-table.png +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/monitoring/__init__.py +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/multivariate/__init__.py +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/multivariate/plots.py +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/Example notebook.ipynb +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/Tablets.xlsx +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/batch/__init__.py +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/batch/alignment-and-pca-example.ipynb +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/batch/batch-optimization.ipynb +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/batch/batch_llm.py +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/batch/batch_llm_multivariate.py +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/batch/batch_music.py +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/batch/demo-multiplots.ipynb +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/experiments/case-studies.py +0 -0
- {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
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/notebooks_examples/multivariate/pca_example.py +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/regression/__init__.py +0 -0
- {process_improve-1.2.0 → process_improve-1.2.6}/process_improve/univariate/__init__.py +0 -0
- {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.
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
479
|
-
if x_axis_tag not in
|
|
480
|
-
|
|
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
|
-
|
|
483
|
-
output.loc[batch_id, tag] = repeated_median_slope(
|
|
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,
|
|
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(
|
|
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,
|
|
632
|
-
if x_axis_tag not in
|
|
633
|
-
|
|
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 =
|
|
721
|
+
subset = batch_data[[x_axis_tag, tag]]
|
|
636
722
|
subset = subset.dropna()
|
|
637
|
-
|
|
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(
|
|
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] =
|
|
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)))
|