Skip to content

Commit 9f58ebb

Browse files
ENH #60693: shift operations for Series and DataFrames
This commit introduces the rshift and lshift method for both Series and DataFrames in pandas. It also adds the corresponding in-place methods. These methdos don't work between a Series and a Dataframe, or if the two Series or DataFrames differ in size. Co-authored-by: Tomas Macieira <[email protected]>
1 parent 8045c2d commit 9f58ebb

File tree

11 files changed

+328
-0
lines changed

11 files changed

+328
-0
lines changed

doc/source/whatsnew/v3.0.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ Other enhancements
7979
- Add ``"delete_rows"`` option to ``if_exists`` argument in :meth:`DataFrame.to_sql` deleting all records of the table before inserting data (:issue:`37210`).
8080
- Added half-year offset classes :class:`HalfYearBegin`, :class:`HalfYearEnd`, :class:`BHalfYearBegin` and :class:`BHalfYearEnd` (:issue:`60928`)
8181
- Errors occurring during SQL I/O will now throw a generic :class:`.DatabaseError` instead of the raw Exception type from the underlying driver manager library (:issue:`60748`)
82+
- Implemented :meth:`Series.rshift`, :meth:`Series.lshift`, :meth:`DataFrame.rshift` and :meth:`DataFrame.lshift` for bitwise right and left shift operations (:issue:`60693`)
8283
- Implemented :meth:`Series.str.isascii` and :meth:`Series.str.isascii` (:issue:`59091`)
8384
- Improved deprecation message for offset aliases (:issue:`60820`)
8485
- Multiplying two :class:`DateOffset` objects will now raise a ``TypeError`` instead of a ``RecursionError`` (:issue:`59442`)

pandas/core/arraylike.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,22 @@ def __pow__(self, other):
248248
def __rpow__(self, other):
249249
return self._arith_method(other, roperator.rpow)
250250

251+
@unpack_zerodim_and_defer("__rshift__")
252+
def __rshift__(self, other):
253+
return self._arith_method(other, operator.rshift)
254+
255+
@unpack_zerodim_and_defer("__rrshift__")
256+
def __rrshift__(self, other):
257+
return self._arith_method(other, roperator.rrshift)
258+
259+
@unpack_zerodim_and_defer("__lshift__")
260+
def __lshift__(self, other):
261+
return self._arith_method(other, operator.lshift)
262+
263+
@unpack_zerodim_and_defer("__rlshift__")
264+
def __rlshift__(self, other):
265+
return self._arith_method(other, roperator.rlshift)
266+
251267

252268
# -----------------------------------------------------------------------------
253269
# Helpers to implement __array_ufunc__

pandas/core/computation/expressions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ def _evaluate_numexpr(op, op_str, left_op, right_op):
153153
roperator.rmod: None,
154154
operator.pow: "**",
155155
roperator.rpow: "**",
156+
operator.rshift: None,
157+
roperator.rrshift: None,
158+
operator.lshift: None,
159+
roperator.rlshift: None,
156160
operator.eq: "==",
157161
operator.ne: "!=",
158162
operator.le: "<=",

pandas/core/frame.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8454,6 +8454,38 @@ def rpow(
84548454
other, roperator.rpow, level=level, fill_value=fill_value, axis=axis
84558455
)
84568456

8457+
@Appender(ops.make_flex_doc("rshift", "dataframe"))
8458+
def rshift(
8459+
self, other, axis: Axis = "columns", level=None, fill_value=None
8460+
) -> DataFrame:
8461+
return self._flex_arith_method(
8462+
other, operator.rshift, level=level, fill_value=fill_value, axis=axis
8463+
)
8464+
8465+
@Appender(ops.make_flex_doc("rrshift", "dataframe"))
8466+
def rrshift(
8467+
self, other, axis: Axis = "columns", level=None, fill_value=None
8468+
) -> DataFrame:
8469+
return self._flex_arith_method(
8470+
other, roperator.rrshift, level=level, fill_value=fill_value, axis=axis
8471+
)
8472+
8473+
@Appender(ops.make_flex_doc("lshift", "dataframe"))
8474+
def lshift(
8475+
self, other, axis: Axis = "columns", level=None, fill_value=None
8476+
) -> DataFrame:
8477+
return self._flex_arith_method(
8478+
other, operator.lshift, level=level, fill_value=fill_value, axis=axis
8479+
)
8480+
8481+
@Appender(ops.make_flex_doc("rlshift", "dataframe"))
8482+
def rlshift(
8483+
self, other, axis: Axis = "columns", level=None, fill_value=None
8484+
) -> DataFrame:
8485+
return self._flex_arith_method(
8486+
other, roperator.rlshift, level=level, fill_value=fill_value, axis=axis
8487+
)
8488+
84578489
# ----------------------------------------------------------------------
84588490
# Combination-Related
84598491

pandas/core/generic.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11705,6 +11705,16 @@ def __ixor__(self, other) -> Self:
1170511705
# error: Unsupported left operand type for ^ ("Type[NDFrame]")
1170611706
return self._inplace_method(other, type(self).__xor__) # type: ignore[operator]
1170711707

11708+
@final
11709+
def __irshift__(self, other) -> Self:
11710+
# error: Unsupported left operand type for >> ("Type[NDFrame]")
11711+
return self._inplace_method(other, type(self).__rshift__) # type: ignore[operator]
11712+
11713+
@final
11714+
def __ilshift__(self, other) -> Self:
11715+
# error: Unsupported left operand type for << ("Type[NDFrame]")
11716+
return self._inplace_method(other, type(self).__lshift__) # type: ignore[operator]
11717+
1170811718
# ----------------------------------------------------------------------
1170911719
# Misc methods
1171011720

pandas/core/ops/array_ops.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,8 @@ def get_array_op(op):
495495
"mod",
496496
"divmod",
497497
"pow",
498+
"shift",
499+
"lshift",
498500
}:
499501
return partial(arithmetic_op, op=op)
500502
else:

pandas/core/ops/docstrings.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,31 @@ def make_flex_doc(op_name: str, typ: str) -> str:
216216
"""
217217
)
218218

219+
_rshift_example_SERIES = (
220+
_common_examples_algebra_SERIES
221+
+ """
222+
>>> a.rshift(1, fill_value=0)
223+
a 0.0
224+
b 0.0
225+
c 0.0
226+
d NaN
227+
dtype: float64
228+
"""
229+
)
230+
231+
_lshift_example_SERIES = (
232+
_common_examples_algebra_SERIES
233+
+ """
234+
>>> a.rshift(1, fill_value=0)
235+
a 2.0
236+
b 2.0
237+
c 2.0
238+
d NaN
239+
dtype: float64
240+
"""
241+
)
242+
243+
219244
_ne_example_SERIES = (
220245
_common_examples_algebra_SERIES
221246
+ """
@@ -365,6 +390,20 @@ def make_flex_doc(op_name: str, typ: str) -> str:
365390
"series_returns": _returns_tuple,
366391
"df_examples": None,
367392
},
393+
"rshift": {
394+
"op": ">>",
395+
"desc": "Bitwise right shift",
396+
"reverse": "rrshift",
397+
"series_examples": _rshift_example_SERIES,
398+
"series_returns": _returns_series,
399+
},
400+
"lshift": {
401+
"op": "<<",
402+
"desc": "Bitwise left shift",
403+
"reverse": "rlshift",
404+
"series_examples": _lshift_example_SERIES,
405+
"series_returns": _returns_series,
406+
},
368407
# Comparison Operators
369408
"eq": {
370409
"op": "==",

pandas/core/roperator.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,11 @@ def ror_(left, right):
6161

6262
def rxor(left, right):
6363
return operator.xor(right, left)
64+
65+
66+
def rrshift(left, right):
67+
return operator.rshift(right, left)
68+
69+
70+
def rlshift(left, right):
71+
return operator.lshift(right, left)

pandas/core/series.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6578,6 +6578,30 @@ def rdivmod(self, other, level=None, fill_value=None, axis: Axis = 0) -> Series:
65786578
other, roperator.rdivmod, level=level, fill_value=fill_value, axis=axis
65796579
)
65806580

6581+
@Appender(ops.make_flex_doc("rshift", "series"))
6582+
def rshift(self, other, level=None, fill_value=None, axis: Axis = 0) -> Series:
6583+
return self._flex_method(
6584+
other, operator.rshift, level=level, fill_value=fill_value, axis=axis
6585+
)
6586+
6587+
@Appender(ops.make_flex_doc("rrshift", "series"))
6588+
def rrshift(self, other, level=None, fill_value=None, axis: Axis = 0) -> Series:
6589+
return self._flex_method(
6590+
other, roperator.rrshift, level=level, fill_value=fill_value, axis=axis
6591+
)
6592+
6593+
@Appender(ops.make_flex_doc("lshift", "series"))
6594+
def lshift(self, other, level=None, fill_value=None, axis: Axis = 0) -> Series:
6595+
return self._flex_method(
6596+
other, operator.lshift, level=level, fill_value=fill_value, axis=axis
6597+
)
6598+
6599+
@Appender(ops.make_flex_doc("rlshift", "series"))
6600+
def rlshift(self, other, level=None, fill_value=None, axis: Axis = 0) -> Series:
6601+
return self._flex_method(
6602+
other, roperator.rlshift, level=level, fill_value=fill_value, axis=axis
6603+
)
6604+
65816605
# ----------------------------------------------------------------------
65826606
# Reductions
65836607

pandas/tests/frame/test_arithmetic.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2199,3 +2199,99 @@ def test_mixed_col_index_dtype(using_infer_string):
21992199
dtype = "string"
22002200
expected.columns = expected.columns.astype(dtype)
22012201
tm.assert_frame_equal(result, expected)
2202+
2203+
2204+
def test_dataframe_rshift():
2205+
df = DataFrame({"a": [8, 16], "b": [32, 64]})
2206+
result = df >> 2
2207+
expected = DataFrame({"a": [2, 4], "b": [8, 16]})
2208+
2209+
tm.assert_frame_equal(result, expected)
2210+
2211+
2212+
def test_dataframe_rshift_negative():
2213+
df = DataFrame({"a": [8, 16], "b": [32, 64]})
2214+
result = df >> -1
2215+
expected = DataFrame({"a": [0, 0], "b": [0, 0]})
2216+
2217+
tm.assert_frame_equal(result, expected)
2218+
2219+
2220+
def test_dataframe_rshift_zero():
2221+
df = DataFrame({"a": [8, 16], "b": [32, 64]})
2222+
result = df >> 0
2223+
2224+
tm.assert_frame_equal(result, df)
2225+
2226+
2227+
def test_dataframe_lshift():
2228+
df = DataFrame({"a": [1, 2], "b": [4, 8]})
2229+
result = df << 2
2230+
expected = DataFrame({"a": [4, 8], "b": [16, 32]})
2231+
2232+
tm.assert_frame_equal(result, expected)
2233+
2234+
2235+
def test_dataframe_lshift_negative():
2236+
df = DataFrame({"a": [8, 16], "b": [32, 64]})
2237+
result = df << -1
2238+
expected = DataFrame({"a": [0, 0], "b": [0, 0]})
2239+
2240+
tm.assert_frame_equal(result, expected)
2241+
2242+
2243+
def test_dataframe_lshift_zero():
2244+
df = DataFrame({"a": [8, 16], "b": [32, 64]})
2245+
result = df << 0
2246+
2247+
tm.assert_frame_equal(result, df)
2248+
2249+
2250+
def test_dataframe_rrshift():
2251+
df = DataFrame({"a": [1, 2], "b": [3, 4]})
2252+
result = operator.rshift(64, df)
2253+
expected = DataFrame({"a": [32, 16], "b": [8, 4]})
2254+
2255+
tm.assert_frame_equal(result, expected)
2256+
2257+
2258+
def test_dataframe_rlshift():
2259+
df = DataFrame({"a": [1, 2], "b": [3, 4]})
2260+
result = operator.lshift(1, df)
2261+
expected = DataFrame({"a": [2, 4], "b": [8, 16]})
2262+
2263+
tm.assert_frame_equal(result, expected)
2264+
2265+
2266+
def test_dataframe_irshift():
2267+
df = DataFrame({"a": [8, 16], "b": [32, 64]})
2268+
df >>= 2
2269+
expected = DataFrame({"a": [2, 4], "b": [8, 16]})
2270+
2271+
tm.assert_frame_equal(df, expected)
2272+
2273+
2274+
def test_dataframe_ilshift():
2275+
df = DataFrame({"a": [1, 2], "b": [4, 8]})
2276+
df <<= 2
2277+
expected = DataFrame({"a": [4, 8], "b": [16, 32]})
2278+
2279+
tm.assert_frame_equal(df, expected)
2280+
2281+
2282+
def test_dataframe_rshift_dataframe():
2283+
df1 = DataFrame({"a": [8, 4], "b": [2, 1]})
2284+
df2 = DataFrame({"a": [3, 2], "b": [1, 0]})
2285+
result = df1 >> df2
2286+
expected = DataFrame({"a": [1, 1], "b": [1, 1]})
2287+
2288+
tm.assert_frame_equal(result, expected)
2289+
2290+
2291+
def test_dataframe_lshift_dataframe():
2292+
df1 = DataFrame({"a": [1, 2], "b": [4, 8]})
2293+
df2 = DataFrame({"a": [3, 2], "b": [1, 0]})
2294+
result = df1 << df2
2295+
expected = DataFrame({"a": [8, 8], "b": [8, 8]})
2296+
2297+
tm.assert_frame_equal(result, expected)

0 commit comments

Comments
 (0)