Skip to content

Commit 00860a9

Browse files
committed
ENH: improve display of hierarchical columns in to_html
1 parent 04d8228 commit 00860a9

File tree

3 files changed

+75
-37
lines changed

3 files changed

+75
-37
lines changed

RELEASE.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ Where to get it
2222
* Binary installers on PyPI: http://pypi.python.org/pypi/pandas
2323
* Documentation: http://pandas.pydata.org
2424

25+
pandas 0.9.1
26+
============
27+
28+
**Release date:** NOT YET RELEASED
29+
30+
**Improvements to existing features**
31+
32+
- Improve HTML display of DataFrame objects with hierarchical columns
33+
2534
pandas 0.9.0
2635
============
2736

pandas/core/format.py

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818
import numpy as np
1919

2020
docstring_to_string = """
21-
Parameters
22-
----------
23-
frame : DataFrame
24-
object to render
21+
Parameters
22+
----------
23+
frame : DataFrame
24+
object to render
2525
buf : StringIO-like, optional
2626
buffer to write to
2727
columns : sequence, optional
@@ -528,16 +528,43 @@ def _column_header():
528528
self.write('<thead>', indent)
529529
row = []
530530

531-
col_row = _column_header()
532531
indent += self.indent_delta
532+
533533
if isinstance(self.columns, MultiIndex):
534-
align = None
534+
template = 'colspan="%d" halign="left"'
535+
536+
levels = self.columns.format(sparsify=True, adjoin=False,
537+
names=False)
538+
col_values = self.columns.values
539+
level_lengths = _get_level_lengths(levels)
540+
541+
for lnum, (records, values) in enumerate(zip(level_lengths, levels)):
542+
name = self.columns.names[lnum]
543+
row = ['' if name is None else str(name)]
544+
545+
tags = {}
546+
j = 1
547+
for i, v in enumerate(values):
548+
if i in records:
549+
if records[i] > 1:
550+
tags[j] = template % records[i]
551+
else:
552+
continue
553+
j += 1
554+
row.append(v)
555+
556+
self.write_tr(row, indent, self.indent_delta, tags=tags,
557+
header=True)
535558
else:
559+
col_row = _column_header()
536560
align = self.fmt.justify
537-
self.write_tr(col_row, indent, self.indent_delta, header=True,
538-
align=align)
561+
562+
self.write_tr(col_row, indent, self.indent_delta, header=True,
563+
align=align)
564+
539565
if self.fmt.has_index_names:
540-
row = self.frame.index.names + [''] * len(self.columns)
566+
row = [x if x is not None else ''
567+
for x in self.frame.index.names] + [''] * len(self.columns)
541568
self.write_tr(row, indent, self.indent_delta, header=True)
542569

543570
indent -= self.indent_delta

pandas/tests/test_format.py

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,8 @@ def test_to_html_unicode(self):
190190
df.to_html()
191191

192192
def test_to_html_multiindex_sparsify(self):
193-
index = pd.MultiIndex.from_arrays([[0, 0, 1, 1], [0, 1, 0, 1]])
193+
index = pd.MultiIndex.from_arrays([[0, 0, 1, 1], [0, 1, 0, 1]],
194+
names=['foo', None])
194195

195196
df = DataFrame([[0, 1], [2, 3], [4, 5], [6, 7]], index=index)
196197

@@ -203,6 +204,12 @@ def test_to_html_multiindex_sparsify(self):
203204
<th>0</th>
204205
<th>1</th>
205206
</tr>
207+
<tr>
208+
<th>foo</th>
209+
<th></th>
210+
<th></th>
211+
<th></th>
212+
</tr>
206213
</thead>
207214
<tbody>
208215
<tr>
@@ -514,28 +521,24 @@ def test_to_html_columns_arg(self):
514521
self.assert_('<th>B</th>' not in result)
515522

516523
def test_to_html_multiindex(self):
517-
columns = pandas.MultiIndex.from_tuples(zip(range(4),
524+
columns = pandas.MultiIndex.from_tuples(zip(np.arange(2).repeat(2),
518525
np.mod(range(4), 2)),
519526
names=['CL0', 'CL1'])
520527
df = pandas.DataFrame([list('abcd'), list('efgh')], columns=columns)
521528
result = df.to_html(justify='left')
522529
expected = ('<table border="1" class="dataframe">\n'
523530
' <thead>\n'
524531
' <tr>\n'
525-
' <th><table><tbody><tr><td>CL0</td></tr><tr>'
526-
'<td>CL1</td></tr></tbody></table></th>\n'
527-
' <th><table align="left" style="text-align: left;">'
528-
'<tbody><tr><td>0</td></tr><tr><td>0</td></tr></tbody>'
529-
'</table></th>\n'
530-
' <th><table align="left" style="text-align: left;">'
531-
'<tbody><tr><td>1</td></tr><tr><td>1</td></tr></tbody>'
532-
'</table></th>\n'
533-
' <th><table align="left" style="text-align: left;">'
534-
'<tbody><tr><td>2</td></tr><tr><td>0</td></tr></tbody>'
535-
'</table></th>\n'
536-
' <th><table align="left" style="text-align: left;">'
537-
'<tbody><tr><td>3</td></tr><tr><td>1</td></tr></tbody>'
538-
'</table></th>\n'
532+
' <th>CL0</th>\n'
533+
' <th colspan="2" halign="left">0</th>\n'
534+
' <th colspan="2" halign="left">1</th>\n'
535+
' </tr>\n'
536+
' <tr>\n'
537+
' <th>CL1</th>\n'
538+
' <th>0</th>\n'
539+
' <th>1</th>\n'
540+
' <th>0</th>\n'
541+
' <th>1</th>\n'
539542
' </tr>\n'
540543
' </thead>\n'
541544
' <tbody>\n'
@@ -567,18 +570,17 @@ def test_to_html_multiindex(self):
567570
' <thead>\n'
568571
' <tr>\n'
569572
' <th></th>\n'
570-
' <th><table align="right" style="text-align:'
571-
' right;"><tbody><tr><td>0</td></tr><tr><td>0</td></tr>'
572-
'</tbody></table></th>\n'
573-
' <th><table align="right" style="text-align:'
574-
' right;"><tbody><tr><td>1</td></tr><tr><td>1</td></tr>'
575-
'</tbody></table></th>\n'
576-
' <th><table align="right" style="text-align:'
577-
' right;"><tbody><tr><td>2</td></tr><tr><td>0</td></tr>'
578-
'</tbody></table></th>\n'
579-
' <th><table align="right" style="text-align:'
580-
' right;"><tbody><tr><td>3</td></tr><tr><td>1</td></tr>'
581-
'</tbody></table></th>\n'
573+
' <th>0</th>\n'
574+
' <th>1</th>\n'
575+
' <th>2</th>\n'
576+
' <th>3</th>\n'
577+
' </tr>\n'
578+
' <tr>\n'
579+
' <th></th>\n'
580+
' <th>0</th>\n'
581+
' <th>1</th>\n'
582+
' <th>0</th>\n'
583+
' <th>1</th>\n'
582584
' </tr>\n'
583585
' </thead>\n'
584586
' <tbody>\n'

0 commit comments

Comments
 (0)