From 59b9e48a6d0829096a7ddf06cee2b7aa54c447c8 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 2 Jul 2019 06:46:21 -0700 Subject: [PATCH 1/4] Add EA.ravel, deprecate Categorical.ravel behavior --- doc/source/whatsnew/v0.25.0.rst | 1 + pandas/core/arrays/base.py | 15 +++++++++++++++ pandas/core/arrays/categorical.py | 2 ++ pandas/tests/extension/base/base.py | 3 +++ pandas/tests/extension/base/reshaping.py | 10 ++++++++++ pandas/tests/extension/test_categorical.py | 6 +++++- pandas/tests/extension/test_sparse.py | 1 + 7 files changed, 37 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index b7614423e11dd..b7917688ebcef 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -626,6 +626,7 @@ Other deprecations - :meth:`Series.put` is deprecated. (:issue:`18262`) - :meth:`Index.item` and :meth:`Series.item` is deprecated. (:issue:`18262`) - :meth:`Index.contains` is deprecated. Use ``key in index`` (``__contains__``) instead (:issue:`17753`). +- :meth:`Categorical.ravel` will return a :class:`Categorical` instead of a ``np.ndarray`` (:issue:`????`) .. _whatsnew_0250.prior_deprecations: diff --git a/pandas/core/arrays/base.py b/pandas/core/arrays/base.py index 0762a607f20ae..75ea81d4e51db 100644 --- a/pandas/core/arrays/base.py +++ b/pandas/core/arrays/base.py @@ -908,6 +908,21 @@ def _formatting_values(self) -> np.ndarray: # Reshaping # ------------------------------------------------------------------------ + def ravel(self, order="C") -> ABCExtensionArray: + """ + Return a flattened view on this array. + + Parameters + ---------- + order : {None, 'C', 'F', 'A', 'K'}, default 'C' + + Notes + ----- + - Because ExtensionArrays are 1D-only, this is a no-op. + - The "order" argument is ignored, is for compatibility with NumPy. + """ + return self + @classmethod def _concat_same_type( cls, diff --git a/pandas/core/arrays/categorical.py b/pandas/core/arrays/categorical.py index b77a4f985067d..339a9b93028c6 100644 --- a/pandas/core/arrays/categorical.py +++ b/pandas/core/arrays/categorical.py @@ -1707,6 +1707,8 @@ def ravel(self, order='C'): ------- numpy.array """ + warn("Categorical.ravel will return a Categorical object instead " + "of an ndarray in a future version.", FutureWarning) return np.array(self) def view(self): diff --git a/pandas/tests/extension/base/base.py b/pandas/tests/extension/base/base.py index b11603c0e185a..84f41abb59e64 100644 --- a/pandas/tests/extension/base/base.py +++ b/pandas/tests/extension/base/base.py @@ -2,6 +2,9 @@ class BaseExtensionTests: + # Whether the EA being tested supports __setitem__ + _supports_setitem = True + assert_equal = staticmethod(tm.assert_equal) assert_series_equal = staticmethod(tm.assert_series_equal) assert_frame_equal = staticmethod(tm.assert_frame_equal) diff --git a/pandas/tests/extension/base/reshaping.py b/pandas/tests/extension/base/reshaping.py index ee22ffb3ccf97..f1ac47bb0bce5 100644 --- a/pandas/tests/extension/base/reshaping.py +++ b/pandas/tests/extension/base/reshaping.py @@ -269,3 +269,13 @@ def test_unstack(self, data, index, obj): result = result.astype(object) self.assert_frame_equal(result, expected) + + def test_ravel(self, data): + # as long as EA is 1D-only, ravel is a no-op + result = data.ravel() + assert type(result) == type(data) + + if self._supports_setitem: + # Check that we have a view, not a copy + result[0] = result[1] + assert data[0] == data[1] diff --git a/pandas/tests/extension/test_categorical.py b/pandas/tests/extension/test_categorical.py index 4cf9f78e1531d..f58ecd5c33703 100644 --- a/pandas/tests/extension/test_categorical.py +++ b/pandas/tests/extension/test_categorical.py @@ -94,7 +94,11 @@ class TestConstructors(base.BaseConstructorsTests): class TestReshaping(base.BaseReshapingTests): - pass + + def test_ravel(self, data): + # GH#?? Categorical.ravel returns self until after deprecation cycle + with pytest.warns(FutureWarning, match="will return a Categorical"): + data.ravel() class TestGetitem(base.BaseGetitemTests): diff --git a/pandas/tests/extension/test_sparse.py b/pandas/tests/extension/test_sparse.py index 86ca3e230ddd5..99b7a4cf4f6fb 100644 --- a/pandas/tests/extension/test_sparse.py +++ b/pandas/tests/extension/test_sparse.py @@ -82,6 +82,7 @@ def data_for_grouping(request): class BaseSparseTests: + _supports_setitem = False def _check_unsupported(self, data): if data.dtype == SparseDtype(int, 0): From b9529471ba0f492466d0e8977c7f46b5af16e67b Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 2 Jul 2019 20:03:23 -0700 Subject: [PATCH 2/4] update GH refs --- doc/source/whatsnew/v0.25.0.rst | 2 +- pandas/tests/extension/test_categorical.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 7fda6c1bef1f8..08909a1f9e422 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -762,7 +762,7 @@ Other deprecations - :meth:`Series.put` is deprecated. (:issue:`18262`) - :meth:`Index.item` and :meth:`Series.item` is deprecated. (:issue:`18262`) - :meth:`Index.contains` is deprecated. Use ``key in index`` (``__contains__``) instead (:issue:`17753`). -- :meth:`Categorical.ravel` will return a :class:`Categorical` instead of a ``np.ndarray`` (:issue:`????`) +- :meth:`Categorical.ravel` will return a :class:`Categorical` instead of a ``np.ndarray`` (:issue:`27199`) .. _whatsnew_0250.prior_deprecations: diff --git a/pandas/tests/extension/test_categorical.py b/pandas/tests/extension/test_categorical.py index f58ecd5c33703..b64edad06fd05 100644 --- a/pandas/tests/extension/test_categorical.py +++ b/pandas/tests/extension/test_categorical.py @@ -96,7 +96,7 @@ class TestConstructors(base.BaseConstructorsTests): class TestReshaping(base.BaseReshapingTests): def test_ravel(self, data): - # GH#?? Categorical.ravel returns self until after deprecation cycle + # GH#27199 Categorical.ravel returns self until after deprecation cycle with pytest.warns(FutureWarning, match="will return a Categorical"): data.ravel() From db3e00115e889c68604c9d277da7d9eb5e694961 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Wed, 3 Jul 2019 09:20:48 -0700 Subject: [PATCH 3/4] use assert_produces_warning instead of pytest.warns --- pandas/core/arrays/categorical.py | 3 ++- pandas/tests/extension/test_categorical.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pandas/core/arrays/categorical.py b/pandas/core/arrays/categorical.py index ba4cb5f1b7c2f..05df4877bf98a 100644 --- a/pandas/core/arrays/categorical.py +++ b/pandas/core/arrays/categorical.py @@ -1708,7 +1708,8 @@ def ravel(self, order='C'): numpy.array """ warn("Categorical.ravel will return a Categorical object instead " - "of an ndarray in a future version.", FutureWarning) + "of an ndarray in a future version.", + FutureWarning, stacklevel=2) return np.array(self) def view(self): diff --git a/pandas/tests/extension/test_categorical.py b/pandas/tests/extension/test_categorical.py index b64edad06fd05..046dcc1c74a03 100644 --- a/pandas/tests/extension/test_categorical.py +++ b/pandas/tests/extension/test_categorical.py @@ -22,6 +22,7 @@ from pandas import Categorical from pandas.api.types import CategoricalDtype from pandas.tests.extension import base +import pandas.util.testing as tm def make_data(): @@ -97,7 +98,7 @@ class TestReshaping(base.BaseReshapingTests): def test_ravel(self, data): # GH#27199 Categorical.ravel returns self until after deprecation cycle - with pytest.warns(FutureWarning, match="will return a Categorical"): + with tm.assert_produces_warning(FutureWarning): data.ravel() From 6db725de57a93902734fba20e0ae4698ca897620 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Wed, 3 Jul 2019 12:18:12 -0700 Subject: [PATCH 4/4] revert testing pattern --- pandas/tests/extension/base/base.py | 2 -- pandas/tests/extension/base/reshaping.py | 7 +++---- pandas/tests/extension/test_sparse.py | 6 ++++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/pandas/tests/extension/base/base.py b/pandas/tests/extension/base/base.py index 84f41abb59e64..55cfbea479c47 100644 --- a/pandas/tests/extension/base/base.py +++ b/pandas/tests/extension/base/base.py @@ -2,8 +2,6 @@ class BaseExtensionTests: - # Whether the EA being tested supports __setitem__ - _supports_setitem = True assert_equal = staticmethod(tm.assert_equal) assert_series_equal = staticmethod(tm.assert_series_equal) diff --git a/pandas/tests/extension/base/reshaping.py b/pandas/tests/extension/base/reshaping.py index f1ac47bb0bce5..4ea78a4239e6e 100644 --- a/pandas/tests/extension/base/reshaping.py +++ b/pandas/tests/extension/base/reshaping.py @@ -275,7 +275,6 @@ def test_ravel(self, data): result = data.ravel() assert type(result) == type(data) - if self._supports_setitem: - # Check that we have a view, not a copy - result[0] = result[1] - assert data[0] == data[1] + # Check that we have a view, not a copy + result[0] = result[1] + assert data[0] == data[1] diff --git a/pandas/tests/extension/test_sparse.py b/pandas/tests/extension/test_sparse.py index 99b7a4cf4f6fb..8ce53270b7ba8 100644 --- a/pandas/tests/extension/test_sparse.py +++ b/pandas/tests/extension/test_sparse.py @@ -82,12 +82,14 @@ def data_for_grouping(request): class BaseSparseTests: - _supports_setitem = False - def _check_unsupported(self, data): if data.dtype == SparseDtype(int, 0): pytest.skip("Can't store nan in int array.") + @pytest.mark.xfail(reason="SparseArray does not support setitem") + def test_ravel(self, data): + super().test_ravel(data) + class TestDtype(BaseSparseTests, base.BaseDtypeTests):