Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/whatsnew/v3.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ Other enhancements
- :meth:`Series.map` can now accept kwargs to pass on to func (:issue:`59814`)
- :meth:`Series.map` now accepts an ``engine`` parameter to allow execution with a third-party execution engine (:issue:`61125`)
- :meth:`Series.rank` and :meth:`DataFrame.rank` with numpy-nullable dtypes preserve ``NA`` values and return ``UInt64`` dtype where appropriate instead of casting ``NA`` to ``NaN`` with ``float64`` dtype (:issue:`62043`)
- :meth:`Series.round` and :meth:`DataFrame.round` now operate pointwise on columns of object dtype (:issue:`62174`)
- :meth:`Series.str.get_dummies` now accepts a ``dtype`` parameter to specify the dtype of the resulting DataFrame (:issue:`47872`)
- :meth:`pandas.concat` will raise a ``ValueError`` when ``ignore_index=True`` and ``keys`` is not ``None`` (:issue:`59274`)
- :py:class:`frozenset` elements in pandas objects are now natively printed (:issue:`60690`)
Expand Down
18 changes: 1 addition & 17 deletions pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,8 @@
is_dataclass,
is_dict_like,
is_float,
is_float_dtype,
is_hashable,
is_integer,
is_integer_dtype,
is_iterator,
is_list_like,
is_scalar,
Expand Down Expand Up @@ -11560,24 +11558,10 @@ def round(
def _dict_round(df: DataFrame, decimals) -> Iterator[Series]:
for col, vals in df.items():
try:
yield _series_round(vals, decimals[col])
yield vals.round(decimals[col])
except KeyError:
yield vals

def _series_round(ser: Series, decimals: int) -> Series:
if is_integer_dtype(ser.dtype) or is_float_dtype(ser.dtype):
return ser.round(decimals)
elif isinstance(ser._values, (DatetimeArray, TimedeltaArray, PeriodArray)):
# GH#57781
# TODO: also the ArrowDtype analogues?
warnings.warn(
"obj.round has no effect with datetime, timedelta, "
"or period dtypes. Use obj.dt.round(...) instead.",
UserWarning,
stacklevel=find_stack_level(),
)
return ser

nv.validate_round(args, kwargs)

if isinstance(decimals, (dict, Series)):
Expand Down
17 changes: 13 additions & 4 deletions pandas/core/internals/blocks.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import functools
import inspect
import re
from typing import (
Expand Down Expand Up @@ -345,7 +346,6 @@ def apply(self, func, **kwargs) -> list[Block]:
one
"""
result = func(self.values, **kwargs)

result = maybe_coerce_values(result)
return self._split_op_result(result)

Expand Down Expand Up @@ -1503,16 +1503,25 @@ def quantile(
def round(self, decimals: int) -> Self:
"""
Rounds the values.
If the block is not of an integer or float dtype, nothing happens.
This is consistent with DataFrame.round behavior.
(Note: Series.round would raise)
If the block is of object dtype, it will operate pointwise and possibly raise.
Otherwise, if the block is not of an integer or float dtype, nothing happens.

Parameters
----------
decimals: int,
Number of decimal places to round to.
Caller is responsible for validating this
"""
if self.dtype == _dtype_obj:
round_func = functools.partial(round, ndigits=decimals)
if self.values.ndim == 1:
values = algos.map_array(self.values, round_func)
else:
values = algos.map_array(self.values.ravel(), round_func).reshape(
self.values.shape
)
return self.make_block_same_class(values, refs=None)

if not self.is_numeric or self.is_bool:
if isinstance(self.values, (DatetimeArray, TimedeltaArray, PeriodArray)):
# GH#57781
Expand Down
2 changes: 0 additions & 2 deletions pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -2515,8 +2515,6 @@ def round(self, decimals: int = 0, *args, **kwargs) -> Series:
dtype: float64
"""
nv.validate_round(args, kwargs)
if self.dtype == "object":
raise TypeError("Expected numeric dtype, got object instead.")
new_mgr = self._mgr.round(decimals=decimals)
return self._constructor_from_mgr(new_mgr, axes=new_mgr.axes).__finalize__(
self, method="round"
Expand Down
38 changes: 38 additions & 0 deletions pandas/tests/frame/methods/test_round.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,41 @@ def test_round_empty_not_input(self):
result = df.round()
tm.assert_frame_equal(df, result)
assert df is not result

def test_round_object_columns(self):
# GH#62174
df = DataFrame(
{
"a": Series([1.1111, 2.2222, 3.3333], dtype="object"),
"b": Series([4.4444, 5.5555, 6.6666]),
"c": Series([7.7777, 8.8888, 9.9999], dtype="object"),
}
)
result = df.round(2)
expected = DataFrame(
{
"a": Series([1.11, 2.22, 3.33]),
"b": Series([4.44, 5.56, 6.67]),
"c": Series([7.78, 8.89, 10.0]),
}
)
tm.assert_frame_equal(result, expected)

def test_round_object_columns_with_dict(self):
# GH#62174
df = DataFrame(
{
"a": Series([1.1111, 2.2222, 3.3333], dtype="object"),
"b": Series([4.4444, 5.5555, 6.6666]),
"c": Series([7.7777, 8.8888, 9.9999], dtype="object"),
}
)
result = df.round({"a": 1, "b": 2, "c": 3})
expected = DataFrame(
{
"a": Series([1.1, 2.2, 3.3]),
"b": Series([4.44, 5.56, 6.67]),
"c": Series([7.778, 8.889, 10.0]),
}
)
tm.assert_frame_equal(result, expected)
15 changes: 11 additions & 4 deletions pandas/tests/series/methods/test_round.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,16 @@ def test_round_ea_boolean(self):
result.iloc[0] = False
tm.assert_series_equal(ser, expected)

def test_round_dtype_object(self):
# GH#61206
ser = Series([0.2], dtype="object")
msg = "Expected numeric dtype, got object instead."
def test_round_numeric_dtype_object(self):
# GH#61206, GH#62174
ser = Series([0.232], dtype="object")
expected = Series([0.2])
result = ser.round(1)
tm.assert_series_equal(result, expected)

def test_round_non_numeric_dtype_object(self):
# GH#62174
ser = Series(["bar"], dtype="object")
msg = "Expected numeric entries for dtype object."
with pytest.raises(TypeError, match=msg):
ser.round()
Loading