Skip to content

Commit 92db032

Browse files
merge master
1 parent ae1ab89 commit 92db032

File tree

4 files changed

+110
-32
lines changed

4 files changed

+110
-32
lines changed

pandas/core/generic.py

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

pandas/io/formats/csvs.py

+63-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,39 @@ def save(self):
188198
for _fh in handles:
189199
_fh.close()
190200

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

193236
writer = self.writer
@@ -200,8 +243,16 @@ def _save_header(self):
200243

201244
has_aliases = isinstance(header, (tuple, list, np.ndarray,
202245
ABCIndexClass))
203-
if not (has_aliases or self.header):
204-
return
246+
if not (has_aliases or header):
247+
# if index_label is False, nothing will display.
248+
if index_label is False:
249+
return
250+
else:
251+
# based on index_label value, encoded labels are given
252+
index_label, encoded_labels = self._index_label_encoder()
253+
encoded_labels.extend([''] * len(obj.columns))
254+
writer.writerow(encoded_labels)
255+
return
205256
if has_aliases:
206257
if len(header) != len(cols):
207258
raise ValueError(('Writing {ncols} cols but got {nalias} '
@@ -215,27 +266,16 @@ def _save_header(self):
215266
if self.index:
216267
# should write something for index label
217268
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)
269+
index_label, encoded_labels = self._index_label_encoder()
237270
else:
238-
encoded_labels = []
271+
# if index is multiindex, multiple empty labels are provided
272+
if isinstance(obj.index, ABCMultiIndex):
273+
index_label = []
274+
index_label.extend([''] * len(obj.index.names))
275+
# if index is single index, list of empty string is provided
276+
else:
277+
index_label = ['']
278+
encoded_labels = list(index_label)
239279

240280
if not has_mi_columns or has_aliases:
241281
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

+38
Original file line numberDiff line numberDiff line change
@@ -561,3 +561,41 @@ 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+
])
573+
def test_to_csv_header_single_index(self, header, index_label,
574+
expected_rows):
575+
# issue 24546
576+
df = pd.DataFrame(np.zeros((2, 2), dtype=int))
577+
df.index.name = 'index.name'
578+
df.columns.name = 'columns.name'
579+
580+
result = df.to_csv(header=header, index_label=index_label)
581+
expected = tm.convert_rows_list_to_csv_str(expected_rows)
582+
assert result == expected
583+
584+
@pytest.mark.parametrize("header, index_label, expected_rows", [
585+
(False, True, ['index.name.0,index.name.1,,', 'a,b,0,0', 'a,c,0,0']),
586+
(True, True, ['index.name.0,index.name.1,0,1', 'a,b,0,0', 'a,c,0,0']),
587+
(False, False, ['a,b,0,0', 'a,c,0,0']),
588+
(True, False, [',,0,1', 'a,b,0,0', 'a,c,0,0']),
589+
(False, None, ['a,b,0,0', 'a,c,0,0']),
590+
(True, None, ['index.name.0,index.name.1,0,1', 'a,b,0,0', 'a,c,0,0'])
591+
])
592+
def test_to_csv_header_multi_index(self, header, index_label,
593+
expected_rows):
594+
# issue 24546
595+
df = pd.DataFrame(np.zeros((2, 2), dtype=int))
596+
df.index = pd.MultiIndex.from_product([['a'], ['b', 'c']], names=[
597+
'index.name.0', 'index.name.1'])
598+
599+
result = df.to_csv(header=header, index_label=index_label)
600+
expected = tm.convert_rows_list_to_csv_str(expected_rows)
601+
assert result == expected

0 commit comments

Comments
 (0)