Skip to content

Commit 9820edc

Browse files
authored
DEPR: indexing (#49412)
* DEPR: disallow Series.__getitem__ with a single-element list containing slice * DEPR: disallow slicing with positional slicer and .loc * DEPR: disallow positional indexing with float key * move whatsnew * DEPR: disallow multi-dimensional indexing * fix matplotlib tests * update install.rst
1 parent e5961e2 commit 9820edc

File tree

16 files changed

+62
-93
lines changed

16 files changed

+62
-93
lines changed

ci/deps/actions-38-minimum_versions.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ dependencies:
3232
- gcsfs=2021.07.0
3333
- jinja2=3.0.0
3434
- lxml=4.6.3
35-
- matplotlib=3.3.2
35+
- matplotlib=3.6.0
3636
- numba=0.53.1
3737
- numexpr=2.7.3
3838
- odfpy=1.4.1

doc/source/getting_started/install.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ Can be managed as optional_extra with ``pandas[plot, output_formatting]``, depen
310310
========================= ================== ================== =============================================================
311311
Dependency Minimum Version optional_extra Notes
312312
========================= ================== ================== =============================================================
313-
matplotlib 3.3.2 plot Plotting library
313+
matplotlib 3.6.0 plot Plotting library
314314
Jinja2 3.0.0 output_formatting Conditional formatting with DataFrame.style
315315
tabulate 0.8.9 output_formatting Printing in Markdown-friendly format (see `tabulate`_)
316316
========================= ================== ================== =============================================================

doc/source/whatsnew/v2.0.0.rst

+4
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ Optional libraries below the lowest tested version may still work, but are not c
124124
+=================+=================+=========+
125125
| pyarrow | 6.0.0 | X |
126126
+-----------------+-----------------+---------+
127+
| matplotlib | 3.6.0 | X |
128+
+-----------------+-----------------+---------+
127129
| fastparquet | 0.6.3 | X |
128130
+-----------------+-----------------+---------+
129131

@@ -282,6 +284,8 @@ Removal of prior version deprecations/changes
282284
- Enforced disallowing the use of ``**kwargs`` in :class:`.ExcelWriter`; use the keyword argument ``engine_kwargs`` instead (:issue:`40430`)
283285
- Enforced disallowing a tuple of column labels into :meth:`.DataFrameGroupBy.__getitem__` (:issue:`30546`)
284286
- Enforced disallowing setting values with ``.loc`` using a positional slice. Use ``.loc`` with labels or ``.iloc`` with positions instead (:issue:`31840`)
287+
- Enforced disallowing positional indexing with a ``float`` key even if that key is a round number, manually cast to integer instead (:issue:`34193`)
288+
- Enforced disallowing indexing on a :class:`Index` or positional indexing on a :class:`Series` producing multi-dimensional objects e.g. ``obj[:, None]``, convert to numpy before indexing instead (:issue:`35141`)
285289
- Enforced disallowing ``dict`` or ``set`` objects in ``suffixes`` in :func:`merge` (:issue:`34810`)
286290
- Enforced disallowing :func:`merge` to produce duplicated columns through the ``suffixes`` keyword and already existing columns (:issue:`22818`)
287291
- Enforced disallowing using :func:`merge` or :func:`join` on a different number of levels (:issue:`34862`)

pandas/compat/_optional.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"gcsfs": "2021.07.0",
2424
"jinja2": "3.0.0",
2525
"lxml.etree": "4.6.3",
26-
"matplotlib": "3.3.2",
26+
"matplotlib": "3.6.0",
2727
"numba": "0.53.1",
2828
"numexpr": "2.7.3",
2929
"odfpy": "1.4.1",

pandas/core/common.py

+7-12
Original file line numberDiff line numberDiff line change
@@ -148,30 +148,25 @@ def is_bool_indexer(key: Any) -> bool:
148148
return False
149149

150150

151-
def cast_scalar_indexer(val, warn_float: bool = False):
151+
def cast_scalar_indexer(val):
152152
"""
153-
To avoid numpy DeprecationWarnings, cast float to integer where valid.
153+
Disallow indexing with a float key, even if that key is a round number.
154154
155155
Parameters
156156
----------
157157
val : scalar
158-
warn_float : bool, default False
159-
If True, issue deprecation warning for a float indexer.
160158
161159
Returns
162160
-------
163161
outval : scalar
164162
"""
165163
# assumes lib.is_scalar(val)
166164
if lib.is_float(val) and val.is_integer():
167-
if warn_float:
168-
warnings.warn(
169-
"Indexing with a float is deprecated, and will raise an IndexError "
170-
"in pandas 2.0. You can manually convert to an integer key instead.",
171-
FutureWarning,
172-
stacklevel=find_stack_level(),
173-
)
174-
return int(val)
165+
raise IndexError(
166+
# GH#34193
167+
"Indexing with a float is no longer supported. Manually convert "
168+
"to an integer key instead."
169+
)
175170
return val
176171

177172

pandas/core/indexers/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
check_array_indexer,
33
check_key_length,
44
check_setitem_lengths,
5-
deprecate_ndim_indexing,
5+
disallow_ndim_indexing,
66
is_empty_indexer,
77
is_list_like_indexer,
88
is_scalar_indexer,
@@ -23,7 +23,7 @@
2323
"validate_indices",
2424
"maybe_convert_indices",
2525
"length_of_indexer",
26-
"deprecate_ndim_indexing",
26+
"disallow_ndim_indexing",
2727
"unpack_1tuple",
2828
"check_key_length",
2929
"check_array_indexer",

pandas/core/indexers/utils.py

+7-13
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,10 @@
77
TYPE_CHECKING,
88
Any,
99
)
10-
import warnings
1110

1211
import numpy as np
1312

1413
from pandas._typing import AnyArrayLike
15-
from pandas.util._exceptions import find_stack_level
1614

1715
from pandas.core.dtypes.common import (
1816
is_array_like,
@@ -333,22 +331,18 @@ def length_of_indexer(indexer, target=None) -> int:
333331
raise AssertionError("cannot find the length of the indexer")
334332

335333

336-
def deprecate_ndim_indexing(result, stacklevel: int = 3) -> None:
334+
def disallow_ndim_indexing(result) -> None:
337335
"""
338-
Helper function to raise the deprecation warning for multi-dimensional
339-
indexing on 1D Series/Index.
336+
Helper function to disallow multi-dimensional indexing on 1D Series/Index.
340337
341338
GH#27125 indexer like idx[:, None] expands dim, but we cannot do that
342-
and keep an index, so we currently return ndarray, which is deprecated
343-
(Deprecation GH#30588).
339+
and keep an index, so we used to return ndarray, which was deprecated
340+
in GH#30588.
344341
"""
345342
if np.ndim(result) > 1:
346-
warnings.warn(
347-
"Support for multi-dimensional indexing (e.g. `obj[:, None]`) "
348-
"is deprecated and will be removed in a future "
349-
"version. Convert to a numpy array before indexing instead.",
350-
FutureWarning,
351-
stacklevel=find_stack_level(),
343+
raise ValueError(
344+
"Multi-dimensional indexing (e.g. `obj[:, None]`) is no longer "
345+
"supported. Convert to a numpy array before indexing instead."
352346
)
353347

354348

pandas/core/indexes/base.py

+3-11
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@
161161
extract_array,
162162
sanitize_array,
163163
)
164-
from pandas.core.indexers import deprecate_ndim_indexing
164+
from pandas.core.indexers import disallow_ndim_indexing
165165
from pandas.core.indexes.frozen import FrozenList
166166
from pandas.core.ops import get_op_result_name
167167
from pandas.core.ops.invalid import make_invalid_op
@@ -5213,7 +5213,7 @@ def __getitem__(self, key):
52135213

52145214
if is_integer(key) or is_float(key):
52155215
# GH#44051 exclude bool, which would return a 2d ndarray
5216-
key = com.cast_scalar_indexer(key, warn_float=True)
5216+
key = com.cast_scalar_indexer(key)
52175217
return getitem(key)
52185218

52195219
if isinstance(key, slice):
@@ -5236,15 +5236,7 @@ def __getitem__(self, key):
52365236
result = getitem(key)
52375237
# Because we ruled out integer above, we always get an arraylike here
52385238
if result.ndim > 1:
5239-
deprecate_ndim_indexing(result)
5240-
if hasattr(result, "_ndarray"):
5241-
# i.e. NDArrayBackedExtensionArray
5242-
# Unpack to ndarray for MPL compat
5243-
# error: Item "ndarray[Any, Any]" of
5244-
# "Union[ExtensionArray, ndarray[Any, Any]]"
5245-
# has no attribute "_ndarray"
5246-
return result._ndarray # type: ignore[union-attr]
5247-
return result
5239+
disallow_ndim_indexing(result)
52485240

52495241
# NB: Using _constructor._simple_new would break if MultiIndex
52505242
# didn't override __getitem__

pandas/core/indexes/multi.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2031,7 +2031,7 @@ def __reduce__(self):
20312031

20322032
def __getitem__(self, key):
20332033
if is_scalar(key):
2034-
key = com.cast_scalar_indexer(key, warn_float=True)
2034+
key = com.cast_scalar_indexer(key)
20352035

20362036
retval = []
20372037
for lev, level_codes in zip(self.levels, self.codes):

pandas/core/series.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@
123123
)
124124
from pandas.core.generic import NDFrame
125125
from pandas.core.indexers import (
126-
deprecate_ndim_indexing,
126+
disallow_ndim_indexing,
127127
unpack_1tuple,
128128
)
129129
from pandas.core.indexes.accessors import CombinedDatetimelikeProperties
@@ -1003,7 +1003,7 @@ def _get_values_tuple(self, key: tuple):
10031003
# see tests.series.timeseries.test_mpl_compat_hack
10041004
# the asarray is needed to avoid returning a 2D DatetimeArray
10051005
result = np.asarray(self._values[key])
1006-
deprecate_ndim_indexing(result, stacklevel=find_stack_level())
1006+
disallow_ndim_indexing(result)
10071007
return result
10081008

10091009
if not isinstance(self.index, MultiIndex):

pandas/tests/indexes/common.py

+8-12
Original file line numberDiff line numberDiff line change
@@ -699,20 +699,16 @@ def test_engine_reference_cycle(self, simple_index):
699699
def test_getitem_2d_deprecated(self, simple_index):
700700
# GH#30588, GH#31479
701701
idx = simple_index
702-
msg = "Support for multi-dimensional indexing"
703-
with tm.assert_produces_warning(FutureWarning, match=msg):
704-
res = idx[:, None]
705-
706-
assert isinstance(res, np.ndarray), type(res)
702+
msg = "Multi-dimensional indexing"
703+
with pytest.raises(ValueError, match=msg):
704+
idx[:, None]
707705

708706
if not isinstance(idx, RangeIndex):
709-
# GH#44051 RangeIndex already raises
710-
with tm.assert_produces_warning(FutureWarning, match=msg):
711-
res = idx[True]
712-
assert isinstance(res, np.ndarray), type(res)
713-
with tm.assert_produces_warning(FutureWarning, match=msg):
714-
res = idx[False]
715-
assert isinstance(res, np.ndarray), type(res)
707+
# GH#44051 RangeIndex already raised pre-2.0 with a different message
708+
with pytest.raises(ValueError, match=msg):
709+
idx[True]
710+
with pytest.raises(ValueError, match=msg):
711+
idx[False]
716712
else:
717713
msg = "only integers, slices"
718714
with pytest.raises(IndexError, match=msg):

pandas/tests/indexes/datetimes/test_indexing.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,9 @@ def test_dti_business_getitem(self, freq):
9898
@pytest.mark.parametrize("freq", ["B", "C"])
9999
def test_dti_business_getitem_matplotlib_hackaround(self, freq):
100100
rng = bdate_range(START, END, freq=freq)
101-
with tm.assert_produces_warning(FutureWarning):
101+
with pytest.raises(ValueError, match="Multi-dimensional indexing"):
102102
# GH#30588 multi-dimensional indexing deprecated
103-
values = rng[:, None]
104-
expected = rng.values[:, None]
105-
tm.assert_numpy_array_equal(values, expected)
103+
rng[:, None]
106104

107105
def test_getitem_int_list(self):
108106
dti = date_range(start="1/1/2005", end="12/1/2005", freq="M")

pandas/tests/indexes/test_base.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ def test_can_hold_identifiers(self, simple_index):
6262

6363
@pytest.mark.parametrize("index", ["datetime"], indirect=True)
6464
def test_new_axis(self, index):
65-
with tm.assert_produces_warning(FutureWarning):
65+
# TODO: a bunch of scattered tests check this deprecation is enforced.
66+
# de-duplicate/centralize them.
67+
with pytest.raises(ValueError, match="Multi-dimensional indexing"):
6668
# GH#30588 multi-dimensional indexing deprecated
67-
new_index = index[None, :]
68-
assert new_index.ndim == 2
69-
assert isinstance(new_index, np.ndarray)
69+
index[None, :]
7070

7171
def test_argsort(self, index):
7272
with tm.maybe_produces_warning(
@@ -1532,15 +1532,15 @@ def test_deprecated_fastpath():
15321532

15331533

15341534
def test_shape_of_invalid_index():
1535-
# Currently, it is possible to create "invalid" index objects backed by
1535+
# Pre-2.0, it was possible to create "invalid" index objects backed by
15361536
# a multi-dimensional array (see https://github.com/pandas-dev/pandas/issues/27125
15371537
# about this). However, as long as this is not solved in general,this test ensures
15381538
# that the returned shape is consistent with this underlying array for
15391539
# compat with matplotlib (see https://github.com/pandas-dev/pandas/issues/27775)
15401540
idx = Index([0, 1, 2, 3])
1541-
with tm.assert_produces_warning(FutureWarning):
1541+
with pytest.raises(ValueError, match="Multi-dimensional indexing"):
15421542
# GH#30588 multi-dimensional indexing deprecated
1543-
assert idx[:, None].shape == (4, 1)
1543+
idx[:, None]
15441544

15451545

15461546
def test_validate_1d_input():

pandas/tests/indexes/test_indexing.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -290,11 +290,9 @@ def test_putmask_with_wrong_mask(self, index):
290290
def test_getitem_deprecated_float(idx):
291291
# https://github.com/pandas-dev/pandas/issues/34191
292292

293-
with tm.assert_produces_warning(FutureWarning):
294-
result = idx[1.0]
295-
296-
expected = idx[1]
297-
assert result == expected
293+
msg = "Indexing with a float is no longer supported"
294+
with pytest.raises(IndexError, match=msg):
295+
idx[1.0]
298296

299297

300298
@pytest.mark.parametrize(

pandas/tests/series/indexing/test_getitem.py

+8-19
Original file line numberDiff line numberDiff line change
@@ -269,19 +269,10 @@ def test_getitem_partial_str_slice_high_reso_with_timedeltaindex(self):
269269

270270
def test_getitem_slice_2d(self, datetime_series):
271271
# GH#30588 multi-dimensional indexing deprecated
272-
273-
with tm.assert_produces_warning(
274-
FutureWarning, match="Support for multi-dimensional indexing"
275-
):
276-
# GH#30867 Don't want to support this long-term, but
277-
# for now ensure that the warning from Index
278-
# doesn't comes through via Series.__getitem__.
279-
result = datetime_series[:, np.newaxis]
280-
expected = datetime_series.values[:, np.newaxis]
281-
tm.assert_almost_equal(result, expected)
272+
with pytest.raises(ValueError, match="Multi-dimensional indexing"):
273+
datetime_series[:, np.newaxis]
282274

283275
# FutureWarning from NumPy.
284-
@pytest.mark.filterwarnings("ignore:Using a non-tuple:FutureWarning")
285276
def test_getitem_median_slice_bug(self):
286277
index = date_range("20090415", "20090519", freq="2B")
287278
ser = Series(np.random.randn(13), index=index)
@@ -291,6 +282,10 @@ def test_getitem_median_slice_bug(self):
291282
with pytest.raises(ValueError, match=msg):
292283
# GH#31299
293284
ser[indexer]
285+
# but we're OK with a single-element tuple
286+
result = ser[(indexer[0],)]
287+
expected = ser[indexer[0]]
288+
tm.assert_series_equal(result, expected)
294289

295290
@pytest.mark.parametrize(
296291
"slc, positions",
@@ -554,14 +549,8 @@ def test_getitem_generator(string_series):
554549
],
555550
)
556551
def test_getitem_ndim_deprecated(series):
557-
with tm.assert_produces_warning(
558-
FutureWarning,
559-
match="Support for multi-dimensional indexing",
560-
):
561-
result = series[:, None]
562-
563-
expected = np.asarray(series)[:, None]
564-
tm.assert_numpy_array_equal(result, expected)
552+
with pytest.raises(ValueError, match="Multi-dimensional indexing"):
553+
series[:, None]
565554

566555

567556
def test_getitem_multilevel_scalar_slice_not_implemented(

pandas/tests/series/indexing/test_indexing.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,6 @@ def test_setslice(datetime_series):
184184
assert sl.index.is_unique is True
185185

186186

187-
# FutureWarning from NumPy about [slice(None, 5).
188-
@pytest.mark.filterwarnings("ignore:Using a non-tuple:FutureWarning")
189187
def test_basic_getitem_setitem_corner(datetime_series):
190188
# invalid tuples, e.g. td.ts[:, None] vs. td.ts[:, 2]
191189
msg = "key of type tuple not found and not a MultiIndex"
@@ -200,6 +198,11 @@ def test_basic_getitem_setitem_corner(datetime_series):
200198
# GH#31299
201199
datetime_series[[slice(None, 5)]]
202200

201+
# but we're OK with a single-element tuple
202+
result = datetime_series[(slice(None, 5),)]
203+
expected = datetime_series[:5]
204+
tm.assert_series_equal(result, expected)
205+
203206
# OK
204207
msg = r"unhashable type(: 'slice')?"
205208
with pytest.raises(TypeError, match=msg):

0 commit comments

Comments
 (0)