diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index d6fd017ff0897..ffacd82b35056 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -54,6 +54,7 @@ Other enhancements - Add support for dict-like names in :class:`MultiIndex.set_names` and :class:`MultiIndex.rename` (:issue:`20421`) - :func:`pandas.read_excel` can now auto detect .xlsb files (:issue:`35416`) - :meth:`.Rolling.sum`, :meth:`.Expanding.sum`, :meth:`.Rolling.mean`, :meth:`.Expanding.mean`, :meth:`.Rolling.median`, :meth:`.Expanding.median`, :meth:`.Rolling.max`, :meth:`.Expanding.max`, :meth:`.Rolling.min`, and :meth:`.Expanding.min` now support ``Numba`` execution with the ``engine`` keyword (:issue:`38895`) +- Added :meth:`NumericArray.round` (:issue:`38844`) .. --------------------------------------------------------------------------- diff --git a/pandas/core/arrays/numeric.py b/pandas/core/arrays/numeric.py index 3c115ec42f6ec..0325b008d88ee 100644 --- a/pandas/core/arrays/numeric.py +++ b/pandas/core/arrays/numeric.py @@ -1,10 +1,12 @@ import datetime -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING, Callable, Union import numpy as np from pandas._libs import Timedelta, missing as libmissing +from pandas.compat.numpy import function as nv from pandas.errors import AbstractMethodError +from pandas.util._decorators import doc from pandas.core.dtypes.common import ( is_float, @@ -56,6 +58,32 @@ def __from_arrow__( return array_class._concat_same_type(results) +_round_doc = """ +Round each value in NumericArray a to the given number of decimals. + +Parameters +---------- +decimals : int, default 0 + Number of decimal places to round to. If decimals is negative, + it specifies the number of positions to the left of the decimal point. +*args, **kwargs + Additional arguments and keywords have no effect but might be + accepted for compatibility with NumPy. + +Returns +------- +NumericArray + Rounded values of the NumericArray. + +See Also +-------- +numpy.around : Round values of an np.array. +DataFrame.round : Round values of a DataFrame. +Series.round : Round values of a Series. + +""" + + class NumericArray(BaseMaskedArray): """ Base class for IntegerArray and FloatingArray. @@ -130,3 +158,16 @@ def _arith_method(self, other, op): ) return self._maybe_mask_result(result, mask, other, op_name) + + def _apply(self, func: Callable, **kwargs) -> "NumericArray": + values = self._data[~self._mask] + values = np.round(values, **kwargs) + + data = np.zeros(self._data.shape) + data[~self._mask] = values + return type(self)(data, self._mask) + + @doc(_round_doc) + def round(self, decimals: int = 0, *args, **kwargs) -> "NumericArray": + nv.validate_round(args, kwargs) + return self._apply(np.round, decimals=decimals, **kwargs) diff --git a/pandas/tests/series/methods/test_round.py b/pandas/tests/series/methods/test_round.py index 88d5c428712dc..0f0a4e9adaa80 100644 --- a/pandas/tests/series/methods/test_round.py +++ b/pandas/tests/series/methods/test_round.py @@ -35,15 +35,23 @@ def test_round_numpy_with_nan(self): expected = Series([2.0, np.nan, 0.0]) tm.assert_series_equal(result, expected) - def test_round_builtin(self): - ser = Series([1.123, 2.123, 3.123], index=range(3)) - result = round(ser) - expected_rounded0 = Series([1.0, 2.0, 3.0], index=range(3)) + def test_round_builtin(self, any_float_allowed_nullable_dtype): + ser = Series( + [1.123, 2.123, 3.123], + index=range(3), + dtype=any_float_allowed_nullable_dtype, + ) + result = round(ser).astype(any_float_allowed_nullable_dtype) + expected_rounded0 = Series( + [1.0, 2.0, 3.0], index=range(3), dtype=any_float_allowed_nullable_dtype + ) tm.assert_series_equal(result, expected_rounded0) decimals = 2 - expected_rounded = Series([1.12, 2.12, 3.12], index=range(3)) - result = round(ser, decimals) + expected_rounded = Series( + [1.12, 2.12, 3.12], index=range(3), dtype=any_float_allowed_nullable_dtype + ) + result = round(ser, decimals).astype(any_float_allowed_nullable_dtype) tm.assert_series_equal(result, expected_rounded) @pytest.mark.parametrize("method", ["round", "floor", "ceil"])