@@ -257,6 +257,13 @@ def arctan_op_impl(x: ibis_types.Value):
257
257
return typing .cast (ibis_types .NumericValue , x ).atan ()
258
258
259
259
260
+ @scalar_op_compiler .register_binary_op (ops .arctan2_op )
261
+ def arctan2_op_impl (x : ibis_types .Value , y : ibis_types .Value ):
262
+ return typing .cast (ibis_types .NumericValue , x ).atan2 (
263
+ typing .cast (ibis_types .NumericValue , y )
264
+ )
265
+
266
+
260
267
# Hyperbolic trig functions
261
268
# BQ has these functions, but Ibis doesn't
262
269
@scalar_op_compiler .register_unary_op (ops .sinh_op )
@@ -319,6 +326,30 @@ def arctanh_op_impl(x: ibis_types.Value):
319
326
320
327
321
328
# Numeric Ops
329
+ @scalar_op_compiler .register_unary_op (ops .floor_op )
330
+ def floor_op_impl (x : ibis_types .Value ):
331
+ x_numeric = typing .cast (ibis_types .NumericValue , x )
332
+ if x_numeric .type ().is_integer ():
333
+ return x_numeric .cast (ibis_dtypes .Float64 ())
334
+ if x_numeric .type ().is_floating ():
335
+ # Default ibis impl tries to cast to integer, which doesn't match pandas and can overflow
336
+ return float_floor (x_numeric )
337
+ else : # numeric
338
+ return x_numeric .floor ()
339
+
340
+
341
+ @scalar_op_compiler .register_unary_op (ops .ceil_op )
342
+ def ceil_op_impl (x : ibis_types .Value ):
343
+ x_numeric = typing .cast (ibis_types .NumericValue , x )
344
+ if x_numeric .type ().is_integer ():
345
+ return x_numeric .cast (ibis_dtypes .Float64 ())
346
+ if x_numeric .type ().is_floating ():
347
+ # Default ibis impl tries to cast to integer, which doesn't match pandas and can overflow
348
+ return float_ceil (x_numeric )
349
+ else : # numeric
350
+ return x_numeric .ceil ()
351
+
352
+
322
353
@scalar_op_compiler .register_unary_op (ops .abs_op )
323
354
def abs_op_impl (x : ibis_types .Value ):
324
355
return typing .cast (ibis_types .NumericValue , x ).abs ()
@@ -347,13 +378,23 @@ def ln_op_impl(x: ibis_types.Value):
347
378
return (~ domain ).ifelse (out_of_domain , numeric_value .ln ())
348
379
349
380
381
+ @scalar_op_compiler .register_unary_op (ops .log1p_op )
382
+ def log1p_op_impl (x : ibis_types .Value ):
383
+ return ln_op_impl (_ibis_num (1 ) + x )
384
+
385
+
350
386
@scalar_op_compiler .register_unary_op (ops .exp_op )
351
387
def exp_op_impl (x : ibis_types .Value ):
352
388
numeric_value = typing .cast (ibis_types .NumericValue , x )
353
389
domain = numeric_value < _FLOAT64_EXP_BOUND
354
390
return (~ domain ).ifelse (_INF , numeric_value .exp ())
355
391
356
392
393
+ @scalar_op_compiler .register_unary_op (ops .expm1_op )
394
+ def expm1_op_impl (x : ibis_types .Value ):
395
+ return exp_op_impl (x ) - _ibis_num (1 )
396
+
397
+
357
398
@scalar_op_compiler .register_unary_op (ops .invert_op )
358
399
def invert_op_impl (x : ibis_types .Value ):
359
400
return typing .cast (ibis_types .NumericValue , x ).negate ()
@@ -1318,3 +1359,16 @@ def _ibis_num(number: float):
1318
1359
@ibis .udf .scalar .builtin
1319
1360
def timestamp (a : str ) -> ibis_dtypes .timestamp :
1320
1361
"""Convert string to timestamp."""
1362
+
1363
+
1364
+ # Need these because ibis otherwise tries to do casts to int that can fail
1365
+ @ibis .udf .scalar .builtin (name = "floor" )
1366
+ def float_floor (a : float ) -> float :
1367
+ """Convert string to timestamp."""
1368
+ return 0 # pragma: NO COVER
1369
+
1370
+
1371
+ @ibis .udf .scalar .builtin (name = "ceil" )
1372
+ def float_ceil (a : float ) -> float :
1373
+ """Convert string to timestamp."""
1374
+ return 0 # pragma: NO COVER
0 commit comments