Repository: pandas-dev/pandas

Issue:
Symmetric difference on equal MultiIndexes raises TypeError
Calling `symmetric_difference` on two equal multiindices results in a TypeError rather than an empty MultiIndex. This is surprising since calling `difference` on the same multiindices results in the expected empty MultiIndex.
#### Code Sample, a copy-pastable example if possible

```
a = pandas.MultiIndex.from_product([['a', 'b'], [0, 1]])
b = pandas.MultiIndex.from_product([['a', 'b'], [0, 1]])
print(a.symmetric_difference(b))
```

Which gives the following error:

```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Miniconda3\envs\balancedbeta\lib\site-packages\pandas\indexes\base.py", line 1674, in __xor__
    return self.symmetric_difference(other)
  File "C:\Miniconda3\envs\balancedbeta\lib\site-packages\pandas\indexes\base.py", line 1911, in symmetric_difference
    return self._shallow_copy_with_infer(the_diff, **attribs)
  File "C:\Miniconda3\envs\balancedbeta\lib\site-packages\pandas\indexes\multi.py", line 387, in _shallow_copy_with_infer
    return self._shallow_copy(values, **kwargs)
  File "C:\Miniconda3\envs\balancedbeta\lib\site-packages\pandas\indexes\multi.py", line 396, in _shallow_copy
    return MultiIndex.from_tuples(values, **kwargs)
  File "C:\Miniconda3\envs\balancedbeta\lib\site-packages\pandas\indexes\multi.py", line 883, in from_tuples
    raise TypeError('Cannot infer number of levels from empty list')
TypeError: Cannot infer number of levels from empty list
```
#### Expected Output

```
MultiIndex(levels=[[], []], labels = [[], []])
```
#### output of `pd.show_versions()`

```
INSTALLED VERSIONS                                                          
------------------                                                          
commit: None                                                                
python: 2.7.11.final.0                                                      
python-bits: 64                                                             
OS: Windows                                                                 
OS-release: 7                                                               
machine: AMD64                                                              
processor: Intel64 Family 6 Model 61 Stepping 4, GenuineIntel               
byteorder: little                                                           
LC_ALL: None                                                                
LANG: DA                                                                    

pandas: 0.18.1                                                              
nose: None                                                                  
pip: 8.1.1                                                                  
setuptools: 20.7.0                                                          
Cython: 0.23                                                                
numpy: 1.10.4                                                               
scipy: 0.17.0                                                               
statsmodels: 0.6.1                                                          
xarray: None                                                                
IPython: 4.2.0                                                              
sphinx: None                                                                
patsy: 0.4.1                                                                
dateutil: 2.5.2                                                             
pytz: 2016.3                                                                
blosc: None                                                                 
bottleneck: None                                                            
tables: 3.2.2                                                               
numexpr: 2.5.2                                                              
matplotlib: 1.5.1                                                           
openpyxl: None                                                              
xlrd: None                                                                  
xlwt: None                                                                  
xlsxwriter: None                                                            
lxml: None                                                                  
bs4: None                                                                   
html5lib: None                                                              
httplib2: None                                                              
apiclient: None                                                             
sqlalchemy: 1.0.12                                                          
pymysql: None                                                               
psycopg2: None                                                              
jinja2: 2.8                                                                 
boto: None                                                                  
pandas_datareader: None                                                     
```



Discussion:
yeah I agree, on empties this should behave the same as `.difference` / `.union`

cc @TomAugspurger 

Agreed.


Resolving patch:
diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py
--- a/pandas/core/indexes/multi.py
+++ b/pandas/core/indexes/multi.py
@@ -414,6 +414,12 @@ 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'])


Test patch:
diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py
--- 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)
 
diff --git a/pandas/tests/indexing/test_multiindex.py b/pandas/tests/indexing/test_multiindex.py
--- 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):