Skip to content

Commit de27eef

Browse files
committed
ENH: to_csv write multi-index columns similar to how they are displayed in to_string
1 parent 8eaf19a commit de27eef

File tree

2 files changed

+46
-5
lines changed

2 files changed

+46
-5
lines changed

pandas/core/format.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -959,9 +959,12 @@ def _save_header(self):
959959
index_label = self.index_label
960960
cols = self.cols
961961
header = self.header
962+
has_mi_columns = isinstance(obj.columns, MultiIndex)
963+
encoded_labels = []
962964

963965
has_aliases = isinstance(header, (tuple, list, np.ndarray))
964966
if has_aliases or self.header:
967+
965968
if self.index:
966969
# should write something for index label
967970
if index_label is not False:
@@ -994,12 +997,40 @@ def _save_header(self):
994997
write_cols = header
995998
else:
996999
write_cols = cols
997-
encoded_cols = list(write_cols)
9981000

999-
writer.writerow(encoded_labels + encoded_cols)
1001+
if not has_mi_columns:
1002+
encoded_labels += list(write_cols)
1003+
10001004
else:
1001-
encoded_cols = list(cols)
1002-
writer.writerow(encoded_cols)
1005+
1006+
if not has_mi_columns:
1007+
encoded_labels += list(cols)
1008+
1009+
# write out the mi
1010+
if has_mi_columns:
1011+
columns = obj.columns
1012+
1013+
# write out the names for each level, then ALL of the values for each level
1014+
for i in range(columns.nlevels):
1015+
1016+
# name is the first column
1017+
col_line = [ columns.names[i] ]
1018+
1019+
# skipp len labels-1
1020+
if self.index and isinstance(index_label,list) and len(index_label)>1:
1021+
col_line.extend([ '' ] * (len(index_label)-1))
1022+
1023+
for j in range(len(columns)):
1024+
col_line.append(columns.levels[i][j])
1025+
1026+
writer.writerow(col_line)
1027+
1028+
# add blanks for the columns, so that we
1029+
# have consistent seps
1030+
encoded_labels.extend([ '' ] * len(columns))
1031+
1032+
# write out the index label line
1033+
writer.writerow(encoded_labels)
10031034

10041035
def _save(self):
10051036

pandas/tests/test_frame.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4962,6 +4962,7 @@ def test_to_csv_multiindex(self):
49624962
frame.index = new_index
49634963

49644964
with ensure_clean(pname) as path:
4965+
49654966
frame.to_csv(path, header=False)
49664967
frame.to_csv(path, cols=['A', 'B'])
49674968

@@ -4973,7 +4974,7 @@ def test_to_csv_multiindex(self):
49734974
self.assertEqual(frame.index.names, df.index.names)
49744975
self.frame.index = old_index # needed if setUP becomes a classmethod
49754976

4976-
# try multiindex with dates
4977+
# try multiindex with dates
49774978
tsframe = self.tsframe
49784979
old_index = tsframe.index
49794980
new_index = [old_index, np.arange(len(old_index))]
@@ -4994,6 +4995,15 @@ def test_to_csv_multiindex(self):
49944995
assert_almost_equal(recons.values, self.tsframe.values)
49954996
self.tsframe.index = old_index # needed if setUP becomes classmethod
49964997

4998+
with ensure_clean(pname) as path:
4999+
# column & index are mi
5000+
import pdb; pdb.set_trace()
5001+
df = mkdf(5,3,r_idx_nlevels=2,c_idx_nlevels=4)
5002+
df.to_csv(path)
5003+
5004+
result = pd.read_csv(path,header=[0,1,2,3],index_col=[0,1])
5005+
5006+
49975007
with ensure_clean(pname) as path:
49985008
# empty
49995009
tsframe[:0].to_csv(path)

0 commit comments

Comments
 (0)