def inverse_transform(self, series: Union[TimeSeries, Sequence[TimeSeries]], *args, **kwargs) -> Union[TimeSeries, List[TimeSeries]]: """Inverse-transform a (sequence of) series. In case a sequence is passed as input data, this function takes care of parallelising the transformation of multiple series in the sequence at the same time. Parameters ---------- series the (sequence of) series be inverse-transformed. args Additional positional arguments for the :func:`ts_inverse_transform()` method kwargs Additional keyword arguments for the :func:`ts_inverse_transform()` method component_mask : Optional[np.ndarray] = None Optionally, a 1-D boolean np.ndarray of length ``series.n_components`` that specifies which components of the underlying `series` the Scaler should consider. Returns ------- Union[TimeSeries, List[TimeSeries]] Inverse transformed data. """ if hasattr(self, "_fit_called"): raise_if_not( self._fit_called, "fit() must have been called before inverse_transform()", logger, ) desc = f"Inverse ({self._name})" if isinstance(series, TimeSeries): data = [series] else: data = series input_iterator = _build_tqdm_iterator( self._inverse_transform_iterator(data), verbose=self._verbose, desc=desc, total=len(data), ) transformed_data = _parallel_apply( input_iterator, self.__class__.ts_inverse_transform, self._n_jobs, args, kwargs, ) return (transformed_data[0] if isinstance(series, TimeSeries) else transformed_data)
def wrapper_multi_ts_support(*args, **kwargs): actual_series = kwargs[ 'actual_series'] if 'actual_series' in kwargs else args[0] pred_series = kwargs['pred_series'] if 'pred_series' in kwargs else args[0] if 'actual_series' in kwargs \ else args[1] n_jobs = kwargs.pop('n_jobs', signature(func).parameters['n_jobs'].default) verbose = kwargs.pop('verbose', signature(func).parameters['verbose'].default) raise_if_not(isinstance(n_jobs, int), "n_jobs must be an integer") raise_if_not(isinstance(verbose, bool), "verbose must be a bool") actual_series = [ actual_series ] if not isinstance(actual_series, Sequence) else actual_series pred_series = [ pred_series ] if not isinstance(pred_series, Sequence) else pred_series raise_if_not( len(actual_series) == len(pred_series), "The two TimeSeries sequences must have the same length.", logger) num_series_in_args = int('actual_series' not in kwargs) + int( 'pred_series' not in kwargs) kwargs.pop('actual_series', 0) kwargs.pop('pred_series', 0) iterator = _build_tqdm_iterator(iterable=zip(actual_series, pred_series), verbose=verbose, total=len(actual_series)) value_list = _parallel_apply(iterator=iterator, fn=func, n_jobs=n_jobs, fn_args=args[num_series_in_args:], fn_kwargs=kwargs) # in case the reduction is not reducing the metrics sequence to a single value, e.g., if returning the # np.ndarray of values with the identity function, we must handle the single TS case, where we should # return a single value instead of a np.array of len 1 if len(value_list) == 1: value_list = value_list[0] if 'inter_reduction' in kwargs: return kwargs['inter_reduction'](value_list) else: return signature(func).parameters['inter_reduction'].default( value_list)
def fit(self, series: Union[TimeSeries, Sequence[TimeSeries]], *args, **kwargs) -> "FittableDataTransformer": """Fit the transformer to the provided series or sequence of series. Fit the data and store the fitting parameters into ``self._fitted_params``. If a sequence is passed as input data, this function takes care of parallelising the fitting of multiple series in the sequence at the same time (in this case ``self._fitted_params`` will contain an array of fitted params, one for each series). Parameters ---------- series (sequence of) series to fit the transformer on. args Additional positional arguments for the :func:`ts_fit` method kwargs Additional keyword arguments for the :func:`ts_fit` method component_mask : Optional[np.ndarray] = None Optionally, a 1-D boolean np.ndarray of length ``series.n_components`` that specifies which components of the underlying `series` the Scaler should consider. Returns ------- FittableDataTransformer Fitted transformer. """ self._fit_called = True desc = f"Fitting ({self._name})" if isinstance(series, TimeSeries): data = [series] else: data = series input_iterator = _build_tqdm_iterator(self._fit_iterator(data), verbose=self._verbose, desc=desc, total=len(data)) self._fitted_params = _parallel_apply(input_iterator, self.__class__.ts_fit, self._n_jobs, args, kwargs) return self
def transform(self, series: Union[TimeSeries, Sequence[TimeSeries]], *args, **kwargs) -> Union[TimeSeries, List[TimeSeries]]: """Transform a (sequence of) of series. In case a ``Sequence`` is passed as input data, this function takes care of parallelising the transformation of multiple series in the sequence at the same time. Parameters ---------- series (sequence of) series to be transformed. args Additional positional arguments for each :func:`ts_transform()` method call kwargs Additional keyword arguments for each :func:`ts_transform()` method call Returns ------- Union[TimeSeries, List[TimeSeries]] Transformed data. """ desc = f"Transform ({self._name})" if isinstance(series, TimeSeries): data = [series] else: data = series input_iterator = _build_tqdm_iterator( self._transform_iterator(data), verbose=self._verbose, desc=desc, total=len(data), ) transformed_data = _parallel_apply(input_iterator, self.__class__.ts_transform, self._n_jobs, args, kwargs) return (transformed_data[0] if isinstance(series, TimeSeries) else transformed_data)
def inverse_transform(self, series: Union[TimeSeries, Sequence[TimeSeries]], *args, **kwargs) -> Union[TimeSeries, List[TimeSeries]]: """ Inverse-transform the data. In case a `Sequence` is passed as input data, this function takes care of parallelising the transformation of multiple series in the sequence at the same time. Parameters ---------- series `TimeSeries` or `Sequence[TimeSeries]` which will be inverse-transformed. args Additional positional arguments for the `ts_inverse_transform()` method kwargs Additional keyword arguments for the `ts_inverse_transform()` method Returns ------- Union[TimeSeries, List[TimeSeries]] Inverse transformed data. """ if hasattr(self, "_fit_called"): raise_if_not(self._fit_called, "fit() must have been called before inverse_transform()", logger) desc = "Inverse ({})".format(self._name) if isinstance(series, TimeSeries): data = [series] else: data = series input_iterator = _build_tqdm_iterator(self._inverse_transform_iterator(data), verbose=self._verbose, desc=desc, total=len(data)) transformed_data = _parallel_apply(input_iterator, self.__class__.ts_inverse_transform, self._n_jobs, args, kwargs) return transformed_data[0] if isinstance(series, TimeSeries) else transformed_data
def fit(self, series: Union[TimeSeries, Sequence[TimeSeries]], *args, **kwargs) -> 'FittableDataTransformer': """ Fit the data and stores the fitting parameters into `self._fitted_params`. If a `Sequence` is passed as input data, this function takes care of parallelising the fitting of multiple series in the sequence at the same time (in this case 'self._fitted_params' will contain an array of fitted params, one for each `TimeSeries`). Parameters ---------- series `TimeSeries` or `Sequence[TimeSeries]` against which the transformer is fit. args Additional positional arguments for the `ts_fit()` method kwargs Additional keyword arguments for the `ts_fit()` method Returns ------- FittableDataTransformer Fitted transformer. """ self._fit_called = True desc = "Fitting ({})".format(self._name) if isinstance(series, TimeSeries): data = [series] else: data = series input_iterator = _build_tqdm_iterator(self._fit_iterator(data), verbose=self._verbose, desc=desc, total=len(data)) self._fitted_params = _parallel_apply(input_iterator, self.__class__.ts_fit, self._n_jobs, args, kwargs) return self
def mase(actual_series: Union[TimeSeries, Sequence[TimeSeries]], pred_series: Union[TimeSeries, Sequence[TimeSeries]], insample: Union[TimeSeries, Sequence[TimeSeries]], m: Optional[int] = 1, intersect: bool = True, *, reduction: Callable[[np.ndarray], float] = np.mean, inter_reduction: Callable[[np.ndarray], Union[float, np.ndarray]] = lambda x: x, n_jobs: int = 1, verbose: bool = False) -> Union[float, np.ndarray]: """ Mean Absolute Scaled Error (MASE). See `Mean absolute scaled error wikipedia page <https://en.wikipedia.org/wiki/Mean_absolute_scaled_error>`_ for details about the MASE and how it is computed. If any of the series is stochastic (containing several samples), the median sample value is considered. Parameters ---------- actual_series The `TimeSeries` or `Sequence[TimeSeries]` of actual values. pred_series The `TimeSeries` or `Sequence[TimeSeries]` of predicted values. insample The training series used to forecast `pred_series` . This series serves to compute the scale of the error obtained by a naive forecaster on the training data. m Optionally, the seasonality to use for differencing. `m=1` corresponds to the non-seasonal MASE, whereas `m>1` corresponds to seasonal MASE. If `m=None`, it will be tentatively inferred from the auto-correlation function (ACF). It will fall back to a value of 1 if this fails. intersect For time series that are overlapping in time without having the same time index, setting `intersect=True` will consider the values only over their common time interval (intersection in time). reduction Function taking as input a `np.ndarray` and returning a scalar value. This function is used to aggregate the metrics of different components in case of multivariate `TimeSeries` instances. inter_reduction Function taking as input a `np.ndarray` and returning either a scalar value or a `np.ndarray`. This function can be used to aggregate the metrics of different series in case the metric is evaluated on a `Sequence[TimeSeries]`. Defaults to the identity function, which returns the pairwise metrics for each pair of `TimeSeries` received in input. Example: `inter_reduction=np.mean`, will return the average of the pairwise metrics. n_jobs The number of jobs to run in parallel. Parallel jobs are created only when a `Sequence[TimeSeries]` is passed as input, parallelising operations regarding different `TimeSeries`. Defaults to `1` (sequential). Setting the parameter to `-1` means using all the available processors. verbose Optionally, whether to print operations progress Raises ------ ValueError If the `insample` series is periodic ( :math:`X_t = X_{t-m}` ) Returns ------- float The Mean Absolute Scaled Error (MASE) """ def _multivariate_mase(actual_series: TimeSeries, pred_series: TimeSeries, insample: TimeSeries, m: int, intersect: bool, reduction: Callable[[np.ndarray], float]): raise_if_not(actual_series.width == pred_series.width, "The two TimeSeries instances must have the same width.", logger) raise_if_not(actual_series.width == insample.width, "The insample TimeSeries must have the same width as the other series.", logger) raise_if_not(insample.end_time() + insample.freq == pred_series.start_time(), "The pred_series must be the forecast of the insample series", logger) insample_ = insample.quantile_timeseries(quantile=0.5) if insample.is_stochastic else insample value_list = [] for i in range(actual_series.width): # old implementation of mase on univariate TimeSeries if m is None: test_season, m = check_seasonality(insample) if not test_season: warn("No seasonality found when computing MASE. Fixing the period to 1.", UserWarning) m = 1 y_true, y_hat = _get_values_or_raise(actual_series.univariate_component(i), pred_series.univariate_component(i), intersect) x_t = insample_.univariate_component(i).values() errors = np.abs(y_true - y_hat) scale = np.mean(np.abs(x_t[m:] - x_t[:-m])) raise_if_not(not np.isclose(scale, 0), "cannot use MASE with periodical signals", logger) value_list.append(np.mean(errors / scale)) return reduction(value_list) if isinstance(actual_series, TimeSeries): raise_if_not(isinstance(pred_series, TimeSeries), "Expecting pred_series to be TimeSeries") raise_if_not(isinstance(insample, TimeSeries), "Expecting insample to be TimeSeries") return _multivariate_mase(actual_series=actual_series, pred_series=pred_series, insample=insample, m=m, intersect=intersect, reduction=reduction) elif isinstance(actual_series, Sequence) and isinstance(actual_series[0], TimeSeries): raise_if_not(isinstance(pred_series, Sequence) and isinstance(pred_series[0], TimeSeries), "Expecting pred_series to be a Sequence[TimeSeries]") raise_if_not(isinstance(insample, Sequence) and isinstance(insample[0], TimeSeries), "Expecting insample to be a Sequence[TimeSeries]") raise_if_not(len(pred_series) == len(actual_series) and len(pred_series) == len(insample), "The TimeSeries sequences must have the same length.", logger) raise_if_not(isinstance(n_jobs, int), "n_jobs must be an integer") raise_if_not(isinstance(verbose, bool), "verbose must be a bool") iterator = _build_tqdm_iterator(iterable=zip(actual_series, pred_series, insample), verbose=verbose, total=len(actual_series)) value_list = _parallel_apply(iterator=iterator, fn=_multivariate_mase, n_jobs=n_jobs, fn_args=dict(), fn_kwargs={ "m": m, "intersect": intersect, "reduction": reduction }) return inter_reduction(value_list) else: raise_log(ValueError("Input type not supported, only TimeSeries and Sequence[TimeSeries] are accepted."))