Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dt accessor to Index #17204

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion pandas/core/indexes/accessors.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
is_timedelta64_dtype, is_categorical_dtype,
is_list_like)


from pandas.core.base import PandasDelegate, NoNewAttributesMixin
from pandas.core.indexes.datetimes import DatetimeIndex
from pandas._libs.period import IncompatibleFrequency # noqa
Expand Down Expand Up @@ -48,8 +49,10 @@ def maybe_to_datetimelike(data, copy=False):
DelegatedClass

"""
from pandas import Series
from pandas import Series, Index

if isinstance(data, Index):
data = data.to_series()
if not isinstance(data, Series):
raise TypeError("cannot convert an object of type {0} to a "
"datetimelike index".format(type(data)))
Expand Down
7 changes: 7 additions & 0 deletions pandas/core/indexes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1592,6 +1592,13 @@ def is_all_dates(self):
return False
return is_datetime_array(_ensure_object(self.values))

@property
def dt(self):
# Non-raising versions of the `.dt` attribute are available in
# DatetimeIndex, PeriodIndex, and TimedeltaIndex.
raise AttributeError("Can only use .dt accessor with datetimelike "
"values")

def __iter__(self):
return iter(self.values)

Expand Down
15 changes: 15 additions & 0 deletions pandas/core/indexes/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,21 @@ def ceil(self, freq):
class DatetimeIndexOpsMixin(object):
""" common ops mixin to support a unified inteface datetimelike Index """

@cache_readonly
def dt(self):
"""
For a datetime-like Index object, `self.dt` returns `self` so that
datetime-like attributes can be accessed symmetrically for Index
and Series objects.

For Index objects that are not datetime-like, `self.dt` will raise
and AttributeError.
"""
# Note: we use a `cache_readonly` instead of AccessorProperty
# to avoid circular imports.
from pandas.core.indexes import accessors
return accessors.CombinedDatetimelikeProperties._make_accessor(self)

def equals(self, other):
"""
Determines if two Index objects contain the same elements.
Expand Down
41 changes: 41 additions & 0 deletions pandas/tests/indexes/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1444,6 +1444,47 @@ def test_join_self(self):
joined = res.join(res, how=kind)
assert res is joined

def test_dt_accessor(self):
# index.dt should raise AttributeError for all index classes other
# than DatetimeIndex, PeriodIndex, and TimedeltaIndex
no_dt_examples = [pd.Index([np.nan]),
pd.Index([np.nan, 1]),
pd.Index([1, 2, np.nan]),
pd.Index(['foo', 'bar']),
pd.RangeIndex(12),
pd.CategoricalIndex(['a', 'b', np.nan])]

dr = pd.date_range('1994-10-06', periods=4, freq='D')
mi = pd.MultiIndex.from_product([[1, 2], ['foo', 'bar'], dr])
no_dt_examples.append(mi)
mi2 = pd.MultiIndex.from_product([range(3), dr])
no_dt_examples.append(mi2)

for index in no_dt_examples:
# Note: in py2, `hasattr` will return False when attempting
# to access the attribute raises.
assert not hasattr(index, 'dt')

dt_examples = [dr,
pd.to_datetime(['NaT']),
pd.to_datetime(['NaT', '2000-01-01']),
pd.to_datetime(['2000-01-01', 'NaT', '2000-01-02']),
pd.to_timedelta(['1 day', 'NaT'])
]

for index in dt_examples:
assert hasattr(index, 'dt')
# Note: index.dt.day returns a Series that has index as its
# index, whereas index.day is just a index containing the day
# values.
dt = index.dt
if isinstance(index, pd.TimedeltaIndex):
tm.assert_almost_equal(dt.days.values, index.days.values)
tm.assert_almost_equal(dt.seconds.values, index.seconds.values)
else:
tm.assert_almost_equal(dt.day.values, index.day.values)
tm.assert_almost_equal(dt.year.values, index.year.values)

def test_str_attribute(self):
# GH9068
methods = ['strip', 'rstrip', 'lstrip']
Expand Down