diff --git a/doc/source/whatsnew/v1.1.0.rst b/doc/source/whatsnew/v1.1.0.rst index d283d4450e6bf..0ecec532d4340 100644 --- a/doc/source/whatsnew/v1.1.0.rst +++ b/doc/source/whatsnew/v1.1.0.rst @@ -88,6 +88,7 @@ Other enhancements - :class:`Series.str` now has a `fullmatch` method that matches a regular expression against the entire string in each row of the series, similar to `re.fullmatch` (:issue:`32806`). - :meth:`DataFrame.sample` will now also allow array-like and BitGenerator objects to be passed to ``random_state`` as seeds (:issue:`32503`) - :meth:`MultiIndex.union` will now raise `RuntimeWarning` if the object inside are unsortable, pass `sort=False` to suppress this warning (:issue:`33015`) +- :func:`to_numeric` will now support downcasting of nullable dtypes. - .. --------------------------------------------------------------------------- diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index 223cc43d158e6..e1ead4afd2486 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -143,9 +143,7 @@ def maybe_downcast_to_dtype(result, dtype): else: dtype = "object" - dtype = np.dtype(dtype) - converted = maybe_downcast_numeric(result, dtype, do_round) if converted is not result: return converted @@ -180,7 +178,8 @@ def maybe_downcast_to_dtype(result, dtype): def maybe_downcast_numeric(result, dtype, do_round: bool = False): """ - Subset of maybe_downcast_to_dtype restricted to numeric dtypes. + Subset of maybe_downcast_to_dtype restricted to numeric and + nullable dtypes. Parameters ---------- @@ -210,9 +209,7 @@ def trans(x): # don't allow upcasts here (except if empty) if result.dtype.itemsize <= dtype.itemsize and result.size: return result - if is_bool_dtype(dtype) or is_integer_dtype(dtype): - if not result.size: # if we don't have any elements, just astype it return trans(result).astype(dtype) @@ -239,7 +236,9 @@ def trans(x): if (new_result == result).all(): return new_result else: - if np.allclose(new_result, result, rtol=0): + # np.allclose raises TypeError on extension arrays + nd_result = np.array(result).astype(result[0].dtype) + if np.allclose(new_result, nd_result, rtol=0): return new_result elif ( diff --git a/pandas/core/tools/numeric.py b/pandas/core/tools/numeric.py index f4eb16602f8a0..cdda33ab6941b 100644 --- a/pandas/core/tools/numeric.py +++ b/pandas/core/tools/numeric.py @@ -159,7 +159,6 @@ def to_numeric(arg, errors="raise", downcast=None): # to a numerical dtype and if a downcast method has been specified if downcast is not None and is_numeric_dtype(values): typecodes = None - if downcast in ("integer", "signed"): typecodes = np.typecodes["Integer"] elif downcast == "unsigned" and (not len(values) or np.min(values) >= 0): diff --git a/pandas/tests/tools/test_to_numeric.py b/pandas/tests/tools/test_to_numeric.py index 263887a8ea36e..0b24e5b0a843d 100644 --- a/pandas/tests/tools/test_to_numeric.py +++ b/pandas/tests/tools/test_to_numeric.py @@ -649,3 +649,14 @@ def test_failure_to_convert_uint64_string_to_NaN(): ser = Series([32, 64, np.nan]) result = to_numeric(pd.Series(["32", "64", "uint64"]), errors="coerce") tm.assert_series_equal(result, ser) + + +def test_support_downcast_of_nullable_dtypes(): + # GH 33013 + try: + pd.to_numeric(pd.Series([1, 2, 3], dtype="Int32"), downcast="integer") + pd.to_numeric(pd.Series([1, 2, 3], dtype="Int64"), downcast="integer") + pd.to_numeric(pd.Series([1, 2], dtype="Int32"), downcast="signed") + pd.to_numeric(pd.Series([1, 2, 3], dtype="Int32"), downcast="float") + except TypeError: + pytest.fail("TypeError raised.")