chemotools 0.1.9__py3-none-any.whl → 0.1.11rc0__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.
@@ -1,6 +1,7 @@
1
1
  from ._add_noise import AddNoise
2
2
  from ._baseline_shift import BaselineShift
3
3
  from ._fractional_shift import FractionalShift
4
+ from ._gaussian_broadening import GaussianBroadening
4
5
  from ._index_shift import IndexShift
5
6
  from ._spectrum_scale import SpectrumScale
6
7
 
@@ -9,6 +10,7 @@ __all__ = [
9
10
  "AddNoise",
10
11
  "BaselineShift",
11
12
  "FractionalShift",
13
+ "GaussianBroadening",
12
14
  "IndexShift",
13
15
  "SpectrumScale",
14
16
  ]
@@ -0,0 +1,136 @@
1
+ from typing import Literal, Optional
2
+ import numpy as np
3
+ from scipy.ndimage import gaussian_filter1d
4
+ from sklearn.base import BaseEstimator, TransformerMixin, OneToOneFeatureMixin
5
+ from sklearn.utils.validation import check_is_fitted, validate_data
6
+
7
+
8
+ class GaussianBroadening(TransformerMixin, OneToOneFeatureMixin, BaseEstimator):
9
+ """
10
+ Transform spectral data by broadening peaks using Gaussian convolution.
11
+
12
+ This transformer applies Gaussian smoothing to broaden peaks in spectral data.
13
+ For each signal, a random sigma is chosen between 0 and the specified sigma value.
14
+
15
+ Parameters
16
+ ----------
17
+ sigma : float, default=1.0
18
+ Maximum standard deviation for the Gaussian kernel.
19
+ The actual sigma used will be randomly chosen between 0 and this value.
20
+
21
+ mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, default='reflect'
22
+ The mode parameter determines how the input array is extended when
23
+ the filter overlaps a border. Default is 'reflect'.
24
+
25
+ pad_value : float, default=0.0
26
+ Value to fill past edges of input if mode is 'constant'.
27
+
28
+ random_state : int, optional, default=None
29
+ Random state for reproducible sigma selection.
30
+
31
+ truncate : float, default=4.0
32
+ Truncate the filter at this many standard deviations.
33
+ Larger values increase computation time but improve accuracy.
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ sigma: float = 1.0,
39
+ mode: Literal["reflect", "constant", "nearest", "mirror", "wrap"] = "reflect",
40
+ pad_value: float = 0.0,
41
+ random_state: Optional[int] = None,
42
+ truncate: float = 4.0,
43
+ ):
44
+ self.sigma = sigma
45
+ self.mode = mode
46
+ self.pad_value = pad_value
47
+ self.random_state = random_state
48
+ self.truncate = truncate
49
+
50
+ def fit(self, X: np.ndarray, y=None) -> "GaussianBroadening":
51
+ """
52
+ Fit the transformer to the data (in this case, only validates input).
53
+
54
+ Parameters
55
+ ----------
56
+ X : array-like of shape (n_samples, n_features)
57
+ Input data to validate.
58
+
59
+ y : None
60
+ Ignored.
61
+
62
+ Returns
63
+ -------
64
+ self : GaussianBroadening
65
+ The fitted transformer.
66
+ """
67
+ X = validate_data(
68
+ self, X, y="no_validation", ensure_2d=True, reset=True, dtype=np.float64
69
+ )
70
+
71
+ # Validate sigma parameter
72
+ if not isinstance(self.sigma, (int, float)):
73
+ raise ValueError("sigma must be a number")
74
+ if self.sigma < 0:
75
+ raise ValueError("sigma must be non-negative")
76
+
77
+ # Initialize random number generator
78
+ self._rng = np.random.default_rng(self.random_state)
79
+
80
+ return self
81
+
82
+ def transform(self, X: np.ndarray, y=None) -> np.ndarray:
83
+ """
84
+ Apply Gaussian broadening to the input data.
85
+
86
+ Parameters
87
+ ----------
88
+ X : array-like of shape (n_samples, n_features)
89
+ The data to transform.
90
+
91
+ y : None
92
+ Ignored.
93
+
94
+ Returns
95
+ -------
96
+ X_transformed : ndarray of shape (n_samples, n_features)
97
+ The transformed data with broadened peaks.
98
+ """
99
+ check_is_fitted(self, "n_features_in_")
100
+ X_ = validate_data(
101
+ self,
102
+ X,
103
+ y="no_validation",
104
+ ensure_2d=True,
105
+ copy=True,
106
+ reset=False,
107
+ dtype=np.float64,
108
+ )
109
+
110
+ # Transform each sample
111
+ for i, x in enumerate(X_):
112
+ X_[i] = self._broaden_signal(x)
113
+
114
+ return X_
115
+
116
+ def _broaden_signal(self, x: np.ndarray) -> np.ndarray:
117
+ """
118
+ Apply Gaussian broadening to a single signal.
119
+
120
+ Parameters
121
+ ----------
122
+ x : ndarray of shape (n_features,)
123
+ The input signal to broaden.
124
+
125
+ Returns
126
+ -------
127
+ broadened_signal : ndarray of shape (n_features,)
128
+ The broadened signal.
129
+ """
130
+ # Randomly choose sigma between 0 and max sigma
131
+ sigma = self._rng.uniform(0, self.sigma)
132
+
133
+ # Apply Gaussian filter
134
+ return gaussian_filter1d(
135
+ x, sigma=sigma, mode=self.mode, cval=self.pad_value, truncate=self.truncate
136
+ )
@@ -0,0 +1,7 @@
1
+ from .dmodx import DModX
2
+ from .hotelling_t2 import HotellingT2
3
+ from .q_residuals import QResiduals
4
+ from .leverage import Leverage
5
+ from .studentized_residuals import StudentizedResiduals
6
+
7
+ __all__ = ["DModX", "HotellingT2", "QResiduals", "Leverage", "StudentizedResiduals"]
@@ -0,0 +1,188 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Optional, Tuple, Union
3
+
4
+ import numpy as np
5
+
6
+ from sklearn.base import BaseEstimator, OutlierMixin
7
+ from sklearn.decomposition._base import _BasePCA
8
+ from sklearn.cross_decomposition._pls import _PLS
9
+ from sklearn.pipeline import Pipeline
10
+ from sklearn.utils.validation import check_is_fitted
11
+
12
+
13
+ ModelTypes = Union[_BasePCA, _PLS]
14
+
15
+
16
+ class _ModelResidualsBase(ABC, BaseEstimator, OutlierMixin):
17
+ """Base class for model outlier calculations.
18
+
19
+ Implements statistical calculations for outlier detection in dimensionality
20
+ reduction models like PCA and PLS.
21
+
22
+ Parameters
23
+ ----------
24
+ model : Union[ModelTypes, Pipeline]
25
+ A fitted _BasePCA or _PLS models or Pipeline ending with such a model
26
+ confidence : float
27
+ Confidence level for statistical calculations (between 0 and 1)
28
+
29
+ Attributes
30
+ ----------
31
+ estimator_ : ModelTypes
32
+ The fitted model of type _BasePCA or _PLS
33
+
34
+ transformer_ : Optional[Pipeline]
35
+ Preprocessing steps before the model
36
+
37
+ n_features_in_ : int
38
+ Number of features in the input data
39
+
40
+ n_components_ : int
41
+ Number of components in the model
42
+
43
+ n_samples_ : int
44
+ Number of samples used to train the model
45
+
46
+ critical_value_ : float
47
+ The calculated critical value for outlier detection
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ model: Union[ModelTypes, Pipeline],
53
+ confidence: float,
54
+ ) -> None:
55
+ (
56
+ self.estimator_,
57
+ self.transformer_,
58
+ self.n_features_in_,
59
+ self.n_components_,
60
+ self.n_samples_,
61
+ ) = _validate_and_extract_model(model)
62
+ self.confidence = _validate_confidence(confidence)
63
+
64
+ def fit_predict_residuals(
65
+ self, X: np.ndarray, y: Optional[np.ndarray] = None
66
+ ) -> np.ndarray:
67
+ """Fit the model to the input data and calculate the residuals.
68
+
69
+ Parameters
70
+ ----------
71
+ X : array-like of shape (n_samples, n_features)
72
+ Input data
73
+
74
+ y : array-like of shape (n_samples,), default=None
75
+ Target values
76
+
77
+ Returns
78
+ -------
79
+ ndarray of shape (n_samples,)
80
+ The residuals of the model
81
+ """
82
+ self.fit(X, y)
83
+ return self.predict_residuals(X, y, validate=True)
84
+
85
+ @abstractmethod
86
+ def predict_residuals(
87
+ self, X: np.ndarray, y: Optional[np.ndarray], validate: bool
88
+ ) -> np.ndarray:
89
+ """Calculate the residuals of the model.
90
+
91
+ Returns
92
+ -------
93
+ ndarray of shape (n_samples,)
94
+ The residuals of the model
95
+ """
96
+
97
+ @abstractmethod
98
+ def _calculate_critical_value(self, X: np.ndarray) -> float:
99
+ """Calculate the critical value for outlier detection.
100
+
101
+ Returns
102
+ -------
103
+ float
104
+ The calculated critical value for outlier detection
105
+ """
106
+
107
+
108
+ def _get_model_parameters(model: ModelTypes) -> Tuple[int, int, int]:
109
+ """
110
+ Get the number of features, components and samples from a model with PLS or PCA. types.
111
+
112
+ Parameters
113
+ ----------
114
+ model : ModelType
115
+ A fitted model of type _BasePCA or _PLS
116
+
117
+ Returns
118
+ -------
119
+ Tuple[int, int, int]
120
+ The number of features, components and samples in the model
121
+ """
122
+ if isinstance(model, _BasePCA):
123
+ return model.n_features_in_, model.n_components_, model.n_samples_
124
+ elif isinstance(model, _PLS):
125
+ return model.n_features_in_, model.n_components, len(model.x_scores_)
126
+ else:
127
+ raise ValueError(
128
+ "Model not a valid model. Must be of base type _BasePCA or _PLS or a Pipeline ending with one of these types."
129
+ )
130
+
131
+
132
+ def _validate_confidence(confidence: float) -> float:
133
+ """Validate parameters using sklearn conventions.
134
+
135
+ Parameters
136
+ ----------
137
+ confidence : float
138
+ Confidence level for statistical calculations (between 0 and 1)
139
+
140
+ Returns
141
+ -------
142
+ float
143
+ The validated confidence level
144
+
145
+ Raises
146
+ ------
147
+ ValueError
148
+ If confidence is not between 0 and 1
149
+ """
150
+ if not 0 < confidence < 1:
151
+ raise ValueError("Confidence must be between 0 and 1")
152
+ return confidence
153
+
154
+
155
+ def _validate_and_extract_model(
156
+ model: Union[ModelTypes, Pipeline],
157
+ ) -> Tuple[ModelTypes, Optional[Pipeline], int, int, int]:
158
+ """Validate and extract the model and preprocessing steps.
159
+
160
+ Parameters
161
+ ----------
162
+ model : Union[ModelTypes, Pipeline]
163
+ A fitted PCA/PLS model or Pipeline ending with such a model
164
+
165
+ Returns
166
+ -------
167
+ Tuple[ModelTypes, Optional[Pipeline]]
168
+ The extracted model and preprocessing steps
169
+
170
+ Raises
171
+ ------
172
+ ValueError
173
+ If the model is not of type _BasePCA or _PLS or a Pipeline ending with one of these types or if the model is not fitted
174
+ """
175
+ if isinstance(model, Pipeline):
176
+ preprocessing = model[:-1]
177
+ model = model[-1]
178
+ else:
179
+ preprocessing = None
180
+
181
+ if not isinstance(model, (_BasePCA, _PLS)):
182
+ raise ValueError(
183
+ "Model not a valid model. Must be of base type _BasePCA or _PLS or a Pipeline ending with one of these types."
184
+ )
185
+
186
+ check_is_fitted(model)
187
+ n_features_in, n_components, n_samples = _get_model_parameters(model)
188
+ return model, preprocessing, n_features_in, n_components, n_samples
@@ -0,0 +1,164 @@
1
+ from typing import Optional, Union
2
+ import numpy as np
3
+
4
+ from sklearn.pipeline import Pipeline
5
+ from sklearn.utils.validation import validate_data, check_is_fitted
6
+ from scipy.stats import f as f_distribution
7
+
8
+
9
+ from ._base import _ModelResidualsBase, ModelTypes
10
+ from .utils import calculate_residual_spectrum
11
+
12
+
13
+ class DModX(_ModelResidualsBase):
14
+ """Calculate Distance to Model (DModX) statistics.
15
+
16
+ DModX measures the distance between an observation and the model plane
17
+ in the X-space, useful for detecting outliers.
18
+
19
+ Parameters
20
+ ----------
21
+ model : Union[ModelType, Pipeline]
22
+ A fitted PCA/PLS model or Pipeline ending with such a model
23
+
24
+ confidence : float, default=0.95
25
+ Confidence level for statistical calculations (between 0 and 1)
26
+
27
+ Attributes
28
+ ----------
29
+ estimator_ : ModelType
30
+ The fitted model of type _BasePCA or _PLS
31
+
32
+ transformer_ : Optional[Pipeline]
33
+ Preprocessing steps before the model
34
+
35
+ n_features_in_ : int
36
+ Number of features in the input data
37
+
38
+ n_components_ : int
39
+ Number of components in the model
40
+
41
+ n_samples_ : int
42
+ Number of samples used to train the model
43
+
44
+ critical_value_ : float
45
+ The calculated critical value for outlier detection
46
+
47
+ train_spe_: float
48
+ The training sum of squared errors (SSE) for the model normalized by degrees of freedom
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ model: Union[ModelTypes, Pipeline],
54
+ confidence: float = 0.95,
55
+ ) -> None:
56
+ model, confidence = model, confidence
57
+ super().__init__(model, confidence)
58
+
59
+ def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None) -> "DModX":
60
+ """
61
+ Fit the model to the input data.
62
+
63
+ This step calculates the critical value for the outlier detection. In the DmodX method,
64
+ the critical value is not depend on the input data but on the model parameters.
65
+ """
66
+ X = validate_data(
67
+ self, X, y="no_validation", ensure_2d=True, reset=True, dtype=np.float64
68
+ )
69
+
70
+ # Calculate the critical value
71
+ self.critical_value_ = self._calculate_critical_value()
72
+
73
+ # Calculate the degrees of freedom normalized SPE of the training set
74
+ residuals = calculate_residual_spectrum(X, self.estimator_)
75
+ squared_errors = np.sum((residuals) ** 2, axis=1)
76
+ self.train_spe_ = np.sqrt(
77
+ squared_errors
78
+ / (self.n_samples_ - self.n_components_ - 1)
79
+ * (self.n_features_in_ - self.n_components_)
80
+ )
81
+
82
+ return self
83
+
84
+ def predict(self, X: np.ndarray) -> np.ndarray:
85
+ """Identify outliers in the input data.
86
+
87
+ Parameters
88
+ ----------
89
+ X : array-like of shape (n_samples, n_features)
90
+ Input data
91
+
92
+ Returns
93
+ -------
94
+ ndarray of shape (n_samples,)
95
+ Boolean array indicating outliers
96
+ """
97
+ # Check the estimator has been fitted
98
+ check_is_fitted(self, ["critical_value_"])
99
+
100
+ # Validate the input data
101
+ X = validate_data(
102
+ self, X, y="no_validation", ensure_2d=True, reset=True, dtype=np.float64
103
+ )
104
+
105
+ # Calculate outliers based on the DModX statistics
106
+ dmodx_values = self.predict_residuals(X, validate=False)
107
+ return np.where(dmodx_values > self.critical_value_, -1, 1)
108
+
109
+ def predict_residuals(
110
+ self, X: np.ndarray, y: Optional[np.ndarray] = None, validate: bool = True
111
+ ) -> np.ndarray:
112
+ """Calculate DModX statistics for input data.
113
+
114
+ Parameters
115
+ ----------
116
+ X : array-like of shape (n_samples, n_features)
117
+ Input data
118
+
119
+ validate : bool, default=True
120
+ Whether to validate the input data
121
+
122
+ Returns
123
+ -------
124
+ ndarray of shape (n_samples,)
125
+ DModX statistics for each sample
126
+ """
127
+ # Check the estimator has been fitted
128
+ check_is_fitted(self, ["critical_value_"])
129
+
130
+ # Validate the input data
131
+ if validate:
132
+ X = validate_data(
133
+ self, X, y="no_validation", ensure_2d=True, reset=True, dtype=np.float64
134
+ )
135
+
136
+ # Apply preprocessing if available
137
+ if self.transformer_:
138
+ X = self.transformer_.transform(X)
139
+
140
+ # Calculate the DModX statistics
141
+ residual = calculate_residual_spectrum(X, self.estimator_)
142
+ squared_errors = np.sum((residual) ** 2, axis=1)
143
+
144
+ return (
145
+ np.sqrt(squared_errors / (self.n_features_in_ - self.n_components_))
146
+ / self.train_spe_
147
+ )
148
+
149
+ def _calculate_critical_value(self, X: Optional[np.ndarray] = None) -> float:
150
+ """Calculate F-distribution based critical value.
151
+
152
+ Returns
153
+ -------
154
+ float
155
+ The critical value for outlier detection
156
+ """
157
+
158
+ dof_numerator = self.n_features_in_ - self.n_components_
159
+ dof_denominator = self.n_features_in_ - self.n_components_ - 1
160
+
161
+ upper_control_limit = f_distribution.ppf(
162
+ self.confidence, dof_numerator, dof_denominator
163
+ )
164
+ return np.sqrt(upper_control_limit)
@@ -0,0 +1,156 @@
1
+ from typing import Optional, Union
2
+ import numpy as np
3
+
4
+ from sklearn.cross_decomposition._pls import _PLS
5
+ from sklearn.decomposition._base import _BasePCA
6
+ from sklearn.pipeline import Pipeline
7
+ from sklearn.utils.validation import validate_data, check_is_fitted
8
+ from scipy.stats import f as f_distribution
9
+
10
+ from ._base import _ModelResidualsBase, ModelTypes
11
+
12
+
13
+ class HotellingT2(_ModelResidualsBase):
14
+ """
15
+ Calculate Hotelling's T-squared statistics for PCA or PLS like models.
16
+
17
+ Parameters
18
+ ----------
19
+ model : Union[ModelType, Pipeline]
20
+ A fitted PCA/PLS model or Pipeline ending with such a model
21
+
22
+ confidence : float, default=0.95
23
+ Confidence level for statistical calculations (between 0 and 1)
24
+
25
+ Attributes
26
+ ----------
27
+ estimator_ : ModelType
28
+ The fitted model of type _BasePCA or _PLS
29
+
30
+ transformer_ : Optional[Pipeline]
31
+ Preprocessing steps before the model
32
+
33
+ n_features_in_ : int
34
+ Number of features in the input data
35
+
36
+ n_components_ : int
37
+ Number of components in the model
38
+
39
+ n_samples_ : int
40
+ Number of samples used to train the model
41
+
42
+ critical_value_ : float
43
+ The calculated critical value for outlier detection
44
+
45
+ References
46
+ ----------
47
+ Johan A. Westerhuis, Stephen P. Gurden, Age K. Smilde (2001) Generalized contribution plots in multivariate statistical process
48
+ monitoring Chemometrics and Intelligent Laboratory Systems 51 2000 95–114
49
+ """
50
+
51
+ def __init__(
52
+ self, model: Union[ModelTypes, Pipeline], confidence: float = 0.95
53
+ ) -> None:
54
+ self.model, self.confidence = model, confidence
55
+ super().__init__(model, confidence)
56
+
57
+ def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None) -> "HotellingT2":
58
+ """
59
+ Fit the model to the input data.
60
+
61
+ This step calculates the critical value for the outlier detection. In the DmodX method,
62
+ the critical value is not depend on the input data but on the model parameters.
63
+ """
64
+ X = validate_data(
65
+ self, X, y="no_validation", ensure_2d=True, reset=True, dtype=np.float64
66
+ )
67
+
68
+ self.critical_value_ = self._calculate_critical_value()
69
+ return self
70
+
71
+ def predict(self, X: np.ndarray) -> np.ndarray:
72
+ """Identify outliers in the input data.
73
+
74
+ Parameters
75
+ ----------
76
+ X : array-like of shape (n_samples, n_features)
77
+ Input data
78
+
79
+ Returns
80
+ -------
81
+ ndarray of shape (n_samples,)
82
+ Boolean array indicating outliers
83
+ """
84
+ # Check the estimator has been fitted
85
+ check_is_fitted(self, ["critical_value_"])
86
+
87
+ # Validate the input data
88
+ X = validate_data(
89
+ self, X, y="no_validation", ensure_2d=True, reset=True, dtype=np.float64
90
+ )
91
+
92
+ # Calculate the Hotelling's T-squared statistics
93
+ hotelling_t2_values = self.predict_residuals(X, y=None, validate=False)
94
+ return np.where(hotelling_t2_values > self.critical_value_, -1, 1)
95
+
96
+ def predict_residuals(
97
+ self, X: np.ndarray, y: Optional[np.ndarray] = None, validate: bool = True
98
+ ) -> np.ndarray:
99
+ """Calculate Hotelling's T-squared statistics for input data.
100
+
101
+ Parameters
102
+ ----------
103
+ X : array-like of shape (n_samples, n_features)
104
+ Input data
105
+
106
+ Returns
107
+ -------
108
+ ndarray of shape (n_samples,)
109
+ Hotellin's T-squared statistics for each sample
110
+ """
111
+ # Check the estimator has been fitted
112
+ check_is_fitted(self, ["critical_value_"])
113
+
114
+ # Validate the input data
115
+ if validate:
116
+ X = validate_data(
117
+ self, X, y="no_validation", ensure_2d=True, reset=True, dtype=np.float64
118
+ )
119
+
120
+ # Apply preprocessing steps
121
+ if self.transformer_:
122
+ X = self.transformer_.transform(X)
123
+
124
+ # Calculate the Hotelling's T-squared statistics
125
+ if isinstance(self.estimator_, _BasePCA):
126
+ # For PCA-like models
127
+ variances = self.estimator_.explained_variance_
128
+
129
+ if isinstance(self.estimator_, _PLS):
130
+ # For PLS-like models
131
+ variances = np.var(self.estimator_.x_scores_, axis=0)
132
+
133
+ # Equivalent to X @ model.components_.T for _BasePCA and X @ model.x_rotations_ for _PLS
134
+ X_transformed = self.estimator_.transform(X)
135
+
136
+ return np.sum((X_transformed**2) / variances, axis=1)
137
+
138
+ def _calculate_critical_value(self, X: Optional[np.ndarray] = None) -> float:
139
+ """
140
+ Calculate the critical value for the Hotelling's T-squared statistics.
141
+
142
+ Returns
143
+ -------
144
+ float
145
+ The critical value for the Hotelling's T-squared statistics
146
+ """
147
+
148
+ critical_value = f_distribution.ppf(
149
+ self.confidence, self.n_components_, self.n_samples_ - self.n_components_
150
+ )
151
+ return (
152
+ critical_value
153
+ * self.n_components_
154
+ * (self.n_samples_ - 1)
155
+ / (self.n_samples_ - self.n_components_)
156
+ )
@@ -0,0 +1,151 @@
1
+ from typing import Optional, Union
2
+ import numpy as np
3
+
4
+ from sklearn.pipeline import Pipeline
5
+ from sklearn.utils.validation import validate_data, check_is_fitted
6
+
7
+
8
+ from ._base import _ModelResidualsBase, ModelTypes
9
+
10
+
11
+ class Leverage(_ModelResidualsBase):
12
+ """
13
+ Calculate the leverage of the training samples on the latent space of a PCA or PLS models.
14
+ This method allows to detect datapoints with high leverage in the model.
15
+
16
+ Parameters
17
+ ----------
18
+ model : Union[ModelType, Pipeline]
19
+ A fitted PCA/PLS model or Pipeline ending with such a model
20
+
21
+ Attributes
22
+ ----------
23
+ estimator_ : ModelType
24
+ The fitted model of type _BasePCA or _PLS
25
+
26
+ transformer_ : Optional[Pipeline]
27
+ Preprocessing steps before the model
28
+
29
+ References
30
+ ----------
31
+
32
+ """
33
+
34
+ def __init__(
35
+ self, model: Union[ModelTypes, Pipeline], confidence: float = 0.95
36
+ ) -> None:
37
+ model, confidence = model, confidence
38
+ super().__init__(model, confidence)
39
+
40
+ def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None) -> "Leverage":
41
+ """
42
+ Fit the model to the input data.
43
+
44
+ Parameters
45
+
46
+ """
47
+ X = validate_data(
48
+ self, X, y="no_validation", ensure_2d=True, reset=True, dtype=np.float64
49
+ )
50
+
51
+ if self.transformer_:
52
+ X = self.transformer_.fit_transform(X)
53
+
54
+ # Compute the critical threshold
55
+ self.critical_value_ = self._calculate_critical_value(X)
56
+
57
+ return self
58
+
59
+ def predict(self, X: np.ndarray, y: Optional[np.ndarray] = None) -> np.ndarray:
60
+ """Calculate Leverage for training data on the model.
61
+
62
+ Parameters
63
+ ----------
64
+ X : array-like of shape (n_samples, n_features)
65
+ Input data
66
+
67
+ Returns
68
+ -------
69
+ ndarray of shape (n_samples,)
70
+ Bool with samples with a leverage above the critical value
71
+ """
72
+ # Check the estimator has been fitted
73
+ check_is_fitted(self, ["critical_value_"])
74
+
75
+ # Validate the input data
76
+ X = validate_data(
77
+ self, X, y="no_validation", ensure_2d=True, reset=True, dtype=np.float64
78
+ )
79
+
80
+ # Preprocess the data
81
+ if self.transformer_:
82
+ X = self.transformer_.transform(X)
83
+
84
+ # Calculate outliers based on samples with too high leverage
85
+ leverage = calculate_leverage(X, self.estimator_)
86
+ return np.where(leverage > self.critical_value_, -1, 1)
87
+
88
+ def predict_residuals(
89
+ self, X: np.ndarray, y: Optional[np.ndarray] = None, validate: bool = True
90
+ ) -> np.ndarray:
91
+ """Calculate the leverage of the samples.
92
+
93
+ Parameters
94
+ ----------
95
+ X : array-like of shape (n_samples, n_features)
96
+ Input data
97
+
98
+ Returns
99
+ -------
100
+ np.ndarray
101
+ Leverage of the samples
102
+ """
103
+ # Check the estimator has been fitted
104
+ check_is_fitted(self, ["critical_value_"])
105
+
106
+ # Validate the input data
107
+ if validate:
108
+ X = validate_data(self, X, ensure_2d=True, dtype=np.float64)
109
+
110
+ # Apply preprocessing if available
111
+ if self.transformer_:
112
+ X = self.transformer_.transform(X)
113
+
114
+ # Calculate the leverage
115
+ return calculate_leverage(X, self.estimator_)
116
+
117
+ def _calculate_critical_value(self, X: np.ndarray) -> float:
118
+ """Calculate the critical value for outlier detection using the percentile outlier method."""
119
+
120
+ # Calculate the leverage of the samples
121
+ leverage = calculate_leverage(X, self.estimator_)
122
+
123
+ # Calculate the critical value
124
+ return np.percentile(leverage, self.confidence * 100)
125
+
126
+
127
+ def calculate_leverage(X: np.ndarray, model: ModelTypes) -> np.ndarray:
128
+ """
129
+ Calculate the leverage of the training samples in a PLS/PCA-like model.
130
+
131
+ Parameters
132
+ ----------
133
+ model : Union[_BasePCA, _PLS]
134
+ A fitted PCA/PLS model
135
+
136
+ X : np.ndarray
137
+ Preprocessed input data
138
+
139
+ Returns
140
+ -------
141
+ np.ndarray
142
+ Leverage of the samples
143
+ """
144
+
145
+ X_transformed = model.transform(X)
146
+
147
+ X_hat = (
148
+ X_transformed @ np.linalg.inv(X_transformed.T @ X_transformed) @ X_transformed.T
149
+ )
150
+
151
+ return np.diag(X_hat)
@@ -0,0 +1,228 @@
1
+ from typing import Optional, Literal, Union
2
+
3
+ import numpy as np
4
+
5
+ from scipy.stats import norm, chi2
6
+ from sklearn.pipeline import Pipeline
7
+ from sklearn.utils.validation import validate_data, check_is_fitted
8
+
9
+ from ._base import _ModelResidualsBase, ModelTypes
10
+ from .utils import calculate_residual_spectrum
11
+
12
+
13
+ class QResiduals(_ModelResidualsBase):
14
+ """
15
+ Calculate Q residuals (Squared Prediction Error - SPE) for PCA or PLS models.
16
+
17
+ Parameters
18
+ ----------
19
+ model : Union[ModelType, Pipeline]
20
+ A fitted PCA/PLS model or Pipeline ending with such a model.
21
+
22
+ confidence : float, default=0.95
23
+ Confidence level for statistical calculations (between 0 and 1).
24
+
25
+ method : str, default="jackson-mudholkar"
26
+ The method used to compute the confidence threshold for Q residuals.
27
+ Options:
28
+ - "chi-square" : Uses mean and standard deviation to approximate Q residuals threshold.
29
+ - "jackson-mudholkar" : Uses eigenvalue-based analytical approximation.
30
+ - "percentile" : Uses empirical percentile threshold.
31
+
32
+ Attributes
33
+ ----------
34
+ estimator_ : ModelType
35
+ The fitted model of type _BasePCA or _PLS.
36
+
37
+ transformer_ : Optional[Pipeline]
38
+ Preprocessing steps before the model.
39
+
40
+ n_features_in_ : int
41
+ Number of features in the input data.
42
+
43
+ n_components_ : int
44
+ Number of components in the model.
45
+
46
+ n_samples_ : int
47
+ Number of samples used to train the model.
48
+
49
+ critical_value_ : float
50
+ The calculated critical value for outlier detection.
51
+
52
+ References
53
+ ----------
54
+ Johan A. Westerhuis, Stephen P. Gurden, Age K. Smilde (2001) Generalized contribution plots in multivariate statistical process
55
+ monitoring Chemometrics and Intelligent Laboratory Systems 51 2000 95–114
56
+ """
57
+
58
+ def __init__(
59
+ self,
60
+ model: Union[ModelTypes, Pipeline],
61
+ confidence: float = 0.95,
62
+ method: Literal[
63
+ "chi-square", "jackson-mudholkar", "percentile"
64
+ ] = "jackson-mudholkar",
65
+ ) -> None:
66
+ self.model, self.confidence, self.method = model, confidence, method
67
+ super().__init__(model, confidence)
68
+
69
+ def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None) -> "QResiduals":
70
+ """
71
+ Fit the Q Residuals model by computing residuals from the training set.
72
+
73
+ Parameters
74
+ ----------
75
+ X : array-like of shape (n_samples, n_features)
76
+ Training data.
77
+
78
+ Returns
79
+ -------
80
+ self : object
81
+ Fitted instance of QResiduals.
82
+ """
83
+ X = validate_data(self, X, ensure_2d=True, dtype=np.float64)
84
+
85
+ if self.transformer_:
86
+ X = self.transformer_.fit_transform(X)
87
+
88
+ # Compute the critical threshold using the chosen method
89
+ self.critical_value_ = self._calculate_critical_value(X)
90
+
91
+ return self
92
+
93
+ def predict(self, X: np.ndarray) -> np.ndarray:
94
+ """Identify outliers in the input data based on Q residuals threshold.
95
+
96
+ Parameters
97
+ ----------
98
+ X : array-like of shape (n_samples, n_features)
99
+ Input data.
100
+
101
+ Returns
102
+ -------
103
+ ndarray of shape (n_samples,)
104
+ Boolean array indicating outliers (-1 for outliers, 1 for normal data).
105
+ """
106
+ # Check the estimator has been fitted
107
+ check_is_fitted(self, ["critical_value_"])
108
+
109
+ # Validate the input data
110
+ X = validate_data(
111
+ self, X, y="no_validation", ensure_2d=True, reset=True, dtype=np.float64
112
+ )
113
+
114
+ # Calculate outliers based on the Q residuals
115
+ Q_residuals = self.predict_residuals(X, validate=False)
116
+ return np.where(Q_residuals > self.critical_value_, -1, 1)
117
+
118
+ def predict_residuals(
119
+ self, X: np.ndarray, y: Optional[np.ndarray] = None, validate: bool = True
120
+ ) -> np.ndarray:
121
+ """Calculate Q residuals (Squared Prediction Error - SPE) for input data.
122
+
123
+ Parameters
124
+ ----------
125
+ X : array-like of shape (n_samples, n_features)
126
+ Input data.
127
+
128
+ validate : bool, default=True
129
+ Whether to validate the input data.
130
+
131
+ Returns
132
+ -------
133
+ ndarray of shape (n_samples,)
134
+ Q residuals for each sample.
135
+ """
136
+ # Check the estimator has been fitted
137
+ check_is_fitted(self, ["critical_value_"])
138
+
139
+ # Validate the input data
140
+ if validate:
141
+ X = validate_data(self, X, ensure_2d=True, dtype=np.float64)
142
+
143
+ # Apply preprocessing if available
144
+ if self.transformer_:
145
+ X = self.transformer_.transform(X)
146
+
147
+ # Compute reconstruction error (Q residuals)
148
+ residual = calculate_residual_spectrum(X, self.estimator_)
149
+ Q_residuals = np.sum(residual**2, axis=1)
150
+
151
+ return Q_residuals
152
+
153
+ def _calculate_critical_value(
154
+ self,
155
+ X: np.ndarray,
156
+ ) -> float:
157
+ """Calculate the critical value for outlier detection.
158
+
159
+ Parameters
160
+ ----------
161
+ X : array-like of shape (n_samples, n_features)
162
+ Input data.
163
+
164
+ X_reconstructed : array-like of shape (n_samples, n_features)
165
+ Reconstructed input data.
166
+
167
+ method : str Literal["chi-square", "jackson-mudholkar", "percentile"]
168
+ The method used to compute the confidence threshold for Q residuals.
169
+
170
+ Returns
171
+ -------
172
+ float
173
+ The calculated critical value for outlier detection.
174
+
175
+ """
176
+ # Compute Q residuals for training data
177
+ residuals = calculate_residual_spectrum(X, self.estimator_)
178
+
179
+ if self.method == "chi-square":
180
+ return self._chi_square_threshold(residuals)
181
+
182
+ elif self.method == "jackson-mudholkar":
183
+ return self._jackson_mudholkar_threshold(residuals)
184
+
185
+ elif self.method == "percentile":
186
+ Q_residuals = np.sum((residuals) ** 2, axis=1)
187
+ return self._percentile_threshold(Q_residuals)
188
+
189
+ else:
190
+ raise ValueError(
191
+ "Invalid method. Choose from 'chi-square', 'jackson-mudholkar', or 'percentile'."
192
+ )
193
+
194
+ def _chi_square_threshold(self, residuals: np.ndarray) -> float:
195
+ """Compute Q residual threshold using Chi-Square Approximation."""
196
+ eigenvalues = np.linalg.trace(np.cov(residuals.T))
197
+
198
+ theta_1 = np.sum(eigenvalues)
199
+ theta_2 = np.sum(eigenvalues**2)
200
+ # Degrees of freedom approximation
201
+ g = theta_2 / theta_1
202
+ h = (2 * theta_1**2) / theta_2
203
+
204
+ # Compute chi-square critical value at given confidence level
205
+ chi_critical = chi2.ppf(self.confidence, df=h)
206
+
207
+ # Compute final Q residual threshold
208
+ return g * chi_critical
209
+
210
+ def _jackson_mudholkar_threshold(self, residuals: np.ndarray) -> float:
211
+ """Compute Q residual threshold using Jackson & Mudholkar’s analytical method."""
212
+
213
+ eigenvalues = np.linalg.trace(np.cov(residuals.T))
214
+ theta_1 = np.sum(eigenvalues)
215
+ theta_2 = np.sum(eigenvalues**2)
216
+ theta_3 = np.sum(eigenvalues**3)
217
+ z_alpha = norm.ppf(self.confidence)
218
+
219
+ h0 = 1 - (2 * theta_1 * theta_3) / (3 * theta_2**2)
220
+
221
+ term1 = theta_2 * h0 * (1 - h0) / theta_1**2
222
+ term2 = np.sqrt(z_alpha * 2 * theta_2 * h0**2) / theta_1
223
+
224
+ return theta_1 * (1 - term1 + term2) ** (1 / h0)
225
+
226
+ def _percentile_threshold(self, Q_residuals: np.ndarray) -> float:
227
+ """Compute Q residual threshold using the empirical percentile method."""
228
+ return np.percentile(Q_residuals, self.confidence * 100)
@@ -0,0 +1,198 @@
1
+ from typing import Optional, Union
2
+ import numpy as np
3
+
4
+ from sklearn.cross_decomposition._pls import _PLS
5
+ from sklearn.pipeline import Pipeline
6
+ from sklearn.utils.validation import validate_data, check_is_fitted
7
+
8
+
9
+ from ._base import _ModelResidualsBase, ModelTypes
10
+ from .leverage import calculate_leverage
11
+
12
+
13
+ class StudentizedResiduals(_ModelResidualsBase):
14
+ """
15
+ Calculate the Studentized Residuals on a _PLS model preditions.
16
+
17
+ Parameters
18
+ ----------
19
+ model : Union[ModelType, Pipeline]
20
+ A fitted _PLS model or Pipeline ending with such a model
21
+
22
+ Attributes
23
+ ----------
24
+ estimator_ : ModelType
25
+ The fitted model of type _BasePCA or _PLS
26
+
27
+ transformer_ : Optional[Pipeline]
28
+ Preprocessing steps before the model
29
+
30
+ References
31
+ ----------
32
+
33
+ """
34
+
35
+ def __init__(self, model: Union[_PLS, Pipeline], confidence=0.95) -> None:
36
+ self.model, self.confidence = model, confidence
37
+ super().__init__(model, confidence)
38
+
39
+ def fit(self, X: np.ndarray, y: Optional[np.ndarray]) -> "StudentizedResiduals":
40
+ """
41
+ Fit the model to the input data.
42
+
43
+ Parameters
44
+ ----------
45
+ X : array-like of shape (n_samples, n_features)
46
+ Input data
47
+
48
+ y : array-like of shape (n_samples,)
49
+ Target data
50
+ """
51
+ # Validate the input data
52
+ X = validate_data(
53
+ self, X, y="no_validation", ensure_2d=True, reset=True, dtype=np.float64
54
+ )
55
+
56
+ # Preprocess the data
57
+ if self.transformer_:
58
+ X = self.transformer_.transform(X)
59
+
60
+ # Calculate y residuals
61
+ y_residuals = y - self.estimator_.predict(X)
62
+ y_residuals = (
63
+ y_residuals.reshape(-1, 1) if len(y_residuals.shape) == 1 else y_residuals
64
+ )
65
+
66
+ # Calculate the studentized residuals
67
+ studentized_residuals = calculate_studentized_residuals(
68
+ self.estimator_, X, y_residuals
69
+ )
70
+
71
+ # Calculate the critical threshold
72
+ self.critical_value_ = self._calculate_critical_value(studentized_residuals)
73
+
74
+ return self
75
+
76
+ def predict(self, X: np.ndarray, y: Optional[np.ndarray]) -> np.ndarray:
77
+ """Calculate studentized residuals in the model predictions. and return a boolean array indicating outliers.
78
+
79
+ Parameters
80
+ ----------
81
+ X : array-like of shape (n_samples, n_features)
82
+ Input data
83
+
84
+ y : array-like of shape (n_samples,)
85
+ Target data
86
+
87
+ Returns
88
+ -------
89
+ ndarray of shape (n_samples,)
90
+ Studentized residuals of the predictions
91
+ """
92
+ # Check the estimator has been fitted
93
+ check_is_fitted(self, ["critical_value_"])
94
+
95
+ # Validate the input data
96
+ X = validate_data(
97
+ self, X, y="no_validation", ensure_2d=True, reset=True, dtype=np.float64
98
+ )
99
+
100
+ # Preprocess the data
101
+ if self.transformer_:
102
+ X = self.transformer_.transform(X)
103
+
104
+ # Calculate y residuals
105
+ y_residuals = y - self.estimator_.predict(X)
106
+ y_residuals = (
107
+ y_residuals.reshape(-1, 1) if len(y_residuals.shape) == 1 else y_residuals
108
+ )
109
+
110
+ # Calculate the studentized residuals
111
+ studentized_residuals = calculate_studentized_residuals(
112
+ self.estimator_, X, y_residuals
113
+ )
114
+ return np.where(studentized_residuals > self.critical_value_, -1, 1)
115
+
116
+ def predict_residuals(
117
+ self, X: np.ndarray, y: Optional[np.ndarray], validate: bool = True
118
+ ) -> np.ndarray:
119
+ """Calculate the studentized residuals of the model predictions.
120
+
121
+ Parameters
122
+ ----------
123
+ X : array-like of shape (n_samples, n_features)
124
+ Input data
125
+
126
+ y : array-like of shape (n_samples,)
127
+ Target values
128
+
129
+ Returns
130
+ -------
131
+ ndarray of shape (n_samples,)
132
+ Studentized residuals of the model predictions
133
+ """
134
+ # Check the estimator has been fitted
135
+ check_is_fitted(self, ["critical_value_"])
136
+
137
+ # Validate the input data
138
+ if validate:
139
+ X = validate_data(self, X, ensure_2d=True, dtype=np.float64)
140
+
141
+ # Apply preprocessing if available
142
+ if self.transformer_:
143
+ X = self.transformer_.transform(X)
144
+
145
+ # Calculate y residuals
146
+ y_residuals = y - self.estimator_.predict(X)
147
+ y_residuals = (
148
+ y_residuals.reshape(-1, 1) if len(y_residuals.shape) == 1 else y_residuals
149
+ )
150
+
151
+ return calculate_studentized_residuals(self.estimator_, X, y_residuals)
152
+
153
+ def _calculate_critical_value(self, X: np.ndarray) -> float:
154
+ """Calculate the critical value for outlier detection.
155
+
156
+ Parameters
157
+ ----------
158
+ X : array-like of shape (n_samples,)
159
+ Studentized residuals
160
+
161
+ Returns
162
+ -------
163
+ float
164
+ The calculated critical value for outlier detection
165
+ """
166
+
167
+ return np.percentile(X, self.confidence * 100) if X is not None else 0.0
168
+
169
+
170
+ def calculate_studentized_residuals(
171
+ model: ModelTypes, X: np.ndarray, y_residuals: np.ndarray
172
+ ) -> np.ndarray:
173
+ """Calculate the studentized residuals of the model predictions.
174
+
175
+ Parameters
176
+ ----------
177
+ model : ModelTypes
178
+ A fitted model
179
+
180
+ X : array-like of shape (n_samples, n_features)
181
+ Input data
182
+
183
+ y : array-like of shape (n_samples,)
184
+ Target values
185
+
186
+ Returns
187
+ -------
188
+ ndarray of shape (n_samples,)
189
+ Studentized residuals of the model predictions
190
+ """
191
+
192
+ # Calculate the leverage of the samples
193
+ leverage = calculate_leverage(X, model)
194
+
195
+ # Calculate the standard deviation of the residuals
196
+ std = np.sqrt(np.sum(y_residuals**2, axis=0) / (X.shape[0] - model.n_components))
197
+
198
+ return (y_residuals / (std * np.sqrt(1 - leverage.reshape(-1, 1)))).flatten()
@@ -0,0 +1,51 @@
1
+ import numpy as np
2
+
3
+ from ._base import ModelTypes
4
+
5
+
6
+ def calculate_decoded_spectrum(X: np.ndarray, estimator: ModelTypes):
7
+ """
8
+ Calculate the decoded spectrum for a given transformed (preprocessed!!) spectrum and estimator from the latent space.
9
+
10
+ Parameters
11
+ ----------
12
+ spectrum : np.ndarray
13
+ The transformed spectrum data.
14
+
15
+ estimator : ModelTypes
16
+ The fitted PCA or PLS model.
17
+
18
+ Returns
19
+ -------
20
+ np.ndarray
21
+ The decoded spectrum.
22
+ """
23
+ # Project the transformed spectrum onto the latent space
24
+ X_transformed = estimator.transform(X)
25
+
26
+ # Decode the spectrum back to the original space
27
+ return estimator.inverse_transform(X_transformed)
28
+
29
+
30
+ def calculate_residual_spectrum(X: np.ndarray, estimator: ModelTypes):
31
+ """
32
+ Calculate the residual spectrum for a given transformed (preprocessed!!) spectrum and estimator.
33
+
34
+ Parameters
35
+ ----------
36
+ spectrum : np.ndarray
37
+ The transformed spectrum data.
38
+
39
+ estimator : ModelTypes
40
+ The fitted PCA or PLS model.
41
+
42
+ Returns
43
+ -------
44
+ np.ndarray
45
+ The residual spectrum.
46
+ """
47
+ # Compute the reconstruction error (Q residuals)
48
+ decoded_spectrum = calculate_decoded_spectrum(X, estimator)
49
+
50
+ # Calculate the residual
51
+ return X - decoded_spectrum
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: chemotools
3
- Version: 0.1.9
3
+ Version: 0.1.11rc0
4
4
  Summary: chemotools: A Python Package that Integrates Chemometrics and scikit-learn
5
5
  License: MIT
6
6
  Author: Pau Cabaneros
@@ -1,8 +1,9 @@
1
1
  chemotools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- chemotools/augmentation/__init__.py,sha256=iRltqvskLJAhwxxlTtGPWJfR5XiwvQfqbv4QdUMF9BU,318
2
+ chemotools/augmentation/__init__.py,sha256=ohlRHgRWTkvNpO3RikKtowzboqunQqx0WqtNccuWOHw,397
3
3
  chemotools/augmentation/_add_noise.py,sha256=fkTJfIYtZXezcjy6Vz8asIhpBoVp4oaIifppK9vZpM8,4362
4
4
  chemotools/augmentation/_baseline_shift.py,sha256=kIlYvmKS9pu9vh_-eZ7PSHPuH_58V9mgYbSJt6Gq3BA,3476
5
5
  chemotools/augmentation/_fractional_shift.py,sha256=dJ0Vuc-U02HhjKkOwc48qnOksZYgbHwL2ko7tWCZTQU,6916
6
+ chemotools/augmentation/_gaussian_broadening.py,sha256=dJsPlTKqpecKaCDU3vOvedIb-t_HyCkQprxNv0DmYZQ,4236
6
7
  chemotools/augmentation/_index_shift.py,sha256=BTtadweDvvMtiF8t7ldwsE6Kl6FmKLCkVJjSzSWyIDs,6904
7
8
  chemotools/augmentation/_spectrum_scale.py,sha256=hMsmzXpssbI7tGm_YnQn9wjbByso3CgVxd3Hs8kfLS8,3442
8
9
  chemotools/baseline/__init__.py,sha256=VzoblGg8Hx_FkTc_n7a-ZjGvtKP8JE_NwJKWenGFQkM,584
@@ -29,6 +30,14 @@ chemotools/derivative/_savitzky_golay.py,sha256=CuCrKoLmrB1YmJ4ihIykgkL3tO3frqkS
29
30
  chemotools/feature_selection/__init__.py,sha256=1_i28hIxijjwhMypTy1w2fLbzXXVkKD5IYzzY8ZSuHw,117
30
31
  chemotools/feature_selection/_index_selector.py,sha256=lNTP2b7P3doWl30KiAr3Xd2HOMxeUmj24MuqoXl4Voc,3556
31
32
  chemotools/feature_selection/_range_cut.py,sha256=lVVVC30ZsK2z9jsDGb_z6l8Ty2I89yM05_dIDbMP73Q,3564
33
+ chemotools/outliers/__init__.py,sha256=wpdlyqU34n1Pb9kGCM4idhcok35WAakxEhzP0xeKaZw,272
34
+ chemotools/outliers/_base.py,sha256=zl0LhRKjpvj5IbYc3su6zEZ7YZ0pDSR3yqNWt2qBjNA,5374
35
+ chemotools/outliers/dmodx.py,sha256=sgizal_BDlqWTZNT8y2D_ImcKAJejXt6vqvFYk4Vqi0,5152
36
+ chemotools/outliers/hotelling_t2.py,sha256=g_IOQD_rhKb3cjIJkn5OTto6bYClQtqXunG_02BSIs8,5087
37
+ chemotools/outliers/leverage.py,sha256=hNQ_x68LPPTDZvSJP_eRqu3GoeV3OBU37VC_XTFEzvw,4250
38
+ chemotools/outliers/q_residuals.py,sha256=sg7u8ockQvSSnXwNM4U-GITB-5OcbsDMX6Oig_TcONM,7598
39
+ chemotools/outliers/studentized_residuals.py,sha256=1L-GiutuO1x9s3UKMOBpmhs2Q-UuDtfG2YLELIxiiao,5890
40
+ chemotools/outliers/utils.py,sha256=SAjvtjl9oWHrQnkqGnDfYE4WWAgiL1RwnKmW-ql5TIc,1304
32
41
  chemotools/scale/__init__.py,sha256=eztqcHg-TKE1Rr0N9ArfytHk8teuqVfi4SZi2DS96vc,175
33
42
  chemotools/scale/_min_max_scaler.py,sha256=YvqRkV2pXu-viQrpjzWcp9KmSSCYSoubSnrZHRLqgKQ,3011
34
43
  chemotools/scale/_norm_scaler.py,sha256=CHWSir2q-pL1hxzw_ZB45yi4mw-SkJ4YOa1CUL4nm2I,2568
@@ -44,7 +53,7 @@ chemotools/smooth/_median_filter.py,sha256=9ndTJCwrZirWlvDNldiigMddy79KIGq9OwwYN
44
53
  chemotools/smooth/_savitzky_golay_filter.py,sha256=27iFUWxdL9_7oZabR0R5L0ZTpBmYfVUjx2XCTukihBE,3509
45
54
  chemotools/smooth/_whittaker_smooth.py,sha256=lpLAyf4GdyDW4ulT1nyEoK6xQEl2cVUKquawQdGWbHU,3571
46
55
  chemotools/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
- chemotools-0.1.9.dist-info/LICENSE,sha256=qtyOy2wDQVX9hxp58h3T-6Lmfv-mSCHoSRkcLUdM9bg,1070
48
- chemotools-0.1.9.dist-info/METADATA,sha256=9sih25qSOTJX36gib96hxm-e86TorD040r_WNFzbc9U,5239
49
- chemotools-0.1.9.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
50
- chemotools-0.1.9.dist-info/RECORD,,
56
+ chemotools-0.1.11rc0.dist-info/LICENSE,sha256=qtyOy2wDQVX9hxp58h3T-6Lmfv-mSCHoSRkcLUdM9bg,1070
57
+ chemotools-0.1.11rc0.dist-info/METADATA,sha256=lhDYugMDLS5dHu86xGNho2mAj25vfFucPMEQAHmhfpA,5243
58
+ chemotools-0.1.11rc0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
59
+ chemotools-0.1.11rc0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.0.1
2
+ Generator: poetry-core 2.1.2
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any