Skip to content

Commit 549a390

Browse files
committed
Merge pull request pandas-dev#6531 from immerrr/fix-out-of-bounds-slice-issues
BUG/TST: fix several issues with slice bound checking code
2 parents 689d765 + 463991b commit 549a390

File tree

10 files changed

+59
-74
lines changed

10 files changed

+59
-74
lines changed

doc/source/release.rst

+3
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ API Changes
108108
- Slicing and advanced/boolean indexing operations on ``Index`` classes will no
109109
longer change type of the resulting index (:issue:`6440`).
110110
- ``set_index`` no longer converts MultiIndexes to an Index of tuples (:issue:`6459`).
111+
- Slicing with negative start, stop & step values handles corner cases better (:issue:`6531`):
112+
- ``df.iloc[:-len(df)]`` is now empty
113+
- ``df.iloc[len(df)::-1]`` now enumerates all elements in reverse
111114

112115
Experimental Features
113116
~~~~~~~~~~~~~~~~~~~~~

pandas/core/frame.py

-5
Original file line numberDiff line numberDiff line change
@@ -1867,11 +1867,6 @@ def eval(self, expr, **kwargs):
18671867
kwargs['resolvers'] = kwargs.get('resolvers', ()) + resolvers
18681868
return _eval(expr, **kwargs)
18691869

1870-
def _slice(self, slobj, axis=0, raise_on_error=False, typ=None):
1871-
axis = self._get_block_manager_axis(axis)
1872-
new_data = self._data.get_slice(
1873-
slobj, axis=axis, raise_on_error=raise_on_error)
1874-
return self._constructor(new_data)
18751870

18761871
def _box_item_values(self, key, values):
18771872
items = self.columns[self.columns.get_loc(key)]

pandas/core/generic.py

+10
Original file line numberDiff line numberDiff line change
@@ -1079,6 +1079,16 @@ def _clear_item_cache(self, i=None):
10791079
else:
10801080
self._item_cache.clear()
10811081

1082+
def _slice(self, slobj, axis=0, typ=None):
1083+
"""
1084+
Construct a slice of this container.
1085+
1086+
typ parameter is maintained for compatibility with Series slicing.
1087+
1088+
"""
1089+
axis = self._get_block_manager_axis(axis)
1090+
return self._constructor(self._data.get_slice(slobj, axis=axis))
1091+
10821092
def _set_item(self, key, value):
10831093
self._data.set(key, value)
10841094
self._clear_item_cache()

pandas/core/indexing.py

+3-40
Original file line numberDiff line numberDiff line change
@@ -91,32 +91,8 @@ def _get_label(self, label, axis=0):
9191
def _get_loc(self, key, axis=0):
9292
return self.obj._ixs(key, axis=axis)
9393

94-
def _slice(self, obj, axis=0, raise_on_error=False, typ=None):
95-
96-
# make out-of-bounds into bounds of the object
97-
if typ == 'iloc':
98-
ax = self.obj._get_axis(axis)
99-
l = len(ax)
100-
start = obj.start
101-
stop = obj.stop
102-
step = obj.step
103-
if start is not None:
104-
# degenerate to return nothing
105-
if start >= l:
106-
return self._getitem_axis(tuple(),axis=axis)
107-
108-
# equiv to a null slice
109-
elif start <= -l:
110-
start = None
111-
if stop is not None:
112-
if stop > l:
113-
stop = None
114-
elif stop <= -l:
115-
stop = None
116-
obj = slice(start,stop,step)
117-
118-
return self.obj._slice(obj, axis=axis, raise_on_error=raise_on_error,
119-
typ=typ)
94+
def _slice(self, obj, axis=0, typ=None):
95+
return self.obj._slice(obj, axis=axis, typ=typ)
12096

12197
def __setitem__(self, key, value):
12298

@@ -1343,8 +1319,7 @@ def _get_slice_axis(self, slice_obj, axis=0):
13431319
return obj
13441320

13451321
if isinstance(slice_obj, slice):
1346-
return self._slice(slice_obj, axis=axis, raise_on_error=True,
1347-
typ='iloc')
1322+
return self._slice(slice_obj, axis=axis, typ='iloc')
13481323
else:
13491324
return self.obj.take(slice_obj, axis=axis, convert=False)
13501325

@@ -1647,18 +1622,6 @@ def _need_slice(obj):
16471622
(obj.step is not None and obj.step != 1))
16481623

16491624

1650-
def _check_slice_bounds(slobj, values):
1651-
l = len(values)
1652-
start = slobj.start
1653-
if start is not None:
1654-
if start < -l or start > l - 1:
1655-
raise IndexError("out-of-bounds on slice (start)")
1656-
stop = slobj.stop
1657-
if stop is not None:
1658-
if stop < -l - 1 or stop > l:
1659-
raise IndexError("out-of-bounds on slice (end)")
1660-
1661-
16621625
def _maybe_droplevels(index, key):
16631626
# drop levels
16641627
original_index = index

pandas/core/internals.py

+3-9
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414
_values_from_object, _is_null_datelike_scalar)
1515
from pandas.core.index import (Index, MultiIndex, _ensure_index,
1616
_handle_legacy_indexes)
17-
from pandas.core.indexing import (_check_slice_bounds, _maybe_convert_indices,
18-
_length_of_indexer)
17+
from pandas.core.indexing import (_maybe_convert_indices, _length_of_indexer)
1918
import pandas.core.common as com
2019
from pandas.sparse.array import _maybe_to_sparse, SparseArray
2120
import pandas.lib as lib
@@ -2669,12 +2668,9 @@ def combine(self, blocks):
26692668
new_axes[0] = new_items
26702669
return self.__class__(new_blocks, new_axes, do_integrity_check=False)
26712670

2672-
def get_slice(self, slobj, axis=0, raise_on_error=False):
2671+
def get_slice(self, slobj, axis=0):
26732672
new_axes = list(self.axes)
26742673

2675-
if raise_on_error:
2676-
_check_slice_bounds(slobj, new_axes[axis])
2677-
26782674
new_axes[axis] = new_axes[axis][slobj]
26792675

26802676
if axis == 0:
@@ -3739,9 +3735,7 @@ def _delete_from_block(self, i, item):
37393735
)
37403736
self._values = self._block.values
37413737

3742-
def get_slice(self, slobj, raise_on_error=False):
3743-
if raise_on_error:
3744-
_check_slice_bounds(slobj, self.index)
3738+
def get_slice(self, slobj):
37453739
return self.__class__(self._block._slice(slobj),
37463740
self.index[slobj], fastpath=True)
37473741

pandas/core/panel.py

-6
Original file line numberDiff line numberDiff line change
@@ -539,12 +539,6 @@ def _box_item_values(self, key, values):
539539
d = self._construct_axes_dict_for_slice(self._AXIS_ORDERS[1:])
540540
return self._constructor_sliced(values, **d)
541541

542-
def _slice(self, slobj, axis=0, raise_on_error=False, typ=None):
543-
new_data = self._data.get_slice(slobj,
544-
axis=axis,
545-
raise_on_error=raise_on_error)
546-
return self._constructor(new_data)
547-
548542
def __setitem__(self, key, value):
549543
shape = tuple(self.shape)
550544
if isinstance(value, self._constructor_sliced):

pandas/core/series.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from pandas.core.index import (Index, MultiIndex, InvalidIndexError,
2929
_ensure_index, _handle_legacy_indexes)
3030
from pandas.core.indexing import (
31-
_check_bool_indexer, _check_slice_bounds,
31+
_check_bool_indexer,
3232
_is_index_slice, _maybe_convert_indices)
3333
from pandas.core import generic, base
3434
from pandas.core.internals import SingleBlockManager
@@ -469,9 +469,7 @@ def _ixs(self, i, axis=0):
469469
def _is_mixed_type(self):
470470
return False
471471

472-
def _slice(self, slobj, axis=0, raise_on_error=False, typ=None):
473-
if raise_on_error:
474-
_check_slice_bounds(slobj, self.values)
472+
def _slice(self, slobj, axis=0, typ=None):
475473
slobj = self.index._convert_slice_indexer(slobj, typ=typ or 'getitem')
476474
return self._get_values(slobj)
477475

pandas/sparse/frame.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from pandas.core.common import (isnull, notnull, _pickle_array,
1414
_unpickle_array, _try_sort)
1515
from pandas.core.index import Index, MultiIndex, _ensure_index
16-
from pandas.core.indexing import _check_slice_bounds, _maybe_convert_indices
16+
from pandas.core.indexing import _maybe_convert_indices
1717
from pandas.core.series import Series
1818
from pandas.core.frame import (DataFrame, extract_index, _prep_ndarray,
1919
_default_index)
@@ -379,15 +379,11 @@ def set_value(self, index, col, value, takeable=False):
379379
return dense.to_sparse(kind=self._default_kind,
380380
fill_value=self._default_fill_value)
381381

382-
def _slice(self, slobj, axis=0, raise_on_error=False, typ=None):
382+
def _slice(self, slobj, axis=0, typ=None):
383383
if axis == 0:
384-
if raise_on_error:
385-
_check_slice_bounds(slobj, self.index)
386384
new_index = self.index[slobj]
387385
new_columns = self.columns
388386
else:
389-
if raise_on_error:
390-
_check_slice_bounds(slobj, self.columns)
391387
new_index = self.index
392388
new_columns = self.columns[slobj]
393389

pandas/sparse/panel.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ def _ixs(self, i, axis=0):
187187

188188
return self.xs(key, axis=axis)
189189

190-
def _slice(self, slobj, axis=0, raise_on_error=False, typ=None):
190+
def _slice(self, slobj, axis=0, typ=None):
191191
"""
192192
for compat as we don't support Block Manager here
193193
"""

pandas/tests/test_indexing.py

+35-3
Original file line numberDiff line numberDiff line change
@@ -393,19 +393,51 @@ def test_iloc_exceeds_bounds(self):
393393
self.assertRaises(IndexError, lambda : df.iloc[-30])
394394

395395
# slices are ok
396-
result = df.iloc[:,4:10]
396+
result = df.iloc[:,4:10] # 0 < start < len < stop
397397
expected = df.iloc[:,4:]
398398
assert_frame_equal(result,expected)
399399

400-
result = df.iloc[:,-4:-10]
401-
expected = df.iloc[:,-4:]
400+
result = df.iloc[:,-4:-10] # stop < 0 < start < len
401+
expected = df.iloc[:,:0]
402+
assert_frame_equal(result,expected)
403+
404+
result = df.iloc[:,10:4:-1] # 0 < stop < len < start (down)
405+
expected = df.iloc[:,:4:-1]
406+
assert_frame_equal(result,expected)
407+
408+
result = df.iloc[:,4:-10:-1] # stop < 0 < start < len (down)
409+
expected = df.iloc[:,4::-1]
410+
assert_frame_equal(result,expected)
411+
412+
result = df.iloc[:,-10:4] # start < 0 < stop < len
413+
expected = df.iloc[:,:4]
414+
assert_frame_equal(result,expected)
415+
416+
result = df.iloc[:,10:4] # 0 < stop < len < start
417+
expected = df.iloc[:,:0]
418+
assert_frame_equal(result,expected)
419+
420+
result = df.iloc[:,-10:-11:-1] # stop < start < 0 < len (down)
421+
expected = df.iloc[:,:0]
422+
assert_frame_equal(result,expected)
423+
424+
result = df.iloc[:,10:11] # 0 < len < start < stop
425+
expected = df.iloc[:,:0]
402426
assert_frame_equal(result,expected)
403427

404428
# slice bounds exceeding is ok
405429
result = s.iloc[18:30]
406430
expected = s.iloc[18:]
407431
assert_series_equal(result,expected)
408432

433+
result = s.iloc[30:]
434+
expected = s.iloc[:0]
435+
assert_series_equal(result,expected)
436+
437+
result = s.iloc[30::-1]
438+
expected = s.iloc[::-1]
439+
assert_series_equal(result,expected)
440+
409441
# doc example
410442
def check(result,expected):
411443
str(result)

0 commit comments

Comments
 (0)