From 8e232964516abe6c41d39fd4a02acd3eb62547b5 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 31 Dec 2019 12:25:50 -0800 Subject: [PATCH 01/10] BUG: validate Index data is 1D --- pandas/core/indexes/base.py | 14 +++++++---- pandas/core/indexes/numeric.py | 4 ++++ pandas/tests/indexes/test_base.py | 32 ++++++++++++++++++++++---- pandas/tests/indexing/test_indexing.py | 30 +++++------------------- 4 files changed, 48 insertions(+), 32 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index a3808f6f4a37e..9259bf25ec873 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -442,6 +442,9 @@ def __new__( pass if kwargs: raise TypeError(f"Unexpected keyword arguments {repr(set(kwargs))}") + if subarr.ndim > 1: + # GH#13601, GH#20285, GH#27125 + raise ValueError("Index data must be 1-dimensional") return cls._simple_new(subarr, name, **kwargs) elif hasattr(data, "__array__"): @@ -657,7 +660,7 @@ def __array_wrap__(self, result, context=None): Gets called after a ufunc. """ result = lib.item_from_zerodim(result) - if is_bool_dtype(result) or lib.is_scalar(result): + if is_bool_dtype(result) or lib.is_scalar(result) or np.ndim(result) > 1: return result attrs = self._get_attributes_dict() @@ -736,11 +739,10 @@ def astype(self, dtype, copy=True): return Index(np.asarray(self), dtype=dtype, copy=copy) try: - return Index( - self.values.astype(dtype, copy=copy), name=self.name, dtype=dtype - ) + casted = self.values.astype(dtype, copy=copy) except (TypeError, ValueError): raise TypeError(f"Cannot cast {type(self).__name__} to dtype {dtype}") + return Index(casted, name=self.name, dtype=dtype) _index_shared_docs[ "take" @@ -4024,6 +4026,10 @@ def __getitem__(self, key): key = com.values_from_object(key) result = getitem(key) if not is_scalar(result): + if np.ndim(result) > 1: + # GH#27125 indexer like idx[:, None] expands dim, but we + # cannot do that and keep an index, so return ndarray + return result return promote(result) else: return result diff --git a/pandas/core/indexes/numeric.py b/pandas/core/indexes/numeric.py index 39cbe5f151262..b9b44284edaa9 100644 --- a/pandas/core/indexes/numeric.py +++ b/pandas/core/indexes/numeric.py @@ -73,6 +73,10 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None): else: subarr = data + if subarr.ndim > 1: + # GH#13601, GH#20285, GH#27125 + raise ValueError("Index data must be 1-dimensional") + name = maybe_extract_name(name, data, cls) return cls._simple_new(subarr, name=name) diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 6ec35a32d74ce..bbda9747471cd 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -2782,9 +2782,33 @@ def test_shape_of_invalid_index(): # about this). However, as long as this is not solved in general,this test ensures # that the returned shape is consistent with this underlying array for # compat with matplotlib (see https://github.com/pandas-dev/pandas/issues/27775) - a = np.arange(8).reshape(2, 2, 2) - idx = pd.Index(a) - assert idx.shape == a.shape - idx = pd.Index([0, 1, 2, 3]) assert idx[:, None].shape == (4, 1) + + +def test_validate_1d_input(): + # GH#27125 check that we do not have >1-dimensional input + msg = "Index data must be 1-dimensional" + + arr = np.arange(8).reshape(2, 2, 2) + with pytest.raises(ValueError, match=msg): + pd.Index(arr) + + with pytest.raises(ValueError, match=msg): + pd.Float64Index(arr.astype(np.float64)) + + with pytest.raises(ValueError, match=msg): + pd.Int64Index(arr.astype(np.int64)) + + with pytest.raises(ValueError, match=msg): + pd.UInt64Index(arr.astype(np.uint64)) + + df = pd.DataFrame(arr.reshape(4, 2)) + with pytest.raises(ValueError, match=msg): + pd.Index(df) + + # GH#13601 trying to assign a multi-dimensional array to an index is not + # allowed + ser = pd.Series(0, range(4)) + with pytest.raises(ValueError, match=msg): + ser.index = np.array([[2, 3]] * 4) diff --git a/pandas/tests/indexing/test_indexing.py b/pandas/tests/indexing/test_indexing.py index d75afd1540f22..c6427bdfb55c2 100644 --- a/pandas/tests/indexing/test_indexing.py +++ b/pandas/tests/indexing/test_indexing.py @@ -83,12 +83,9 @@ def test_getitem_ndarray_3d(self, index, obj, idxr, idxr_id): msg = ( r"Buffer has wrong number of dimensions \(expected 1," r" got 3\)|" - "The truth value of an array with more than one element is" - " ambiguous|" "Cannot index with multidimensional key|" r"Wrong number of dimensions. values.ndim != ndim \[3 != 1\]|" - "No matching signature found|" # TypeError - "unhashable type: 'numpy.ndarray'" # TypeError + "Index data must be 1-dimensional" ) if ( @@ -106,18 +103,7 @@ def test_getitem_ndarray_3d(self, index, obj, idxr, idxr_id): ): idxr[nd3] else: - if ( - isinstance(obj, DataFrame) - and idxr_id == "getitem" - and index.inferred_type == "boolean" - ): - error = TypeError - elif idxr_id == "getitem" and index.inferred_type == "interval": - error = TypeError - else: - error = ValueError - - with pytest.raises(error, match=msg): + with pytest.raises(ValueError, match=msg): idxr[nd3] @pytest.mark.parametrize( @@ -148,14 +134,12 @@ def test_setitem_ndarray_3d(self, index, obj, idxr, idxr_id): msg = ( r"Buffer has wrong number of dimensions \(expected 1," r" got 3\)|" - "The truth value of an array with more than one element is" - " ambiguous|" - "Only 1-dimensional input arrays are supported|" "'pandas._libs.interval.IntervalTree' object has no attribute" " 'set_value'|" # AttributeError "unhashable type: 'numpy.ndarray'|" # TypeError "No matching signature found|" # TypeError - r"^\[\[\[" # pandas.core.indexing.IndexingError + r"^\[\[\[|" # pandas.core.indexing.IndexingError + "Index data must be 1-dimensional" ) if (idxr_id == "iloc") or ( @@ -176,10 +160,8 @@ def test_setitem_ndarray_3d(self, index, obj, idxr, idxr_id): ): idxr[nd3] = 0 else: - with pytest.raises( - (ValueError, AttributeError, TypeError, pd.core.indexing.IndexingError), - match=msg, - ): + err = (ValueError, AttributeError) + with pytest.raises(err, match=msg): idxr[nd3] = 0 def test_inf_upcast(self): From 181457a35721de06386e6131e490c9f917783335 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Thu, 2 Jan 2020 08:36:00 -0800 Subject: [PATCH 02/10] Use single-entry MultiIndex --- pandas/tests/io/test_feather.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/io/test_feather.py b/pandas/tests/io/test_feather.py index e06f2c31a2870..3500470035819 100644 --- a/pandas/tests/io/test_feather.py +++ b/pandas/tests/io/test_feather.py @@ -136,7 +136,7 @@ def test_write_with_index(self): # column multi-index df.index = [0, 1, 2] - df.columns = (pd.MultiIndex.from_tuples([("a", 1), ("a", 2), ("b", 1)]),) + df.columns = pd.MultiIndex.from_tuples([("a", 1)]) self.check_error_on_write(df, ValueError) def test_path_pathlib(self): From 2898896563d8bc5772e9cd400d2c46819b56ad79 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Thu, 2 Jan 2020 08:38:05 -0800 Subject: [PATCH 03/10] whatsnew --- doc/source/whatsnew/v1.0.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index 59644440149ff..15a05ded59a19 100755 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -994,6 +994,7 @@ Other - Bug in :class:`DataFrame` constructor when passing a 2D ``ndarray`` and an extension dtype (:issue:`12513`) - Bug in :meth:`DaataFrame.to_csv` when supplied a series with a ``dtype="string"`` and a ``na_rep``, the ``na_rep`` was being truncated to 2 characters. (:issue:`29975`) - Bug where :meth:`DataFrame.itertuples` would incorrectly determine whether or not namedtuples could be used for dataframes of 255 columns (:issue:`28282`) +- Bug in :class:`Index` constructor incorrectly allowing 2-dimensional input arrays (:issue:`13601`, :issue:`27125`) .. _whatsnew_1000.contributors: From e265c1568ebc243113a136a259190fd2bbfec068 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Mon, 6 Jan 2020 08:42:54 -0800 Subject: [PATCH 04/10] deprecate 2d returns --- doc/source/whatsnew/v1.0.0.rst | 1 + pandas/core/indexes/base.py | 7 +++++++ pandas/tests/indexes/test_base.py | 11 ++++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index a2eff32407cbd..89b6b5460ad4b 100755 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -581,6 +581,7 @@ Deprecations - The ``pandas.util.testing`` module has been deprecated. Use the public API in ``pandas.testing`` documented at :ref:`api.general.testing` (:issue:`16232`). - ``pandas.SparseArray`` has been deprecated. Use ``pandas.arrays.SparseArray`` (:class:`arrays.SparseArray`) instead. (:issue:`30642`) - The parameter ``is_copy`` of :meth:`DataFrame.take` has been deprecated and will be removed in a future version. (:issue:`27357`) +- Support for indexing indexing an :class:`Index` with ``index[:, None]`` is deprecated and will be removed in a future version (:issue:`30588`) **Selecting Columns from a Grouped DataFrame** diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 60ea8cab329dd..d34cef6d00d45 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -3936,6 +3936,13 @@ def __getitem__(self, key): if np.ndim(result) > 1: # GH#27125 indexer like idx[:, None] expands dim, but we # cannot do that and keep an index, so return ndarray + # Deprecation GH#30588 + warnings.warn( + "Support for Index[:, None] is deprecated and will be " + "removed in a future version.", + DeprecationWarning, + stacklevel=2, + ) return result return promote(result) else: diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 01b274afb79a2..f751abcdd8496 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -1905,6 +1905,14 @@ def test_set_value_deprecated(self): idx.set_value(arr, idx[1], 80) assert arr[1] == 80 + def test_getitem_2d_deprecated(self): + # GH#30588 + idx = self.create_index() + with tm.assert_produces_warning(DeprecationWarning): + res = idx[:, None] + + assert isinstance(res, np.ndarray) + @pytest.mark.parametrize( "index", ["string", "int", "datetime", "timedelta"], indirect=True ) @@ -2785,7 +2793,8 @@ def test_shape_of_invalid_index(): # that the returned shape is consistent with this underlying array for # compat with matplotlib (see https://github.com/pandas-dev/pandas/issues/27775) idx = pd.Index([0, 1, 2, 3]) - assert idx[:, None].shape == (4, 1) + with tm.assert_produces_warning(DeprecationWarning): + assert idx[:, None].shape == (4, 1) def test_validate_1d_input(): From c14f01a5d1f2fba40db66a3d8211350bfc95bafa Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Mon, 6 Jan 2020 09:34:16 -0800 Subject: [PATCH 05/10] Update doc/source/whatsnew/v1.0.0.rst Co-Authored-By: Joris Van den Bossche --- doc/source/whatsnew/v1.0.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index 89b6b5460ad4b..1fd88e5476155 100755 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -581,7 +581,7 @@ Deprecations - The ``pandas.util.testing`` module has been deprecated. Use the public API in ``pandas.testing`` documented at :ref:`api.general.testing` (:issue:`16232`). - ``pandas.SparseArray`` has been deprecated. Use ``pandas.arrays.SparseArray`` (:class:`arrays.SparseArray`) instead. (:issue:`30642`) - The parameter ``is_copy`` of :meth:`DataFrame.take` has been deprecated and will be removed in a future version. (:issue:`27357`) -- Support for indexing indexing an :class:`Index` with ``index[:, None]`` is deprecated and will be removed in a future version (:issue:`30588`) +- Support for indexing an :class:`Index` with ``index[:, None]`` is deprecated and will be removed in a future version (:issue:`30588`) **Selecting Columns from a Grouped DataFrame** From 4cc1c27bf61c9a511d6bf7f0f568898cd7aaeb54 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Mon, 6 Jan 2020 13:08:47 -0800 Subject: [PATCH 06/10] update tests, Categorical, IntervalIndex --- doc/source/whatsnew/v1.0.0.rst | 2 +- pandas/core/arrays/categorical.py | 7 ++++--- pandas/core/arrays/datetimelike.py | 2 -- pandas/core/arrays/interval.py | 4 +++- pandas/core/indexes/base.py | 25 +++++++++++++++---------- pandas/core/indexes/datetimes.py | 3 ++- pandas/core/indexes/timedeltas.py | 10 +++++++++- pandas/tests/indexes/common.py | 8 ++++++++ pandas/tests/indexes/test_base.py | 8 -------- pandas/tests/indexing/test_indexing.py | 3 ++- pandas/tests/plotting/test_converter.py | 14 +++++--------- 11 files changed, 49 insertions(+), 37 deletions(-) diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index 748700362989d..2c89a8e96e05b 100755 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -581,7 +581,7 @@ Deprecations - The ``pandas.util.testing`` module has been deprecated. Use the public API in ``pandas.testing`` documented at :ref:`api.general.testing` (:issue:`16232`). - ``pandas.SparseArray`` has been deprecated. Use ``pandas.arrays.SparseArray`` (:class:`arrays.SparseArray`) instead. (:issue:`30642`) - The parameter ``is_copy`` of :meth:`DataFrame.take` has been deprecated and will be removed in a future version. (:issue:`27357`) -- Support for multi-dimensional indexing (e.g. ``index[:, None]`` on a :class:`Index` is deprecated and will be removed in a future version, convert to a numpy array before indexing instead (:issue:`30588`) +- Support for multi-dimensional indexing (e.g. ``index[:, None]``) on a :class:`Index` is deprecated and will be removed in a future version, convert to a numpy array before indexing instead (:issue:`30588`) **Selecting Columns from a Grouped DataFrame** diff --git a/pandas/core/arrays/categorical.py b/pandas/core/arrays/categorical.py index 7386c9d0ef1de..8055652b7203d 100644 --- a/pandas/core/arrays/categorical.py +++ b/pandas/core/arrays/categorical.py @@ -2001,9 +2001,10 @@ def __getitem__(self, key): elif com.is_bool_indexer(key): key = check_bool_array_indexer(self, key) - return self._constructor( - values=self._codes[key], dtype=self.dtype, fastpath=True - ) + result = self._codes[key] + if result.ndim > 1: + return result + return self._constructor(result, dtype=self.dtype, fastpath=True) def __setitem__(self, key, value): """ diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index b9a6daf7c630a..9a8cf14a612ef 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -465,8 +465,6 @@ def __getitem__(self, key): if result.ndim > 1: # To support MPL which performs slicing with 2 dim # even though it only has 1 dim by definition - if is_period: - return self._simple_new(result, dtype=self.dtype, freq=freq) return result return self._simple_new(result, dtype=self.dtype, freq=freq) diff --git a/pandas/core/arrays/interval.py b/pandas/core/arrays/interval.py index 75dd00104db1b..e4d9d33129f44 100644 --- a/pandas/core/arrays/interval.py +++ b/pandas/core/arrays/interval.py @@ -500,8 +500,10 @@ def __getitem__(self, value): # scalar if not isinstance(left, ABCIndexClass): - if isna(left): + if is_scalar(left) and isna(left): return self._fill_value + if np.ndim(left) > 1: + return np.c_[left, right] return Interval(left, right, self.closed) return self._shallow_copy(left, right) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 61dc237be70e5..df82c2cbcbcc5 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -3934,16 +3934,7 @@ def __getitem__(self, key): result = getitem(key) if not is_scalar(result): if np.ndim(result) > 1: - # GH#27125 indexer like idx[:, None] expands dim, but we - # cannot do that and keep an index, so return ndarray - # Deprecation GH#30588 - warnings.warn( - "Support for multi-dimensional indexing (e.g. `index[:, None]` " - "on an Index is deprecated and will be removed in a future " - "version. Convert to a numpy array before indexing instead.", - DeprecationWarning, - stacklevel=2, - ) + deprecate_ndim_indexing(result) return result return promote(result) else: @@ -5576,3 +5567,17 @@ def _try_convert_to_int_array( pass raise ValueError + + +def deprecate_ndim_indexing(result): + if np.ndim(result) > 1: + # GH#27125 indexer like idx[:, None] expands dim, but we + # cannot do that and keep an index, so return ndarray + # Deprecation GH#30588 + warnings.warn( + "Support for multi-dimensional indexing (e.g. `index[:, None]`) " + "on an Index is deprecated and will be removed in a future " + "version. Convert to a numpy array before indexing instead.", + DeprecationWarning, + stacklevel=3, + ) diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 40d3823c9700b..89e02a2eb00d1 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -22,7 +22,7 @@ ) from pandas.core.base import _shared_docs import pandas.core.common as com -from pandas.core.indexes.base import Index, maybe_extract_name +from pandas.core.indexes.base import Index, deprecate_ndim_indexing, maybe_extract_name from pandas.core.indexes.datetimelike import ( DatetimelikeDelegateMixin, DatetimeTimedeltaMixin, @@ -882,6 +882,7 @@ def __getitem__(self, key): elif result.ndim > 1: # To support MPL which performs slicing with 2 dim # even though it only has 1 dim by definition + deprecate_ndim_indexing(result) assert isinstance(result, np.ndarray), result return result return type(self)(result, name=self.name) diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index ee6e5b984ae7b..7226dd26e6ba7 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -23,7 +23,12 @@ from pandas.core.arrays.timedeltas import TimedeltaArray, _is_convertible_to_td from pandas.core.base import _shared_docs import pandas.core.common as com -from pandas.core.indexes.base import Index, _index_shared_docs, maybe_extract_name +from pandas.core.indexes.base import ( + Index, + _index_shared_docs, + deprecate_ndim_indexing, + maybe_extract_name, +) from pandas.core.indexes.datetimelike import ( DatetimeIndexOpsMixin, DatetimelikeDelegateMixin, @@ -229,6 +234,9 @@ def __getitem__(self, key): result = self._data.__getitem__(key) if is_scalar(result): return result + if np.ndim(result) > 1: + deprecate_ndim_indexing(result) + return result return type(self)(result, name=self.name) # ------------------------------------------------------------------- diff --git a/pandas/tests/indexes/common.py b/pandas/tests/indexes/common.py index ceb3ac8b61c0b..a16017b0e12c0 100644 --- a/pandas/tests/indexes/common.py +++ b/pandas/tests/indexes/common.py @@ -875,3 +875,11 @@ def test_engine_reference_cycle(self): nrefs_pre = len(gc.get_referrers(index)) index._engine assert len(gc.get_referrers(index)) == nrefs_pre + + def test_getitem_2d_deprecated(self): + # GH#30588 + idx = self.create_index() + with tm.assert_produces_warning(DeprecationWarning, check_stacklevel=False): + res = idx[:, None] + + assert isinstance(res, np.ndarray), type(res) diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index f751abcdd8496..d6f704be95e8b 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -1905,14 +1905,6 @@ def test_set_value_deprecated(self): idx.set_value(arr, idx[1], 80) assert arr[1] == 80 - def test_getitem_2d_deprecated(self): - # GH#30588 - idx = self.create_index() - with tm.assert_produces_warning(DeprecationWarning): - res = idx[:, None] - - assert isinstance(res, np.ndarray) - @pytest.mark.parametrize( "index", ["string", "int", "datetime", "timedelta"], indirect=True ) diff --git a/pandas/tests/indexing/test_indexing.py b/pandas/tests/indexing/test_indexing.py index c8737f5e70515..448a06070c45c 100644 --- a/pandas/tests/indexing/test_indexing.py +++ b/pandas/tests/indexing/test_indexing.py @@ -101,7 +101,8 @@ def test_getitem_ndarray_3d(self, index, obj, idxr, idxr_id): "categorical", ] ): - idxr[nd3] + with tm.assert_produces_warning(DeprecationWarning, check_stacklevel=False): + idxr[nd3] else: with pytest.raises(ValueError, match=msg): with tm.assert_produces_warning(DeprecationWarning): diff --git a/pandas/tests/plotting/test_converter.py b/pandas/tests/plotting/test_converter.py index 8fdf86ddabafe..9cd3ccbf9214e 100644 --- a/pandas/tests/plotting/test_converter.py +++ b/pandas/tests/plotting/test_converter.py @@ -66,11 +66,10 @@ def test_registering_no_warning(self): # Set to the "warn" state, in case this isn't the first test run register_matplotlib_converters() - with tm.assert_produces_warning(None) as w: + with tm.assert_produces_warning(DeprecationWarning, check_stacklevel=False): + # GH#30588 DeprecationWarning from 2D indexing ax.plot(s.index, s.values) - assert len(w) == 0 - def test_pandas_plots_register(self): pytest.importorskip("matplotlib.pyplot") s = Series(range(12), index=date_range("2017", periods=12)) @@ -101,19 +100,16 @@ def test_option_no_warning(self): # Test without registering first, no warning with ctx: - with tm.assert_produces_warning(None) as w: + # GH#30588 DeprecationWarning from 2D indexing on Index + with tm.assert_produces_warning(DeprecationWarning, check_stacklevel=False): ax.plot(s.index, s.values) - assert len(w) == 0 - # Now test with registering register_matplotlib_converters() with ctx: - with tm.assert_produces_warning(None) as w: + with tm.assert_produces_warning(DeprecationWarning, check_stacklevel=False): ax.plot(s.index, s.values) - assert len(w) == 0 - def test_registry_resets(self): units = pytest.importorskip("matplotlib.units") dates = pytest.importorskip("matplotlib.dates") From 1e6a3f5e4eb24fedf97e366c47f686c8f7b443fe Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Mon, 6 Jan 2020 15:17:09 -0800 Subject: [PATCH 07/10] update categorical test --- pandas/tests/indexes/categorical/test_category.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pandas/tests/indexes/categorical/test_category.py b/pandas/tests/indexes/categorical/test_category.py index 61e30d3e5c139..7de370e531b7f 100644 --- a/pandas/tests/indexes/categorical/test_category.py +++ b/pandas/tests/indexes/categorical/test_category.py @@ -975,3 +975,9 @@ def test_engine_type(self, dtype, engine_type): ci.values._codes = ci.values._codes.astype("int64") assert np.issubdtype(ci.codes.dtype, dtype) assert isinstance(ci._engine, engine_type) + + def test_getitem_2d_deprecated(self): + # GH#30588 multi-dim indexing is deprecated, but raising is also acceptable + idx = self.create_index() + with pytest.raises(ValueError, match="cannot mask with array containing NA"): + idx[:, None] From e02dd13036b028d6cac90e08a032cd366a2d0839 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 9 Jan 2020 09:28:43 -0800 Subject: [PATCH 08/10] catch warnings --- pandas/tests/indexes/datetimes/test_indexing.py | 8 ++++++-- pandas/tests/indexes/test_base.py | 5 ++++- pandas/tests/series/test_timeseries.py | 4 +++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/pandas/tests/indexes/datetimes/test_indexing.py b/pandas/tests/indexes/datetimes/test_indexing.py index 91007a1ba529e..4c600e510790a 100644 --- a/pandas/tests/indexes/datetimes/test_indexing.py +++ b/pandas/tests/indexes/datetimes/test_indexing.py @@ -86,7 +86,9 @@ def test_dti_business_getitem(self): def test_dti_business_getitem_matplotlib_hackaround(self): rng = pd.bdate_range(START, END) - values = rng[:, None] + with tm.assert_produces_warning(DeprecationWarning): + # GH#30588 multi-dimensional indexing deprecated + values = rng[:, None] expected = rng.values[:, None] tm.assert_numpy_array_equal(values, expected) @@ -110,7 +112,9 @@ def test_dti_custom_getitem(self): def test_dti_custom_getitem_matplotlib_hackaround(self): rng = pd.bdate_range(START, END, freq="C") - values = rng[:, None] + with tm.assert_produces_warning(DeprecationWarning): + # GH#30588 multi-dimensional indexing deprecated + values = rng[:, None] expected = rng.values[:, None] tm.assert_numpy_array_equal(values, expected) diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index dff65358eb86b..8dd430b55117e 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -71,7 +71,9 @@ def test_can_hold_identifiers(self): @pytest.mark.parametrize("index", ["datetime"], indirect=True) def test_new_axis(self, index): - new_index = index[None, :] + with tm.assert_produces_warning(DeprecationWarning): + # GH#30588 multi-dimensional indexing deprecated + new_index = index[None, :] assert new_index.ndim == 2 assert isinstance(new_index, np.ndarray) @@ -2786,6 +2788,7 @@ def test_shape_of_invalid_index(): # compat with matplotlib (see https://github.com/pandas-dev/pandas/issues/27775) idx = pd.Index([0, 1, 2, 3]) with tm.assert_produces_warning(DeprecationWarning): + # GH#30588 multi-dimensional indexing deprecated assert idx[:, None].shape == (4, 1) diff --git a/pandas/tests/series/test_timeseries.py b/pandas/tests/series/test_timeseries.py index a135c0cf7cd7e..c909889835c47 100644 --- a/pandas/tests/series/test_timeseries.py +++ b/pandas/tests/series/test_timeseries.py @@ -137,7 +137,9 @@ def test_first_last_valid(self, datetime_series): assert ts.last_valid_index().freq == ts.index.freq def test_mpl_compat_hack(self, datetime_series): - result = datetime_series[:, np.newaxis] + with tm.assert_produces_warning(DeprecationWarning): + # GH#30588 multi-dimensional indexing deprecated + result = datetime_series[:, np.newaxis] expected = datetime_series.values[:, np.newaxis] tm.assert_almost_equal(result, expected) From d163da8ced5a5dedffdf07fa46e5c3c2e0611afe Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 9 Jan 2020 10:22:03 -0800 Subject: [PATCH 09/10] Disallow multi-dim indexing for IntervalArray --- pandas/core/arrays/interval.py | 3 ++- pandas/tests/indexes/interval/test_base.py | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pandas/core/arrays/interval.py b/pandas/core/arrays/interval.py index fd0461057d13b..e4efb187ffae5 100644 --- a/pandas/core/arrays/interval.py +++ b/pandas/core/arrays/interval.py @@ -503,7 +503,8 @@ def __getitem__(self, value): if is_scalar(left) and isna(left): return self._fill_value if np.ndim(left) > 1: - return np.c_[left, right] + # GH#30588 multi-dimensional indexer disallowed + raise ValueError("multi-dimensional indexing not allowed") return Interval(left, right, self.closed) return self._shallow_copy(left, right) diff --git a/pandas/tests/indexes/interval/test_base.py b/pandas/tests/indexes/interval/test_base.py index 91f8dddea71d7..c104ce12ac8ff 100644 --- a/pandas/tests/indexes/interval/test_base.py +++ b/pandas/tests/indexes/interval/test_base.py @@ -79,3 +79,10 @@ def test_where(self, closed, klass): expected = IntervalIndex([np.nan] + idx[1:].tolist()) result = idx.where(klass(cond)) tm.assert_index_equal(result, expected) + + def test_getitem_2d_deprecated(self): + # GH#30588 multi-dim indexing is deprecated, but raising is also acceptable + idx = self.create_index() + with pytest.raises(ValueError, match="multi-dimensional indexing not allowed"): + with tm.assert_produces_warning(DeprecationWarning): + idx[:, None] From 74dffbedacb34bde7e5a8fb2396e0fef9efac499 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 9 Jan 2020 10:52:05 -0800 Subject: [PATCH 10/10] ignore stacklevel --- pandas/tests/indexes/interval/test_base.py | 2 +- pandas/tests/series/test_timeseries.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/tests/indexes/interval/test_base.py b/pandas/tests/indexes/interval/test_base.py index c104ce12ac8ff..d8c2ba8413cfb 100644 --- a/pandas/tests/indexes/interval/test_base.py +++ b/pandas/tests/indexes/interval/test_base.py @@ -84,5 +84,5 @@ def test_getitem_2d_deprecated(self): # GH#30588 multi-dim indexing is deprecated, but raising is also acceptable idx = self.create_index() with pytest.raises(ValueError, match="multi-dimensional indexing not allowed"): - with tm.assert_produces_warning(DeprecationWarning): + with tm.assert_produces_warning(DeprecationWarning, check_stacklevel=False): idx[:, None] diff --git a/pandas/tests/series/test_timeseries.py b/pandas/tests/series/test_timeseries.py index c909889835c47..a2d14f27d7b7a 100644 --- a/pandas/tests/series/test_timeseries.py +++ b/pandas/tests/series/test_timeseries.py @@ -137,7 +137,7 @@ def test_first_last_valid(self, datetime_series): assert ts.last_valid_index().freq == ts.index.freq def test_mpl_compat_hack(self, datetime_series): - with tm.assert_produces_warning(DeprecationWarning): + with tm.assert_produces_warning(DeprecationWarning, check_stacklevel=False): # GH#30588 multi-dimensional indexing deprecated result = datetime_series[:, np.newaxis] expected = datetime_series.values[:, np.newaxis]