def searchsorted(self, value, side='left', sorter=None): if isinstance(value, Period): if value.freq != self.freq: msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, value.freqstr) raise IncompatibleFrequency(msg) value = value.ordinal elif isinstance(value, compat.string_types): try: value = Period(value, freq=self.freq).ordinal except DateParseError: raise KeyError("Cannot interpret '{}' as period".format(value)) return self._ndarray_values.searchsorted(value, side=side, sorter=sorter)
def _sub_period(self, other): # If the operation is well-defined, we return an object-Index # of DateOffsets. Null entries are filled with pd.NaT if self.freq != other.freq: msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr) raise IncompatibleFrequency(msg) asi8 = self.asi8 new_data = asi8 - other.ordinal new_data = np.array([self.freq * x for x in new_data]) if self.hasnans: new_data[self._isnan] = NaT return new_data
def _add_delta_td(self, other): assert isinstance(other, (timedelta, np.timedelta64, Tick)) nanos = delta_to_nanoseconds(other) own_offset = frequencies.to_offset(self.freq.rule_code) if isinstance(own_offset, Tick): offset_nanos = delta_to_nanoseconds(own_offset) if np.all(nanos % offset_nanos == 0): return self.shift(nanos // offset_nanos) # raise when input doesn't have freq raise IncompatibleFrequency("Input has different freq from " "{cls}(freq={freqstr})" .format(cls=type(self).__name__, freqstr=self.freqstr))
def get_indexer_non_unique(self, target): target = ensure_index(target) if isinstance(target, PeriodIndex): target = target.asi8 if hasattr(target, "freq") and target.freq != self.freq: msg = DIFFERENT_FREQ.format( cls=type(self).__name__, own_freq=self.freqstr, other_freq=target.freqstr, ) raise IncompatibleFrequency(msg) indexer, missing = self._int64index.get_indexer_non_unique(target) return ensure_platform_int(indexer), missing
def get_indexer(self, target, method=None, limit=None, tolerance=None): target = ensure_index(target) if hasattr(target, 'freq') and target.freq != self.freq: msg = DIFFERENT_FREQ.format(cls=type(self).__name__, own_freq=self.freqstr, other_freq=target.freqstr) raise IncompatibleFrequency(msg) if isinstance(target, PeriodIndex): target = target.asi8 if tolerance is not None: tolerance = self._convert_tolerance(tolerance, target) return Index.get_indexer(self._int64index, target, method, limit, tolerance)
def searchsorted(self, value, side="left", sorter=None): if isinstance(value, Period): if value.freq != self.freq: msg = DIFFERENT_FREQ.format( cls=type(self).__name__, own_freq=self.freqstr, other_freq=value.freqstr, ) raise IncompatibleFrequency(msg) value = value.ordinal elif isinstance(value, str): try: value = Period(value, freq=self.freq).ordinal except DateParseError: raise KeyError("Cannot interpret '{}' as period".format(value)) return self._ndarray_values.searchsorted(value, side=side, sorter=sorter)
def _require_matching_freq(self, other, base: bool = False) -> None: # See also arrays.period.raise_on_incompatible if isinstance(other, BaseOffset): other_freq = other else: other_freq = other.freq if base: condition = self.freq.base != other_freq.base else: condition = self.freq != other_freq if condition: msg = DIFFERENT_FREQ.format( cls=type(self).__name__, own_freq=self.freqstr, other_freq=other_freq.freqstr, ) raise IncompatibleFrequency(msg)
def _add_timedelta_arraylike( self, other: TimedeltaArray | npt.NDArray[np.timedelta64] ) -> PeriodArray: """ Parameters ---------- other : TimedeltaArray or ndarray[timedelta64] Returns ------- PeriodArray """ freq = self.freq if not isinstance(freq, Tick): # We cannot add timedelta-like to non-tick PeriodArray raise TypeError( f"Cannot add or subtract timedelta64[ns] dtype from {self.dtype}" ) dtype = np.dtype(f"m8[{freq._td64_unit}]") try: delta = astype_overflowsafe(np.asarray(other), dtype=dtype, copy=False, round_ok=False) except ValueError as err: # e.g. if we have minutes freq and try to add 30s # "Cannot losslessly convert units" raise IncompatibleFrequency( "Cannot add/subtract timedelta-like from PeriodArray that is " "not an integer multiple of the PeriodArray's freq.") from err b_mask = np.isnat(delta) res_values = algos.checked_add_with_arr(self.asi8, delta.view("i8"), arr_mask=self._isnan, b_mask=b_mask) np.putmask(res_values, self._isnan | b_mask, iNaT) return type(self)(res_values, freq=self.freq)
def take(self, indices, allow_fill=False, fill_value=None): if allow_fill: if isna(fill_value): fill_value = iNaT elif isinstance(fill_value, Period): if self.freq != fill_value.freq: msg = DIFFERENT_FREQ_INDEX.format(self.freq.freqstr, fill_value.freqstr) raise IncompatibleFrequency(msg) fill_value = fill_value.ordinal else: msg = "'fill_value' should be a Period. Got '{}'." raise ValueError(msg.format(fill_value)) new_values = algos.take(self._data, indices, allow_fill=allow_fill, fill_value=fill_value) return type(self)(new_values, self.freq)
def _sub_period_array(self, other): """ Subtract a Period Array/Index from self. This is only valid if self is itself a Period Array/Index, raises otherwise. Both objects must have the same frequency. Parameters ---------- other : PeriodIndex or PeriodArray Returns ------- result : np.ndarray[object] Array of DateOffset objects; nulls represented by NaT """ if not is_period_dtype(self): raise TypeError("cannot subtract {dtype}-dtype from {cls}".format( dtype=other.dtype, cls=type(self).__name__)) if len(self) != len(other): raise ValueError("cannot subtract arrays/indices of " "unequal length") if self.freq != other.freq: msg = DIFFERENT_FREQ.format(cls=type(self).__name__, own_freq=self.freqstr, other_freq=other.freqstr) raise IncompatibleFrequency(msg) new_values = checked_add_with_arr(self.asi8, -other.asi8, arr_mask=self._isnan, b_mask=other._isnan) new_values = np.array([self.freq.base * x for x in new_values]) if self._hasnans or other._hasnans: mask = (self._isnan) | (other._isnan) new_values[mask] = NaT return new_values
def validate_dtype_freq( dtype, freq: BaseOffsetT | timedelta | str | None) -> BaseOffsetT: """ If both a dtype and a freq are available, ensure they match. If only dtype is available, extract the implied freq. Parameters ---------- dtype : dtype freq : DateOffset or None Returns ------- freq : DateOffset Raises ------ ValueError : non-period dtype IncompatibleFrequency : mismatch between dtype and freq """ if freq is not None: # error: Incompatible types in assignment (expression has type # "BaseOffset", variable has type "Union[BaseOffsetT, timedelta, # str, None]") freq = to_offset(freq) # type: ignore[assignment] if dtype is not None: dtype = pandas_dtype(dtype) if not is_period_dtype(dtype): raise ValueError("dtype must be PeriodDtype") if freq is None: freq = dtype.freq elif freq != dtype.freq: raise IncompatibleFrequency( "specified freq and dtype are different") # error: Incompatible return value type (got "Union[BaseOffset, Any, None]", # expected "BaseOffset") return freq # type: ignore[return-value]
def _add_delta(self, other): """ Add a timedelta-like, Tick, or TimedeltaIndex-like object to self, yielding a new PeriodArray Parameters ---------- other : {timedelta, np.timedelta64, Tick, TimedeltaIndex, ndarray[timedelta64]} Returns ------- result : PeriodArray """ if not isinstance(self.freq, Tick): # We cannot add timedelta-like to non-tick PeriodArray raise IncompatibleFrequency("Input has different freq from " "{cls}(freq={freqstr})" .format(cls=type(self).__name__, freqstr=self.freqstr)) new_ordinals = super(PeriodArray, self)._add_delta(other) return type(self)(new_ordinals, freq=self.freq)
def __init__(self, values, freq=None, copy=False): if freq is not None: freq = Period._maybe_convert_freq(freq) if isinstance(values, ABCSeries): values = values._values if not isinstance(values, type(self)): raise TypeError("Incorrect dtype") elif isinstance(values, ABCPeriodIndex): values = values._values if isinstance(values, type(self)): if freq is not None and freq != values.freq: msg = DIFFERENT_FREQ_INDEX.format(values.freq.freqstr, freq.freqstr) raise IncompatibleFrequency(msg) values, freq = values._data, values.freq values = np.array(values, dtype='int64', copy=copy) self._data = values if freq is None: raise ValueError('freq is not specified and cannot be inferred') self._dtype = PeriodDtype(freq)
def __new__(cls, data=None, ordinal=None, freq=None, start=None, end=None, periods=None, copy=False, name=None, tz=None, dtype=None, **kwargs): if periods is not None: if is_float(periods): periods = int(periods) elif not is_integer(periods): msg = 'periods must be a number, got {periods}' raise TypeError(msg.format(periods=periods)) if name is None and hasattr(data, 'name'): name = data.name if dtype is not None: dtype = pandas_dtype(dtype) if not is_period_dtype(dtype): raise ValueError('dtype must be PeriodDtype') if freq is None: freq = dtype.freq elif freq != dtype.freq: msg = 'specified freq and dtype are different' raise IncompatibleFrequency(msg) # coerce freq to freq object, otherwise it can be coerced elementwise # which is slow if freq: freq = Period._maybe_convert_freq(freq) if data is None: if ordinal is not None: data = np.asarray(ordinal, dtype=np.int64) else: data, freq = cls._generate_range(start, end, periods, freq, kwargs) return cls._from_ordinals(data, name=name, freq=freq) if isinstance(data, PeriodIndex): if freq is None or freq == data.freq: # no freq change freq = data.freq data = data._values else: base1, _ = _gfc(data.freq) base2, _ = _gfc(freq) data = period.period_asfreq_arr(data._values, base1, base2, 1) return cls._simple_new(data, name=name, freq=freq) # not array / index if not isinstance( data, (np.ndarray, PeriodIndex, DatetimeIndex, Int64Index)): if is_scalar(data) or isinstance(data, Period): cls._scalar_data_error(data) # other iterable of some kind if not isinstance(data, (list, tuple)): data = list(data) data = np.asarray(data) # datetime other than period if is_datetime64_dtype(data.dtype): data = dt64arr_to_periodarr(data, freq, tz) return cls._from_ordinals(data, name=name, freq=freq) # check not floats if infer_dtype(data) == 'floating' and len(data) > 0: raise TypeError("PeriodIndex does not allow " "floating point in construction") # anything else, likely an array of strings or periods data = _ensure_object(data) freq = freq or period.extract_freq(data) data = period.extract_ordinals(data, freq) return cls._from_ordinals(data, name=name, freq=freq)
def _check_compatible_with(self, other): if self.freqstr != other.freqstr: msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr) raise IncompatibleFrequency(msg)
def __new__(cls, data=None, ordinal=None, freq=None, start=None, end=None, periods=None, tz=None, dtype=None, copy=False, name=None, **fields): valid_field_set = { 'year', 'month', 'day', 'quarter', 'hour', 'minute', 'second' } if not set(fields).issubset(valid_field_set): raise TypeError( '__new__() got an unexpected keyword argument {}'.format( list(set(fields) - valid_field_set)[0])) if name is None and hasattr(data, 'name'): name = data.name if data is None and ordinal is None: # range-based. if periods is not None: if is_float(periods): periods = int(periods) elif not is_integer(periods): msg = 'periods must be a number, got {periods}' raise TypeError(msg.format(periods=periods)) data, freq = PeriodArray._generate_range(start, end, periods, freq, fields) data = PeriodArray(data, freq=freq) else: if freq is None and dtype is not None: freq = PeriodDtype(dtype).freq elif freq and dtype: freq = PeriodDtype(freq).freq dtype = PeriodDtype(dtype).freq if freq != dtype: msg = "specified freq and dtype are different" raise IncompatibleFrequency(msg) # PeriodIndex allow PeriodIndex(period_index, freq=different) # Let's not encourage that kind of behavior in PeriodArray. if freq and isinstance(data, cls) and data.freq != freq: # TODO: We can do some of these with no-copy / coercion? # e.g. D -> 2D seems to be OK data = data.asfreq(freq) if data is None and ordinal is not None: # we strangely ignore `ordinal` if data is passed. ordinal = np.asarray(ordinal, dtype=np.int64) data = PeriodArray(ordinal, freq) else: # don't pass copy here, since we copy later. data = period_array(data=data, freq=freq) if copy: data = data.copy() return cls._simple_new(data, name=name)