예제 #1
0
    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)
예제 #2
0
파일: metrics.py 프로젝트: camilaagw/darts
    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)
예제 #3
0
    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
예제 #4
0
    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)
예제 #5
0
    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
예제 #6
0
    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
예제 #7
0
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."))