@@ -138,6 +138,8 @@ def __init__(
138
138
raise ValueError ("Invalid impute_method given." )
139
139
if self .boundary_method not in valid_boundary_methods :
140
140
raise ValueError ("Invalid boundary_method given." )
141
+ if self .window_length <= 1 :
142
+ raise ValueError ("Window length is too short." )
141
143
142
144
if smoother_name == "savgol" :
143
145
# The polynomial fitting is done on a past window of size window_length
@@ -165,20 +167,36 @@ def smooth(self, signal: Union[np.ndarray, pd.Series]) -> Union[np.ndarray, pd.S
165
167
A smoothed 1D signal. Returns an array of the same type and length as
166
168
the input.
167
169
"""
170
+ # If all nans, pass through
171
+ if np .all (np .isnan (signal )):
172
+ return signal
173
+
168
174
is_pandas_series = isinstance (signal , pd .Series )
169
175
signal = signal .to_numpy () if is_pandas_series else signal
170
176
171
- signal = self .impute (signal )
177
+ # Find where the first non-nan value is located and truncate the initial nans
178
+ ix = np .where (~ np .isnan (signal ))[0 ][0 ]
179
+ signal = signal [ix :]
172
180
173
- if self .smoother_name == "savgol" :
174
- signal_smoothed = self .savgol_smoother (signal )
175
- elif self .smoother_name == "left_gauss_linear" :
176
- signal_smoothed = self .left_gauss_linear_smoother (signal )
177
- elif self .smoother_name == "moving_average" :
178
- signal_smoothed = self .moving_average_smoother (signal )
179
- else :
181
+ # Don't smooth in certain edge cases
182
+ if len (signal ) < self .poly_fit_degree or len (signal ) == 1 :
180
183
signal_smoothed = signal .copy ()
181
-
184
+ else :
185
+ # Impute
186
+ signal = self .impute (signal )
187
+
188
+ # Smooth
189
+ if self .smoother_name == "savgol" :
190
+ signal_smoothed = self .savgol_smoother (signal )
191
+ elif self .smoother_name == "left_gauss_linear" :
192
+ signal_smoothed = self .left_gauss_linear_smoother (signal )
193
+ elif self .smoother_name == "moving_average" :
194
+ signal_smoothed = self .moving_average_smoother (signal )
195
+ elif self .smoother_name == "identity" :
196
+ signal_smoothed = signal
197
+
198
+ # Append the nans back, since we want to preserve length
199
+ signal_smoothed = np .hstack ([np .nan * np .ones (ix ), signal_smoothed ])
182
200
signal_smoothed = signal_smoothed if not is_pandas_series else pd .Series (signal_smoothed )
183
201
return signal_smoothed
184
202
@@ -282,7 +300,7 @@ def left_gauss_linear_smoother(self, signal):
282
300
283
301
def savgol_predict (self , signal , poly_fit_degree , nr ):
284
302
"""Predict a single value using the savgol method.
285
-
303
+
286
304
Fits a polynomial through the values given by the signal and returns the value
287
305
of the polynomial at the right-most signal-value. More precisely, for a signal of length
288
306
n, fits a poly_fit_degree polynomial through the points signal[-n+1+nr], signal[-n+2+nr],
@@ -311,7 +329,8 @@ def savgol_predict(self, signal, poly_fit_degree, nr):
311
329
def savgol_coeffs (self , nl , nr , poly_fit_degree ):
312
330
"""Solve for the Savitzky-Golay coefficients.
313
331
314
- The coefficients c_i give a filter so that
332
+ Solves for the Savitzky-Golay coefficients. The coefficients c_i
333
+ give a filter so that
315
334
y = sum_{i=-{n_l}}^{n_r} c_i x_i
316
335
is the value at 0 (thus the constant term) of the polynomial fit
317
336
through the points {x_i}. The coefficients are c_i are calculated as
@@ -385,7 +404,7 @@ def savgol_smoother(self, signal):
385
404
# - identity keeps the original signal (doesn't smooth)
386
405
# - nan writes nans
387
406
if self .boundary_method == "shortened_window" :
388
- for ix in range (len (self .coeffs )):
407
+ for ix in range (min ( len (self .coeffs ), len ( signal ) )):
389
408
if ix == 0 :
390
409
signal_smoothed [ix ] = signal [ix ]
391
410
else :
0 commit comments