Skip to content

Commit 13181a1

Browse files
authored
feat: add mode option to round (#375)
Context: pola-rs/polars#21800 Adds optional round mode option to series and expr, defaults to `halftoeven`
1 parent d827438 commit 13181a1

File tree

6 files changed

+57
-12
lines changed

6 files changed

+57
-12
lines changed

__tests__/expr.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,22 @@ describe("expr", () => {
791791
const actual = df.select(col("a").round({ decimals: 2 }).as("rounded"));
792792
expect(actual).toFrameStrictEqual(expected);
793793
});
794+
test("round:halfawayfromzero:opt", () => {
795+
const df = pl.DataFrame({ a: [1.00523, 2.35878, 3.3349999] });
796+
const expected = pl.DataFrame({ rounded: [1.01, 2.36, 3.33] });
797+
const actual = df.select(
798+
col("a").round({ decimals: 2, mode: "halfawayfromzero" }).as("rounded"),
799+
);
800+
expect(actual).toFrameStrictEqual(expected);
801+
});
802+
test("round:halfawayfromzero", () => {
803+
const df = pl.DataFrame({ a: [1.00523, 2.35878, 3.3349999] });
804+
const expected = pl.DataFrame({ rounded: [1.01, 2.36, 3.33] });
805+
const actual = df.select(
806+
col("a").round(2, "halfawayfromzero").as("rounded"),
807+
);
808+
expect(actual).toFrameStrictEqual(expected);
809+
});
794810
test("sample", () => {
795811
const df = pl.DataFrame({ n: [1, 2, 3] });
796812
let actual = df.withColumns(

__tests__/series.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,18 @@ describe("series functions", () => {
720720
const actual = s.round(2);
721721
expect(actual).toSeriesEqual(expected);
722722
});
723+
test("round:halfawayfromzero", () => {
724+
const s = pl.Series([1.5, 2.5, -1.5, -2.5]);
725+
const expected = pl.Series([2, 3, -2, -3]);
726+
const actual = s.round(0, "halfawayfromzero");
727+
expect(actual).toSeriesEqual(expected);
728+
});
729+
test("round:halfawayfromzero:opt", () => {
730+
const s = pl.Series([1.5, 2.5, -1.5, -2.5]);
731+
const expected = pl.Series([2, 3, -2, -3]);
732+
const actual = s.round({ decimals: 0, mode: "halfawayfromzero" });
733+
expect(actual).toSeriesEqual(expected);
734+
});
723735
test("round:named", () => {
724736
const s = pl.Series([1.1111, 2.2222]);
725737
const expected = pl.Series([1.11, 2.22]);

polars/lazy/expr/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import type {
2727
FillNullStrategy,
2828
InterpolationMethod,
2929
RankMethod,
30+
RoundMode,
3031
} from "../../types";
3132
import {
3233
type ExprOrString,
@@ -1777,8 +1778,13 @@ export const _Expr = (_expr: any): Expr => {
17771778

17781779
return wrap("rollingSkew", val.windowSize, val.bias ?? bias);
17791780
},
1780-
round(decimals) {
1781-
return _Expr(_expr.round(decimals?.decimals ?? decimals, "halftoeven"));
1781+
round(decimals, mode?: RoundMode) {
1782+
return _Expr(
1783+
_expr.round(
1784+
decimals?.decimals ?? decimals,
1785+
decimals?.mode ?? mode ?? "halftoeven",
1786+
),
1787+
);
17821788
},
17831789
sample(opts?, frac?, withReplacement = false, seed?) {
17841790
if (opts?.n !== undefined || opts?.frac !== undefined) {

polars/series/index.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1693,15 +1693,14 @@ export function _Series(_s: any): Series {
16931693
ceil() {
16941694
return wrap("ceil");
16951695
},
1696-
round(opt): any {
1697-
const mode = "halftoeven";
1698-
if (this.isNumeric()) {
1699-
if (typeof opt === "number") {
1700-
return wrap("round", opt, mode);
1701-
}
1702-
return wrap("round", opt.decimals, mode);
1696+
round(opt, mode): any {
1697+
if (!this.isNumeric()) {
1698+
throw new InvalidOperationError("round", this.dtype);
1699+
}
1700+
if (typeof opt === "number") {
1701+
return wrap("round", opt, mode ?? "halftoeven");
17031702
}
1704-
throw new InvalidOperationError("round", this.dtype);
1703+
return wrap("round", opt.decimals, opt.mode ?? "halftoeven");
17051704
},
17061705
clip(...args) {
17071706
return expr_op("clip", ...args);

polars/shared_traits.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
RollingOptions,
77
RollingQuantileOptions,
88
RollingSkewOptions,
9+
RoundMode,
910
} from "./types";
1011
import type { ColumnsOrExpr, StartBy } from "./utils";
1112

@@ -426,10 +427,16 @@ export interface Round<T> {
426427
*
427428
* Similar functionality to javascript `toFixed`
428429
* @param decimals number of decimals to round by.
430+
* @param mode Rounding mode, the default is "half to even" (also known as "bankers' rounding").
431+
RoundMode.
432+
* *halftoeven*
433+
round to the nearest even number
434+
* *halfawayfromzero*
435+
round to the nearest number away from zero
429436
* @category Math
430437
*/
431-
round(decimals: number): T;
432-
round(options: { decimals: number }): T;
438+
round(decimals: number, mode?: RoundMode): T;
439+
round(options: { decimals: number; mode?: RoundMode }): T;
433440
/**
434441
* Floor underlying floating point array to the lowest integers smaller or equal to the float value.
435442
* Only works on floating point Series

polars/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ export type RankMethod =
3535
| "ordinal"
3636
| "random";
3737

38+
/**
39+
* Round modes
40+
*/
41+
export type RoundMode = "halftoeven" | "halfawayfromzero";
42+
3843
/**
3944
* Options for {@link concat}
4045
*/

0 commit comments

Comments
 (0)