def to_absolute_int(self, start, cutoff=None): """Return absolute values as zero-based integer index starting from `start`. Parameters ---------- start : pd.Period, pd.Timestamp, int Start value returned as zero. cutoff : pd.Period, pd.Timestamp, int, optional (default=None) Cutoff value required to convert a relative forecasting horizon to an absolute one (and vice versa). Returns ------- fh : ForecastingHorizon Absolute representation of forecasting horizon as zero-based integer index. """ # We here check the start value, the cutoff value is checked when we use it # to convert the horizon to the absolute representation below absolute = self.to_absolute(cutoff).to_pandas() _check_start(start, absolute) # Note: We should here also coerce to periods for more reliable arithmetic # operations as in `to_relative` but currently doesn't work with # `update_predict` and incomplete time indices where the `freq` information # is lost, see comment on issue #534 integers = absolute - start if isinstance(absolute, (pd.PeriodIndex, pd.DatetimeIndex)): integers = _coerce_duration_to_int(integers, freq=_get_freq(cutoff)) return self._new(integers, is_relative=False)
def _align_seasonal(self, y): """Align seasonal components with y's time index""" shift = (-_get_duration( y.index[0], self._y_index[0], coerce_to_int=True, unit=_get_freq(self._y_index), ) % self.sp) return np.resize(np.roll(self.seasonal_, shift=shift), y.shape[0])
def _to_relative(fh: ForecastingHorizon, cutoff=None) -> ForecastingHorizon: """Return forecasting horizon values relative to a cutoff. Parameters ---------- fh : ForecastingHorizon cutoff : pd.Period, pd.Timestamp, int, optional (default=None) Cutoff value required to convert a relative forecasting horizon to an absolute one (and vice versa). Returns ------- fh : ForecastingHorizon Relative representation of forecasting horizon. """ if fh.is_relative: return fh._new() else: absolute = fh.to_pandas() _check_cutoff(cutoff, absolute) # We cannot use the freq from the ForecastingHorizon itself (or its # wrapped pd.DatetimeIndex) because it may be none for non-regular # indices, so instead we use the freq of cutoff. freq = _get_freq(cutoff) if isinstance(absolute, pd.DatetimeIndex): # coerce to pd.Period for reliable arithmetics and computations of # time deltas absolute = _coerce_to_period(absolute, freq) cutoff = _coerce_to_period(cutoff, freq) # TODO: Replace when we upgrade our lower pandas bound # to a version where this is fixed # Compute relative values # The following line circumvents the bug in pandas # periods = pd.period_range(start="2021-01-01", periods=3, freq="2H") # periods - periods[0] # Out: Index([<0 * Hours>, <4 * Hours>, <8 * Hours>], dtype = 'object') # [v - periods[0] for v in periods] # Out: Index([<0 * Hours>, <2 * Hours>, <4 * Hours>], dtype='object') # TODO: v0.12.0: Check if this comment below can be removed, # so check if pandas has released the fix to PyPI: # This bug was reported: https://github.com/pandas-dev/pandas/issues/45999 # and fixed: https://github.com/pandas-dev/pandas/pull/46006 # Most likely it will be released with pandas 1.5 # Once the bug is fixed the line should simply be: # relative = absolute - cutoff relative = pd.Index([date - cutoff for date in absolute]) # Coerce durations (time deltas) into integer values for given frequency if isinstance(absolute, (pd.PeriodIndex, pd.DatetimeIndex)): relative = _coerce_duration_to_int(relative, freq=freq) return fh._new(relative, is_relative=True)
def test_get_freq(): """Test whether get_freq runs without error.""" x = pd.Series( index=pd.date_range(start="2017-01-01", periods=700, freq="W"), data=np.random.randn(700), ) x1 = x.index x2 = x.resample("W").sum().index x3 = pd.Series(index=[ datetime.datetime(2017, 1, 1) + datetime.timedelta(days=int(i)) for i in np.arange(1, 100, 7) ]).index x4 = [ datetime.datetime(2017, 1, 1) + datetime.timedelta(days=int(i)) for i in np.arange(1, 100, 7) ] assert _get_freq(x1) == "W" assert _get_freq(x2) == "W" assert _get_freq(x3) is None assert _get_freq(x4) is None
def to_relative(self, cutoff=None): """Return forecasting horizon values relative to a cutoff. Parameters ---------- cutoff : pd.Period, pd.Timestamp, int, optional (default=None) Cutoff value required to convert a relative forecasting horizon to an absolute one (and vice versa). Returns ------- fh : ForecastingHorizon Relative representation of forecasting horizon. """ if self.is_relative: return self._new() else: absolute = self.to_pandas() _check_cutoff(cutoff, absolute) if isinstance(absolute, pd.DatetimeIndex): # We cannot use the freq from the the ForecastingHorizon itself (or its # wrapped pd.DatetimeIndex) because it may be none for non-regular # indices, so instead we use the freq of cutoff. freq = _get_freq(cutoff) # coerce to pd.Period for reliable arithmetics and computations of # time deltas absolute = _coerce_to_period(absolute, freq) cutoff = _coerce_to_period(cutoff, freq) # Compute relative values relative = absolute - cutoff # Coerce durations (time deltas) into integer values for given frequency if isinstance(absolute, (pd.PeriodIndex, pd.DatetimeIndex)): relative = _coerce_duration_to_int(relative, freq=_get_freq(cutoff)) return self._new(relative, is_relative=True)
def test_coerce_duration_to_int(duration): ret = _coerce_duration_to_int(duration, freq=_get_freq(duration)) # check output type is always integer assert type(ret) in (pd.Int64Index, np.integer, int) # check result if isinstance(duration, pd.Index): np.testing.assert_array_equal(ret, range(3)) if isinstance(duration, pd.tseries.offsets.BaseOffset): assert ret == 3
def test_coerce_duration_to_int(duration): """Test coercion of duration to int.""" ret = _coerce_duration_to_int(duration, freq=_get_freq(duration)) # check output type is always integer assert (type(ret) in (np.integer, int)) or is_integer_index(ret) # check result if isinstance(duration, pd.Index): np.testing.assert_array_equal(ret, range(3)) if isinstance(duration, pd.tseries.offsets.BaseOffset): assert ret == 3
def _coerce_to_period(x, freq=None): """Helper function to coerce pd.Timestamp to pd.Period or pd.DatetimeIndex to pd.PeriodIndex for more reliable arithmetic operations with time indices""" if freq is None: freq = _get_freq(x) try: return x.to_period(freq) except (ValueError, AttributeError) as e: msg = str(e) if "Invalid frequency" in msg or "_period_dtype_code" in msg: raise ValueError( "Invalid frequency. Please select a frequency that can " "be converted to a regular `pd.PeriodIndex`. For other " "frequencies, basic arithmetic operation to compute " "durations currently do not work reliably.") else: raise
def test_coerce_duration_to_int_with_non_allowed_durations(duration): """Test coercion of duration to int.""" with pytest.raises(ValueError, match="frequency is missing"): _coerce_duration_to_int(duration, freq=_get_freq(duration))