Skip to content

Commit 594f653

Browse files
committed
PERF: rework MultiIndex.is_monotonic as per @ssanderson idea
1 parent c8563a6 commit 594f653

File tree

2 files changed

+17
-9
lines changed

2 files changed

+17
-9
lines changed

asv_bench/benchmarks/indexing.py

+3
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,9 @@ def time_multiindex_get_indexer(self):
204204
self.miint.get_indexer(np.array([(0, 10), (0, 11), (0, 12), (0, 13), (0, 14),
205205
(0, 15), (0, 16),(0, 17), (0, 18), (0, 19)], dtype=object))
206206

207+
def time_is_monotonic(self):
208+
self.miint.is_monotonic
209+
207210

208211
class PanelIndexing(object):
209212
goal_time = 0.2

pandas/indexes/multi.py

+14-9
Original file line numberDiff line numberDiff line change
@@ -667,15 +667,20 @@ def _has_complex_internals(self):
667667
@cache_readonly
668668
def is_monotonic(self):
669669

670-
# TODO
671-
# this is unfortunate we end up tupelizing
672-
# just to determine monotonicity :<
673-
674-
# fast-path
675-
if not self.levels[0].is_monotonic:
676-
return False
677-
678-
return Index(self.values).is_monotonic
670+
def level_values(level):
671+
unique = self.levels[level]
672+
labels = self.labels[level]
673+
return algos.take_1d(unique.values, labels,
674+
fill_value=unique._na_value)
675+
676+
def int64_is_monotonic(a):
677+
# This is needlessly expensive in the non-monotonic case.
678+
return (np.diff(a) >= 0).all()
679+
680+
# reversed() because lexsort() wants the most significant key last.
681+
values = [level_values(i) for i in reversed(range(len(self.levels)))]
682+
sort_order = np.lexsort(values)
683+
return int64_is_monotonic(sort_order)
679684

680685
@cache_readonly
681686
def is_unique(self):

0 commit comments

Comments
 (0)