Skip to content

Commit 560fa87

Browse files
committed
API: enable float slicing with .ix
1 parent a5801f6 commit 560fa87

File tree

4 files changed

+65
-86
lines changed

4 files changed

+65
-86
lines changed

doc/source/indexing.rst

+1-9
Original file line numberDiff line numberDiff line change
@@ -295,26 +295,18 @@ Selection By Label
295295
.. warning::
296296

297297
``.loc`` is strict when you present slicers that are not compatible (or convertible) with the index type. For example
298-
using integers in a ``DatetimeIndex`` or float indexers in an ``Int64Index``. These will raise a ``TypeError``.
298+
using integers in a ``DatetimeIndex``. These will raise a ``TypeError``.
299299

300300
.. ipython:: python
301301
302302
dfl = DataFrame(np.random.randn(5,4), columns=list('ABCD'), index=date_range('20130101',periods=5))
303303
dfl
304-
sl = Series(range(5),[-2,-1,1,2,3])
305-
sl
306304
307305
.. code-block:: python
308306
309307
In [4]: dfl.loc[2:3]
310308
TypeError: cannot do slice indexing on <class 'pandas.tseries.index.DatetimeIndex'> with these indexers [2] of <type 'int'>
311309
312-
.. code-block:: python
313-
314-
In [8]: sl.loc[-1.0:2]
315-
TypeError: cannot do slice indexing on <class 'pandas.core.index.Int64Index'> with these indexers [-1.0] of <type 'float'>
316-
317-
318310
String likes in slicing *can* be convertible to the type of the index and lead to natural slicing.
319311

320312
.. ipython:: python

doc/source/whatsnew/v0.16.0.txt

+17-9
Original file line numberDiff line numberDiff line change
@@ -322,24 +322,32 @@ The behavior of a small sub-set of edge cases for using ``.loc`` have changed (:
322322
In [6]: s.loc[-10:3]
323323
KeyError: 'start bound [-10] is not the [index]'
324324

325-
In [8]: s.loc[-1.0:2]
326-
Out[2]:
327-
-1 1
328-
1 2
329-
2 3
330-
dtype: int64
331-
332325
New Behavior
333326

334327
.. ipython:: python
335328

336329
df.loc['2013-01-02':'2013-01-10']
337330
s.loc[-10:3]
338331

332+
- allow slicing with float-like values on an integer index for ``.ix``. Previously this was only enabled for ``.loc``:
333+
339334
.. code-block:: python
340335

341-
In [8]: s.loc[-1.0:2]
342-
TypeError: cannot do slice indexing on <class 'pandas.core.index.Int64Index'> with these indexers [-1.0] of <type 'float'>
336+
Previous Behavior
337+
338+
In [8]: s.ix[-1.0:2]
339+
TypeError: the slice start value [-1.0] is not a proper indexer for this index type (Int64Index)
340+
341+
New Behavior
342+
343+
.. ipython:: python
344+
345+
In [8]: s.ix[-1.0:2]
346+
Out[2]:
347+
-1 1
348+
1 2
349+
2 3
350+
dtype: int64
343351

344352
- provide a useful exception for indexing with an invalid type for that index when using ``.loc``. For example trying to use ``.loc`` on an index of type ``DatetimeIndex`` or ``PeriodIndex`` or ``TimedeltaIndex``, with an integer (or a float).
345353

pandas/core/index.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -707,8 +707,11 @@ def validate(v):
707707
if v is None or is_integer(v):
708708
return True
709709

710-
# dissallow floats
710+
# dissallow floats (except for .ix)
711711
elif is_float(v):
712+
if typ == 'ix':
713+
return True
714+
712715
return False
713716

714717
return True

pandas/tests/test_indexing.py

+43-67
Original file line numberDiff line numberDiff line change
@@ -4004,6 +4004,12 @@ def check_index(index, error):
40044004

40054005
def test_slice_indexer(self):
40064006

4007+
def check_iloc_compat(s):
4008+
# invalid type for iloc (but works with a warning)
4009+
self.assert_produces_warning(FutureWarning, lambda : s.iloc[6.0:8])
4010+
self.assert_produces_warning(FutureWarning, lambda : s.iloc[6.0:8.0])
4011+
self.assert_produces_warning(FutureWarning, lambda : s.iloc[6:8.0])
4012+
40074013
def check_slicing_positional(index):
40084014

40094015
s = Series(np.arange(len(index))+10,index=index)
@@ -4031,17 +4037,16 @@ def check_slicing_positional(index):
40314037
self.assertRaises(TypeError, lambda : s.loc[2.0:5.0])
40324038
self.assertRaises(TypeError, lambda : s.loc[2:5.0])
40334039

4034-
# these work for now
4035-
#self.assertRaises(TypeError, lambda : s.iloc[2.0:5])
4036-
#self.assertRaises(TypeError, lambda : s.iloc[2.0:5.0])
4037-
#self.assertRaises(TypeError, lambda : s.iloc[2:5.0])
4040+
check_iloc_compat(s)
40384041

40394042
# all index types except int, float
40404043
for index in [ tm.makeStringIndex, tm.makeUnicodeIndex,
40414044
tm.makeDateIndex, tm.makeTimedeltaIndex, tm.makePeriodIndex ]:
40424045
check_slicing_positional(index())
40434046

4044-
# int
4047+
############
4048+
# IntIndex #
4049+
############
40454050
index = tm.makeIntIndex()
40464051
s = Series(np.arange(len(index))+10,index+5)
40474052

@@ -4050,38 +4055,34 @@ def check_slicing_positional(index):
40504055
result4 = s.iloc[2:5]
40514056
assert_series_equal(result1, result4)
40524057

4053-
# these are all value based
4058+
# these are all label based
40544059
result2 = s.ix[2:5]
40554060
result3 = s.loc[2:5]
40564061
assert_series_equal(result2, result3)
40574062

40584063
# float slicers on an int index
40594064
expected = Series([11,12,13],index=[6,7,8])
4060-
result = s.loc[6.0:8.5]
4061-
assert_series_equal(result, expected)
4065+
for method in [lambda x: x.loc, lambda x: x.ix]:
4066+
result = method(s)[6.0:8.5]
4067+
assert_series_equal(result, expected)
40624068

4063-
result = s.loc[5.5:8.5]
4064-
assert_series_equal(result, expected)
4069+
result = method(s)[5.5:8.5]
4070+
assert_series_equal(result, expected)
40654071

4066-
result = s.loc[5.5:8.0]
4067-
assert_series_equal(result, expected)
4072+
result = method(s)[5.5:8.0]
4073+
assert_series_equal(result, expected)
40684074

4069-
# make all float slicing fail for ix/[] with an int index
4075+
# make all float slicing fail for [] with an int index
40704076
self.assertRaises(TypeError, lambda : s[6.0:8])
40714077
self.assertRaises(TypeError, lambda : s[6.0:8.0])
40724078
self.assertRaises(TypeError, lambda : s[6:8.0])
4073-
self.assertRaises(TypeError, lambda : s.ix[6.0:8])
4074-
self.assertRaises(TypeError, lambda : s.ix[6.0:8.0])
4075-
self.assertRaises(TypeError, lambda : s.ix[6:8.0])
40764079

4077-
# these work for now
4078-
#self.assertRaises(TypeError, lambda : s.iloc[6.0:8])
4079-
#self.assertRaises(TypeError, lambda : s.iloc[6.0:8.0])
4080-
#self.assertRaises(TypeError, lambda : s.iloc[6:8.0])
4080+
check_iloc_compat(s)
40814081

4082-
# float
4083-
index = tm.makeFloatIndex()
4084-
s = Series(np.arange(len(index))+10,index=index+5)
4082+
##############
4083+
# FloatIndex #
4084+
##############
4085+
s.index = s.index.astype('float64')
40854086

40864087
# these are all value based
40874088
result1 = s[6:8]
@@ -4090,50 +4091,25 @@ def check_slicing_positional(index):
40904091
assert_series_equal(result1, result2)
40914092
assert_series_equal(result1, result3)
40924093

4093-
# these are all valid
4094-
result1a = s[6.0:8]
4095-
result2a = s[6.0:8.0]
4096-
result3a = s[6:8.0]
4097-
result1b = s[6.5:8]
4098-
result2b = s[6.5:8.5]
4099-
result3b = s[6:8.5]
4100-
assert_series_equal(result1a, result2a)
4101-
assert_series_equal(result1a, result3a)
4102-
assert_series_equal(result1a, result1b)
4103-
assert_series_equal(result1a, result2b)
4104-
assert_series_equal(result1a, result3b)
4105-
4106-
result1c = s.ix[6.0:8]
4107-
result2c = s.ix[6.0:8.0]
4108-
result3c = s.ix[6:8.0]
4109-
result1d = s.ix[6.5:8]
4110-
result2d = s.ix[6.5:8.5]
4111-
result3d = s.ix[6:8.5]
4112-
assert_series_equal(result1a, result1c)
4113-
assert_series_equal(result1a, result2c)
4114-
assert_series_equal(result1a, result3c)
4115-
assert_series_equal(result1a, result1d)
4116-
assert_series_equal(result1a, result2d)
4117-
assert_series_equal(result1a, result3d)
4118-
4119-
result1e = s.loc[6.0:8]
4120-
result2e = s.loc[6.0:8.0]
4121-
result3e = s.loc[6:8.0]
4122-
result1f = s.loc[6.5:8]
4123-
result2f = s.loc[6.5:8.5]
4124-
result3f = s.loc[6:8.5]
4125-
assert_series_equal(result1a, result1e)
4126-
assert_series_equal(result1a, result2e)
4127-
assert_series_equal(result1a, result3e)
4128-
assert_series_equal(result1a, result1f)
4129-
assert_series_equal(result1a, result2f)
4130-
assert_series_equal(result1a, result3f)
4131-
4132-
4133-
# these work for now
4134-
#self.assertRaises(TypeError, lambda : s.iloc[2.0:5])
4135-
#self.assertRaises(TypeError, lambda : s.iloc[2.0:5.0])
4136-
#self.assertRaises(TypeError, lambda : s.iloc[2:5.0])
4094+
# these are valid for all methods
4095+
# these are treated like labels (e.g. the rhs IS included)
4096+
def compare(slicers, expected):
4097+
for method in [lambda x: x, lambda x: x.loc, lambda x: x.ix ]:
4098+
for slices in slicers:
4099+
4100+
result = method(s)[slices]
4101+
assert_series_equal(result, expected)
4102+
4103+
compare([slice(6.0,8),slice(6.0,8.0),slice(6,8.0)],
4104+
s[(s.index>=6.0)&(s.index<=8)])
4105+
compare([slice(6.5,8),slice(6.5,8.5)],
4106+
s[(s.index>=6.5)&(s.index<=8.5)])
4107+
compare([slice(6,8.5)],
4108+
s[(s.index>=6.0)&(s.index<=8.5)])
4109+
compare([slice(6.5,6.5)],
4110+
s[(s.index>=6.5)&(s.index<=6.5)])
4111+
4112+
check_iloc_compat(s)
41374113

41384114
def test_set_ix_out_of_bounds_axis_0(self):
41394115
df = pd.DataFrame(randn(2, 5), index=["row%s" % i for i in range(2)], columns=["col%s" % i for i in range(5)])

0 commit comments

Comments
 (0)