From 98834d24ca73a13478c0271f3a971d48bf4690ec Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 22 Sep 2022 09:49:18 -0700 Subject: [PATCH 1/4] DEPR: enforce deprecation on Series(ts_aware, dtype=naive) --- doc/source/whatsnew/v1.6.0.rst | 2 +- pandas/core/dtypes/cast.py | 27 ++--------------- pandas/tests/frame/test_constructors.py | 37 +++++++++++++++--------- pandas/tests/series/test_constructors.py | 5 ++-- 4 files changed, 30 insertions(+), 41 deletions(-) diff --git a/doc/source/whatsnew/v1.6.0.rst b/doc/source/whatsnew/v1.6.0.rst index 3eea7081ae2de..daeb84478728d 100644 --- a/doc/source/whatsnew/v1.6.0.rst +++ b/doc/source/whatsnew/v1.6.0.rst @@ -115,7 +115,7 @@ See :ref:`install.dependencies` and :ref:`install.optional_dependencies` for mor Other API changes ^^^^^^^^^^^^^^^^^ -- +- Enforced deprecation disallowing passing a timezone-aware :class:`Timestamp` and ``dtype="datetime64[ns]"`` to :class:`Series` or :class:`DataFrame` constructors (:issue:`41555`) - .. --------------------------------------------------------------------------- diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index 75a0db3233130..dfe181453e41f 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -1726,6 +1726,7 @@ def construct_1d_arraylike_from_scalar( return subarr +# TODO: rename? remove? def _maybe_unbox_datetimelike_tz_deprecation(value: Scalar, dtype: DtypeObj): """ Wrap _maybe_unbox_datetimelike with a check for a timezone-aware Timestamp @@ -1737,31 +1738,7 @@ def _maybe_unbox_datetimelike_tz_deprecation(value: Scalar, dtype: DtypeObj): # we dont want to box dt64, in particular datetime64("NaT") value = maybe_box_datetimelike(value, dtype) - try: - value = _maybe_unbox_datetimelike(value, dtype) - except TypeError: - if ( - isinstance(value, Timestamp) - and value.tzinfo is not None - and isinstance(dtype, np.dtype) - and dtype.kind == "M" - ): - warnings.warn( - "Data is timezone-aware. Converting " - "timezone-aware data to timezone-naive by " - "passing dtype='datetime64[ns]' to " - "DataFrame or Series is deprecated and will " - "raise in a future version. Use " - "`pd.Series(values).dt.tz_localize(None)` " - "instead.", - FutureWarning, - stacklevel=find_stack_level(inspect.currentframe()), - ) - new_value = value.tz_localize(None) - return _maybe_unbox_datetimelike(new_value, dtype) - else: - raise - return value + return _maybe_unbox_datetimelike(value, dtype) def construct_1d_object_array_from_listlike(values: Sized) -> np.ndarray: diff --git a/pandas/tests/frame/test_constructors.py b/pandas/tests/frame/test_constructors.py index 37e08adcfdf88..cb2e65c40c1e8 100644 --- a/pandas/tests/frame/test_constructors.py +++ b/pandas/tests/frame/test_constructors.py @@ -2835,9 +2835,9 @@ def test_constructor_data_aware_dtype_naive(self, tz_aware_fixture, pydt): expected = DataFrame({0: [ts_naive]}) tm.assert_frame_equal(result, expected) - with tm.assert_produces_warning(FutureWarning): - result = DataFrame({0: ts}, index=[0], dtype="datetime64[ns]") - tm.assert_frame_equal(result, expected) + msg = "Cannot unbox tzaware Timestamp to tznaive dtype" + with pytest.raises(TypeError, match=msg): + DataFrame({0: ts}, index=[0], dtype="datetime64[ns]") with tm.assert_produces_warning(FutureWarning): result = DataFrame([ts], dtype="datetime64[ns]") @@ -2847,9 +2847,8 @@ def test_constructor_data_aware_dtype_naive(self, tz_aware_fixture, pydt): result = DataFrame(np.array([ts], dtype=object), dtype="datetime64[ns]") tm.assert_frame_equal(result, expected) - with tm.assert_produces_warning(FutureWarning): - result = DataFrame(ts, index=[0], columns=[0], dtype="datetime64[ns]") - tm.assert_frame_equal(result, expected) + with pytest.raises(TypeError, match=msg): + DataFrame(ts, index=[0], columns=[0], dtype="datetime64[ns]") with tm.assert_produces_warning(FutureWarning): df = DataFrame([Series([ts])], dtype="datetime64[ns]") @@ -3030,8 +3029,11 @@ def get1(obj): # TODO: make a helper in tm? class TestFromScalar: @pytest.fixture(params=[list, dict, None]) - def constructor(self, request, frame_or_series): - box = request.param + def box(self, request): + return request.param + + @pytest.fixture + def constructor(self, frame_or_series, box): extra = {"index": range(2)} if frame_or_series is DataFrame: @@ -3120,16 +3122,25 @@ def test_from_out_of_bounds_timedelta(self, constructor, cls): assert type(get1(result)) is cls - def test_tzaware_data_tznaive_dtype(self, constructor): + def test_tzaware_data_tznaive_dtype(self, constructor, box, frame_or_series): tz = "US/Eastern" ts = Timestamp("2019", tz=tz) ts_naive = Timestamp("2019") - with tm.assert_produces_warning(FutureWarning, match="Data is timezone-aware"): - result = constructor(ts, dtype="M8[ns]") + if box is None or (frame_or_series is DataFrame and box is dict): + msg = "Cannot unbox tzaware Timestamp to tznaive dtype" + with pytest.raises(TypeError, match=msg): + constructor(ts, dtype="M8[ns]") + + else: + + with tm.assert_produces_warning( + FutureWarning, match="Data is timezone-aware" + ): + result = constructor(ts, dtype="M8[ns]") - assert np.all(result.dtypes == "M8[ns]") - assert np.all(result == ts_naive) + assert np.all(result.dtypes == "M8[ns]") + assert np.all(result == ts_naive) # TODO: better location for this test? diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 4f355be7f0745..26622249367ab 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -1722,9 +1722,10 @@ def test_constructor_data_aware_dtype_naive(self, tz_aware_fixture, pydt): result = Series({0: ts}, dtype="datetime64[ns]") tm.assert_series_equal(result, expected) - with tm.assert_produces_warning(FutureWarning): + with pytest.raises( + TypeError, match="Cannot unbox tzaware Timestamp to tznaive dtype" + ): result = Series(ts, index=[0], dtype="datetime64[ns]") - tm.assert_series_equal(result, expected) def test_constructor_datetime64(self): rng = date_range("1/1/2000 00:00:00", "1/1/2000 1:59:50", freq="10s") From b84a556b0097adecf5d002bb464155a1b582fdfa Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 22 Sep 2022 13:33:02 -0700 Subject: [PATCH 2/4] DEPR: enforce deprecation on Series(tzaware_seq, dtype=naive) --- doc/source/whatsnew/v1.6.0.rst | 1 + pandas/core/dtypes/cast.py | 17 ++----- pandas/tests/frame/test_constructors.py | 57 +++++++++++------------- pandas/tests/series/test_constructors.py | 28 ++++++------ 4 files changed, 43 insertions(+), 60 deletions(-) diff --git a/doc/source/whatsnew/v1.6.0.rst b/doc/source/whatsnew/v1.6.0.rst index daeb84478728d..60b25d89fda2b 100644 --- a/doc/source/whatsnew/v1.6.0.rst +++ b/doc/source/whatsnew/v1.6.0.rst @@ -116,6 +116,7 @@ See :ref:`install.dependencies` and :ref:`install.optional_dependencies` for mor Other API changes ^^^^^^^^^^^^^^^^^ - Enforced deprecation disallowing passing a timezone-aware :class:`Timestamp` and ``dtype="datetime64[ns]"`` to :class:`Series` or :class:`DataFrame` constructors (:issue:`41555`) +- Enforced deprecation disallowing passing a sequence of timezone-aware values and ``dtype="datetime64[ns]"`` to to :class:`Series` or :class:`DataFrame` constructors (:issue:`41555`) - .. --------------------------------------------------------------------------- diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index dfe181453e41f..c39a5d8a12da1 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -1337,20 +1337,11 @@ def maybe_cast_to_datetime( # didn't specify one if dta.tz is not None: - warnings.warn( - "Data is timezone-aware. Converting " - "timezone-aware data to timezone-naive by " - "passing dtype='datetime64[ns]' to " - "DataFrame or Series is deprecated and will " - "raise in a future version. Use " - "`pd.Series(values).dt.tz_localize(None)` " - "instead.", - FutureWarning, - stacklevel=find_stack_level(inspect.currentframe()), + raise ValueError( + "Cannot convert timezone-aware data to " + "timezone-naive dtype. Use " + "pd.Series(values).dt.tz_localize(None) instead." ) - # equiv: dta.view(dtype) - # Note: NOT equivalent to dta.astype(dtype) - dta = dta.tz_localize(None) value = dta elif is_datetime64tz: diff --git a/pandas/tests/frame/test_constructors.py b/pandas/tests/frame/test_constructors.py index cb2e65c40c1e8..08f788887f5a2 100644 --- a/pandas/tests/frame/test_constructors.py +++ b/pandas/tests/frame/test_constructors.py @@ -2827,36 +2827,32 @@ def test_constructor_data_aware_dtype_naive(self, tz_aware_fixture, pydt): ts = Timestamp("2019", tz=tz) if pydt: ts = ts.to_pydatetime() - ts_naive = Timestamp("2019") - with tm.assert_produces_warning(FutureWarning): - result = DataFrame({0: [ts]}, dtype="datetime64[ns]") - - expected = DataFrame({0: [ts_naive]}) - tm.assert_frame_equal(result, expected) + msg = ( + "Cannot convert timezone-aware data to timezone-naive dtype. " + r"Use pd.Series\(values\).dt.tz_localize\(None\) instead." + ) + with pytest.raises(ValueError, match=msg): + DataFrame({0: [ts]}, dtype="datetime64[ns]") - msg = "Cannot unbox tzaware Timestamp to tznaive dtype" - with pytest.raises(TypeError, match=msg): + msg2 = "Cannot unbox tzaware Timestamp to tznaive dtype" + with pytest.raises(TypeError, match=msg2): DataFrame({0: ts}, index=[0], dtype="datetime64[ns]") - with tm.assert_produces_warning(FutureWarning): - result = DataFrame([ts], dtype="datetime64[ns]") - tm.assert_frame_equal(result, expected) + with pytest.raises(ValueError, match=msg): + DataFrame([ts], dtype="datetime64[ns]") - with tm.assert_produces_warning(FutureWarning): - result = DataFrame(np.array([ts], dtype=object), dtype="datetime64[ns]") - tm.assert_frame_equal(result, expected) + with pytest.raises(ValueError, match=msg): + DataFrame(np.array([ts], dtype=object), dtype="datetime64[ns]") - with pytest.raises(TypeError, match=msg): + with pytest.raises(TypeError, match=msg2): DataFrame(ts, index=[0], columns=[0], dtype="datetime64[ns]") - with tm.assert_produces_warning(FutureWarning): - df = DataFrame([Series([ts])], dtype="datetime64[ns]") - tm.assert_frame_equal(result, expected) + with pytest.raises(ValueError, match=msg): + DataFrame([Series([ts])], dtype="datetime64[ns]") - with tm.assert_produces_warning(FutureWarning): - df = DataFrame([[ts]], columns=[0], dtype="datetime64[ns]") - tm.assert_equal(df, expected) + with pytest.raises(ValueError, match=msg): + DataFrame([[ts]], columns=[0], dtype="datetime64[ns]") def test_from_dict(self): @@ -3125,22 +3121,19 @@ def test_from_out_of_bounds_timedelta(self, constructor, cls): def test_tzaware_data_tznaive_dtype(self, constructor, box, frame_or_series): tz = "US/Eastern" ts = Timestamp("2019", tz=tz) - ts_naive = Timestamp("2019") if box is None or (frame_or_series is DataFrame and box is dict): msg = "Cannot unbox tzaware Timestamp to tznaive dtype" - with pytest.raises(TypeError, match=msg): - constructor(ts, dtype="M8[ns]") - + err = TypeError else: + msg = ( + "Cannot convert timezone-aware data to timezone-naive dtype. " + r"Use pd.Series\(values\).dt.tz_localize\(None\) instead." + ) + err = ValueError - with tm.assert_produces_warning( - FutureWarning, match="Data is timezone-aware" - ): - result = constructor(ts, dtype="M8[ns]") - - assert np.all(result.dtypes == "M8[ns]") - assert np.all(result == ts_naive) + with pytest.raises(err, match=msg): + constructor(ts, dtype="M8[ns]") # TODO: better location for this test? diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 26622249367ab..098d23256beb1 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -1707,25 +1707,23 @@ def test_constructor_data_aware_dtype_naive(self, tz_aware_fixture, pydt): ts = Timestamp("2019", tz=tz) if pydt: ts = ts.to_pydatetime() - ts_naive = Timestamp("2019") - with tm.assert_produces_warning(FutureWarning): - result = Series([ts], dtype="datetime64[ns]") - expected = Series([ts_naive]) - tm.assert_series_equal(result, expected) + msg = ( + "Cannot convert timezone-aware data to timezone-naive dtype. " + r"Use pd.Series\(values\).dt.tz_localize\(None\) instead." + ) + with pytest.raises(ValueError, match=msg): + Series([ts], dtype="datetime64[ns]") - with tm.assert_produces_warning(FutureWarning): - result = Series(np.array([ts], dtype=object), dtype="datetime64[ns]") - tm.assert_series_equal(result, expected) + with pytest.raises(ValueError, match=msg): + Series(np.array([ts], dtype=object), dtype="datetime64[ns]") - with tm.assert_produces_warning(FutureWarning): - result = Series({0: ts}, dtype="datetime64[ns]") - tm.assert_series_equal(result, expected) + with pytest.raises(ValueError, match=msg): + Series({0: ts}, dtype="datetime64[ns]") - with pytest.raises( - TypeError, match="Cannot unbox tzaware Timestamp to tznaive dtype" - ): - result = Series(ts, index=[0], dtype="datetime64[ns]") + msg = "Cannot unbox tzaware Timestamp to tznaive dtype" + with pytest.raises(TypeError, match=msg): + Series(ts, index=[0], dtype="datetime64[ns]") def test_constructor_datetime64(self): rng = date_range("1/1/2000 00:00:00", "1/1/2000 1:59:50", freq="10s") From 7a51e8e90a809165374370fe98a72e84dfeea877 Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 23 Sep 2022 08:29:10 -0700 Subject: [PATCH 3/4] rename --- pandas/core/dtypes/cast.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index c39a5d8a12da1..d555522ad0e12 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -1643,7 +1643,7 @@ def construct_2d_arraylike_from_scalar( shape = (length, width) if dtype.kind in ["m", "M"]: - value = _maybe_unbox_datetimelike_tz_deprecation(value, dtype) + value = _maybe_box_and_unbox_datetimelike(value, dtype) elif dtype == _dtype_obj: if isinstance(value, (np.timedelta64, np.datetime64)): # calling np.array below would cast to pytimedelta/pydatetime @@ -1707,7 +1707,7 @@ def construct_1d_arraylike_from_scalar( if not isna(value): value = ensure_str(value) elif dtype.kind in ["M", "m"]: - value = _maybe_unbox_datetimelike_tz_deprecation(value, dtype) + value = _maybe_box_and_unbox_datetimelike(value, dtype) subarr = np.empty(length, dtype=dtype) if length: @@ -1717,12 +1717,7 @@ def construct_1d_arraylike_from_scalar( return subarr -# TODO: rename? remove? -def _maybe_unbox_datetimelike_tz_deprecation(value: Scalar, dtype: DtypeObj): - """ - Wrap _maybe_unbox_datetimelike with a check for a timezone-aware Timestamp - along with a timezone-naive datetime64 dtype, which is deprecated. - """ +def _maybe_box_and_unbox_datetimelike(value: Scalar, dtype: DtypeObj): # Caller is responsible for checking dtype.kind in ["m", "M"] if isinstance(value, datetime): From dd8820c78d49323602177367642bb7e323b641da Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 19 Oct 2022 09:40:17 -0700 Subject: [PATCH 4/4] move whatsnew --- doc/source/whatsnew/v2.0.0.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst index 5c2af294cf306..22e181e3433d4 100644 --- a/doc/source/whatsnew/v2.0.0.rst +++ b/doc/source/whatsnew/v2.0.0.rst @@ -118,8 +118,6 @@ See :ref:`install.dependencies` and :ref:`install.optional_dependencies` for mor Other API changes ^^^^^^^^^^^^^^^^^ -- Enforced deprecation disallowing passing a timezone-aware :class:`Timestamp` and ``dtype="datetime64[ns]"`` to :class:`Series` or :class:`DataFrame` constructors (:issue:`41555`) -- Enforced deprecation disallowing passing a sequence of timezone-aware values and ``dtype="datetime64[ns]"`` to to :class:`Series` or :class:`DataFrame` constructors (:issue:`41555`) - Passing ``nanoseconds`` greater than 999 or less than 0 in :class:`Timestamp` now raises a ``ValueError`` (:issue:`48538`, :issue:`48255`) - :func:`read_csv`: specifying an incorrect number of columns with ``index_col`` of now raises ``ParserError`` instead of ``IndexError`` when using the c parser. - Default value of ``dtype`` in :func:`get_dummies` is changed to ``bool`` from ``uint8`` (:issue:`45848`) @@ -146,6 +144,8 @@ Deprecations Removal of prior version deprecations/changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +- Enforced deprecation disallowing passing a timezone-aware :class:`Timestamp` and ``dtype="datetime64[ns]"`` to :class:`Series` or :class:`DataFrame` constructors (:issue:`41555`) +- Enforced deprecation disallowing passing a sequence of timezone-aware values and ``dtype="datetime64[ns]"`` to to :class:`Series` or :class:`DataFrame` constructors (:issue:`41555`) - Disallow passing non-round floats to :class:`Timestamp` with ``unit="M"`` or ``unit="Y"`` (:issue:`47266`) -