From 4903b20bc385ac335a8da0fdfd33a5a79ed69c25 Mon Sep 17 00:00:00 2001 From: Christian Stade-Schuldt Date: Wed, 24 May 2017 13:35:49 -0700 Subject: [PATCH 1/7] return empty MultiIndex for symmetrical difference on equal MultiIndexes --- pandas/core/indexes/base.py | 66 +++++++++++++++---------------- pandas/tests/indexes/test_base.py | 9 ++++- 2 files changed, 40 insertions(+), 35 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 2af4f112ca941..c1b637e64b515 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -12,7 +12,6 @@ from pandas.compat.numpy import function as nv from pandas import compat - from pandas.core.dtypes.generic import ABCSeries, ABCMultiIndex, ABCPeriodIndex from pandas.core.dtypes.missing import isnull, array_equivalent from pandas.core.dtypes.common import ( @@ -53,7 +52,6 @@ from pandas.core.strings import StringAccessorMixin from pandas.core.config import get_option - # simplify default_pprint = lambda x, max_seq_items=None: \ pprint_thing(x, escape_chars=('\t', '\r', '\n'), quote_strings=True, @@ -182,8 +180,8 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None, elif isinstance(data, (np.ndarray, Index, ABCSeries)): if (is_datetime64_any_dtype(data) or - (dtype is not None and is_datetime64_any_dtype(dtype)) or - 'tz' in kwargs): + (dtype is not None and is_datetime64_any_dtype(dtype)) or + 'tz' in kwargs): from pandas.core.indexes.datetimes import DatetimeIndex result = DatetimeIndex(data, copy=copy, name=name, dtype=dtype, **kwargs) @@ -193,7 +191,7 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None, return result elif (is_timedelta64_dtype(data) or - (dtype is not None and is_timedelta64_dtype(dtype))): + (dtype is not None and is_timedelta64_dtype(dtype))): from pandas.core.indexes.timedeltas import TimedeltaIndex result = TimedeltaIndex(data, copy=copy, name=name, **kwargs) if dtype is not None and _o_dtype == dtype: @@ -297,7 +295,7 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None, elif inferred != 'string': if inferred.startswith('datetime'): if (lib.is_datetime_with_singletz_array(subarr) or - 'tz' in kwargs): + 'tz' in kwargs): # only when subarr has the same tz from pandas.core.indexes.datetimes import ( DatetimeIndex) @@ -903,7 +901,7 @@ def best_len(values): # line, then don't justify if (is_truncated or not (len(', '.join(head)) < display_width and - len(', '.join(tail)) < display_width)): + len(', '.join(tail)) < display_width)): max_len = max(best_len(head), best_len(tail)) head = [x.rjust(max_len) for x in head] tail = [x.rjust(max_len) for x in tail] @@ -1053,7 +1051,7 @@ def nlevels(self): return 1 def _get_names(self): - return FrozenList((self.name, )) + return FrozenList((self.name,)) def _set_names(self, values, level=None): if len(values) != 1: @@ -1257,7 +1255,7 @@ def _convert_scalar_indexer(self, key, kind=None): if kind == 'iloc': return self._validate_indexer('positional', key, kind) - if len(self) and not isinstance(self, ABCMultiIndex,): + if len(self) and not isinstance(self, ABCMultiIndex, ): # we can raise here if we are definitive that this # is positional indexing (eg. .ix on with a float) @@ -1465,8 +1463,8 @@ def _invalid_indexer(self, form, key): """ consistent invalid indexer message """ raise TypeError("cannot do {form} indexing on {klass} with these " "indexers [{key}] of {kind}".format( - form=form, klass=type(self), key=key, - kind=type(key))) + form=form, klass=type(self), key=key, + kind=type(key))) def get_duplicates(self): from collections import defaultdict @@ -1500,7 +1498,7 @@ def _validate_index_level(self, level): if isinstance(level, int): if level < 0 and level != -1: raise IndexError("Too many levels: Index has only 1 level," - " %d is not a valid level number" % (level, )) + " %d is not a valid level number" % (level,)) elif level > 0: raise IndexError("Too many levels:" " Index has only 1 level, not %d" % @@ -2320,11 +2318,15 @@ def symmetric_difference(self, other, result_name=None): except TypeError: pass - attribs = self._get_attributes_dict() - attribs['name'] = result_name - if 'freq' in attribs: - attribs['freq'] = None - return self._shallow_copy_with_infer(the_diff, **attribs) + if self.nlevels > 1 and len(the_diff) == 0: + return type(self)([[] for _ in range(self.nlevels)], + [[] for _ in range(self.nlevels)]) + else: + attribs = self._get_attributes_dict() + attribs['name'] = result_name + if 'freq' in attribs: + attribs['freq'] = None + return self._shallow_copy_with_infer(the_diff, **attribs) sym_diff = deprecate('sym_diff', symmetric_difference) @@ -3369,7 +3371,7 @@ def _maybe_cast_slice_bound(self, label, side, kind): # reject them if is_float(label): if not (kind in ['ix'] and (self.holds_integer() or - self.is_floating())): + self.is_floating())): self._invalid_indexer('slice', label) # we are trying to find integer bounds on a non-integer based index @@ -3387,7 +3389,7 @@ def _searchsorted_monotonic(self, label, side='left'): # everything for it to work (element ordering, search side and # resulting value). pos = self[::-1].searchsorted(label, side='right' if side == 'left' - else 'right') + else 'right') return len(self) - pos raise ValueError('index must be monotonic increasing or decreasing') @@ -3418,7 +3420,7 @@ def get_slice_bound(self, label, side, kind): if side not in ('left', 'right'): raise ValueError("Invalid value for side kwarg," " must be either 'left' or 'right': %s" % - (side, )) + (side,)) original_label = label @@ -3665,7 +3667,7 @@ def _evaluate_compare(self, other): return self._evaluate_compare(other, op) if (is_object_dtype(self) and - self.nlevels == 1): + self.nlevels == 1): # don't pass MultiIndex with np.errstate(all='ignore'): @@ -3741,9 +3743,9 @@ def _validate_for_numeric_unaryop(self, op, opstr): if not self._is_numeric_dtype: raise TypeError("cannot evaluate a numeric op " "{opstr} for type: {typ}".format( - opstr=opstr, - typ=type(self)) - ) + opstr=opstr, + typ=type(self)) + ) def _validate_for_numeric_binop(self, other, op, opstr): """ @@ -3759,17 +3761,17 @@ def _validate_for_numeric_binop(self, other, op, opstr): if not self._is_numeric_dtype: raise TypeError("cannot evaluate a numeric op {opstr} " "for type: {typ}".format( - opstr=opstr, - typ=type(self)) - ) + opstr=opstr, + typ=type(self)) + ) if isinstance(other, Index): if not other._is_numeric_dtype: raise TypeError("cannot evaluate a numeric op " "{opstr} with type: {typ}".format( - opstr=type(self), - typ=type(other)) - ) + opstr=type(self), + typ=type(other)) + ) elif isinstance(other, np.ndarray) and not other.ndim: other = other.item() @@ -3866,9 +3868,7 @@ def _add_numeric_methods_unary(cls): """ add in numeric unary methods """ def _make_evaluate_unary(op, opstr): - def _evaluate_numeric_unary(self): - self._validate_for_numeric_unaryop(op, opstr) attrs = self._get_attributes_dict() attrs = self._maybe_update_attributes(attrs) @@ -3909,7 +3909,7 @@ def _make_logical_function(name, desc, f): def logical_func(self, *args, **kwargs): result = f(self.values) if (isinstance(result, (np.ndarray, ABCSeries, Index)) and - result.ndim == 0): + result.ndim == 0): # return NumPy type return result.dtype.type(result.item()) else: # pragma: no cover diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 6a2087b37631e..934281c428f0f 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -188,7 +188,6 @@ def test_constructor_ndarray_like(self): # 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 @@ -246,7 +245,6 @@ def test_index_ctor_infer_nan_nat(self): [np.timedelta64('nat'), np.nan], [pd.NaT, np.timedelta64('nat')], [np.timedelta64('nat'), pd.NaT]]: - tm.assert_index_equal(Index(data), exp) tm.assert_index_equal(Index(np.array(data, dtype=object)), exp) @@ -909,6 +907,13 @@ def test_symmetric_difference(self): expected = MultiIndex.from_tuples([('bar', 2), ('baz', 3), ('bar', 3)]) assert tm.equalContents(result, expected) + # on equal multiIndexs + idx1 = MultiIndex.from_tuples(self.tuples) + idx2 = MultiIndex.from_tuples(self.tuples) + result = idx1.symmetric_difference(idx2) + expected = MultiIndex(levels=[[], []], labels=[[], []]) + assert tm.equalContents(result, expected) + # nans: # GH 13514 change: {nan} - {nan} == {} # (GH 6444, sorting of nans, is no longer an issue) From 743aa47c71f7425d12246de23fdb85556437d3a9 Mon Sep 17 00:00:00 2001 From: Christian Stade-Schuldt Date: Wed, 24 May 2017 14:48:15 -0700 Subject: [PATCH 2/7] move test to its own method test_symmetric_difference_on_equal_multiindex --- pandas/core/indexes/base.py | 65 +++++++++++++++++-------------- pandas/tests/indexes/test_base.py | 15 +++---- 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index c1b637e64b515..b6f63b60d66ba 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -12,6 +12,7 @@ from pandas.compat.numpy import function as nv from pandas import compat + from pandas.core.dtypes.generic import ABCSeries, ABCMultiIndex, ABCPeriodIndex from pandas.core.dtypes.missing import isnull, array_equivalent from pandas.core.dtypes.common import ( @@ -52,6 +53,7 @@ from pandas.core.strings import StringAccessorMixin from pandas.core.config import get_option + # simplify default_pprint = lambda x, max_seq_items=None: \ pprint_thing(x, escape_chars=('\t', '\r', '\n'), quote_strings=True, @@ -180,8 +182,8 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None, elif isinstance(data, (np.ndarray, Index, ABCSeries)): if (is_datetime64_any_dtype(data) or - (dtype is not None and is_datetime64_any_dtype(dtype)) or - 'tz' in kwargs): + (dtype is not None and is_datetime64_any_dtype(dtype)) or + 'tz' in kwargs): from pandas.core.indexes.datetimes import DatetimeIndex result = DatetimeIndex(data, copy=copy, name=name, dtype=dtype, **kwargs) @@ -191,7 +193,7 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None, return result elif (is_timedelta64_dtype(data) or - (dtype is not None and is_timedelta64_dtype(dtype))): + (dtype is not None and is_timedelta64_dtype(dtype))): from pandas.core.indexes.timedeltas import TimedeltaIndex result = TimedeltaIndex(data, copy=copy, name=name, **kwargs) if dtype is not None and _o_dtype == dtype: @@ -295,7 +297,7 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None, elif inferred != 'string': if inferred.startswith('datetime'): if (lib.is_datetime_with_singletz_array(subarr) or - 'tz' in kwargs): + 'tz' in kwargs): # only when subarr has the same tz from pandas.core.indexes.datetimes import ( DatetimeIndex) @@ -901,7 +903,7 @@ def best_len(values): # line, then don't justify if (is_truncated or not (len(', '.join(head)) < display_width and - len(', '.join(tail)) < display_width)): + len(', '.join(tail)) < display_width)): max_len = max(best_len(head), best_len(tail)) head = [x.rjust(max_len) for x in head] tail = [x.rjust(max_len) for x in tail] @@ -1051,7 +1053,7 @@ def nlevels(self): return 1 def _get_names(self): - return FrozenList((self.name,)) + return FrozenList((self.name, )) def _set_names(self, values, level=None): if len(values) != 1: @@ -1255,7 +1257,7 @@ def _convert_scalar_indexer(self, key, kind=None): if kind == 'iloc': return self._validate_indexer('positional', key, kind) - if len(self) and not isinstance(self, ABCMultiIndex, ): + if len(self) and not isinstance(self, ABCMultiIndex,): # we can raise here if we are definitive that this # is positional indexing (eg. .ix on with a float) @@ -1463,8 +1465,8 @@ def _invalid_indexer(self, form, key): """ consistent invalid indexer message """ raise TypeError("cannot do {form} indexing on {klass} with these " "indexers [{key}] of {kind}".format( - form=form, klass=type(self), key=key, - kind=type(key))) + form=form, klass=type(self), key=key, + kind=type(key))) def get_duplicates(self): from collections import defaultdict @@ -1498,7 +1500,7 @@ def _validate_index_level(self, level): if isinstance(level, int): if level < 0 and level != -1: raise IndexError("Too many levels: Index has only 1 level," - " %d is not a valid level number" % (level,)) + " %d is not a valid level number" % (level, )) elif level > 0: raise IndexError("Too many levels:" " Index has only 1 level, not %d" % @@ -2318,15 +2320,16 @@ def symmetric_difference(self, other, result_name=None): except TypeError: pass + # On equal MultiIndexes the difference is empty. Therefore an empty + # MultiIndex is returned GH13490 if self.nlevels > 1 and len(the_diff) == 0: return type(self)([[] for _ in range(self.nlevels)], [[] for _ in range(self.nlevels)]) - else: - attribs = self._get_attributes_dict() - attribs['name'] = result_name - if 'freq' in attribs: - attribs['freq'] = None - return self._shallow_copy_with_infer(the_diff, **attribs) + attribs = self._get_attributes_dict() + attribs['name'] = result_name + if 'freq' in attribs: + attribs['freq'] = None + return self._shallow_copy_with_infer(the_diff, **attribs) sym_diff = deprecate('sym_diff', symmetric_difference) @@ -3371,7 +3374,7 @@ def _maybe_cast_slice_bound(self, label, side, kind): # reject them if is_float(label): if not (kind in ['ix'] and (self.holds_integer() or - self.is_floating())): + self.is_floating())): self._invalid_indexer('slice', label) # we are trying to find integer bounds on a non-integer based index @@ -3389,7 +3392,7 @@ def _searchsorted_monotonic(self, label, side='left'): # everything for it to work (element ordering, search side and # resulting value). pos = self[::-1].searchsorted(label, side='right' if side == 'left' - else 'right') + else 'right') return len(self) - pos raise ValueError('index must be monotonic increasing or decreasing') @@ -3420,7 +3423,7 @@ def get_slice_bound(self, label, side, kind): if side not in ('left', 'right'): raise ValueError("Invalid value for side kwarg," " must be either 'left' or 'right': %s" % - (side,)) + (side, )) original_label = label @@ -3667,7 +3670,7 @@ def _evaluate_compare(self, other): return self._evaluate_compare(other, op) if (is_object_dtype(self) and - self.nlevels == 1): + self.nlevels == 1): # don't pass MultiIndex with np.errstate(all='ignore'): @@ -3743,9 +3746,9 @@ def _validate_for_numeric_unaryop(self, op, opstr): if not self._is_numeric_dtype: raise TypeError("cannot evaluate a numeric op " "{opstr} for type: {typ}".format( - opstr=opstr, - typ=type(self)) - ) + opstr=opstr, + typ=type(self)) + ) def _validate_for_numeric_binop(self, other, op, opstr): """ @@ -3761,17 +3764,17 @@ def _validate_for_numeric_binop(self, other, op, opstr): if not self._is_numeric_dtype: raise TypeError("cannot evaluate a numeric op {opstr} " "for type: {typ}".format( - opstr=opstr, - typ=type(self)) - ) + opstr=opstr, + typ=type(self)) + ) if isinstance(other, Index): if not other._is_numeric_dtype: raise TypeError("cannot evaluate a numeric op " "{opstr} with type: {typ}".format( - opstr=type(self), - typ=type(other)) - ) + opstr=type(self), + typ=type(other)) + ) elif isinstance(other, np.ndarray) and not other.ndim: other = other.item() @@ -3868,7 +3871,9 @@ def _add_numeric_methods_unary(cls): """ add in numeric unary methods """ def _make_evaluate_unary(op, opstr): + def _evaluate_numeric_unary(self): + self._validate_for_numeric_unaryop(op, opstr) attrs = self._get_attributes_dict() attrs = self._maybe_update_attributes(attrs) @@ -3909,7 +3914,7 @@ def _make_logical_function(name, desc, f): def logical_func(self, *args, **kwargs): result = f(self.values) if (isinstance(result, (np.ndarray, ABCSeries, Index)) and - result.ndim == 0): + result.ndim == 0): # return NumPy type return result.dtype.type(result.item()) else: # pragma: no cover diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 934281c428f0f..8daec8b533de3 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -907,13 +907,6 @@ def test_symmetric_difference(self): expected = MultiIndex.from_tuples([('bar', 2), ('baz', 3), ('bar', 3)]) assert tm.equalContents(result, expected) - # on equal multiIndexs - idx1 = MultiIndex.from_tuples(self.tuples) - idx2 = MultiIndex.from_tuples(self.tuples) - result = idx1.symmetric_difference(idx2) - expected = MultiIndex(levels=[[], []], labels=[[], []]) - assert tm.equalContents(result, expected) - # nans: # GH 13514 change: {nan} - {nan} == {} # (GH 6444, sorting of nans, is no longer an issue) @@ -941,6 +934,14 @@ def test_symmetric_difference(self): assert tm.equalContents(result, expected) assert result.name == 'new_name' + def test_symmetric_difference_on_equal_multiindex(self): + # GH13490 + idx1 = MultiIndex.from_tuples(self.tuples) + idx2 = MultiIndex.from_tuples(self.tuples) + result = idx1.symmetric_difference(idx2) + expected = MultiIndex(levels=[[], []], labels=[[], []]) + assert tm.equalContents(result, expected) + def test_is_numeric(self): assert not self.dateIndex.is_numeric() assert not self.strIndex.is_numeric() From efb5f86c207c048563ecf147ad84f9da2d81d728 Mon Sep 17 00:00:00 2001 From: Christian Stade-Schuldt Date: Wed, 24 May 2017 16:49:39 -0700 Subject: [PATCH 3/7] update whatsnew --- doc/source/whatsnew/v0.20.2.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v0.20.2.txt b/doc/source/whatsnew/v0.20.2.txt index 6c9728191f5b6..edae4cc3e9193 100644 --- a/doc/source/whatsnew/v0.20.2.txt +++ b/doc/source/whatsnew/v0.20.2.txt @@ -37,6 +37,7 @@ Bug Fixes ~~~~~~~~~ - Bug in using ``pathlib.Path`` or ``py.path.local`` objects with io functions (:issue:`16291`) +- Bug in ``Index`` calling symmetric_difference() on two equal multiindices results in a TypeError (:issue `13490`) Conversion ^^^^^^^^^^ From 104d8e689ad73b92cd0934b3af95d9795f12b35d Mon Sep 17 00:00:00 2001 From: Christian Stade-Schuldt Date: Wed, 31 May 2017 00:15:19 +0200 Subject: [PATCH 4/7] move empty MultiIndex creation to MultiIndex._shallow_copy --- pandas/core/indexes/base.py | 5 ----- pandas/core/indexes/multi.py | 5 +++++ pandas/tests/indexing/test_multiindex.py | 11 +++++++++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 3016fdc15c8cf..2af4f112ca941 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -2320,11 +2320,6 @@ def symmetric_difference(self, other, result_name=None): except TypeError: pass - # On equal MultiIndexes the difference is empty. Therefore an empty - # MultiIndex is returned GH13490 - if self.nlevels > 1 and len(the_diff) == 0: - return type(self)([[] for _ in range(self.nlevels)], - [[] for _ in range(self.nlevels)])._shallow_copy() attribs = self._get_attributes_dict() attribs['name'] = result_name if 'freq' in attribs: diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 569e16f2141ae..d48917e520ec1 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -419,6 +419,11 @@ def _shallow_copy_with_infer(self, values=None, **kwargs): @Appender(_index_shared_docs['_shallow_copy']) def _shallow_copy(self, values=None, **kwargs): if values is not None: + # On equal MultiIndexes the difference is empty. + # Therefore, an empty MultiIndex is returned GH13490 + if len(values) == 0: + return MultiIndex(levels=[[] for _ in range(self.nlevels)], + labels=[[] for _ in range(self.nlevels)], **kwargs) if 'name' in kwargs: kwargs['names'] = kwargs.pop('name', None) # discards freq diff --git a/pandas/tests/indexing/test_multiindex.py b/pandas/tests/indexing/test_multiindex.py index 483c39ed8694e..fc6c627075c96 100644 --- a/pandas/tests/indexing/test_multiindex.py +++ b/pandas/tests/indexing/test_multiindex.py @@ -697,6 +697,17 @@ def test_multiindex_slice_first_level(self): index=range(30, 71)) tm.assert_frame_equal(result, expected) + def test_multiindex_symmetric_difference(self): + # GH 13490 + idx = MultiIndex.from_product([['a', 'b'], ['A', 'B']], + names=['a', 'b']) + result = idx ^ idx + assert result.names == idx.names + + idx2 = idx.copy().rename(['A', 'B']) + result = idx ^ idx2 + assert result.names == [None, None] + class TestMultiIndexSlicers(object): From 85b4b5510499fb5bdd2abfc3ec70eecf6618dd7e Mon Sep 17 00:00:00 2001 From: Christian Stade-Schuldt Date: Wed, 31 May 2017 01:02:10 +0200 Subject: [PATCH 5/7] remove old test --- pandas/tests/indexes/test_base.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index d84d95c34a37e..02561cba784b8 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -934,14 +934,6 @@ def test_symmetric_difference(self): assert tm.equalContents(result, expected) assert result.name == 'new_name' - def test_symmetric_difference_on_equal_multiindex(self): - # GH13490 - idx1 = MultiIndex.from_tuples(self.tuples) - idx2 = MultiIndex.from_tuples(self.tuples) - result = idx1.symmetric_difference(idx2) - expected = MultiIndex(levels=[[], []], labels=[[], []]) - assert tm.assert_index_equal(result, expected) - def test_is_numeric(self): assert not self.dateIndex.is_numeric() assert not self.strIndex.is_numeric() From f2d24a3be96826f347aeb561e86bb21db6b58be2 Mon Sep 17 00:00:00 2001 From: Christian Stade-Schuldt Date: Wed, 31 May 2017 01:09:21 +0200 Subject: [PATCH 6/7] move block to _shallow_copy_with_infer --- doc/source/whatsnew/v0.20.2.txt | 2 +- pandas/core/indexes/multi.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/source/whatsnew/v0.20.2.txt b/doc/source/whatsnew/v0.20.2.txt index 98b4505a4d768..5849eff487e2e 100644 --- a/doc/source/whatsnew/v0.20.2.txt +++ b/doc/source/whatsnew/v0.20.2.txt @@ -40,7 +40,7 @@ Bug Fixes - Silenced a warning on some Windows environments about "tput: terminal attributes: No such device or address" when detecting the terminal size. This fix only applies to python 3 (:issue:`16496`) - Bug in using ``pathlib.Path`` or ``py.path.local`` objects with io functions (:issue:`16291`) -- Bug in ``Index`` calling symmetric_difference() on two equal multiindices results in a TypeError (:issue `13490`) +- Bug in ``Index.symmetric_difference()`` on two equal MultiIndex's, results in a TypeError (:issue `13490`) - Bug in ``DataFrame.update()`` with ``overwrite=False`` and ``NaN values`` (:issue:`15593`) diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index d48917e520ec1..772974ce1fc72 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -414,16 +414,16 @@ def view(self, cls=None): return result def _shallow_copy_with_infer(self, values=None, **kwargs): + # On equal MultiIndexes the difference is empty. + # Therefore, an empty MultiIndex is returned GH13490 + if len(values) == 0: + return MultiIndex(levels=[[] for _ in range(self.nlevels)], + labels=[[] for _ in range(self.nlevels)], **kwargs) return self._shallow_copy(values, **kwargs) @Appender(_index_shared_docs['_shallow_copy']) def _shallow_copy(self, values=None, **kwargs): if values is not None: - # On equal MultiIndexes the difference is empty. - # Therefore, an empty MultiIndex is returned GH13490 - if len(values) == 0: - return MultiIndex(levels=[[] for _ in range(self.nlevels)], - labels=[[] for _ in range(self.nlevels)], **kwargs) if 'name' in kwargs: kwargs['names'] = kwargs.pop('name', None) # discards freq From 4cd39a906f3c65fc7839dc5ac7d40045c63b580c Mon Sep 17 00:00:00 2001 From: Christian Stade-Schuldt Date: Wed, 31 May 2017 08:41:29 +0200 Subject: [PATCH 7/7] PEP8 compliancy --- pandas/core/indexes/multi.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 772974ce1fc72..981a6a696a618 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -418,7 +418,8 @@ def _shallow_copy_with_infer(self, values=None, **kwargs): # Therefore, an empty MultiIndex is returned GH13490 if len(values) == 0: return MultiIndex(levels=[[] for _ in range(self.nlevels)], - labels=[[] for _ in range(self.nlevels)], **kwargs) + labels=[[] for _ in range(self.nlevels)], + **kwargs) return self._shallow_copy(values, **kwargs) @Appender(_index_shared_docs['_shallow_copy'])