Skip to content

Commit

Permalink
fix: Implement abs for Decimal, error on Date/Time/Datetime (#13821)
Browse files Browse the repository at this point in the history
  • Loading branch information
stinodego authored Jan 19, 2024
1 parent 585b9f3 commit f713e22
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 43 deletions.
35 changes: 25 additions & 10 deletions crates/polars-ops/src/series/ops/abs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,34 @@ where

/// Convert numerical values to their absolute value.
pub fn abs(s: &Series) -> PolarsResult<Series> {
let physical_s = s.to_physical_repr();
use DataType::*;
let out = match physical_s.dtype() {
let out = match s.dtype() {
#[cfg(feature = "dtype-i8")]
Int8 => abs_numeric(physical_s.i8()?).into_series(),
Int8 => abs_numeric(s.i8().unwrap()).into_series(),
#[cfg(feature = "dtype-i16")]
Int16 => abs_numeric(physical_s.i16()?).into_series(),
Int32 => abs_numeric(physical_s.i32()?).into_series(),
Int64 => abs_numeric(physical_s.i64()?).into_series(),
UInt8 | UInt16 | UInt32 | UInt64 => s.clone(),
Float32 => abs_numeric(physical_s.f32()?).into_series(),
Float64 => abs_numeric(physical_s.f64()?).into_series(),
Int16 => abs_numeric(s.i16().unwrap()).into_series(),
Int32 => abs_numeric(s.i32().unwrap()).into_series(),
Int64 => abs_numeric(s.i64().unwrap()).into_series(),
Float32 => abs_numeric(s.f32().unwrap()).into_series(),
Float64 => abs_numeric(s.f64().unwrap()).into_series(),
#[cfg(feature = "dtype-decimal")]
Decimal(_, _) => {
let ca = s.decimal().unwrap();
let precision = ca.precision();
let scale = ca.scale();

let out = abs_numeric(ca.as_ref());
out.into_decimal_unchecked(precision, scale).into_series()
},
#[cfg(feature = "dtype-duration")]
Duration(_) => {
let physical = s.to_physical_repr();
let ca = physical.i64().unwrap();
let out = abs_numeric(ca).into_series();
out.cast(s.dtype())?
},
dt if dt.is_unsigned_integer() => s.clone(),
dt => polars_bail!(opq = abs, dt),
};
out.cast(s.dtype())
Ok(out)
}
7 changes: 0 additions & 7 deletions py-polars/tests/unit/expr/test_exprs.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,13 +412,6 @@ def test_search_sorted() -> None:
assert a.search_sorted(b, side="right").to_list() == [0, 2, 2, 4, 4]


def test_abs_expr() -> None:
df = pl.DataFrame({"x": [-1, 0, 1]})
out = df.select(abs(pl.col("x")))

assert out["x"].to_list() == [1, 0, 1]


def test_logical_boolean() -> None:
# note, cannot use expressions in logical
# boolean context (eg: and/or/not operators)
Expand Down
6 changes: 0 additions & 6 deletions py-polars/tests/unit/functions/test_functions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from __future__ import annotations

from datetime import timedelta
from typing import TYPE_CHECKING, Any

import numpy as np
Expand Down Expand Up @@ -390,11 +389,6 @@ def test_fill_null_unknown_output_type() -> None:
}


def test_abs_logical_type() -> None:
s = pl.Series([timedelta(hours=1), timedelta(hours=-1)])
assert s.abs().to_list() == [timedelta(hours=1), timedelta(hours=1)]


def test_approx_n_unique() -> None:
df1 = pl.DataFrame({"a": [None, 1, 2], "b": [None, 2, 1]})

Expand Down
110 changes: 110 additions & 0 deletions py-polars/tests/unit/operations/test_abs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from __future__ import annotations

from datetime import date, timedelta
from decimal import Decimal as D
from typing import cast

import numpy as np
import pytest

import polars as pl
from polars.testing import assert_frame_equal, assert_series_equal


def test_abs() -> None:
# ints
s = pl.Series([1, -2, 3, -4])
assert_series_equal(s.abs(), pl.Series([1, 2, 3, 4]))
assert_series_equal(cast(pl.Series, np.abs(s)), pl.Series([1, 2, 3, 4]))

# floats
s = pl.Series([1.0, -2.0, 3, -4.0])
assert_series_equal(s.abs(), pl.Series([1.0, 2.0, 3.0, 4.0]))
assert_series_equal(cast(pl.Series, np.abs(s)), pl.Series([1.0, 2.0, 3.0, 4.0]))
assert_series_equal(
pl.select(pl.lit(s).abs()).to_series(), pl.Series([1.0, 2.0, 3.0, 4.0])
)


def test_abs_series_duration() -> None:
s = pl.Series([timedelta(hours=1), timedelta(hours=-1)])
assert s.abs().to_list() == [timedelta(hours=1), timedelta(hours=1)]


def test_abs_expr() -> None:
df = pl.DataFrame({"x": [-1, 0, 1]})
out = df.select(abs(pl.col("x")))

assert out["x"].to_list() == [1, 0, 1]


def test_builtin_abs() -> None:
s = pl.Series("s", [-1, 0, 1, None])
assert abs(s).to_list() == [1, 0, 1, None]


@pytest.mark.parametrize(
"dtype", [pl.Int8, pl.Int16, pl.Int32, pl.Int64, pl.Float32, pl.Float64]
)
def test_abs_builtin(dtype: pl.PolarsDataType) -> None:
lf = pl.LazyFrame({"a": [-1, 0, 1, None]}, schema={"a": dtype})
result = lf.select(abs(pl.col("a")))
expected = pl.LazyFrame({"a": [1, 0, 1, None]}, schema={"a": dtype})
assert_frame_equal(result, expected)


def test_abs_method() -> None:
lf = pl.LazyFrame({"a": [-1, 0, 1, None]})
result_op = lf.select(abs(pl.col("a")))
result_method = lf.select(pl.col("a").abs())
assert_frame_equal(result_op, result_method)


def test_abs_decimal() -> None:
lf = pl.LazyFrame({"a": [D("-1.5"), D("0.0"), D("5.0"), None]})
result = lf.select(pl.col("a").abs())
expected = pl.LazyFrame({"a": [D("1.5"), D("0.0"), D("5.0"), None]})
assert_frame_equal(result, expected)


def test_abs_duration() -> None:
lf = pl.LazyFrame({"a": [timedelta(hours=2), timedelta(days=-2), None]})
result = lf.select(pl.col("a").abs())
expected = pl.LazyFrame({"a": [timedelta(hours=2), timedelta(days=2), None]})
assert_frame_equal(result, expected)


def test_abs_overflow() -> None:
df = pl.DataFrame({"a": [-128]}, schema={"a": pl.Int8})
with pytest.raises(pl.PolarsPanicError, match="attempt to negate with overflow"):
df.select(pl.col("a").abs())


def test_abs_unsigned_int() -> None:
df = pl.DataFrame({"a": [1, 2, 3]}, schema={"a": pl.UInt8})
result = df.select(pl.col("a").abs())
assert_frame_equal(result, df)


def test_abs_non_numeric() -> None:
df = pl.DataFrame({"a": ["p", "q", "r"]})
with pytest.raises(
pl.InvalidOperationError, match="`abs` operation not supported for dtype `str`"
):
df.select(pl.col("a").abs())


def test_abs_date() -> None:
df = pl.DataFrame({"date": [date(1960, 1, 1), date(1970, 1, 1), date(1980, 1, 1)]})

with pytest.raises(
pl.InvalidOperationError, match="`abs` operation not supported for dtype `date`"
):
df.select(pl.col("date").abs())


def test_abs_series_builtin() -> None:
s = pl.Series("a", [-1, 0, 1, None])
result = abs(s)
expected = pl.Series("a", [1, 0, 1, None])
assert_series_equal(result, expected)
20 changes: 0 additions & 20 deletions py-polars/tests/unit/series/test_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -1654,21 +1654,6 @@ def test_temporal_comparison(
)


def test_abs() -> None:
# ints
s = pl.Series([1, -2, 3, -4])
assert_series_equal(s.abs(), pl.Series([1, 2, 3, 4]))
assert_series_equal(cast(pl.Series, np.abs(s)), pl.Series([1, 2, 3, 4]))

# floats
s = pl.Series([1.0, -2.0, 3, -4.0])
assert_series_equal(s.abs(), pl.Series([1.0, 2.0, 3.0, 4.0]))
assert_series_equal(cast(pl.Series, np.abs(s)), pl.Series([1.0, 2.0, 3.0, 4.0]))
assert_series_equal(
pl.select(pl.lit(s).abs()).to_series(), pl.Series([1.0, 2.0, 3.0, 4.0])
)


def test_to_dummies() -> None:
s = pl.Series("a", [1, 2, 3])
result = s.to_dummies()
Expand Down Expand Up @@ -2399,11 +2384,6 @@ def test_repr_html(df: pl.DataFrame) -> None:
assert "<table" in html


def test_builtin_abs() -> None:
s = pl.Series("s", [-1, 0, 1, None])
assert abs(s).to_list() == [1, 0, 1, None]


@pytest.mark.parametrize(
("value", "time_unit", "exp", "exp_type"),
[
Expand Down

0 comments on commit f713e22

Please sign in to comment.