Skip to content

Commit 6fe32fb

Browse files
jbrockmendelvladu
authored andcommitted
REF: move shift logic from BlockManager to DataFrame (pandas-dev#40536)
1 parent 44f3831 commit 6fe32fb

File tree

3 files changed

+35
-44
lines changed

3 files changed

+35
-44
lines changed

pandas/core/frame.py

+34-17
Original file line numberDiff line numberDiff line change
@@ -5080,28 +5080,45 @@ def shift(
50805080
axis = self._get_axis_number(axis)
50815081

50825082
ncols = len(self.columns)
5083-
if axis == 1 and periods != 0 and fill_value is lib.no_default and ncols > 0:
5084-
# We will infer fill_value to match the closest column
50855083

5086-
# Use a column that we know is valid for our column's dtype GH#38434
5087-
label = self.columns[0]
5084+
if (
5085+
axis == 1
5086+
and periods != 0
5087+
and ncols > 0
5088+
and (fill_value is lib.no_default or len(self._mgr.arrays) > 1)
5089+
):
5090+
# Exclude single-array-with-fill_value case so we issue a FutureWarning
5091+
# if an integer is passed with datetimelike dtype GH#31971
5092+
from pandas import concat
50885093

5094+
# tail: the data that is still in our shifted DataFrame
50895095
if periods > 0:
5090-
result = self.iloc[:, :-periods]
5091-
for col in range(min(ncols, abs(periods))):
5092-
# TODO(EA2D): doing this in a loop unnecessary with 2D EAs
5093-
# Define filler inside loop so we get a copy
5094-
filler = self.iloc[:, 0].shift(len(self))
5095-
result.insert(0, label, filler, allow_duplicates=True)
5096+
tail = self.iloc[:, :-periods]
50965097
else:
5097-
result = self.iloc[:, -periods:]
5098-
for col in range(min(ncols, abs(periods))):
5099-
# Define filler inside loop so we get a copy
5100-
filler = self.iloc[:, -1].shift(len(self))
5101-
result.insert(
5102-
len(result.columns), label, filler, allow_duplicates=True
5103-
)
5098+
tail = self.iloc[:, -periods:]
5099+
# pin a simple Index to avoid costly casting
5100+
tail.columns = range(len(tail.columns))
5101+
5102+
if fill_value is not lib.no_default:
5103+
# GH#35488
5104+
# TODO(EA2D): with 2D EAs we could construct other directly
5105+
ser = Series(fill_value, index=self.index)
5106+
else:
5107+
# We infer fill_value to match the closest column
5108+
if periods > 0:
5109+
ser = self.iloc[:, 0].shift(len(self))
5110+
else:
5111+
ser = self.iloc[:, -1].shift(len(self))
5112+
5113+
width = min(abs(periods), ncols)
5114+
other = concat([ser] * width, axis=1)
5115+
5116+
if periods > 0:
5117+
result = concat([other, tail], axis=1)
5118+
else:
5119+
result = concat([tail, other], axis=1)
51045120

5121+
result = cast(DataFrame, result)
51055122
result.columns = self.columns.copy()
51065123
return result
51075124

pandas/core/internals/managers.py

-19
Original file line numberDiff line numberDiff line change
@@ -620,25 +620,6 @@ def shift(self, periods: int, axis: int, fill_value) -> BlockManager:
620620
if fill_value is lib.no_default:
621621
fill_value = None
622622

623-
if axis == 0 and self.ndim == 2 and self.nblocks > 1:
624-
# GH#35488 we need to watch out for multi-block cases
625-
# We only get here with fill_value not-lib.no_default
626-
ncols = self.shape[0]
627-
if periods > 0:
628-
indexer = [-1] * periods + list(range(ncols - periods))
629-
else:
630-
nper = abs(periods)
631-
indexer = list(range(nper, ncols)) + [-1] * nper
632-
result = self.reindex_indexer(
633-
self.items,
634-
indexer,
635-
axis=0,
636-
fill_value=fill_value,
637-
allow_dups=True,
638-
consolidate=False,
639-
)
640-
return result
641-
642623
return self.apply("shift", periods=periods, axis=axis, fill_value=fill_value)
643624

644625
def fillna(self, value, limit, inplace: bool, downcast) -> BlockManager:

pandas/tests/apply/test_frame_transform.py

+1-8
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,8 @@ def test_transform_ufunc(axis, float_frame, frame_or_series):
3939

4040

4141
@pytest.mark.parametrize("op", frame_transform_kernels)
42-
def test_transform_groupby_kernel(axis, float_frame, op, using_array_manager, request):
42+
def test_transform_groupby_kernel(axis, float_frame, op, request):
4343
# GH 35964
44-
if using_array_manager and op == "pct_change" and axis in (1, "columns"):
45-
# TODO(ArrayManager) shift with axis=1
46-
request.node.add_marker(
47-
pytest.mark.xfail(
48-
reason="shift axis=1 not yet implemented for ArrayManager"
49-
)
50-
)
5144

5245
args = [0.0] if op == "fillna" else []
5346
if axis == 0 or axis == "index":

0 commit comments

Comments
 (0)