Skip to content

Commit 3c4b8cc

Browse files
committed
Implement MultiIndex.is_monotonic_decreasing
Implemented MultiIndex.is_monotonic_decreasing, and added associated tests. Also added tests for IntervalIndex.is_monotonic_decreasing, as it uses MultiIndex under the hood.
1 parent d457791 commit 3c4b8cc

File tree

4 files changed

+178
-13
lines changed

4 files changed

+178
-13
lines changed

doc/source/whatsnew/v0.21.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ Other Enhancements
130130
- `read_*` methods can now infer compression from non-string paths, such as ``pathlib.Path`` objects (:issue:`17206`).
131131
- :func:`pd.read_sas()` now recognizes much more of the most frequently used date (datetime) formats in SAS7BDAT files (:issue:`15871`).
132132
- :func:`DataFrame.items` and :func:`Series.items` is now present in both Python 2 and 3 and is lazy in all cases (:issue:`13918`, :issue:`17213`)
133+
- :func:`MultiIndex.is_monotonic_decreasing` has been implemented. Previously returned ``False`` in all cases. (:issue:`16554`)
133134

134135

135136

pandas/core/indexes/multi.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -706,13 +706,14 @@ def is_monotonic_increasing(self):
706706
# we have mixed types and np.lexsort is not happy
707707
return Index(self.values).is_monotonic
708708

709-
@property
709+
@cache_readonly
710710
def is_monotonic_decreasing(self):
711711
"""
712712
return if the index is monotonic decreasing (only equal or
713713
decreasing) values.
714714
"""
715-
return False
715+
# monotonic decreasing if and only if reverse is monotonic increasing
716+
return self[::-1].is_monotonic_increasing
716717

717718
@cache_readonly
718719
def is_unique(self):

pandas/tests/indexes/test_interval.py

+95-7
Original file line numberDiff line numberDiff line change
@@ -262,21 +262,109 @@ def test_take(self):
262262
actual = self.index.take([0, 0, 1])
263263
assert expected.equals(actual)
264264

265-
def test_monotonic_and_unique(self):
266-
assert self.index.is_monotonic
267-
assert self.index.is_unique
265+
def test_unique(self):
266+
# unique non-overlapping
267+
idx = IntervalIndex.from_tuples([(0, 1), (2, 3), (4, 5)])
268+
assert idx.is_unique
268269

270+
# unique overlapping - distinct endpoints
269271
idx = IntervalIndex.from_tuples([(0, 1), (0.5, 1.5)])
270-
assert idx.is_monotonic
271272
assert idx.is_unique
272273

273-
idx = IntervalIndex.from_tuples([(0, 1), (2, 3), (1, 2)])
274-
assert not idx.is_monotonic
274+
# unique overlapping - shared endpoints
275+
idx = pd.IntervalIndex.from_tuples([(1, 2), (1, 3), (2, 3)])
276+
assert idx.is_unique
277+
278+
# unique nested
279+
idx = IntervalIndex.from_tuples([(-1, 1), (-2, 2)])
280+
assert idx.is_unique
281+
282+
# duplicate
283+
idx = IntervalIndex.from_tuples([(0, 1), (0, 1), (2, 3)])
284+
assert not idx.is_unique
285+
286+
# unique mixed
287+
idx = IntervalIndex.from_tuples([(0, 1), ('a', 'b')])
275288
assert idx.is_unique
276289

277-
idx = IntervalIndex.from_tuples([(0, 2), (0, 2)])
290+
# duplicate mixed
291+
idx = IntervalIndex.from_tuples([(0, 1), ('a', 'b'), (0, 1)])
278292
assert not idx.is_unique
293+
294+
# empty
295+
idx = IntervalIndex([])
296+
assert idx.is_unique
297+
298+
def test_monotonic(self):
299+
# increasing non-overlapping
300+
idx = IntervalIndex.from_tuples([(0, 1), (2, 3), (4, 5)])
301+
assert idx.is_monotonic
302+
assert idx._is_strictly_monotonic_increasing
303+
assert not idx.is_monotonic_decreasing
304+
assert not idx._is_strictly_monotonic_decreasing
305+
306+
# decreasing non-overlapping
307+
idx = IntervalIndex.from_tuples([(4, 5), (2, 3), (1, 2)])
308+
assert not idx.is_monotonic
309+
assert not idx._is_strictly_monotonic_increasing
310+
assert idx.is_monotonic_decreasing
311+
assert idx._is_strictly_monotonic_decreasing
312+
313+
# unordered non-overlapping
314+
idx = IntervalIndex.from_tuples([(0, 1), (4, 5), (2, 3)])
315+
assert not idx.is_monotonic
316+
assert not idx._is_strictly_monotonic_increasing
317+
assert not idx.is_monotonic_decreasing
318+
assert not idx._is_strictly_monotonic_decreasing
319+
320+
# increasing overlapping
321+
idx = IntervalIndex.from_tuples([(0, 2), (0.5, 2.5), (1, 3)])
322+
assert idx.is_monotonic
323+
assert idx._is_strictly_monotonic_increasing
324+
assert not idx.is_monotonic_decreasing
325+
assert not idx._is_strictly_monotonic_decreasing
326+
327+
# decreasing overlapping
328+
idx = IntervalIndex.from_tuples([(1, 3), (0.5, 2.5), (0, 2)])
329+
assert not idx.is_monotonic
330+
assert not idx._is_strictly_monotonic_increasing
331+
assert idx.is_monotonic_decreasing
332+
assert idx._is_strictly_monotonic_decreasing
333+
334+
# unordered overlapping
335+
idx = IntervalIndex.from_tuples([(0.5, 2.5), (0, 2), (1, 3)])
336+
assert not idx.is_monotonic
337+
assert not idx._is_strictly_monotonic_increasing
338+
assert not idx.is_monotonic_decreasing
339+
assert not idx._is_strictly_monotonic_decreasing
340+
341+
# increasing overlapping shared endpoints
342+
idx = pd.IntervalIndex.from_tuples([(1, 2), (1, 3), (2, 3)])
343+
assert idx.is_monotonic
344+
assert idx._is_strictly_monotonic_increasing
345+
assert not idx.is_monotonic_decreasing
346+
assert not idx._is_strictly_monotonic_decreasing
347+
348+
# decreasing overlapping shared endpoints
349+
idx = pd.IntervalIndex.from_tuples([(2, 3), (1, 3), (1, 2)])
350+
assert not idx.is_monotonic
351+
assert not idx._is_strictly_monotonic_increasing
352+
assert idx.is_monotonic_decreasing
353+
assert idx._is_strictly_monotonic_decreasing
354+
355+
# stationary
356+
idx = IntervalIndex.from_tuples([(0, 1), (0, 1)])
357+
assert idx.is_monotonic
358+
assert not idx._is_strictly_monotonic_increasing
359+
assert idx.is_monotonic_decreasing
360+
assert not idx._is_strictly_monotonic_decreasing
361+
362+
# empty
363+
idx = IntervalIndex([])
279364
assert idx.is_monotonic
365+
assert idx._is_strictly_monotonic_increasing
366+
assert idx.is_monotonic_decreasing
367+
assert idx._is_strictly_monotonic_decreasing
280368

281369
@pytest.mark.xfail(reason='not a valid repr as we use interval notation')
282370
def test_repr(self):

pandas/tests/indexes/test_multi.py

+79-4
Original file line numberDiff line numberDiff line change
@@ -2381,7 +2381,7 @@ def test_level_setting_resets_attributes(self):
23812381
# if this fails, probably didn't reset the cache correctly.
23822382
assert not ind.is_monotonic
23832383

2384-
def test_is_monotonic(self):
2384+
def test_is_monotonic_increasing(self):
23852385
i = MultiIndex.from_product([np.arange(10),
23862386
np.arange(10)], names=['one', 'two'])
23872387
assert i.is_monotonic
@@ -2442,14 +2442,89 @@ def test_is_monotonic(self):
24422442
assert not i.is_monotonic
24432443
assert not i._is_strictly_monotonic_increasing
24442444

2445-
def test_is_strictly_monotonic(self):
2445+
# empty
2446+
i = MultiIndex.from_arrays([[], []])
2447+
assert i.is_monotonic
2448+
assert Index(i.values).is_monotonic
2449+
assert i._is_strictly_monotonic_increasing
2450+
assert Index(i.values)._is_strictly_monotonic_increasing
2451+
2452+
def test_is_monotonic_decreasing(self):
2453+
i = MultiIndex.from_product([np.arange(9, -1, -1),
2454+
np.arange(9, -1, -1)],
2455+
names=['one', 'two'])
2456+
assert i.is_monotonic_decreasing
2457+
assert i._is_strictly_monotonic_decreasing
2458+
assert Index(i.values).is_monotonic_decreasing
2459+
assert i._is_strictly_monotonic_decreasing
2460+
2461+
i = MultiIndex.from_product([np.arange(10),
2462+
np.arange(10, 0, -1)],
2463+
names=['one', 'two'])
2464+
assert not i.is_monotonic_decreasing
2465+
assert not i._is_strictly_monotonic_decreasing
2466+
assert not Index(i.values).is_monotonic_decreasing
2467+
assert not Index(i.values)._is_strictly_monotonic_decreasing
2468+
2469+
i = MultiIndex.from_product([np.arange(10, 0, -1),
2470+
np.arange(10)], names=['one', 'two'])
2471+
assert not i.is_monotonic_decreasing
2472+
assert not i._is_strictly_monotonic_decreasing
2473+
assert not Index(i.values).is_monotonic_decreasing
2474+
assert not Index(i.values)._is_strictly_monotonic_decreasing
2475+
2476+
i = MultiIndex.from_product([[2.0, np.nan, 1.0], ['c', 'b', 'a']])
2477+
assert not i.is_monotonic_decreasing
2478+
assert not i._is_strictly_monotonic_decreasing
2479+
assert not Index(i.values).is_monotonic_decreasing
2480+
assert not Index(i.values)._is_strictly_monotonic_decreasing
2481+
2482+
# string ordering
2483+
i = MultiIndex(levels=[['qux', 'foo', 'baz', 'bar'],
2484+
['three', 'two', 'one']],
2485+
labels=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3],
2486+
[0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
2487+
names=['first', 'second'])
2488+
assert not i.is_monotonic_decreasing
2489+
assert not Index(i.values).is_monotonic_decreasing
2490+
assert not i._is_strictly_monotonic_decreasing
2491+
assert not Index(i.values)._is_strictly_monotonic_decreasing
2492+
2493+
i = MultiIndex(levels=[['qux', 'foo', 'baz', 'bar'],
2494+
['zenith', 'next', 'mom']],
2495+
labels=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3],
2496+
[0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
2497+
names=['first', 'second'])
2498+
assert i.is_monotonic_decreasing
2499+
assert Index(i.values).is_monotonic_decreasing
2500+
assert i._is_strictly_monotonic_decreasing
2501+
assert Index(i.values)._is_strictly_monotonic_decreasing
2502+
2503+
# mixed levels, hits the TypeError
2504+
i = MultiIndex(
2505+
levels=[[4, 3, 2, 1], ['nl0000301109', 'nl0000289965',
2506+
'nl0000289783', 'lu0197800237',
2507+
'gb00b03mlx29']],
2508+
labels=[[0, 1, 1, 2, 2, 2, 3], [4, 2, 0, 0, 1, 3, -1]],
2509+
names=['household_id', 'asset_id'])
2510+
2511+
assert not i.is_monotonic_decreasing
2512+
assert not i._is_strictly_monotonic_decreasing
2513+
2514+
# empty
2515+
i = MultiIndex.from_arrays([[], []])
2516+
assert i.is_monotonic_decreasing
2517+
assert Index(i.values).is_monotonic_decreasing
2518+
assert i._is_strictly_monotonic_decreasing
2519+
assert Index(i.values)._is_strictly_monotonic_decreasing
2520+
2521+
def test_is_strictly_monotonic_increasing(self):
24462522
idx = pd.MultiIndex(levels=[['bar', 'baz'], ['mom', 'next']],
24472523
labels=[[0, 0, 1, 1], [0, 0, 0, 1]])
24482524
assert idx.is_monotonic_increasing
24492525
assert not idx._is_strictly_monotonic_increasing
24502526

2451-
@pytest.mark.xfail(reason="buggy MultiIndex.is_monotonic_decresaing.")
2452-
def test__is_strictly_monotonic_decreasing(self):
2527+
def test_is_strictly_monotonic_decreasing(self):
24532528
idx = pd.MultiIndex(levels=[['baz', 'bar'], ['next', 'mom']],
24542529
labels=[[0, 0, 1, 1], [0, 0, 0, 1]])
24552530
assert idx.is_monotonic_decreasing

0 commit comments

Comments
 (0)