Skip to content

Instantly share code, notes, and snippets.

@remram44
Created September 17, 2017 00:57
Show Gist options
  • Save remram44/ea5297e41484b646eaaf3b9914747e73 to your computer and use it in GitHub Desktop.
Save remram44/ea5297e41484b646eaaf3b9914747e73 to your computer and use it in GitHub Desktop.
Pretty HTML formatting for cross tabulation pandas DataFrame, similar to Qualtrics
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