Created
September 17, 2017 00:57
-
-
Save remram44/ea5297e41484b646eaaf3b9914747e73 to your computer and use it in GitHub Desktop.
Pretty HTML formatting for cross tabulation pandas DataFrame, similar to Qualtrics
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from pandas.core.indexes.multi import MultiIndex | |
def _read_index(idx): | |
if isinstance(idx, MultiIndex): | |
return list(idx), idx.names, lambda t: t | |
else: | |
return [(e,) for e in idx], [idx.name], lambda t: t[0] | |
skip_row = object() | |
skip_column = object() | |
def crosstab_to_html(df): | |
row_index, row_names, row_access = _read_index(df.index) | |
column_index, column_names, column_access = _read_index(df.columns) | |
# On the left, leave out 2 * number of row indexes | |
left_padding = 2 * len(row_index[0]) | |
# On the top, leave out 2 * number of column indexes | |
top_padding = 2 * len(column_index[0]) | |
cells = [ | |
[('', 'empty')] * (left_padding + len(column_index)) | |
for _ in range(top_padding + len(row_index)) | |
] | |
# Write column index labels | |
for i, label in enumerate(column_names): | |
cells[i * 2][left_padding] = label, 'idx' | |
for j in range(1, len(column_index)): | |
cells[i * 2][left_padding + j] = skip_column | |
# Write column values | |
prev = () | |
for col, idx in enumerate(column_index): | |
for row, value in enumerate(idx): | |
if idx[:row + 1] != prev[:row + 1]: | |
cells[row * 2 + 1][left_padding + col] = value, 'label' | |
else: | |
cells[row * 2 + 1][left_padding + col] = skip_column | |
prev = idx | |
# Write row index labels | |
for i, label in enumerate(row_names): | |
cells[top_padding][i * 2] = label, 'idx' | |
for j in range(1, len(row_index)): | |
cells[top_padding + j][i * 2] = skip_row | |
# Write row values | |
prev = () | |
for row, idx in enumerate(row_index): | |
for col, value in enumerate(idx): | |
if idx[:col + 1] != prev[:col + 1]: | |
cells[top_padding + row][col * 2 + 1] = value, 'label' | |
else: | |
cells[top_padding + row][col * 2 + 1] = skip_row | |
prev = idx | |
# Write values | |
for r, row in enumerate(row_index): | |
for c, col in enumerate(column_index): | |
cells[top_padding + r][left_padding + c] = \ | |
df[column_access(col)][row_access(row)], 'value' | |
# Apply spans | |
cells_spans = [] | |
for r, row in enumerate(cells): | |
new_row = [] | |
cells_spans.append(new_row) | |
for c, value in enumerate(row): | |
if value is skip_column: | |
cells_spans[r][c - 1][1][0] += 1 | |
new_row.append([None, cells_spans[r][c - 1][1], [1]]) | |
elif value is skip_row: | |
cells_spans[r - 1][c][2][0] += 1 | |
new_row.append([None, [1], cells_spans[r - 1][c][2]]) | |
else: | |
new_row.append([value, [1], [1]]) | |
return ('''\ | |
<style type="text/css"> | |
td.crosstab-value { | |
border: 1px solid black; | |
text-align: right; | |
} | |
td.crosstab-empty { | |
background: white; | |
} | |
td.crosstab-idx { | |
border: 1px solid black; | |
font-weight: bold; | |
text-decoration: underline; | |
text-align: center; | |
} | |
td.crosstab-label { | |
border: 1px solid black; | |
font-weight: bold; | |
text-align: center; | |
} | |
</style> | |
''' + | |
'<table>' + | |
'\n'.join('<tr class="crosstab">' + | |
''.join('<td class="crosstab-%s" colspan="%d" rowspan="%d">' | |
'%s</td>' % (cell[1], cs, rs, cell[0]) | |
for cell, [cs], [rs] in row | |
if cell is not None) + | |
'</tr>' | |
for row in cells_spans) + | |
'</table>') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment