diff --git a/pandas/core/tools/numeric.py b/pandas/core/tools/numeric.py index 7d2bb75934c33..104b625d08e45 100644 --- a/pandas/core/tools/numeric.py +++ b/pandas/core/tools/numeric.py @@ -102,18 +102,22 @@ def to_numeric(arg, errors="raise", downcast=None): 1 2 2 -3 dtype: int8 - >>> s = pd.Series(['apple', '1.0', '2', -3]) + >>> s = pd.Series(['apple', '1.0', '2', -3, pd.to_datetime(0), pd.NaT]) >>> pd.to_numeric(s, errors='ignore') - 0 apple - 1 1.0 - 2 2 - 3 -3 + 0 apple + 1 1.0 + 2 2 + 3 -3 + 4 1970-01-01 00:00:00 + 5 NaT dtype: object >>> pd.to_numeric(s, errors='coerce') 0 NaN 1 1.0 2 2.0 3 -3.0 + 4 NaN + 5 NaN dtype: float64 Downcasting of nullable integer and floating dtypes is supported: @@ -137,6 +141,15 @@ def to_numeric(arg, errors="raise", downcast=None): if errors not in ("ignore", "raise", "coerce"): raise ValueError("invalid error value specified") + # Handle inputs of "date" type as objects + arg_dtype = getattr(arg, "dtype", None) + if is_datetime_or_timedelta_dtype(arg_dtype): + try: + arg = arg._constructor(arg, dtype="O") + except AttributeError: + # when `arg` is a a numpy array + arg = arg.astype("O") + is_series = False is_index = False is_scalars = False @@ -146,10 +159,7 @@ def to_numeric(arg, errors="raise", downcast=None): values = arg.values elif isinstance(arg, ABCIndex): is_index = True - if needs_i8_conversion(arg.dtype): - values = arg.asi8 - else: - values = arg.values + values = arg.values elif isinstance(arg, (list, tuple)): values = np.array(arg, dtype="O") elif is_scalar(arg): diff --git a/pandas/tests/tools/test_to_numeric.py b/pandas/tests/tools/test_to_numeric.py index 643a5617abbeb..96afbff7dfbb7 100644 --- a/pandas/tests/tools/test_to_numeric.py +++ b/pandas/tests/tools/test_to_numeric.py @@ -77,6 +77,30 @@ def test_series(last_val): tm.assert_series_equal(result, expected) +@pytest.mark.parametrize( + "list_data,kwargs", + [ + (["-3.14", 7], {}), + ( + ["-3.14", 7, pd.to_datetime(0), pd.NaT, ["30", -10]], + {"errors": "coerce"} + ), + ( + ["-3.14", 7, pd.to_datetime(0), pd.NaT, ["30", -10]], + {"errors": "ignore"} + ), + ] +) +def test_list_series(list_data, kwargs): + lis = list_data + ser = Series(list_data) + + result = to_numeric(lis, **kwargs) + expected = to_numeric(ser, **kwargs).values + + tm.assert_numpy_array_equal(result, expected) + + @pytest.mark.parametrize( "data", [ @@ -111,10 +135,31 @@ def test_error(data, msg): @pytest.mark.parametrize( - "errors,exp_data", [("ignore", [1, -3.14, "apple"]), ("coerce", [1, -3.14, np.nan])] + "data,msg", + [ + ([22.06, "-86", pd.NaT], "Invalid object type at position 2"), + ( + [pd.to_datetime(0), 22.06, "-86", pd.NaT], + "Invalid object type at position 0" + ), + ], +) +def test_type_error(data, msg): + ser = Series(data) + + with pytest.raises(TypeError, match=msg): + to_numeric(ser, errors="raise") + + +@pytest.mark.parametrize( + "errors,exp_data", + [ + ("ignore", [1, -3.14, "apple", pd.to_datetime(0), pd.NaT]), + ("coerce", [1, -3.14, np.nan, np.nan, np.nan]) + ], ) def test_ignore_error(errors, exp_data): - ser = Series([1, -3.14, "apple"]) + ser = Series([1, -3.14, "apple", pd.to_datetime(0), pd.NaT]) result = to_numeric(ser, errors=errors) expected = Series(exp_data) @@ -372,39 +417,6 @@ def test_str(data, exp, transform_assert_equal): assert_equal(result, expected) -def test_datetime_like(tz_naive_fixture, transform_assert_equal): - transform, assert_equal = transform_assert_equal - idx = pd.date_range("20130101", periods=3, tz=tz_naive_fixture) - - result = to_numeric(transform(idx)) - expected = transform(idx.asi8) - assert_equal(result, expected) - - -def test_timedelta(transform_assert_equal): - transform, assert_equal = transform_assert_equal - idx = pd.timedelta_range("1 days", periods=3, freq="D") - - result = to_numeric(transform(idx)) - expected = transform(idx.asi8) - assert_equal(result, expected) - - -def test_period(transform_assert_equal): - transform, assert_equal = transform_assert_equal - - idx = pd.period_range("2011-01", periods=3, freq="M", name="") - inp = transform(idx) - - if isinstance(inp, Index): - result = to_numeric(inp) - expected = transform(idx.asi8) - assert_equal(result, expected) - else: - # TODO: PeriodDtype, so support it in to_numeric. - pytest.skip("Missing PeriodDtype support in to_numeric") - - @pytest.mark.parametrize( "errors,expected", [ @@ -450,7 +462,6 @@ def test_errors_invalid_value(): [ ["1", 2, 3], [1, 2, 3], - np.array(["1970-01-02", "1970-01-03", "1970-01-04"], dtype="datetime64[D]"), ], ) @pytest.mark.parametrize( @@ -478,7 +489,6 @@ def test_downcast_basic(data, kwargs, exp_dtype): [ ["1", 2, 3], [1, 2, 3], - np.array(["1970-01-02", "1970-01-03", "1970-01-04"], dtype="datetime64[D]"), ], ) def test_signed_downcast(data, signed_downcast):