Rounding
coola.rounding ¶
Provide functions to round numerical values.
The values can be native numbers, NumPy arrays, PyTorch tensors, or
pandas, polars and pyarrow Series, arrays and tables. A rounder can be
registered for another type with register_rounders.
coola.rounding.UnsupportedTypeError ¶
Bases: TypeError
Raised when an object, or its dtype, cannot be rounded.
Example
>>> from coola.rounding import UnsupportedTypeError, round_values
>>> try:
... round_values("abc")
... except UnsupportedTypeError as error:
... print(type(error).__name__)
...
UnsupportedTypeError
coola.rounding.get_default_registry ¶
get_default_registry() -> TypeRegistry[Rounder]
Return the default registry of the rounders.
The registry is created on the first call and reused on all the
subsequent calls (singleton pattern). It has the rounders of the
native numbers, and the rounders of NumPy, PyTorch, pandas, polars
and pyarrow, which are loaded the first time an object of the
library is rounded. The plugins of the group
coola.rounding.rounders are applied when it is created.
Returns:
| Type | Description |
|---|---|
TypeRegistry[Rounder]
|
The singleton registry. |
Example
>>> from coola.rounding import get_default_registry
>>> get_default_registry().has(float)
True
coola.rounding.register_rounders ¶
register_rounders(
mapping: Mapping[type, Rounder],
*,
exist_ok: bool = False,
) -> None
Register rounders into the default registry.
A rounder is a function (data, mode, decimals) -> result that
rounds the objects of one type. mode and decimals are
validated by round_values. The function returns a new object,
and raises UnsupportedTypeError for a type or a dtype that it
does not support. The rounder of the most specific type along the
MRO of the object is used.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mapping
|
Mapping[type, Rounder]
|
The mapping from types to rounders. |
required |
exist_ok
|
bool
|
If |
False
|
Example
>>> from coola.rounding import register_rounders, round_values
>>> class Meters:
... def __init__(self, value):
... self.value = value
...
>>> def round_meters(data, mode, decimals):
... return Meters(round_values(data.value, mode=mode, decimals=decimals))
...
>>> register_rounders({Meters: round_meters})
>>> round_values(Meters(2.567), decimals=1).value
2.6
coola.rounding.round_values ¶
round_values(
data: Any,
*,
mode: RoundMode = "round",
decimals: int = 0,
recursive: bool = False,
on_unsupported: Literal["raise", "ignore"] = "ignore",
on_missing_package: MissingPackagePolicy = "raise",
registry: TypeRegistry[Rounder] | None = None,
) -> Any
Round the numerical values of an object.
The supported objects are the following ones, and a rounder can be
registered for another type with register_rounders:
- the native
intandfloat; - the NumPy arrays and scalars;
- the PyTorch tensors;
- the pandas Series and DataFrames;
- the polars Series and DataFrames;
- the pyarrow Arrays, ChunkedArrays, Tables and RecordBatches.
The integer values are never changed, whatever the mode and the
number of decimals. A floating-point value stays a floating-point
value of the same type (round_values(2.5) is 2.0, not
2). NaN, the infinite values and the missing values are
kept. The input is not modified and the result never shares
memory with it.
The tables (pandas, polars and pyarrow) are heterogeneous: only
their floating-point columns are rounded and the other columns
(integers, strings, booleans, dates, ...) are left as they are. A
Series, an array or a tensor must be numerical. The date and time
types, the complex numbers and the other objects (Decimal,
Fraction, containers, ...) are not supported.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Any
|
The object to round. |
required |
mode
|
RoundMode
|
The rounding mode. |
'round'
|
decimals
|
int
|
The number of decimals to keep. It must be an
integer (a NumPy integer is accepted) greater than or equal
to |
0
|
recursive
|
bool
|
If |
False
|
on_unsupported
|
Literal['raise', 'ignore']
|
What to do with a leaf that has no rounder
when |
'ignore'
|
on_missing_package
|
MissingPackagePolicy
|
What to do when the rounder of an object
needs a package that is not installed (NumPy for the
polars and pyarrow objects). |
'raise'
|
registry
|
TypeRegistry[Rounder] | None
|
The registry used to find the rounder of each type.
If |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
The object with the rounded values, with the same type, dtype
and shape as |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
TypeError
|
If |
UnsupportedTypeError
|
If |
Notes
One rule is used for the three modes and for every library. A
float is read as the decimal that repr shows (the shortest
string that round-trips in its dtype). "floor", "ceil"
and "round" (halfway cases to the even digit) act on this
decimal, and the result is the float of the dtype that is
nearest to the grid point. So round_values(0.29,
mode="floor", decimals=2) is 0.29 and not 0.28, and
round_values(2.675, decimals=2) is 2.68.
This differs from the Python round function and from
numpy.round, which round the exact binary value (Python)
or scale and round (NumPy), so the halfway cases of a decimal
literal give another result (round(2.675, 2) is 2.67).
The result equals Decimal(repr(x)).quantize(...) while
abs(x) * 10**decimals is below 1e13. Above, it is
within one grid step of it. A value that is zero after the
rounding has the sign of the input (-0.4 ceiled is
-0.0, like numpy.ceil).
Example
>>> from coola.rounding import round_values
>>> round_values(2.567, decimals=1)
2.6
>>> round_values(2.567, mode="floor", decimals=2)
2.56
>>> round_values(2.567, mode="ceil")
3.0
>>> round_values({"a": [1.234, "x"], "b": (5.678, 3)}, decimals=1, recursive=True)
{'a': [1.2, 'x'], 'b': (5.7, 3)}
>>> import numpy as np
>>> round_values(np.array([1.5, 2.5, -0.5]))
array([ 2., 2., -0.])
>>> import torch
>>> round_values(torch.tensor([1.234, 5.678]), mode="ceil", decimals=1)
tensor([1.3000, 5.7000])
>>> import pandas as pd
>>> round_values(pd.DataFrame({"a": [1.234, 5.678], "b": [1, 2]}), decimals=2)
a b
0 1.23 1
1 5.68 2