Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strictly monotonic #16555

Merged
merged 2 commits into from
Jun 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1286,6 +1286,8 @@ Attributes
Index.is_monotonic
Index.is_monotonic_increasing
Index.is_monotonic_decreasing
Index.is_strictly_monotonic_increasing
Index.is_strictly_monotonic_decreasing
Index.is_unique
Index.has_duplicates
Index.dtype
Expand Down
3 changes: 2 additions & 1 deletion doc/source/whatsnew/v0.20.2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Enhancements

- Unblocked access to additional compression types supported in pytables: 'blosc:blosclz, 'blosc:lz4', 'blosc:lz4hc', 'blosc:snappy', 'blosc:zlib', 'blosc:zstd' (:issue:`14478`)
- ``Series`` provides a ``to_latex`` method (:issue:`16180`)
- Added :attr:`Index.is_strictly_monotonic_increasing` and :attr:`Index.is_strictly_monotonic_decreasing` properties (:issue:`16515`)

.. _whatsnew_0202.performance:

Expand Down Expand Up @@ -62,7 +63,7 @@ Indexing
^^^^^^^^

- Bug in ``DataFrame.reset_index(level=)`` with single level index (:issue:`16263`)

- Bug in partial string indexing with a monotonic, but not strictly-monotonic, index incorrectly reversing the slice bounds (:issue:`16515`)

I/O
^^^
Expand Down
50 changes: 50 additions & 0 deletions pandas/core/indexes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1191,6 +1191,15 @@ def is_monotonic_increasing(self):
"""
return if the index is monotonic increasing (only equal or
increasing) values.

Examples
--------
>>> Index([1, 2, 3]).is_monotonic_increasing
True
>>> Index([1, 2, 2]).is_monotonic_increasing
True
>>> Index([1, 3, 2]).is_monotonic_increasing
False
"""
return self._engine.is_monotonic_increasing

Expand All @@ -1199,9 +1208,50 @@ def is_monotonic_decreasing(self):
"""
return if the index is monotonic decreasing (only equal or
decreasing) values.

Examples
--------
>>> Index([3, 2, 1]).is_monotonic_decreasing
True
>>> Index([3, 2, 2]).is_monotonic_decreasing
True
>>> Index([3, 1, 2]).is_monotonic_decreasing
False
"""
return self._engine.is_monotonic_decreasing

@property
def is_strictly_monotonic_increasing(self):
"""return if the index is strictly monotonic increasing
(only increasing) values

Examples
--------
>>> Index([1, 2, 3]).is_strictly_monotonic_increasing
True
>>> Index([1, 2, 2]).is_strictly_monotonic_increasing
False
>>> Index([1, 3, 2]).is_strictly_monotonic_increasing
False
"""
return self.is_unique and self.is_monotonic_increasing

@property
def is_strictly_monotonic_decreasing(self):
"""return if the index is strictly monotonic decreasing
(only decreasing) values

Examples
--------
>>> Index([3, 2, 1]).is_strictly_monotonic_decreasing
True
>>> Index([3, 2, 2]).is_strictly_monotonic_decreasing
False
>>> Index([3, 1, 2]).is_strictly_monotonic_decreasing
False
"""
return self.is_unique and self.is_monotonic_decreasing

def is_lexsorted_for_tuple(self, tup):
return True

Expand Down
2 changes: 1 addition & 1 deletion pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1472,7 +1472,7 @@ def _maybe_cast_slice_bound(self, label, side, kind):
# the bounds need swapped if index is reverse sorted and has a
# length > 1 (is_monotonic_decreasing gives True for empty
# and length 1 index)
if self.is_monotonic_decreasing and len(self) > 1:
if self.is_strictly_monotonic_decreasing and len(self) > 1:
return upper if side == 'left' else lower
return lower if side == 'left' else upper
else:
Expand Down
7 changes: 7 additions & 0 deletions pandas/tests/indexes/datetimes/test_datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -771,3 +771,10 @@ def test_slice_bounds_empty(self):
left = empty_idx._maybe_cast_slice_bound('2015-01-02', 'left', 'loc')
exp = Timestamp('2015-01-02 00:00:00')
assert left == exp

def test_slice_duplicate_monotonic(self):
# https://github.com/pandas-dev/pandas/issues/16515
idx = pd.DatetimeIndex(['2017', '2017'])
result = idx._maybe_cast_slice_bound('2017-01-01', 'left', 'loc')
expected = Timestamp('2017-01-01')
assert result == expected
6 changes: 5 additions & 1 deletion pandas/tests/indexes/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1328,8 +1328,10 @@ def test_tuple_union_bug(self):

def test_is_monotonic_incomparable(self):
index = Index([5, datetime.now(), 7])
assert not index.is_monotonic
assert not index.is_monotonic_increasing
assert not index.is_monotonic_decreasing
assert not index.is_strictly_monotonic_increasing
assert not index.is_strictly_monotonic_decreasing

def test_get_set_value(self):
values = np.random.randn(100)
Expand Down Expand Up @@ -2028,6 +2030,8 @@ def test_is_monotonic_na(self):
for index in examples:
assert not index.is_monotonic_increasing
assert not index.is_monotonic_decreasing
assert not index.is_strictly_monotonic_increasing
assert not index.is_strictly_monotonic_decreasing

def test_repr_summary(self):
with cf.option_context('display.max_seq_items', 10):
Expand Down
26 changes: 26 additions & 0 deletions pandas/tests/indexes/test_multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -2373,22 +2373,30 @@ def test_is_monotonic(self):
i = MultiIndex.from_product([np.arange(10),
np.arange(10)], names=['one', 'two'])
assert i.is_monotonic
assert i.is_strictly_monotonic_increasing
assert Index(i.values).is_monotonic
assert i.is_strictly_monotonic_increasing

i = MultiIndex.from_product([np.arange(10, 0, -1),
np.arange(10)], names=['one', 'two'])
assert not i.is_monotonic
assert not i.is_strictly_monotonic_increasing
assert not Index(i.values).is_monotonic
assert not Index(i.values).is_strictly_monotonic_increasing

i = MultiIndex.from_product([np.arange(10),
np.arange(10, 0, -1)],
names=['one', 'two'])
assert not i.is_monotonic
assert not i.is_strictly_monotonic_increasing
assert not Index(i.values).is_monotonic
assert not Index(i.values).is_strictly_monotonic_increasing

i = MultiIndex.from_product([[1.0, np.nan, 2.0], ['a', 'b', 'c']])
assert not i.is_monotonic
assert not i.is_strictly_monotonic_increasing
assert not Index(i.values).is_monotonic
assert not Index(i.values).is_strictly_monotonic_increasing

# string ordering
i = MultiIndex(levels=[['foo', 'bar', 'baz', 'qux'],
Expand All @@ -2398,6 +2406,8 @@ def test_is_monotonic(self):
names=['first', 'second'])
assert not i.is_monotonic
assert not Index(i.values).is_monotonic
assert not i.is_strictly_monotonic_increasing
assert not Index(i.values).is_strictly_monotonic_increasing

i = MultiIndex(levels=[['bar', 'baz', 'foo', 'qux'],
['mom', 'next', 'zenith']],
Expand All @@ -2406,6 +2416,8 @@ def test_is_monotonic(self):
names=['first', 'second'])
assert i.is_monotonic
assert Index(i.values).is_monotonic
assert i.is_strictly_monotonic_increasing
assert Index(i.values).is_strictly_monotonic_increasing

# mixed levels, hits the TypeError
i = MultiIndex(
Expand All @@ -2416,6 +2428,20 @@ def test_is_monotonic(self):
names=['household_id', 'asset_id'])

assert not i.is_monotonic
assert not i.is_strictly_monotonic_increasing

def test_is_strictly_monotonic(self):
idx = pd.MultiIndex(levels=[['bar', 'baz'], ['mom', 'next']],
labels=[[0, 0, 1, 1], [0, 0, 0, 1]])
assert idx.is_monotonic_increasing
assert not idx.is_strictly_monotonic_increasing

@pytest.mark.xfail(reason="buggy MultiIndex.is_monotonic_decresaing.")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

def test_is_strictly_monotonic_decreasing(self):
idx = pd.MultiIndex(levels=[['baz', 'bar'], ['next', 'mom']],
labels=[[0, 0, 1, 1], [0, 0, 0, 1]])
assert idx.is_monotonic_decreasing
assert not idx.is_strictly_monotonic_decreasing

def test_reconstruct_sort(self):

Expand Down
22 changes: 21 additions & 1 deletion pandas/tests/indexes/test_numeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,16 +465,36 @@ def test_view(self):
def test_is_monotonic(self):
assert self.index.is_monotonic
assert self.index.is_monotonic_increasing
assert self.index.is_strictly_monotonic_increasing
assert not self.index.is_monotonic_decreasing
assert not self.index.is_strictly_monotonic_decreasing

index = self._holder([4, 3, 2, 1])
assert not index.is_monotonic
assert index.is_monotonic_decreasing
assert not index.is_strictly_monotonic_increasing
assert index.is_strictly_monotonic_decreasing

index = self._holder([1])
assert index.is_monotonic
assert index.is_monotonic_increasing
assert index.is_monotonic_decreasing
assert index.is_strictly_monotonic_increasing
assert index.is_strictly_monotonic_decreasing

def test_is_strictly_monotonic(self):
index = self._holder([1, 1, 2, 3])
assert index.is_monotonic_increasing
assert not index.is_strictly_monotonic_increasing

index = self._holder([3, 2, 1, 1])
assert index.is_monotonic_decreasing
assert not index.is_strictly_monotonic_decreasing

index = self._holder([1, 1])
assert index.is_monotonic_increasing
assert index.is_monotonic_decreasing
assert not index.is_strictly_monotonic_increasing
assert not index.is_strictly_monotonic_decreasing

def test_logical_compat(self):
idx = self.create_index()
Expand Down
10 changes: 10 additions & 0 deletions pandas/tests/indexes/test_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,25 +331,35 @@ def test_is_monotonic(self):
assert self.index.is_monotonic
assert self.index.is_monotonic_increasing
assert not self.index.is_monotonic_decreasing
assert self.index.is_strictly_monotonic_increasing
assert not self.index.is_strictly_monotonic_decreasing

index = RangeIndex(4, 0, -1)
assert not index.is_monotonic
assert not index.is_strictly_monotonic_increasing
assert index.is_monotonic_decreasing
assert index.is_strictly_monotonic_decreasing

index = RangeIndex(1, 2)
assert index.is_monotonic
assert index.is_monotonic_increasing
assert index.is_monotonic_decreasing
assert index.is_strictly_monotonic_increasing
assert index.is_strictly_monotonic_decreasing

index = RangeIndex(2, 1)
assert index.is_monotonic
assert index.is_monotonic_increasing
assert index.is_monotonic_decreasing
assert index.is_strictly_monotonic_increasing
assert index.is_strictly_monotonic_decreasing

index = RangeIndex(1, 1)
assert index.is_monotonic
assert index.is_monotonic_increasing
assert index.is_monotonic_decreasing
assert index.is_strictly_monotonic_increasing
assert index.is_strictly_monotonic_decreasing

def test_equals_range(self):
equiv_pairs = [(RangeIndex(0, 9, 2), RangeIndex(0, 10, 2)),
Expand Down