def equals(self, other): """ Determines if two Index objects contain the same elements. """ if self.is_(other): return True if not isinstance(other, ABCIndexClass): return False elif not isinstance(other, type(self)): try: other = type(self)(other) except Exception: return False if not is_dtype_equal(self.dtype, other.dtype): # have different timezone return False elif is_period_dtype(self): if not is_period_dtype(other): return False if self.freq != other.freq: return False return np.array_equal(self.asi8, other.asi8)
def test_is_period_dtype(): assert not com.is_period_dtype(object) assert not com.is_period_dtype([1, 2, 3]) assert not com.is_period_dtype(pd.Period("2017-01-01")) assert com.is_period_dtype(PeriodDtype(freq="D")) assert com.is_period_dtype(pd.PeriodIndex([], freq="A"))
def sort_values(self, return_indexer=False, ascending=True): """ Return sorted copy of Index. """ if return_indexer: _as = self.argsort() if not ascending: _as = _as[::-1] sorted_index = self.take(_as) return sorted_index, _as else: sorted_values = np.sort(self._ndarray_values) attribs = self._get_attributes_dict() freq = attribs['freq'] if freq is not None and not is_period_dtype(self): if freq.n > 0 and not ascending: freq = freq * -1 elif freq.n < 0 and ascending: freq = freq * -1 attribs['freq'] = freq if not ascending: sorted_values = sorted_values[::-1] return self._simple_new(sorted_values, **attribs)
def validate_dtype_freq(dtype, freq): """ 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: freq = frequencies.to_offset(freq) 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') return freq
def convert_pandas_type_to_json_field(arr, dtype=None): dtype = dtype or arr.dtype if arr.name is None: name = 'values' else: name = arr.name field = {'name': name, 'type': as_json_table_type(dtype)} if is_categorical_dtype(arr): if hasattr(arr, 'categories'): cats = arr.categories ordered = arr.ordered else: cats = arr.cat.categories ordered = arr.cat.ordered field['constraints'] = {"enum": list(cats)} field['ordered'] = ordered elif is_period_dtype(arr): field['freq'] = arr.freqstr elif is_datetime64tz_dtype(arr): if hasattr(arr, 'dt'): field['tz'] = arr.dt.tz.zone else: field['tz'] = arr.tz.zone return field
def _addsub_offset_array(self, other, op): """ Add or subtract array-like of DateOffset objects Parameters ---------- other : Index, np.ndarray object-dtype containing pd.DateOffset objects op : {operator.add, operator.sub} Returns ------- result : same class as self """ assert op in [operator.add, operator.sub] if len(other) == 1: return op(self, other[0]) warnings.warn("Adding/subtracting array of DateOffsets to " "{cls} not vectorized" .format(cls=type(self).__name__), PerformanceWarning) # For EA self.astype('O') returns a numpy array, not an Index left = lib.values_from_object(self.astype('O')) res_values = op(left, np.array(other)) if not is_period_dtype(self): return type(self)(res_values, freq='infer') return self._from_sequence(res_values)
def _addsub_int_array(self, other, op): """ Add or subtract array-like of integers equivalent to applying `_time_shift` pointwise. Parameters ---------- other : Index, ExtensionArray, np.ndarray integer-dtype op : {operator.add, operator.sub} Returns ------- result : same class as self """ # _addsub_int_array is overriden by PeriodArray assert not is_period_dtype(self) assert op in [operator.add, operator.sub] if self.freq is None: # GH#19123 raise NullFrequencyError("Cannot shift with no freq") elif isinstance(self.freq, Tick): # easy case where we can convert to timedelta64 operation td = Timedelta(self.freq) return op(self, td * other) # We should only get here with DatetimeIndex; dispatch # to _addsub_offset_array assert not is_timedelta64_dtype(self) return op(self, np.array(other) * self.freq)
def _addsub_offset_array(self, other, op): """ Add or subtract array-like of DateOffset objects Parameters ---------- other : Index, np.ndarray object-dtype containing pd.DateOffset objects op : {operator.add, operator.sub} Returns ------- result : same class as self """ assert op in [operator.add, operator.sub] if len(other) == 1: return op(self, other[0]) warnings.warn("Adding/subtracting array of DateOffsets to " "{cls} not vectorized" .format(cls=type(self).__name__), PerformanceWarning) res_values = op(self.astype('O').values, np.array(other)) kwargs = {} if not is_period_dtype(self): kwargs['freq'] = 'infer' return type(self)(res_values, **kwargs)
def _validate_frequency(cls, index, freq, **kwargs): """ Validate that a frequency is compatible with the values of a given Datetime Array/Index or Timedelta Array/Index Parameters ---------- index : DatetimeIndex or TimedeltaIndex The index on which to determine if the given frequency is valid freq : DateOffset The frequency to validate """ if is_period_dtype(cls): # Frequency validation is not meaningful for Period Array/Index return None inferred = index.inferred_freq if index.size == 0 or inferred == freq.freqstr: return None on_freq = cls._generate_range(start=index[0], end=None, periods=len(index), freq=freq, **kwargs) if not np.array_equal(index.asi8, on_freq.asi8): raise ValueError('Inferred frequency {infer} from passed values ' 'does not conform to passed frequency {passed}' .format(infer=inferred, passed=freq.freqstr))
def __sub__(self, other): other = lib.item_from_zerodim(other) if isinstance(other, (ABCSeries, ABCDataFrame)): return NotImplemented # scalar others elif other is NaT: result = self._sub_nat() elif isinstance(other, (Tick, timedelta, np.timedelta64)): result = self._add_delta(-other) elif isinstance(other, DateOffset): # specifically _not_ a Tick result = self._add_offset(-other) elif isinstance(other, (datetime, np.datetime64)): result = self._sub_datetimelike_scalar(other) elif lib.is_integer(other): # This check must come after the check for np.timedelta64 # as is_integer returns True for these maybe_integer_op_deprecated(self) result = self._time_shift(-other) elif isinstance(other, Period): result = self._sub_period(other) # array-like others elif is_timedelta64_dtype(other): # TimedeltaIndex, ndarray[timedelta64] result = self._add_delta(-other) elif is_offsetlike(other): # Array/Index of DateOffset objects result = self._addsub_offset_array(other, operator.sub) elif is_datetime64_dtype(other) or is_datetime64tz_dtype(other): # DatetimeIndex, ndarray[datetime64] result = self._sub_datetime_arraylike(other) elif is_period_dtype(other): # PeriodIndex result = self._sub_period_array(other) elif is_integer_dtype(other): maybe_integer_op_deprecated(self) result = self._addsub_int_array(other, operator.sub) elif isinstance(other, ABCIndexClass): raise TypeError("cannot subtract {cls} and {typ}" .format(cls=type(self).__name__, typ=type(other).__name__)) elif is_float_dtype(other): # Explicitly catch invalid dtypes raise TypeError("cannot subtract {dtype}-dtype from {cls}" .format(dtype=other.dtype, cls=type(self).__name__)) elif is_extension_array_dtype(other): # Categorical op will raise; defer explicitly return NotImplemented else: # pragma: no cover return NotImplemented if is_timedelta64_dtype(result) and isinstance(result, np.ndarray): from pandas.core.arrays import TimedeltaArrayMixin # TODO: infer freq? return TimedeltaArrayMixin(result) return result
def _sub_period_array(self, other): """ Subtract one PeriodIndex from another. This is only valid if they have the same frequency. Parameters ---------- other : PeriodIndex 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 to {cls}" .format(dtype=other.dtype, cls=type(self).__name__)) if not len(self) == len(other): raise ValueError("cannot subtract indices of unequal length") if self.freq != other.freq: msg = _DIFFERENT_FREQ_INDEX.format(self.freqstr, 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 * 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 astype(self, dtype, copy=True): # We handle Period[T] -> Period[U] # Our parent handles everything else. dtype = pandas_dtype(dtype) if is_period_dtype(dtype): return self.asfreq(dtype.freq) return super(PeriodArray, self).astype(dtype, copy=copy)
def __new__(cls, values, freq=None, **kwargs): if is_period_dtype(values): # PeriodArray, PeriodIndex if freq is not None and values.freq != freq: raise IncompatibleFrequency(freq, values.freq) freq = values.freq values = values.asi8 return cls._simple_new(values, freq, **kwargs)
def __add__(self, other): other = lib.item_from_zerodim(other) if isinstance(other, (ABCSeries, ABCDataFrame)): return NotImplemented # scalar others elif other is NaT: result = self._add_nat() elif isinstance(other, (Tick, timedelta, np.timedelta64)): result = self._add_delta(other) elif isinstance(other, DateOffset): # specifically _not_ a Tick result = self._add_offset(other) elif isinstance(other, (datetime, np.datetime64)): result = self._add_datetimelike_scalar(other) elif lib.is_integer(other): # This check must come after the check for np.timedelta64 # as is_integer returns True for these maybe_integer_op_deprecated(self) result = self._time_shift(other) # array-like others elif is_timedelta64_dtype(other): # TimedeltaIndex, ndarray[timedelta64] result = self._add_delta(other) elif is_offsetlike(other): # Array/Index of DateOffset objects result = self._addsub_offset_array(other, operator.add) elif is_datetime64_dtype(other) or is_datetime64tz_dtype(other): # DatetimeIndex, ndarray[datetime64] return self._add_datetime_arraylike(other) elif is_integer_dtype(other): maybe_integer_op_deprecated(self) result = self._addsub_int_array(other, operator.add) elif is_float_dtype(other): # Explicitly catch invalid dtypes raise TypeError("cannot add {dtype}-dtype to {cls}" .format(dtype=other.dtype, cls=type(self).__name__)) elif is_period_dtype(other): # if self is a TimedeltaArray and other is a PeriodArray with # a timedelta-like (i.e. Tick) freq, this operation is valid. # Defer to the PeriodArray implementation. # In remaining cases, this will end up raising TypeError. return NotImplemented elif is_extension_array_dtype(other): # Categorical op will raise; defer explicitly return NotImplemented else: # pragma: no cover return NotImplemented if is_timedelta64_dtype(result) and isinstance(result, np.ndarray): from pandas.core.arrays import TimedeltaArrayMixin # TODO: infer freq? return TimedeltaArrayMixin(result) return result
def _add_nat(self): """Add pd.NaT to self""" if is_period_dtype(self): raise TypeError('Cannot add {cls} and {typ}' .format(cls=type(self).__name__, typ=type(NaT).__name__)) # GH#19124 pd.NaT is treated like a timedelta for both timedelta # and datetime dtypes return self._nat_new(box=True)
def test_construction(self): with tm.assertRaises(ValueError): PeriodDtype('xx') for s in ['period[D]', 'Period[D]', 'D']: dt = PeriodDtype(s) self.assertEqual(dt.freq, pd.tseries.offsets.Day()) self.assertTrue(is_period_dtype(dt)) for s in ['period[3D]', 'Period[3D]', '3D']: dt = PeriodDtype(s) self.assertEqual(dt.freq, pd.tseries.offsets.Day(3)) self.assertTrue(is_period_dtype(dt)) for s in ['period[26H]', 'Period[26H]', '26H', 'period[1D2H]', 'Period[1D2H]', '1D2H']: dt = PeriodDtype(s) self.assertEqual(dt.freq, pd.tseries.offsets.Hour(26)) self.assertTrue(is_period_dtype(dt))
def astype(self, dtype, copy=True, how='start'): dtype = pandas_dtype(dtype) if is_integer_dtype(dtype): return self._int64index.copy() if copy else self._int64index elif is_datetime64_any_dtype(dtype): tz = getattr(dtype, 'tz', None) return self.to_timestamp(how=how).tz_localize(tz) elif is_period_dtype(dtype): return self.asfreq(freq=dtype.freq) return super(PeriodIndex, self).astype(dtype, copy=copy)
def test_construction(self): with pytest.raises(ValueError): PeriodDtype('xx') for s in ['period[D]', 'Period[D]', 'D']: dt = PeriodDtype(s) assert dt.freq == pd.tseries.offsets.Day() assert is_period_dtype(dt) for s in ['period[3D]', 'Period[3D]', '3D']: dt = PeriodDtype(s) assert dt.freq == pd.tseries.offsets.Day(3) assert is_period_dtype(dt) for s in ['period[26H]', 'Period[26H]', '26H', 'period[1D2H]', 'Period[1D2H]', '1D2H']: dt = PeriodDtype(s) assert dt.freq == pd.tseries.offsets.Hour(26) assert is_period_dtype(dt)
def repeat(self, repeats, *args, **kwargs): """ Analogous to ndarray.repeat. """ nv.validate_repeat(args, kwargs) if is_period_dtype(self): freq = self.freq else: freq = None return self._shallow_copy(self.asi8.repeat(repeats), freq=freq)
def __getitem__(self, key): """ This getitem defers to the underlying array, which by-definition can only handle list-likes, slices, and integer scalars """ is_int = lib.is_integer(key) if lib.is_scalar(key) and not is_int: raise IndexError("only integers, slices (`:`), ellipsis (`...`), " "numpy.newaxis (`None`) and integer or boolean " "arrays are valid indices") getitem = self._data.__getitem__ if is_int: val = getitem(key) return self._box_func(val) if com.is_bool_indexer(key): key = np.asarray(key, dtype=bool) if key.all(): key = slice(0, None, None) else: key = lib.maybe_booleans_to_slice(key.view(np.uint8)) attribs = self._get_attributes_dict() is_period = is_period_dtype(self) if is_period: freq = self.freq else: freq = None if isinstance(key, slice): if self.freq is not None and key.step is not None: freq = key.step * self.freq else: freq = self.freq elif key is Ellipsis: # GH#21282 indexing with Ellipsis is similar to a full slice, # should preserve `freq` attribute freq = self.freq attribs['freq'] = freq result = getitem(key) if result.ndim > 1: # To support MPL which performs slicing with 2 dim # even though it only has 1 dim by definition if is_period: return self._simple_new(result, **attribs) return result return self._simple_new(result, **attribs)
def __new__(cls, values, freq=None, **kwargs): if is_period_dtype(values): # PeriodArray, PeriodIndex if freq is not None and values.freq != freq: raise IncompatibleFrequency(freq, values.freq) freq = values.freq values = values.asi8 elif is_datetime64_dtype(values): # TODO: what if it has tz? values = dt64arr_to_periodarr(values, freq) return cls._simple_new(values, freq=freq, **kwargs)
def __add__(self, other): other = lib.item_from_zerodim(other) if isinstance(other, (ABCSeries, ABCDataFrame)): return NotImplemented # scalar others elif other is NaT: result = self._add_nat() elif isinstance(other, (Tick, timedelta, np.timedelta64)): result = self._add_delta(other) elif isinstance(other, DateOffset): # specifically _not_ a Tick result = self._add_offset(other) elif isinstance(other, (datetime, np.datetime64)): result = self._add_datelike(other) elif is_integer(other): # This check must come after the check for np.timedelta64 # as is_integer returns True for these result = self.shift(other) # array-like others elif is_timedelta64_dtype(other): # TimedeltaIndex, ndarray[timedelta64] result = self._add_delta(other) elif is_offsetlike(other): # Array/Index of DateOffset objects result = self._addsub_offset_array(other, operator.add) elif is_datetime64_dtype(other) or is_datetime64tz_dtype(other): # DatetimeIndex, ndarray[datetime64] return self._add_datelike(other) elif is_integer_dtype(other): result = self._addsub_int_array(other, operator.add) elif is_float_dtype(other) or is_period_dtype(other): # Explicitly catch invalid dtypes raise TypeError("cannot add {dtype}-dtype to {cls}" .format(dtype=other.dtype, cls=type(self).__name__)) elif is_categorical_dtype(other): # Categorical op will raise; defer explicitly return NotImplemented else: # pragma: no cover return NotImplemented if result is NotImplemented: return NotImplemented elif not isinstance(result, Index): # Index.__new__ will choose appropriate subclass for dtype result = Index(result) res_name = ops.get_op_result_name(self, other) result.name = res_name return result
def _add_nat(self): """ Add pd.NaT to self """ if is_period_dtype(self): raise TypeError('Cannot add {cls} and {typ}' .format(cls=type(self).__name__, typ=type(NaT).__name__)) # GH#19124 pd.NaT is treated like a timedelta for both timedelta # and datetime dtypes result = np.zeros(len(self), dtype=np.int64) result.fill(iNaT) return type(self)(result, dtype=self.dtype, freq=None)
def __init__(self, obj, orient, date_format, double_precision, ensure_ascii, date_unit, index, default_handler=None): """ Adds a `schema` attribute with the Table Schema, resets the index (can't do in caller, because the schema inference needs to know what the index is, forces orient to records, and forces date_format to 'iso'. """ super(JSONTableWriter, self).__init__( obj, orient, date_format, double_precision, ensure_ascii, date_unit, index, default_handler=default_handler) if date_format != 'iso': msg = ("Trying to write with `orient='table'` and " "`date_format='{fmt}'`. Table Schema requires dates " "to be formatted with `date_format='iso'`" .format(fmt=date_format)) raise ValueError(msg) self.schema = build_table_schema(obj, index=self.index) # NotImplementd on a column MultiIndex if obj.ndim == 2 and isinstance(obj.columns, MultiIndex): raise NotImplementedError( "orient='table' is not supported for MultiIndex") # TODO: Do this timedelta properly in objToJSON.c See GH #15137 if ((obj.ndim == 1) and (obj.name in set(obj.index.names)) or len(obj.columns & obj.index.names)): msg = "Overlapping names between the index and columns" raise ValueError(msg) obj = obj.copy() timedeltas = obj.select_dtypes(include=['timedelta']).columns if len(timedeltas): obj[timedeltas] = obj[timedeltas].applymap( lambda x: x.isoformat()) # Convert PeriodIndex to datetimes before serialzing if is_period_dtype(obj.index): obj.index = obj.index.to_timestamp() # exclude index from obj if index=False if not self.index: self.obj = obj.reset_index(drop=True) else: self.obj = obj.reset_index(drop=False) self.date_format = 'iso' self.orient = 'records' self.index = index
def _concat_same_dtype(self, to_concat, name): """ Concatenate to_concat which has the same class """ attribs = self._get_attributes_dict() attribs['name'] = name if not is_period_dtype(self): # reset freq attribs['freq'] = None if getattr(self, 'tz', None) is not None: return _concat._concat_datetimetz(to_concat, name) else: new_data = np.concatenate([c.asi8 for c in to_concat]) return self._simple_new(new_data, **attribs)
def _concat_same_dtype(self, to_concat, name): """ Concatenate to_concat which has the same class. """ attribs = self._get_attributes_dict() attribs['name'] = name # do not pass tz to set because tzlocal cannot be hashed if len({str(x.dtype) for x in to_concat}) != 1: raise ValueError('to_concat must have the same tz') if not is_period_dtype(self): # reset freq attribs['freq'] = None new_data = type(self._values)._concat_same_type(to_concat).asi8 return self._simple_new(new_data, **attribs)
def astype(self, dtype, copy=True, how='start'): dtype = pandas_dtype(dtype) if is_object_dtype(dtype): return self.asobject elif is_integer_dtype(dtype): if copy: return self._int64index.copy() else: return self._int64index elif is_datetime64_dtype(dtype): return self.to_timestamp(how=how) elif is_datetime64tz_dtype(dtype): return self.to_timestamp(how=how).tz_localize(dtype.tz) elif is_period_dtype(dtype): return self.asfreq(freq=dtype.freq) raise ValueError('Cannot cast PeriodIndex to dtype %s' % dtype)
def as_json_table_type(x): """ Convert a NumPy / pandas type to its corresponding json_table. Parameters ---------- x : array or dtype Returns ------- t : str the Table Schema data types Notes ----- This table shows the relationship between NumPy / pandas dtypes, and Table Schema dtypes. ============== ================= Pandas type Table Schema type ============== ================= int64 integer float64 number bool boolean datetime64[ns] datetime timedelta64[ns] duration object str categorical any =============== ================= """ if is_integer_dtype(x): return 'integer' elif is_bool_dtype(x): return 'boolean' elif is_numeric_dtype(x): return 'number' elif (is_datetime64_dtype(x) or is_datetime64tz_dtype(x) or is_period_dtype(x)): return 'datetime' elif is_timedelta64_dtype(x): return 'duration' elif is_categorical_dtype(x): return 'any' elif is_string_dtype(x): return 'string' else: return 'any'
def take(self, indices, axis=0, allow_fill=True, fill_value=None, **kwargs): nv.validate_take(tuple(), kwargs) indices = ensure_int64(indices) maybe_slice = lib.maybe_indices_to_slice(indices, len(self)) if isinstance(maybe_slice, slice): return self[maybe_slice] taken = self._assert_take_fillable(self.asi8, indices, allow_fill=allow_fill, fill_value=fill_value, na_value=iNaT) # keep freq in PeriodArray/Index, reset otherwise freq = self.freq if is_period_dtype(self) else None return self._shallow_copy(taken, freq=freq)
def _simple_new(cls, values, freq=None, **kwargs): """ Values can be any type that can be coerced to Periods. Ordinals in an ndarray are fastpath-ed to `_from_ordinals` """ if is_period_dtype(values): freq = dtl.validate_dtype_freq(values.dtype, freq) values = values.asi8 if not is_integer_dtype(values): values = np.array(values, copy=False) if len(values) > 0 and is_float_dtype(values): raise TypeError("{cls} can't take floats" .format(cls=cls.__name__)) return cls(values, freq=freq, **kwargs) return cls._from_ordinals(values, freq=freq, **kwargs)
def __rsub__(self, other): if is_datetime64_dtype(other) and is_timedelta64_dtype(self): # ndarray[datetime64] cannot be subtracted from self, so # we need to wrap in DatetimeArray/Index and flip the operation if not isinstance(other, DatetimeLikeArrayMixin): # Avoid down-casting DatetimeIndex from pandas.core.arrays import DatetimeArrayMixin other = DatetimeArrayMixin(other) return other - self elif (is_datetime64_any_dtype(self) and hasattr(other, 'dtype') and not is_datetime64_any_dtype(other)): # GH#19959 datetime - datetime is well-defined as timedelta, # but any other type - datetime is not well-defined. raise TypeError("cannot subtract {cls} from {typ}".format( cls=type(self).__name__, typ=type(other).__name__)) elif is_period_dtype(self) and is_timedelta64_dtype(other): # TODO: Can we simplify/generalize these cases at all? raise TypeError("cannot subtract {cls} from {dtype}".format( cls=type(self).__name__, dtype=other.dtype)) return -(self - other)
def _nat_new(self, box=True): """ Return Array/Index or ndarray filled with NaT which has the same length as the caller. Parameters ---------- box : boolean, default True - If True returns a Array/Index as the same as caller. - If False returns ndarray of np.int64. """ result = np.zeros(len(self), dtype=np.int64) result.fill(iNaT) if not box: return result attribs = self._get_attributes_dict() if not is_period_dtype(self): attribs['freq'] = None return self._simple_new(result, **attribs)
def _get_delete_freq(self, loc: int): """ Find the `freq` for self.delete(loc). """ freq = None if is_period_dtype(self.dtype): freq = self.freq elif self.freq is not None: if is_integer(loc): if loc in (0, -len(self), -1, len(self) - 1): freq = self.freq else: if is_list_like(loc): loc = lib.maybe_indices_to_slice( np.asarray(loc, dtype=np.intp), len(self) ) if isinstance(loc, slice) and loc.step in (1, None): if loc.start in (0, None) or loc.stop in (len(self), None): freq = self.freq return freq
def _concat_same_dtype(self, to_concat, name): """ Concatenate to_concat which has the same class. """ attribs = self._get_attributes_dict() attribs["name"] = name # do not pass tz to set because tzlocal cannot be hashed if len({str(x.dtype) for x in to_concat}) != 1: raise ValueError("to_concat must have the same tz") new_data = type(self._values)._concat_same_type(to_concat).asi8 # GH 3232: If the concat result is evenly spaced, we can retain the # original frequency is_diff_evenly_spaced = len(unique_deltas(new_data)) == 1 if not is_period_dtype(self) and not is_diff_evenly_spaced: # reset freq attribs["freq"] = None return self._simple_new(new_data, **attribs)
def delete(self, loc): new_i8s = np.delete(self.asi8, loc) freq = None if is_period_dtype(self.dtype): freq = self.freq elif is_integer(loc): if loc in (0, -len(self), -1, len(self) - 1): freq = self.freq else: if is_list_like(loc): loc = lib.maybe_indices_to_slice( np.asarray(loc, dtype=np.intp), len(self) ) if isinstance(loc, slice) and loc.step in (1, None): if loc.start in (0, None) or loc.stop in (len(self), None): freq = self.freq arr = type(self._data)._simple_new(new_i8s, dtype=self.dtype, freq=freq) return type(self)._simple_new(arr, name=self.name)
def take(self, indices, axis=0, allow_fill=True, fill_value=None, **kwargs): nv.validate_take(tuple(), kwargs) indices = ensure_int64(indices) maybe_slice = lib.maybe_indices_to_slice(indices, len(self)) if isinstance(maybe_slice, slice): return self[maybe_slice] taken = ExtensionIndex.take(self, indices, axis, allow_fill, fill_value, **kwargs) # keep freq in PeriodArray/Index, reset otherwise freq = self.freq if is_period_dtype(self) else None assert taken.freq == freq, (taken.freq, freq, taken) return self._shallow_copy(taken, freq=freq)
def get_dtype_kinds(l): """ Parameters ---------- l : list of arrays Returns ------- a set of kinds that exist in this list of arrays """ typs = set() for arr in l: dtype = arr.dtype if is_categorical_dtype(dtype): typ = 'category' elif is_sparse(arr): typ = 'sparse' elif isinstance(arr, ABCRangeIndex): typ = 'range' elif is_datetimetz(arr): # if to_concat contains different tz, # the result must be object dtype typ = str(arr.dtype) elif is_datetime64_dtype(dtype): typ = 'datetime' elif is_timedelta64_dtype(dtype): typ = 'timedelta' elif is_object_dtype(dtype): typ = 'object' elif is_bool_dtype(dtype): typ = 'bool' elif is_period_dtype(dtype): typ = str(arr.dtype) elif is_interval_dtype(dtype): typ = str(arr.dtype) else: typ = dtype.kind typs.add(typ) return typs
def astype(self, dtype, copy=True, how='start'): dtype = pandas_dtype(dtype) if is_object_dtype(dtype): return self._box_values_as_index() elif is_integer_dtype(dtype): if copy: return self._int64index.copy() else: return self._int64index elif is_datetime64_dtype(dtype): return self.to_timestamp(how=how) elif is_datetime64tz_dtype(dtype): return self.to_timestamp(how=how).tz_localize(dtype.tz) elif is_period_dtype(dtype): return self.asfreq(freq=dtype.freq) elif is_categorical_dtype(dtype): return CategoricalIndex(self.values, name=self.name, dtype=dtype, copy=copy) raise TypeError('Cannot cast PeriodIndex to dtype %s' % dtype)
def test_basic(self): assert is_period_dtype(self.dtype) pidx = pd.period_range('2013-01-01 09:00', periods=5, freq='H') assert is_period_dtype(pidx.dtype) assert is_period_dtype(pidx) assert is_period(pidx) s = Series(pidx, name='A') assert is_period_dtype(s.dtype) assert is_period_dtype(s) assert is_period(s) assert not is_period_dtype(np.dtype('float64')) assert not is_period_dtype(1.0) assert not is_period(np.dtype('float64')) assert not is_period(1.0)
def test_unstack(self, data, index, obj): data = data[:len(index)] if obj == "series": ser = pd.Series(data, index=index) else: ser = pd.DataFrame({"A": data, "B": data}, index=index) n = index.nlevels levels = list(range(n)) # [0, 1, 2] # [(0,), (1,), (2,), (0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)] combinations = itertools.chain.from_iterable( itertools.permutations(levels, i) for i in range(1, n)) for level in combinations: result = ser.unstack(level=level) assert all( isinstance(result[col].array, type(data)) for col in result.columns) if obj == "series": # We should get the same result with to_frame+unstack+droplevel df = ser.to_frame() alt = df.unstack(level=level).droplevel(0, axis=1) self.assert_frame_equal(result, alt) expected = ser.astype(object).unstack( level=level, fill_value=data.dtype.na_value) if obj == "series": # TODO: special cases belong in dtype-specific tests if is_period_dtype(data.dtype): assert expected.dtypes.apply(is_period_dtype).all() expected = expected.astype(object) if is_interval_dtype(data.dtype): assert expected.dtypes.apply(is_interval_dtype).all() expected = expected.astype(object) result = result.astype(object) self.assert_frame_equal(result, expected)
def _addsub_int_array(self, other, op): """ Add or subtract array-like of integers equivalent to applying `shift` pointwise. Parameters ---------- other : Index, ExtensionArray, np.ndarray integer-dtype op : {operator.add, operator.sub} Returns ------- result : same class as self """ assert op in [operator.add, operator.sub] if is_period_dtype(self): # easy case for PeriodIndex if op is operator.sub: other = -other res_values = checked_add_with_arr(self.asi8, other, arr_mask=self._isnan) res_values = res_values.view('i8') res_values[self._isnan] = iNaT return self._from_ordinals(res_values, freq=self.freq) elif self.freq is None: # GH#19123 raise NullFrequencyError("Cannot shift with no freq") elif isinstance(self.freq, Tick): # easy case where we can convert to timedelta64 operation td = Timedelta(self.freq) return op(self, td * other) # We should only get here with DatetimeIndex; dispatch # to _addsub_offset_array assert not is_timedelta64_dtype(self) return op(self, np.array(other) * self.freq)
def convert_pandas_type_to_json_field(arr): dtype = arr.dtype if arr.name is None: name = "values" else: name = arr.name field: dict[str, JSONSerializable] = { "name": name, "type": as_json_table_type(dtype), } if is_categorical_dtype(dtype): cats = dtype.categories ordered = dtype.ordered field["constraints"] = {"enum": list(cats)} field["ordered"] = ordered elif is_period_dtype(dtype): field["freq"] = dtype.freq.freqstr elif is_datetime64tz_dtype(dtype): field["tz"] = dtype.tz.zone return field
def make_field(arr, dtype=None): dtype = dtype or arr.dtype field = {'name': arr.name or 'values', 'type': as_json_table_type(dtype)} if is_categorical_dtype(arr): if hasattr(arr, 'categories'): cats = arr.categories ordered = arr.ordered else: cats = arr.cat.categories ordered = arr.cat.ordered field['constraints'] = {"enum": list(cats)} field['ordered'] = ordered elif is_period_dtype(arr): field['freq'] = arr.freqstr elif is_datetime64tz_dtype(arr): if hasattr(arr, 'dt'): field['tz'] = arr.dt.tz.zone else: field['tz'] = arr.tz.zone return field
def _ea_wrap_cython_operation( self, kind: str, values, how: str, axis: int, min_count: int = -1, **kwargs) -> Tuple[np.ndarray, Optional[List[str]]]: """ If we have an ExtensionArray, unwrap, call _cython_operation, and re-wrap if appropriate. """ # TODO: general case implementation overrideable by EAs. orig_values = values if is_datetime64tz_dtype(values.dtype) or is_period_dtype( values.dtype): # All of the functions implemented here are ordinal, so we can # operate on the tz-naive equivalents values = values.view("M8[ns]") res_values, names = self._cython_operation(kind, values, how, axis, min_count, **kwargs) res_values = res_values.astype("i8", copy=False) # FIXME: this is wrong for rank, but not tested. result = type(orig_values)._simple_new(res_values, dtype=orig_values.dtype) return result, names elif is_integer_dtype(values.dtype) or is_bool_dtype(values.dtype): # IntegerArray or BooleanArray values = ensure_int_or_float(values) res_values, names = self._cython_operation(kind, values, how, axis, min_count, **kwargs) result = maybe_cast_result(result=res_values, obj=orig_values, how=how) return result, names raise NotImplementedError(values.dtype)
def _concat_same_dtype(self, to_concat, name): """ Concatenate to_concat which has the same class. """ attribs = self._get_attributes_dict() attribs['name'] = name # do not pass tz to set because tzlocal cannot be hashed if len({str(x.dtype) for x in to_concat}) != 1: raise ValueError('to_concat must have the same tz') if not is_period_dtype(self): # reset freq attribs['freq'] = None # TODO(DatetimeArray) # - remove the .asi8 here # - remove the _maybe_box_as_values # - combine with the `else` block new_data = self._concat_same_type(to_concat).asi8 else: new_data = type(self._values)._concat_same_type(to_concat) return self._simple_new(new_data, **attribs)
def _get_delete_freq(self, loc: int | slice | Sequence[int]): """ Find the `freq` for self.delete(loc). """ freq = None if is_period_dtype(self.dtype): freq = self.freq elif self.freq is not None: if is_integer(loc): if loc in (0, -len(self), -1, len(self) - 1): freq = self.freq else: if is_list_like(loc): # error: Incompatible types in assignment (expression has # type "Union[slice, ndarray]", variable has type # "Union[int, slice, Sequence[int]]") loc = lib.maybe_indices_to_slice( # type: ignore[assignment] np.asarray(loc, dtype=np.intp), len(self)) if isinstance(loc, slice) and loc.step in (1, None): if loc.start in (0, None) or loc.stop in (len(self), None): freq = self.freq return freq
def _validate_frequency(cls, index, freq, **kwargs): """ Validate that a frequency is compatible with the values of a given Datetime Array/Index or Timedelta Array/Index Parameters ---------- index : DatetimeIndex or TimedeltaIndex The index on which to determine if the given frequency is valid freq : DateOffset The frequency to validate """ if is_period_dtype(cls): # Frequency validation is not meaningful for Period Array/Index return None inferred = index.inferred_freq if index.size == 0 or inferred == freq.freqstr: return None try: on_freq = cls._generate_range(start=index[0], end=None, periods=len(index), freq=freq, **kwargs) if not np.array_equal(index.asi8, on_freq.asi8): raise ValueError except ValueError as e: if "non-fixed" in str(e): # non-fixed frequencies are not meaningful for timedelta64; # we retain that error message raise e # GH#11587 the main way this is reached is if the `np.array_equal` # check above is False. This can also be reached if index[0] # is `NaT`, in which case the call to `cls._generate_range` will # raise a ValueError, which we re-raise with a more targeted # message. raise ValueError('Inferred frequency {infer} from passed values ' 'does not conform to passed frequency {passed}' .format(infer=inferred, passed=freq.freqstr))
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 test_basic(self): assert is_period_dtype(self.dtype) pidx = pd.period_range('2013-01-01 09:00', periods=5, freq='H') assert is_period_dtype(pidx.dtype) assert is_period_dtype(pidx) assert is_period(pidx) s = Series(pidx, name='A') # dtypes # series results in object dtype currently, # is_period checks period_arraylike assert not is_period_dtype(s.dtype) assert not is_period_dtype(s) assert is_period(s) assert not is_period_dtype(np.dtype('float64')) assert not is_period_dtype(1.0) assert not is_period(np.dtype('float64')) assert not is_period(1.0)
def astype(self, dtype, copy=True): # TODO: Figure out something better here... # We have DatetimeLikeArrayMixin -> # super(...), which ends up being... DatetimeIndexOpsMixin? # this is complicated. # need a pandas_astype(arr, dtype). from pandas import Categorical dtype = pandas_dtype(dtype) if is_object_dtype(dtype): return np.asarray(self, dtype=object) elif is_string_dtype(dtype) and not is_categorical_dtype(dtype): return self._format_native_types() elif is_integer_dtype(dtype): values = self._data if values.dtype != dtype: # int32 vs. int64 values = values.astype(dtype) elif copy: values = values.copy() return values elif (is_datetime_or_timedelta_dtype(dtype) and not is_dtype_equal(self.dtype, dtype)) or is_float_dtype(dtype): # disallow conversion between datetime/timedelta, # and conversions for any datetimelike to float msg = 'Cannot cast {name} to dtype {dtype}' raise TypeError(msg.format(name=type(self).__name__, dtype=dtype)) elif is_categorical_dtype(dtype): return Categorical(self, dtype=dtype) elif is_period_dtype(dtype): return self.asfreq(dtype.freq) else: return np.asarray(self, dtype=dtype)
def as_json_table_type(x) -> str: if is_integer_dtype(x): return 'integer' elif is_bool_dtype(x): return 'boolean' elif is_numeric_dtype(x): return 'number' elif (is_datetime64_dtype(x) or is_datetime64tz_dtype(x) or is_period_dtype(x)): # TODO: fix this # return 'datetime' return 'string' elif is_timedelta64_dtype(x): # TODO: fix this # return 'duration' return 'string' elif is_categorical_dtype(x): # TODO: fix this # return 'any' return 'string' elif is_string_dtype(x): return 'string' else: return 'any'
def _get_insert_freq(self, loc: int, item): """ Find the `freq` for self.insert(loc, item). """ value = self._data._validate_scalar(item) item = self._data._box_func(value) freq = None if is_period_dtype(self.dtype): freq = self.freq elif self.freq is not None: # freq can be preserved on edge cases if self.size: if item is NaT: pass elif (loc == 0 or loc == -len(self)) and item + self.freq == self[0]: freq = self.freq elif (loc == len(self)) and item - self.freq == self[-1]: freq = self.freq else: # Adding a single item to an empty index may preserve freq if self.freq.is_on_offset(item): freq = self.freq return freq
def test_basic(self): assert is_period_dtype(self.dtype) pidx = pd.period_range('2013-01-01 09:00', periods=5, freq='H') assert is_period_dtype(pidx.dtype) assert is_period_dtype(pidx) with tm.assert_produces_warning(FutureWarning): assert is_period(pidx) s = Series(pidx, name='A') assert is_period_dtype(s.dtype) assert is_period_dtype(s) with tm.assert_produces_warning(FutureWarning): assert is_period(s) assert not is_period_dtype(np.dtype('float64')) assert not is_period_dtype(1.0) with tm.assert_produces_warning(FutureWarning): assert not is_period(np.dtype('float64')) with tm.assert_produces_warning(FutureWarning): assert not is_period(1.0)
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 __sub__(self, other): other = lib.item_from_zerodim(other) if isinstance(other, (ABCSeries, ABCDataFrame)): return NotImplemented # scalar others elif other is NaT: result = self._sub_nat() elif isinstance(other, (Tick, timedelta, np.timedelta64)): result = self._add_delta(-other) elif isinstance(other, DateOffset): # specifically _not_ a Tick result = self._add_offset(-other) elif isinstance(other, (datetime, np.datetime64)): result = self._sub_datetimelike_scalar(other) elif lib.is_integer(other): # This check must come after the check for np.timedelta64 # as is_integer returns True for these if not is_period_dtype(self): maybe_integer_op_deprecated(self) result = self._time_shift(-other) elif isinstance(other, Period): result = self._sub_period(other) # array-like others elif is_timedelta64_dtype(other): # TimedeltaIndex, ndarray[timedelta64] result = self._add_delta(-other) elif is_offsetlike(other): # Array/Index of DateOffset objects result = self._addsub_offset_array(other, operator.sub) elif is_datetime64_dtype(other) or is_datetime64tz_dtype(other): # DatetimeIndex, ndarray[datetime64] result = self._sub_datetime_arraylike(other) elif is_period_dtype(other): # PeriodIndex result = self._sub_period_array(other) elif is_integer_dtype(other): if not is_period_dtype(self): maybe_integer_op_deprecated(self) result = self._addsub_int_array(other, operator.sub) elif isinstance(other, ABCIndexClass): raise TypeError("cannot subtract {cls} and {typ}" .format(cls=type(self).__name__, typ=type(other).__name__)) elif is_float_dtype(other): # Explicitly catch invalid dtypes raise TypeError("cannot subtract {dtype}-dtype from {cls}" .format(dtype=other.dtype, cls=type(self).__name__)) elif is_extension_array_dtype(other): # Categorical op will raise; defer explicitly return NotImplemented else: # pragma: no cover return NotImplemented if is_timedelta64_dtype(result) and isinstance(result, np.ndarray): from pandas.core.arrays import TimedeltaArrayMixin # TODO: infer freq? return TimedeltaArrayMixin(result) return result
def __init__( self, obj, orient: str | None, date_format: str, double_precision: int, ensure_ascii: bool, date_unit: str, index: bool, default_handler: Callable[[Any], JSONSerializable] | None = None, indent: int = 0, ): """ Adds a `schema` attribute with the Table Schema, resets the index (can't do in caller, because the schema inference needs to know what the index is, forces orient to records, and forces date_format to 'iso'. """ super().__init__( obj, orient, date_format, double_precision, ensure_ascii, date_unit, index, default_handler=default_handler, indent=indent, ) if date_format != "iso": msg = ( "Trying to write with `orient='table'` and " f"`date_format='{date_format}'`. Table Schema requires dates " "to be formatted with `date_format='iso'`") raise ValueError(msg) self.schema = build_table_schema(obj, index=self.index) # NotImplemented on a column MultiIndex if obj.ndim == 2 and isinstance(obj.columns, MultiIndex): raise NotImplementedError( "orient='table' is not supported for MultiIndex columns") # TODO: Do this timedelta properly in objToJSON.c See GH #15137 if ((obj.ndim == 1) and (obj.name in set(obj.index.names)) or len(obj.columns.intersection(obj.index.names))): msg = "Overlapping names between the index and columns" raise ValueError(msg) obj = obj.copy() timedeltas = obj.select_dtypes(include=["timedelta"]).columns if len(timedeltas): obj[timedeltas] = obj[timedeltas].applymap(lambda x: x.isoformat()) # Convert PeriodIndex to datetimes before serializing if is_period_dtype(obj.index.dtype): obj.index = obj.index.to_timestamp() # exclude index from obj if index=False if not self.index: self.obj = obj.reset_index(drop=True) else: self.obj = obj.reset_index(drop=False) self.date_format = "iso" self.orient = "records" self.index = index
def repeat(self, repeats, axis=None): nv.validate_repeat(tuple(), dict(axis=axis)) freq = self.freq if is_period_dtype(self) else None return self._shallow_copy(self.asi8.repeat(repeats), freq=freq)
def infer_freq(index, warn: bool = True) -> Optional[str]: """ Infer the most likely frequency given the input index. If the frequency is uncertain, a warning will be printed. Parameters ---------- index : DatetimeIndex or TimedeltaIndex If passed a Series will use the values of the series (NOT THE INDEX). warn : bool, default True Returns ------- str or None None if no discernible frequency. Raises ------ TypeError If the index is not datetime-like. ValueError If there are fewer than three values. """ import pandas as pd if isinstance(index, ABCSeries): values = index._values if not ( is_datetime64_dtype(values) or is_timedelta64_dtype(values) or values.dtype == object ): raise TypeError( "cannot infer freq from a non-convertible dtype " f"on a Series of {index.dtype}" ) index = values inferer: _FrequencyInferer if not hasattr(index, "dtype"): pass elif is_period_dtype(index.dtype): raise TypeError( "PeriodIndex given. Check the `freq` attribute " "instead of using infer_freq." ) elif is_timedelta64_dtype(index.dtype): # Allow TimedeltaIndex and TimedeltaArray inferer = _TimedeltaFrequencyInferer(index, warn=warn) return inferer.get_freq() if isinstance(index, pd.Index) and not isinstance(index, pd.DatetimeIndex): if isinstance(index, (pd.Int64Index, pd.Float64Index)): raise TypeError( f"cannot infer freq from a non-convertible index type {type(index)}" ) index = index._values if not isinstance(index, pd.DatetimeIndex): index = pd.DatetimeIndex(index) inferer = _FrequencyInferer(index, warn=warn) return inferer.get_freq()
def __sub__(self, other): from pandas import Index other = lib.item_from_zerodim(other) if isinstance(other, (ABCSeries, ABCDataFrame)): return NotImplemented # scalar others elif other is NaT: result = self._sub_nat() elif isinstance(other, (Tick, timedelta, np.timedelta64)): result = self._add_delta(-other) elif isinstance(other, DateOffset): # specifically _not_ a Tick result = self._add_offset(-other) elif isinstance(other, (datetime, np.datetime64)): result = self._sub_datelike(other) elif is_integer(other): # This check must come after the check for np.timedelta64 # as is_integer returns True for these result = self.shift(-other) elif isinstance(other, Period): result = self._sub_period(other) # array-like others elif is_timedelta64_dtype(other): # TimedeltaIndex, ndarray[timedelta64] result = self._add_delta(-other) elif is_offsetlike(other): # Array/Index of DateOffset objects result = self._addsub_offset_array(other, operator.sub) elif is_datetime64_dtype(other) or is_datetime64tz_dtype(other): # DatetimeIndex, ndarray[datetime64] result = self._sub_datelike(other) elif is_period_dtype(other): # PeriodIndex result = self._sub_period_array(other) elif is_integer_dtype(other): result = self._addsub_int_array(other, operator.sub) elif isinstance(other, Index): raise TypeError("cannot subtract {cls} and {typ}".format( cls=type(self).__name__, typ=type(other).__name__)) elif is_float_dtype(other): # Explicitly catch invalid dtypes raise TypeError( "cannot subtract {dtype}-dtype from {cls}".format( dtype=other.dtype, cls=type(self).__name__)) elif is_categorical_dtype(other): # Categorical op will raise; defer explicitly return NotImplemented else: # pragma: no cover return NotImplemented if result is NotImplemented: return NotImplemented elif not isinstance(result, Index): # Index.__new__ will choose appropriate subclass for dtype result = Index(result) res_name = ops.get_op_result_name(self, other) result.name = res_name return result