Skip to content

Commit a5915f7

Browse files
committed
ENH: add Index.is_monotonic_decreasing and Index.is_monotonic_increasing
Index.is_monotonic will have a performance degradation (still O(n) time) in cases where the Index is decreasing monotonic. If necessary, we could work around this, but I think we can probably get away with this because the fall- back options are much slower and in many cases (e.g., for slice indexing) the next thing we'll want to know is if it's decreasing monotonic, anyways. see GH7860
1 parent 02de853 commit a5915f7

File tree

5 files changed

+116
-40
lines changed

5 files changed

+116
-40
lines changed

pandas/core/index.py

+16-2
Original file line numberDiff line numberDiff line change
@@ -573,8 +573,22 @@ def _mpl_repr(self):
573573

574574
@property
575575
def is_monotonic(self):
576-
""" return if the index has monotonic (only equaly or increasing) values """
577-
return self._engine.is_monotonic
576+
""" alias for is_monotonic_increasing (deprecated) """
577+
return self._engine.is_monotonic_increasing
578+
579+
@property
580+
def is_monotonic_increasing(self):
581+
""" return if the index is monotonic increasing (only equal or
582+
increasing) values
583+
"""
584+
return self._engine.is_monotonic_increasing
585+
586+
@property
587+
def is_monotonic_decreasing(self):
588+
""" return if the index is monotonic decreasing (only equal or
589+
decreasing values
590+
"""
591+
return self._engine.is_monotonic_decreasing
578592

579593
def is_lexsorted_for_tuple(self, tup):
580594
return True

pandas/index.pyx

+21-10
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ cdef class IndexEngine:
7777
bint over_size_threshold
7878

7979
cdef:
80-
bint unique, monotonic
80+
bint unique, monotonic_inc, monotonic_dec
8181
bint initialized, monotonic_check, unique_check
8282

8383
def __init__(self, vgetter, n):
@@ -89,7 +89,8 @@ cdef class IndexEngine:
8989
self.monotonic_check = 0
9090

9191
self.unique = 0
92-
self.monotonic = 0
92+
self.monotonic_inc = 0
93+
self.monotonic_dec = 0
9394

9495
def __contains__(self, object val):
9596
self._ensure_mapping_populated()
@@ -134,7 +135,7 @@ cdef class IndexEngine:
134135
if is_definitely_invalid_key(val):
135136
raise TypeError
136137

137-
if self.over_size_threshold and self.is_monotonic:
138+
if self.over_size_threshold and self.is_monotonic_increasing:
138139
if not self.is_unique:
139140
return self._get_loc_duplicates(val)
140141
values = self._get_index_values()
@@ -158,7 +159,7 @@ cdef class IndexEngine:
158159
cdef:
159160
Py_ssize_t diff
160161

161-
if self.is_monotonic:
162+
if self.is_monotonic_increasing:
162163
values = self._get_index_values()
163164
left = values.searchsorted(val, side='left')
164165
right = values.searchsorted(val, side='right')
@@ -210,25 +211,35 @@ cdef class IndexEngine:
210211

211212
return self.unique == 1
212213

213-
property is_monotonic:
214+
property is_monotonic_increasing:
214215

215216
def __get__(self):
216217
if not self.monotonic_check:
217218
self._do_monotonic_check()
218219

219-
return self.monotonic == 1
220+
return self.monotonic_inc == 1
221+
222+
property is_monotonic_decreasing:
223+
224+
def __get__(self):
225+
if not self.monotonic_check:
226+
self._do_monotonic_check()
227+
228+
return self.monotonic_dec == 1
220229

221230
cdef inline _do_monotonic_check(self):
222231
try:
223232
values = self._get_index_values()
224-
self.monotonic, unique = self._call_monotonic(values)
233+
self.monotonic_inc, self.monotonic_dec, unique = \
234+
self._call_monotonic(values)
225235

226236
if unique is not None:
227237
self.unique = unique
228238
self.unique_check = 1
229239

230240
except TypeError:
231-
self.monotonic = 0
241+
self.monotonic_inc = 0
242+
self.monotonic_dec = 0
232243
self.monotonic_check = 1
233244

234245
cdef _get_index_values(self):
@@ -506,7 +517,7 @@ cdef class DatetimeEngine(Int64Engine):
506517
return 'M8[ns]'
507518

508519
def __contains__(self, object val):
509-
if self.over_size_threshold and self.is_monotonic:
520+
if self.over_size_threshold and self.is_monotonic_increasing:
510521
if not self.is_unique:
511522
return self._get_loc_duplicates(val)
512523
values = self._get_index_values()
@@ -529,7 +540,7 @@ cdef class DatetimeEngine(Int64Engine):
529540

530541
# Welcome to the spaghetti factory
531542

532-
if self.over_size_threshold and self.is_monotonic:
543+
if self.over_size_threshold and self.is_monotonic_increasing:
533544
if not self.is_unique:
534545
val = _to_i8(val)
535546
return self._get_loc_duplicates(val)

pandas/src/generate_code.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -543,27 +543,33 @@ def is_monotonic_%(name)s(ndarray[%(c_type)s] arr):
543543
'''
544544
Returns
545545
-------
546-
is_monotonic, is_unique
546+
is_monotonic_inc, is_monotonic_dec, is_unique
547547
'''
548548
cdef:
549549
Py_ssize_t i, n
550550
%(c_type)s prev, cur
551551
bint is_unique = 1
552+
bint is_monotonic_inc = 1
553+
bint is_monotonic_dec = 1
552554
553555
n = len(arr)
554556
555557
if n < 2:
556-
return True, True
558+
return True, True, True
557559
558560
prev = arr[0]
559561
for i in range(1, n):
560562
cur = arr[i]
561563
if cur < prev:
562-
return False, None
564+
is_monotonic_inc = 0
565+
elif cur > prev:
566+
is_monotonic_dec = 0
563567
elif cur == prev:
564568
is_unique = 0
569+
if not is_monotonic_inc and not is_monotonic_dec:
570+
return False, False, None
565571
prev = cur
566-
return True, is_unique
572+
return is_monotonic_inc, is_monotonic_dec, is_unique
567573
"""
568574

569575
map_indices_template = """@cython.wraparound(False)

pandas/src/generated.pyx

+60-24
Original file line numberDiff line numberDiff line change
@@ -1803,162 +1803,198 @@ def is_monotonic_float64(ndarray[float64_t] arr):
18031803
'''
18041804
Returns
18051805
-------
1806-
is_monotonic, is_unique
1806+
is_monotonic_inc, is_monotonic_dec, is_unique
18071807
'''
18081808
cdef:
18091809
Py_ssize_t i, n
18101810
float64_t prev, cur
18111811
bint is_unique = 1
1812+
bint is_monotonic_inc = 1
1813+
bint is_monotonic_dec = 1
18121814

18131815
n = len(arr)
18141816

18151817
if n < 2:
1816-
return True, True
1818+
return True, True, True
18171819

18181820
prev = arr[0]
18191821
for i in range(1, n):
18201822
cur = arr[i]
18211823
if cur < prev:
1822-
return False, None
1824+
is_monotonic_inc = 0
1825+
elif cur > prev:
1826+
is_monotonic_dec = 0
18231827
elif cur == prev:
18241828
is_unique = 0
1829+
if not is_monotonic_inc and not is_monotonic_dec:
1830+
return False, False, None
18251831
prev = cur
1826-
return True, is_unique
1832+
return is_monotonic_inc, is_monotonic_dec, is_unique
18271833
@cython.boundscheck(False)
18281834
@cython.wraparound(False)
18291835
def is_monotonic_float32(ndarray[float32_t] arr):
18301836
'''
18311837
Returns
18321838
-------
1833-
is_monotonic, is_unique
1839+
is_monotonic_inc, is_monotonic_dec, is_unique
18341840
'''
18351841
cdef:
18361842
Py_ssize_t i, n
18371843
float32_t prev, cur
18381844
bint is_unique = 1
1845+
bint is_monotonic_inc = 1
1846+
bint is_monotonic_dec = 1
18391847

18401848
n = len(arr)
18411849

18421850
if n < 2:
1843-
return True, True
1851+
return True, True, True
18441852

18451853
prev = arr[0]
18461854
for i in range(1, n):
18471855
cur = arr[i]
18481856
if cur < prev:
1849-
return False, None
1857+
is_monotonic_inc = 0
1858+
elif cur > prev:
1859+
is_monotonic_dec = 0
18501860
elif cur == prev:
18511861
is_unique = 0
1862+
if not is_monotonic_inc and not is_monotonic_dec:
1863+
return False, False, None
18521864
prev = cur
1853-
return True, is_unique
1865+
return is_monotonic_inc, is_monotonic_dec, is_unique
18541866
@cython.boundscheck(False)
18551867
@cython.wraparound(False)
18561868
def is_monotonic_object(ndarray[object] arr):
18571869
'''
18581870
Returns
18591871
-------
1860-
is_monotonic, is_unique
1872+
is_monotonic_inc, is_monotonic_dec, is_unique
18611873
'''
18621874
cdef:
18631875
Py_ssize_t i, n
18641876
object prev, cur
18651877
bint is_unique = 1
1878+
bint is_monotonic_inc = 1
1879+
bint is_monotonic_dec = 1
18661880

18671881
n = len(arr)
18681882

18691883
if n < 2:
1870-
return True, True
1884+
return True, True, True
18711885

18721886
prev = arr[0]
18731887
for i in range(1, n):
18741888
cur = arr[i]
18751889
if cur < prev:
1876-
return False, None
1890+
is_monotonic_inc = 0
1891+
elif cur > prev:
1892+
is_monotonic_dec = 0
18771893
elif cur == prev:
18781894
is_unique = 0
1895+
if not is_monotonic_inc and not is_monotonic_dec:
1896+
return False, False, None
18791897
prev = cur
1880-
return True, is_unique
1898+
return is_monotonic_inc, is_monotonic_dec, is_unique
18811899
@cython.boundscheck(False)
18821900
@cython.wraparound(False)
18831901
def is_monotonic_int32(ndarray[int32_t] arr):
18841902
'''
18851903
Returns
18861904
-------
1887-
is_monotonic, is_unique
1905+
is_monotonic_inc, is_monotonic_dec, is_unique
18881906
'''
18891907
cdef:
18901908
Py_ssize_t i, n
18911909
int32_t prev, cur
18921910
bint is_unique = 1
1911+
bint is_monotonic_inc = 1
1912+
bint is_monotonic_dec = 1
18931913

18941914
n = len(arr)
18951915

18961916
if n < 2:
1897-
return True, True
1917+
return True, True, True
18981918

18991919
prev = arr[0]
19001920
for i in range(1, n):
19011921
cur = arr[i]
19021922
if cur < prev:
1903-
return False, None
1923+
is_monotonic_inc = 0
1924+
elif cur > prev:
1925+
is_monotonic_dec = 0
19041926
elif cur == prev:
19051927
is_unique = 0
1928+
if not is_monotonic_inc and not is_monotonic_dec:
1929+
return False, False, None
19061930
prev = cur
1907-
return True, is_unique
1931+
return is_monotonic_inc, is_monotonic_dec, is_unique
19081932
@cython.boundscheck(False)
19091933
@cython.wraparound(False)
19101934
def is_monotonic_int64(ndarray[int64_t] arr):
19111935
'''
19121936
Returns
19131937
-------
1914-
is_monotonic, is_unique
1938+
is_monotonic_inc, is_monotonic_dec, is_unique
19151939
'''
19161940
cdef:
19171941
Py_ssize_t i, n
19181942
int64_t prev, cur
19191943
bint is_unique = 1
1944+
bint is_monotonic_inc = 1
1945+
bint is_monotonic_dec = 1
19201946

19211947
n = len(arr)
19221948

19231949
if n < 2:
1924-
return True, True
1950+
return True, True, True
19251951

19261952
prev = arr[0]
19271953
for i in range(1, n):
19281954
cur = arr[i]
19291955
if cur < prev:
1930-
return False, None
1956+
is_monotonic_inc = 0
1957+
elif cur > prev:
1958+
is_monotonic_dec = 0
19311959
elif cur == prev:
19321960
is_unique = 0
1961+
if not is_monotonic_inc and not is_monotonic_dec:
1962+
return False, False, None
19331963
prev = cur
1934-
return True, is_unique
1964+
return is_monotonic_inc, is_monotonic_dec, is_unique
19351965
@cython.boundscheck(False)
19361966
@cython.wraparound(False)
19371967
def is_monotonic_bool(ndarray[uint8_t] arr):
19381968
'''
19391969
Returns
19401970
-------
1941-
is_monotonic, is_unique
1971+
is_monotonic_inc, is_monotonic_dec, is_unique
19421972
'''
19431973
cdef:
19441974
Py_ssize_t i, n
19451975
uint8_t prev, cur
19461976
bint is_unique = 1
1977+
bint is_monotonic_inc = 1
1978+
bint is_monotonic_dec = 1
19471979

19481980
n = len(arr)
19491981

19501982
if n < 2:
1951-
return True, True
1983+
return True, True, True
19521984

19531985
prev = arr[0]
19541986
for i in range(1, n):
19551987
cur = arr[i]
19561988
if cur < prev:
1957-
return False, None
1989+
is_monotonic_inc = 0
1990+
elif cur > prev:
1991+
is_monotonic_dec = 0
19581992
elif cur == prev:
19591993
is_unique = 0
1994+
if not is_monotonic_inc and not is_monotonic_dec:
1995+
return False, False, None
19601996
prev = cur
1961-
return True, is_unique
1997+
return is_monotonic_inc, is_monotonic_dec, is_unique
19621998

19631999
@cython.wraparound(False)
19642000
@cython.boundscheck(False)

0 commit comments

Comments
 (0)