pandas-dev/pandas · #66117

BUG: interpolate leaving NAs unfilled for pyarrow dtypes

apoorvdarshan · merged Sep 16, 20263 files · 25 + / 13
doc/source/whatsnew/v3.0.6.rst1 + / 0
@@ -20,6 +20,7 @@ Fixed regressions - Bug in full-slice assignment (``arr[:] = value``) into a PyArrow-backed array where the result shared memory with ``value``, so later modifying ``value`` also changed the assigned-to object (:issue:`67990`) - Fixed bug in :func:`read_csv` with the ``c`` engine and ``low_memory=True`` where a mixed-dtype column selected by ``usecols`` raised ``IndexError`` instead of emitting a :class:`DtypeWarning` unless it was the first column in the file, and where that warning named the wrong column when the file had an index column (:issue:`67375`) - Fixed bug in :func:`read_csv` with the ``c`` engine and ``low_memory=True`` where the number reported next to each column name in the mixed-type :class:`DtypeWarning` was a running count of the warned columns rather than the column's position in the file (:issue:`67375`)+- Regression in :meth:`Series.interpolate` with ``method="linear"`` and a pyarrow-backed dtype leaving consecutive and trailing missing values unfilled, and truncating interpolated values for integer dtypes; the result now matches the equivalent masked (e.g. ``Int64``) dtype, including the upcast to ``float64[pyarrow]`` (:issue:`65345`)  .. --------------------------------------------------------------------------- .. _whatsnew_306.bug_fixes:
pandas/core/arrays/arrow/array.py5 + / 12
@@ -3227,18 +3227,11 @@ def interpolate(         if not self.dtype._is_numeric:             raise TypeError(f"Cannot interpolate with {self.dtype} dtype") -        if (-            method == "linear"-            and limit_area is None-            and limit is None-            and limit_direction == "forward"-        ):-            values = self._pa_array.combine_chunks()-            na_value = pa.array([None], type=values.type)-            y_diff_2 = pc.fill_null_backward(pc.pairwise_diff_checked(values, period=2))-            prev_values = pa.concat_arrays([na_value, values[:-2], na_value])-            interps = pc.add_checked(prev_values, pc.divide_checked(y_diff_2, 2))-            return self._from_pyarrow_array(pc.coalesce(self._pa_array, interps))+        # GH#65345: a pyarrow-native fast path for+        # method="linear"/limit_direction="forward" was removed here because+        # it only handled isolated NAs (leaving consecutive and trailing NAs+        # unfilled), truncated interpolated values for integer dtypes, and+        # did not upcast to float64 like the general path below.          mask = self.isna()         if self.dtype.kind == "f":
pandas/tests/extension/test_arrow.py19 + / 1
@@ -5086,9 +5086,27 @@ def test_interpolate_not_numeric(data):  @pytest.mark.parametrize("dtype", ["int64[pyarrow]", "float64[pyarrow]"]) def test_interpolate_linear(dtype):+    # GH#65345 results should match the masked (e.g. Int64) dtypes:+    # upcast to float, and fill the trailing NA going forward     ser = pd.Series([None, 1, 2, None, 4, None], dtype=dtype)     result = ser.interpolate()-    expected = pd.Series([None, 1, 2, 3, 4, None], dtype=dtype)+    expected = pd.Series([None, 1.0, 2.0, 3.0, 4.0, 4.0], dtype="float64[pyarrow]")+    tm.assert_series_equal(result, expected)+++def test_interpolate_linear_consecutive_na():+    # GH#65345 consecutive interior NAs were left unfilled+    ser = pd.Series([1, 2, 3, None, None, 6, 7], dtype="int64[pyarrow]")+    result = ser.interpolate(method="linear", limit_direction="forward")+    expected = pd.Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0], dtype="float64[pyarrow]")+    tm.assert_series_equal(result, expected)+++def test_interpolate_linear_int_fractional():+    # GH#65345 result should not truncate the interpolated value (1 instead of 1.5)+    ser = pd.Series([1, None, 2], dtype="int64[pyarrow]")+    result = ser.interpolate(method="linear")+    expected = pd.Series([1.0, 1.5, 2.0], dtype="float64[pyarrow]")     tm.assert_series_equal(result, expected)