Skip to content

Commit 8b503a8

Browse files
authored
API: stop silent conversion of object-Index to DatetimeIndex (pandas-dev#49169)
* API: stop silent conversion of object-Index to DatetimeIndex * GH refs * fix pytables roundtrip
1 parent 4d41ccc commit 8b503a8

File tree

5 files changed

+11
-35
lines changed

5 files changed

+11
-35
lines changed

doc/source/whatsnew/v2.0.0.rst

+2
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ Other API changes
127127
- Passing ``dtype`` of "timedelta64[s]", "timedelta64[ms]", or "timedelta64[us]" to :class:`TimedeltaIndex`, :class:`Series`, or :class:`DataFrame` constructors will now retain that dtype instead of casting to "timedelta64[ns]"; passing a dtype with lower resolution for :class:`Series` or :class:`DataFrame` will be cast to the lowest supported resolution "timedelta64[s]" (:issue:`49014`)
128128
- Passing a ``np.datetime64`` object with non-nanosecond resolution to :class:`Timestamp` will retain the input resolution if it is "s", "ms", or "ns"; otherwise it will be cast to the closest supported resolution (:issue:`49008`)
129129
- The ``other`` argument in :meth:`DataFrame.mask` and :meth:`Series.mask` now defaults to ``no_default`` instead of ``np.nan`` consistent with :meth:`DataFrame.where` and :meth:`Series.where`. Entries will be filled with the corresponding NULL value (``np.nan`` for numpy dtypes, ``pd.NA`` for extension dtypes). (:issue:`49111`)
130+
- When creating a :class:`Series` with a object-dtype :class:`Index` of datetime objects, pandas no longer silently converts the index to a :class:`DatetimeIndex` (:issue:`39307`, :issue:`23598`)
131+
-
130132

131133
.. ---------------------------------------------------------------------------
132134
.. _whatsnew_200.deprecations:

pandas/core/generic.py

+5
Original file line numberDiff line numberDiff line change
@@ -825,7 +825,12 @@ def _set_axis_nocheck(self, labels, axis: Axis, inplace: bool_t, copy: bool_t):
825825
setattr(obj, obj._get_axis_name(axis), labels)
826826
return obj
827827

828+
@final
828829
def _set_axis(self, axis: AxisInt, labels: AnyArrayLike | list) -> None:
830+
"""
831+
This is called from the cython code when we set the `index` attribute
832+
directly, e.g. `series.index = [1, 2, 3]`.
833+
"""
829834
labels = ensure_index(labels)
830835
self._mgr.set_axis(axis, labels)
831836
self._clear_item_cache()

pandas/core/series.py

-33
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
lib,
3030
properties,
3131
reshape,
32-
tslibs,
3332
)
3433
from pandas._libs.lib import no_default
3534
from pandas._typing import (
@@ -132,13 +131,11 @@
132131
)
133132
from pandas.core.indexes.accessors import CombinedDatetimelikeProperties
134133
from pandas.core.indexes.api import (
135-
CategoricalIndex,
136134
DatetimeIndex,
137135
Float64Index,
138136
Index,
139137
MultiIndex,
140138
PeriodIndex,
141-
TimedeltaIndex,
142139
default_index,
143140
ensure_index,
144141
)
@@ -570,36 +567,6 @@ def _constructor_expanddim(self) -> Callable[..., DataFrame]:
570567
def _can_hold_na(self) -> bool:
571568
return self._mgr._can_hold_na
572569

573-
def _set_axis(self, axis: AxisInt, labels: AnyArrayLike | list) -> None:
574-
"""
575-
Override generic, we want to set the _typ here.
576-
577-
This is called from the cython code when we set the `index` attribute
578-
directly, e.g. `series.index = [1, 2, 3]`.
579-
"""
580-
labels = ensure_index(labels)
581-
582-
if labels._is_all_dates and not (
583-
type(labels) is Index and not isinstance(labels.dtype, np.dtype)
584-
):
585-
# exclude e.g. timestamp[ns][pyarrow] dtype from this casting
586-
deep_labels = labels
587-
if isinstance(labels, CategoricalIndex):
588-
deep_labels = labels.categories
589-
590-
if not isinstance(
591-
deep_labels, (DatetimeIndex, PeriodIndex, TimedeltaIndex)
592-
):
593-
try:
594-
labels = DatetimeIndex(labels)
595-
except (tslibs.OutOfBoundsDatetime, ValueError):
596-
# labels may exceeds datetime bounds,
597-
# or not be a DatetimeIndex
598-
pass
599-
600-
# The ensure_index call above ensures we have an Index object
601-
self._mgr.set_axis(axis, labels)
602-
603570
# ndarray compatibility
604571
@property
605572
def dtype(self) -> DtypeObj:

pandas/io/pytables.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3006,7 +3006,7 @@ def read_index_node(
30063006
attrs = node._v_attrs
30073007
factory, kwargs = self._get_index_factory(attrs)
30083008

3009-
if kind == "date":
3009+
if kind == "date" or kind == "object":
30103010
index = factory(
30113011
_unconvert_index(
30123012
data, kind, encoding=self.encoding, errors=self.errors

pandas/tests/series/test_constructors.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1986,7 +1986,9 @@ def test_series_constructor_datetimelike_index_coercion(self):
19861986
ser = Series(np.random.randn(len(idx)), idx.astype(object))
19871987
with tm.assert_produces_warning(FutureWarning):
19881988
assert ser.index.is_all_dates
1989-
assert isinstance(ser.index, DatetimeIndex)
1989+
# as of 2.0, we no longer silently cast the object-dtype index
1990+
# to DatetimeIndex GH#39307, GH#23598
1991+
assert not isinstance(ser.index, DatetimeIndex)
19901992

19911993
def test_series_constructor_infer_multiindex(self):
19921994
index_lists = [["a", "a", "b", "b"], ["x", "y", "x", "y"]]

0 commit comments

Comments
 (0)