Skip to content

Commit 0bf83d6

Browse files
authored
BUG: series.interpolate(inplace=True) *actually* inplace (#44749)
1 parent d6dbe6f commit 0bf83d6

File tree

4 files changed

+46
-26
lines changed

4 files changed

+46
-26
lines changed

doc/source/whatsnew/v1.4.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,7 @@ Missing
701701
- Bug in :meth:`DataFrame.fillna` with limit and no method ignores axis='columns' or ``axis = 1`` (:issue:`40989`)
702702
- Bug in :meth:`DataFrame.fillna` not replacing missing values when using a dict-like ``value`` and duplicate column names (:issue:`43476`)
703703
- Bug in constructing a :class:`DataFrame` with a dictionary ``np.datetime64`` as a value and ``dtype='timedelta64[ns]'``, or vice-versa, incorrectly casting instead of raising (:issue:`??`)
704+
- Bug in :meth:`Series.interpolate` and :meth:`DataFrame.interpolate` with ``inplace=True`` not writing to the underlying array(s) in-place (:issue:`44749`)
704705
-
705706

706707
MultiIndex

pandas/core/internals/blocks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,7 +1079,7 @@ def interpolate(
10791079
data = self.values if inplace else self.values.copy()
10801080
data = cast(np.ndarray, data) # bc overridden by ExtensionBlock
10811081

1082-
interp_values = missing.interpolate_array_2d(
1082+
missing.interpolate_array_2d(
10831083
data,
10841084
method=method,
10851085
axis=axis,
@@ -1091,7 +1091,7 @@ def interpolate(
10911091
**kwargs,
10921092
)
10931093

1094-
nb = self.make_block_same_class(interp_values)
1094+
nb = self.make_block_same_class(data)
10951095
return nb._maybe_downcast([nb], downcast)
10961096

10971097
def take_nd(

pandas/core/missing.py

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,13 @@ def interpolate_array_2d(
217217
coerce: bool = False,
218218
downcast: str | None = None,
219219
**kwargs,
220-
) -> np.ndarray:
220+
) -> None:
221221
"""
222222
Wrapper to dispatch to either interpolate_2d or _interpolate_2d_with_fill.
223223
224-
Returned ndarray has same dtype as 'data'.
224+
Notes
225+
-----
226+
Alters 'data' in-place.
225227
"""
226228
try:
227229
m = clean_fill_method(method)
@@ -240,11 +242,10 @@ def interpolate_array_2d(
240242
limit=limit,
241243
limit_area=limit_area,
242244
)
243-
interp_values = data
244245
else:
245246
assert index is not None # for mypy
246247

247-
interp_values = _interpolate_2d_with_fill(
248+
_interpolate_2d_with_fill(
248249
data=data,
249250
index=index,
250251
axis=axis,
@@ -255,7 +256,7 @@ def interpolate_array_2d(
255256
fill_value=fill_value,
256257
**kwargs,
257258
)
258-
return interp_values
259+
return
259260

260261

261262
def _interpolate_2d_with_fill(
@@ -268,13 +269,15 @@ def _interpolate_2d_with_fill(
268269
limit_area: str | None = None,
269270
fill_value: Any | None = None,
270271
**kwargs,
271-
) -> np.ndarray:
272+
) -> None:
272273
"""
273274
Column-wise application of _interpolate_1d.
274275
275276
Notes
276277
-----
277-
The signature does differs from _interpolate_1d because it only
278+
Alters 'data' in-place.
279+
280+
The signature does differ from _interpolate_1d because it only
278281
includes what is needed for Block.interpolate.
279282
"""
280283
# validate the interp method
@@ -314,12 +317,10 @@ def _interpolate_2d_with_fill(
314317

315318
indices = _index_to_interp_indices(index, method)
316319

317-
def func(yvalues: np.ndarray) -> np.ndarray:
318-
# process 1-d slices in the axis direction, returning it
320+
def func(yvalues: np.ndarray) -> None:
321+
# process 1-d slices in the axis direction
319322

320-
# should the axis argument be handled below in apply_along_axis?
321-
# i.e. not an arg to _interpolate_1d
322-
return _interpolate_1d(
323+
_interpolate_1d(
323324
indices=indices,
324325
yvalues=yvalues,
325326
method=method,
@@ -332,7 +333,8 @@ def func(yvalues: np.ndarray) -> np.ndarray:
332333
)
333334

334335
# interp each column independently
335-
return np.apply_along_axis(func, axis, data)
336+
np.apply_along_axis(func, axis, data)
337+
return
336338

337339

338340
def _index_to_interp_indices(index: Index, method: str) -> np.ndarray:
@@ -370,23 +372,25 @@ def _interpolate_1d(
370372
**kwargs,
371373
):
372374
"""
373-
Logic for the 1-d interpolation. The result should be 1-d, inputs
375+
Logic for the 1-d interpolation. The input
374376
indices and yvalues will each be 1-d arrays of the same length.
375377
376378
Bounds_error is currently hardcoded to False since non-scipy ones don't
377379
take it as an argument.
380+
381+
Notes
382+
-----
383+
Fills 'yvalues' in-place.
378384
"""
379385

380386
invalid = isna(yvalues)
381387
valid = ~invalid
382388

383389
if not valid.any():
384-
result = np.empty(indices.shape, dtype=np.float64)
385-
result.fill(np.nan)
386-
return result
390+
return
387391

388392
if valid.all():
389-
return yvalues
393+
return
390394

391395
# These are sets of index pointers to invalid values... i.e. {0, 1, etc...
392396
all_nans = set(np.flatnonzero(invalid))
@@ -432,17 +436,15 @@ def _interpolate_1d(
432436
# sort preserve_nans and convert to list
433437
preserve_nans = sorted(preserve_nans)
434438

435-
result = yvalues.copy()
436-
437439
if method in NP_METHODS:
438440
# np.interp requires sorted X values, #21037
439441

440442
indexer = np.argsort(indices[valid])
441-
result[invalid] = np.interp(
443+
yvalues[invalid] = np.interp(
442444
indices[invalid], indices[valid][indexer], yvalues[valid][indexer]
443445
)
444446
else:
445-
result[invalid] = _interpolate_scipy_wrapper(
447+
yvalues[invalid] = _interpolate_scipy_wrapper(
446448
indices[valid],
447449
yvalues[valid],
448450
indices[invalid],
@@ -453,8 +455,8 @@ def _interpolate_1d(
453455
**kwargs,
454456
)
455457

456-
result[preserve_nans] = np.nan
457-
return result
458+
yvalues[preserve_nans] = np.nan
459+
return
458460

459461

460462
def _interpolate_scipy_wrapper(

pandas/tests/frame/methods/test_interpolate.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,23 @@
1212

1313

1414
class TestDataFrameInterpolate:
15+
def test_interpolate_inplace(self, frame_or_series, using_array_manager, request):
16+
# GH#44749
17+
if using_array_manager and frame_or_series is DataFrame:
18+
mark = pytest.mark.xfail(reason=".values-based in-place check is invalid")
19+
request.node.add_marker(mark)
20+
21+
obj = frame_or_series([1, np.nan, 2])
22+
orig = obj.values
23+
24+
obj.interpolate(inplace=True)
25+
expected = frame_or_series([1, 1.5, 2])
26+
tm.assert_equal(obj, expected)
27+
28+
# check we operated *actually* inplace
29+
assert np.shares_memory(orig, obj.values)
30+
assert orig.squeeze()[1] == 1.5
31+
1532
def test_interp_basic(self):
1633
df = DataFrame(
1734
{

0 commit comments

Comments
 (0)