From 44e371f04063e7b6cd6abba7a063cf4b01262f35 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 18 Feb 2021 20:14:39 -0800 Subject: [PATCH 1/2] REF: consolidate paths for astype --- pandas/core/arrays/datetimelike.py | 4 +++- pandas/core/arrays/datetimes.py | 4 ++++ pandas/core/indexes/extension.py | 4 ++++ pandas/core/internals/blocks.py | 11 +++++++++++ pandas/tests/dtypes/test_common.py | 2 +- pandas/tests/frame/methods/test_astype.py | 12 ++---------- .../tests/indexes/datetimes/methods/test_astype.py | 6 +++--- pandas/tests/indexes/period/methods/test_astype.py | 6 +++--- .../tests/indexes/timedeltas/methods/test_astype.py | 6 +++--- pandas/tests/series/test_constructors.py | 2 +- 10 files changed, 35 insertions(+), 22 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 5dd55ff0f1fa2..8e1d7e607fb8a 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -60,6 +60,7 @@ Substitution, cache_readonly, ) +from pandas.util._exceptions import find_stack_level from pandas.core.dtypes.common import ( is_categorical_dtype, @@ -397,12 +398,13 @@ def astype(self, dtype, copy=True): elif is_integer_dtype(dtype): # we deliberately ignore int32 vs. int64 here. # See https://github.com/pandas-dev/pandas/issues/24381 for more. + level = find_stack_level() warnings.warn( f"casting {self.dtype} values to int64 with .astype(...) is " "deprecated and will raise in a future version. " "Use .view(...) instead.", FutureWarning, - stacklevel=3, + stacklevel=level, ) values = self.asi8 diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 05184ea02e7a2..3982a7deca2bb 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -616,6 +616,10 @@ def astype(self, dtype, copy=True): elif is_datetime64_ns_dtype(dtype): return astype_dt64_to_dt64tz(self, dtype, copy, via_utc=False) + elif self.tz is None and is_datetime64_dtype(dtype) and dtype != self.dtype: + # unit conversion e.g. datetime64[s] + return self._data.astype(dtype) + elif is_period_dtype(dtype): return self.to_period(freq=dtype.freq) return dtl.DatetimeLikeArrayMixin.astype(self, dtype, copy) diff --git a/pandas/core/indexes/extension.py b/pandas/core/indexes/extension.py index 4150ec745bd2e..0097959245686 100644 --- a/pandas/core/indexes/extension.py +++ b/pandas/core/indexes/extension.py @@ -309,6 +309,10 @@ def astype(self, dtype, copy=True): return self return self.copy() + if isinstance(dtype, np.dtype) and dtype.kind == "M" and dtype != "M8[ns]": + # For now Datetime supports this by unwrapping ndarray, but DTI doesn't + raise TypeError(f"Cannot cast {type(self._data).__name__} to dtype") + new_values = self._data.astype(dtype, copy=copy) # pass copy=False because any copying will be done in the diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index 2eb5be01c932c..dec0e62d6d17f 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -673,6 +673,17 @@ def astype(self, dtype, copy: bool = False, errors: str = "raise"): def _astype(self, dtype: DtypeObj, copy: bool) -> ArrayLike: values = self.values + if values.dtype.kind in ["m", "M"]: + values = self.array_values() + + if ( + values.dtype.kind in ["m", "M"] + and dtype.kind in ["i", "u"] + and dtype.itemsize != 8 + ): + # TODO(2.0) remove special case once deprecation on DTA/TDA is enforced + msg = rf"cannot astype a datetimelike from [{values.dtype}] to [{dtype}]" + raise TypeError(msg) if is_datetime64tz_dtype(dtype) and is_datetime64_dtype(values.dtype): return astype_dt64_to_dt64tz(values, dtype, copy, via_utc=True) diff --git a/pandas/tests/dtypes/test_common.py b/pandas/tests/dtypes/test_common.py index a5522e503c7f4..2b689364c5002 100644 --- a/pandas/tests/dtypes/test_common.py +++ b/pandas/tests/dtypes/test_common.py @@ -724,7 +724,7 @@ def test_astype_nansafe(val, typ): msg = "Cannot convert NaT values to integer" with pytest.raises(ValueError, match=msg): - with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + with tm.assert_produces_warning(FutureWarning): # datetimelike astype(int64) deprecated astype_nansafe(arr, dtype=typ) diff --git a/pandas/tests/frame/methods/test_astype.py b/pandas/tests/frame/methods/test_astype.py index 46f5a20f38941..35e958ff3a2b1 100644 --- a/pandas/tests/frame/methods/test_astype.py +++ b/pandas/tests/frame/methods/test_astype.py @@ -432,19 +432,11 @@ def test_astype_to_incorrect_datetimelike(self, unit): other = f"m8[{unit}]" df = DataFrame(np.array([[1, 2, 3]], dtype=dtype)) - msg = ( - fr"cannot astype a datetimelike from \[datetime64\[ns\]\] to " - fr"\[timedelta64\[{unit}\]\]" - fr"|(Cannot cast DatetimeArray to dtype timedelta64\[{unit}\])" - ) + msg = fr"Cannot cast DatetimeArray to dtype timedelta64\[{unit}\]" with pytest.raises(TypeError, match=msg): df.astype(other) - msg = ( - fr"cannot astype a timedelta from \[timedelta64\[ns\]\] to " - fr"\[datetime64\[{unit}\]\]" - fr"|(Cannot cast TimedeltaArray to dtype datetime64\[{unit}\])" - ) + msg = fr"Cannot cast TimedeltaArray to dtype datetime64\[{unit}\]" df = DataFrame(np.array([[1, 2, 3]], dtype=other)) with pytest.raises(TypeError, match=msg): df.astype(dtype) diff --git a/pandas/tests/indexes/datetimes/methods/test_astype.py b/pandas/tests/indexes/datetimes/methods/test_astype.py index bed7cb9b54eba..8eb0e086ec3f7 100644 --- a/pandas/tests/indexes/datetimes/methods/test_astype.py +++ b/pandas/tests/indexes/datetimes/methods/test_astype.py @@ -29,7 +29,7 @@ def test_astype(self): ) tm.assert_index_equal(result, expected) - with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + with tm.assert_produces_warning(FutureWarning): result = idx.astype(int) expected = Int64Index( [1463356800000000000] + [-9223372036854775808] * 3, @@ -39,7 +39,7 @@ def test_astype(self): tm.assert_index_equal(result, expected) rng = date_range("1/1/2000", periods=10, name="idx") - with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + with tm.assert_produces_warning(FutureWarning): result = rng.astype("i8") tm.assert_index_equal(result, Index(rng.asi8, name="idx")) tm.assert_numpy_array_equal(result.values, rng.asi8) @@ -50,7 +50,7 @@ def test_astype_uint(self): np.array([946684800000000000, 946771200000000000], dtype="uint64"), name="idx", ) - with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + with tm.assert_produces_warning(FutureWarning): tm.assert_index_equal(arr.astype("uint64"), expected) tm.assert_index_equal(arr.astype("uint32"), expected) diff --git a/pandas/tests/indexes/period/methods/test_astype.py b/pandas/tests/indexes/period/methods/test_astype.py index 943b2605363c7..73439d349bebd 100644 --- a/pandas/tests/indexes/period/methods/test_astype.py +++ b/pandas/tests/indexes/period/methods/test_astype.py @@ -37,7 +37,7 @@ def test_astype_conversion(self): ) tm.assert_index_equal(result, expected) - with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + with tm.assert_produces_warning(FutureWarning): result = idx.astype(np.int64) expected = Int64Index( [16937] + [-9223372036854775808] * 3, dtype=np.int64, name="idx" @@ -49,7 +49,7 @@ def test_astype_conversion(self): tm.assert_index_equal(result, expected) idx = period_range("1990", "2009", freq="A", name="idx") - with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + with tm.assert_produces_warning(FutureWarning): result = idx.astype("i8") tm.assert_index_equal(result, Index(idx.asi8, name="idx")) tm.assert_numpy_array_equal(result.values, idx.asi8) @@ -57,7 +57,7 @@ def test_astype_conversion(self): def test_astype_uint(self): arr = period_range("2000", periods=2, name="idx") expected = UInt64Index(np.array([10957, 10958], dtype="uint64"), name="idx") - with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + with tm.assert_produces_warning(FutureWarning): tm.assert_index_equal(arr.astype("uint64"), expected) tm.assert_index_equal(arr.astype("uint32"), expected) diff --git a/pandas/tests/indexes/timedeltas/methods/test_astype.py b/pandas/tests/indexes/timedeltas/methods/test_astype.py index a849ffa98324c..c2c7a1f32ae6e 100644 --- a/pandas/tests/indexes/timedeltas/methods/test_astype.py +++ b/pandas/tests/indexes/timedeltas/methods/test_astype.py @@ -55,7 +55,7 @@ def test_astype(self): ) tm.assert_index_equal(result, expected) - with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + with tm.assert_produces_warning(FutureWarning): result = idx.astype(int) expected = Int64Index( [100000000000000] + [-9223372036854775808] * 3, dtype=np.int64, name="idx" @@ -67,7 +67,7 @@ def test_astype(self): tm.assert_index_equal(result, expected) rng = timedelta_range("1 days", periods=10) - with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + with tm.assert_produces_warning(FutureWarning): result = rng.astype("i8") tm.assert_index_equal(result, Index(rng.asi8)) tm.assert_numpy_array_equal(rng.asi8, result.values) @@ -77,7 +77,7 @@ def test_astype_uint(self): expected = pd.UInt64Index( np.array([3600000000000, 90000000000000], dtype="uint64") ) - with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + with tm.assert_produces_warning(FutureWarning): tm.assert_index_equal(arr.astype("uint64"), expected) tm.assert_index_equal(arr.astype("uint32"), expected) diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 1daeee8645f2e..6cd2a1dd180c1 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -1318,7 +1318,7 @@ def test_constructor_dtype_timedelta64(self): td.astype("int64") # invalid casting - msg = r"cannot astype a timedelta from \[timedelta64\[ns\]\] to \[int32\]" + msg = r"cannot astype a datetimelike from \[timedelta64\[ns\]\] to \[int32\]" with pytest.raises(TypeError, match=msg): td.astype("int32") From 3d51028f9e8a5cf5f5756e9364867ab67386ddf2 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 18 Feb 2021 21:07:00 -0800 Subject: [PATCH 2/2] mypy fixup --- pandas/core/internals/blocks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index dec0e62d6d17f..1377928f71915 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -679,6 +679,7 @@ def _astype(self, dtype: DtypeObj, copy: bool) -> ArrayLike: if ( values.dtype.kind in ["m", "M"] and dtype.kind in ["i", "u"] + and isinstance(dtype, np.dtype) and dtype.itemsize != 8 ): # TODO(2.0) remove special case once deprecation on DTA/TDA is enforced