From ffd643ab43a0b023d66c540355c6267ad9e4d115 Mon Sep 17 00:00:00 2001 From: Brett Date: Fri, 8 Aug 2025 20:03:33 -0700 Subject: [PATCH 1/3] BUG: Series.round with pd.NA no longer raises; coerce object+NA to nullable float and round; add test (#61712) --- pandas/core/series.py | 7 ++++++- pandas/tests/series/methods/test_round.py | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/pandas/core/series.py b/pandas/core/series.py index 6ae03f2464f76..1d43cb5866ea5 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -2516,7 +2516,12 @@ def round(self, decimals: int = 0, *args, **kwargs) -> Series: """ nv.validate_round(args, kwargs) if self.dtype == "object": - raise TypeError("Expected numeric dtype, got object instead.") + # Try to safely cast to numeric type (like float) if values are pd.NA or numbers + try: + converted = self.astype("Float64") + return converted.round(decimals) + except Exception: + 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" diff --git a/pandas/tests/series/methods/test_round.py b/pandas/tests/series/methods/test_round.py index a78f77e990ae1..0f5c168d354fb 100644 --- a/pandas/tests/series/methods/test_round.py +++ b/pandas/tests/series/methods/test_round.py @@ -79,3 +79,11 @@ def test_round_dtype_object(self): msg = "Expected numeric dtype, got object instead." with pytest.raises(TypeError, match=msg): ser.round() + + def test_round_with_pd_na(self): + import pandas as pd + import numpy as np + s = pd.Series([0.5, pd.NA], dtype="object") + result = s.round(0) + expected = pd.Series([0.0, pd.NA], dtype="Float64") + pd.testing.assert_series_equal(result, expected) From 4c5e20495cec21b25377b0a0d6bf178aabd607e4 Mon Sep 17 00:00:00 2001 From: Brett Date: Fri, 8 Aug 2025 21:24:23 -0700 Subject: [PATCH 2/3] Fix Series.round TypeError on object dtype with pd.NA (GH#61712) + tests + whatsnew --- doc/source/whatsnew/v3.0.0.rst | 3 ++- pandas/core/series.py | 7 ++++--- pandas/tests/series/methods/test_round.py | 9 ++++----- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index e9996dcf8bc6a..83084f78ccc3b 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -82,6 +82,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` raising ``TypeError`` for ``object`` dtype with ``pd.NA``. The result now coerces to nullable ``Float64`` and rounds as expected (:issue:`61712`). - :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`) @@ -97,7 +98,7 @@ Other enhancements - Support passing a :class:`Iterable[Hashable]` input to :meth:`DataFrame.drop_duplicates` (:issue:`59237`) - Support reading Stata 102-format (Stata 1) dta files (:issue:`58978`) - Support reading Stata 110-format (Stata 7) dta files (:issue:`47176`) -- + .. --------------------------------------------------------------------------- .. _whatsnew_300.notable_bug_fixes: diff --git a/pandas/core/series.py b/pandas/core/series.py index 1d43cb5866ea5..8f1f4605d4046 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -2516,12 +2516,13 @@ def round(self, decimals: int = 0, *args, **kwargs) -> Series: """ nv.validate_round(args, kwargs) if self.dtype == "object": - # Try to safely cast to numeric type (like float) if values are pd.NA or numbers + # Try to safely cast to a numeric nullable dtype when + # values are numbers or pd.NA. try: converted = self.astype("Float64") return converted.round(decimals) - except Exception: - raise TypeError("Expected numeric dtype, got object instead.") + except Exception as err: + raise TypeError("Expected numeric dtype, got object instead.") from err new_mgr = self._mgr.round(decimals=decimals) return self._constructor_from_mgr(new_mgr, axes=new_mgr.axes).__finalize__( self, method="round" diff --git a/pandas/tests/series/methods/test_round.py b/pandas/tests/series/methods/test_round.py index 0f5c168d354fb..0779a00b2d79d 100644 --- a/pandas/tests/series/methods/test_round.py +++ b/pandas/tests/series/methods/test_round.py @@ -81,9 +81,8 @@ def test_round_dtype_object(self): ser.round() def test_round_with_pd_na(self): - import pandas as pd - import numpy as np - s = pd.Series([0.5, pd.NA], dtype="object") + # GH61712 + s = Series([0.5, pd.NA], dtype="object") result = s.round(0) - expected = pd.Series([0.0, pd.NA], dtype="Float64") - pd.testing.assert_series_equal(result, expected) + expected = Series([0.0, pd.NA], dtype="Float64") + tm.assert_series_equal(result, expected) From d78b8ce0a5bd99d2611bd7ba1185fedd93a47720 Mon Sep 17 00:00:00 2001 From: Brett Date: Sat, 9 Aug 2025 04:08:48 -0700 Subject: [PATCH 3/3] Series.round: only coerce object dtype when pd.NA present; preserve legacy TypeError otherwise (GH#61712) --- pandas/core/series.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/pandas/core/series.py b/pandas/core/series.py index 8f1f4605d4046..a6996867fb541 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -2516,13 +2516,21 @@ def round(self, decimals: int = 0, *args, **kwargs) -> Series: """ nv.validate_round(args, kwargs) if self.dtype == "object": - # Try to safely cast to a numeric nullable dtype when - # values are numbers or pd.NA. - try: - converted = self.astype("Float64") - return converted.round(decimals) - except Exception as err: - raise TypeError("Expected numeric dtype, got object instead.") from err + # If the object-dtype Series contains pd.NA and is otherwise numeric, + # cast to nullable Float64 and round. Otherwise, fall back to the + # existing implementation to preserve prior behavior (incl. errors). + has_pd_na = self.isna().any() + if has_pd_na: + try: + converted = self.astype("Float64") + except Exception as err: + raise TypeError( + "Expected numeric dtype, got object instead." + ) from err + else: + return converted.round(decimals) + + 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"