Skip to content

Commit f4e368f

Browse files
EricCheaPingviinituutti
authored andcommitted
BUG: Fix rendering of 1-level MultiIndex in to_csv (pandas-dev#19831)
Display the level by itself instead of as a tuple (i.e. "squash" the MultiIndex into a single level) Closes pandas-devgh-19589.
1 parent 8b6e90d commit f4e368f

File tree

3 files changed

+29
-6
lines changed

3 files changed

+29
-6
lines changed

doc/source/whatsnew/v0.24.0.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ Renaming names in a MultiIndex
188188
:func:`DataFrame.rename_axis` now supports ``index`` and ``columns`` arguments
189189
and :func:`Series.rename_axis` supports ``index`` argument (:issue:`19978`)
190190

191-
This change allows a dictionary to be passed so that some of the names
191+
This change allows a dictionary to be passed so that some of the names
192192
of a ``MultiIndex`` can be changed.
193193

194194
Example:
@@ -1216,6 +1216,7 @@ Notice how we now instead output ``np.nan`` itself instead of a stringified form
12161216
- :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`)
12171217
- 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`)
12181218
- 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`)
1219+
- 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`).
12191220

12201221
Plotting
12211222
^^^^^^^^

pandas/core/indexes/multi.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -732,11 +732,14 @@ def _format_native_types(self, na_rep='nan', **kwargs):
732732
new_levels.append(level)
733733
new_labels.append(label)
734734

735-
# reconstruct the multi-index
736-
mi = MultiIndex(levels=new_levels, labels=new_labels, names=self.names,
737-
sortorder=self.sortorder, verify_integrity=False)
738-
739-
return mi.values
735+
if len(new_levels) == 1:
736+
return Index(new_levels[0])._format_native_types()
737+
else:
738+
# reconstruct the multi-index
739+
mi = MultiIndex(levels=new_levels, labels=new_labels,
740+
names=self.names, sortorder=self.sortorder,
741+
verify_integrity=False)
742+
return mi.values
740743

741744
@Appender(_index_shared_docs['_get_grouper_for_level'])
742745
def _get_grouper_for_level(self, mapper, level):

pandas/tests/io/formats/test_to_csv.py

+19
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,25 @@ def test_to_csv_multi_index(self):
325325
exp = tm.convert_rows_list_to_csv_str(exp_rows)
326326
assert df.to_csv(index=False) == exp
327327

328+
@pytest.mark.parametrize("ind,expected", [
329+
(pd.MultiIndex(levels=[[1.0]],
330+
labels=[[0]],
331+
names=["x"]),
332+
"x,data\n1.0,1\n"),
333+
(pd.MultiIndex(levels=[[1.], [2.]],
334+
labels=[[0], [0]],
335+
names=["x", "y"]),
336+
"x,y,data\n1.0,2.0,1\n")
337+
])
338+
@pytest.mark.parametrize("klass", [
339+
pd.DataFrame, pd.Series
340+
])
341+
def test_to_csv_single_level_multi_index(self, ind, expected, klass):
342+
# see gh-19589
343+
result = klass(pd.Series([1], ind, name="data")).to_csv(
344+
line_terminator="\n", header=True)
345+
assert result == expected
346+
328347
def test_to_csv_string_array_ascii(self):
329348
# GH 10813
330349
str_array = [{'names': ['foo', 'bar']}, {'names': ['baz', 'qux']}]

0 commit comments

Comments
 (0)