Skip to content

Commit b9284a2

Browse files
simonjayhawkinsWillAyd
authored andcommitted
BUG: output formatting with to_html(), index=False and/or index_names=False (pandas-dev#22579, pandas-dev#22747) (pandas-dev#22655)
1 parent a16c29b commit b9284a2

File tree

84 files changed

+2287
-24
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+2287
-24
lines changed

doc/source/whatsnew/v0.24.0.rst

+2
Original file line numberDiff line numberDiff line change
@@ -1596,6 +1596,8 @@ Notice how we now instead output ``np.nan`` itself instead of a stringified form
15961596
- :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`)
15971597
- 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`)
15981598
- Bug in :func:`to_html()` with ``index=False`` misses truncation indicators (...) on truncated DataFrame (:issue:`15019`, :issue:`22783`)
1599+
- Bug in :func:`to_html()` with ``index=False`` when both columns and row index are ``MultiIndex`` (:issue:`22579`)
1600+
- Bug in :func:`to_html()` with ``index_names=False`` displaying index name (:issue:`22747`)
15991601
- 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`)
16001602
- Bug in :func:`DataFrame.to_string()` that caused representations of :class:`DataFrame` to not take up the whole window (:issue:`22984`)
16011603
- 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

+79-21
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,35 @@ 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_col_idx_names(self):
48+
# see gh-22579
49+
# Column misalignment also occurs for
50+
# a standard index when the columns index is named.
51+
# Determine if ANY column names need to be displayed
52+
# since if the row index is not displayed a column of
53+
# blank cells need to be included before the DataFrame values.
54+
# TODO: refactor to add show_col_idx_names property to
55+
# DataFrameFormatter
56+
return all((self.fmt.has_column_names,
57+
self.fmt.show_index_names,
58+
self.fmt.header))
59+
60+
@property
61+
def row_levels(self):
62+
if self.fmt.index:
63+
# showing (row) index
64+
return self.frame.index.nlevels
65+
elif self.show_col_idx_names:
66+
# see gh-22579
67+
# Column misalignment also occurs for
68+
# a standard index when the columns index is named.
69+
# If the row index is not displayed a column of
70+
# blank cells need to be included before the DataFrame values.
71+
return 1
72+
# not showing (row) index
73+
return 0
74+
4675
@property
4776
def is_truncated(self):
4877
return self.fmt.is_truncated
@@ -201,7 +230,7 @@ def write_result(self, buf):
201230

202231
def _write_header(self, indent):
203232
truncate_h = self.fmt.truncate_h
204-
row_levels = self.frame.index.nlevels
233+
205234
if not self.fmt.header:
206235
# write nothing
207236
return indent
@@ -267,12 +296,26 @@ def _write_header(self, indent):
267296
values = (values[:ins_col] + [u('...')] +
268297
values[ins_col:])
269298

270-
name = self.columns.names[lnum]
271-
row = [''] * (row_levels - 1) + ['' if name is None else
272-
pprint_thing(name)]
273-
274-
if row == [""] and self.fmt.index is False:
275-
row = []
299+
# see gh-22579
300+
# Column Offset Bug with to_html(index=False) with
301+
# MultiIndex Columns and Index.
302+
# Initially fill row with blank cells before column names.
303+
# TODO: Refactor to remove code duplication with code
304+
# block below for standard columns index.
305+
row = [''] * (self.row_levels - 1)
306+
if self.fmt.index or self.show_col_idx_names:
307+
# see gh-22747
308+
# If to_html(index_names=False) do not show columns
309+
# index names.
310+
# TODO: Refactor to use _get_column_name_list from
311+
# DataFrameFormatter class and create a
312+
# _get_formatted_column_labels function for code
313+
# parity with DataFrameFormatter class.
314+
if self.fmt.show_index_names:
315+
name = self.columns.names[lnum]
316+
row.append(pprint_thing(name or ''))
317+
else:
318+
row.append('')
276319

277320
tags = {}
278321
j = len(row)
@@ -287,18 +330,28 @@ def _write_header(self, indent):
287330
self.write_tr(row, indent, self.indent_delta, tags=tags,
288331
header=True)
289332
else:
290-
if self.fmt.index:
291-
row = [''] * (self.frame.index.nlevels - 1)
292-
row.append(self.columns.name or '')
293-
else:
294-
row = []
333+
# see gh-22579
334+
# Column misalignment also occurs for
335+
# a standard index when the columns index is named.
336+
# Initially fill row with blank cells before column names.
337+
# TODO: Refactor to remove code duplication with code block
338+
# above for columns MultiIndex.
339+
row = [''] * (self.row_levels - 1)
340+
if self.fmt.index or self.show_col_idx_names:
341+
# see gh-22747
342+
# If to_html(index_names=False) do not show columns
343+
# index names.
344+
# TODO: Refactor to use _get_column_name_list from
345+
# DataFrameFormatter class.
346+
if self.fmt.show_index_names:
347+
row.append(self.columns.name or '')
348+
else:
349+
row.append('')
295350
row.extend(self.columns)
296351
align = self.fmt.justify
297352

298353
if truncate_h:
299-
if not self.fmt.index:
300-
row_levels = 0
301-
ins_col = row_levels + self.fmt.tr_col_num
354+
ins_col = self.row_levels + self.fmt.tr_col_num
302355
row.insert(ins_col, '...')
303356

304357
self.write_tr(row, indent, self.indent_delta, header=True,
@@ -346,28 +399,31 @@ def _write_regular_rows(self, fmt_values, indent):
346399
index_values = self.fmt.tr_frame.index.map(fmt)
347400
else:
348401
index_values = self.fmt.tr_frame.index.format()
349-
row_levels = 1
350-
else:
351-
row_levels = 0
352402

353403
row = []
354404
for i in range(nrows):
355405

356406
if truncate_v and i == (self.fmt.tr_row_num):
357407
str_sep_row = ['...'] * len(row)
358408
self.write_tr(str_sep_row, indent, self.indent_delta,
359-
tags=None, nindex_levels=row_levels)
409+
tags=None, nindex_levels=self.row_levels)
360410

361411
row = []
362412
if self.fmt.index:
363413
row.append(index_values[i])
414+
# see gh-22579
415+
# Column misalignment also occurs for
416+
# a standard index when the columns index is named.
417+
# Add blank cell before data cells.
418+
elif self.show_col_idx_names:
419+
row.append('')
364420
row.extend(fmt_values[j][i] for j in range(self.ncols))
365421

366422
if truncate_h:
367-
dot_col_ix = self.fmt.tr_col_num + row_levels
423+
dot_col_ix = self.fmt.tr_col_num + self.row_levels
368424
row.insert(dot_col_ix, '...')
369425
self.write_tr(row, indent, self.indent_delta, tags=None,
370-
nindex_levels=row_levels)
426+
nindex_levels=self.row_levels)
371427

372428
def _write_hierarchical_rows(self, fmt_values, indent):
373429
template = 'rowspan="{span}" valign="top"'
@@ -376,6 +432,8 @@ def _write_hierarchical_rows(self, fmt_values, indent):
376432
truncate_v = self.fmt.truncate_v
377433
frame = self.fmt.tr_frame
378434
nrows = len(frame)
435+
# TODO: after gh-22887 fixed, refactor to use class property
436+
# in place of row_levels
379437
row_levels = self.frame.index.nlevels
380438

381439
idx_values = frame.index.format(sparsify=False, adjoin=False,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<table border="1" class="dataframe">
2+
<thead>
3+
<tr>
4+
<th colspan="2" halign="left">a</th>
5+
<th colspan="2" halign="left">b</th>
6+
</tr>
7+
<tr>
8+
<th>c</th>
9+
<th>d</th>
10+
<th>c</th>
11+
<th>d</th>
12+
</tr>
13+
</thead>
14+
<tbody>
15+
<tr>
16+
<td>0</td>
17+
<td>10</td>
18+
<td>10</td>
19+
<td>10</td>
20+
</tr>
21+
<tr>
22+
<td>1</td>
23+
<td>11</td>
24+
<td>11</td>
25+
<td>11</td>
26+
</tr>
27+
<tr>
28+
<td>2</td>
29+
<td>12</td>
30+
<td>12</td>
31+
<td>12</td>
32+
</tr>
33+
<tr>
34+
<td>3</td>
35+
<td>13</td>
36+
<td>13</td>
37+
<td>13</td>
38+
</tr>
39+
<tr>
40+
<td>4</td>
41+
<td>14</td>
42+
<td>14</td>
43+
<td>14</td>
44+
</tr>
45+
<tr>
46+
<td>5</td>
47+
<td>15</td>
48+
<td>15</td>
49+
<td>15</td>
50+
</tr>
51+
<tr>
52+
<td>6</td>
53+
<td>16</td>
54+
<td>16</td>
55+
<td>16</td>
56+
</tr>
57+
<tr>
58+
<td>7</td>
59+
<td>17</td>
60+
<td>17</td>
61+
<td>17</td>
62+
</tr>
63+
<tr>
64+
<td>8</td>
65+
<td>18</td>
66+
<td>18</td>
67+
<td>18</td>
68+
</tr>
69+
<tr>
70+
<td>9</td>
71+
<td>19</td>
72+
<td>19</td>
73+
<td>19</td>
74+
</tr>
75+
</tbody>
76+
</table>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<table border="1" class="dataframe">
2+
<thead>
3+
<tr style="text-align: right;">
4+
<th>columns.name</th>
5+
<th>0</th>
6+
<th>1</th>
7+
<th>...</th>
8+
<th>3</th>
9+
<th>4</th>
10+
</tr>
11+
</thead>
12+
<tbody>
13+
<tr>
14+
<th></th>
15+
<td>1.764052</td>
16+
<td>0.400157</td>
17+
<td>...</td>
18+
<td>2.240893</td>
19+
<td>1.867558</td>
20+
</tr>
21+
<tr>
22+
<th></th>
23+
<td>-0.977278</td>
24+
<td>0.950088</td>
25+
<td>...</td>
26+
<td>-0.103219</td>
27+
<td>0.410599</td>
28+
</tr>
29+
</tbody>
30+
</table>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<table border="1" class="dataframe">
2+
<thead>
3+
<tr>
4+
<th></th>
5+
<th>columns.name.0</th>
6+
<th colspan="2" halign="left">a</th>
7+
</tr>
8+
<tr>
9+
<th></th>
10+
<th>columns.name.1</th>
11+
<th>b</th>
12+
<th>c</th>
13+
</tr>
14+
<tr>
15+
<th>index.name.0</th>
16+
<th>index.name.1</th>
17+
<th></th>
18+
<th></th>
19+
</tr>
20+
</thead>
21+
<tbody>
22+
<tr>
23+
<th rowspan="2" valign="top">a</th>
24+
<th>b</th>
25+
<td>0</td>
26+
<td>0</td>
27+
</tr>
28+
<tr>
29+
<th>c</th>
30+
<td>0</td>
31+
<td>0</td>
32+
</tr>
33+
</tbody>
34+
</table>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<table border="1" class="dataframe">
2+
<thead>
3+
<tr style="text-align: right;">
4+
<th></th>
5+
<th>columns.name</th>
6+
<th>0</th>
7+
<th>1</th>
8+
</tr>
9+
<tr>
10+
<th>index.name.0</th>
11+
<th>index.name.1</th>
12+
<th></th>
13+
<th></th>
14+
</tr>
15+
</thead>
16+
<tbody>
17+
<tr>
18+
<th rowspan="2" valign="top">a</th>
19+
<th>b</th>
20+
<td>0</td>
21+
<td>0</td>
22+
</tr>
23+
<tr>
24+
<th>c</th>
25+
<td>0</td>
26+
<td>0</td>
27+
</tr>
28+
</tbody>
29+
</table>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<table border="1" class="dataframe">
2+
<thead>
3+
<tr>
4+
<th></th>
5+
<th></th>
6+
<th colspan="2" halign="left">a</th>
7+
</tr>
8+
<tr>
9+
<th></th>
10+
<th></th>
11+
<th>b</th>
12+
<th>c</th>
13+
</tr>
14+
<tr>
15+
<th>index.name.0</th>
16+
<th>index.name.1</th>
17+
<th></th>
18+
<th></th>
19+
</tr>
20+
</thead>
21+
<tbody>
22+
<tr>
23+
<th rowspan="2" valign="top">a</th>
24+
<th>b</th>
25+
<td>0</td>
26+
<td>0</td>
27+
</tr>
28+
<tr>
29+
<th>c</th>
30+
<td>0</td>
31+
<td>0</td>
32+
</tr>
33+
</tbody>
34+
</table>

0 commit comments

Comments
 (0)