machinegnostics 0.0.1__py3-none-any.whl
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.
- __init__.py +0 -0
- machinegnostics/__init__.py +24 -0
- machinegnostics/magcal/__init__.py +37 -0
- machinegnostics/magcal/characteristics.py +460 -0
- machinegnostics/magcal/criteria_eval.py +268 -0
- machinegnostics/magcal/criterion.py +140 -0
- machinegnostics/magcal/data_conversion.py +381 -0
- machinegnostics/magcal/gcor.py +64 -0
- machinegnostics/magcal/gdf/__init__.py +2 -0
- machinegnostics/magcal/gdf/base_df.py +39 -0
- machinegnostics/magcal/gdf/base_distfunc.py +1202 -0
- machinegnostics/magcal/gdf/base_egdf.py +823 -0
- machinegnostics/magcal/gdf/base_eldf.py +830 -0
- machinegnostics/magcal/gdf/base_qgdf.py +1234 -0
- machinegnostics/magcal/gdf/base_qldf.py +1019 -0
- machinegnostics/magcal/gdf/cluster_analysis.py +456 -0
- machinegnostics/magcal/gdf/data_cluster.py +975 -0
- machinegnostics/magcal/gdf/data_intervals.py +853 -0
- machinegnostics/magcal/gdf/data_membership.py +536 -0
- machinegnostics/magcal/gdf/der_egdf.py +243 -0
- machinegnostics/magcal/gdf/distfunc_engine.py +841 -0
- machinegnostics/magcal/gdf/egdf.py +324 -0
- machinegnostics/magcal/gdf/eldf.py +297 -0
- machinegnostics/magcal/gdf/eldf_intv.py +609 -0
- machinegnostics/magcal/gdf/eldf_ma.py +627 -0
- machinegnostics/magcal/gdf/homogeneity.py +1218 -0
- machinegnostics/magcal/gdf/intv_engine.py +1523 -0
- machinegnostics/magcal/gdf/marginal_intv_analysis.py +558 -0
- machinegnostics/magcal/gdf/qgdf.py +289 -0
- machinegnostics/magcal/gdf/qldf.py +296 -0
- machinegnostics/magcal/gdf/scedasticity.py +197 -0
- machinegnostics/magcal/gdf/wedf.py +181 -0
- machinegnostics/magcal/gdf/z0_estimator.py +1047 -0
- machinegnostics/magcal/layer_base.py +42 -0
- machinegnostics/magcal/layer_history_base.py +74 -0
- machinegnostics/magcal/layer_io_process_base.py +238 -0
- machinegnostics/magcal/layer_param_base.py +448 -0
- machinegnostics/magcal/mg_weights.py +36 -0
- machinegnostics/magcal/sample_characteristics.py +532 -0
- machinegnostics/magcal/scale_optimization.py +185 -0
- machinegnostics/magcal/scale_param.py +313 -0
- machinegnostics/magcal/util/__init__.py +0 -0
- machinegnostics/magcal/util/dis_docstring.py +18 -0
- machinegnostics/magcal/util/logging.py +24 -0
- machinegnostics/magcal/util/min_max_float.py +34 -0
- machinegnostics/magnet/__init__.py +0 -0
- machinegnostics/metrics/__init__.py +28 -0
- machinegnostics/metrics/accu.py +61 -0
- machinegnostics/metrics/accuracy.py +67 -0
- machinegnostics/metrics/auto_correlation.py +183 -0
- machinegnostics/metrics/auto_covariance.py +204 -0
- machinegnostics/metrics/cls_report.py +130 -0
- machinegnostics/metrics/conf_matrix.py +93 -0
- machinegnostics/metrics/correlation.py +178 -0
- machinegnostics/metrics/cross_variance.py +167 -0
- machinegnostics/metrics/divi.py +82 -0
- machinegnostics/metrics/evalmet.py +109 -0
- machinegnostics/metrics/f1_score.py +128 -0
- machinegnostics/metrics/gmmfe.py +108 -0
- machinegnostics/metrics/hc.py +141 -0
- machinegnostics/metrics/mae.py +72 -0
- machinegnostics/metrics/mean.py +117 -0
- machinegnostics/metrics/median.py +122 -0
- machinegnostics/metrics/mg_r2.py +167 -0
- machinegnostics/metrics/mse.py +78 -0
- machinegnostics/metrics/precision.py +119 -0
- machinegnostics/metrics/r2.py +122 -0
- machinegnostics/metrics/recall.py +108 -0
- machinegnostics/metrics/rmse.py +77 -0
- machinegnostics/metrics/robr2.py +119 -0
- machinegnostics/metrics/std.py +144 -0
- machinegnostics/metrics/variance.py +101 -0
- machinegnostics/models/__init__.py +2 -0
- machinegnostics/models/classification/__init__.py +1 -0
- machinegnostics/models/classification/layer_history_log_reg.py +121 -0
- machinegnostics/models/classification/layer_io_process_log_reg.py +98 -0
- machinegnostics/models/classification/layer_mlflow_log_reg.py +107 -0
- machinegnostics/models/classification/layer_param_log_reg.py +275 -0
- machinegnostics/models/classification/mg_log_reg.py +273 -0
- machinegnostics/models/cross_validation.py +118 -0
- machinegnostics/models/data_split.py +106 -0
- machinegnostics/models/regression/__init__.py +2 -0
- machinegnostics/models/regression/layer_histroy_rob_reg.py +139 -0
- machinegnostics/models/regression/layer_io_process_rob_rig.py +88 -0
- machinegnostics/models/regression/layer_mlflow_rob_reg.py +134 -0
- machinegnostics/models/regression/layer_param_rob_reg.py +212 -0
- machinegnostics/models/regression/mg_lin_reg.py +253 -0
- machinegnostics/models/regression/mg_poly_reg.py +258 -0
- machinegnostics-0.0.1.dist-info/METADATA +246 -0
- machinegnostics-0.0.1.dist-info/RECORD +93 -0
- machinegnostics-0.0.1.dist-info/WHEEL +5 -0
- machinegnostics-0.0.1.dist-info/licenses/LICENSE +674 -0
- machinegnostics-0.0.1.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Cluster Analysis
|
|
3
|
+
|
|
4
|
+
Module for clustering-based bound estimation for interval analysis.
|
|
5
|
+
This class do cluster end-to-end cluster analysis to estimate bounds.
|
|
6
|
+
|
|
7
|
+
Authors: Nirmal Parmar
|
|
8
|
+
Machine Gnostics
|
|
9
|
+
'''
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
import warnings
|
|
13
|
+
import logging
|
|
14
|
+
from machinegnostics.magcal.util.logging import get_logger
|
|
15
|
+
from machinegnostics.magcal import ELDF, EGDF, DataCluster, DataHomogeneity
|
|
16
|
+
|
|
17
|
+
class ClusterAnalysis:
|
|
18
|
+
"""
|
|
19
|
+
ClusterAnalysis - End-to-End Clustering-Based Bound Estimation for Interval Analysis
|
|
20
|
+
|
|
21
|
+
The `ClusterAnalysis` class provides a high-level, automated workflow for estimating the main cluster bounds of a dataset using Gnostic Distribution Functions (GDFs) and advanced clustering analysis. It is designed for robust, interpretable, and reproducible interval estimation in scientific, engineering, and data science applications.
|
|
22
|
+
|
|
23
|
+
This class orchestrates the entire process of:
|
|
24
|
+
1. Fitting a GDF (typically ELDF/EGDF) to the data,
|
|
25
|
+
2. Assessing data homogeneity,
|
|
26
|
+
3. Performing cluster boundary detection using the DataCluster algorithm,
|
|
27
|
+
4. Returning interpretable lower and upper cluster bounds (LCB, UCB) for the main data cluster.
|
|
28
|
+
|
|
29
|
+
**Key Features:**
|
|
30
|
+
- Fully automated pipeline for cluster-based bound estimation
|
|
31
|
+
- Integrates GDF fitting, homogeneity testing, and cluster analysis
|
|
32
|
+
- Supports both local (ELDF) and global (EGDF) GDFs
|
|
33
|
+
- Handles weighted data, bounded/unbounded domains, and advanced parameterization
|
|
34
|
+
- Detailed error/warning logging and reproducible parameter tracking
|
|
35
|
+
- Optional memory-efficient operation via flushing intermediate results
|
|
36
|
+
- Visualization support for both GDF and cluster analysis results
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
verbose : bool, optional (default=False)
|
|
41
|
+
If True, prints detailed logs and progress information during processing.
|
|
42
|
+
catch : bool, optional (default=True)
|
|
43
|
+
If True, stores intermediate results and diagnostic information for further analysis.
|
|
44
|
+
derivative_threshold : float, optional (default=0.01)
|
|
45
|
+
Threshold for derivative-based cluster boundary detection (used by DataCluster).
|
|
46
|
+
slope_percentile : int, optional (default=70)
|
|
47
|
+
Percentile threshold for slope-based boundary detection (used by DataCluster).
|
|
48
|
+
DLB : float, optional
|
|
49
|
+
Data Lower Bound. If None, inferred from data.
|
|
50
|
+
DUB : float, optional
|
|
51
|
+
Data Upper Bound. If None, inferred from data.
|
|
52
|
+
LB : float, optional
|
|
53
|
+
Lower probable bound for the distribution.
|
|
54
|
+
UB : float, optional
|
|
55
|
+
Upper probable bound for the distribution.
|
|
56
|
+
S : float or str, optional (default='auto')
|
|
57
|
+
Scale parameter for the GDF. Use 'auto' for automatic estimation.
|
|
58
|
+
varS : bool, optional (default=False)
|
|
59
|
+
If True, allows variable scale parameter during optimization.
|
|
60
|
+
z0_optimize : bool, optional (default=True)
|
|
61
|
+
If True, optimizes the location parameter Z0 during fitting.
|
|
62
|
+
tolerance : float, optional (default=1e-5)
|
|
63
|
+
Convergence tolerance for optimization.
|
|
64
|
+
data_form : str, optional (default='a')
|
|
65
|
+
Data processing form: 'a' for additive, 'm' for multiplicative.
|
|
66
|
+
n_points : int, optional (default=1000)
|
|
67
|
+
Number of points for GDF evaluation and PDF generation.
|
|
68
|
+
homogeneous : bool, optional (default=True)
|
|
69
|
+
If True, assumes data is homogeneous; triggers warnings if not.
|
|
70
|
+
weights : np.ndarray, optional
|
|
71
|
+
Prior weights for data points. If None, uniform weights are used.
|
|
72
|
+
wedf : bool, optional (default=False)
|
|
73
|
+
If True, uses Weighted Empirical Distribution Function.
|
|
74
|
+
opt_method : str, optional (default='L-BFGS-B')
|
|
75
|
+
Optimization method for parameter estimation.
|
|
76
|
+
max_data_size : int, optional (default=1000)
|
|
77
|
+
Maximum data size for smooth GDF generation.
|
|
78
|
+
flush : bool, optional (default=False)
|
|
79
|
+
If True, flushes intermediate results after fitting to save memory.
|
|
80
|
+
|
|
81
|
+
Attributes
|
|
82
|
+
----------
|
|
83
|
+
LCB : float or None
|
|
84
|
+
Lower Cluster Bound estimated from the data (main cluster lower edge).
|
|
85
|
+
UCB : float or None
|
|
86
|
+
Upper Cluster Bound estimated from the data (main cluster upper edge).
|
|
87
|
+
params : dict
|
|
88
|
+
Dictionary containing all parameters, intermediate results, errors, and warnings.
|
|
89
|
+
_fitted : bool
|
|
90
|
+
Indicates whether the analysis has been successfully completed.
|
|
91
|
+
|
|
92
|
+
Methods
|
|
93
|
+
-------
|
|
94
|
+
fit(data: np.ndarray, plot: bool = False) -> tuple
|
|
95
|
+
Runs the full cluster analysis pipeline on the input data.
|
|
96
|
+
Returns (LCB, UCB) as the main cluster bounds.
|
|
97
|
+
results() -> dict
|
|
98
|
+
Returns a dictionary with the estimated bounds and key results.
|
|
99
|
+
plot() -> None
|
|
100
|
+
Visualizes the fitted GDF and cluster analysis results (if not flushed).
|
|
101
|
+
|
|
102
|
+
Workflow
|
|
103
|
+
--------
|
|
104
|
+
1. Initialize ClusterAnalysis with desired parameters (no data required).
|
|
105
|
+
2. Call `fit(data)` to perform the complete analysis and estimate cluster bounds.
|
|
106
|
+
3. Access results via `results()` or visualize with `plot()`.
|
|
107
|
+
|
|
108
|
+
Example
|
|
109
|
+
-------
|
|
110
|
+
>>> from machinegnostics.magcal import ClusterAnalysis
|
|
111
|
+
>>> data = np.random.normal(0, 1, 1000)
|
|
112
|
+
>>> ca = ClusterAnalysis(verbose=True)
|
|
113
|
+
>>> LCB, UCB = ca.fit(data)
|
|
114
|
+
>>> print(f"Main cluster bounds: LCB={LCB:.3f}, UCB={UCB:.3f}")
|
|
115
|
+
>>> ca.plot()
|
|
116
|
+
>>> results = ca.results()
|
|
117
|
+
>>> print(results)
|
|
118
|
+
|
|
119
|
+
Notes
|
|
120
|
+
-----
|
|
121
|
+
- The class is designed for robust, interpretable cluster-based bound estimation.
|
|
122
|
+
- Works best with local GDFs (ELDF); global GDFs (EGDF) are supported but less flexible for clustering.
|
|
123
|
+
- If `homogeneous=True` but the data is found to be heterogeneous, a warning is issued.
|
|
124
|
+
- All intermediate parameters, errors, and warnings are tracked in `params` for reproducibility.
|
|
125
|
+
- For large datasets or memory-constrained environments, set `flush=True` to save memory (but disables plotting).
|
|
126
|
+
|
|
127
|
+
Raises
|
|
128
|
+
------
|
|
129
|
+
RuntimeError
|
|
130
|
+
If results or plot are requested before fitting.
|
|
131
|
+
Exception
|
|
132
|
+
If any step in the pipeline fails (errors are logged in `params`).
|
|
133
|
+
|
|
134
|
+
References
|
|
135
|
+
----------
|
|
136
|
+
- Gnostic Distribution Function theory and clustering methods (see mathematical gnostics literature).
|
|
137
|
+
- For details on the underlying algorithms, see the documentation for ELDF, EGDF, and DataCluster classes.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __init__(self,
|
|
141
|
+
verbose: bool = False,
|
|
142
|
+
catch: bool = True,
|
|
143
|
+
derivative_threshold: float = 0.01,
|
|
144
|
+
slope_percentile: int = 70,
|
|
145
|
+
DLB: float = None,
|
|
146
|
+
DUB: float = None,
|
|
147
|
+
LB: float = None,
|
|
148
|
+
UB: float = None,
|
|
149
|
+
S: str = 'auto',
|
|
150
|
+
varS: bool = False,
|
|
151
|
+
z0_optimize: bool = True,
|
|
152
|
+
tolerance: float = 0.00001,
|
|
153
|
+
data_form: str = 'a',
|
|
154
|
+
n_points: int = 1000,
|
|
155
|
+
homogeneous: bool = True,
|
|
156
|
+
weights: np.ndarray = None,
|
|
157
|
+
wedf: bool = False,
|
|
158
|
+
opt_method: str = 'L-BFGS-B',
|
|
159
|
+
max_data_size: int = 1000,
|
|
160
|
+
flush: bool = False
|
|
161
|
+
):
|
|
162
|
+
ELDF.__init__(self)
|
|
163
|
+
self.verbose = verbose
|
|
164
|
+
self.catch = catch
|
|
165
|
+
self.derivative_threshold = derivative_threshold
|
|
166
|
+
self.slope_percentile = slope_percentile
|
|
167
|
+
self.DLB = DLB
|
|
168
|
+
self.DUB = DUB
|
|
169
|
+
self.LB = LB
|
|
170
|
+
self.UB = UB
|
|
171
|
+
self.S = S
|
|
172
|
+
self.varS = varS
|
|
173
|
+
self.z0_optimize = z0_optimize
|
|
174
|
+
self.tolerance = tolerance
|
|
175
|
+
self.data_form = data_form
|
|
176
|
+
self.n_points = n_points
|
|
177
|
+
self.homogeneous = homogeneous
|
|
178
|
+
self.weights = weights
|
|
179
|
+
self.wedf = wedf
|
|
180
|
+
self.opt_method = opt_method
|
|
181
|
+
self.max_data_size = max_data_size
|
|
182
|
+
self.flush = flush
|
|
183
|
+
|
|
184
|
+
self._fitted = False
|
|
185
|
+
|
|
186
|
+
self.LCB = None
|
|
187
|
+
self.UCB = None
|
|
188
|
+
|
|
189
|
+
self.params = {}
|
|
190
|
+
self.params['error'] = []
|
|
191
|
+
self.params['warnings'] = []
|
|
192
|
+
|
|
193
|
+
# append arguments to params
|
|
194
|
+
if self.catch:
|
|
195
|
+
self.params['ClusterAnalysis'] = {
|
|
196
|
+
'verbose': self.verbose,
|
|
197
|
+
'catch': self.catch,
|
|
198
|
+
'derivative_threshold': self.derivative_threshold,
|
|
199
|
+
'slope_percentile': self.slope_percentile,
|
|
200
|
+
'DLB': self.DLB,
|
|
201
|
+
'DUB': self.DUB,
|
|
202
|
+
'LB': self.LB,
|
|
203
|
+
'UB': self.UB,
|
|
204
|
+
'S': self.S,
|
|
205
|
+
'varS': self.varS,
|
|
206
|
+
'z0_optimize': self.z0_optimize,
|
|
207
|
+
'tolerance': self.tolerance,
|
|
208
|
+
'data_form': self.data_form,
|
|
209
|
+
'n_points': self.n_points,
|
|
210
|
+
'homogeneous': self.homogeneous,
|
|
211
|
+
'weights': self.weights,
|
|
212
|
+
'wedf': self.wedf,
|
|
213
|
+
'opt_method': self.opt_method,
|
|
214
|
+
'max_data_size': self.max_data_size,
|
|
215
|
+
'flush': self.flush
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# logger setup
|
|
219
|
+
self.logger = get_logger(self.__class__.__name__, logging.DEBUG if verbose else logging.WARNING)
|
|
220
|
+
self.logger.debug(f"{self.__class__.__name__} initialized:")
|
|
221
|
+
|
|
222
|
+
def _add_warning(self, warning: str):
|
|
223
|
+
self.params['warnings'].append(warning)
|
|
224
|
+
self.logger.warning(warning)
|
|
225
|
+
|
|
226
|
+
def _add_error(self, error: str):
|
|
227
|
+
self.params['error'].append(error)
|
|
228
|
+
self.logger.error(error)
|
|
229
|
+
|
|
230
|
+
def fit(self, data: np.ndarray, plot: bool = False) -> tuple:
|
|
231
|
+
"""
|
|
232
|
+
Fit the ClusterAnalysis model to the input data and estimate main cluster bounds.
|
|
233
|
+
|
|
234
|
+
This method performs a comprehensive, automated clustering-based interval analysis on the provided data.
|
|
235
|
+
It integrates Gnostic Distribution Function (GDF) fitting, homogeneity assessment, and advanced cluster boundary detection
|
|
236
|
+
to robustly estimate the lower and upper bounds (LCB, UCB) of the main data cluster.
|
|
237
|
+
|
|
238
|
+
The process is designed to be robust, interpretable, and reproducible, handling both homogeneous and heterogeneous data,
|
|
239
|
+
supporting weighted and bounded datasets, and providing detailed diagnostics and error handling.
|
|
240
|
+
All intermediate results, warnings, and errors are tracked in the `params` attribute for transparency.
|
|
241
|
+
|
|
242
|
+
Parameters
|
|
243
|
+
----------
|
|
244
|
+
data : np.ndarray
|
|
245
|
+
Input data array for interval analysis. Should be 1D and numeric.
|
|
246
|
+
plot : bool, optional (default=False)
|
|
247
|
+
If True, generates plots for the fitted GDF and cluster analysis.
|
|
248
|
+
|
|
249
|
+
Returns
|
|
250
|
+
-------
|
|
251
|
+
tuple
|
|
252
|
+
(LCB, UCB): The estimated lower and upper bounds of the main data cluster.
|
|
253
|
+
Returns (None, None) if fitting fails.
|
|
254
|
+
|
|
255
|
+
Notes
|
|
256
|
+
-----
|
|
257
|
+
- If the data is found to be heterogeneous but `homogeneous=True`, a warning is issued.
|
|
258
|
+
- If `flush=True`, intermediate objects are deleted after fitting to save memory (disables plotting).
|
|
259
|
+
- All errors and warnings are logged in `params` for reproducibility and debugging.
|
|
260
|
+
|
|
261
|
+
Raises
|
|
262
|
+
------
|
|
263
|
+
Exception
|
|
264
|
+
Any error during the fitting process is caught, logged, and (LCB, UCB) is set to (None, None).
|
|
265
|
+
|
|
266
|
+
Example
|
|
267
|
+
-------
|
|
268
|
+
>>> ca = ClusterAnalysis(verbose=True)
|
|
269
|
+
>>> LCB, UCB = ca.fit(data)
|
|
270
|
+
>>> print(f"Cluster bounds: LCB={LCB}, UCB={UCB}")
|
|
271
|
+
"""
|
|
272
|
+
self.logger.info("Starting ClusterAnalysis fit process...")
|
|
273
|
+
try:
|
|
274
|
+
kwrgs_egdf = {
|
|
275
|
+
"DLB": self.DLB,
|
|
276
|
+
"DUB": self.DUB,
|
|
277
|
+
"LB": self.LB,
|
|
278
|
+
"UB": self.UB,
|
|
279
|
+
"S": self.S,
|
|
280
|
+
"z0_optimize": self.z0_optimize,
|
|
281
|
+
"tolerance": self.tolerance,
|
|
282
|
+
"data_form": self.data_form,
|
|
283
|
+
"n_points": self.n_points,
|
|
284
|
+
"homogeneous": self.homogeneous,
|
|
285
|
+
"catch": self.catch,
|
|
286
|
+
"weights": self.weights,
|
|
287
|
+
"wedf": self.wedf,
|
|
288
|
+
"opt_method": self.opt_method,
|
|
289
|
+
"verbose": self.verbose,
|
|
290
|
+
"max_data_size": self.max_data_size,
|
|
291
|
+
"flush": self.flush
|
|
292
|
+
}
|
|
293
|
+
# estimate egdf
|
|
294
|
+
self.logger.info("ClusterAnalysis: Fitting EGDF...")
|
|
295
|
+
self._egdf = EGDF(**kwrgs_egdf)
|
|
296
|
+
self._egdf.fit(data, plot=False)
|
|
297
|
+
if self.catch:
|
|
298
|
+
self.params['EGDF'] = self._egdf.params
|
|
299
|
+
|
|
300
|
+
# check data homogeneity
|
|
301
|
+
self._data_homogeneity = DataHomogeneity(gdf=self._egdf,
|
|
302
|
+
verbose=self.verbose,
|
|
303
|
+
catch=self.catch,
|
|
304
|
+
flush=self.flush)
|
|
305
|
+
is_homogeneous = self._data_homogeneity.fit(plot=False)
|
|
306
|
+
if self.catch:
|
|
307
|
+
self.params['DataHomogeneity'] = self._data_homogeneity.params
|
|
308
|
+
|
|
309
|
+
# if self.homogeneous is True, and is_homogeneous is False, raise a warning for user, that user understanding for data may not be correct
|
|
310
|
+
if self.homogeneous and not is_homogeneous:
|
|
311
|
+
warning_msg = "Data is not homogeneous, but 'homogeneous' parameter is set to True. User understanding for data may not be correct."
|
|
312
|
+
self._add_warning(warning_msg)
|
|
313
|
+
warnings.warn(warning_msg)
|
|
314
|
+
|
|
315
|
+
# fit eldf
|
|
316
|
+
self.logger.info("ClusterAnalysis: Fitting ELDF...")
|
|
317
|
+
self._eldf = ELDF(DLB=self.DLB,
|
|
318
|
+
DUB=self.DUB,
|
|
319
|
+
LB=self.LB,
|
|
320
|
+
UB=self.UB,
|
|
321
|
+
S=self.S,
|
|
322
|
+
varS=self.varS,
|
|
323
|
+
z0_optimize=self.z0_optimize,
|
|
324
|
+
tolerance=self.tolerance,
|
|
325
|
+
data_form=self.data_form,
|
|
326
|
+
n_points=self.n_points,
|
|
327
|
+
homogeneous=self.homogeneous,
|
|
328
|
+
catch=self.catch,
|
|
329
|
+
weights=self.weights,
|
|
330
|
+
wedf=self.wedf,
|
|
331
|
+
opt_method=self.opt_method,
|
|
332
|
+
verbose=self.verbose,
|
|
333
|
+
max_data_size=self.max_data_size,
|
|
334
|
+
flush=self.flush)
|
|
335
|
+
self._eldf.fit(data, plot=False)
|
|
336
|
+
if self.catch:
|
|
337
|
+
self.params['ELDF'] = self._eldf.params
|
|
338
|
+
|
|
339
|
+
# get cluster bounds
|
|
340
|
+
self.logger.info("ClusterAnalysis: Estimating cluster bounds...")
|
|
341
|
+
|
|
342
|
+
# note for user, if is_homogeneous is False, LCB and UCB will provide main cluster of the data.
|
|
343
|
+
if not is_homogeneous:
|
|
344
|
+
info_msg = "Data is not homogeneous, LCB and UCB will provide bounds for the main cluster of the data."
|
|
345
|
+
self._add_warning(info_msg)
|
|
346
|
+
self.logger.info(f'ClusterAnalysis: Info: {info_msg}')
|
|
347
|
+
|
|
348
|
+
self._data_cluster = DataCluster(gdf=self._eldf,
|
|
349
|
+
verbose=self.verbose,
|
|
350
|
+
catch=self.catch,
|
|
351
|
+
derivative_threshold=self.derivative_threshold, slope_percentile=self.slope_percentile)
|
|
352
|
+
self.LCB, self.UCB = self._data_cluster.fit(plot=plot)
|
|
353
|
+
if self.catch:
|
|
354
|
+
self.params['DataCluster'] = self._data_cluster.params
|
|
355
|
+
|
|
356
|
+
# save results
|
|
357
|
+
self._fitted = True
|
|
358
|
+
if self.catch:
|
|
359
|
+
self.params['results'] = {
|
|
360
|
+
'LCB': self.LCB,
|
|
361
|
+
'UCB': self.UCB
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
# flush
|
|
365
|
+
if self.flush:
|
|
366
|
+
self._egdf = None
|
|
367
|
+
self._eldf = None
|
|
368
|
+
self._data_homogeneity = None
|
|
369
|
+
self._data_cluster = None
|
|
370
|
+
# deleter respective params to save memory
|
|
371
|
+
# keep erros and warnings
|
|
372
|
+
if self.catch:
|
|
373
|
+
if 'EGDF' in self.params:
|
|
374
|
+
del self.params['EGDF']
|
|
375
|
+
if 'ELDF' in self.params:
|
|
376
|
+
del self.params['ELDF']
|
|
377
|
+
if 'DataHomogeneity' in self.params:
|
|
378
|
+
del self.params['DataHomogeneity']
|
|
379
|
+
if 'DataCluster' in self.params:
|
|
380
|
+
del self.params['DataCluster']
|
|
381
|
+
self.logger.info("ClusterAnalysis: Data flushed to save memory.")
|
|
382
|
+
|
|
383
|
+
self.logger.info(f'ClusterAnalysis: Fitting completed. LCB: {self.LCB}, UCB: {self.UCB}')
|
|
384
|
+
return self.LCB, self.UCB
|
|
385
|
+
|
|
386
|
+
except Exception as e:
|
|
387
|
+
self._add_error(str(e))
|
|
388
|
+
self.logger.error(f'ClusterAnalysis: Error during fit: {e}')
|
|
389
|
+
return None, None
|
|
390
|
+
|
|
391
|
+
def results(self) -> dict:
|
|
392
|
+
"""
|
|
393
|
+
Get the results of the Cluster Analysis.
|
|
394
|
+
|
|
395
|
+
Returns
|
|
396
|
+
-------
|
|
397
|
+
dict
|
|
398
|
+
A dictionary containing the estimated lower and upper cluster bounds:
|
|
399
|
+
{
|
|
400
|
+
'LCB': float,
|
|
401
|
+
'UCB': float
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
Raises
|
|
405
|
+
------
|
|
406
|
+
RuntimeError
|
|
407
|
+
If the model has not been fitted yet.
|
|
408
|
+
|
|
409
|
+
Notes
|
|
410
|
+
-----
|
|
411
|
+
- Call this method after `fit()` to retrieve the main cluster bounds.
|
|
412
|
+
- The returned values are floats or None if fitting failed.
|
|
413
|
+
"""
|
|
414
|
+
self.logger.info("Retrieving ClusterAnalysis results...")
|
|
415
|
+
if not self._fitted:
|
|
416
|
+
self.logger.error("ClusterAnalysis: The model is not fitted yet. Please call the 'fit' method first.")
|
|
417
|
+
raise RuntimeError("ClusterAnalysis: The model is not fitted yet. Please call the 'fit' method first.")
|
|
418
|
+
return {
|
|
419
|
+
'LCB': float(self.LCB),
|
|
420
|
+
'UCB': float(self.UCB)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
def plot(self) -> None:
|
|
424
|
+
"""
|
|
425
|
+
Plot the ELDF and DataCluster results.
|
|
426
|
+
|
|
427
|
+
This method visualizes the fitted Empirical Local Distribution Function (ELDF)
|
|
428
|
+
and the detected cluster boundaries from DataCluster. It is only available if
|
|
429
|
+
the model has been fitted and intermediate data has not been flushed.
|
|
430
|
+
|
|
431
|
+
Returns
|
|
432
|
+
-------
|
|
433
|
+
None
|
|
434
|
+
|
|
435
|
+
Raises
|
|
436
|
+
------
|
|
437
|
+
RuntimeError
|
|
438
|
+
If the model has not been fitted yet, or if `flush=True` was set and intermediate data is unavailable.
|
|
439
|
+
|
|
440
|
+
Notes
|
|
441
|
+
-----
|
|
442
|
+
- Call this method after `fit()` to visualize the results.
|
|
443
|
+
- If `flush=True` was set during initialization, plotting is disabled to save memory.
|
|
444
|
+
"""
|
|
445
|
+
if not self._fitted:
|
|
446
|
+
self.logger.error("ClusterAnalysis: The model is not fitted yet. Please call the 'fit' method first.")
|
|
447
|
+
raise RuntimeError("ClusterAnalysis: The model is not fitted yet. Please call the 'fit' method first.")
|
|
448
|
+
if self.flush:
|
|
449
|
+
self.logger.error("ClusterAnalysis: Data has been flushed. Cannot plot. Please set 'flush' to False during initialization to enable plotting.")
|
|
450
|
+
raise RuntimeError("ClusterAnalysis: Data has been flushed. Cannot plot. Please set 'flush' to False during initialization to enable plotting.")
|
|
451
|
+
|
|
452
|
+
# Plot ELDF
|
|
453
|
+
self._eldf.plot(plot='both')
|
|
454
|
+
|
|
455
|
+
# Plot DataCluster
|
|
456
|
+
self._data_cluster.plot()
|