Skip to content

Commit 68723bc

Browse files
tswastgoogle-labs-jules[bot]gcf-owl-bot[bot]
authored
feat: add support for np.isnan and np.isfinite ufuncs (#2188)
* feat: Implement isnan and isfinite in bigframes This commit implements the `isnan` and `isfinite` numpy ufuncs in bigframes. The following changes were made: - Added `IsNanOp` and `IsFiniteOp` to `bigframes/operations/numeric_ops.py` - Mapped `np.isnan` and `np.isfinite` to the new ops in `bigframes/operations/numpy_op_maps.py` - Added compilation logic for the new ops to the ibis, polars, and sqlglot compilers - Added tests for the new ops in `tests/system/small/test_numpy.py` * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * feat: Implement isnan and isfinite in bigframes This commit implements the `isnan` and `isfinite` numpy ufuncs in bigframes. The following changes were made: - Added `IsNanOrNullOp` and `IsFiniteOp` to `bigframes/operations/numeric_ops.py` - Mapped `np.isnan` and `np.isfinite` to the new ops in `bigframes/operations/numpy_op_maps.py` - Added compilation logic for the new ops to the ibis, polars, and sqlglot compilers - Added tests for the new ops in `tests/system/small/test_numpy.py` - Renamed `IsNanOp` to `IsNanOrNullOp` to match numpy semantics and updated compilers accordingly. * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Apply suggestions from code review * Update bigframes/core/compile/sqlglot/scalar_compiler.py * Update bigframes/core/compile/sqlglot/scalar_compiler.py * Update bigframes/core/compile/sqlglot/scalar_compiler.py * preserve NA * fix annotations --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent e0b2257 commit 68723bc

File tree

6 files changed

+65
-0
lines changed

6 files changed

+65
-0
lines changed

bigframes/core/compile/ibis_compiler/scalar_op_compiler.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from bigframes.core import agg_expressions, ordering
2727
import bigframes.core.compile.ibis_types
2828
import bigframes.core.expression as ex
29+
from bigframes.operations import numeric_ops
2930

3031
if TYPE_CHECKING:
3132
import bigframes.operations as ops
@@ -267,3 +268,13 @@ def _convert_range_ordering_to_table_value(
267268

268269
# Singleton compiler
269270
scalar_op_compiler = ExpressionCompiler()
271+
272+
273+
@scalar_op_compiler.register_unary_op(numeric_ops.isnan_op)
274+
def isnanornull(arg):
275+
return arg.isnan()
276+
277+
278+
@scalar_op_compiler.register_unary_op(numeric_ops.isfinite_op)
279+
def isfinite(arg):
280+
return arg.isinf().negate() & arg.isnan().negate()

bigframes/core/compile/polars/operations/numeric_ops.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,21 @@ def sqrt_op_impl(
8989
import polars as pl
9090

9191
return pl.when(input < 0).then(float("nan")).otherwise(input.sqrt())
92+
93+
94+
@polars_compiler.register_op(numeric_ops.IsNanOp)
95+
def is_nan_op_impl(
96+
compiler: polars_compiler.PolarsExpressionCompiler,
97+
op: numeric_ops.IsNanOp, # type: ignore
98+
input: pl.Expr,
99+
) -> pl.Expr:
100+
return input.is_nan()
101+
102+
103+
@polars_compiler.register_op(numeric_ops.IsFiniteOp)
104+
def is_finite_op_impl(
105+
compiler: polars_compiler.PolarsExpressionCompiler,
106+
op: numeric_ops.IsFiniteOp, # type: ignore
107+
input: pl.Expr,
108+
) -> pl.Expr:
109+
return input.is_finite()

bigframes/core/compile/sqlglot/expressions/numeric_ops.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import bigframes.core.compile.sqlglot.expressions.constants as constants
2323
from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr
2424
import bigframes.core.compile.sqlglot.scalar_compiler as scalar_compiler
25+
from bigframes.operations import numeric_ops
2526

2627
register_unary_op = scalar_compiler.scalar_op_compiler.register_unary_op
2728
register_binary_op = scalar_compiler.scalar_op_compiler.register_binary_op
@@ -408,6 +409,21 @@ def _(left: TypedExpr, right: TypedExpr) -> sge.Expression:
408409
)
409410

410411

412+
@register_unary_op(numeric_ops.isnan_op)
413+
def isnan(arg: TypedExpr) -> sge.Expression:
414+
return sge.IsNan(this=arg.expr)
415+
416+
417+
@register_unary_op(numeric_ops.isfinite_op)
418+
def isfinite(arg: TypedExpr) -> sge.Expression:
419+
return sge.Not(
420+
this=sge.Or(
421+
this=sge.IsInf(this=arg.expr),
422+
right=sge.IsNan(this=arg.expr),
423+
),
424+
)
425+
426+
411427
def _coerce_bool_to_int(typed_expr: TypedExpr) -> sge.Expression:
412428
"""Coerce boolean expression to integer."""
413429
if typed_expr.dtype == dtypes.BOOL_DTYPE:

bigframes/operations/numeric_ops.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,3 +348,19 @@ def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionT
348348
name="unsafe_pow_op", type_signature=op_typing.BINARY_REAL_NUMERIC
349349
)
350350
unsafe_pow_op = UnsafePowOp()
351+
352+
IsNanOp = base_ops.create_unary_op(
353+
name="isnan",
354+
type_signature=op_typing.FixedOutputType(
355+
dtypes.is_numeric, dtypes.BOOL_DTYPE, "numeric"
356+
),
357+
)
358+
isnan_op = IsNanOp()
359+
360+
IsFiniteOp = base_ops.create_unary_op(
361+
name="isfinite",
362+
type_signature=op_typing.FixedOutputType(
363+
dtypes.is_numeric, dtypes.BOOL_DTYPE, "numeric"
364+
),
365+
)
366+
isfinite_op = IsFiniteOp()

bigframes/operations/numpy_op_maps.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
np.ceil: numeric_ops.ceil_op,
4141
np.log1p: numeric_ops.log1p_op,
4242
np.expm1: numeric_ops.expm1_op,
43+
np.isnan: numeric_ops.isnan_op,
44+
np.isfinite: numeric_ops.isfinite_op,
4345
}
4446

4547

tests/system/small/test_numpy.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
("log10",),
3838
("sqrt",),
3939
("abs",),
40+
("isnan",),
41+
("isfinite",),
4042
],
4143
)
4244
def test_series_ufuncs(floats_pd, floats_bf, opname):

0 commit comments

Comments
 (0)