diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index 92124a536fe26..fd298491d3816 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -446,6 +446,7 @@ Reshaping ^^^^^^^^^ - Bug in :func:`crosstab` when ``dropna=False`` would not keep ``np.nan`` in the result (:issue:`10772`) - Bug in :func:`merge_asof` raising ``KeyError`` for extension dtypes (:issue:`52904`) +- Bug in :func:`merge_asof` raising ``ValueError`` for data backed by read-only ndarrays (:issue:`53513`) - Bug in :meth:`DataFrame.agg` and :meth:`Series.agg` on non-unique columns would return incorrect type when dist-like argument passed in (:issue:`51099`) - Bug in :meth:`DataFrame.idxmin` and :meth:`DataFrame.idxmax`, where the axis dtype would be lost for empty frames (:issue:`53265`) - Bug in :meth:`DataFrame.merge` not merging correctly when having ``MultiIndex`` with single level (:issue:`52331`) diff --git a/pandas/_libs/join.pyi b/pandas/_libs/join.pyi index 11b65b859095f..7ee649a55fd8f 100644 --- a/pandas/_libs/join.pyi +++ b/pandas/_libs/join.pyi @@ -50,28 +50,28 @@ def outer_join_indexer( npt.NDArray[np.intp], ]: ... def asof_join_backward_on_X_by_Y( - left_values: np.ndarray, # asof_t[:] - right_values: np.ndarray, # asof_t[:] - left_by_values: np.ndarray, # by_t[:] - right_by_values: np.ndarray, # by_t[:] + left_values: np.ndarray, # ndarray[numeric_t] + right_values: np.ndarray, # ndarray[numeric_t] + left_by_values: np.ndarray, # ndarray[by_t] + right_by_values: np.ndarray, # ndarray[by_t] allow_exact_matches: bool = ..., tolerance: np.number | float | None = ..., use_hashtable: bool = ..., ) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: ... def asof_join_forward_on_X_by_Y( - left_values: np.ndarray, # asof_t[:] - right_values: np.ndarray, # asof_t[:] - left_by_values: np.ndarray, # by_t[:] - right_by_values: np.ndarray, # by_t[:] + left_values: np.ndarray, # ndarray[numeric_t] + right_values: np.ndarray, # ndarray[numeric_t] + left_by_values: np.ndarray, # ndarray[by_t] + right_by_values: np.ndarray, # ndarray[by_t] allow_exact_matches: bool = ..., tolerance: np.number | float | None = ..., use_hashtable: bool = ..., ) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: ... def asof_join_nearest_on_X_by_Y( - left_values: np.ndarray, # asof_t[:] - right_values: np.ndarray, # asof_t[:] - left_by_values: np.ndarray, # by_t[:] - right_by_values: np.ndarray, # by_t[:] + left_values: np.ndarray, # ndarray[numeric_t] + right_values: np.ndarray, # ndarray[numeric_t] + left_by_values: np.ndarray, # ndarray[by_t] + right_by_values: np.ndarray, # ndarray[by_t] allow_exact_matches: bool = ..., tolerance: np.number | float | None = ..., use_hashtable: bool = ..., diff --git a/pandas/_libs/join.pyx b/pandas/_libs/join.pyx index c49eaa3d619bd..164ed8a5c9227 100644 --- a/pandas/_libs/join.pyx +++ b/pandas/_libs/join.pyx @@ -679,10 +679,10 @@ ctypedef fused by_t: uint64_t -def asof_join_backward_on_X_by_Y(numeric_t[:] left_values, - numeric_t[:] right_values, - by_t[:] left_by_values, - by_t[:] right_by_values, +def asof_join_backward_on_X_by_Y(ndarray[numeric_t] left_values, + ndarray[numeric_t] right_values, + ndarray[by_t] left_by_values, + ndarray[by_t] right_by_values, bint allow_exact_matches=True, tolerance=None, bint use_hashtable=True): @@ -756,10 +756,10 @@ def asof_join_backward_on_X_by_Y(numeric_t[:] left_values, return left_indexer, right_indexer -def asof_join_forward_on_X_by_Y(numeric_t[:] left_values, - numeric_t[:] right_values, - by_t[:] left_by_values, - by_t[:] right_by_values, +def asof_join_forward_on_X_by_Y(ndarray[numeric_t] left_values, + ndarray[numeric_t] right_values, + ndarray[by_t] left_by_values, + ndarray[by_t] right_by_values, bint allow_exact_matches=1, tolerance=None, bint use_hashtable=True): diff --git a/pandas/core/reshape/merge.py b/pandas/core/reshape/merge.py index 65cd7e7983bfe..d057a1952289a 100644 --- a/pandas/core/reshape/merge.py +++ b/pandas/core/reshape/merge.py @@ -2142,13 +2142,13 @@ def injection(obj): # we've verified above that no nulls exist left_values = left_values._data elif isinstance(left_values, ExtensionArray): - left_values = np.array(left_values) + left_values = left_values.to_numpy() if isinstance(right_values, BaseMaskedArray): # we've verified above that no nulls exist right_values = right_values._data elif isinstance(right_values, ExtensionArray): - right_values = np.array(right_values) + right_values = right_values.to_numpy() # a "by" parameter requires special handling if self.left_by is not None: diff --git a/pandas/tests/reshape/merge/test_merge_asof.py b/pandas/tests/reshape/merge/test_merge_asof.py index d62dc44cda219..0678074943ffb 100644 --- a/pandas/tests/reshape/merge/test_merge_asof.py +++ b/pandas/tests/reshape/merge/test_merge_asof.py @@ -1627,3 +1627,15 @@ def test_merge_asof_extension_dtype(dtype): ) expected = expected.astype({"join_col": dtype}) tm.assert_frame_equal(result, expected) + + +def test_merge_asof_read_only_ndarray(): + # GH 53513 + left = pd.Series([2], index=[2], name="left") + right = pd.Series([1], index=[1], name="right") + # set to read-only + left.index.values.flags.writeable = False + right.index.values.flags.writeable = False + result = merge_asof(left, right, left_index=True, right_index=True) + expected = pd.DataFrame({"left": [2], "right": [1]}, index=[2]) + tm.assert_frame_equal(result, expected)