Source code for df_eval.functions
"""
Built-in functions for expression evaluation.
This module provides built-in functions that can be used in expressions.
These are safe, vectorized functions that are allow-listed for use in expressions.
"""
import numpy as np
import pandas as pd
from typing import Any, Callable
[docs]
def safe_divide(a: Any, b: Any) -> Any:
"""
Safely divide two values, returning NaN for division by zero.
Args:
a: The numerator.
b: The denominator.
Returns:
The result of a / b, or NaN if b is zero.
"""
with np.errstate(divide='ignore', invalid='ignore'):
return np.where(b != 0, np.divide(a, b), np.nan)
[docs]
def coalesce(*args: Any) -> Any:
"""
Return the first non-null value from the arguments.
Args:
*args: Values to check.
Returns:
The first non-null value, or None if all are null.
"""
for arg in args:
if arg is not None and (not isinstance(arg, float) or not np.isnan(arg)):
return arg
return None
[docs]
def clip_value(value: Any, min_val: float | None = None, max_val: float | None = None) -> Any:
"""
Clip values to a specified range.
Args:
value: The value to clip.
min_val: The minimum value (optional).
max_val: The maximum value (optional).
Returns:
The clipped value.
"""
if min_val is not None:
value = np.maximum(value, min_val)
if max_val is not None:
value = np.minimum(value, max_val)
return value
[docs]
def safe_abs(value: Any) -> Any:
"""Absolute value function."""
return np.abs(value)
[docs]
def safe_log(value: Any) -> Any:
"""Natural logarithm function."""
with np.errstate(divide='ignore', invalid='ignore'):
return np.log(value)
[docs]
def safe_exp(value: Any) -> Any:
"""Exponential function."""
with np.errstate(over='ignore'):
return np.exp(value)
[docs]
def safe_sqrt(value: Any) -> Any:
"""Square root function."""
with np.errstate(invalid='ignore'):
return np.sqrt(value)
[docs]
def safe_clip(value: Any, a_min: Any, a_max: Any) -> Any:
"""Clip values to a range."""
return np.clip(value, a_min, a_max)
[docs]
def safe_where(condition: Any, x: Any, y: Any) -> Any:
"""Return elements from x or y depending on condition."""
return np.where(condition, x, y)
[docs]
def safe_isna(value: Any) -> Any:
"""Check for NaN/None values."""
if isinstance(value, pd.Series):
return value.isna()
return pd.isna(value)
[docs]
def safe_fillna(value: Any, fill_value: Any) -> Any:
"""Fill NaN/None values with a specified value."""
if isinstance(value, pd.Series):
return value.fillna(fill_value)
return fill_value if pd.isna(value) else value
# Dictionary of allow-listed safe functions available for expressions
BUILTIN_FUNCTIONS: dict[str, Callable] = {
"safe_divide": safe_divide,
"coalesce": coalesce,
"clip": clip_value,
# Allow-listed safe functions
"abs": safe_abs,
"log": safe_log,
"exp": safe_exp,
"sqrt": safe_sqrt,
"clip": safe_clip,
"where": safe_where,
"isna": safe_isna,
"fillna": safe_fillna,
}