Skip to content

Commit 1d5237e

Browse files
merge master
1 parent 42b4c97 commit 1d5237e

File tree

4 files changed

+119
-32
lines changed

4 files changed

+119
-32
lines changed

pandas/core/generic.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -2922,12 +2922,12 @@ def to_csv(self, path_or_buf=None, sep=",", na_rep='', float_format=None,
29222922
29232923
index : bool, default True
29242924
Write row names (index).
2925-
index_label : str or sequence, or False, default None
2926-
Column label for index column(s) if desired. If None is given, and
2927-
`header` and `index` are True, then the index names are used. A
2928-
sequence should be given if the object uses MultiIndex. If
2929-
False do not print fields for index names. Use index_label=False
2930-
for easier importing in R.
2925+
index_label : bool or str or sequence, default None
2926+
If index_label is not explicitly called, False if either header
2927+
or index is set to False; otherwise, True. If index_label is
2928+
explicitly called by allowed types of input, then input will be
2929+
given to index_label. If False, do not print fields
2930+
for index names. Use index_label=False for easier importing in R.
29312931
mode : str
29322932
Python write mode, default 'w'.
29332933
encoding : str, optional

pandas/io/formats/csvs.py

+66-23
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,17 @@ def __init__(self, obj, path_or_buf=None, sep=",", na_rep='',
5050

5151
self.header = header
5252
self.index = index
53-
self.index_label = index_label
53+
# if index label is not explicitly called, index label is True if
54+
# header or index is not False; otherwise, index label is set to False
55+
if index_label is None:
56+
if self.header is False or self.header is None or not self.index:
57+
self.index_label = False
58+
else:
59+
self.index_label = True
60+
else:
61+
# if index label is explicitly called, then use the caller.
62+
self.index_label = index_label
63+
5464
self.mode = mode
5565
if encoding is None:
5666
encoding = 'ascii' if compat.PY2 else 'utf-8'
@@ -188,6 +198,42 @@ def save(self):
188198
for _fh in handles:
189199
_fh.close()
190200

201+
def _index_label_encoder(self):
202+
"""
203+
Encode index label if it is not False.
204+
205+
Returns
206+
-------
207+
index_label: list
208+
New index_label given index types
209+
encode_labels: list
210+
List of index labels
211+
"""
212+
index_label = self.index_label
213+
obj = self.obj
214+
215+
if index_label is True:
216+
index_label = []
217+
# append index label based on index type
218+
if isinstance(obj.index, ABCMultiIndex):
219+
for name in obj.index.names:
220+
# add empty string is name is None
221+
if name is None:
222+
name = ''
223+
index_label.append(name)
224+
else:
225+
# if no name, use empty string
226+
if obj.index.name is None:
227+
index_label.append('')
228+
else:
229+
index_label.append(obj.index.name)
230+
elif not isinstance(index_label,
231+
(list, tuple, np.ndarray, ABCIndexClass)):
232+
index_label = [index_label]
233+
234+
encoded_labels = list(index_label)
235+
return index_label, encoded_labels
236+
191237
def _save_header(self):
192238

193239
writer = self.writer
@@ -200,8 +246,16 @@ def _save_header(self):
200246

201247
has_aliases = isinstance(header, (tuple, list, np.ndarray,
202248
ABCIndexClass))
203-
if not (has_aliases or self.header):
204-
return
249+
if not (has_aliases or header):
250+
# if index_label is False, nothing will display.
251+
if index_label is False:
252+
return
253+
else:
254+
# based on index_label value, encoded labels are given
255+
index_label, encoded_labels = self._index_label_encoder()
256+
encoded_labels.extend([''] * len(obj.columns))
257+
writer.writerow(encoded_labels)
258+
return
205259
if has_aliases:
206260
if len(header) != len(cols):
207261
raise ValueError(('Writing {ncols} cols but got {nalias} '
@@ -215,27 +269,16 @@ def _save_header(self):
215269
if self.index:
216270
# should write something for index label
217271
if index_label is not False:
218-
if index_label is None:
219-
if isinstance(obj.index, ABCMultiIndex):
220-
index_label = []
221-
for i, name in enumerate(obj.index.names):
222-
if name is None:
223-
name = ''
224-
index_label.append(name)
225-
else:
226-
index_label = obj.index.name
227-
if index_label is None:
228-
index_label = ['']
229-
else:
230-
index_label = [index_label]
231-
elif not isinstance(index_label,
232-
(list, tuple, np.ndarray, ABCIndexClass)):
233-
# given a string for a DF with Index
234-
index_label = [index_label]
235-
236-
encoded_labels = list(index_label)
272+
index_label, encoded_labels = self._index_label_encoder()
237273
else:
238-
encoded_labels = []
274+
# if index is multiindex, multiple empty labels are provided
275+
if isinstance(obj.index, ABCMultiIndex):
276+
index_label = []
277+
index_label.extend([''] * len(obj.index.names))
278+
# if index is single index, list of empty string is provided
279+
else:
280+
index_label = ['']
281+
encoded_labels = list(index_label)
239282

240283
if not has_mi_columns or has_aliases:
241284
encoded_labels += list(write_cols)

pandas/tests/frame/test_to_csv.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -861,14 +861,14 @@ def test_to_csv_quote_none(self):
861861
expected = tm.convert_rows_list_to_csv_str(expected_rows)
862862
assert result == expected
863863

864-
def test_to_csv_index_no_leading_comma(self):
864+
def test_to_csv_index_leading_comma(self):
865865
df = DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]},
866866
index=['one', 'two', 'three'])
867867

868868
buf = StringIO()
869-
df.to_csv(buf, index_label=False)
869+
df.to_csv(buf)
870870

871-
expected_rows = ['A,B',
871+
expected_rows = [',A,B',
872872
'one,1,4',
873873
'two,2,5',
874874
'three,3,6']

pandas/tests/io/formats/test_to_csv.py

+44
Original file line numberDiff line numberDiff line change
@@ -561,3 +561,47 @@ def test_to_csv_compression(self, compression_only,
561561
result = pd.read_csv(path, index_col=0,
562562
compression=read_compression)
563563
tm.assert_frame_equal(result, df)
564+
565+
@pytest.mark.parametrize("header, index_label, expected_rows", [
566+
(False, True, ['index.name,,', '0,0,0', '1,0,0']),
567+
(True, True, ['index.name,0,1', '0,0,0', '1,0,0']),
568+
(False, False, ['0,0,0', '1,0,0']),
569+
(True, False, [',0,1', '0,0,0', '1,0,0']),
570+
(False, None, ['0,0,0', '1,0,0']),
571+
(True, None, ['index.name,0,1', '0,0,0', '1,0,0']),
572+
(True, "new_index", ['new_index,0,1', '0,0,0', '1,0,0']),
573+
(True, ["new_index"], ['new_index,0,1', '0,0,0', '1,0,0'])
574+
])
575+
def test_to_csv_header_single_index(self, header, index_label,
576+
expected_rows):
577+
# issue 24546
578+
df = pd.DataFrame(np.zeros((2, 2), dtype=int))
579+
df.index.name = 'index.name'
580+
df.columns.name = 'columns.name'
581+
582+
result = df.to_csv(header=header, index_label=index_label)
583+
expected = tm.convert_rows_list_to_csv_str(expected_rows)
584+
assert result == expected
585+
586+
@pytest.mark.parametrize("header, index_label, expected_rows", [
587+
(False, True, ['index.name.0,index.name.1,,', 'a,b,0,0', 'a,c,0,0']),
588+
(True, True, ['index.name.0,index.name.1,0,1', 'a,b,0,0', 'a,c,0,0']),
589+
(False, False, ['a,b,0,0', 'a,c,0,0']),
590+
(True, False, [',,0,1', 'a,b,0,0', 'a,c,0,0']),
591+
(False, None, ['a,b,0,0', 'a,c,0,0']),
592+
(True, None, ['index.name.0,index.name.1,0,1', 'a,b,0,0', 'a,c,0,0']),
593+
(True, ("index1", "index2"),
594+
['index1,index2,0,1', 'a,b,0,0', 'a,c,0,0']),
595+
(True, ["index1", "index2"],
596+
['index1,index2,0,1', 'a,b,0,0', 'a,c,0,0'])
597+
])
598+
def test_to_csv_header_multi_index(self, header, index_label,
599+
expected_rows):
600+
# issue 24546
601+
df = pd.DataFrame(np.zeros((2, 2), dtype=int))
602+
df.index = pd.MultiIndex.from_product([['a'], ['b', 'c']], names=[
603+
'index.name.0', 'index.name.1'])
604+
605+
result = df.to_csv(header=header, index_label=index_label)
606+
expected = tm.convert_rows_list_to_csv_str(expected_rows)
607+
assert result == expected

0 commit comments

Comments
 (0)