Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
287 changes: 272 additions & 15 deletions pandas/core/indexes/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@
NullFrequencyError,
)
from pandas.util._decorators import (
Appender,
cache_readonly,
doc,
)

from pandas.core.dtypes.common import (
Expand All @@ -57,12 +55,10 @@
PeriodArray,
TimedeltaArray,
)
from pandas.core.arrays.datetimelike import DatetimeLikeArrayMixin
import pandas.core.common as com
import pandas.core.indexes.base as ibase
from pandas.core.indexes.base import (
Index,
_index_shared_docs,
)
from pandas.core.indexes.extension import NDArrayBackedExtensionIndex
from pandas.core.indexes.range import RangeIndex
Expand Down Expand Up @@ -92,8 +88,51 @@ class DatetimeIndexOpsMixin(NDArrayBackedExtensionIndex, ABC):
_can_hold_strings = False
_data: DatetimeArray | TimedeltaArray | PeriodArray

@doc(DatetimeLikeArrayMixin.mean)
def mean(self, *, skipna: bool = True, axis: int | None = 0):
"""
Return the mean value of the Array.

Parameters
----------
skipna : bool, default True
Whether to ignore any NaT elements.
axis : int, optional, default 0
Axis for the function to be applied on.

Returns
-------
scalar
Timestamp or Timedelta.

See Also
--------
numpy.ndarray.mean : Returns the average of array elements along a given axis.
Series.mean : Return the mean value in a Series.

Notes
-----
mean is only defined for Datetime and Timedelta dtypes, not for Period.

Examples
--------
For :class:`pandas.DatetimeIndex`:

>>> idx = pd.date_range("2001-01-01 00:00", periods=3)
>>> idx
DatetimeIndex(['2001-01-01', '2001-01-02', '2001-01-03'],
dtype='datetime64[ns]', freq='D')
>>> idx.mean()
Timestamp('2001-01-02 00:00:00')

For :class:`pandas.TimedeltaIndex`:

>>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit="D")
>>> tdelta_idx
TimedeltaIndex(['1 days', '2 days', '3 days'],
dtype='timedelta64[ns]', freq=None)
>>> tdelta_idx.mean()
Timedelta('2 days 00:00:00')
"""
return self._data.mean(skipna=skipna, axis=axis)

@property
Expand Down Expand Up @@ -136,8 +175,37 @@ def asi8(self) -> npt.NDArray[np.int64]:
return self._data.asi8

@property
@doc(DatetimeLikeArrayMixin.freqstr)
def freqstr(self) -> str:
"""
Return the frequency object as a string if it's set, otherwise None.

See Also
--------
DatetimeIndex.inferred_freq : Returns a string representing a frequency
generated by infer_freq.

Examples
--------
For DatetimeIndex:

>>> idx = pd.DatetimeIndex(["1/1/2020 10:00:00+00:00"], freq="D")
>>> idx.freqstr
'D'

The frequency can be inferred if there are more than 2 points:

>>> idx = pd.DatetimeIndex(
... ["2018-01-01", "2018-01-03", "2018-01-05"], freq="infer"
... )
>>> idx.freqstr
'2D'

For PeriodIndex:

>>> idx = pd.PeriodIndex(["2023-1", "2023-2", "2023-3"], freq="M")
>>> idx.freqstr
'M'
"""
from pandas import PeriodIndex

if self._data.freqstr is not None and isinstance(
Expand All @@ -153,8 +221,10 @@ def freqstr(self) -> str:
def _resolution_obj(self) -> Resolution: ...

@cache_readonly
@doc(DatetimeLikeArrayMixin.resolution)
def resolution(self) -> str:
"""
Returns day, hour, minute, second, millisecond or microsecond
"""
return self._data.resolution

# ------------------------------------------------------------------------
Expand Down Expand Up @@ -199,8 +269,40 @@ def equals(self, other: Any) -> bool:

return np.array_equal(self.asi8, other.asi8)

@Appender(Index.__contains__.__doc__)
def __contains__(self, key: Any) -> bool:
"""
Return a boolean indicating whether the provided key is in the index.

Parameters
----------
key : label
The key to check if it is present in the index.

Returns
-------
bool
Whether the key search is in the index.

Raises
------
TypeError
If the key is not hashable.

See Also
--------
Index.isin : Returns an ndarray of boolean dtype indicating whether the
list-like key is in the index.

Examples
--------
>>> idx = pd.Index([1, 2, 3, 4])
>>> idx
Index([1, 2, 3, 4], dtype='int64')
>>> 2 in idx
True
>>> 6 in idx
False
"""
hash(key)
try:
self.get_loc(key)
Expand Down Expand Up @@ -243,8 +345,19 @@ def _format_attrs(self):
attrs.append(("freq", freq))
return attrs

@Appender(Index._summary.__doc__)
def _summary(self, name=None) -> str:
"""
Return a summarized representation.

Parameters
----------
name : str
name to use in the summary representation

Returns
-------
String with a summarized representation of the index
"""
result = super()._summary(name=name)
if self.freq:
result += f"\nFreq: {self.freqstr}"
Expand Down Expand Up @@ -405,8 +518,10 @@ def shift(self, periods: int = 1, freq=None) -> Self:

# --------------------------------------------------------------------

@doc(Index._maybe_cast_listlike_indexer)
def _maybe_cast_listlike_indexer(self, keyarr):
"""
Analogue to maybe_cast_indexer for get_indexer instead of get_loc.
"""
try:
res = self._data._validate_listlike(keyarr, allow_object=True)
except (ValueError, TypeError):
Expand Down Expand Up @@ -497,8 +612,32 @@ def values(self) -> np.ndarray:
data.flags.writeable = False
return data

@doc(DatetimeIndexOpsMixin.shift)
def shift(self, periods: int = 1, freq=None) -> Self:
"""
Shift index by desired number of time frequency increments.
This method is for shifting the values of datetime-like indexes
by a specified time increment a given number of times.

Parameters
----------
periods : int, default 1
Number of periods (or increments) to shift by,
can be positive or negative.
freq : pandas.DateOffset, pandas.Timedelta or string, optional
Frequency increment to shift by.
If None, the index is shifted by its own `freq` attribute.
Offset aliases are valid strings, e.g., 'D', 'W', 'M' etc.

Returns
-------
pandas.DatetimeIndex
Shifted index.

See Also
--------
Index.shift : Shift values of Index.
PeriodIndex.shift : Shift values of PeriodIndex.
"""
if freq is not None and freq != self.freq:
if isinstance(freq, str):
freq = to_offset(freq)
Expand All @@ -524,8 +663,38 @@ def shift(self, periods: int = 1, freq=None) -> Self:
return type(self)._simple_new(result, name=self.name)

@cache_readonly
@doc(DatetimeLikeArrayMixin.inferred_freq)
def inferred_freq(self) -> str | None:
"""
Return the inferred frequency of the index.

Returns
-------
str or None
A string representing a frequency generated by ``infer_freq``.
Returns ``None`` if the frequency cannot be inferred.

See Also
--------
DatetimeIndex.freqstr : Return the frequency object as a string if it's set,
otherwise ``None``.

Examples
--------
For ``DatetimeIndex``:

>>> idx = pd.DatetimeIndex(["2018-01-01", "2018-01-03", "2018-01-05"])
>>> idx.inferred_freq
'2D'

For ``TimedeltaIndex``:

>>> tdelta_idx = pd.to_timedelta(["0 days", "10 days", "20 days"])
>>> tdelta_idx
TimedeltaIndex(['0 days', '10 days', '20 days'],
dtype='timedelta64[ns]', freq=None)
>>> tdelta_idx.inferred_freq
'10D'
"""
return self._data.inferred_freq

# --------------------------------------------------------------------
Expand Down Expand Up @@ -816,14 +985,66 @@ def _get_insert_freq(self, loc: int, item):
freq = self.freq
return freq

@doc(NDArrayBackedExtensionIndex.delete)
def delete(self, loc) -> Self:
"""
Make new Index with passed location(-s) deleted.

Parameters
----------
loc : int or list of int
Location of item(-s) which will be deleted.
Use a list of locations to delete more than one value at the same time.

Returns
-------
Index
Will be same type as self, except for RangeIndex.

See Also
--------
numpy.delete : Delete any rows and column from NumPy array (ndarray).

Examples
--------
>>> idx = pd.Index(["a", "b", "c"])
>>> idx.delete(1)
Index(['a', 'c'], dtype='str')
>>> idx = pd.Index(["a", "b", "c"])
>>> idx.delete([0, 2])
Index(['b'], dtype='str')
"""
result = super().delete(loc)
result._data._freq = self._get_delete_freq(loc)
return result

@doc(NDArrayBackedExtensionIndex.insert)
def insert(self, loc: int, item):
"""
Make new Index inserting new item at location.
Follows Python numpy.insert semantics for negative values.

Parameters
----------
loc : int
The integer location where the new item will be inserted.
item : object
The new item to be inserted into the Index.

Returns
-------
Index
Returns a new Index object resulting from inserting the specified item at
the specified location within the original Index.

See Also
--------
Index.append : Append a collection of Indexes together.

Examples
--------
>>> idx = pd.Index(["a", "b", "c"])
>>> idx.insert(1, "x")
Index(['a', 'x', 'b', 'c'], dtype='str')
"""
result = super().insert(loc, item)
if isinstance(result, type(self)):
# i.e. parent class method did not cast
Expand All @@ -833,7 +1054,6 @@ def insert(self, loc: int, item):
# --------------------------------------------------------------------
# NDArray-Like Methods

@Appender(_index_shared_docs["take"] % _index_doc_kwargs)
def take(
self,
indices,
Expand All @@ -842,6 +1062,43 @@ def take(
fill_value=None,
**kwargs,
) -> Self:
"""
Return a new Index of the values selected by the indices.
For internal compatibility with numpy arrays.
Parameters
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This docstring needs spaces between sections

----------
indices : array-like
Indices to be taken.
axis : int, optional
The axis over which to select values, always 0.
allow_fill : bool, default True
How to handle negative values in `indices`.
* False: negative values in `indices` indicate positional indices
from the right (the default). This is similar to
:func:`numpy.take`.
* True: negative values in `indices` indicate
missing values. These values are set to `fill_value`. Any other
other negative values raise a ``ValueError``.
fill_value : scalar, default None
If allow_fill=True and fill_value is not None, indices specified by
-1 are regarded as NA. If Index doesn't hold NA, raise ValueError.
**kwargs
Required for compatibility with numpy.
Returns
-------
Index
An index formed of elements at the given indices. Will be the same
type as self, except for RangeIndex.
See Also
--------
numpy.ndarray.take: Return an array formed from the
elements of a at the given indices.
Examples
--------
>>> idx = pd.Index(["a", "b", "c"])
>>> idx.take([2, 2, 1, 2])
Index(['c', 'c', 'b', 'c'], dtype='str')
"""
nv.validate_take((), kwargs)
indices = np.asarray(indices, dtype=np.intp)

Expand Down
Loading
Loading