Skip to content

Commit c608a38

Browse files
authored
BUG: df.shift(n, axis=1) with multiple blocks (#35578)
1 parent 63a48bb commit c608a38

File tree

3 files changed

+51
-2
lines changed

3 files changed

+51
-2
lines changed

doc/source/whatsnew/v1.1.1.rst

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Fixed regressions
1717

1818
- Fixed regression where :func:`read_csv` would raise a ``ValueError`` when ``pandas.options.mode.use_inf_as_na`` was set to ``True`` (:issue:`35493`).
1919
- Fixed regression in :class:`pandas.core.groupby.RollingGroupby` where column selection was ignored (:issue:`35486`)
20+
- Fixed regression in :meth:`DataFrame.shift` with ``axis=1`` and heterogeneous dtypes (:issue:`35488`)
2021

2122
.. ---------------------------------------------------------------------------
2223

pandas/core/internals/managers.py

+23-2
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,24 @@ def interpolate(self, **kwargs) -> "BlockManager":
551551
return self.apply("interpolate", **kwargs)
552552

553553
def shift(self, periods: int, axis: int, fill_value) -> "BlockManager":
554+
if axis == 0 and self.ndim == 2 and self.nblocks > 1:
555+
# GH#35488 we need to watch out for multi-block cases
556+
ncols = self.shape[0]
557+
if periods > 0:
558+
indexer = [-1] * periods + list(range(ncols - periods))
559+
else:
560+
nper = abs(periods)
561+
indexer = list(range(nper, ncols)) + [-1] * nper
562+
result = self.reindex_indexer(
563+
self.items,
564+
indexer,
565+
axis=0,
566+
fill_value=fill_value,
567+
allow_dups=True,
568+
consolidate=False,
569+
)
570+
return result
571+
554572
return self.apply("shift", periods=periods, axis=axis, fill_value=fill_value)
555573

556574
def fillna(self, value, limit, inplace: bool, downcast) -> "BlockManager":
@@ -1213,6 +1231,7 @@ def reindex_indexer(
12131231
fill_value=None,
12141232
allow_dups: bool = False,
12151233
copy: bool = True,
1234+
consolidate: bool = True,
12161235
) -> T:
12171236
"""
12181237
Parameters
@@ -1223,7 +1242,8 @@ def reindex_indexer(
12231242
fill_value : object, default None
12241243
allow_dups : bool, default False
12251244
copy : bool, default True
1226-
1245+
consolidate: bool, default True
1246+
Whether to consolidate inplace before reindexing.
12271247
12281248
pandas-indexer with -1's only.
12291249
"""
@@ -1236,7 +1256,8 @@ def reindex_indexer(
12361256
result.axes[axis] = new_axis
12371257
return result
12381258

1239-
self._consolidate_inplace()
1259+
if consolidate:
1260+
self._consolidate_inplace()
12401261

12411262
# some axes don't allow reindexing with dups
12421263
if not allow_dups:

pandas/tests/frame/methods/test_shift.py

+27
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,33 @@ def test_shift_duplicate_columns(self):
145145
tm.assert_frame_equal(shifted[0], shifted[1])
146146
tm.assert_frame_equal(shifted[0], shifted[2])
147147

148+
def test_shift_axis1_multiple_blocks(self):
149+
# GH#35488
150+
df1 = pd.DataFrame(np.random.randint(1000, size=(5, 3)))
151+
df2 = pd.DataFrame(np.random.randint(1000, size=(5, 2)))
152+
df3 = pd.concat([df1, df2], axis=1)
153+
assert len(df3._mgr.blocks) == 2
154+
155+
result = df3.shift(2, axis=1)
156+
157+
expected = df3.take([-1, -1, 0, 1, 2], axis=1)
158+
expected.iloc[:, :2] = np.nan
159+
expected.columns = df3.columns
160+
161+
tm.assert_frame_equal(result, expected)
162+
163+
# Case with periods < 0
164+
# rebuild df3 because `take` call above consolidated
165+
df3 = pd.concat([df1, df2], axis=1)
166+
assert len(df3._mgr.blocks) == 2
167+
result = df3.shift(-2, axis=1)
168+
169+
expected = df3.take([2, 3, 4, -1, -1], axis=1)
170+
expected.iloc[:, -2:] = np.nan
171+
expected.columns = df3.columns
172+
173+
tm.assert_frame_equal(result, expected)
174+
148175
@pytest.mark.filterwarnings("ignore:tshift is deprecated:FutureWarning")
149176
def test_tshift(self, datetime_frame):
150177
# TODO: remove this test when tshift deprecation is enforced

0 commit comments

Comments
 (0)