Skip to content

Commit 3ad0cfc

Browse files
simonjayhawkinsjreback
authored andcommitted
BUG: (row) Index Name with to_html(header=False) is not displayed (#24547)
1 parent 4395a8c commit 3ad0cfc

9 files changed

+232
-26
lines changed

doc/source/whatsnew/v0.24.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -1600,6 +1600,7 @@ Notice how we now instead output ``np.nan`` itself instead of a stringified form
16001600
- Bug in :func:`to_html()` with ``index=False`` misses truncation indicators (...) on truncated DataFrame (:issue:`15019`, :issue:`22783`)
16011601
- Bug in :func:`to_html()` with ``index=False`` when both columns and row index are ``MultiIndex`` (:issue:`22579`)
16021602
- Bug in :func:`to_html()` with ``index_names=False`` displaying index name (:issue:`22747`)
1603+
- Bug in :func:`to_html()` with ``header=False`` not displaying row index names (:issue:`23788`)
16031604
- Bug in :func:`DataFrame.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`)
16041605
- Bug in :func:`DataFrame.to_string()` that caused representations of :class:`DataFrame` to not take up the whole window (:issue:`22984`)
16051606
- 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`).

pandas/io/formats/html.py

+27-19
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ def __init__(self, formatter, classes=None, notebook=False, border=None,
4343
self.table_id = table_id
4444
self.render_links = render_links
4545

46+
@property
47+
def show_row_idx_names(self):
48+
return all((self.fmt.has_index_names,
49+
self.fmt.index,
50+
self.fmt.show_index_names))
51+
4652
@property
4753
def show_col_idx_names(self):
4854
# see gh-22579
@@ -165,9 +171,7 @@ def write_style(self):
165171
element_props.append(('thead tr th',
166172
'text-align',
167173
'left'))
168-
if all((self.fmt.has_index_names,
169-
self.fmt.index,
170-
self.fmt.show_index_names)):
174+
if self.show_row_idx_names:
171175
element_props.append(('thead tr:last-of-type th',
172176
'text-align',
173177
'right'))
@@ -228,17 +232,8 @@ def write_result(self, buf):
228232

229233
buffer_put_lines(buf, self.elements)
230234

231-
def _write_header(self, indent):
235+
def _write_col_header(self, indent):
232236
truncate_h = self.fmt.truncate_h
233-
234-
if not self.fmt.header:
235-
# write nothing
236-
return indent
237-
238-
self.write('<thead>', indent)
239-
240-
indent += self.indent_delta
241-
242237
if isinstance(self.columns, ABCMultiIndex):
243238
template = 'colspan="{span:d}" halign="left"'
244239

@@ -357,12 +352,25 @@ def _write_header(self, indent):
357352
self.write_tr(row, indent, self.indent_delta, header=True,
358353
align=align)
359354

360-
if all((self.fmt.has_index_names,
361-
self.fmt.index,
362-
self.fmt.show_index_names)):
363-
row = ([x if x is not None else '' for x in self.frame.index.names]
364-
+ [''] * (self.ncols + (1 if truncate_h else 0)))
365-
self.write_tr(row, indent, self.indent_delta, header=True)
355+
def _write_row_header(self, indent):
356+
truncate_h = self.fmt.truncate_h
357+
row = ([x if x is not None else '' for x in self.frame.index.names]
358+
+ [''] * (self.ncols + (1 if truncate_h else 0)))
359+
self.write_tr(row, indent, self.indent_delta, header=True)
360+
361+
def _write_header(self, indent):
362+
if not (self.fmt.header or self.show_row_idx_names):
363+
# write nothing
364+
return indent
365+
366+
self.write('<thead>', indent)
367+
indent += self.indent_delta
368+
369+
if self.fmt.header:
370+
self._write_col_header(indent)
371+
372+
if self.show_row_idx_names:
373+
self._write_row_header(indent)
366374

367375
indent -= self.indent_delta
368376
self.write('</thead>', indent)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<table border="1" class="dataframe">
2+
<thead>
3+
<tr>
4+
<th>index.name.0</th>
5+
<th>index.name.1</th>
6+
<th></th>
7+
<th></th>
8+
</tr>
9+
</thead>
10+
<tbody>
11+
<tr>
12+
<th rowspan="2" valign="top">a</th>
13+
<th>b</th>
14+
<td>0</td>
15+
<td>0</td>
16+
</tr>
17+
<tr>
18+
<th>c</th>
19+
<td>0</td>
20+
<td>0</td>
21+
</tr>
22+
</tbody>
23+
</table>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<table border="1" class="dataframe">
2+
<thead>
3+
<tr>
4+
<th>index.name</th>
5+
<th></th>
6+
<th></th>
7+
</tr>
8+
</thead>
9+
<tbody>
10+
<tr>
11+
<th>0</th>
12+
<td>0</td>
13+
<td>0</td>
14+
</tr>
15+
<tr>
16+
<th>1</th>
17+
<td>0</td>
18+
<td>0</td>
19+
</tr>
20+
</tbody>
21+
</table>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<table border="1" class="dataframe">
2+
<tbody>
3+
<tr>
4+
<th rowspan="2" valign="top">a</th>
5+
<th>b</th>
6+
<td>0</td>
7+
<td>0</td>
8+
</tr>
9+
<tr>
10+
<th>c</th>
11+
<td>0</td>
12+
<td>0</td>
13+
</tr>
14+
</tbody>
15+
</table>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<table border="1" class="dataframe">
2+
<tbody>
3+
<tr>
4+
<th>0</th>
5+
<td>0</td>
6+
<td>0</td>
7+
</tr>
8+
<tr>
9+
<th>1</th>
10+
<td>0</td>
11+
<td>0</td>
12+
</tr>
13+
</tbody>
14+
</table>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<table border="1" class="dataframe">
2+
<thead>
3+
<tr>
4+
<th>foo</th>
5+
<th></th>
6+
<th>baz</th>
7+
<th></th>
8+
<th></th>
9+
<th></th>
10+
<th></th>
11+
<th></th>
12+
</tr>
13+
</thead>
14+
<tbody>
15+
<tr>
16+
<th rowspan="2" valign="top">a</th>
17+
<th rowspan="2" valign="top">c</th>
18+
<th>e</th>
19+
<td>0</td>
20+
<td>1</td>
21+
<td>...</td>
22+
<td>6</td>
23+
<td>7</td>
24+
</tr>
25+
<tr>
26+
<th>f</th>
27+
<td>8</td>
28+
<td>9</td>
29+
<td>...</td>
30+
<td>14</td>
31+
<td>15</td>
32+
</tr>
33+
<tr>
34+
<th>...</th>
35+
<th>...</th>
36+
<th>...</th>
37+
<td>...</td>
38+
<td>...</td>
39+
<td>...</td>
40+
<td>...</td>
41+
<td>...</td>
42+
</tr>
43+
<tr>
44+
<th rowspan="2" valign="top">b</th>
45+
<th rowspan="2" valign="top">d</th>
46+
<th>e</th>
47+
<td>48</td>
48+
<td>49</td>
49+
<td>...</td>
50+
<td>54</td>
51+
<td>55</td>
52+
</tr>
53+
<tr>
54+
<th>f</th>
55+
<td>56</td>
56+
<td>57</td>
57+
<td>...</td>
58+
<td>62</td>
59+
<td>63</td>
60+
</tr>
61+
</tbody>
62+
</table>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<table border="1" class="dataframe">
2+
<thead>
3+
<tr>
4+
<th>index.name</th>
5+
<th></th>
6+
<th></th>
7+
<th></th>
8+
<th></th>
9+
<th></th>
10+
</tr>
11+
</thead>
12+
<tbody>
13+
<tr>
14+
<th>0</th>
15+
<td>0</td>
16+
<td>1</td>
17+
<td>...</td>
18+
<td>6</td>
19+
<td>7</td>
20+
</tr>
21+
<tr>
22+
<th>1</th>
23+
<td>8</td>
24+
<td>9</td>
25+
<td>...</td>
26+
<td>14</td>
27+
<td>15</td>
28+
</tr>
29+
<tr>
30+
<th>...</th>
31+
<td>...</td>
32+
<td>...</td>
33+
<td>...</td>
34+
<td>...</td>
35+
<td>...</td>
36+
</tr>
37+
<tr>
38+
<th>6</th>
39+
<td>48</td>
40+
<td>49</td>
41+
<td>...</td>
42+
<td>54</td>
43+
<td>55</td>
44+
</tr>
45+
<tr>
46+
<th>7</th>
47+
<td>56</td>
48+
<td>57</td>
49+
<td>...</td>
50+
<td>62</td>
51+
<td>63</td>
52+
</tr>
53+
</tbody>
54+
</table>

pandas/tests/io/formats/test_to_html.py

+15-7
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ def test_to_html_multi_indexes_index_false(self, datapath):
429429
assert result == expected
430430

431431
@pytest.mark.parametrize('index_names', [True, False])
432+
@pytest.mark.parametrize('header', [True, False])
432433
@pytest.mark.parametrize('index', [True, False])
433434
@pytest.mark.parametrize('column_index, column_type', [
434435
(Index([0, 1]), 'unnamed_standard'),
@@ -448,25 +449,29 @@ def test_to_html_multi_indexes_index_false(self, datapath):
448449
])
449450
def test_to_html_basic_alignment(
450451
self, datapath, row_index, row_type, column_index, column_type,
451-
index, index_names):
452+
index, header, index_names):
452453
# GH 22747, GH 22579
453454
df = DataFrame(np.zeros((2, 2), dtype=int),
454455
index=row_index, columns=column_index)
455-
result = df.to_html(index=index, index_names=index_names)
456+
result = df.to_html(
457+
index=index, header=header, index_names=index_names)
456458

457459
if not index:
458460
row_type = 'none'
459461
elif not index_names and row_type.startswith('named'):
460462
row_type = 'un' + row_type
461463

462-
if not index_names and column_type.startswith('named'):
464+
if not header:
465+
column_type = 'none'
466+
elif not index_names and column_type.startswith('named'):
463467
column_type = 'un' + column_type
464468

465469
filename = 'index_' + row_type + '_columns_' + column_type
466470
expected = expected_html(datapath, filename)
467471
assert result == expected
468472

469473
@pytest.mark.parametrize('index_names', [True, False])
474+
@pytest.mark.parametrize('header', [True, False])
470475
@pytest.mark.parametrize('index', [True, False])
471476
@pytest.mark.parametrize('column_index, column_type', [
472477
(Index(np.arange(8)), 'unnamed_standard'),
@@ -488,19 +493,22 @@ def test_to_html_basic_alignment(
488493
])
489494
def test_to_html_alignment_with_truncation(
490495
self, datapath, row_index, row_type, column_index, column_type,
491-
index, index_names):
496+
index, header, index_names):
492497
# GH 22747, GH 22579
493498
df = DataFrame(np.arange(64).reshape(8, 8),
494499
index=row_index, columns=column_index)
495-
result = df.to_html(max_rows=4, max_cols=4,
496-
index=index, index_names=index_names)
500+
result = df.to_html(
501+
max_rows=4, max_cols=4,
502+
index=index, header=header, index_names=index_names)
497503

498504
if not index:
499505
row_type = 'none'
500506
elif not index_names and row_type.startswith('named'):
501507
row_type = 'un' + row_type
502508

503-
if not index_names and column_type.startswith('named'):
509+
if not header:
510+
column_type = 'none'
511+
elif not index_names and column_type.startswith('named'):
504512
column_type = 'un' + column_type
505513

506514
filename = 'trunc_df_index_' + row_type + '_columns_' + column_type

0 commit comments

Comments
 (0)