# pylint: disable=E1101,E1103,W0232

from datetime import datetime, timedelta
from pandas.compat import range, lrange, lzip, u, zip
import operator
import re
import nose
import warnings
import os

import numpy as np
from numpy.testing import assert_array_equal

from pandas import period_range, date_range

from pandas.core.index import (Index, Float64Index, Int64Index, MultiIndex,
                               InvalidIndexError, NumericIndex)
from pandas.tseries.index import DatetimeIndex
from pandas.tseries.tdi import TimedeltaIndex
from pandas.tseries.period import PeriodIndex
from pandas.core.series import Series
from pandas.util.testing import (assert_almost_equal, assertRaisesRegexp,
                                 assert_copy)
from pandas import compat
from pandas.compat import long

import pandas.util.testing as tm
import pandas.core.config as cf

from pandas.tseries.index import _to_m8
import pandas.tseries.offsets as offsets

import pandas as pd
from pandas.lib import Timestamp


class Base(object):
    """ base class for index sub-class tests """
    _holder = None
    _compat_props = ['shape', 'ndim', 'size', 'itemsize', 'nbytes']

    def verify_pickle(self,index):
        unpickled = self.round_trip_pickle(index)
        self.assertTrue(index.equals(unpickled))

    def test_pickle_compat_construction(self):
        # this is testing for pickle compat
        if self._holder is None:
            return

        # need an object to create with
        self.assertRaises(TypeError, self._holder)

    def test_numeric_compat(self):

        idx = self.create_index()
        tm.assertRaisesRegexp(TypeError,
                              "cannot perform __mul__",
                              lambda : idx * 1)
        tm.assertRaisesRegexp(TypeError,
                              "cannot perform __mul__",
                              lambda : 1 * idx)

        div_err = "cannot perform __truediv__" if compat.PY3 else "cannot perform __div__"
        tm.assertRaisesRegexp(TypeError,
                              div_err,
                              lambda : idx / 1)
        tm.assertRaisesRegexp(TypeError,
                              div_err,
                              lambda : 1 / idx)
        tm.assertRaisesRegexp(TypeError,
                              "cannot perform __floordiv__",
                              lambda : idx // 1)
        tm.assertRaisesRegexp(TypeError,
                              "cannot perform __floordiv__",
                              lambda : 1 // idx)

    def test_logical_compat(self):
        idx = self.create_index()
        tm.assertRaisesRegexp(TypeError,
                              'cannot perform all',
                              lambda : idx.all())
        tm.assertRaisesRegexp(TypeError,
                              'cannot perform any',
                              lambda : idx.any())

    def test_boolean_context_compat(self):

        # boolean context compat
        idx = self.create_index()
        def f():
            if idx:
                pass
        tm.assertRaisesRegexp(ValueError,'The truth value of a',f)

    def test_ndarray_compat_properties(self):

        idx = self.create_index()
        self.assertTrue(idx.T.equals(idx))
        self.assertTrue(idx.transpose().equals(idx))

        values = idx.values
        for prop in self._compat_props:
            self.assertEqual(getattr(idx, prop), getattr(values, prop))

        # test for validity
        idx.nbytes
        idx.values.nbytes

class TestIndex(Base, tm.TestCase):
    _holder = Index
    _multiprocess_can_split_ = True

    def setUp(self):
        self.indices = dict(
            unicodeIndex = tm.makeUnicodeIndex(100),
            strIndex = tm.makeStringIndex(100),
            dateIndex = tm.makeDateIndex(100),
            intIndex = tm.makeIntIndex(100),
            floatIndex = tm.makeFloatIndex(100),
            boolIndex = Index([True,False]),
            empty = Index([]),
            tuples = MultiIndex.from_tuples(lzip(['foo', 'bar', 'baz'],
                                                 [1, 2, 3]))
        )
        for name, ind in self.indices.items():
            setattr(self, name, ind)

    def create_index(self):
        return Index(list('abcde'))

    def test_wrong_number_names(self):
        def testit(ind):
            ind.names = ["apple", "banana", "carrot"]

        for ind in self.indices.values():
            assertRaisesRegexp(ValueError, "^Length", testit, ind)

    def test_set_name_methods(self):
        new_name = "This is the new name for this index"
        indices = (self.dateIndex, self.intIndex, self.unicodeIndex,
                   self.empty)
        for ind in indices:
            original_name = ind.name
            new_ind = ind.set_names([new_name])
            self.assertEqual(new_ind.name, new_name)
            self.assertEqual(ind.name, original_name)
            res = ind.rename(new_name, inplace=True)
            # should return None
            self.assertIsNone(res)
            self.assertEqual(ind.name, new_name)
            self.assertEqual(ind.names, [new_name])
            #with assertRaisesRegexp(TypeError, "list-like"):
            #    # should still fail even if it would be the right length
            #    ind.set_names("a")
            with assertRaisesRegexp(ValueError, "Level must be None"):
                ind.set_names("a", level=0)
        # rename in place just leaves tuples and other containers alone
        name = ('A', 'B')
        ind = self.intIndex
        ind.rename(name, inplace=True)
        self.assertEqual(ind.name, name)
        self.assertEqual(ind.names, [name])

    def test_hash_error(self):
        with tm.assertRaisesRegexp(TypeError,
                                   "unhashable type: %r" %
                                   type(self.strIndex).__name__):
            hash(self.strIndex)

    def test_new_axis(self):
        new_index = self.dateIndex[None, :]
        self.assertEqual(new_index.ndim, 2)
        tm.assert_isinstance(new_index, np.ndarray)

    def test_copy_and_deepcopy(self):
        from copy import copy, deepcopy

        for func in (copy, deepcopy):
            idx_copy = func(self.strIndex)
            self.assertIsNot(idx_copy, self.strIndex)
            self.assertTrue(idx_copy.equals(self.strIndex))

        new_copy = self.strIndex.copy(deep=True, name="banana")
        self.assertEqual(new_copy.name, "banana")
        new_copy2 = self.intIndex.copy(dtype=int)
        self.assertEqual(new_copy2.dtype.kind, 'i')

    def test_duplicates(self):
        idx = Index([0, 0, 0])
        self.assertFalse(idx.is_unique)

    def test_sort(self):
        self.assertRaises(TypeError, self.strIndex.sort)

    def test_mutability(self):
        self.assertRaises(TypeError, self.strIndex.__setitem__, 0, 'foo')

    def test_constructor(self):
        # regular instance creation
        tm.assert_contains_all(self.strIndex, self.strIndex)
        tm.assert_contains_all(self.dateIndex, self.dateIndex)

        # casting
        arr = np.array(self.strIndex)
        index = Index(arr)
        tm.assert_contains_all(arr, index)
        self.assert_numpy_array_equal(self.strIndex, index)

        # copy
        arr = np.array(self.strIndex)
        index = Index(arr, copy=True, name='name')
        tm.assert_isinstance(index, Index)
        self.assertEqual(index.name, 'name')
        assert_array_equal(arr, index)
        arr[0] = "SOMEBIGLONGSTRING"
        self.assertNotEqual(index[0], "SOMEBIGLONGSTRING")

        # what to do here?
        # arr = np.array(5.)
        # self.assertRaises(Exception, arr.view, Index)

    def test_constructor_corner(self):
        # corner case
        self.assertRaises(TypeError, Index, 0)

    def test_constructor_from_series(self):

        expected = DatetimeIndex([Timestamp('20110101'),Timestamp('20120101'),Timestamp('20130101')])
        s = Series([Timestamp('20110101'),Timestamp('20120101'),Timestamp('20130101')])
        result = Index(s)
        self.assertTrue(result.equals(expected))
        result = DatetimeIndex(s)
        self.assertTrue(result.equals(expected))

        # GH 6273
        # create from a series, passing a freq
        s = Series(pd.to_datetime(['1-1-1990', '2-1-1990', '3-1-1990', '4-1-1990', '5-1-1990']))
        result = DatetimeIndex(s, freq='MS')
        expected = DatetimeIndex(['1-1-1990', '2-1-1990', '3-1-1990', '4-1-1990', '5-1-1990'],freq='MS')
        self.assertTrue(result.equals(expected))

        df = pd.DataFrame(np.random.rand(5,3))
        df['date'] = ['1-1-1990', '2-1-1990', '3-1-1990', '4-1-1990', '5-1-1990']
        result = DatetimeIndex(df['date'], freq='MS')

        # GH 6274
        # infer freq of same
        result = pd.infer_freq(df['date'])
        self.assertEqual(result,'MS')

    def test_constructor_ndarray_like(self):
        # GH 5460#issuecomment-44474502
        # it should be possible to convert any object that satisfies the numpy
        # ndarray interface directly into an Index
        class ArrayLike(object):
            def __init__(self, array):
                self.array = array
            def __array__(self, dtype=None):
                return self.array

        for array in [np.arange(5),
                      np.array(['a', 'b', 'c']),
                      date_range('2000-01-01', periods=3).values]:
            expected = pd.Index(array)
            result = pd.Index(ArrayLike(array))
            self.assertTrue(result.equals(expected))

    def test_index_ctor_infer_periodindex(self):
        xp = period_range('2012-1-1', freq='M', periods=3)
        rs = Index(xp)
        assert_array_equal(rs, xp)
        tm.assert_isinstance(rs, PeriodIndex)

    def test_constructor_simple_new(self):
        idx = Index([1, 2, 3, 4, 5], name='int')
        result = idx._simple_new(idx, 'int')
        self.assertTrue(result.equals(idx))

        idx = Index([1.1, np.nan, 2.2, 3.0], name='float')
        result = idx._simple_new(idx, 'float')
        self.assertTrue(result.equals(idx))

        idx = Index(['A', 'B', 'C', np.nan], name='obj')
        result = idx._simple_new(idx, 'obj')
        self.assertTrue(result.equals(idx))

    def test_copy(self):
        i = Index([], name='Foo')
        i_copy = i.copy()
        self.assertEqual(i_copy.name, 'Foo')

    def test_view(self):
        i = Index([], name='Foo')
        i_view = i.view()
        self.assertEqual(i_view.name, 'Foo')

    def test_legacy_pickle_identity(self):

        # GH 8431
        pth = tm.get_data_path()
        s1 = pd.read_pickle(os.path.join(pth,'s1-0.12.0.pickle'))
        s2 = pd.read_pickle(os.path.join(pth,'s2-0.12.0.pickle'))
        self.assertFalse(s1.index.identical(s2.index))
        self.assertFalse(s1.index.equals(s2.index))

    def test_astype(self):
        casted = self.intIndex.astype('i8')

        # it works!
        casted.get_loc(5)

        # pass on name
        self.intIndex.name = 'foobar'
        casted = self.intIndex.astype('i8')
        self.assertEqual(casted.name, 'foobar')

    def test_compat(self):
        self.strIndex.tolist()

    def test_equals(self):
        # same
        self.assertTrue(Index(['a', 'b', 'c']).equals(Index(['a', 'b', 'c'])))

        # different length
        self.assertFalse(Index(['a', 'b', 'c']).equals(Index(['a', 'b'])))

        # same length, different values
        self.assertFalse(Index(['a', 'b', 'c']).equals(Index(['a', 'b', 'd'])))

        # Must also be an Index
        self.assertFalse(Index(['a', 'b', 'c']).equals(['a', 'b', 'c']))

    def test_insert(self):

        # GH 7256
        # validate neg/pos inserts
        result = Index(['b', 'c', 'd'])

        #test 0th element
        self.assertTrue(Index(['a', 'b', 'c', 'd']).equals(
            result.insert(0, 'a')))

        #test Nth element that follows Python list behavior
        self.assertTrue(Index(['b', 'c', 'e', 'd']).equals(
            result.insert(-1, 'e')))

        #test loc +/- neq (0, -1)
        self.assertTrue(result.insert(1, 'z').equals(
            result.insert(-2, 'z')))

        #test empty
        null_index = Index([])
        self.assertTrue(Index(['a']).equals(
            null_index.insert(0, 'a')))

    def test_delete(self):
        idx = Index(['a', 'b', 'c', 'd'], name='idx')

        expected = Index(['b', 'c', 'd'], name='idx')
        result = idx.delete(0)
        self.assertTrue(result.equals(expected))
        self.assertEqual(result.name, expected.name)

        expected = Index(['a', 'b', 'c'], name='idx')
        result = idx.delete(-1)
        self.assertTrue(result.equals(expected))
        self.assertEqual(result.name, expected.name)

        with tm.assertRaises((IndexError, ValueError)):
            # either depeidnig on numpy version
            result = idx.delete(5)

    def test_identical(self):

        # index
        i1 = Index(['a', 'b', 'c'])
        i2 = Index(['a', 'b', 'c'])

        self.assertTrue(i1.identical(i2))

        i1 = i1.rename('foo')
        self.assertTrue(i1.equals(i2))
        self.assertFalse(i1.identical(i2))

        i2 = i2.rename('foo')
        self.assertTrue(i1.identical(i2))

        i3 = Index([('a', 'a'), ('a', 'b'), ('b', 'a')])
        i4 = Index([('a', 'a'), ('a', 'b'), ('b', 'a')], tupleize_cols=False)
        self.assertFalse(i3.identical(i4))

    def test_is_(self):
        ind = Index(range(10))
        self.assertTrue(ind.is_(ind))
        self.assertTrue(ind.is_(ind.view().view().view().view()))
        self.assertFalse(ind.is_(Index(range(10))))
        self.assertFalse(ind.is_(ind.copy()))
        self.assertFalse(ind.is_(ind.copy(deep=False)))
        self.assertFalse(ind.is_(ind[:]))
        self.assertFalse(ind.is_(ind.view(np.ndarray).view(Index)))
        self.assertFalse(ind.is_(np.array(range(10))))

        # quasi-implementation dependent
        self.assertTrue(ind.is_(ind.view()))
        ind2 = ind.view()
        ind2.name = 'bob'
        self.assertTrue(ind.is_(ind2))
        self.assertTrue(ind2.is_(ind))
        # doesn't matter if Indices are *actually* views of underlying data,
        self.assertFalse(ind.is_(Index(ind.values)))
        arr = np.array(range(1, 11))
        ind1 = Index(arr, copy=False)
        ind2 = Index(arr, copy=False)
        self.assertFalse(ind1.is_(ind2))

    def test_asof(self):
        d = self.dateIndex[0]
        self.assertIs(self.dateIndex.asof(d), d)
        self.assertTrue(np.isnan(self.dateIndex.asof(d - timedelta(1))))

        d = self.dateIndex[-1]
        self.assertEqual(self.dateIndex.asof(d + timedelta(1)), d)

        d = self.dateIndex[0].to_datetime()
        tm.assert_isinstance(self.dateIndex.asof(d), Timestamp)

    def test_asof_datetime_partial(self):
        idx = pd.date_range('2010-01-01', periods=2, freq='m')
        expected = Timestamp('2010-01-31')
        result = idx.asof('2010-02')
        self.assertEqual(result, expected)

    def test_nanosecond_index_access(self):
        s = Series([Timestamp('20130101')]).values.view('i8')[0]
        r = DatetimeIndex([s + 50 + i for i in range(100)])
        x = Series(np.random.randn(100), index=r)

        first_value = x.asof(x.index[0])

        # this does not yet work, as parsing strings is done via dateutil
        #self.assertEqual(first_value, x['2013-01-01 00:00:00.000000050+0000'])

        self.assertEqual(first_value, x[Timestamp(np.datetime64('2013-01-01 00:00:00.000000050+0000', 'ns'))])

    def test_argsort(self):
        result = self.strIndex.argsort()
        expected = np.array(self.strIndex).argsort()
        self.assert_numpy_array_equal(result, expected)

    def test_comparators(self):
        index = self.dateIndex
        element = index[len(index) // 2]
        element = _to_m8(element)

        arr = np.array(index)

        def _check(op):
            arr_result = op(arr, element)
            index_result = op(index, element)

            self.assertIsInstance(index_result, np.ndarray)
            self.assert_numpy_array_equal(arr_result, index_result)

        _check(operator.eq)
        _check(operator.ne)
        _check(operator.gt)
        _check(operator.lt)
        _check(operator.ge)
        _check(operator.le)

    def test_booleanindex(self):
        boolIdx = np.repeat(True, len(self.strIndex)).astype(bool)
        boolIdx[5:30:2] = False

        subIndex = self.strIndex[boolIdx]

        for i, val in enumerate(subIndex):
            self.assertEqual(subIndex.get_loc(val), i)

        subIndex = self.strIndex[list(boolIdx)]
        for i, val in enumerate(subIndex):
            self.assertEqual(subIndex.get_loc(val), i)

    def test_fancy(self):
        sl = self.strIndex[[1, 2, 3]]
        for i in sl:
            self.assertEqual(i, sl[sl.get_loc(i)])

    def test_empty_fancy(self):
        empty_farr = np.array([], dtype=np.float_)
        empty_iarr = np.array([], dtype=np.int_)
        empty_barr = np.array([], dtype=np.bool_)

        # pd.DatetimeIndex is excluded, because it overrides getitem and should
        # be tested separately.
        for idx in [self.strIndex, self.intIndex, self.floatIndex]:
            empty_idx = idx.__class__([])
            values = idx.values

            self.assertTrue(idx[[]].identical(empty_idx))
            self.assertTrue(idx[empty_iarr].identical(empty_idx))
            self.assertTrue(idx[empty_barr].identical(empty_idx))

            # np.ndarray only accepts ndarray of int & bool dtypes, so should
            # Index.
            self.assertRaises(IndexError, idx.__getitem__, empty_farr)

    def test_getitem(self):
        arr = np.array(self.dateIndex)
        exp = self.dateIndex[5]
        exp = _to_m8(exp)

        self.assertEqual(exp, arr[5])

    def test_shift(self):
        shifted = self.dateIndex.shift(0, timedelta(1))
        self.assertIs(shifted, self.dateIndex)

        shifted = self.dateIndex.shift(5, timedelta(1))
        self.assert_numpy_array_equal(shifted, self.dateIndex + timedelta(5))

        shifted = self.dateIndex.shift(1, 'B')
        self.assert_numpy_array_equal(shifted, self.dateIndex + offsets.BDay())

        shifted.name = 'shifted'
        self.assertEqual(shifted.name, shifted.shift(1, 'D').name)

    def test_intersection(self):
        first = self.strIndex[:20]
        second = self.strIndex[:10]
        intersect = first.intersection(second)

        self.assertTrue(tm.equalContents(intersect, second))

        # Corner cases
        inter = first.intersection(first)
        self.assertIs(inter, first)

        # non-iterable input
        assertRaisesRegexp(TypeError, "iterable", first.intersection, 0.5)

        idx1 = Index([1, 2, 3, 4, 5], name='idx')
        # if target has the same name, it is preserved
        idx2 = Index([3, 4, 5, 6, 7], name='idx')
        expected2 = Index([3, 4, 5], name='idx')
        result2 = idx1.intersection(idx2)
        self.assertTrue(result2.equals(expected2))
        self.assertEqual(result2.name, expected2.name)

        # if target name is different, it will be reset
        idx3 = Index([3, 4, 5, 6, 7], name='other')
        expected3 = Index([3, 4, 5], name=None)
        result3 = idx1.intersection(idx3)
        self.assertTrue(result3.equals(expected3))
        self.assertEqual(result3.name, expected3.name)

        # non monotonic
        idx1 = Index([5, 3, 2, 4, 1], name='idx')
        idx2 = Index([4, 7, 6, 5, 3], name='idx')
        result2 = idx1.intersection(idx2)
        self.assertTrue(tm.equalContents(result2, expected2))
        self.assertEqual(result2.name, expected2.name)

        idx3 = Index([4, 7, 6, 5, 3], name='other')
        result3 = idx1.intersection(idx3)
        self.assertTrue(tm.equalContents(result3, expected3))
        self.assertEqual(result3.name, expected3.name)

        # non-monotonic non-unique
        idx1 = Index(['A','B','A','C'])
        idx2 = Index(['B','D'])
        expected = Index(['B'], dtype='object')
        result = idx1.intersection(idx2)
        self.assertTrue(result.equals(expected))

    def test_union(self):
        first = self.strIndex[5:20]
        second = self.strIndex[:10]
        everything = self.strIndex[:20]
        union = first.union(second)
        self.assertTrue(tm.equalContents(union, everything))

        # Corner cases
        union = first.union(first)
        self.assertIs(union, first)

        union = first.union([])
        self.assertIs(union, first)

        union = Index([]).union(first)
        self.assertIs(union, first)

        # non-iterable input
        assertRaisesRegexp(TypeError, "iterable", first.union, 0.5)

        # preserve names
        first.name = 'A'
        second.name = 'A'
        union = first.union(second)
        self.assertEqual(union.name, 'A')

        second.name = 'B'
        union = first.union(second)
        self.assertIsNone(union.name)

    def test_add(self):

        # - API change GH 8226
        with tm.assert_produces_warning():
            self.strIndex + self.strIndex

        firstCat = self.strIndex.union(self.dateIndex)
        secondCat = self.strIndex.union(self.strIndex)

        if self.dateIndex.dtype == np.object_:
            appended = np.append(self.strIndex, self.dateIndex)
        else:
            appended = np.append(self.strIndex, self.dateIndex.astype('O'))

        self.assertTrue(tm.equalContents(firstCat, appended))
        self.assertTrue(tm.equalContents(secondCat, self.strIndex))
        tm.assert_contains_all(self.strIndex, firstCat)
        tm.assert_contains_all(self.strIndex, secondCat)
        tm.assert_contains_all(self.dateIndex, firstCat)

    def test_append_multiple(self):
        index = Index(['a', 'b', 'c', 'd', 'e', 'f'])

        foos = [index[:2], index[2:4], index[4:]]
        result = foos[0].append(foos[1:])
        self.assertTrue(result.equals(index))

        # empty
        result = index.append([])
        self.assertTrue(result.equals(index))

    def test_append_empty_preserve_name(self):
        left = Index([], name='foo')
        right = Index([1, 2, 3], name='foo')

        result = left.append(right)
        self.assertEqual(result.name, 'foo')

        left = Index([], name='foo')
        right = Index([1, 2, 3], name='bar')

        result = left.append(right)
        self.assertIsNone(result.name)

    def test_add_string(self):
        # from bug report
        index = Index(['a', 'b', 'c'])
        index2 = index + 'foo'

        self.assertNotIn('a', index2)
        self.assertIn('afoo', index2)

    def test_iadd_string(self):
        index = pd.Index(['a', 'b', 'c'])
        # doesn't fail test unless there is a check before `+=`
        self.assertIn('a', index)

        index += '_x'
        self.assertIn('a_x', index)

    def test_difference(self):

        first = self.strIndex[5:20]
        second = self.strIndex[:10]
        answer = self.strIndex[10:20]
        first.name = 'name'
        # different names
        result = first.difference(second)

        self.assertTrue(tm.equalContents(result, answer))
        self.assertEqual(result.name, None)

        # same names
        second.name = 'name'
        result = first.difference(second)
        self.assertEqual(result.name, 'name')

        # with empty
        result = first.difference([])
        self.assertTrue(tm.equalContents(result, first))
        self.assertEqual(result.name, first.name)

        # with everythin
        result = first.difference(first)
        self.assertEqual(len(result), 0)
        self.assertEqual(result.name, first.name)

        # non-iterable input
        assertRaisesRegexp(TypeError, "iterable", first.diff, 0.5)

    def test_symmetric_diff(self):
        # smoke
        idx1 = Index([1, 2, 3, 4], name='idx1')
        idx2 = Index([2, 3, 4, 5])
        result = idx1.sym_diff(idx2)
        expected = Index([1, 5])
        self.assertTrue(tm.equalContents(result, expected))
        self.assertIsNone(result.name)

        # __xor__ syntax
        expected = idx1 ^ idx2
        self.assertTrue(tm.equalContents(result, expected))
        self.assertIsNone(result.name)

        # multiIndex
        idx1 = MultiIndex.from_tuples(self.tuples)
        idx2 = MultiIndex.from_tuples([('foo', 1), ('bar', 3)])
        result = idx1.sym_diff(idx2)
        expected = MultiIndex.from_tuples([('bar', 2), ('baz', 3), ('bar', 3)])
        self.assertTrue(tm.equalContents(result, expected))

        # nans:
        # GH #6444, sorting of nans. Make sure the number of nans is right
        # and the correct non-nan values are there. punt on sorting.
        idx1 = Index([1, 2, 3, np.nan])
        idx2 = Index([0, 1, np.nan])
        result = idx1.sym_diff(idx2)
        # expected = Index([0.0, np.nan, 2.0, 3.0, np.nan])

        nans = pd.isnull(result)
        self.assertEqual(nans.sum(), 2)
        self.assertEqual((~nans).sum(), 3)
        [self.assertIn(x, result) for x in [0.0, 2.0, 3.0]]

        # other not an Index:
        idx1 = Index([1, 2, 3, 4], name='idx1')
        idx2 = np.array([2, 3, 4, 5])
        expected = Index([1, 5])
        result = idx1.sym_diff(idx2)
        self.assertTrue(tm.equalContents(result, expected))
        self.assertEqual(result.name, 'idx1')

        result = idx1.sym_diff(idx2, result_name='new_name')
        self.assertTrue(tm.equalContents(result, expected))
        self.assertEqual(result.name, 'new_name')

        # other isn't iterable
        with tm.assertRaises(TypeError):
            Index(idx1,dtype='object') - 1

    def test_pickle(self):

        self.verify_pickle(self.strIndex)
        self.strIndex.name = 'foo'
        self.verify_pickle(self.strIndex)
        self.verify_pickle(self.dateIndex)

    def test_is_numeric(self):
        self.assertFalse(self.dateIndex.is_numeric())
        self.assertFalse(self.strIndex.is_numeric())
        self.assertTrue(self.intIndex.is_numeric())
        self.assertTrue(self.floatIndex.is_numeric())

    def test_is_object(self):
        self.assertTrue(self.strIndex.is_object())
        self.assertTrue(self.boolIndex.is_object())
        self.assertFalse(self.intIndex.is_object())
        self.assertFalse(self.dateIndex.is_object())
        self.assertFalse(self.floatIndex.is_object())

    def test_is_all_dates(self):
        self.assertTrue(self.dateIndex.is_all_dates)
        self.assertFalse(self.strIndex.is_all_dates)
        self.assertFalse(self.intIndex.is_all_dates)

    def test_summary(self):
        self._check_method_works(Index.summary)
        # GH3869
        ind = Index(['{other}%s', "~:{range}:0"], name='A')
        result = ind.summary()
        # shouldn't be formatted accidentally.
        self.assertIn('~:{range}:0', result)
        self.assertIn('{other}%s', result)

    def test_format(self):
        self._check_method_works(Index.format)

        index = Index([datetime.now()])
        formatted = index.format()
        expected = [str(index[0])]
        self.assertEqual(formatted, expected)

        # 2845
        index = Index([1, 2.0+3.0j, np.nan])
        formatted = index.format()
        expected = [str(index[0]), str(index[1]), u('NaN')]
        self.assertEqual(formatted, expected)

        # is this really allowed?
        index = Index([1, 2.0+3.0j, None])
        formatted = index.format()
        expected = [str(index[0]), str(index[1]), u('NaN')]
        self.assertEqual(formatted, expected)

        self.strIndex[:0].format()

    def test_format_with_name_time_info(self):
        # bug I fixed 12/20/2011
        inc = timedelta(hours=4)
        dates = Index([dt + inc for dt in self.dateIndex], name='something')

        formatted = dates.format(name=True)
        self.assertEqual(formatted[0], 'something')

    def test_format_datetime_with_time(self):
        t = Index([datetime(2012, 2, 7), datetime(2012, 2, 7, 23)])

        result = t.format()
        expected = ['2012-02-07 00:00:00', '2012-02-07 23:00:00']
        self.assertEqual(len(result), 2)
        self.assertEqual(result, expected)

    def test_format_none(self):
        values = ['a', 'b', 'c', None]

        idx = Index(values)
        idx.format()
        self.assertIsNone(idx[3])

    def test_take(self):
        indexer = [4, 3, 0, 2]
        result = self.dateIndex.take(indexer)
        expected = self.dateIndex[indexer]
        self.assertTrue(result.equals(expected))

    def test_logical_compat(self):
        idx = self.create_index()
        self.assertEqual(idx.all(), idx.values.all())
        self.assertEqual(idx.any(), idx.values.any())

    def _check_method_works(self, method):
        method(self.empty)
        method(self.dateIndex)
        method(self.unicodeIndex)
        method(self.strIndex)
        method(self.intIndex)
        method(self.tuples)

    def test_get_indexer(self):
        idx1 = Index([1, 2, 3, 4, 5])
        idx2 = Index([2, 4, 6])

        r1 = idx1.get_indexer(idx2)
        assert_almost_equal(r1, [1, 3, -1])

        r1 = idx2.get_indexer(idx1, method='pad')
        assert_almost_equal(r1, [-1, 0, 0, 1, 1])

        rffill1 = idx2.get_indexer(idx1, method='ffill')
        assert_almost_equal(r1, rffill1)

        r1 = idx2.get_indexer(idx1, method='backfill')
        assert_almost_equal(r1, [0, 0, 1, 1, 2])

        rbfill1 = idx2.get_indexer(idx1, method='bfill')
        assert_almost_equal(r1, rbfill1)

    def test_slice_locs(self):
        for dtype in [int, float]:
            idx = Index(np.array([0, 1, 2, 5, 6, 7, 9, 10], dtype=dtype))
            n = len(idx)

            self.assertEqual(idx.slice_locs(start=2), (2, n))
            self.assertEqual(idx.slice_locs(start=3), (3, n))
            self.assertEqual(idx.slice_locs(3, 8), (3, 6))
            self.assertEqual(idx.slice_locs(5, 10), (3, n))
            self.assertEqual(idx.slice_locs(5.0, 10.0), (3, n))
            self.assertEqual(idx.slice_locs(4.5, 10.5), (3, 8))
            self.assertEqual(idx.slice_locs(end=8), (0, 6))
            self.assertEqual(idx.slice_locs(end=9), (0, 7))

            idx2 = idx[::-1]
            self.assertEqual(idx2.slice_locs(8, 2), (2, 6))
            self.assertEqual(idx2.slice_locs(8.5, 1.5), (2, 6))
            self.assertEqual(idx2.slice_locs(7, 3), (2, 5))
            self.assertEqual(idx2.slice_locs(10.5, -1), (0, n))

    def test_slice_locs_dup(self):
        idx = Index(['a', 'a', 'b', 'c', 'd', 'd'])
        self.assertEqual(idx.slice_locs('a', 'd'), (0, 6))
        self.assertEqual(idx.slice_locs(end='d'), (0, 6))
        self.assertEqual(idx.slice_locs('a', 'c'), (0, 4))
        self.assertEqual(idx.slice_locs('b', 'd'), (2, 6))

        idx2 = idx[::-1]
        self.assertEqual(idx2.slice_locs('d', 'a'), (0, 6))
        self.assertEqual(idx2.slice_locs(end='a'), (0, 6))
        self.assertEqual(idx2.slice_locs('d', 'b'), (0, 4))
        self.assertEqual(idx2.slice_locs('c', 'a'), (2, 6))

        for dtype in [int, float]:
            idx = Index(np.array([10, 12, 12, 14], dtype=dtype))
            self.assertEqual(idx.slice_locs(12, 12), (1, 3))
            self.assertEqual(idx.slice_locs(11, 13), (1, 3))

            idx2 = idx[::-1]
            self.assertEqual(idx2.slice_locs(12, 12), (1, 3))
            self.assertEqual(idx2.slice_locs(13, 11), (1, 3))

    def test_slice_locs_na(self):
        idx = Index([np.nan, 1, 2])
        self.assertRaises(KeyError, idx.slice_locs, start=1.5)
        self.assertRaises(KeyError, idx.slice_locs, end=1.5)
        self.assertEqual(idx.slice_locs(1), (1, 3))
        self.assertEqual(idx.slice_locs(np.nan), (0, 3))

        idx = Index([0, np.nan, np.nan, 1, 2])
        self.assertEqual(idx.slice_locs(np.nan), (1, 5))

    def test_slice_locs_negative_step(self):
        idx = Index(list('bcdxy'))

        SLC = pd.IndexSlice

        def check_slice(in_slice, expected):
            s_start, s_stop = idx.slice_locs(in_slice.start, in_slice.stop,
                                             in_slice.step)
            result = idx[s_start:s_stop:in_slice.step]
            expected = pd.Index(list(expected))
            self.assertTrue(result.equals(expected))

        for in_slice, expected in [
                (SLC[::-1], 'yxdcb'), (SLC['b':'y':-1], ''),
                (SLC['b'::-1], 'b'), (SLC[:'b':-1], 'yxdcb'),
                (SLC[:'y':-1], 'y'), (SLC['y'::-1], 'yxdcb'),
                (SLC['y'::-4], 'yb'),
                # absent labels
                (SLC[:'a':-1], 'yxdcb'), (SLC[:'a':-2], 'ydb'),
                (SLC['z'::-1], 'yxdcb'), (SLC['z'::-3], 'yc'),
                (SLC['m'::-1], 'dcb'), (SLC[:'m':-1], 'yx'),
                (SLC['a':'a':-1], ''), (SLC['z':'z':-1], ''),
                (SLC['m':'m':-1], '')
        ]:
            check_slice(in_slice, expected)

    def test_drop(self):
        n = len(self.strIndex)

        dropped = self.strIndex.drop(self.strIndex[lrange(5, 10)])
        expected = self.strIndex[lrange(5) + lrange(10, n)]
        self.assertTrue(dropped.equals(expected))

        self.assertRaises(ValueError, self.strIndex.drop, ['foo', 'bar'])

        dropped = self.strIndex.drop(self.strIndex[0])
        expected = self.strIndex[1:]
        self.assertTrue(dropped.equals(expected))

        ser = Index([1, 2, 3])
        dropped = ser.drop(1)
        expected = Index([2, 3])
        self.assertTrue(dropped.equals(expected))

    def test_tuple_union_bug(self):
        import pandas
        import numpy as np

        aidx1 = np.array([(1, 'A'), (2, 'A'), (1, 'B'), (2, 'B')],
                         dtype=[('num', int), ('let', 'a1')])
        aidx2 = np.array([(1, 'A'), (2, 'A'), (1, 'B'), (2, 'B'), (1, 'C'), (2,
                         'C')], dtype=[('num', int), ('let', 'a1')])

        idx1 = pandas.Index(aidx1)
        idx2 = pandas.Index(aidx2)

        # intersection broken?
        int_idx = idx1.intersection(idx2)
        # needs to be 1d like idx1 and idx2
        expected = idx1[:4]  # pandas.Index(sorted(set(idx1) & set(idx2)))
        self.assertEqual(int_idx.ndim, 1)
        self.assertTrue(int_idx.equals(expected))

        # union broken
        union_idx = idx1.union(idx2)
        expected = idx2
        self.assertEqual(union_idx.ndim, 1)
        self.assertTrue(union_idx.equals(expected))

    def test_is_monotonic_incomparable(self):
        index = Index([5, datetime.now(), 7])
        self.assertFalse(index.is_monotonic)
        self.assertFalse(index.is_monotonic_decreasing)

    def test_get_set_value(self):
        values = np.random.randn(100)
        date = self.dateIndex[67]

        assert_almost_equal(self.dateIndex.get_value(values, date),
                            values[67])

        self.dateIndex.set_value(values, date, 10)
        self.assertEqual(values[67], 10)

    def test_isin(self):
        values = ['foo', 'bar', 'quux']

        idx = Index(['qux', 'baz', 'foo', 'bar'])
        result = idx.isin(values)
        expected = np.array([False, False, True, True])
        self.assert_numpy_array_equal(result, expected)

        # empty, return dtype bool
        idx = Index([])
        result = idx.isin(values)
        self.assertEqual(len(result), 0)
        self.assertEqual(result.dtype, np.bool_)

    def test_isin_nan(self):
        self.assert_numpy_array_equal(
            Index(['a', np.nan]).isin([np.nan]), [False, True])
        self.assert_numpy_array_equal(
            Index(['a', pd.NaT]).isin([pd.NaT]), [False, True])
        self.assert_numpy_array_equal(
            Index(['a', np.nan]).isin([float('nan')]), [False, False])
        self.assert_numpy_array_equal(
            Index(['a', np.nan]).isin([pd.NaT]), [False, False])
        # Float64Index overrides isin, so must be checked separately
        self.assert_numpy_array_equal(
            Float64Index([1.0, np.nan]).isin([np.nan]), [False, True])
        self.assert_numpy_array_equal(
            Float64Index([1.0, np.nan]).isin([float('nan')]), [False, True])
        self.assert_numpy_array_equal(
            Float64Index([1.0, np.nan]).isin([pd.NaT]), [False, True])

    def test_isin_level_kwarg(self):
        def check_idx(idx):
            values = idx.tolist()[-2:] + ['nonexisting']

            expected = np.array([False, False, True, True])
            self.assert_numpy_array_equal(expected, idx.isin(values, level=0))
            self.assert_numpy_array_equal(expected, idx.isin(values, level=-1))

            self.assertRaises(IndexError, idx.isin, values, level=1)
            self.assertRaises(IndexError, idx.isin, values, level=10)
            self.assertRaises(IndexError, idx.isin, values, level=-2)

            self.assertRaises(KeyError, idx.isin, values, level=1.0)
            self.assertRaises(KeyError, idx.isin, values, level='foobar')

            idx.name = 'foobar'
            self.assert_numpy_array_equal(expected,
                                          idx.isin(values, level='foobar'))

            self.assertRaises(KeyError, idx.isin, values, level='xyzzy')
            self.assertRaises(KeyError, idx.isin, values, level=np.nan)

        check_idx(Index(['qux', 'baz', 'foo', 'bar']))
        # Float64Index overrides isin, so must be checked separately
        check_idx(Float64Index([1.0, 2.0, 3.0, 4.0]))

    def test_boolean_cmp(self):
        values = [1, 2, 3, 4]

        idx = Index(values)
        res = (idx == values)

        self.assert_numpy_array_equal(res,np.array([True,True,True,True],dtype=bool))

    def test_get_level_values(self):
        result = self.strIndex.get_level_values(0)
        self.assertTrue(result.equals(self.strIndex))

    def test_slice_keep_name(self):
        idx = Index(['a', 'b'], name='asdf')
        self.assertEqual(idx.name, idx[1:].name)

    def test_join_self(self):
        # instance attributes of the form self.<name>Index
        indices = 'unicode', 'str', 'date', 'int', 'float'
        kinds = 'outer', 'inner', 'left', 'right'
        for index_kind in indices:
            res = getattr(self, '{0}Index'.format(index_kind))

            for kind in kinds:
                joined = res.join(res, how=kind)
                self.assertIs(res, joined)

    def test_indexing_doesnt_change_class(self):
        idx = Index([1, 2, 3, 'a', 'b', 'c'])

        self.assertTrue(idx[1:3].identical(
            pd.Index([2, 3], dtype=np.object_)))
        self.assertTrue(idx[[0,1]].identical(
            pd.Index([1, 2], dtype=np.object_)))

    def test_outer_join_sort(self):
        left_idx = Index(np.random.permutation(15))
        right_idx = tm.makeDateIndex(10)

        with tm.assert_produces_warning(RuntimeWarning):
            joined = left_idx.join(right_idx, how='outer')
        # right_idx in this case because DatetimeIndex has join precedence over
        # Int64Index
        expected = right_idx.astype(object).union(left_idx.astype(object))
        tm.assert_index_equal(joined, expected)

    def test_nan_first_take_datetime(self):
        idx = Index([pd.NaT, Timestamp('20130101'), Timestamp('20130102')])
        res = idx.take([-1, 0, 1])
        exp = Index([idx[-1], idx[0], idx[1]])
        tm.assert_index_equal(res, exp)

    def test_reindex_preserves_name_if_target_is_list_or_ndarray(self):
        # GH6552
        idx = pd.Index([0, 1, 2])

        dt_idx = pd.date_range('20130101', periods=3)

        idx.name = None
        self.assertEqual(idx.reindex([])[0].name, None)
        self.assertEqual(idx.reindex(np.array([]))[0].name, None)
        self.assertEqual(idx.reindex(idx.tolist())[0].name, None)
        self.assertEqual(idx.reindex(idx.tolist()[:-1])[0].name, None)
        self.assertEqual(idx.reindex(idx.values)[0].name, None)
        self.assertEqual(idx.reindex(idx.values[:-1])[0].name, None)

        # Must preserve name even if dtype changes.
        self.assertEqual(idx.reindex(dt_idx.values)[0].name, None)
        self.assertEqual(idx.reindex(dt_idx.tolist())[0].name, None)

        idx.name = 'foobar'
        self.assertEqual(idx.reindex([])[0].name, 'foobar')
        self.assertEqual(idx.reindex(np.array([]))[0].name, 'foobar')
        self.assertEqual(idx.reindex(idx.tolist())[0].name, 'foobar')
        self.assertEqual(idx.reindex(idx.tolist()[:-1])[0].name, 'foobar')
        self.assertEqual(idx.reindex(idx.values)[0].name, 'foobar')
        self.assertEqual(idx.reindex(idx.values[:-1])[0].name, 'foobar')

        # Must preserve name even if dtype changes.
        self.assertEqual(idx.reindex(dt_idx.values)[0].name, 'foobar')
        self.assertEqual(idx.reindex(dt_idx.tolist())[0].name, 'foobar')

    def test_reindex_preserves_type_if_target_is_empty_list_or_array(self):
        # GH7774
        idx = pd.Index(list('abc'))
        def get_reindex_type(target):
            return idx.reindex(target)[0].dtype.type

        self.assertEqual(get_reindex_type([]), np.object_)
        self.assertEqual(get_reindex_type(np.array([])), np.object_)
        self.assertEqual(get_reindex_type(np.array([], dtype=np.int64)),
                         np.object_)

    def test_reindex_doesnt_preserve_type_if_target_is_empty_index(self):
        # GH7774
        idx = pd.Index(list('abc'))
        def get_reindex_type(target):
            return idx.reindex(target)[0].dtype.type

        self.assertEqual(get_reindex_type(pd.Int64Index([])), np.int64)
        self.assertEqual(get_reindex_type(pd.Float64Index([])), np.float64)
        self.assertEqual(get_reindex_type(pd.DatetimeIndex([])), np.datetime64)

        reindexed = idx.reindex(pd.MultiIndex([pd.Int64Index([]),
                                               pd.Float64Index([])],
                                              [[], []]))[0]
        self.assertEqual(reindexed.levels[0].dtype.type, np.int64)
        self.assertEqual(reindexed.levels[1].dtype.type, np.float64)

    def test_groupby(self):
        idx = Index(range(5))
        groups = idx.groupby(np.array([1,1,2,2,2]))
        exp = {1: [0, 1], 2: [2, 3, 4]}
        tm.assert_dict_equal(groups, exp)


class Numeric(Base):

    def test_numeric_compat(self):

        idx = self._holder(np.arange(5,dtype='int64'))
        didx = self._holder(np.arange(5,dtype='int64')**2
                            )
        result = idx * 1
        tm.assert_index_equal(result, idx)

        result = 1 * idx
        tm.assert_index_equal(result, idx)

        result = idx * idx
        tm.assert_index_equal(result, didx)

        result = idx / 1
        tm.assert_index_equal(result, idx)

        result = idx // 1
        tm.assert_index_equal(result, idx)

        result = idx * np.array(5,dtype='int64')
        tm.assert_index_equal(result, self._holder(np.arange(5,dtype='int64')*5))

        result = idx * np.arange(5,dtype='int64')
        tm.assert_index_equal(result, didx)

        result = idx * Series(np.arange(5,dtype='int64'))
        tm.assert_index_equal(result, didx)

        result = idx * Series(np.arange(5,dtype='float64')+0.1)
        tm.assert_index_equal(result,
                              Float64Index(np.arange(5,dtype='float64')*(np.arange(5,dtype='float64')+0.1)))

        # invalid
        self.assertRaises(TypeError, lambda : idx * date_range('20130101',periods=5))
        self.assertRaises(ValueError, lambda : idx * self._holder(np.arange(3)))
        self.assertRaises(ValueError, lambda : idx * np.array([1,2]))


    def test_explicit_conversions(self):

        # GH 8608
        # add/sub are overriden explicity for Float/Int Index
        idx = self._holder(np.arange(5,dtype='int64'))

        # float conversions
        arr = np.arange(5,dtype='int64')*3.2
        expected = Float64Index(arr)
        fidx = idx * 3.2
        tm.assert_index_equal(fidx,expected)
        fidx = 3.2 * idx
        tm.assert_index_equal(fidx,expected)

        # interops with numpy arrays
        expected = Float64Index(arr)
        a = np.zeros(5,dtype='float64')
        result = fidx - a
        tm.assert_index_equal(result,expected)

        expected = Float64Index(-arr)
        a = np.zeros(5,dtype='float64')
        result = a - fidx
        tm.assert_index_equal(result,expected)

    def test_ufunc_compat(self):
        idx = self._holder(np.arange(5,dtype='int64'))
        result = np.sin(idx)
        expected = Float64Index(np.sin(np.arange(5,dtype='int64')))
        tm.assert_index_equal(result, expected)

class TestFloat64Index(Numeric, tm.TestCase):
    _holder = Float64Index
    _multiprocess_can_split_ = True

    def setUp(self):
        self.mixed = Float64Index([1.5, 2, 3, 4, 5])
        self.float = Float64Index(np.arange(5) * 2.5)

    def create_index(self):
        return Float64Index(np.arange(5,dtype='float64'))

    def test_hash_error(self):
        with tm.assertRaisesRegexp(TypeError,
                                   "unhashable type: %r" %
                                   type(self.float).__name__):
            hash(self.float)

    def test_repr_roundtrip(self):
        for ind in (self.mixed, self.float):
            tm.assert_index_equal(eval(repr(ind)), ind)

    def check_is_index(self, i):
        self.assertIsInstance(i, Index)
        self.assertNotIsInstance(i, Float64Index)

    def check_coerce(self, a, b, is_float_index=True):
        self.assertTrue(a.equals(b))
        if is_float_index:
            self.assertIsInstance(b, Float64Index)
        else:
            self.check_is_index(b)

    def test_constructor(self):

        # explicit construction
        index = Float64Index([1,2,3,4,5])
        self.assertIsInstance(index, Float64Index)
        self.assertTrue((index.values == np.array([1,2,3,4,5],dtype='float64')).all())
        index = Float64Index(np.array([1,2,3,4,5]))
        self.assertIsInstance(index, Float64Index)
        index = Float64Index([1.,2,3,4,5])
        self.assertIsInstance(index, Float64Index)
        index = Float64Index(np.array([1.,2,3,4,5]))
        self.assertIsInstance(index, Float64Index)
        self.assertEqual(index.dtype, float)

        index = Float64Index(np.array([1.,2,3,4,5]),dtype=np.float32)
        self.assertIsInstance(index, Float64Index)
        self.assertEqual(index.dtype, np.float64)

        index = Float64Index(np.array([1,2,3,4,5]),dtype=np.float32)
        self.assertIsInstance(index, Float64Index)
        self.assertEqual(index.dtype, np.float64)

        # nan handling
        result = Float64Index([np.nan, np.nan])
        self.assertTrue(pd.isnull(result.values).all())
        result = Float64Index(np.array([np.nan]))
        self.assertTrue(pd.isnull(result.values).all())
        result = Index(np.array([np.nan]))
        self.assertTrue(pd.isnull(result.values).all())

    def test_constructor_invalid(self):

        # invalid
        self.assertRaises(TypeError, Float64Index, 0.)
        self.assertRaises(TypeError, Float64Index, ['a','b',0.])
        self.assertRaises(TypeError, Float64Index, [Timestamp('20130101')])

    def test_constructor_coerce(self):

        self.check_coerce(self.mixed,Index([1.5, 2, 3, 4, 5]))
        self.check_coerce(self.float,Index(np.arange(5) * 2.5))
        self.check_coerce(self.float,Index(np.array(np.arange(5) * 2.5, dtype=object)))

    def test_constructor_explicit(self):

        # these don't auto convert
        self.check_coerce(self.float,Index((np.arange(5) * 2.5), dtype=object),
                          is_float_index=False)
        self.check_coerce(self.mixed,Index([1.5, 2, 3, 4, 5],dtype=object),
                          is_float_index=False)

    def test_astype(self):

        result = self.float.astype(object)
        self.assertTrue(result.equals(self.float))
        self.assertTrue(self.float.equals(result))
        self.check_is_index(result)

        i = self.mixed.copy()
        i.name = 'foo'
        result = i.astype(object)
        self.assertTrue(result.equals(i))
        self.assertTrue(i.equals(result))
        self.check_is_index(result)

    def test_equals(self):

        i = Float64Index([1.0,2.0])
        self.assertTrue(i.equals(i))
        self.assertTrue(i.identical(i))

        i2 = Float64Index([1.0,2.0])
        self.assertTrue(i.equals(i2))

        i = Float64Index([1.0,np.nan])
        self.assertTrue(i.equals(i))
        self.assertTrue(i.identical(i))

        i2 = Float64Index([1.0,np.nan])
        self.assertTrue(i.equals(i2))

    def test_get_loc_na(self):
        idx = Float64Index([np.nan, 1, 2])
        self.assertEqual(idx.get_loc(1), 1)
        self.assertEqual(idx.get_loc(np.nan), 0)

        idx = Float64Index([np.nan, 1, np.nan])
        self.assertEqual(idx.get_loc(1), 1)
        self.assertRaises(KeyError, idx.slice_locs, np.nan)

    def test_contains_nans(self):
        i = Float64Index([1.0, 2.0, np.nan])
        self.assertTrue(np.nan in i)

    def test_contains_not_nans(self):
        i = Float64Index([1.0, 2.0, np.nan])
        self.assertTrue(1.0 in i)

    def test_doesnt_contain_all_the_things(self):
        i = Float64Index([np.nan])
        self.assertFalse(i.isin([0]).item())
        self.assertFalse(i.isin([1]).item())
        self.assertTrue(i.isin([np.nan]).item())

    def test_nan_multiple_containment(self):
        i = Float64Index([1.0, np.nan])
        np.testing.assert_array_equal(i.isin([1.0]), np.array([True, False]))
        np.testing.assert_array_equal(i.isin([2.0, np.pi]),
                                      np.array([False, False]))
        np.testing.assert_array_equal(i.isin([np.nan]),
                                      np.array([False, True]))
        np.testing.assert_array_equal(i.isin([1.0, np.nan]),
                                      np.array([True, True]))
        i = Float64Index([1.0, 2.0])
        np.testing.assert_array_equal(i.isin([np.nan]),
                                      np.array([False, False]))

    def test_astype_from_object(self):
        index = Index([1.0, np.nan, 0.2], dtype='object')
        result = index.astype(float)
        expected = Float64Index([1.0, np.nan, 0.2])
        tm.assert_equal(result.dtype, expected.dtype)
        tm.assert_index_equal(result, expected)


class TestInt64Index(Numeric, tm.TestCase):
    _holder = Int64Index
    _multiprocess_can_split_ = True

    def setUp(self):
        self.index = Int64Index(np.arange(0, 20, 2))

    def create_index(self):
        return Int64Index(np.arange(5,dtype='int64'))

    def test_too_many_names(self):
        def testit():
            self.index.names = ["roger", "harold"]
        assertRaisesRegexp(ValueError, "^Length", testit)

    def test_constructor(self):
        # pass list, coerce fine
        index = Int64Index([-5, 0, 1, 2])
        expected = np.array([-5, 0, 1, 2], dtype=np.int64)
        self.assert_numpy_array_equal(index, expected)

        # from iterable
        index = Int64Index(iter([-5, 0, 1, 2]))
        self.assert_numpy_array_equal(index, expected)

        # scalar raise Exception
        self.assertRaises(TypeError, Int64Index, 5)

        # copy
        arr = self.index.values
        new_index = Int64Index(arr, copy=True)
        self.assert_numpy_array_equal(new_index, self.index)
        val = arr[0] + 3000
        # this should not change index
        arr[0] = val
        self.assertNotEqual(new_index[0], val)

    def test_constructor_corner(self):
        arr = np.array([1, 2, 3, 4], dtype=object)
        index = Int64Index(arr)
        self.assertEqual(index.values.dtype, np.int64)
        self.assertTrue(index.equals(arr))

        # preventing casting
        arr = np.array([1, '2', 3, '4'], dtype=object)
        with tm.assertRaisesRegexp(TypeError, 'casting'):
            Int64Index(arr)

        arr_with_floats = [0, 2, 3, 4, 5, 1.25, 3, -1]
        with tm.assertRaisesRegexp(TypeError, 'casting'):
            Int64Index(arr_with_floats)

    def test_hash_error(self):
        with tm.assertRaisesRegexp(TypeError,
                                   "unhashable type: %r" %
                                   type(self.index).__name__):
            hash(self.index)

    def test_copy(self):
        i = Int64Index([], name='Foo')
        i_copy = i.copy()
        self.assertEqual(i_copy.name, 'Foo')

    def test_view(self):
        i = Int64Index([], name='Foo')
        i_view = i.view()
        self.assertEqual(i_view.name, 'Foo')

    def test_coerce_list(self):
        # coerce things
        arr = Index([1, 2, 3, 4])
        tm.assert_isinstance(arr, Int64Index)

        # but not if explicit dtype passed
        arr = Index([1, 2, 3, 4], dtype=object)
        tm.assert_isinstance(arr, Index)

    def test_dtype(self):
        self.assertEqual(self.index.dtype, np.int64)

    def test_is_monotonic(self):
        self.assertTrue(self.index.is_monotonic)
        self.assertTrue(self.index.is_monotonic_increasing)
        self.assertFalse(self.index.is_monotonic_decreasing)

        index = Int64Index([4, 3, 2, 1])
        self.assertFalse(index.is_monotonic)
        self.assertTrue(index.is_monotonic_decreasing)

        index = Int64Index([1])
        self.assertTrue(index.is_monotonic)
        self.assertTrue(index.is_monotonic_increasing)
        self.assertTrue(index.is_monotonic_decreasing)

    def test_is_monotonic_na(self):
        examples = [Index([np.nan]),
                    Index([np.nan, 1]),
                    Index([1, 2, np.nan]),
                    Index(['a', 'b', np.nan]),
                    pd.to_datetime(['NaT']),
                    pd.to_datetime(['NaT', '2000-01-01']),
                    pd.to_datetime(['2000-01-01', 'NaT', '2000-01-02']),
                    pd.to_timedelta(['1 day', 'NaT']),
                   ]
        for index in examples:
            self.assertFalse(index.is_monotonic_increasing)
            self.assertFalse(index.is_monotonic_decreasing)

    def test_equals(self):
        same_values = Index(self.index, dtype=object)
        self.assertTrue(self.index.equals(same_values))
        self.assertTrue(same_values.equals(self.index))

    def test_logical_compat(self):
        idx = self.create_index()
        self.assertEqual(idx.all(), idx.values.all())
        self.assertEqual(idx.any(), idx.values.any())

    def test_identical(self):
        i = Index(self.index.copy())
        self.assertTrue(i.identical(self.index))

        same_values_different_type = Index(i, dtype=object)
        self.assertFalse(i.identical(same_values_different_type))

        i = self.index.copy(dtype=object)
        i = i.rename('foo')
        same_values = Index(i, dtype=object)
        self.assertTrue(same_values.identical(self.index.copy(dtype=object)))

        self.assertFalse(i.identical(self.index))
        self.assertTrue(Index(same_values, name='foo', dtype=object
                              ).identical(i))

        self.assertFalse(
            self.index.copy(dtype=object)
            .identical(self.index.copy(dtype='int64')))

    def test_get_indexer(self):
        target = Int64Index(np.arange(10))
        indexer = self.index.get_indexer(target)
        expected = np.array([0, -1, 1, -1, 2, -1, 3, -1, 4, -1])
        self.assert_numpy_array_equal(indexer, expected)

    def test_get_indexer_pad(self):
        target = Int64Index(np.arange(10))
        indexer = self.index.get_indexer(target, method='pad')
        expected = np.array([0, 0, 1, 1, 2, 2, 3, 3, 4, 4])
        self.assert_numpy_array_equal(indexer, expected)

    def test_get_indexer_backfill(self):
        target = Int64Index(np.arange(10))
        indexer = self.index.get_indexer(target, method='backfill')
        expected = np.array([0, 1, 1, 2, 2, 3, 3, 4, 4, 5])
        self.assert_numpy_array_equal(indexer, expected)

    def test_join_outer(self):
        other = Int64Index([7, 12, 25, 1, 2, 5])
        other_mono = Int64Index([1, 2, 5, 7, 12, 25])

        # not monotonic
        # guarantee of sortedness
        res, lidx, ridx = self.index.join(other, how='outer',
                                          return_indexers=True)
        noidx_res = self.index.join(other, how='outer')
        self.assertTrue(res.equals(noidx_res))

        eres = Int64Index([0, 1, 2, 4, 5, 6, 7, 8, 10, 12, 14, 16, 18, 25])
        elidx = np.array([0, -1, 1, 2, -1, 3, -1, 4, 5, 6, 7, 8, 9, -1],
                         dtype=np.int64)
        eridx = np.array([-1, 3, 4, -1, 5, -1, 0, -1, -1, 1, -1, -1, -1, 2],
                         dtype=np.int64)

        tm.assert_isinstance(res, Int64Index)
        self.assertTrue(res.equals(eres))
        self.assert_numpy_array_equal(lidx, elidx)
        self.assert_numpy_array_equal(ridx, eridx)

        # monotonic
        res, lidx, ridx = self.index.join(other_mono, how='outer',
                                          return_indexers=True)
        noidx_res = self.index.join(other_mono, how='outer')
        self.assertTrue(res.equals(noidx_res))

        eridx = np.array([-1, 0, 1, -1, 2, -1, 3, -1, -1, 4, -1, -1, -1, 5],
                         dtype=np.int64)
        tm.assert_isinstance(res, Int64Index)
        self.assertTrue(res.equals(eres))
        self.assert_numpy_array_equal(lidx, elidx)
        self.assert_numpy_array_equal(ridx, eridx)

    def test_join_inner(self):
        other = Int64Index([7, 12, 25, 1, 2, 5])
        other_mono = Int64Index([1, 2, 5, 7, 12, 25])

        # not monotonic
        res, lidx, ridx = self.index.join(other, how='inner',
                                          return_indexers=True)

        # no guarantee of sortedness, so sort for comparison purposes
        ind = res.argsort()
        res = res.take(ind)
        lidx = lidx.take(ind)
        ridx = ridx.take(ind)

        eres = Int64Index([2, 12])
        elidx = np.array([1, 6])
        eridx = np.array([4, 1])

        tm.assert_isinstance(res, Int64Index)
        self.assertTrue(res.equals(eres))
        self.assert_numpy_array_equal(lidx, elidx)
        self.assert_numpy_array_equal(ridx, eridx)

        # monotonic
        res, lidx, ridx = self.index.join(other_mono, how='inner',
                                          return_indexers=True)

        res2 = self.index.intersection(other_mono)
        self.assertTrue(res.equals(res2))

        eridx = np.array([1, 4])
        tm.assert_isinstance(res, Int64Index)
        self.assertTrue(res.equals(eres))
        self.assert_numpy_array_equal(lidx, elidx)
        self.assert_numpy_array_equal(ridx, eridx)

    def test_join_left(self):
        other = Int64Index([7, 12, 25, 1, 2, 5])
        other_mono = Int64Index([1, 2, 5, 7, 12, 25])

        # not monotonic
        res, lidx, ridx = self.index.join(other, how='left',
                                          return_indexers=True)
        eres = self.index
        eridx = np.array([-1, 4, -1, -1, -1, -1, 1, -1, -1, -1],
                         dtype=np.int64)

        tm.assert_isinstance(res, Int64Index)
        self.assertTrue(res.equals(eres))
        self.assertIsNone(lidx)
        self.assert_numpy_array_equal(ridx, eridx)

        # monotonic
        res, lidx, ridx = self.index.join(other_mono, how='left',
                                          return_indexers=True)
        eridx = np.array([-1, 1, -1, -1, -1, -1, 4, -1, -1, -1],
                         dtype=np.int64)
        tm.assert_isinstance(res, Int64Index)
        self.assertTrue(res.equals(eres))
        self.assertIsNone(lidx)
        self.assert_numpy_array_equal(ridx, eridx)

        # non-unique
        """
        idx = Index([1,1,2,5])
        idx2 = Index([1,2,5,7,9])
        res, lidx, ridx = idx2.join(idx, how='left', return_indexers=True)
        eres = idx2
        eridx = np.array([0, 2, 3, -1, -1])
        elidx = np.array([0, 1, 2, 3, 4])
        self.assertTrue(res.equals(eres))
        self.assert_numpy_array_equal(lidx, elidx)
        self.assert_numpy_array_equal(ridx, eridx)
        """

    def test_join_right(self):
        other = Int64Index([7, 12, 25, 1, 2, 5])
        other_mono = Int64Index([1, 2, 5, 7, 12, 25])

        # not monotonic
        res, lidx, ridx = self.index.join(other, how='right',
                                          return_indexers=True)
        eres = other
        elidx = np.array([-1, 6, -1, -1, 1, -1],
                         dtype=np.int64)

        tm.assert_isinstance(other, Int64Index)
        self.assertTrue(res.equals(eres))
        self.assert_numpy_array_equal(lidx, elidx)
        self.assertIsNone(ridx)

        # monotonic
        res, lidx, ridx = self.index.join(other_mono, how='right',
                                          return_indexers=True)
        eres = other_mono
        elidx = np.array([-1, 1, -1, -1, 6, -1],
                         dtype=np.int64)
        tm.assert_isinstance(other, Int64Index)
        self.assertTrue(res.equals(eres))
        self.assert_numpy_array_equal(lidx, elidx)
        self.assertIsNone(ridx)

        # non-unique
        """
        idx = Index([1,1,2,5])
        idx2 = Index([1,2,5,7,9])
        res, lidx, ridx = idx.join(idx2, how='right', return_indexers=True)
        eres = idx2
        elidx = np.array([0, 2, 3, -1, -1])
        eridx = np.array([0, 1, 2, 3, 4])
        self.assertTrue(res.equals(eres))
        self.assert_numpy_array_equal(lidx, elidx)
        self.assert_numpy_array_equal(ridx, eridx)

        idx = Index([1,1,2,5])
        idx2 = Index([1,2,5,9,7])
        res = idx.join(idx2, how='right', return_indexers=False)
        eres = idx2
        self.assert(res.equals(eres))
        """

    def test_join_non_int_index(self):
        other = Index([3, 6, 7, 8, 10], dtype=object)

        outer = self.index.join(other, how='outer')
        outer2 = other.join(self.index, how='outer')
        expected = Index([0, 2, 3, 4, 6, 7, 8, 10, 12, 14,
                          16, 18], dtype=object)
        self.assertTrue(outer.equals(outer2))
        self.assertTrue(outer.equals(expected))

        inner = self.index.join(other, how='inner')
        inner2 = other.join(self.index, how='inner')
        expected = Index([6, 8, 10], dtype=object)
        self.assertTrue(inner.equals(inner2))
        self.assertTrue(inner.equals(expected))

        left = self.index.join(other, how='left')
        self.assertTrue(left.equals(self.index))

        left2 = other.join(self.index, how='left')
        self.assertTrue(left2.equals(other))

        right = self.index.join(other, how='right')
        self.assertTrue(right.equals(other))

        right2 = other.join(self.index, how='right')
        self.assertTrue(right2.equals(self.index))

    def test_join_non_unique(self):
        left = Index([4, 4, 3, 3])

        joined, lidx, ridx = left.join(left, return_indexers=True)

        exp_joined = Index([3, 3, 3, 3, 4, 4, 4, 4])
        self.assertTrue(joined.equals(exp_joined))

        exp_lidx = np.array([2, 2, 3, 3, 0, 0, 1, 1], dtype=np.int64)
        self.assert_numpy_array_equal(lidx, exp_lidx)

        exp_ridx = np.array([2, 3, 2, 3, 0, 1, 0, 1], dtype=np.int64)
        self.assert_numpy_array_equal(ridx, exp_ridx)

    def test_join_self(self):
        kinds = 'outer', 'inner', 'left', 'right'
        for kind in kinds:
            joined = self.index.join(self.index, how=kind)
            self.assertIs(self.index, joined)

    def test_intersection(self):
        other = Index([1, 2, 3, 4, 5])
        result = self.index.intersection(other)
        expected = np.sort(np.intersect1d(self.index.values, other.values))
        self.assert_numpy_array_equal(result, expected)

        result = other.intersection(self.index)
        expected = np.sort(np.asarray(np.intersect1d(self.index.values,
                                                     other.values)))
        self.assert_numpy_array_equal(result, expected)

    def test_intersect_str_dates(self):
        dt_dates = [datetime(2012, 2, 9), datetime(2012, 2, 22)]

        i1 = Index(dt_dates, dtype=object)
        i2 = Index(['aa'], dtype=object)
        res = i2.intersection(i1)

        self.assertEqual(len(res), 0)

    def test_union_noncomparable(self):
        from datetime import datetime, timedelta
        # corner case, non-Int64Index
        now = datetime.now()
        other = Index([now + timedelta(i) for i in range(4)], dtype=object)
        result = self.index.union(other)
        expected = np.concatenate((self.index, other))
        self.assert_numpy_array_equal(result, expected)

        result = other.union(self.index)
        expected = np.concatenate((other, self.index))
        self.assert_numpy_array_equal(result, expected)

    def test_cant_or_shouldnt_cast(self):
        # can't
        data = ['foo', 'bar', 'baz']
        self.assertRaises(TypeError, Int64Index, data)

        # shouldn't
        data = ['0', '1', '2']
        self.assertRaises(TypeError, Int64Index, data)

    def test_view_Index(self):
        self.index.view(Index)

    def test_prevent_casting(self):
        result = self.index.astype('O')
        self.assertEqual(result.dtype, np.object_)

    def test_take_preserve_name(self):
        index = Int64Index([1, 2, 3, 4], name='foo')
        taken = index.take([3, 0, 1])
        self.assertEqual(index.name, taken.name)

    def test_int_name_format(self):
        from pandas import Series, DataFrame
        index = Index(['a', 'b', 'c'], name=0)
        s = Series(lrange(3), index)
        df = DataFrame(lrange(3), index=index)
        repr(s)
        repr(df)

    def test_print_unicode_columns(self):
        df = pd.DataFrame(
            {u("\u05d0"): [1, 2, 3], "\u05d1": [4, 5, 6], "c": [7, 8, 9]})
        repr(df.columns)  # should not raise UnicodeDecodeError

    def test_repr_summary(self):
        with cf.option_context('display.max_seq_items', 10):
            r = repr(pd.Index(np.arange(1000)))
            self.assertTrue(len(r) < 100)
            self.assertTrue("..." in r)

    def test_repr_roundtrip(self):
        tm.assert_index_equal(eval(repr(self.index)), self.index)

    def test_unicode_string_with_unicode(self):
        idx = Index(lrange(1000))

        if compat.PY3:
            str(idx)
        else:
            compat.text_type(idx)

    def test_bytestring_with_unicode(self):
        idx = Index(lrange(1000))
        if compat.PY3:
            bytes(idx)
        else:
            str(idx)

    def test_slice_keep_name(self):
        idx = Int64Index([1, 2], name='asdf')
        self.assertEqual(idx.name, idx[1:].name)

class TestDatetimeIndex(Base, tm.TestCase):
    _holder = DatetimeIndex
    _multiprocess_can_split_ = True

    def create_index(self):
        return date_range('20130101',periods=5)

    def test_pickle_compat_construction(self):
        pass

    def test_numeric_compat(self):
        super(TestDatetimeIndex, self).test_numeric_compat()

        if not compat.PY3_2:
            for f in [lambda : np.timedelta64(1, 'D').astype('m8[ns]') * pd.date_range('2000-01-01', periods=3),
                      lambda : pd.date_range('2000-01-01', periods=3) * np.timedelta64(1, 'D').astype('m8[ns]') ]:
                self.assertRaises(TypeError, f)

    def test_roundtrip_pickle_with_tz(self):

        # GH 8367
        # round-trip of timezone
        index=date_range('20130101',periods=3,tz='US/Eastern',name='foo')
        unpickled = self.round_trip_pickle(index)
        self.assertTrue(index.equals(unpickled))

    def test_reindex_preserves_tz_if_target_is_empty_list_or_array(self):
        # GH7774
        index = date_range('20130101', periods=3, tz='US/Eastern')
        self.assertEqual(str(index.reindex([])[0].tz), 'US/Eastern')
        self.assertEqual(str(index.reindex(np.array([]))[0].tz), 'US/Eastern')


class TestPeriodIndex(Base, tm.TestCase):
    _holder = PeriodIndex
    _multiprocess_can_split_ = True

    def create_index(self):
        return period_range('20130101',periods=5,freq='D')

    def test_pickle_compat_construction(self):
        pass

class TestTimedeltaIndex(Base, tm.TestCase):
    _holder = TimedeltaIndex
    _multiprocess_can_split_ = True

    def create_index(self):
        return pd.to_timedelta(range(5),unit='d') + pd.offsets.Hour(1)

    def test_numeric_compat(self):

        idx = self._holder(np.arange(5,dtype='int64'))
        didx = self._holder(np.arange(5,dtype='int64')**2
                            )
        result = idx * 1
        tm.assert_index_equal(result, idx)

        result = 1 * idx
        tm.assert_index_equal(result, idx)

        result = idx / 1
        tm.assert_index_equal(result, idx)

        result = idx // 1
        tm.assert_index_equal(result, idx)

        result = idx * np.array(5,dtype='int64')
        tm.assert_index_equal(result, self._holder(np.arange(5,dtype='int64')*5))

        result = idx * np.arange(5,dtype='int64')
        tm.assert_index_equal(result, didx)

        result = idx * Series(np.arange(5,dtype='int64'))
        tm.assert_index_equal(result, didx)

        result = idx * Series(np.arange(5,dtype='float64')+0.1)
        tm.assert_index_equal(result,
                              Float64Index(np.arange(5,dtype='float64')*(np.arange(5,dtype='float64')+0.1)))


        # invalid
        self.assertRaises(TypeError, lambda : idx * idx)
        self.assertRaises(ValueError, lambda : idx * self._holder(np.arange(3)))
        self.assertRaises(ValueError, lambda : idx * np.array([1,2]))

    def test_pickle_compat_construction(self):
        pass

class TestMultiIndex(Base, tm.TestCase):
    _holder = MultiIndex
    _multiprocess_can_split_ = True
    _compat_props = ['shape', 'ndim', 'size', 'itemsize']

    def setUp(self):
        major_axis = Index(['foo', 'bar', 'baz', 'qux'])
        minor_axis = Index(['one', 'two'])

        major_labels = np.array([0, 0, 1, 2, 3, 3])
        minor_labels = np.array([0, 1, 0, 1, 0, 1])
        self.index_names = ['first', 'second']
        self.index = MultiIndex(levels=[major_axis, minor_axis],
                                labels=[major_labels, minor_labels],
                                names=self.index_names, verify_integrity=False)

    def create_index(self):
        return self.index

    def test_boolean_context_compat2(self):

        # boolean context compat
        # GH7897
        i1 = MultiIndex.from_tuples([('A', 1), ('A', 2)])
        i2 = MultiIndex.from_tuples([('A', 1), ('A', 3)])
        common = i1.intersection(i2)

        def f():
            if common:
                pass
        tm.assertRaisesRegexp(ValueError,'The truth value of a',f)

    def test_labels_dtypes(self):

        # GH 8456
        i = MultiIndex.from_tuples([('A', 1), ('A', 2)])
        self.assertTrue(i.labels[0].dtype == 'int8')
        self.assertTrue(i.labels[1].dtype == 'int8')

        i = MultiIndex.from_product([['a'],range(40)])
        self.assertTrue(i.labels[1].dtype == 'int8')
        i = MultiIndex.from_product([['a'],range(400)])
        self.assertTrue(i.labels[1].dtype == 'int16')
        i = MultiIndex.from_product([['a'],range(40000)])
        self.assertTrue(i.labels[1].dtype == 'int32')

        i = pd.MultiIndex.from_product([['a'],range(1000)])
        self.assertTrue((i.labels[0]>=0).all())
        self.assertTrue((i.labels[1]>=0).all())

    def test_hash_error(self):
        with tm.assertRaisesRegexp(TypeError,
                                   "unhashable type: %r" %
                                   type(self.index).__name__):
            hash(self.index)

    def test_set_names_and_rename(self):
        # so long as these are synonyms, we don't need to test set_names
        self.assertEqual(self.index.rename, self.index.set_names)
        new_names = [name + "SUFFIX" for name in self.index_names]
        ind = self.index.set_names(new_names)
        self.assertEqual(self.index.names, self.index_names)
        self.assertEqual(ind.names, new_names)
        with assertRaisesRegexp(ValueError, "^Length"):
            ind.set_names(new_names + new_names)
        new_names2 = [name + "SUFFIX2" for name in new_names]
        res = ind.set_names(new_names2, inplace=True)
        self.assertIsNone(res)
        self.assertEqual(ind.names, new_names2)

        # set names for specific level (# GH7792)
        ind = self.index.set_names(new_names[0], level=0)
        self.assertEqual(self.index.names, self.index_names)
        self.assertEqual(ind.names, [new_names[0], self.index_names[1]])

        res = ind.set_names(new_names2[0], level=0, inplace=True)
        self.assertIsNone(res)
        self.assertEqual(ind.names, [new_names2[0], self.index_names[1]])

        # set names for multiple levels
        ind = self.index.set_names(new_names, level=[0, 1])
        self.assertEqual(self.index.names, self.index_names)
        self.assertEqual(ind.names, new_names)

        res = ind.set_names(new_names2, level=[0, 1], inplace=True)
        self.assertIsNone(res)
        self.assertEqual(ind.names, new_names2)


    def test_set_levels(self):

        # side note - you probably wouldn't want to use levels and labels
        # directly like this - but it is possible.
        levels, labels = self.index.levels, self.index.labels
        new_levels = [[lev + 'a' for lev in level] for level in levels]

        def assert_matching(actual, expected):
            # avoid specifying internal representation
            # as much as possible
            self.assertEqual(len(actual), len(expected))
            for act, exp in zip(actual, expected):
                act = np.asarray(act)
                exp = np.asarray(exp)
                assert_almost_equal(act, exp)

        # level changing [w/o mutation]
        ind2 = self.index.set_levels(new_levels)
        assert_matching(ind2.levels, new_levels)
        assert_matching(self.index.levels, levels)

        # level changing [w/ mutation]
        ind2 = self.index.copy()
        inplace_return = ind2.set_levels(new_levels, inplace=True)
        self.assertIsNone(inplace_return)
        assert_matching(ind2.levels, new_levels)

        # level changing specific level [w/o mutation]
        ind2 = self.index.set_levels(new_levels[0], level=0)
        assert_matching(ind2.levels, [new_levels[0], levels[1]])
        assert_matching(self.index.levels, levels)

        ind2 = self.index.set_levels(new_levels[1], level=1)
        assert_matching(ind2.levels, [levels[0], new_levels[1]])
        assert_matching(self.index.levels, levels)

        # level changing multiple levels [w/o mutation]
        ind2 = self.index.set_levels(new_levels, level=[0, 1])
        assert_matching(ind2.levels, new_levels)
        assert_matching(self.index.levels, levels)

        # level changing specific level [w/ mutation]
        ind2 = self.index.copy()
        inplace_return = ind2.set_levels(new_levels[0], level=0, inplace=True)
        self.assertIsNone(inplace_return)
        assert_matching(ind2.levels, [new_levels[0], levels[1]])
        assert_matching(self.index.levels, levels)

        ind2 = self.index.copy()
        inplace_return = ind2.set_levels(new_levels[1], level=1, inplace=True)
        self.assertIsNone(inplace_return)
        assert_matching(ind2.levels, [levels[0], new_levels[1]])
        assert_matching(self.index.levels, levels)

        # level changing multiple levels [w/ mutation]
        ind2 = self.index.copy()
        inplace_return = ind2.set_levels(new_levels, level=[0, 1], inplace=True)
        self.assertIsNone(inplace_return)
        assert_matching(ind2.levels, new_levels)
        assert_matching(self.index.levels, levels)

    def test_set_labels(self):
        # side note - you probably wouldn't want to use levels and labels
        # directly like this - but it is possible.
        levels, labels = self.index.levels, self.index.labels
        major_labels, minor_labels = labels
        major_labels = [(x + 1) % 3 for x in major_labels]
        minor_labels = [(x + 1) % 1 for x in minor_labels]
        new_labels = [major_labels, minor_labels]

        def assert_matching(actual, expected):
            # avoid specifying internal representation
            # as much as possible
            self.assertEqual(len(actual), len(expected))
            for act, exp in zip(actual, expected):
                act = np.asarray(act)
                exp = np.asarray(exp)
                assert_almost_equal(act, exp)

        # label changing [w/o mutation]
        ind2 = self.index.set_labels(new_labels)
        assert_matching(ind2.labels, new_labels)
        assert_matching(self.index.labels, labels)

        # label changing [w/ mutation]
        ind2 = self.index.copy()
        inplace_return = ind2.set_labels(new_labels, inplace=True)
        self.assertIsNone(inplace_return)
        assert_matching(ind2.labels, new_labels)

        # label changing specific level [w/o mutation]
        ind2 = self.index.set_labels(new_labels[0], level=0)
        assert_matching(ind2.labels, [new_labels[0], labels[1]])
        assert_matching(self.index.labels, labels)

        ind2 = self.index.set_labels(new_labels[1], level=1)
        assert_matching(ind2.labels, [labels[0], new_labels[1]])
        assert_matching(self.index.labels, labels)

        # label changing multiple levels [w/o mutation]
        ind2 = self.index.set_labels(new_labels, level=[0, 1])
        assert_matching(ind2.labels, new_labels)
        assert_matching(self.index.labels, labels)

        # label changing specific level [w/ mutation]
        ind2 = self.index.copy()
        inplace_return = ind2.set_labels(new_labels[0], level=0, inplace=True)
        self.assertIsNone(inplace_return)
        assert_matching(ind2.labels, [new_labels[0], labels[1]])
        assert_matching(self.index.labels, labels)

        ind2 = self.index.copy()
        inplace_return = ind2.set_labels(new_labels[1], level=1, inplace=True)
        self.assertIsNone(inplace_return)
        assert_matching(ind2.labels, [labels[0], new_labels[1]])
        assert_matching(self.index.labels, labels)

        # label changing multiple levels [w/ mutation]
        ind2 = self.index.copy()
        inplace_return = ind2.set_labels(new_labels, level=[0, 1], inplace=True)
        self.assertIsNone(inplace_return)
        assert_matching(ind2.labels, new_labels)
        assert_matching(self.index.labels, labels)

    def test_set_levels_labels_names_bad_input(self):
        levels, labels = self.index.levels, self.index.labels
        names = self.index.names

        with tm.assertRaisesRegexp(ValueError, 'Length of levels'):
            self.index.set_levels([levels[0]])

        with tm.assertRaisesRegexp(ValueError, 'Length of labels'):
            self.index.set_labels([labels[0]])

        with tm.assertRaisesRegexp(ValueError, 'Length of names'):
            self.index.set_names([names[0]])

        # shouldn't scalar data error, instead should demand list-like
        with tm.assertRaisesRegexp(TypeError, 'list of lists-like'):
            self.index.set_levels(levels[0])

        # shouldn't scalar data error, instead should demand list-like
        with tm.assertRaisesRegexp(TypeError, 'list of lists-like'):
            self.index.set_labels(labels[0])

        # shouldn't scalar data error, instead should demand list-like
        with tm.assertRaisesRegexp(TypeError, 'list-like'):
            self.index.set_names(names[0])

        # should have equal lengths
        with tm.assertRaisesRegexp(TypeError, 'list of lists-like'):
            self.index.set_levels(levels[0], level=[0, 1])

        with tm.assertRaisesRegexp(TypeError, 'list-like'):
            self.index.set_levels(levels, level=0)

        # should have equal lengths
        with tm.assertRaisesRegexp(TypeError, 'list of lists-like'):
            self.index.set_labels(labels[0], level=[0, 1])

        with tm.assertRaisesRegexp(TypeError, 'list-like'):
            self.index.set_labels(labels, level=0)

        # should have equal lengths
        with tm.assertRaisesRegexp(ValueError, 'Length of names'):
            self.index.set_names(names[0], level=[0, 1])

        with tm.assertRaisesRegexp(TypeError, 'string'):
            self.index.set_names(names, level=0)

    def test_metadata_immutable(self):
        levels, labels = self.index.levels, self.index.labels
        # shouldn't be able to set at either the top level or base level
        mutable_regex = re.compile('does not support mutable operations')
        with assertRaisesRegexp(TypeError, mutable_regex):
            levels[0] = levels[0]
        with assertRaisesRegexp(TypeError, mutable_regex):
            levels[0][0] = levels[0][0]
        # ditto for labels
        with assertRaisesRegexp(TypeError, mutable_regex):
            labels[0] = labels[0]
        with assertRaisesRegexp(TypeError, mutable_regex):
            labels[0][0] = labels[0][0]
        # and for names
        names = self.index.names
        with assertRaisesRegexp(TypeError, mutable_regex):
            names[0] = names[0]

    def test_inplace_mutation_resets_values(self):
        levels = [['a', 'b', 'c'], [4]]
        levels2 = [[1, 2, 3], ['a']]
        labels = [[0, 1, 0, 2, 2, 0], [0, 0, 0, 0, 0, 0]]
        mi1 = MultiIndex(levels=levels, labels=labels)
        mi2 = MultiIndex(levels=levels2, labels=labels)
        vals = mi1.values.copy()
        vals2 = mi2.values.copy()
        self.assertIsNotNone(mi1._tuples)

        # make sure level setting works
        new_vals = mi1.set_levels(levels2).values
        assert_almost_equal(vals2, new_vals)
        # non-inplace doesn't kill _tuples [implementation detail]
        assert_almost_equal(mi1._tuples, vals)
        # and values is still same too
        assert_almost_equal(mi1.values, vals)

        # inplace should kill _tuples
        mi1.set_levels(levels2, inplace=True)
        assert_almost_equal(mi1.values, vals2)

        # make sure label setting works too
        labels2 = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]
        exp_values = np.array([(long(1), 'a')] * 6, dtype=object)
        new_values = mi2.set_labels(labels2).values
        # not inplace shouldn't change
        assert_almost_equal(mi2._tuples, vals2)
        # should have correct values
        assert_almost_equal(exp_values, new_values)

        # and again setting inplace should kill _tuples, etc
        mi2.set_labels(labels2, inplace=True)
        assert_almost_equal(mi2.values, new_values)

    def test_copy_in_constructor(self):
        levels = np.array(["a", "b", "c"])
        labels = np.array([1, 1, 2, 0, 0, 1, 1])
        val = labels[0]
        mi = MultiIndex(levels=[levels, levels], labels=[labels, labels],
                        copy=True)
        self.assertEqual(mi.labels[0][0], val)
        labels[0] = 15
        self.assertEqual(mi.labels[0][0], val)
        val = levels[0]
        levels[0] = "PANDA"
        self.assertEqual(mi.levels[0][0], val)

    def test_set_value_keeps_names(self):
        # motivating example from #3742
        lev1 = ['hans', 'hans', 'hans', 'grethe', 'grethe', 'grethe']
        lev2 = ['1', '2', '3'] * 2
        idx = pd.MultiIndex.from_arrays(
            [lev1, lev2],
            names=['Name', 'Number'])
        df = pd.DataFrame(
            np.random.randn(6, 4),
            columns=['one', 'two', 'three', 'four'],
            index=idx)
        df = df.sortlevel()
        self.assertIsNone(df.is_copy)
        self.assertEqual(df.index.names, ('Name', 'Number'))
        df = df.set_value(('grethe', '4'), 'one', 99.34)
        self.assertIsNone(df.is_copy)
        self.assertEqual(df.index.names, ('Name', 'Number'))

    def test_names(self):

        # names are assigned in __init__
        names = self.index_names
        level_names = [level.name for level in self.index.levels]
        self.assertEqual(names, level_names)

        # setting bad names on existing
        index = self.index
        assertRaisesRegexp(ValueError, "^Length of names", setattr, index,
                           "names", list(index.names) + ["third"])
        assertRaisesRegexp(ValueError, "^Length of names", setattr, index,
                           "names", [])

        # initializing with bad names (should always be equivalent)
        major_axis, minor_axis = self.index.levels
        major_labels, minor_labels = self.index.labels
        assertRaisesRegexp(ValueError, "^Length of names", MultiIndex,
                           levels=[major_axis, minor_axis],
                           labels=[major_labels, minor_labels],
                           names=['first'])
        assertRaisesRegexp(ValueError, "^Length of names", MultiIndex,
                           levels=[major_axis, minor_axis],
                           labels=[major_labels, minor_labels],
                           names=['first', 'second', 'third'])

        # names are assigned
        index.names = ["a", "b"]
        ind_names = list(index.names)
        level_names = [level.name for level in index.levels]
        self.assertEqual(ind_names, level_names)

    def test_reference_duplicate_name(self):
        idx = MultiIndex.from_tuples([('a', 'b'), ('c', 'd')], names=['x', 'x'])
        self.assertTrue(idx._reference_duplicate_name('x'))

        idx = MultiIndex.from_tuples([('a', 'b'), ('c', 'd')], names=['x', 'y'])
        self.assertFalse(idx._reference_duplicate_name('x'))

    def test_astype(self):
        expected = self.index.copy()
        actual = self.index.astype('O')
        assert_copy(actual.levels, expected.levels)
        assert_copy(actual.labels, expected.labels)
        self.check_level_names(actual, expected.names)

        with assertRaisesRegexp(TypeError, "^Setting.*dtype.*object"):
            self.index.astype(np.dtype(int))

    def test_constructor_single_level(self):
        single_level = MultiIndex(levels=[['foo', 'bar', 'baz', 'qux']],
                                  labels=[[0, 1, 2, 3]],
                                  names=['first'])
        tm.assert_isinstance(single_level, Index)
        self.assertNotIsInstance(single_level, MultiIndex)
        self.assertEqual(single_level.name, 'first')

        single_level = MultiIndex(levels=[['foo', 'bar', 'baz', 'qux']],
                                  labels=[[0, 1, 2, 3]])
        self.assertIsNone(single_level.name)

    def test_constructor_no_levels(self):
        assertRaisesRegexp(ValueError, "non-zero number of levels/labels",
                           MultiIndex, levels=[], labels=[])
        both_re = re.compile('Must pass both levels and labels')
        with tm.assertRaisesRegexp(TypeError, both_re):
            MultiIndex(levels=[])
        with tm.assertRaisesRegexp(TypeError, both_re):
            MultiIndex(labels=[])

    def test_constructor_mismatched_label_levels(self):
        labels = [np.array([1]), np.array([2]), np.array([3])]
        levels = ["a"]
        assertRaisesRegexp(ValueError, "Length of levels and labels must be"
                           " the same", MultiIndex, levels=levels,
                           labels=labels)
        length_error = re.compile('>= length of level')
        label_error = re.compile(r'Unequal label lengths: \[4, 2\]')

        # important to check that it's looking at the right thing.
        with tm.assertRaisesRegexp(ValueError, length_error):
            MultiIndex(levels=[['a'], ['b']], labels=[[0, 1, 2, 3], [0, 3, 4, 1]])

        with tm.assertRaisesRegexp(ValueError, label_error):
            MultiIndex(levels=[['a'], ['b']], labels=[[0, 0, 0, 0], [0, 0]])

        # external API
        with tm.assertRaisesRegexp(ValueError, length_error):
            self.index.copy().set_levels([['a'], ['b']])

        with tm.assertRaisesRegexp(ValueError, label_error):
            self.index.copy().set_labels([[0, 0, 0, 0], [0, 0]])

        # deprecated properties
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')

            with tm.assertRaisesRegexp(ValueError, length_error):
                self.index.copy().levels = [['a'], ['b']]

            with tm.assertRaisesRegexp(ValueError, label_error):
                self.index.copy().labels = [[0, 0, 0, 0], [0, 0]]


    def assert_multiindex_copied(self, copy, original):
        # levels shoudl be (at least, shallow copied)
        assert_copy(copy.levels, original.levels)

        assert_almost_equal(copy.labels, original.labels)

        # labels doesn't matter which way copied
        assert_almost_equal(copy.labels, original.labels)
        self.assertIsNot(copy.labels, original.labels)

        # names doesn't matter which way copied
        self.assertEqual(copy.names, original.names)
        self.assertIsNot(copy.names, original.names)

        # sort order should be copied
        self.assertEqual(copy.sortorder, original.sortorder)

    def test_copy(self):
        i_copy = self.index.copy()

        self.assert_multiindex_copied(i_copy, self.index)

    def test_shallow_copy(self):
        i_copy = self.index._shallow_copy()

        self.assert_multiindex_copied(i_copy, self.index)

    def test_view(self):
        i_view = self.index.view()

        self.assert_multiindex_copied(i_view, self.index)

    def check_level_names(self, index, names):
        self.assertEqual([level.name for level in index.levels], list(names))

    def test_changing_names(self):

        # names should be applied to levels
        level_names = [level.name for level in self.index.levels]
        self.check_level_names(self.index, self.index.names)

        view = self.index.view()
        copy = self.index.copy()
        shallow_copy = self.index._shallow_copy()

        # changing names should change level names on object
        new_names = [name + "a" for name in self.index.names]
        self.index.names = new_names
        self.check_level_names(self.index, new_names)

        # but not on copies
        self.check_level_names(view, level_names)
        self.check_level_names(copy, level_names)
        self.check_level_names(shallow_copy, level_names)

        # and copies shouldn't change original
        shallow_copy.names = [name + "c" for name in shallow_copy.names]
        self.check_level_names(self.index, new_names)

    def test_duplicate_names(self):
        self.index.names = ['foo', 'foo']
        assertRaisesRegexp(KeyError, 'Level foo not found',
                           self.index._get_level_number, 'foo')

    def test_get_level_number_integer(self):
        self.index.names = [1, 0]
        self.assertEqual(self.index._get_level_number(1), 0)
        self.assertEqual(self.index._get_level_number(0), 1)
        self.assertRaises(IndexError, self.index._get_level_number, 2)
        assertRaisesRegexp(KeyError, 'Level fourth not found',
                           self.index._get_level_number, 'fourth')

    def test_from_arrays(self):
        arrays = []
        for lev, lab in zip(self.index.levels, self.index.labels):
            arrays.append(np.asarray(lev).take(lab))

        result = MultiIndex.from_arrays(arrays)
        self.assertEqual(list(result), list(self.index))

        # infer correctly
        result = MultiIndex.from_arrays([[pd.NaT, Timestamp('20130101')], ['a', 'b']])
        self.assertTrue(result.levels[0].equals(Index([Timestamp('20130101')])))
        self.assertTrue(result.levels[1].equals(Index(['a','b'])))

    def test_from_product(self):

        first = ['foo', 'bar', 'buz']
        second = ['a', 'b', 'c']
        names = ['first', 'second']
        result = MultiIndex.from_product([first, second], names=names)

        tuples = [('foo', 'a'), ('foo', 'b'), ('foo', 'c'),
                  ('bar', 'a'), ('bar', 'b'), ('bar', 'c'),
                  ('buz', 'a'), ('buz', 'b'), ('buz', 'c')]
        expected = MultiIndex.from_tuples(tuples, names=names)

        assert_array_equal(result, expected)
        self.assertEqual(result.names, names)

    def test_from_product_datetimeindex(self):
        dt_index = date_range('2000-01-01', periods=2)
        mi = pd.MultiIndex.from_product([[1, 2], dt_index])
        etalon = pd.lib.list_to_object_array([(1, pd.Timestamp('2000-01-01')),
                                              (1, pd.Timestamp('2000-01-02')),
                                              (2, pd.Timestamp('2000-01-01')),
                                              (2, pd.Timestamp('2000-01-02'))])
        assert_array_equal(mi.values, etalon)

    def test_values_boxed(self):
        tuples = [(1, pd.Timestamp('2000-01-01')),
                  (2, pd.NaT),
                  (3, pd.Timestamp('2000-01-03')),
                  (1, pd.Timestamp('2000-01-04')),
                  (2, pd.Timestamp('2000-01-02')),
                  (3, pd.Timestamp('2000-01-03'))]
        mi = pd.MultiIndex.from_tuples(tuples)
        assert_array_equal(mi.values, pd.lib.list_to_object_array(tuples))
        # Check that code branches for boxed values produce identical results
        assert_array_equal(mi.values[:4], mi[:4].values)

    def test_append(self):
        result = self.index[:3].append(self.index[3:])
        self.assertTrue(result.equals(self.index))

        foos = [self.index[:1], self.index[1:3], self.index[3:]]
        result = foos[0].append(foos[1:])
        self.assertTrue(result.equals(self.index))

        # empty
        result = self.index.append([])
        self.assertTrue(result.equals(self.index))

    def test_get_level_values(self):
        result = self.index.get_level_values(0)
        expected = ['foo', 'foo', 'bar', 'baz', 'qux', 'qux']
        self.assert_numpy_array_equal(result, expected)

        self.assertEqual(result.name, 'first')

        result = self.index.get_level_values('first')
        expected = self.index.get_level_values(0)
        self.assert_numpy_array_equal(result, expected)

    def test_get_level_values_na(self):
        arrays = [['a', 'b', 'b'], [1, np.nan, 2]]
        index = pd.MultiIndex.from_arrays(arrays)
        values = index.get_level_values(1)
        expected = [1, np.nan, 2]
        assert_array_equal(values.values.astype(float), expected)

        arrays = [['a', 'b', 'b'], [np.nan, np.nan, 2]]
        index = pd.MultiIndex.from_arrays(arrays)
        values = index.get_level_values(1)
        expected = [np.nan, np.nan, 2]
        assert_array_equal(values.values.astype(float), expected)

        arrays = [[np.nan, np.nan, np.nan], ['a', np.nan, 1]]
        index = pd.MultiIndex.from_arrays(arrays)
        values = index.get_level_values(0)
        expected = [np.nan, np.nan, np.nan]
        assert_array_equal(values.values.astype(float), expected)
        values = index.get_level_values(1)
        expected = np.array(['a', np.nan, 1],dtype=object)
        assert_array_equal(values.values, expected)

        arrays = [['a', 'b', 'b'], pd.DatetimeIndex([0, 1, pd.NaT])]
        index = pd.MultiIndex.from_arrays(arrays)
        values = index.get_level_values(1)
        expected = pd.DatetimeIndex([0, 1, pd.NaT])
        assert_array_equal(values.values, expected.values)

        arrays = [[], []]
        index = pd.MultiIndex.from_arrays(arrays)
        values = index.get_level_values(0)
        self.assertEqual(values.shape, (0,))

    def test_reorder_levels(self):
        # this blows up
        assertRaisesRegexp(IndexError, '^Too many levels',
                           self.index.reorder_levels, [2, 1, 0])

    def test_nlevels(self):
        self.assertEqual(self.index.nlevels, 2)

    def test_iter(self):
        result = list(self.index)
        expected = [('foo', 'one'), ('foo', 'two'), ('bar', 'one'),
                    ('baz', 'two'), ('qux', 'one'), ('qux', 'two')]
        self.assertEqual(result, expected)

    def test_legacy_pickle(self):
        if compat.PY3:
            raise nose.SkipTest("testing for legacy pickles not support on py3")

        path = tm.get_data_path('multiindex_v1.pickle')
        obj = pd.read_pickle(path)

        obj2 = MultiIndex.from_tuples(obj.values)
        self.assertTrue(obj.equals(obj2))

        res = obj.get_indexer(obj)
        exp = np.arange(len(obj))
        assert_almost_equal(res, exp)

        res = obj.get_indexer(obj2[::-1])
        exp = obj.get_indexer(obj[::-1])
        exp2 = obj2.get_indexer(obj2[::-1])
        assert_almost_equal(res, exp)
        assert_almost_equal(exp, exp2)

    def test_legacy_v2_unpickle(self):

        # 0.7.3 -> 0.8.0 format manage
        path = tm.get_data_path('mindex_073.pickle')
        obj = pd.read_pickle(path)

        obj2 = MultiIndex.from_tuples(obj.values)
        self.assertTrue(obj.equals(obj2))

        res = obj.get_indexer(obj)
        exp = np.arange(len(obj))
        assert_almost_equal(res, exp)

        res = obj.get_indexer(obj2[::-1])
        exp = obj.get_indexer(obj[::-1])
        exp2 = obj2.get_indexer(obj2[::-1])
        assert_almost_equal(res, exp)
        assert_almost_equal(exp, exp2)

    def test_roundtrip_pickle_with_tz(self):

        # GH 8367
        # round-trip of timezone
        index=MultiIndex.from_product([[1,2],['a','b'],date_range('20130101',periods=3,tz='US/Eastern')],names=['one','two','three'])
        unpickled = self.round_trip_pickle(index)
        self.assertTrue(index.equal_levels(unpickled))

    def test_from_tuples_index_values(self):
        result = MultiIndex.from_tuples(self.index)
        self.assertTrue((result.values == self.index.values).all())

    def test_contains(self):
        self.assertIn(('foo', 'two'), self.index)
        self.assertNotIn(('bar', 'two'), self.index)
        self.assertNotIn(None, self.index)

    def test_is_all_dates(self):
        self.assertFalse(self.index.is_all_dates)

    def test_is_numeric(self):
        # MultiIndex is never numeric
        self.assertFalse(self.index.is_numeric())

    def test_getitem(self):
        # scalar
        self.assertEqual(self.index[2], ('bar', 'one'))

        # slice
        result = self.index[2:5]
        expected = self.index[[2, 3, 4]]
        self.assertTrue(result.equals(expected))

        # boolean
        result = self.index[[True, False, True, False, True, True]]
        result2 = self.index[np.array([True, False, True, False, True, True])]
        expected = self.index[[0, 2, 4, 5]]
        self.assertTrue(result.equals(expected))
        self.assertTrue(result2.equals(expected))

    def test_getitem_group_select(self):
        sorted_idx, _ = self.index.sortlevel(0)
        self.assertEqual(sorted_idx.get_loc('baz'), slice(3, 4))
        self.assertEqual(sorted_idx.get_loc('foo'), slice(0, 2))

    def test_get_loc(self):
        self.assertEqual(self.index.get_loc(('foo', 'two')), 1)
        self.assertEqual(self.index.get_loc(('baz', 'two')), 3)
        self.assertRaises(KeyError, self.index.get_loc, ('bar', 'two'))
        self.assertRaises(KeyError, self.index.get_loc, 'quux')

        # 3 levels
        index = MultiIndex(levels=[Index(lrange(4)),
                                   Index(lrange(4)),
                                   Index(lrange(4))],
                           labels=[np.array([0, 0, 1, 2, 2, 2, 3, 3]),
                                   np.array([0, 1, 0, 0, 0, 1, 0, 1]),
                                   np.array([1, 0, 1, 1, 0, 0, 1, 0])])
        self.assertRaises(KeyError, index.get_loc, (1, 1))
        self.assertEqual(index.get_loc((2, 0)), slice(3, 5))

    def test_get_loc_duplicates(self):
        index = Index([2, 2, 2, 2])
        result = index.get_loc(2)
        expected = slice(0, 4)
        self.assertEqual(result, expected)
        # self.assertRaises(Exception, index.get_loc, 2)

        index = Index(['c', 'a', 'a', 'b', 'b'])
        rs = index.get_loc('c')
        xp = 0
        assert(rs == xp)

    def test_get_loc_level(self):
        index = MultiIndex(levels=[Index(lrange(4)),
                                   Index(lrange(4)),
                                   Index(lrange(4))],
                           labels=[np.array([0, 0, 1, 2, 2, 2, 3, 3]),
                                   np.array([0, 1, 0, 0, 0, 1, 0, 1]),
                                   np.array([1, 0, 1, 1, 0, 0, 1, 0])])

        loc, new_index = index.get_loc_level((0, 1))
        expected = slice(1, 2)
        exp_index = index[expected].droplevel(0).droplevel(0)
        self.assertEqual(loc, expected)
        self.assertTrue(new_index.equals(exp_index))

        loc, new_index = index.get_loc_level((0, 1, 0))
        expected = 1
        self.assertEqual(loc, expected)
        self.assertIsNone(new_index)

        self.assertRaises(KeyError, index.get_loc_level, (2, 2))

        index = MultiIndex(levels=[[2000], lrange(4)],
                           labels=[np.array([0, 0, 0, 0]),
                                   np.array([0, 1, 2, 3])])
        result, new_index = index.get_loc_level((2000, slice(None, None)))
        expected = slice(None, None)
        self.assertEqual(result, expected)
        self.assertTrue(new_index.equals(index.droplevel(0)))

    def test_slice_locs(self):
        df = tm.makeTimeDataFrame()
        stacked = df.stack()
        idx = stacked.index

        slob = slice(*idx.slice_locs(df.index[5], df.index[15]))
        sliced = stacked[slob]
        expected = df[5:16].stack()
        tm.assert_almost_equal(sliced.values, expected.values)

        slob = slice(*idx.slice_locs(df.index[5] + timedelta(seconds=30),
                                     df.index[15] - timedelta(seconds=30)))
        sliced = stacked[slob]
        expected = df[6:15].stack()
        tm.assert_almost_equal(sliced.values, expected.values)

    def test_slice_locs_with_type_mismatch(self):
        df = tm.makeTimeDataFrame()
        stacked = df.stack()
        idx = stacked.index
        assertRaisesRegexp(TypeError, '^Level type mismatch', idx.slice_locs,
                           (1, 3))
        assertRaisesRegexp(TypeError, '^Level type mismatch', idx.slice_locs,
                           df.index[5] + timedelta(seconds=30), (5, 2))
        df = tm.makeCustomDataframe(5, 5)
        stacked = df.stack()
        idx = stacked.index
        with assertRaisesRegexp(TypeError, '^Level type mismatch'):
            idx.slice_locs(timedelta(seconds=30))
        # TODO: Try creating a UnicodeDecodeError in exception message
        with assertRaisesRegexp(TypeError, '^Level type mismatch'):
            idx.slice_locs(df.index[1], (16, "a"))

    def test_slice_locs_not_sorted(self):
        index = MultiIndex(levels=[Index(lrange(4)),
                                   Index(lrange(4)),
                                   Index(lrange(4))],
                           labels=[np.array([0, 0, 1, 2, 2, 2, 3, 3]),
                                   np.array([0, 1, 0, 0, 0, 1, 0, 1]),
                                   np.array([1, 0, 1, 1, 0, 0, 1, 0])])

        assertRaisesRegexp(KeyError, "[Kk]ey length.*greater than MultiIndex"
                           " lexsort depth", index.slice_locs, (1, 0, 1),
                           (2, 1, 0))

        # works
        sorted_index, _ = index.sortlevel(0)
        # should there be a test case here???
        sorted_index.slice_locs((1, 0, 1), (2, 1, 0))

    def test_slice_locs_partial(self):
        sorted_idx, _ = self.index.sortlevel(0)

        result = sorted_idx.slice_locs(('foo', 'two'), ('qux', 'one'))
        self.assertEqual(result, (1, 5))

        result = sorted_idx.slice_locs(None, ('qux', 'one'))
        self.assertEqual(result, (0, 5))

        result = sorted_idx.slice_locs(('foo', 'two'), None)
        self.assertEqual(result, (1, len(sorted_idx)))

        result = sorted_idx.slice_locs('bar', 'baz')
        self.assertEqual(result, (2, 4))

    def test_slice_locs_not_contained(self):
        # some searchsorted action

        index = MultiIndex(levels=[[0, 2, 4, 6], [0, 2, 4]],
                           labels=[[0, 0, 0, 1, 1, 2, 3, 3, 3],
                                   [0, 1, 2, 1, 2, 2, 0, 1, 2]],
                           sortorder=0)

        result = index.slice_locs((1, 0), (5, 2))
        self.assertEqual(result, (3, 6))

        result = index.slice_locs(1, 5)
        self.assertEqual(result, (3, 6))

        result = index.slice_locs((2, 2), (5, 2))
        self.assertEqual(result, (3, 6))

        result = index.slice_locs(2, 5)
        self.assertEqual(result, (3, 6))

        result = index.slice_locs((1, 0), (6, 3))
        self.assertEqual(result, (3, 8))

        result = index.slice_locs(-1, 10)
        self.assertEqual(result, (0, len(index)))

    def test_consistency(self):
        # need to construct an overflow
        major_axis = lrange(70000)
        minor_axis = lrange(10)

        major_labels = np.arange(70000)
        minor_labels = np.repeat(lrange(10), 7000)

        # the fact that is works means it's consistent
        index = MultiIndex(levels=[major_axis, minor_axis],
                           labels=[major_labels, minor_labels])

        # inconsistent
        major_labels = np.array([0, 0, 1, 1, 1, 2, 2, 3, 3])
        minor_labels = np.array([0, 1, 0, 1, 1, 0, 1, 0, 1])
        index = MultiIndex(levels=[major_axis, minor_axis],
                           labels=[major_labels, minor_labels])

        self.assertFalse(index.is_unique)

    def test_truncate(self):
        major_axis = Index(lrange(4))
        minor_axis = Index(lrange(2))

        major_labels = np.array([0, 0, 1, 2, 3, 3])
        minor_labels = np.array([0, 1, 0, 1, 0, 1])

        index = MultiIndex(levels=[major_axis, minor_axis],
                           labels=[major_labels, minor_labels])

        result = index.truncate(before=1)
        self.assertNotIn('foo', result.levels[0])
        self.assertIn(1, result.levels[0])

        result = index.truncate(after=1)
        self.assertNotIn(2, result.levels[0])
        self.assertIn(1, result.levels[0])

        result = index.truncate(before=1, after=2)
        self.assertEqual(len(result.levels[0]), 2)

        # after < before
        self.assertRaises(ValueError, index.truncate, 3, 1)

    def test_get_indexer(self):
        major_axis = Index(lrange(4))
        minor_axis = Index(lrange(2))

        major_labels = np.array([0, 0, 1, 2, 2, 3, 3])
        minor_labels = np.array([0, 1, 0, 0, 1, 0, 1])

        index = MultiIndex(levels=[major_axis, minor_axis],
                           labels=[major_labels, minor_labels])
        idx1 = index[:5]
        idx2 = index[[1, 3, 5]]

        r1 = idx1.get_indexer(idx2)
        assert_almost_equal(r1, [1, 3, -1])

        r1 = idx2.get_indexer(idx1, method='pad')
        assert_almost_equal(r1, [-1, 0, 0, 1, 1])

        rffill1 = idx2.get_indexer(idx1, method='ffill')
        assert_almost_equal(r1, rffill1)

        r1 = idx2.get_indexer(idx1, method='backfill')
        assert_almost_equal(r1, [0, 0, 1, 1, 2])

        rbfill1 = idx2.get_indexer(idx1, method='bfill')
        assert_almost_equal(r1, rbfill1)

        # pass non-MultiIndex
        r1 = idx1.get_indexer(idx2._tuple_index)
        rexp1 = idx1.get_indexer(idx2)
        assert_almost_equal(r1, rexp1)

        r1 = idx1.get_indexer([1, 2, 3])
        self.assertTrue((r1 == [-1, -1, -1]).all())

        # create index with duplicates
        idx1 = Index(lrange(10) + lrange(10))
        idx2 = Index(lrange(20))
        assertRaisesRegexp(InvalidIndexError, "Reindexing only valid with"
                           " uniquely valued Index objects",
                           idx1.get_indexer, idx2)

    def test_format(self):
        self.index.format()
        self.index[:0].format()

    def test_format_integer_names(self):
        index = MultiIndex(levels=[[0, 1], [0, 1]],
                           labels=[[0, 0, 1, 1], [0, 1, 0, 1]],
                           names=[0, 1])
        index.format(names=True)

    def test_format_sparse_display(self):
        index = MultiIndex(levels=[[0, 1], [0, 1], [0, 1], [0]],
                           labels=[[0, 0, 0, 1, 1, 1],
                                   [0, 0, 1, 0, 0, 1],
                                   [0, 1, 0, 0, 1, 0],
                                   [0, 0, 0, 0, 0, 0]])

        result = index.format()
        self.assertEqual(result[3], '1  0  0  0')

    def test_format_sparse_config(self):
        warn_filters = warnings.filters
        warnings.filterwarnings('ignore',
                                category=FutureWarning,
                                module=".*format")
        # GH1538
        pd.set_option('display.multi_sparse', False)

        result = self.index.format()
        self.assertEqual(result[1], 'foo  two')

        self.reset_display_options()

        warnings.filters = warn_filters

    def test_to_hierarchical(self):
        index = MultiIndex.from_tuples([(1, 'one'), (1, 'two'),
                                        (2, 'one'), (2, 'two')])
        result = index.to_hierarchical(3)
        expected = MultiIndex(levels=[[1, 2], ['one', 'two']],
                              labels=[[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
                                      [0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1]])
        tm.assert_index_equal(result, expected)
        self.assertEqual(result.names, index.names)

        # K > 1
        result = index.to_hierarchical(3, 2)
        expected = MultiIndex(levels=[[1, 2], ['one', 'two']],
                              labels=[[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
                                      [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]])
        tm.assert_index_equal(result, expected)
        self.assertEqual(result.names, index.names)

        # non-sorted
        index = MultiIndex.from_tuples([(2, 'c'), (1, 'b'),
                                        (2, 'a'), (2, 'b')],
                                       names=['N1', 'N2'])

        result = index.to_hierarchical(2)
        expected = MultiIndex.from_tuples([(2, 'c'), (2, 'c'), (1, 'b'), (1, 'b'),
                                           (2, 'a'), (2, 'a'), (2, 'b'), (2, 'b')],
                                          names=['N1', 'N2'])
        tm.assert_index_equal(result, expected)
        self.assertEqual(result.names, index.names)

    def test_bounds(self):
        self.index._bounds

    def test_equals(self):
        self.assertTrue(self.index.equals(self.index))
        self.assertTrue(self.index.equal_levels(self.index))

        self.assertFalse(self.index.equals(self.index[:-1]))

        self.assertTrue(self.index.equals(self.index._tuple_index))

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

        index2 = MultiIndex(levels=index.levels[:-1],
                            labels=index.labels[:-1])
        self.assertFalse(index.equals(index2))
        self.assertFalse(index.equal_levels(index2))

        # levels are different
        major_axis = Index(lrange(4))
        minor_axis = Index(lrange(2))

        major_labels = np.array([0, 0, 1, 2, 2, 3])
        minor_labels = np.array([0, 1, 0, 0, 1, 0])

        index = MultiIndex(levels=[major_axis, minor_axis],
                           labels=[major_labels, minor_labels])
        self.assertFalse(self.index.equals(index))
        self.assertFalse(self.index.equal_levels(index))

        # some of the labels are different
        major_axis = Index(['foo', 'bar', 'baz', 'qux'])
        minor_axis = Index(['one', 'two'])

        major_labels = np.array([0, 0, 2, 2, 3, 3])
        minor_labels = np.array([0, 1, 0, 1, 0, 1])

        index = MultiIndex(levels=[major_axis, minor_axis],
                           labels=[major_labels, minor_labels])
        self.assertFalse(self.index.equals(index))

    def test_identical(self):
        mi = self.index.copy()
        mi2 = self.index.copy()
        self.assertTrue(mi.identical(mi2))

        mi = mi.set_names(['new1', 'new2'])
        self.assertTrue(mi.equals(mi2))
        self.assertFalse(mi.identical(mi2))

        mi2 = mi2.set_names(['new1', 'new2'])
        self.assertTrue(mi.identical(mi2))

        mi3 = Index(mi.tolist(), names=mi.names)
        mi4 = Index(mi.tolist(), names=mi.names, tupleize_cols=False)
        self.assertTrue(mi.identical(mi3))
        self.assertFalse(mi.identical(mi4))
        self.assertTrue(mi.equals(mi4))

    def test_is_(self):

        mi = MultiIndex.from_tuples(lzip(range(10), range(10)))
        self.assertTrue(mi.is_(mi))
        self.assertTrue(mi.is_(mi.view()))
        self.assertTrue(mi.is_(mi.view().view().view().view()))
        mi2 = mi.view()
        # names are metadata, they don't change id
        mi2.names = ["A", "B"]
        self.assertTrue(mi2.is_(mi))
        self.assertTrue(mi.is_(mi2))

        self.assertTrue(mi.is_(mi.set_names(["C", "D"])))
        mi2 = mi.view()
        mi2.set_names(["E", "F"], inplace=True)
        self.assertTrue(mi.is_(mi2))
        # levels are inherent properties, they change identity
        mi3 = mi2.set_levels([lrange(10), lrange(10)])
        self.assertFalse(mi3.is_(mi2))
        # shouldn't change
        self.assertTrue(mi2.is_(mi))
        mi4 = mi3.view()
        mi4.set_levels([[1 for _ in range(10)], lrange(10)], inplace=True)
        self.assertFalse(mi4.is_(mi3))
        mi5 = mi.view()
        mi5.set_levels(mi5.levels, inplace=True)
        self.assertFalse(mi5.is_(mi))

    def test_union(self):
        piece1 = self.index[:5][::-1]
        piece2 = self.index[3:]

        the_union = piece1 | piece2

        tups = sorted(self.index._tuple_index)
        expected = MultiIndex.from_tuples(tups)

        self.assertTrue(the_union.equals(expected))

        # corner case, pass self or empty thing:
        the_union = self.index.union(self.index)
        self.assertIs(the_union, self.index)

        the_union = self.index.union(self.index[:0])
        self.assertIs(the_union, self.index)

        # won't work in python 3
        # tuples = self.index._tuple_index
        # result = self.index[:4] | tuples[4:]
        # self.assertTrue(result.equals(tuples))

    # not valid for python 3
    # def test_union_with_regular_index(self):
    #     other = Index(['A', 'B', 'C'])

    #     result = other.union(self.index)
    #     self.assertIn(('foo', 'one'), result)
    #     self.assertIn('B', result)

    #     result2 = self.index.union(other)
    #     self.assertTrue(result.equals(result2))

    def test_intersection(self):
        piece1 = self.index[:5][::-1]
        piece2 = self.index[3:]

        the_int = piece1 & piece2
        tups = sorted(self.index[3:5]._tuple_index)
        expected = MultiIndex.from_tuples(tups)
        self.assertTrue(the_int.equals(expected))

        # corner case, pass self
        the_int = self.index.intersection(self.index)
        self.assertIs(the_int, self.index)

        # empty intersection: disjoint
        empty = self.index[:2] & self.index[2:]
        expected = self.index[:0]
        self.assertTrue(empty.equals(expected))

        # can't do in python 3
        # tuples = self.index._tuple_index
        # result = self.index & tuples
        # self.assertTrue(result.equals(tuples))

    def test_difference(self):

        first = self.index
        result = first.difference(self.index[-3:])

        # - API change GH 8226
        with tm.assert_produces_warning():
            first - self.index[-3:]

        expected = MultiIndex.from_tuples(sorted(self.index[:-3].values),
                                          sortorder=0,
                                          names=self.index.names)

        tm.assert_isinstance(result, MultiIndex)
        self.assertTrue(result.equals(expected))
        self.assertEqual(result.names, self.index.names)

        # empty difference: reflexive
        result = self.index.difference(self.index)
        expected = self.index[:0]
        self.assertTrue(result.equals(expected))
        self.assertEqual(result.names, self.index.names)

        # empty difference: superset
        result = self.index[-3:].difference(self.index)
        expected = self.index[:0]
        self.assertTrue(result.equals(expected))
        self.assertEqual(result.names, self.index.names)

        # empty difference: degenerate
        result = self.index[:0].difference(self.index)
        expected = self.index[:0]
        self.assertTrue(result.equals(expected))
        self.assertEqual(result.names, self.index.names)

        # names not the same
        chunklet = self.index[-3:]
        chunklet.names = ['foo', 'baz']
        result = first.difference(chunklet)
        self.assertEqual(result.names, (None, None))

        # empty, but non-equal
        result = self.index.difference(self.index.sortlevel(1)[0])
        self.assertEqual(len(result), 0)

        # raise Exception called with non-MultiIndex
        result = first.difference(first._tuple_index)
        self.assertTrue(result.equals(first[:0]))

        # name from empty array
        result = first.difference([])
        self.assertTrue(first.equals(result))
        self.assertEqual(first.names, result.names)

        # name from non-empty array
        result = first.difference([('foo', 'one')])
        expected = pd.MultiIndex.from_tuples([('bar', 'one'), ('baz', 'two'),
                                            ('foo', 'two'), ('qux', 'one'),
                                            ('qux', 'two')])
        expected.names = first.names
        self.assertEqual(first.names, result.names)
        assertRaisesRegexp(TypeError, "other must be a MultiIndex or a list"
                           " of tuples", first.difference, [1, 2, 3, 4, 5])

    def test_from_tuples(self):
        assertRaisesRegexp(TypeError, 'Cannot infer number of levels from'
                           ' empty list', MultiIndex.from_tuples, [])

        idx = MultiIndex.from_tuples(((1, 2), (3, 4)), names=['a', 'b'])
        self.assertEqual(len(idx), 2)

    def test_argsort(self):
        result = self.index.argsort()
        expected = self.index._tuple_index.argsort()
        self.assert_numpy_array_equal(result, expected)

    def test_sortlevel(self):
        import random

        tuples = list(self.index)
        random.shuffle(tuples)

        index = MultiIndex.from_tuples(tuples)

        sorted_idx, _ = index.sortlevel(0)
        expected = MultiIndex.from_tuples(sorted(tuples))
        self.assertTrue(sorted_idx.equals(expected))

        sorted_idx, _ = index.sortlevel(0, ascending=False)
        self.assertTrue(sorted_idx.equals(expected[::-1]))

        sorted_idx, _ = index.sortlevel(1)
        by1 = sorted(tuples, key=lambda x: (x[1], x[0]))
        expected = MultiIndex.from_tuples(by1)
        self.assertTrue(sorted_idx.equals(expected))

        sorted_idx, _ = index.sortlevel(1, ascending=False)
        self.assertTrue(sorted_idx.equals(expected[::-1]))

    def test_sortlevel_not_sort_remaining(self):
        mi = MultiIndex.from_tuples([[1, 1, 3], [1, 1, 1]], names=list('ABC'))
        sorted_idx, _ = mi.sortlevel('A', sort_remaining=False)
        self.assertTrue(sorted_idx.equals(mi))

    def test_sortlevel_deterministic(self):
        tuples = [('bar', 'one'), ('foo', 'two'), ('qux', 'two'),
                  ('foo', 'one'), ('baz', 'two'), ('qux', 'one')]

        index = MultiIndex.from_tuples(tuples)

        sorted_idx, _ = index.sortlevel(0)
        expected = MultiIndex.from_tuples(sorted(tuples))
        self.assertTrue(sorted_idx.equals(expected))

        sorted_idx, _ = index.sortlevel(0, ascending=False)
        self.assertTrue(sorted_idx.equals(expected[::-1]))

        sorted_idx, _ = index.sortlevel(1)
        by1 = sorted(tuples, key=lambda x: (x[1], x[0]))
        expected = MultiIndex.from_tuples(by1)
        self.assertTrue(sorted_idx.equals(expected))

        sorted_idx, _ = index.sortlevel(1, ascending=False)
        self.assertTrue(sorted_idx.equals(expected[::-1]))

    def test_dims(self):
        pass

    def test_drop(self):
        dropped = self.index.drop([('foo', 'two'), ('qux', 'one')])

        index = MultiIndex.from_tuples([('foo', 'two'), ('qux', 'one')])
        dropped2 = self.index.drop(index)

        expected = self.index[[0, 2, 3, 5]]
        self.assertTrue(dropped.equals(expected))
        self.assertTrue(dropped2.equals(expected))

        dropped = self.index.drop(['bar'])
        expected = self.index[[0, 1, 3, 4, 5]]
        self.assertTrue(dropped.equals(expected))

        index = MultiIndex.from_tuples([('bar', 'two')])
        self.assertRaises(KeyError, self.index.drop, [('bar', 'two')])
        self.assertRaises(KeyError, self.index.drop, index)

        # mixed partial / full drop
        dropped = self.index.drop(['foo', ('qux', 'one')])
        expected = self.index[[2, 3, 5]]
        self.assertTrue(dropped.equals(expected))

    def test_droplevel_with_names(self):
        index = self.index[self.index.get_loc('foo')]
        dropped = index.droplevel(0)
        self.assertEqual(dropped.name, 'second')

        index = MultiIndex(levels=[Index(lrange(4)),
                                   Index(lrange(4)),
                                   Index(lrange(4))],
                           labels=[np.array([0, 0, 1, 2, 2, 2, 3, 3]),
                                   np.array([0, 1, 0, 0, 0, 1, 0, 1]),
                                   np.array([1, 0, 1, 1, 0, 0, 1, 0])],
                           names=['one', 'two', 'three'])
        dropped = index.droplevel(0)
        self.assertEqual(dropped.names, ('two', 'three'))

        dropped = index.droplevel('two')
        expected = index.droplevel(1)
        self.assertTrue(dropped.equals(expected))

    def test_droplevel_multiple(self):
        index = MultiIndex(levels=[Index(lrange(4)),
                                   Index(lrange(4)),
                                   Index(lrange(4))],
                           labels=[np.array([0, 0, 1, 2, 2, 2, 3, 3]),
                                   np.array([0, 1, 0, 0, 0, 1, 0, 1]),
                                   np.array([1, 0, 1, 1, 0, 0, 1, 0])],
                           names=['one', 'two', 'three'])

        dropped = index[:2].droplevel(['three', 'one'])
        expected = index[:2].droplevel(2).droplevel(0)
        self.assertTrue(dropped.equals(expected))

    def test_insert(self):
        # key contained in all levels
        new_index = self.index.insert(0, ('bar', 'two'))
        self.assertTrue(new_index.equal_levels(self.index))
        self.assertEqual(new_index[0], ('bar', 'two'))

        # key not contained in all levels
        new_index = self.index.insert(0, ('abc', 'three'))
        self.assert_numpy_array_equal(new_index.levels[0],
                                      list(self.index.levels[0]) + ['abc'])
        self.assert_numpy_array_equal(new_index.levels[1],
                                      list(self.index.levels[1]) + ['three'])
        self.assertEqual(new_index[0], ('abc', 'three'))

        # key wrong length
        assertRaisesRegexp(ValueError, "Item must have length equal to number"
                           " of levels", self.index.insert, 0, ('foo2',))

    def test_take_preserve_name(self):
        taken = self.index.take([3, 0, 1])
        self.assertEqual(taken.names, self.index.names)

    def test_join_level(self):
        def _check_how(other, how):
            join_index, lidx, ridx = other.join(self.index, how=how,
                                                level='second',
                                                return_indexers=True)

            exp_level = other.join(self.index.levels[1], how=how)
            self.assertTrue(join_index.levels[0].equals(self.index.levels[0]))
            self.assertTrue(join_index.levels[1].equals(exp_level))

            # pare down levels
            mask = np.array(
                [x[1] in exp_level for x in self.index], dtype=bool)
            exp_values = self.index.values[mask]
            self.assert_numpy_array_equal(join_index.values, exp_values)

            if how in ('outer', 'inner'):
                join_index2, ridx2, lidx2 = \
                    self.index.join(other, how=how, level='second',
                                    return_indexers=True)

                self.assertTrue(join_index.equals(join_index2))
                self.assert_numpy_array_equal(lidx, lidx2)
                self.assert_numpy_array_equal(ridx, ridx2)
                self.assert_numpy_array_equal(join_index2.values, exp_values)

        def _check_all(other):
            _check_how(other, 'outer')
            _check_how(other, 'inner')
            _check_how(other, 'left')
            _check_how(other, 'right')

        _check_all(Index(['three', 'one', 'two']))
        _check_all(Index(['one']))
        _check_all(Index(['one', 'three']))

        # some corner cases
        idx = Index(['three', 'one', 'two'])
        result = idx.join(self.index, level='second')
        tm.assert_isinstance(result, MultiIndex)

        assertRaisesRegexp(TypeError, "Join.*MultiIndex.*ambiguous",
                           self.index.join, self.index, level=1)

    def test_join_self(self):
        kinds = 'outer', 'inner', 'left', 'right'
        for kind in kinds:
            res = self.index
            joined = res.join(res, how=kind)
            self.assertIs(res, joined)

    def test_reindex(self):
        result, indexer = self.index.reindex(list(self.index[:4]))
        tm.assert_isinstance(result, MultiIndex)
        self.check_level_names(result, self.index[:4].names)

        result, indexer = self.index.reindex(list(self.index))
        tm.assert_isinstance(result, MultiIndex)
        self.assertIsNone(indexer)
        self.check_level_names(result, self.index.names)

    def test_reindex_level(self):
        idx = Index(['one'])

        target, indexer = self.index.reindex(idx, level='second')
        target2, indexer2 = idx.reindex(self.index, level='second')

        exp_index = self.index.join(idx, level='second', how='right')
        exp_index2 = self.index.join(idx, level='second', how='left')

        self.assertTrue(target.equals(exp_index))
        exp_indexer = np.array([0, 2, 4])
        self.assert_numpy_array_equal(indexer, exp_indexer)

        self.assertTrue(target2.equals(exp_index2))
        exp_indexer2 = np.array([0, -1, 0, -1, 0, -1])
        self.assert_numpy_array_equal(indexer2, exp_indexer2)

        assertRaisesRegexp(TypeError, "Fill method not supported",
                           self.index.reindex, self.index, method='pad',
                           level='second')

        assertRaisesRegexp(TypeError, "Fill method not supported",
                           idx.reindex, idx, method='bfill', level='first')

    def test_has_duplicates(self):
        self.assertFalse(self.index.has_duplicates)
        self.assertTrue(self.index.append(self.index).has_duplicates)

        index = MultiIndex(levels=[[0, 1], [0, 1, 2]],
                           labels=[[0, 0, 0, 0, 1, 1, 1],
                                   [0, 1, 2, 0, 0, 1, 2]])
        self.assertTrue(index.has_duplicates)

    def test_tolist(self):
        result = self.index.tolist()
        exp = list(self.index.values)
        self.assertEqual(result, exp)

    def test_repr_with_unicode_data(self):
        with pd.core.config.option_context("display.encoding",'UTF-8'):
            d = {"a": [u("\u05d0"), 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]}
            index = pd.DataFrame(d).set_index(["a", "b"]).index
            self.assertFalse("\\u" in repr(index))  # we don't want unicode-escaped

    def test_repr_roundtrip(self):
        tm.assert_index_equal(eval(repr(self.index)), self.index)

    def test_unicode_string_with_unicode(self):
        d = {"a": [u("\u05d0"), 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]}
        idx = pd.DataFrame(d).set_index(["a", "b"]).index

        if compat.PY3:
            str(idx)
        else:
            compat.text_type(idx)

    def test_bytestring_with_unicode(self):
        d = {"a": [u("\u05d0"), 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]}
        idx = pd.DataFrame(d).set_index(["a", "b"]).index

        if compat.PY3:
            bytes(idx)
        else:
            str(idx)

    def test_slice_keep_name(self):
        x = MultiIndex.from_tuples([('a', 'b'), (1, 2), ('c', 'd')],
                                   names=['x', 'y'])
        self.assertEqual(x[1:].names, x.names)

    def test_isnull_behavior(self):
        # should not segfault GH5123
        # NOTE: if MI representation changes, may make sense to allow
        # isnull(MI)
        with tm.assertRaises(NotImplementedError):
            pd.isnull(self.index)

    def test_level_setting_resets_attributes(self):
        ind = MultiIndex.from_arrays([
            ['A', 'A', 'B', 'B', 'B'],
            [1, 2, 1, 2, 3]])
        assert ind.is_monotonic
        ind.set_levels([['A', 'B', 'A', 'A', 'B'], [2, 1, 3, -2, 5]],
                       inplace=True)
        # if this fails, probably didn't reset the cache correctly.
        assert not ind.is_monotonic

    def test_isin(self):
        values = [('foo', 2), ('bar', 3), ('quux', 4)]

        idx = MultiIndex.from_arrays([['qux', 'baz', 'foo', 'bar'],
                                      np.arange(4)])
        result = idx.isin(values)
        expected = np.array([False, False, True, True])
        self.assert_numpy_array_equal(result, expected)

        # empty, return dtype bool
        idx = MultiIndex.from_arrays([[], []])
        result = idx.isin(values)
        self.assertEqual(len(result), 0)
        self.assertEqual(result.dtype, np.bool_)

    def test_isin_nan(self):
        idx = MultiIndex.from_arrays([['foo', 'bar'], [1.0, np.nan]])
        self.assert_numpy_array_equal(idx.isin([('bar', np.nan)]),
                                      [False, False])
        self.assert_numpy_array_equal(idx.isin([('bar', float('nan'))]),
                                      [False, False])

    def test_isin_level_kwarg(self):
        idx = MultiIndex.from_arrays([['qux', 'baz', 'foo', 'bar'],
                                      np.arange(4)])

        vals_0 = ['foo', 'bar', 'quux']
        vals_1 = [2, 3, 10]

        expected = np.array([False, False, True, True])
        self.assert_numpy_array_equal(expected, idx.isin(vals_0, level=0))
        self.assert_numpy_array_equal(expected, idx.isin(vals_0, level=-2))

        self.assert_numpy_array_equal(expected, idx.isin(vals_1, level=1))
        self.assert_numpy_array_equal(expected, idx.isin(vals_1, level=-1))

        self.assertRaises(IndexError, idx.isin, vals_0, level=5)
        self.assertRaises(IndexError, idx.isin, vals_0, level=-5)

        self.assertRaises(KeyError, idx.isin, vals_0, level=1.0)
        self.assertRaises(KeyError, idx.isin, vals_1, level=-1.0)
        self.assertRaises(KeyError, idx.isin, vals_1, level='A')

        idx.names = ['A', 'B']
        self.assert_numpy_array_equal(expected, idx.isin(vals_0, level='A'))
        self.assert_numpy_array_equal(expected, idx.isin(vals_1, level='B'))

        self.assertRaises(KeyError, idx.isin, vals_1, level='C')

    def test_reindex_preserves_names_when_target_is_list_or_ndarray(self):
        # GH6552
        idx = self.index.copy()
        target = idx.copy()
        idx.names = target.names = [None, None]

        other_dtype = pd.MultiIndex.from_product([[1, 2], [3, 4]])

        # list & ndarray cases
        self.assertEqual(idx.reindex([])[0].names, [None, None])
        self.assertEqual(idx.reindex(np.array([]))[0].names, [None, None])
        self.assertEqual(idx.reindex(target.tolist())[0].names, [None, None])
        self.assertEqual(idx.reindex(target.values)[0].names, [None, None])
        self.assertEqual(idx.reindex(other_dtype.tolist())[0].names, [None, None])
        self.assertEqual(idx.reindex(other_dtype.values)[0].names, [None, None])

        idx.names = ['foo', 'bar']
        self.assertEqual(idx.reindex([])[0].names, ['foo', 'bar'])
        self.assertEqual(idx.reindex(np.array([]))[0].names, ['foo', 'bar'])
        self.assertEqual(idx.reindex(target.tolist())[0].names, ['foo', 'bar'])
        self.assertEqual(idx.reindex(target.values)[0].names, ['foo', 'bar'])
        self.assertEqual(idx.reindex(other_dtype.tolist())[0].names, ['foo', 'bar'])
        self.assertEqual(idx.reindex(other_dtype.values)[0].names, ['foo', 'bar'])

    def test_reindex_lvl_preserves_names_when_target_is_list_or_array(self):
        # GH7774
        idx = pd.MultiIndex.from_product([[0, 1], ['a', 'b']],
                                         names=['foo', 'bar'])
        self.assertEqual(idx.reindex([], level=0)[0].names, ['foo', 'bar'])
        self.assertEqual(idx.reindex([], level=1)[0].names, ['foo', 'bar'])

    def test_reindex_lvl_preserves_type_if_target_is_empty_list_or_array(self):
        # GH7774
        idx = pd.MultiIndex.from_product([[0, 1], ['a', 'b']])
        self.assertEqual(idx.reindex([], level=0)[0].levels[0].dtype.type,
                         np.int64)
        self.assertEqual(idx.reindex([], level=1)[0].levels[1].dtype.type,
                         np.object_)

    def test_groupby(self):
        groups = self.index.groupby(np.array([1, 1, 1, 2, 2, 2]))
        labels = self.index.get_values().tolist()
        exp = {1: labels[:3], 2: labels[3:]}
        tm.assert_dict_equal(groups, exp)

        # GH5620
        groups = self.index.groupby(self.index)
        exp = dict((key, [key]) for key in self.index)
        tm.assert_dict_equal(groups, exp)


def test_get_combined_index():
    from pandas.core.index import _get_combined_index
    result = _get_combined_index([])
    assert(result.equals(Index([])))



if __name__ == '__main__':
    nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],
                   exit=False)
