pandas-dev/pandas · #66117
BUG: interpolate leaving NAs unfilled for pyarrow dtypes
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)