Implement Series-to-Series Transformer to difference a time series
See original GitHub issueIs your feature request related to a problem? Please describe. Sktime currently does not include a stand alone transformer to apply a set of differences to a time series. Differencing is common in time series forecasting (to among other things ensure a time series is stationary before using a model that assumes stationarity).
This feature would be useful in forecasting pipelines (e.g. TransformedTargetForecaster()
) and also useful in the work on the Theta
and/or AutoTheta
forecasters (eg. #922).
Describe the solution you’d like
A Differencer()
should be a series-to-series transformer that takes in an input timeseries and transforms it to apply a set of differences.
An important feature is that the Differencer()
should be able to apply multiple differences at different lags sequentially (as a list, tuple or array in the constructor). This allows users to easily use the Differencer()
for use cases like iteratively applying a first-difference and seasonal-difference to a series.
Some implementations such as in pmdarima only allow users to specify higher-order differences, but restrict the iterative differences to use the same lag.
The Differencer()
should also implement an inverse_transform()
method to reverse the transformations.
Describe alternatives you’ve considered
One difficulty in implementing the Differencer()
is the slightly different approaches to reversing the transformation on “in-sample” data and predictions.
@mloning and I have discussed two potential solutions (as listed below). Interested to hear everyone’s thoughts on these options and if anyone has other ideas on the different inverse_transform application with predictions versus training data.
One option that is being considered is to have a constructor parameter that indicates how the Differencer
will be used.
class Differencer(_SeriesToSeriesTransformer):
def __init__(self, lags = 1, use_with_predict=False):
self.lags = lags
self.use_with_predict=use_with_predict
...
def transform(...):
# Code to do transformation and store info to do inverse transform
...
def inverse_transform(...):
# Uses self.use_with_predict to diffentiate inverse_transform case
if self.use_with_predict:
...
else:
...
The other option is to create a Differencer
that applies the transformation to a forecaster.
class Differencer(BaseForecaster, _SeriesToSeriesTransformer):
def __init__(self, forecaster = None, lags = 1):
self.forecaster = forecaster
self.lags = lags
...
def fit(...):
y_transform = self.transform(...)
self.forecaster.fit(y_transform, ...)
return self
def predict(...):
y_pred = self.forecaster.predict(...)
# code for inverse difference on predicted data to calculate y_pred_inv
...
return y_pred_inv
def transform(...):
...
def inverse_transform(...):
# Used when inverse transforming data outside of the prediction use case
...
Option 2 could be used as a stand-alone or with other sktime functionality:
forecaster = Differencer(forecaster=ETS())
TransformedTargetForecaster(
BoxCox(), ..., Differencer(forecaster=ETS())
)
Issue Analytics
- State:
- Created 2 years ago
- Reactions:1
- Comments:7
@aiwalter, @fkiraly and @mloning, I appreciate the feedback. I’ve got a PR out there for this now so you can look at the actual implementation (see #945).
@aiwalter I think the original idea with option number 2 was to make it easier to deal with the inverse transformation when predicting, I’ve actually implemented something along the lines of option number 1.
@fkiraly I agree on it just being a differencer, I went that route and we can figure out how to deal with the difference in the inverse transformation in prediction vs. on the input data. Your right-on in terms of the iterative cumulative summing.
@mloning and I had talked about in-sample vs. out-of-sample being made on the index of the time series passed to the inverse transform. I’d like to find a way to go that route, but I think there might be issues with in-sample predictions (negative forecast horizons).
I’ll post a note about that in #945 and the inverse transformation in general.
This was clsoed when #945 was merged.