Skip to content

Commit db6f8fd

Browse files
committed
ENH: support monotonic decreasing indexes in Index.slice_locs
also: NaN and NaT imply not monotonic
1 parent a5915f7 commit db6f8fd

File tree

11 files changed

+231
-62
lines changed

11 files changed

+231
-62
lines changed

doc/source/api.rst

+2
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,8 @@ Attributes
11661166

11671167
Index.values
11681168
Index.is_monotonic
1169+
Index.is_monotonic_increasing
1170+
Index.is_monotonic_decreasing
11691171
Index.is_unique
11701172
Index.dtype
11711173
Index.inferred_type

doc/source/whatsnew/v0.15.1.txt

+26-2
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,29 @@ API changes
146146

147147
s.dt.hour
148148

149+
- support for slicing with monotonic decreasing indexes, even if ``start`` or ``stop`` is
150+
not found in the index (:issue:`7860`):
151+
152+
.. ipython:: python
153+
154+
s = pd.Series(['a', 'b', 'c', 'd'], [4, 3, 2, 1])
155+
s
156+
157+
previous behavior:
158+
159+
.. code-block:: python
160+
161+
In [8]: s.loc[3.5:1.5]
162+
KeyError: 3.5
163+
164+
current behavior:
165+
166+
.. ipython:: python
167+
168+
s.loc[3.5:1.5]
169+
170+
- added Index properties `is_monotonic_increasing` and `is_monotonic_decreasing` (:issue:`8680`).
171+
149172
.. _whatsnew_0151.enhancements:
150173

151174
Enhancements
@@ -208,8 +231,9 @@ Bug Fixes
208231
- Bug in ix/loc block splitting on setitem (manifests with integer-like dtypes, e.g. datetime64) (:issue:`8607`)
209232

210233

211-
212-
234+
- Bug when doing label based indexing with integers not found in the index for
235+
non-unique but monotonic indexes (:issue:`8680`).
236+
- Bug when indexing a Float64Index with ``np.nan`` on numpy 1.7 (:issue:`8980`).
213237

214238

215239

pandas/core/generic.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1461,7 +1461,7 @@ def xs(self, key, axis=0, level=None, copy=None, drop_level=True):
14611461
name=self.index[loc])
14621462

14631463
else:
1464-
result = self[loc]
1464+
result = self.iloc[loc]
14651465
result.index = new_index
14661466

14671467
# this could be a view

pandas/core/index.py

+12-13
Original file line numberDiff line numberDiff line change
@@ -2002,16 +2002,12 @@ def _get_slice(starting_value, offset, search_side, slice_property,
20022002
slc += offset
20032003

20042004
except KeyError:
2005-
if self.is_monotonic:
2006-
2007-
# we are duplicated but non-unique
2008-
# so if we have an indexer then we are done
2009-
# else search for it (GH 7523)
2010-
if not is_unique and is_integer(search_value):
2011-
slc = search_value
2012-
else:
2013-
slc = self.searchsorted(search_value,
2014-
side=search_side)
2005+
if self.is_monotonic_increasing:
2006+
slc = self.searchsorted(search_value, side=search_side)
2007+
elif self.is_monotonic_decreasing:
2008+
search_side = 'right' if search_side == 'left' else 'left'
2009+
slc = len(self) - self[::-1].searchsorted(search_value,
2010+
side=search_side)
20152011
else:
20162012
raise
20172013
return slc
@@ -2445,10 +2441,13 @@ def __contains__(self, other):
24452441
def get_loc(self, key):
24462442
try:
24472443
if np.all(np.isnan(key)):
2444+
nan_idxs = self._nan_idxs
24482445
try:
2449-
return self._nan_idxs.item()
2450-
except ValueError:
2451-
return self._nan_idxs
2446+
return nan_idxs.item()
2447+
except (ValueError, IndexError):
2448+
# should only need to catch ValueError here but on numpy
2449+
# 1.7 .item() can raise IndexError when NaNs are present
2450+
return nan_idxs
24522451
except (TypeError, NotImplementedError):
24532452
pass
24542453
return super(Float64Index, self).get_loc(key)

pandas/index.pyx

+4-4
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ cdef class Int64Engine(IndexEngine):
356356
return _hash.Int64HashTable(n)
357357

358358
def _call_monotonic(self, values):
359-
return algos.is_monotonic_int64(values)
359+
return algos.is_monotonic_int64(values, timelike=False)
360360

361361
def get_pad_indexer(self, other, limit=None):
362362
return algos.pad_int64(self._get_index_values(), other,
@@ -446,7 +446,7 @@ cdef class Float64Engine(IndexEngine):
446446
return result
447447

448448
def _call_monotonic(self, values):
449-
return algos.is_monotonic_float64(values)
449+
return algos.is_monotonic_float64(values, timelike=False)
450450

451451
def get_pad_indexer(self, other, limit=None):
452452
return algos.pad_float64(self._get_index_values(), other,
@@ -500,7 +500,7 @@ cdef class ObjectEngine(IndexEngine):
500500
return _hash.PyObjectHashTable(n)
501501

502502
def _call_monotonic(self, values):
503-
return algos.is_monotonic_object(values)
503+
return algos.is_monotonic_object(values, timelike=False)
504504

505505
def get_pad_indexer(self, other, limit=None):
506506
return algos.pad_object(self._get_index_values(), other,
@@ -532,7 +532,7 @@ cdef class DatetimeEngine(Int64Engine):
532532
return self.vgetter().view('i8')
533533

534534
def _call_monotonic(self, values):
535-
return algos.is_monotonic_int64(values)
535+
return algos.is_monotonic_int64(values, timelike=True)
536536

537537
cpdef get_loc(self, object val):
538538
if is_definitely_invalid_key(val):

pandas/src/generate_code.py

+16-2
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ def diff_2d_%(name)s(ndarray[%(c_type)s, ndim=2] arr,
539539

540540
is_monotonic_template = """@cython.boundscheck(False)
541541
@cython.wraparound(False)
542-
def is_monotonic_%(name)s(ndarray[%(c_type)s] arr):
542+
def is_monotonic_%(name)s(ndarray[%(c_type)s] arr, bint timelike):
543543
'''
544544
Returns
545545
-------
@@ -554,18 +554,32 @@ def is_monotonic_%(name)s(ndarray[%(c_type)s] arr):
554554
555555
n = len(arr)
556556
557-
if n < 2:
557+
if n == 1:
558+
if arr[0] != arr[0] or (timelike and arr[0] == iNaT):
559+
# single value is NaN
560+
return False, False, True
561+
else:
562+
return True, True, True
563+
elif n < 2:
558564
return True, True, True
559565
566+
if timelike and arr[0] == iNaT:
567+
return False, False, None
568+
560569
prev = arr[0]
561570
for i in range(1, n):
562571
cur = arr[i]
572+
if timelike and cur == iNaT:
573+
return False, False, None
563574
if cur < prev:
564575
is_monotonic_inc = 0
565576
elif cur > prev:
566577
is_monotonic_dec = 0
567578
elif cur == prev:
568579
is_unique = 0
580+
else:
581+
# cur or prev is NaN
582+
return False, False, None
569583
if not is_monotonic_inc and not is_monotonic_dec:
570584
return False, False, None
571585
prev = cur

0 commit comments

Comments
 (0)