Skip to content

Commit 968d825

Browse files
feat: Add +, - as unary ops, ^ binary op (#724)
1 parent 6a78c89 commit 968d825

File tree

8 files changed

+190
-3
lines changed

8 files changed

+190
-3
lines changed

bigframes/core/compile/scalar_op_compiler.py

+20
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,16 @@ def abs_op_impl(x: ibis_types.Value):
375375
return typing.cast(ibis_types.NumericValue, x).abs()
376376

377377

378+
@scalar_op_compiler.register_unary_op(ops.pos_op)
379+
def pos_op_impl(x: ibis_types.Value):
380+
return typing.cast(ibis_types.NumericValue, x)
381+
382+
383+
@scalar_op_compiler.register_unary_op(ops.neg_op)
384+
def neg_op_impl(x: ibis_types.Value):
385+
return typing.cast(ibis_types.NumericValue, x).negate()
386+
387+
378388
@scalar_op_compiler.register_unary_op(ops.sqrt_op)
379389
def sqrt_op_impl(x: ibis_types.Value):
380390
numeric_value = typing.cast(ibis_types.NumericValue, x)
@@ -979,6 +989,16 @@ def or_op(
979989
)
980990

981991

992+
@scalar_op_compiler.register_binary_op(ops.xor_op)
993+
def xor_op(
994+
x: ibis_types.Value,
995+
y: ibis_types.Value,
996+
):
997+
return typing.cast(ibis_types.BooleanValue, x) ^ typing.cast(
998+
ibis_types.BooleanValue, y
999+
)
1000+
1001+
9821002
@scalar_op_compiler.register_binary_op(ops.add_op)
9831003
@short_circuit_nulls()
9841004
def add_op(

bigframes/dataframe.py

+27
Original file line numberDiff line numberDiff line change
@@ -1112,6 +1112,33 @@ def __rpow__(self, other):
11121112

11131113
__rpow__.__doc__ = inspect.getdoc(vendored_pandas_frame.DataFrame.__rpow__)
11141114

1115+
def __and__(self, other: bool | int | bigframes.series.Series) -> DataFrame:
1116+
return self._apply_binop(other, ops.and_op)
1117+
1118+
__and__.__doc__ = inspect.getdoc(vendored_pandas_frame.DataFrame.__and__)
1119+
1120+
__rand__ = __and__
1121+
1122+
def __or__(self, other: bool | int | bigframes.series.Series) -> DataFrame:
1123+
return self._apply_binop(other, ops.or_op)
1124+
1125+
__or__.__doc__ = inspect.getdoc(vendored_pandas_frame.DataFrame.__or__)
1126+
1127+
__ror__ = __or__
1128+
1129+
def __xor__(self, other: bool | int | bigframes.series.Series) -> DataFrame:
1130+
return self._apply_binop(other, ops.xor_op)
1131+
1132+
__xor__.__doc__ = inspect.getdoc(vendored_pandas_frame.DataFrame.__xor__)
1133+
1134+
__rxor__ = __xor__
1135+
1136+
def __pos__(self) -> DataFrame:
1137+
return self._apply_unary_op(ops.pos_op)
1138+
1139+
def __neg__(self) -> DataFrame:
1140+
return self._apply_unary_op(ops.neg_op)
1141+
11151142
def align(
11161143
self,
11171144
other: typing.Union[DataFrame, bigframes.series.Series],

bigframes/operations/__init__.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ def create_binary_op(
186186
dtypes.is_binary_like,
187187
description="binary-like",
188188
),
189-
) # numeric
189+
)
190190
isnull_op = create_unary_op(
191191
name="isnull",
192192
type_signature=op_typing.FixedOutputType(
@@ -311,6 +311,8 @@ def create_binary_op(
311311
floor_op = create_unary_op(name="floor", type_signature=op_typing.UNARY_REAL_NUMERIC)
312312
ceil_op = create_unary_op(name="ceil", type_signature=op_typing.UNARY_REAL_NUMERIC)
313313
abs_op = create_unary_op(name="abs", type_signature=op_typing.UNARY_NUMERIC)
314+
pos_op = create_unary_op(name="pos", type_signature=op_typing.UNARY_NUMERIC)
315+
neg_op = create_unary_op(name="neg", type_signature=op_typing.UNARY_NUMERIC)
314316
exp_op = create_unary_op(name="exp", type_signature=op_typing.UNARY_REAL_NUMERIC)
315317
expm1_op = create_unary_op(name="expm1", type_signature=op_typing.UNARY_REAL_NUMERIC)
316318
ln_op = create_unary_op(name="log", type_signature=op_typing.UNARY_REAL_NUMERIC)
@@ -650,6 +652,7 @@ def output_type(self, *input_types):
650652
# Logical Ops
651653
and_op = create_binary_op(name="and", type_signature=op_typing.LOGICAL)
652654
or_op = create_binary_op(name="or", type_signature=op_typing.LOGICAL)
655+
xor_op = create_binary_op(name="xor", type_signature=op_typing.LOGICAL)
653656

654657
## Comparison Ops
655658
eq_op = create_binary_op(name="eq", type_signature=op_typing.COMPARISON)

bigframes/series.py

+13
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,13 @@ def __or__(self, other: bool | int | Series) -> Series:
668668

669669
__ror__ = __or__
670670

671+
def __xor__(self, other: bool | int | Series) -> Series:
672+
return self._apply_binary_op(other, ops.xor_op)
673+
674+
__or__.__doc__ = inspect.getdoc(vendored_pandas_series.Series.__xor__)
675+
676+
__rxor__ = __xor__
677+
671678
def __add__(self, other: float | int | Series) -> Series:
672679
return self.add(other)
673680

@@ -1036,6 +1043,12 @@ def __invert__(self) -> Series:
10361043

10371044
__invert__.__doc__ = inspect.getdoc(vendored_pandas_series.Series.__invert__)
10381045

1046+
def __pos__(self) -> Series:
1047+
return self._apply_unary_op(ops.pos_op)
1048+
1049+
def __neg__(self) -> Series:
1050+
return self._apply_unary_op(ops.neg_op)
1051+
10391052
def eq(self, other: object) -> Series:
10401053
# TODO: enforce stricter alignment
10411054
return self._apply_binary_op(other, ops.eq_op)

tests/system/small/test_dataframe.py

+16
Original file line numberDiff line numberDiff line change
@@ -1699,6 +1699,22 @@ def test_df_abs(scalars_dfs):
16991699
assert_pandas_df_equal(bf_result, pd_result)
17001700

17011701

1702+
def test_df_pos(scalars_dfs):
1703+
scalars_df, scalars_pandas_df = scalars_dfs
1704+
bf_result = (+scalars_df[["int64_col", "numeric_col"]]).to_pandas()
1705+
pd_result = +scalars_pandas_df[["int64_col", "numeric_col"]]
1706+
1707+
assert_pandas_df_equal(pd_result, bf_result)
1708+
1709+
1710+
def test_df_neg(scalars_dfs):
1711+
scalars_df, scalars_pandas_df = scalars_dfs
1712+
bf_result = (-scalars_df[["int64_col", "numeric_col"]]).to_pandas()
1713+
pd_result = -scalars_pandas_df[["int64_col", "numeric_col"]]
1714+
1715+
assert_pandas_df_equal(pd_result, bf_result)
1716+
1717+
17021718
def test_df_invert(scalars_dfs):
17031719
scalars_df, scalars_pandas_df = scalars_dfs
17041720
columns = ["int64_col", "bool_col"]

tests/system/small/test_series.py

+34
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,36 @@ def test_abs(scalars_dfs, col_name):
354354
assert_series_equal(pd_result, bf_result)
355355

356356

357+
@pytest.mark.parametrize(
358+
("col_name",),
359+
(
360+
("float64_col",),
361+
("int64_too",),
362+
),
363+
)
364+
def test_series_pos(scalars_dfs, col_name):
365+
scalars_df, scalars_pandas_df = scalars_dfs
366+
bf_result = (+scalars_df[col_name]).to_pandas()
367+
pd_result = +scalars_pandas_df[col_name]
368+
369+
assert_series_equal(pd_result, bf_result)
370+
371+
372+
@pytest.mark.parametrize(
373+
("col_name",),
374+
(
375+
("float64_col",),
376+
("int64_too",),
377+
),
378+
)
379+
def test_series_neg(scalars_dfs, col_name):
380+
scalars_df, scalars_pandas_df = scalars_dfs
381+
bf_result = (-scalars_df[col_name]).to_pandas()
382+
pd_result = -scalars_pandas_df[col_name]
383+
384+
assert_series_equal(pd_result, bf_result)
385+
386+
357387
@pytest.mark.parametrize(
358388
("col_name",),
359389
(
@@ -678,10 +708,12 @@ def test_series_pow_scalar_reverse(scalars_dfs):
678708
[
679709
(lambda x, y: x & y),
680710
(lambda x, y: x | y),
711+
(lambda x, y: x ^ y),
681712
],
682713
ids=[
683714
"and",
684715
"or",
716+
"xor",
685717
],
686718
)
687719
@pytest.mark.parametrize(("other_scalar"), [True, False, pd.NA])
@@ -714,6 +746,7 @@ def test_series_bool_bool_operators_scalar(
714746
(lambda x, y: x // y),
715747
(lambda x, y: x & y),
716748
(lambda x, y: x | y),
749+
(lambda x, y: x ^ y),
717750
],
718751
ids=[
719752
"add",
@@ -728,6 +761,7 @@ def test_series_bool_bool_operators_scalar(
728761
"floordivide",
729762
"bitwise_and",
730763
"bitwise_or",
764+
"bitwise_xor",
731765
],
732766
)
733767
def test_series_int_int_operators_series(scalars_dfs, operator):

third_party/bigframes_vendored/pandas/core/frame.py

+36
Original file line numberDiff line numberDiff line change
@@ -3555,6 +3555,42 @@ def __rpow__(self, other):
35553555
"""
35563556
raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE)
35573557

3558+
def __and__(self, other):
3559+
"""Get bitwise AND of DataFrame and other, element-wise, using operator `&`.
3560+
3561+
Args:
3562+
other (scalar, Series or DataFrame):
3563+
Object to bitwise AND with the DataFrame.
3564+
3565+
Returns:
3566+
bigframes.dataframe.DataFrame: The result of the operation.
3567+
"""
3568+
raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE)
3569+
3570+
def __or__(self, other):
3571+
"""Get bitwise OR of DataFrame and other, element-wise, using operator `|`.
3572+
3573+
Args:
3574+
other (scalar, Series or DataFrame):
3575+
Object to bitwise OR with the DataFrame.
3576+
3577+
Returns:
3578+
bigframes.dataframe.DataFrame: The result of the operation.
3579+
"""
3580+
raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE)
3581+
3582+
def __xor__(self, other):
3583+
"""Get bitwise XOR of DataFrame and other, element-wise, using operator `^`.
3584+
3585+
Args:
3586+
other (scalar, Series or DataFrame):
3587+
Object to bitwise XOR with the DataFrame.
3588+
3589+
Returns:
3590+
bigframes.dataframe.DataFrame: The result of the operation.
3591+
"""
3592+
raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE)
3593+
35583594
def combine(
35593595
self, other, func, fill_value=None, overwrite: bool = True
35603596
) -> DataFrame:

third_party/bigframes_vendored/pandas/core/series.py

+40-2
Original file line numberDiff line numberDiff line change
@@ -4224,7 +4224,7 @@ def __and__(self, other):
42244224
Object to bitwise AND with the Series.
42254225
42264226
Returns:
4227-
Series: The result of the operation.
4227+
bigframes.series.Series: The result of the operation.
42284228
"""
42294229
raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE)
42304230

@@ -4262,7 +4262,45 @@ def __or__(self, other):
42624262
Object to bitwise OR with the Series.
42634263
42644264
Returns:
4265-
Series: The result of the operation.
4265+
bigframes.series.Series: The result of the operation.
4266+
"""
4267+
raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE)
4268+
4269+
def __xor__(self, other):
4270+
"""Get bitwise XOR of Series and other, element-wise, using operator `^`.
4271+
4272+
**Examples:**
4273+
4274+
>>> import bigframes.pandas as bpd
4275+
>>> bpd.options.display.progress_bar = None
4276+
4277+
>>> s = bpd.Series([0, 1, 2, 3])
4278+
4279+
You can operate with a scalar.
4280+
4281+
>>> s ^ 6
4282+
0 6
4283+
1 7
4284+
2 4
4285+
3 5
4286+
dtype: Int64
4287+
4288+
You can operate with another Series.
4289+
4290+
>>> s1 = bpd.Series([5, 6, 7, 8])
4291+
>>> s ^ s1
4292+
0 5
4293+
1 7
4294+
2 5
4295+
3 11
4296+
dtype: Int64
4297+
4298+
Args:
4299+
other (scalar or Series):
4300+
Object to bitwise XOR with the Series.
4301+
4302+
Returns:
4303+
bigframes.series.Series: The result of the operation.
42664304
"""
42674305
raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE)
42684306

0 commit comments

Comments
 (0)