From bdc20d4c55807387bf10ff321936b4f8b87c0a6f Mon Sep 17 00:00:00 2001 From: gfyoung Date: Wed, 31 Oct 2018 07:47:57 +0000 Subject: [PATCH] BUG: Fix rendering of 1-level MultiIndex in to_csv Display the level by itself instead of as a tuple (i.e. "squash" the MultiIndex into a single level) Closes gh-19589. --- doc/source/whatsnew/v0.24.0.txt | 3 ++- pandas/core/indexes/multi.py | 13 ++++++++----- pandas/tests/io/formats/test_to_csv.py | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index de111072bef02..7a5d455ff853f 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -188,7 +188,7 @@ Renaming names in a MultiIndex :func:`DataFrame.rename_axis` now supports ``index`` and ``columns`` arguments and :func:`Series.rename_axis` supports ``index`` argument (:issue:`19978`) -This change allows a dictionary to be passed so that some of the names +This change allows a dictionary to be passed so that some of the names of a ``MultiIndex`` can be changed. Example: @@ -1216,6 +1216,7 @@ Notice how we now instead output ``np.nan`` itself instead of a stringified form - :func:`read_sas()` will correctly parse sas7bdat files with data page types having also bit 7 set (so page type is 128 + 256 = 384) (:issue:`16615`) - Bug in :meth:`detect_client_encoding` where potential ``IOError`` goes unhandled when importing in a mod_wsgi process due to restricted access to stdout. (:issue:`21552`) - Bug in :func:`to_string()` that broke column alignment when ``index=False`` and width of first column's values is greater than the width of first column's header (:issue:`16839`, :issue:`13032`) +- Bug in :func:`DataFrame.to_csv` where a single level MultiIndex incorrectly wrote a tuple. Now just the value of the index is written (:issue:`19589`). Plotting ^^^^^^^^ diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 24e7d9bebae3e..c694289efc493 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -732,11 +732,14 @@ def _format_native_types(self, na_rep='nan', **kwargs): new_levels.append(level) new_labels.append(label) - # reconstruct the multi-index - mi = MultiIndex(levels=new_levels, labels=new_labels, names=self.names, - sortorder=self.sortorder, verify_integrity=False) - - return mi.values + if len(new_levels) == 1: + return Index(new_levels[0])._format_native_types() + else: + # reconstruct the multi-index + mi = MultiIndex(levels=new_levels, labels=new_labels, + names=self.names, sortorder=self.sortorder, + verify_integrity=False) + return mi.values @Appender(_index_shared_docs['_get_grouper_for_level']) def _get_grouper_for_level(self, mapper, level): diff --git a/pandas/tests/io/formats/test_to_csv.py b/pandas/tests/io/formats/test_to_csv.py index 7042cae526207..228373a7bf545 100644 --- a/pandas/tests/io/formats/test_to_csv.py +++ b/pandas/tests/io/formats/test_to_csv.py @@ -325,6 +325,25 @@ def test_to_csv_multi_index(self): exp = tm.convert_rows_list_to_csv_str(exp_rows) assert df.to_csv(index=False) == exp + @pytest.mark.parametrize("ind,expected", [ + (pd.MultiIndex(levels=[[1.0]], + labels=[[0]], + names=["x"]), + "x,data\n1.0,1\n"), + (pd.MultiIndex(levels=[[1.], [2.]], + labels=[[0], [0]], + names=["x", "y"]), + "x,y,data\n1.0,2.0,1\n") + ]) + @pytest.mark.parametrize("klass", [ + pd.DataFrame, pd.Series + ]) + def test_to_csv_single_level_multi_index(self, ind, expected, klass): + # see gh-19589 + result = klass(pd.Series([1], ind, name="data")).to_csv( + line_terminator="\n", header=True) + assert result == expected + def test_to_csv_string_array_ascii(self): # GH 10813 str_array = [{'names': ['foo', 'bar']}, {'names': ['baz', 'qux']}]