Skip to content

Commit c299b45

Browse files
committed
fixes
1 parent 876c0a9 commit c299b45

File tree

5 files changed

+91
-27
lines changed

5 files changed

+91
-27
lines changed

pandas/index.pyx

+2-1
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,8 @@ cdef inline bint _is_utc(object tz):
556556
cdef class MultiIndexEngine(IndexEngine):
557557

558558
def _call_monotonic(self, object mi):
559-
return mi.is_lexsorted(), mi.is_monotonic, mi.is_unique
559+
# defer these back to the mi iteself
560+
return mi.is_monotonic_increasing, mi.is_monotonic_decreasing, mi.is_unique
560561

561562
def get_backfill_indexer(self, other, limit=None):
562563
# we coerce to ndarray-of-tuples

pandas/indexes/multi.py

+30-15
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,18 @@ def _has_complex_internals(self):
666666

667667
@cache_readonly
668668
def is_monotonic(self):
669+
"""
670+
return if the index is monotonic increasing (only equal or
671+
increasing) values.
672+
"""
673+
return self.is_monotonic_increasing
674+
675+
@cache_readonly
676+
def is_monotonic_increasing(self):
677+
"""
678+
return if the index is monotonic increasing (only equal or
679+
increasing) values.
680+
"""
669681

670682
# reversed() because lexsort() wants the most significant key last.
671683
values = [self._get_level_values(i)
@@ -678,6 +690,14 @@ def is_monotonic(self):
678690
# we have mixed types and np.lexsort is not happy
679691
return Index(self.values).is_monotonic
680692

693+
@property
694+
def is_monotonic_decreasing(self):
695+
"""
696+
return if the index is monotonic decreasing (only equal or
697+
decreasing) values.
698+
"""
699+
return False
700+
681701
@cache_readonly
682702
def is_unique(self):
683703
return not self.duplicated().any()
@@ -1922,15 +1942,8 @@ def partial_selection(key, indexer=None):
19221942

19231943
key = tuple(self[indexer].tolist()[0])
19241944

1925-
try:
1926-
return (self._engine.get_loc(
1927-
_values_from_object(key)), None)
1928-
except ValueError:
1929-
# if we hae a very odd MultiIndex,
1930-
# e.g. with embedded tuples, this might fail
1931-
# TODO: should prob not allow construction of a MI
1932-
# like this in the first place
1933-
return Index(self.values).get_loc(key)
1945+
return (self._engine.get_loc(
1946+
_values_from_object(key)), None)
19341947

19351948
else:
19361949
return partial_selection(key)
@@ -2203,14 +2216,16 @@ def equals(self, other):
22032216
if self.is_(other):
22042217
return True
22052218

2206-
if not isinstance(other, Index):
2207-
if not isinstance(other, tuple):
2219+
if not isinstance(other, MultiIndex):
2220+
if isinstance(other, tuple):
2221+
other = [other]
2222+
if not is_list_like(other):
22082223
return False
2209-
other = Index([other])
22102224

2211-
if not isinstance(other, MultiIndex):
2212-
return array_equivalent(self._values,
2213-
_values_from_object(_ensure_index(other)))
2225+
try:
2226+
other = MultiIndex.from_tuples(other)
2227+
except:
2228+
return False
22142229

22152230
if self.nlevels != other.nlevels:
22162231
return False

pandas/src/hashtable_class_helper.pxi.in

+5-1
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,11 @@ cdef class MultiIndexHashTable(HashTable):
881881
khiter_t k
882882
uint64_t value
883883

884-
value = self.mi._hashed_indexing_key(key)
884+
try:
885+
value = self.mi._hashed_indexing_key(key)
886+
except TypeError:
887+
return False
888+
885889
k = kh_get_uint64(self.table, value)
886890
if k != self.table.n_buckets:
887891
loc = self.table.vals[k]

pandas/tests/frame/test_mutate_columns.py

+25-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# -*- coding: utf-8 -*-
22

33
from __future__ import print_function
4-
4+
import pytest
55
from pandas.compat import range, lrange
66
import numpy as np
77

8-
from pandas import DataFrame, Series, Index
8+
from pandas import DataFrame, Series, Index, MultiIndex
99

1010
from pandas.util.testing import (assert_series_equal,
1111
assert_frame_equal,
@@ -165,6 +165,29 @@ def test_delitem(self):
165165
del self.frame['A']
166166
self.assertNotIn('A', self.frame)
167167

168+
def test_delitem_multiindex(self):
169+
midx = MultiIndex.from_product([['A', 'B'], [1, 2]])
170+
df = DataFrame(np.random.randn(4, 4), columns=midx)
171+
assert len(df.columns) == 4
172+
assert ('A', ) in df.columns
173+
assert 'A' in df.columns
174+
175+
result = df['A']
176+
assert isinstance(result, DataFrame)
177+
del df['A']
178+
179+
assert len(df.columns) == 2
180+
181+
# A still in the levels, BUT get a KeyError if trying
182+
# to delete
183+
assert ('A', ) not in df.columns
184+
with pytest.raises(KeyError):
185+
del df[('A',)]
186+
187+
assert 'A' in df.columns
188+
with pytest.raises(KeyError):
189+
del df['A']
190+
168191
def test_pop(self):
169192
self.frame.columns.name = 'baz'
170193

pandas/tests/indexes/test_multi.py

+29-8
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,11 @@ def test_contains(self):
10461046
self.assertNotIn(('bar', 'two'), self.index)
10471047
self.assertNotIn(None, self.index)
10481048

1049+
def test_contains_top_level(self):
1050+
midx = MultiIndex.from_product([['A', 'B'], [1, 2]])
1051+
assert 'A' in midx
1052+
assert 'A' not in midx._engine
1053+
10491054
def test_contains_with_nat(self):
10501055
# MI with a NaT
10511056
mi = MultiIndex(levels=[['C'],
@@ -1442,20 +1447,36 @@ def test_to_hierarchical(self):
14421447
def test_bounds(self):
14431448
self.index._bounds
14441449

1450+
def test_equals(self):
1451+
1452+
for name, idx in compat.iteritems(self.indices):
1453+
self.assertTrue(idx.equals(idx))
1454+
self.assertTrue(idx.equals(idx.copy()))
1455+
self.assertTrue(idx.equals(idx.astype(object)))
1456+
self.assertTrue(idx.equals(list(idx)))
1457+
self.assertTrue(idx.equals(np.array(idx)))
1458+
1459+
same_values = Index(idx, dtype=object)
1460+
self.assertTrue(idx.equals(same_values))
1461+
self.assertTrue(same_values.equals(idx))
1462+
14451463
def test_equals_multi(self):
1446-
self.assertTrue(self.index.equals(self.index))
1447-
self.assertTrue(self.index.equal_levels(self.index))
1464+
assert self.index.equals(self.index)
1465+
assert self.index.equals(self.index.values)
1466+
assert self.index.equals(Index(self.index.values))
14481467

1449-
self.assertFalse(self.index.equals(self.index[:-1]))
1468+
assert self.index.equal_levels(self.index)
1469+
assert not self.index.equals(self.index[:-1])
1470+
assert not self.index.equals(self.index[-1])
14501471

14511472
# different number of levels
14521473
index = MultiIndex(levels=[Index(lrange(4)), Index(lrange(4)), Index(
14531474
lrange(4))], labels=[np.array([0, 0, 1, 2, 2, 2, 3, 3]), np.array(
14541475
[0, 1, 0, 0, 0, 1, 0, 1]), np.array([1, 0, 1, 1, 0, 0, 1, 0])])
14551476

14561477
index2 = MultiIndex(levels=index.levels[:-1], labels=index.labels[:-1])
1457-
self.assertFalse(index.equals(index2))
1458-
self.assertFalse(index.equal_levels(index2))
1478+
assert not index.equals(index2)
1479+
assert not index.equal_levels(index2)
14591480

14601481
# levels are different
14611482
major_axis = Index(lrange(4))
@@ -1466,8 +1487,8 @@ def test_equals_multi(self):
14661487

14671488
index = MultiIndex(levels=[major_axis, minor_axis],
14681489
labels=[major_labels, minor_labels])
1469-
self.assertFalse(self.index.equals(index))
1470-
self.assertFalse(self.index.equal_levels(index))
1490+
assert not self.index.equals(index)
1491+
assert not self.index.equal_levels(index)
14711492

14721493
# some of the labels are different
14731494
major_axis = Index(['foo', 'bar', 'baz', 'qux'])
@@ -1478,7 +1499,7 @@ def test_equals_multi(self):
14781499

14791500
index = MultiIndex(levels=[major_axis, minor_axis],
14801501
labels=[major_labels, minor_labels])
1481-
self.assertFalse(self.index.equals(index))
1502+
assert not self.index.equals(index)
14821503

14831504
def test_equals_missing_values(self):
14841505
# make sure take is not using -1

0 commit comments

Comments
 (0)