@@ -57,7 +57,7 @@ def __init__(
57
57
impute = True ,
58
58
minval = None ,
59
59
poly_fit_degree = 3 ,
60
- boundary_method = "identity " ,
60
+ boundary_method = "shortened_window " ,
61
61
savgol_weighted = False ,
62
62
):
63
63
self .method_name = method_name
@@ -135,16 +135,15 @@ def moving_average_smoother(
135
135
return signal_smoothed
136
136
137
137
def left_gauss_linear_smoother (
138
- self , signal : np .ndarray , gaussian_bandwidth = 10 , impute = False , minval = None ,
138
+ self , signal : np .ndarray , gaussian_bandwidth = 100 , impute = False , minval = None ,
139
139
) -> np .ndarray :
140
140
"""
141
141
Smooth the y-values using a local linear regression with Gaussian weights.
142
142
143
143
At each time t, we use the data from times 1, ..., t-dt, weighted
144
144
using the Gaussian kernel, to produce the estimate at time t.
145
145
146
- For math details, see the smoothing_docs folder. TL;DR: the A matrix in the
147
- code below is the design matrix of the regression.
146
+ For math details, see the smoothing_docs folder.
148
147
149
148
Parameters
150
149
----------
@@ -161,7 +160,7 @@ def left_gauss_linear_smoother(
161
160
"""
162
161
n = len (signal )
163
162
signal_smoothed = np .zeros_like (signal )
164
- A = np .vstack ([np .ones (n ), np .arange (n )]).T
163
+ A = np .vstack ([np .ones (n ), np .arange (n )]).T # the regression design matrix
165
164
for idx in range (n ):
166
165
weights = np .exp (- ((np .arange (idx + 1 ) - idx ) ** 2 ) / gaussian_bandwidth )
167
166
AwA = np .dot (A [: (idx + 1 ), :].T * weights , A [: (idx + 1 ), :])
@@ -178,7 +177,7 @@ def left_gauss_linear_smoother(
178
177
return signal_smoothed
179
178
180
179
def causal_savgol_coeffs (
181
- self , nl , nr , poly_fit_degree , weighted = False , gaussian_bandwidth = 10
180
+ self , nl , nr , poly_fit_degree , weighted = False , gaussian_bandwidth = 100
182
181
):
183
182
"""
184
183
Solves for the Savitzky-Golay coefficients. The coefficients c_i
@@ -229,7 +228,7 @@ def causal_savgol_coeffs(
229
228
coeffs [i ] = (mat_inverse @ basis_vector )[0 ]
230
229
return coeffs
231
230
232
- def pad_and_convolve (self , signal , coeffs , boundary_method = "identity " ):
231
+ def pad_and_convolve (self , signal , coeffs , boundary_method = "shortened_window " ):
233
232
"""
234
233
Returns a specific type of convolution of the 1D signal with the 1D signal
235
234
coeffs, respecting boundary effects. That is, the output y is
@@ -258,19 +257,38 @@ def pad_and_convolve(self, signal, coeffs, boundary_method="identity"):
258
257
259
258
# reverse because np.convolve reverses the second argument
260
259
temp_reversed_coeffs = np .array (list (reversed (coeffs )))
260
+
261
+ signal_padded = np .append (np .nan * np .ones (len (coeffs ) - 1 ), signal )
262
+ signal_smoothed = np .convolve (signal_padded , temp_reversed_coeffs , mode = "valid" )
263
+
264
+ # this section handles the smoothing behavior at the (left) boundary:
265
+ # - identity keeps the original signal (doesn't smooth)
266
+ # - shortened_window applies savgol with a smaller window to do the fit
267
+ # - nan writes nans
261
268
if boundary_method == "identity" :
262
- signal_padded = np .append (np .nan * np .ones (len (coeffs ) - 1 ), signal )
263
- signal_smoothed = np .convolve (
264
- signal_padded , temp_reversed_coeffs , mode = "valid"
265
- )
266
269
for ix in range (len (coeffs )):
267
270
signal_smoothed [ix ] = signal [ix ]
268
271
return signal_smoothed
272
+ elif boundary_method == "shortened_window" :
273
+ for ix in range (len (coeffs )):
274
+ if ix == 0 :
275
+ signal_smoothed [ix ] = signal [ix ]
276
+ else :
277
+ try :
278
+ ix_coeffs = self .causal_savgol_coeffs (
279
+ - ix ,
280
+ 0 ,
281
+ self .poly_fit_degree ,
282
+ weighted = self .savgol_weighted ,
283
+ gaussian_bandwidth = self .gaussian_bandwidth ,
284
+ )
285
+ signal_smoothed [ix ] = ix_coeffs @ signal [: (ix + 1 )]
286
+ except (
287
+ np .linalg .LinAlgError , # for small ix, the design matrix is singular
288
+ ):
289
+ signal_smoothed [ix ] = signal [ix ]
290
+ return signal_smoothed
269
291
elif boundary_method == "nan" :
270
- signal_padded = np .append (np .nan * np .ones (len (coeffs ) - 1 ), signal )
271
- signal_smoothed = np .convolve (
272
- signal_padded , temp_reversed_coeffs , mode = "valid"
273
- )
274
292
return signal_smoothed
275
293
else :
276
294
raise ValueError ("Unknown boundary method." )
0 commit comments