diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index 672e60c9dcde5..5155662d2f97d 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -649,10 +649,12 @@ def astype_nansafe(arr, dtype, copy=True): if issubclass(dtype.type, text_type): # in Py3 that's str, in Py2 that's unicode return lib.astype_unicode(arr.ravel()).reshape(arr.shape) + elif issubclass(dtype.type, string_types): return lib.astype_str(arr.ravel()).reshape(arr.shape) + elif is_datetime64_dtype(arr): - if dtype == object: + if is_object_dtype(dtype): return tslib.ints_to_pydatetime(arr.view(np.int64)) elif dtype == np.int64: return arr.view(dtype) @@ -666,10 +668,10 @@ def astype_nansafe(arr, dtype, copy=True): to_dtype=dtype)) elif is_timedelta64_dtype(arr): - if dtype == np.int64: - return arr.view(dtype) - elif dtype == object: + if is_object_dtype(dtype): return tslib.ints_to_pytimedelta(arr.view(np.int64)) + elif dtype == np.int64: + return arr.view(dtype) # in py3, timedelta64[ns] are int64 if ((PY3 and dtype not in [_INT64_DTYPE, _TD_DTYPE]) or @@ -696,9 +698,21 @@ def astype_nansafe(arr, dtype, copy=True): raise ValueError('Cannot convert non-finite values (NA or inf) to ' 'integer') - elif is_object_dtype(arr.dtype) and np.issubdtype(dtype.type, np.integer): + elif is_object_dtype(arr): + # work around NumPy brokenness, #1987 - return lib.astype_intsafe(arr.ravel(), dtype).reshape(arr.shape) + if np.issubdtype(dtype.type, np.integer): + return lib.astype_intsafe(arr.ravel(), dtype).reshape(arr.shape) + + # if we have a datetime/timedelta array of objects + # then coerce to a proper dtype and recall astype_nansafe + + elif is_datetime64_dtype(dtype): + from pandas import to_datetime + return astype_nansafe(to_datetime(arr).values, dtype, copy=copy) + elif is_timedelta64_dtype(dtype): + from pandas import to_timedelta + return astype_nansafe(to_timedelta(arr).values, dtype, copy=copy) if dtype.name in ("datetime64", "timedelta64"): msg = ("Passing in '{dtype}' dtype with no frequency is " @@ -709,7 +723,6 @@ def astype_nansafe(arr, dtype, copy=True): dtype = np.dtype(dtype.name + "[ns]") if copy: - return arr.astype(dtype, copy=True) return arr.view(dtype) diff --git a/pandas/tests/frame/test_dtypes.py b/pandas/tests/frame/test_dtypes.py index 70eee5984b438..38bdecc9eb88f 100644 --- a/pandas/tests/frame/test_dtypes.py +++ b/pandas/tests/frame/test_dtypes.py @@ -640,6 +640,22 @@ def test_astype_categoricaldtype_class_raises(self, cls): with tm.assert_raises_regex(TypeError, xpr): df['A'].astype(cls) + @pytest.mark.parametrize("dtype", ["M8", "m8"]) + @pytest.mark.parametrize("unit", ['ns', 'us', 'ms', 's', 'h', 'm', 'D']) + def test_astype_from_datetimelike_to_objectt(self, dtype, unit): + # tests astype to object dtype + # gh-19223 / gh-12425 + dtype = "{}[{}]".format(dtype, unit) + arr = np.array([[1, 2, 3]], dtype=dtype) + df = DataFrame(arr) + result = df.astype(object) + assert (result.dtypes == object).all() + + if dtype.startswith('M8'): + assert result.iloc[0, 0] == pd.to_datetime(1, unit=unit) + else: + assert result.iloc[0, 0] == pd.to_timedelta(1, unit=unit) + @pytest.mark.parametrize("arr_dtype", [np.int64, np.float64]) @pytest.mark.parametrize("dtype", ["M8", "m8"]) @pytest.mark.parametrize("unit", ['ns', 'us', 'ms', 's', 'h', 'm', 'D'])