chemotools 0.1.8__py3-none-any.whl → 0.1.9__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,5 +1,6 @@
1
1
  from ._add_noise import AddNoise
2
2
  from ._baseline_shift import BaselineShift
3
+ from ._fractional_shift import FractionalShift
3
4
  from ._index_shift import IndexShift
4
5
  from ._spectrum_scale import SpectrumScale
5
6
 
@@ -7,6 +8,7 @@ from ._spectrum_scale import SpectrumScale
7
8
  __all__ = [
8
9
  "AddNoise",
9
10
  "BaselineShift",
11
+ "FractionalShift",
10
12
  "IndexShift",
11
13
  "SpectrumScale",
12
14
  ]
@@ -6,72 +6,95 @@ from sklearn.utils.validation import check_is_fitted, validate_data
6
6
 
7
7
 
8
8
  class AddNoise(TransformerMixin, OneToOneFeatureMixin, BaseEstimator):
9
- """
10
- Add normal noise to the input data.
9
+ """Add noise to input data from various probability distributions.
10
+
11
+ This transformer adds random noise from specified probability distributions
12
+ to the input data. Supported distributions include Gaussian, Poisson, and
13
+ exponential.
14
+
15
+ Parameters
16
+ ----------
17
+ distribution : {'gaussian', 'poisson', 'exponential'}, default='gaussian'
18
+ The probability distribution to sample noise from.
19
+ scale : float, default=0.0
20
+ Scale parameter for the noise distribution:
21
+ - For gaussian: standard deviation
22
+ - For poisson: multiplication factor for sampled values
23
+ - For exponential: scale parameter (1/λ)
24
+ random_state : int, optional
25
+ Random seed for reproducibility.
26
+
27
+ Attributes
28
+ ----------
29
+ n_features_in_ : int
30
+ Number of features in the training data.
11
31
  """
12
32
 
13
33
  def __init__(
14
34
  self,
15
- noise_distribution: Literal["gaussian", "poisson", "exponential"] = "gaussian",
35
+ distribution: Literal["gaussian", "poisson", "exponential"] = "gaussian",
16
36
  scale: float = 0.0,
17
37
  random_state: Optional[int] = None,
18
38
  ):
19
- self.noise_distribution = noise_distribution
39
+ self.distribution = distribution
20
40
  self.scale = scale
21
41
  self.random_state = random_state
22
42
 
23
43
  def fit(self, X: np.ndarray, y=None) -> "AddNoise":
24
- """
25
- Fit the transformer to the input data.
44
+ """Fit the transformer to the input data.
26
45
 
27
46
  Parameters
28
47
  ----------
29
- X : np.ndarray of shape (n_samples, n_features)
30
- The input data to fit the transformer to.
31
-
48
+ X : array-like of shape (n_samples, n_features)
49
+ Training data.
32
50
  y : None
33
- Ignored.
51
+ Ignored. Present for API consistency.
34
52
 
35
53
  Returns
36
54
  -------
37
- self : NormalNoise
38
- The fitted transformer.
55
+ self : AddNoise
56
+ Fitted transformer.
57
+
58
+ Raises
59
+ ------
60
+ ValueError
61
+ If X is not a 2D array or contains non-finite values.
39
62
  """
63
+
40
64
  # Check that X is a 2D array and has only finite values
41
65
  X = validate_data(
42
66
  self, X, y="no_validation", ensure_2d=True, reset=True, dtype=np.float64
43
67
  )
44
68
 
45
- # Set the number of features
46
- self.n_features_in_ = X.shape[1]
47
-
48
- # Set the fitted attribute to True
49
- self._is_fitted = True
50
-
51
69
  # Instantiate the random number generator
52
70
  self._rng = np.random.default_rng(self.random_state)
53
71
 
54
72
  return self
55
73
 
56
74
  def transform(self, X: np.ndarray, y=None) -> np.ndarray:
57
- """
58
- Transform the input data by adding random normal noise.
75
+ """Transform the input data by adding random noise.
59
76
 
60
77
  Parameters
61
78
  ----------
62
- X : np.ndarray of shape (n_samples, n_features)
63
- The input data to transform.
64
-
79
+ X : array-like of shape (n_samples, n_features)
80
+ Input data to transform.
65
81
  y : None
66
- Ignored.
82
+ Ignored. Present for API consistency.
67
83
 
68
84
  Returns
69
85
  -------
70
- X_ : np.ndarray of shape (n_samples, n_features)
71
- The transformed data.
86
+ X_noisy : ndarray of shape (n_samples, n_features)
87
+ Transformed data with added noise.
88
+
89
+ Raises
90
+ ------
91
+ ValueError
92
+ If X has different number of features than the training data,
93
+ or if an invalid noise distribution is specified.
72
94
  """
95
+
73
96
  # Check that the estimator is fitted
74
- check_is_fitted(self, "_is_fitted")
97
+ check_is_fitted(self, "n_features_in_")
75
98
 
76
99
  # Check that X is a 2D array and has only finite values
77
100
  X_ = validate_data(
@@ -84,31 +107,29 @@ class AddNoise(TransformerMixin, OneToOneFeatureMixin, BaseEstimator):
84
107
  dtype=np.float64,
85
108
  )
86
109
 
87
- # Check that the number of features is the same as the fitted data
88
- if X_.shape[1] != self.n_features_in_:
110
+ # Select the noise function based on the selected distribution
111
+ noise_func = {
112
+ "gaussian": self._add_gaussian_noise,
113
+ "poisson": self._add_poisson_noise,
114
+ "exponential": self._add_exponential_noise,
115
+ }.get(self.distribution)
116
+
117
+ if noise_func is None:
89
118
  raise ValueError(
90
- f"Expected {self.n_features_in_} features but got {X_.shape[1]}"
119
+ f"Invalid noise distribution: {self.distribution}. "
120
+ "Expected one of: gaussian, poisson, exponential"
91
121
  )
92
122
 
93
- # Calculate the standard normal variate
94
- for i, x in enumerate(X_):
95
- match self.noise_distribution:
96
- case "gaussian":
97
- X_[i] = self._add_gaussian_noise(x)
98
- case "poisson":
99
- X_[i] = self._add_poisson_noise(x)
100
- case "exponential":
101
- X_[i] = self._add_exponential_noise(x)
102
- case _:
103
- raise ValueError("Invalid noise distribution")
104
-
105
- return X_.reshape(-1, 1) if X_.ndim == 1 else X_
123
+ return noise_func(X_)
106
124
 
107
- def _add_gaussian_noise(self, x) -> np.ndarray:
108
- return x + self._rng.normal(0, self.scale, size=x.shape)
125
+ def _add_gaussian_noise(self, X: np.ndarray) -> np.ndarray:
126
+ """Add Gaussian noise to the input array."""
127
+ return X + self._rng.normal(0, self.scale, size=X.shape)
109
128
 
110
- def _add_poisson_noise(self, x) -> np.ndarray:
111
- return self._rng.poisson(x, size=x.shape) * self.scale
129
+ def _add_poisson_noise(self, X: np.ndarray) -> np.ndarray:
130
+ """Add Poisson noise to the input array."""
131
+ return X + self._rng.poisson(X, size=X.shape) * self.scale
112
132
 
113
- def _add_exponential_noise(self, x) -> np.ndarray:
114
- return x + self._rng.exponential(self.scale, size=x.shape)
133
+ def _add_exponential_noise(self, X: np.ndarray) -> np.ndarray:
134
+ """Add exponential noise to the input array."""
135
+ return X + self._rng.exponential(self.scale, size=X.shape)
@@ -0,0 +1,203 @@
1
+ from typing import Literal, Optional
2
+
3
+ import numpy as np
4
+ from scipy.interpolate import CubicSpline
5
+ from scipy import stats
6
+ from sklearn.base import BaseEstimator, TransformerMixin, OneToOneFeatureMixin
7
+ from sklearn.utils.validation import check_is_fitted, validate_data
8
+
9
+
10
+ class FractionalShift(TransformerMixin, OneToOneFeatureMixin, BaseEstimator):
11
+ """
12
+ Shift the spectrum by a fractional amount, allowing shifts below one index.
13
+
14
+ Parameters
15
+ ----------
16
+ shift : float, default=0.0
17
+ Maximum amount by which the data is randomly shifted.
18
+ The actual shift is a random float between -shift and shift.
19
+
20
+ padding_mode : {'zeros', 'constant', 'wrap', 'extend', 'mirror', 'linear'}, default='linear'
21
+ Specifies how to handle padding when shifting the data:
22
+ - 'zeros': Pads with zeros.
23
+ - 'constant': Pads with a constant value defined by `pad_value`.
24
+ - 'wrap': Circular shift (wraps around).
25
+ - 'extend': Extends using edge values.
26
+ - 'mirror': Mirrors the signal.
27
+ - 'linear': Uses linear regression on 5 points to extrapolate values.
28
+
29
+ pad_value : float, default=0.0
30
+ The value used for padding when `padding_mode='constant'`.
31
+
32
+ random_state : int, optional, default=None
33
+ The random seed for reproducibility.
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ shift: float = 0.0,
39
+ padding_mode: Literal[
40
+ "zeros", "constant", "extend", "mirror", "linear"
41
+ ] = "linear",
42
+ pad_value: float = 0.0,
43
+ random_state: Optional[int] = None,
44
+ ):
45
+ self.shift = shift
46
+ self.padding_mode = padding_mode
47
+ self.pad_value = pad_value
48
+ self.random_state = random_state
49
+
50
+ def fit(self, X: np.ndarray, y=None) -> "FractionalShift":
51
+ """
52
+ Fit the transformer to the input data.
53
+
54
+ Parameters
55
+ ----------
56
+ X : np.ndarray of shape (n_samples, n_features)
57
+ The input data to fit the transformer to.
58
+
59
+ y : None
60
+ Ignored.
61
+
62
+ Returns
63
+ -------
64
+ self : FractionalShift
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
+ self._rng = np.random.default_rng(self.random_state)
71
+ return self
72
+
73
+ def transform(self, X: np.ndarray, y=None) -> np.ndarray:
74
+ """
75
+ Transform the input data by shifting the spectrum.
76
+
77
+ Parameters
78
+ ----------
79
+ X : np.ndarray of shape (n_samples, n_features)
80
+ The input data to transform.
81
+
82
+ y : None
83
+ Ignored.
84
+
85
+ Returns
86
+ -------
87
+ X_ : np.ndarray of shape (n_samples, n_features)
88
+ The transformed data with the applied shifts.
89
+ """
90
+ check_is_fitted(self, "n_features_in_")
91
+ X_ = validate_data(
92
+ self,
93
+ X,
94
+ y="no_validation",
95
+ ensure_2d=True,
96
+ copy=True,
97
+ reset=False,
98
+ dtype=np.float64,
99
+ )
100
+
101
+ for i, x in enumerate(X_):
102
+ X_[i] = self._shift_signal(x)
103
+
104
+ return X_.reshape(-1, 1) if X_.ndim == 1 else X_
105
+
106
+ def _shift_signal(self, x: np.ndarray) -> np.ndarray:
107
+ """
108
+ Shifts a signal by a fractional amount using cubic spline interpolation.
109
+
110
+ Parameters
111
+ ----------
112
+ x : np.ndarray of shape (n_features,)
113
+ The input signal to shift.
114
+
115
+ Returns
116
+ -------
117
+ shifted_signal : np.ndarray of shape (n_features,)
118
+ The shifted signal.
119
+ """
120
+ shift = self._rng.uniform(-self.shift, self.shift)
121
+ n = len(x)
122
+ indices = np.arange(n)
123
+ shifted_indices = indices + shift
124
+
125
+ # Create cubic spline interpolator
126
+ spline = CubicSpline(indices, x, bc_type="not-a-knot")
127
+ shifted_signal = spline(shifted_indices)
128
+
129
+ # Determine padding direction and length
130
+ if shift >= 0:
131
+ pad_length = len(shifted_indices[shifted_indices >= n - 1])
132
+ pad_left = False
133
+ else:
134
+ pad_length = len(shifted_indices[shifted_indices < 0])
135
+ pad_left = True
136
+
137
+ # Handle padding based on mode
138
+ if self.padding_mode == "zeros":
139
+ shifted_signal[shifted_indices < 0] = 0
140
+ shifted_signal[shifted_indices >= n - 1] = 0
141
+
142
+ elif self.padding_mode == "constant":
143
+ shifted_signal[shifted_indices < 0] = self.pad_value
144
+ shifted_signal[shifted_indices >= n - 1] = self.pad_value
145
+
146
+ elif self.padding_mode == "mirror":
147
+ if pad_left:
148
+ pad_values = x[pad_length - 1 :: -1]
149
+ shifted_signal[shifted_indices < 0] = pad_values[:pad_length]
150
+ else:
151
+ pad_values = x[:-1][::-1]
152
+ shifted_signal[shifted_indices >= n - 1] = pad_values[:pad_length]
153
+
154
+ elif self.padding_mode == "extend":
155
+ if pad_left:
156
+ shifted_signal[shifted_indices < 0] = x[0]
157
+ else:
158
+ shifted_signal[shifted_indices >= n - 1] = x[-1]
159
+
160
+ elif self.padding_mode == "linear":
161
+ if pad_left:
162
+ # Use first 5 points for regression
163
+ if len(x) < 5:
164
+ points = x[: len(x)] # Use all points if less than 5
165
+ else:
166
+ points = x[:5]
167
+ x_coords = np.arange(len(points))
168
+
169
+ # Reshape arrays for linregress
170
+ x_coords = x_coords.reshape(-1)
171
+ points = points.reshape(-1)
172
+
173
+ # Perform regression
174
+ slope, intercept, _, _, _ = stats.linregress(x_coords, points)
175
+
176
+ # Generate new points using linear regression
177
+ new_x = np.arange(-pad_length, 0)
178
+ extrapolated = slope * new_x + intercept
179
+ shifted_signal[shifted_indices < 0] = extrapolated
180
+ else:
181
+ # Use last 5 points for regression
182
+ if len(x) < 5:
183
+ points = x[-len(x) :] # Use all points if less than 5
184
+ else:
185
+ points = x[-5:]
186
+ x_coords = np.arange(len(points))
187
+
188
+ # Reshape arrays for linregress
189
+ x_coords = x_coords.reshape(-1)
190
+ points = points.reshape(-1)
191
+
192
+ # Perform regression
193
+ slope, intercept, _, _, _ = stats.linregress(x_coords, points)
194
+
195
+ # Generate new points using linear regression
196
+ new_x = np.arange(len(points), len(points) + pad_length)
197
+ extrapolated = slope * new_x + intercept
198
+ shifted_signal[shifted_indices >= n] = extrapolated
199
+
200
+ else:
201
+ raise ValueError(f"Unknown padding mode: {self.padding_mode}")
202
+
203
+ return shifted_signal
@@ -1,23 +1,37 @@
1
1
  from typing import Literal, Optional
2
2
 
3
3
  import numpy as np
4
- from numpy.polynomial import polynomial as poly
4
+ from scipy.signal import convolve
5
+ from scipy import stats
5
6
  from sklearn.base import BaseEstimator, TransformerMixin, OneToOneFeatureMixin
6
7
  from sklearn.utils.validation import check_is_fitted, validate_data
7
8
 
8
9
 
9
10
  class IndexShift(TransformerMixin, OneToOneFeatureMixin, BaseEstimator):
10
11
  """
11
- Shift the spectrum a given number of indices between - shift and + shift drawn
12
+ Shift the spectrum a given number of indices between -shift and +shift drawn
12
13
  from a discrete uniform distribution.
13
14
 
14
15
  Parameters
15
16
  ----------
16
- shift : float, default=0.0
17
- Shifts the data by a random integer between -shift and shift.
17
+ shift : int, default=0
18
+ Maximum number of indices by which the data is randomly shifted.
19
+ The actual shift is a random integer between -shift and shift (inclusive).
18
20
 
19
- random_state : int, default=None
20
- The random state to use for the random number generator.
21
+ padding_mode : {'zeros', 'constant', 'wrap', 'extend', 'mirror', 'linear'}, default='linear'
22
+ Specifies how to handle padding when shifting the data:
23
+ - 'zeros': Pads with zeros.
24
+ - 'constant': Pads with a constant value defined by `pad_value`.
25
+ - 'wrap': Circular shift (wraps around).
26
+ - 'extend': Extends using edge values.
27
+ - 'mirror': Mirrors the signal.
28
+ - 'linear': Uses linear regression to extrapolate values.
29
+
30
+ pad_value : float, default=0.0
31
+ The value used for padding when `padding_mode='constant'`.
32
+
33
+ random_state : int, optional, default=None
34
+ The random seed for reproducibility.
21
35
 
22
36
  Attributes
23
37
  ----------
@@ -27,23 +41,22 @@ class IndexShift(TransformerMixin, OneToOneFeatureMixin, BaseEstimator):
27
41
  _is_fitted : bool
28
42
  Whether the transformer has been fitted to data.
29
43
 
30
- Methods
31
- -------
32
- fit(X, y=None)
33
- Fit the transformer to the input data.
34
-
35
- transform(X, y=0, copy=True)
36
- Transform the input data by shifting the spectrum.
44
+ _rng : numpy.random.Generator
45
+ Random number generator instance used for shifting.
37
46
  """
38
47
 
39
48
  def __init__(
40
49
  self,
41
50
  shift: int = 0,
42
- fill_method: Literal["constant", "linear", "quadratic"] = "constant",
51
+ padding_mode: Literal[
52
+ "zeros", "constant", "wrap", "extend", "mirror", "linear"
53
+ ] = "linear",
54
+ pad_value: float = 0.0,
43
55
  random_state: Optional[int] = None,
44
56
  ):
45
57
  self.shift = shift
46
- self.fill_method = fill_method
58
+ self.padding_mode = padding_mode
59
+ self.pad_value = pad_value
47
60
  self.random_state = random_state
48
61
 
49
62
  def fit(self, X: np.ndarray, y=None) -> "IndexShift":
@@ -68,12 +81,6 @@ class IndexShift(TransformerMixin, OneToOneFeatureMixin, BaseEstimator):
68
81
  self, X, y="no_validation", ensure_2d=True, reset=True, dtype=np.float64
69
82
  )
70
83
 
71
- # Set the number of features
72
- self.n_features_in_ = X.shape[1]
73
-
74
- # Set the fitted attribute to True
75
- self._is_fitted = True
76
-
77
84
  # Instantiate the random number generator
78
85
  self._rng = np.random.default_rng(self.random_state)
79
86
 
@@ -94,10 +101,10 @@ class IndexShift(TransformerMixin, OneToOneFeatureMixin, BaseEstimator):
94
101
  Returns
95
102
  -------
96
103
  X_ : np.ndarray of shape (n_samples, n_features)
97
- The transformed data.
104
+ The transformed data with the applied shifts.
98
105
  """
99
106
  # Check that the estimator is fitted
100
- check_is_fitted(self, "_is_fitted")
107
+ check_is_fitted(self, "n_features_in_")
101
108
 
102
109
  # Check that X is a 2D array and has only finite values
103
110
  X_ = validate_data(
@@ -110,90 +117,98 @@ class IndexShift(TransformerMixin, OneToOneFeatureMixin, BaseEstimator):
110
117
  dtype=np.float64,
111
118
  )
112
119
 
113
- # Check that the number of features is the same as the fitted data
114
- if X_.shape[1] != self.n_features_in_:
115
- raise ValueError(
116
- f"Expected {self.n_features_in_} features but got {X_.shape[1]}"
117
- )
118
-
119
120
  # Calculate the standard normal variate
120
121
  for i, x in enumerate(X_):
121
- X_[i] = self._shift_vector(x)
122
+ X_[i] = self._shift_signal(x)
122
123
 
123
124
  return X_.reshape(-1, 1) if X_.ndim == 1 else X_
124
125
 
125
- def _shift_spectrum(self, x) -> np.ndarray:
126
- shift_amount = self._rng.integers(-self.shift, self.shift, endpoint=True)
127
- return np.roll(x, shift_amount)
128
-
129
- def _shift_vector(
130
- self,
131
- x: np.ndarray,
132
- ) -> np.ndarray:
126
+ def _shift_signal(self, x: np.ndarray):
133
127
  """
134
- Shift vector with option to fill missing values.
135
-
136
- Args:
137
- arr: Input numpy array
138
- shift: Number of positions to shift
139
- fill_method: Method to fill missing values
140
- 'constant': fill with first/last value
141
- 'linear': fill using linear regression
142
- 'quadratic': fill using quadratic regression
143
-
144
- Returns:
145
- Shifted numpy array
146
- """
147
- shift = self._rng.integers(-self.shift, self.shift, endpoint=True)
148
-
149
- result = np.roll(x, shift)
150
-
151
- if self.fill_method == "constant":
152
- if shift > 0:
153
- result[:shift] = x[0]
154
- elif shift < 0:
155
- result[shift:] = x[-1]
156
-
157
- elif self.fill_method == "linear":
158
- if shift > 0:
159
- x_ = np.arange(5)
160
- coeffs = poly.polyfit(x_, x[:5], 1)
128
+ Shifts a discrete signal using convolution with a Dirac delta kernel.
161
129
 
162
- extrapolate_x = np.arange(-shift, 0)
163
- extrapolated_values = poly.polyval(extrapolate_x, coeffs)
164
-
165
- result[:shift] = extrapolated_values
166
-
167
- elif shift < 0:
168
- x_ = np.arange(5)
169
- coeffs = poly.polyfit(x_, x[-5:], 1)
170
-
171
- extrapolate_x = np.arange(len(x_), len(x_) - shift)
172
- extrapolated_values = poly.polyval(extrapolate_x, coeffs)
173
-
174
- result[shift:] = extrapolated_values
175
-
176
- elif self.fill_method == "quadratic":
177
- if shift > 0:
178
- # Use first 3 values for quadratic regression
179
- x_ = np.arange(5)
180
- coeffs = poly.polyfit(x_, x[:5], 2)
181
-
182
- # Extrapolate to fill shifted region
183
- extrapolate_x = np.arange(-shift, 0)
184
- extrapolated_values = poly.polyval(extrapolate_x, coeffs)
185
-
186
- result[:shift] = extrapolated_values
187
-
188
- elif shift < 0:
189
- # Use last 3 values for quadratic regression
190
- x_ = np.arange(5)
191
- coeffs = poly.polyfit(x_, x[-5:], 2)
192
-
193
- # Extrapolate to fill shifted region
194
- extrapolate_x = np.arange(len(x_), len(x_) - shift)
195
- extrapolated_values = poly.polyval(extrapolate_x, coeffs)
130
+ Parameters
131
+ ----------
132
+ x : np.ndarray of shape (n_features,)
133
+ The input signal to shift.
196
134
 
197
- result[shift:] = extrapolated_values
135
+ Returns
136
+ -------
137
+ result : np.ndarray of shape (n_features,)
138
+ The shifted signal.
139
+ """
140
+ shift = self._rng.integers(-self.shift, self.shift, endpoint=True)
198
141
 
199
- return result
142
+ if self.padding_mode == "wrap":
143
+ return np.roll(x, shift)
144
+
145
+ # Create Dirac delta kernel with proper dimensions
146
+
147
+ if shift >= 0:
148
+ kernel = np.zeros(shift + 1)
149
+ kernel[-1] = 1
150
+ else:
151
+ kernel = np.zeros(-shift + 1)
152
+ kernel[0] = 1
153
+
154
+ # Convolve signal with kernel
155
+ shifted = convolve(x, kernel, mode="full")
156
+
157
+ if shift >= 0:
158
+ result = shifted[: len(x)] if x.ndim == 1 else shifted[: x.shape[0]]
159
+ pad_length = shift
160
+ pad_left = True
161
+ else:
162
+ result = shifted[-len(x) :] if x.ndim == 1 else shifted[-x.shape[0] :]
163
+ pad_length = -shift
164
+ pad_left = False
165
+
166
+ if self.padding_mode == "zeros":
167
+ return result
168
+
169
+ elif self.padding_mode == "constant":
170
+ mask = np.abs(result) < 1e-10
171
+ result[mask] = self.pad_value
172
+ return result
173
+
174
+ elif self.padding_mode == "mirror":
175
+ if pad_left:
176
+ pad_values = x[pad_length - 1 :: -1]
177
+ result[:pad_length] = pad_values[-pad_length:]
178
+ else:
179
+ pad_values = x[:-1][::-1]
180
+ result[-pad_length:] = pad_values[:pad_length]
181
+
182
+ return result
183
+
184
+ elif self.padding_mode == "extend":
185
+ if pad_left:
186
+ result[:pad_length] = x[0]
187
+ else:
188
+ result[-pad_length:] = x[-1]
189
+ return result
190
+
191
+ elif self.padding_mode == "linear":
192
+ # Get points for linear regression
193
+ if pad_left:
194
+ points = x[: pad_length + 1] # Take first pad_length+1 points
195
+ x_coords = np.arange(len(points))
196
+ slope, intercept, _, _, _ = stats.linregress(x_coords, points)
197
+
198
+ # Generate new points using linear regression
199
+ new_x = np.arange(-pad_length, 0)
200
+ extrapolated = slope * new_x + intercept
201
+ result[:pad_length] = extrapolated
202
+ else:
203
+ points = x[-pad_length - 1 :] # Take last pad_length+1 points
204
+ x_coords = np.arange(len(points))
205
+ slope, intercept, _, _, _ = stats.linregress(x_coords, points)
206
+
207
+ # Generate new points using linear regression
208
+ new_x = np.arange(len(points), len(points) + pad_length)
209
+ extrapolated = slope * new_x + intercept
210
+ result[-pad_length:] = extrapolated
211
+ return result
212
+
213
+ else:
214
+ raise ValueError(f"Unknown padding mode: {self.padding_mode}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: chemotools
3
- Version: 0.1.8
3
+ Version: 0.1.9
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=_DiyO7M0xztix8Ea_esxe0xjEYHTneJVJZ52bu5WFpg,248
3
- chemotools/augmentation/_add_noise.py,sha256=4SQFiU9Snl0Dz5EfvRjimpndlNGdXxW2ya3YplHL2fg,3502
2
+ chemotools/augmentation/__init__.py,sha256=iRltqvskLJAhwxxlTtGPWJfR5XiwvQfqbv4QdUMF9BU,318
3
+ chemotools/augmentation/_add_noise.py,sha256=fkTJfIYtZXezcjy6Vz8asIhpBoVp4oaIifppK9vZpM8,4362
4
4
  chemotools/augmentation/_baseline_shift.py,sha256=kIlYvmKS9pu9vh_-eZ7PSHPuH_58V9mgYbSJt6Gq3BA,3476
5
- chemotools/augmentation/_index_shift.py,sha256=w1maDHGLAKSiGAQ8c9yYHofs_PJnxeN0nB1RU-pINcE,6042
5
+ chemotools/augmentation/_fractional_shift.py,sha256=dJ0Vuc-U02HhjKkOwc48qnOksZYgbHwL2ko7tWCZTQU,6916
6
+ chemotools/augmentation/_index_shift.py,sha256=BTtadweDvvMtiF8t7ldwsE6Kl6FmKLCkVJjSzSWyIDs,6904
6
7
  chemotools/augmentation/_spectrum_scale.py,sha256=hMsmzXpssbI7tGm_YnQn9wjbByso3CgVxd3Hs8kfLS8,3442
7
8
  chemotools/baseline/__init__.py,sha256=VzoblGg8Hx_FkTc_n7a-ZjGvtKP8JE_NwJKWenGFQkM,584
8
9
  chemotools/baseline/_air_pls.py,sha256=eotXuIEsus7Z-c17oLx8UbiwOHM7DzQJ6rruHnwCGPQ,5067
@@ -43,7 +44,7 @@ chemotools/smooth/_median_filter.py,sha256=9ndTJCwrZirWlvDNldiigMddy79KIGq9OwwYN
43
44
  chemotools/smooth/_savitzky_golay_filter.py,sha256=27iFUWxdL9_7oZabR0R5L0ZTpBmYfVUjx2XCTukihBE,3509
44
45
  chemotools/smooth/_whittaker_smooth.py,sha256=lpLAyf4GdyDW4ulT1nyEoK6xQEl2cVUKquawQdGWbHU,3571
45
46
  chemotools/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
- chemotools-0.1.8.dist-info/LICENSE,sha256=qtyOy2wDQVX9hxp58h3T-6Lmfv-mSCHoSRkcLUdM9bg,1070
47
- chemotools-0.1.8.dist-info/METADATA,sha256=gK71zOTZyaFxCqjxXGGKfQi4TvN43AXhBIaWdMWVJh4,5239
48
- chemotools-0.1.8.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
49
- chemotools-0.1.8.dist-info/RECORD,,
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,,