Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions doc/source/whatsnew/v3.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,8 @@ Indexing
- Bug in reindexing of :class:`DataFrame` with :class:`PeriodDtype` columns in case of consolidated block (:issue:`60980`, :issue:`60273`)
- Bug in :meth:`DataFrame.loc.__getitem__` and :meth:`DataFrame.iloc.__getitem__` with a :class:`CategoricalDtype` column with integer categories raising when trying to index a row containing a ``NaN`` entry (:issue:`58954`)
- Bug in :meth:`Index.__getitem__` incorrectly raising with a 0-dim ``np.ndarray`` key (:issue:`55601`)
- Bug in indexing on a :class:`DatetimeIndex` with a ``timestamp[pyarrow]`` dtype or on a :class:`TimedeltaIndex` with a ``duration[pyarrow]`` dtype (:issue:`62277`)
-

Missing
^^^^^^^
Expand Down Expand Up @@ -1114,6 +1116,7 @@ Other
- Bug in :meth:`Series.diff` allowing non-integer values for the ``periods`` argument. (:issue:`56607`)
- Bug in :meth:`Series.dt` methods in :class:`ArrowDtype` that were returning incorrect values. (:issue:`57355`)
- Bug in :meth:`Series.isin` raising ``TypeError`` when series is large (>10**6) and ``values`` contains NA (:issue:`60678`)
- Bug in :meth:`Series.map` with a ``timestamp[pyarrow]`` dtype or ``duration[pyarrow]`` dtype incorrectly returning all-``NaN`` entries (:issue:`61231`)
- Bug in :meth:`Series.mode` where an exception was raised when taking the mode with nullable types with no null values in the series. (:issue:`58926`)
- Bug in :meth:`Series.rank` that doesn't preserve missing values for nullable integers when ``na_option='keep'``. (:issue:`56976`)
- Bug in :meth:`Series.replace` and :meth:`DataFrame.replace` throwing ``ValueError`` when ``regex=True`` and all NA values. (:issue:`60688`)
Expand All @@ -1128,6 +1131,7 @@ Other
- Bug in printing a :class:`Series` with a :class:`DataFrame` stored in :attr:`Series.attrs` raised a ``ValueError`` (:issue:`60568`)
- Fixed bug where the :class:`DataFrame` constructor misclassified array-like objects with a ``.name`` attribute as :class:`Series` or :class:`Index` (:issue:`61443`)
- Fixed regression in :meth:`DataFrame.from_records` not initializing subclasses properly (:issue:`57008`)
-

.. ***DO NOT USE THIS SECTION***

Expand Down
4 changes: 4 additions & 0 deletions pandas/core/arrays/arrow/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -1614,6 +1614,10 @@ def map(self, mapper, na_action: Literal["ignore"] | None = None):
if is_numeric_dtype(self.dtype):
return map_array(self.to_numpy(), mapper, na_action=na_action)
else:
# For "mM" cases, the super() method passes `self` without the
# to_numpy call, which inside map_array casts to ndarray[object].
# Without the to_numpy() call, NA is preserved instead of changed
# to None.
return super().map(mapper, na_action)

@doc(ExtensionArray.duplicated)
Expand Down
16 changes: 15 additions & 1 deletion pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@
)

from pandas.core.dtypes.common import is_scalar
from pandas.core.dtypes.dtypes import DatetimeTZDtype
from pandas.core.dtypes.dtypes import (
ArrowDtype,
DatetimeTZDtype,
)
from pandas.core.dtypes.generic import ABCSeries
from pandas.core.dtypes.missing import is_valid_na_for_dtype

Expand Down Expand Up @@ -384,6 +387,17 @@ def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
"""
Can we compare values of the given dtype to our own?
"""
if isinstance(dtype, ArrowDtype):
# GH#62277
import pyarrow as pa

pa_dtype = dtype.pyarrow_dtype
if not pa.types.is_timestamp(pa_dtype):
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if not pa.types.is_timestamp(pa_dtype):
if dtype.kind != "M":

Would allow you to avoid the pyarrow import

Copy link
Member Author

Choose a reason for hiding this comment

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

good call, will update

return False
if (pa_dtype.tz is None) ^ (self.tz is None):
return False
return True

if self.tz is not None:
# If we have tz, we can compare to tzaware
return isinstance(dtype, DatetimeTZDtype)
Expand Down
5 changes: 5 additions & 0 deletions pandas/core/indexes/timedeltas.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
is_scalar,
pandas_dtype,
)
from pandas.core.dtypes.dtypes import ArrowDtype
from pandas.core.dtypes.generic import ABCSeries

from pandas.core.arrays.timedeltas import TimedeltaArray
Expand Down Expand Up @@ -194,6 +195,10 @@ def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
"""
Can we compare values of the given dtype to our own?
"""
if isinstance(dtype, ArrowDtype):
import pyarrow as pa

return pa.types.is_duration(dtype.pyarrow_dtype)
Copy link
Member

Choose a reason for hiding this comment

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

Similar comment here

return lib.is_np_dtype(dtype, "m") # aka self._data._is_recognized_dtype

# -------------------------------------------------------------------
Expand Down
21 changes: 21 additions & 0 deletions pandas/tests/indexes/datetimes/test_indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from pandas._libs import index as libindex
from pandas.compat.numpy import np_long
import pandas.util._test_decorators as td

import pandas as pd
from pandas import (
Expand Down Expand Up @@ -513,6 +514,26 @@ def test_contains_nonunique(self, vals):


class TestGetIndexer:
@td.skip_if_no("pyarrow")
@pytest.mark.parametrize("as_td", [True, False])
def test_get_indexer_pyarrow(self, as_td):
# GH#62277
index = date_range("2016-01-01", periods=3)
target = index.astype("timestamp[ns][pyarrow]")[::-1]
if as_td:
# Test duration dtypes while we're here
index = index - index[0]
target = target - target[-1]

result = index.get_indexer(target)

expected = np.array([2, 1, 0], dtype=np.intp)
tm.assert_numpy_array_equal(result, expected)

# Reversed op should work the same
result2 = target.get_indexer(index)
tm.assert_numpy_array_equal(result2, expected)

def test_get_indexer_date_objs(self):
rng = date_range("1/1/2000", periods=20)

Expand Down
24 changes: 24 additions & 0 deletions pandas/tests/series/methods/test_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import pytest

from pandas.errors import Pandas4Warning
import pandas.util._test_decorators as td

import pandas as pd
from pandas import (
Expand Down Expand Up @@ -653,3 +654,26 @@ def test_map_engine_not_executor():

with pytest.raises(ValueError, match="Not a valid engine: 'something'"):
s.map(lambda x: x, engine="something")


@td.skip_if_no("pyarrow")
@pytest.mark.parametrize("as_td", [True, False])
def test_map_pyarrow_timestamp(as_td):
# GH#61231
dti = date_range("2018-01-01 00:00:00", "2018-01-07 00:00:00")
ser = Series(dti, dtype="timestamp[ns][pyarrow]", name="a")
if as_td:
# duration dtype
ser = ser - ser[0]

mapper = {date: i for i, date in enumerate(ser)}

res_series = ser.map(mapper)
expected = Series(range(len(ser)), name="a", dtype="int64")
tm.assert_series_equal(res_series, expected)

res_index = Index(ser).map(mapper)
# For now (as of 2025-09-06) at least, we do inference on Index.map that
# we don't for Series.map
expected_index = Index(expected).astype("int64[pyarrow]")
tm.assert_index_equal(res_index, expected_index)
Loading