Repository: pandas-dev/pandas

Issue:
BUG: DataFrame.to_excel(index=False) misplaces data/columns in MultiIndex case
Example:

``` python
>>> df = pd.DataFrame({'a': [10, 20], 'b': [30, 40], 'c': [50, 60]}, 
...                   index=pd.MultiIndex.from_tuples([(70, 80), (90, 100)]))
>>> writer = pd.ExcelWriter('test.xls')
>>> df.to_excel(writer, sheet_name='1', index=False)
>>> writer.save()
>>> pd.read_excel('test.xls', '1')
        a   b   c
10 30  50 NaN NaN
20 40  60 NaN NaN
```

The column names are obviously misplaced (erroneously shifted to the right by the number of levels in the MultiIndex). Or is it the data that is misplaced? Wonder why no one noticed this before.



Discussion:
Thanks for the bug report. Can you post what version of pandas and xlwt you
are using?
On Nov 29, 2013 11:50 AM, "Ivan Smirnov" notifications@github.com wrote:

> Example:
> 
> > > > df = pd.DataFrame({'a': [1, 2], 'b': [3, 4], 'c': [5, 6]}, ...                   index=pd.MultiIndex.from_tuples([(7, 8), (9, 10)]))>>> writer = pd.ExcelWriter('test.xls')>>> df.to_excel(writer, sheet_name='1', index=False)>>> writer.save()>>> pd.read_excel('test.xls', '1')
> > > >      a   b   c1 3  5 NaN NaN2 4  6 NaN NaN
> 
> The column names are obviously misplaced (erroneously shifted to the right
> by the number of levels in the MultiIndex). Or is it the data that is
> misplaced?
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/pydata/pandas/issues/5616
> .

@jtratner it's `xlwt==0.7.5` and pandas is fetched from git head about a week ago (btw it registers as `pandas==0.0.0` on my machine since it's behind a corporate proxy/firewall and git fails to retrieve a proper version... oh well, guess that's another bug).

Can you run `git describe` in the pandas directory and post what you see?
Doesn't really make sense to me that it comes out as 0.0.0 - proxy
shouldn't have anything to do with it, but not a big deal.

Regardless, definitely a bug that needs to be fixed, but I'm guessing it
won't be that difficult.

@jtratner Well, that's the thing - I cannot access git due to SSL restrictions, proxies etc on a remote box at work, hence I had to download a zip of the master branch. Obviously then, `git describe` returns a fatal error since it's not a git repo in the first place, and pandas ended up installing itself to `/site-packages/pandas-0.0.0-py2.7-linux-x86_64.egg/pandas`. Bottom line is, is pandas (dev) versioning supposed to be so tightly coupled to git?

it's supposed to fall back on using the hard-coded major/minor version.
 Anyways, not a big deal, going to take a look when I can.

On Mon, Dec 2, 2013 at 12:09 PM, Ivan Smirnov notifications@github.comwrote:

> @jtratner https://github.com/jtratner Well, that's the thing - I cannot
> access git due to SSL restrictions, proxies etc on a remote box at work,
> hence I had to download a zip of the master branch. Obviously then, git
> describe returns a fatal error since it's not a git repo in the first
> place, and pandas ended up installing itself to
> /site-packages/pandas-0.0.0-py2.7-linux-x86_64.egg/pandas. Bottom line
> is, is pandas versioning supposed to be so tightly coupled to git?
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/pydata/pandas/issues/5616#issuecomment-29636624
> .

Adding a comment to track this issue.


Resolving patch:
diff --git a/pandas/core/format.py b/pandas/core/format.py
--- a/pandas/core/format.py
+++ b/pandas/core/format.py
@@ -1373,7 +1373,7 @@ def _format_header_mi(self):
         coloffset = 0
         lnum = 0
 
-        if isinstance(self.df.index, MultiIndex):
+        if self.index and isinstance(self.df.index, MultiIndex):
             coloffset = len(self.df.index[0]) - 1
 
         if self.merge_cells:
@@ -1412,10 +1412,11 @@ def _format_header_regular(self):
         has_aliases = isinstance(self.header, (tuple, list, np.ndarray))
         if has_aliases or self.header:
             coloffset = 0
+
             if self.index:
                 coloffset = 1
-            if isinstance(self.df.index, MultiIndex):
-                coloffset = len(self.df.index[0])
+                if isinstance(self.df.index, MultiIndex):
+                    coloffset = len(self.df.index[0])
 
             colnames = self.columns
             if has_aliases:


Test patch:
diff --git a/pandas/io/tests/test_excel.py b/pandas/io/tests/test_excel.py
--- a/pandas/io/tests/test_excel.py
+++ b/pandas/io/tests/test_excel.py
@@ -615,7 +615,7 @@ def test_roundtrip_indexlabels(self):
                                   has_index_names=self.merge_cells
                                   ).astype(np.int64)
             frame.index.names = ['test']
-            tm.assert_frame_equal(frame,recons.astype(bool))
+            tm.assert_frame_equal(frame, recons.astype(bool))
 
         with ensure_clean(self.ext) as path:
 
@@ -715,6 +715,31 @@ def test_to_excel_multiindex_dates(self):
             tm.assert_frame_equal(tsframe, recons)
             self.assertEquals(recons.index.names, ('time', 'foo'))
 
+    def test_to_excel_multiindex_no_write_index(self):
+        _skip_if_no_xlrd()
+
+        # Test writing and re-reading a MI witout the index. GH 5616.
+
+        # Initial non-MI frame.
+        frame1 = pd.DataFrame({'a': [10, 20], 'b': [30, 40], 'c': [50, 60]})
+
+        # Add a MI.
+        frame2 = frame1.copy()
+        multi_index = pd.MultiIndex.from_tuples([(70, 80), (90, 100)])
+        frame2.index = multi_index
+
+        with ensure_clean(self.ext) as path:
+
+            # Write out to Excel without the index.
+            frame2.to_excel(path, 'test1', index=False)
+
+            # Read it back in.
+            reader = ExcelFile(path)
+            frame3 = reader.parse('test1')
+
+            # Test that it is the same as the initial frame.
+            tm.assert_frame_equal(frame1, frame3)
+
     def test_to_excel_float_format(self):
         _skip_if_no_xlrd()