def test_kalman(self): """KalmanFilter test. Creates an increasing sequence of numbers, adds noise and assumes the kalman filter predicts values closer to real values """ testing_signal = np.arange(1, 5, 0.1) noise = np.random.normal(0, 0.7, testing_signal.shape) testing_signal_with_noise = testing_signal + noise df = pd.DataFrame(data=testing_signal_with_noise, columns=["signal"]) testing_signal_with_noise_ts = TimeSeries.from_dataframe( df, value_cols=["signal"]) kf = KalmanFilter(dim_x=1) kf.fit(testing_signal_with_noise_ts) filtered_ts = kf.filter(testing_signal_with_noise_ts, num_samples=1) filtered_values = filtered_ts.univariate_values() noise_distance = testing_signal_with_noise - testing_signal prediction_distance = filtered_values - testing_signal self.assertGreater(noise_distance.std(), prediction_distance.std()) self.assertEqual(filtered_ts.width, 1) self.assertEqual(filtered_ts.n_samples, 1)
def __init__(self, dim_x: int = 1, kf: Optional[Kalman] = None): """Kalman filter Forecaster This model uses a Kalman filter to produce forecasts. It uses a :class:`darts.models.filtering.kalman_filter.KalmanFilter` object and treats future values as missing values. The model can optionally receive a :class:`nfoursid.kalman.Kalman` object specifying the Kalman filter, or, if not specified, the filter will be trained using the N4SID system identification algorithm. Parameters ---------- dim_x : int Size of the Kalman filter state vector. kf : nfoursid.kalman.Kalman Optionally, an instance of `nfoursid.kalman.Kalman`. If this is provided, the parameter dim_x is ignored. This instance will be copied for every call to `predict()`, so the state is not carried over from one time series to another across several calls to `predict()`. The various dimensionalities of the filter must match those of the `TimeSeries` used when calling `predict()`. If this is specified, it is still necessary to call `fit()` before calling `predict()`, although this will have no effect on the Kalman filter. """ super().__init__() self.dim_x = dim_x self.kf = kf self.darts_kf = KalmanFilter(dim_x, kf)
def test_kalman_samples(self): kf = KalmanFilter(dim_x=1) series = tg.sine_timeseries(length=30, value_frequency=0.1) kf.fit(series) prediction = kf.filter(series, num_samples=10) self.assertEqual(prediction.width, 1) self.assertEqual(prediction.n_samples, 10)
def test_kalman_covariates(self): kf = KalmanFilter(dim_x=2) series = tg.sine_timeseries(length=30, value_frequency=0.1) covariates = -series.copy() kf.fit(series, covariates=covariates) prediction = kf.filter(series, covariates=covariates) self.assertEqual(prediction.width, 1) self.assertEqual(prediction.n_samples, 1)
def test_kalman_multivariate(self): kf = KalmanFilter(dim_x=3) sine_ts = tg.sine_timeseries(length=30, value_frequency=0.1) noise_ts = tg.gaussian_timeseries(length=30) * 0.1 series = sine_ts.stack(noise_ts) kf.fit(series) prediction = kf.filter(series) self.assertEqual(prediction.width, 2) self.assertEqual(prediction.n_samples, 1)
def test_kalman_given_kf(self): nfoursid_ss = state_space.StateSpace(a=np.eye(2), b=np.ones((2, 1)), c=np.ones((1, 2)), d=np.ones((1, 1))) nfoursid_kf = kalman.Kalman(nfoursid_ss, np.ones((3, 3)) * 0.1) kf = KalmanFilter(dim_x=1, kf=nfoursid_kf) series = tg.sine_timeseries(length=30, value_frequency=0.1) prediction = kf.filter(series, covariates=-series.copy()) self.assertEqual(kf.dim_u, 1) self.assertEqual(kf.dim_x, 2) self.assertEqual(prediction.width, 1) self.assertEqual(prediction.n_samples, 1)
def test_kalman_missing_values(self): sine = tg.sine_timeseries( length=100, value_frequency=0.05) + 0.1 * tg.gaussian_timeseries(length=100) values = sine.values() values[20:22] = np.nan values[28:40] = np.nan sine_holes = TimeSeries.from_values(values) sine = TimeSeries.from_values(sine.values()) kf = KalmanFilter(dim_x=2) kf.fit(sine_holes[-50:]) # fit on the part with no holes # reconstructruction should succeed filtered_series = kf.filter(sine_holes, num_samples=100) # reconstruction error should be sufficiently small self.assertLess(rmse(filtered_series, sine), 0.1)
class KalmanForecaster(DualCovariatesForecastingModel): def __init__(self, dim_x: int = 1, kf: Optional[Kalman] = None): """Kalman filter Forecaster This model uses a Kalman filter to produce forecasts. It uses a :class:`darts.models.filtering.kalman_filter.KalmanFilter` object and treats future values as missing values. The model can optionally receive a :class:`nfoursid.kalman.Kalman` object specifying the Kalman filter, or, if not specified, the filter will be trained using the N4SID system identification algorithm. Parameters ---------- dim_x : int Size of the Kalman filter state vector. kf : nfoursid.kalman.Kalman Optionally, an instance of `nfoursid.kalman.Kalman`. If this is provided, the parameter dim_x is ignored. This instance will be copied for every call to `predict()`, so the state is not carried over from one time series to another across several calls to `predict()`. The various dimensionalities of the filter must match those of the `TimeSeries` used when calling `predict()`. If this is specified, it is still necessary to call `fit()` before calling `predict()`, although this will have no effect on the Kalman filter. """ super().__init__() self.dim_x = dim_x self.kf = kf self.darts_kf = KalmanFilter(dim_x, kf) def __str__(self): return f"Kalman Filter Forecaster (dim_x={self.dim_x})" def _fit(self, series: TimeSeries, future_covariates: Optional[TimeSeries] = None): super()._fit(series, future_covariates) if self.kf is None: self.darts_kf.fit(series=series, covariates=future_covariates) return self def _predict( self, n: int, future_covariates: Optional[TimeSeries] = None, num_samples: int = 1, ) -> TimeSeries: super()._predict(n, future_covariates, num_samples) time_index = self._generate_new_dates(n) placeholder_vals = np.zeros((n, self.training_series.width)) * np.nan series_future = TimeSeries.from_times_and_values( time_index, placeholder_vals, columns=self.training_series.columns, static_covariates=self.training_series.static_covariates, hierarchy=self.training_series.hierarchy, ) whole_series = self.training_series.append(series_future) filtered_series = self.darts_kf.filter( whole_series, covariates=future_covariates, num_samples=num_samples ) return filtered_series[-n:] def _is_probabilistic(self) -> bool: return True