-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathpandas_excel_styler.py
133 lines (106 loc) · 5.26 KB
/
pandas_excel_styler.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# -*- coding: utf-8 -*-
"""
Extended classes for styling individual cells of pandas Excel exports.
Currently, only export to "xls" files via xlwt is supported. "xlsx" export via OpenPyXL is not available.
Created on Tue Oct 4 11:32:10 2016
@author: Markus Konrad <[email protected]>
"""
import numpy as np
import pandas as pd
from pandas import compat
import pandas.io.formats.excel as fmt
#%% Class extensions to pandas
class ExcelFormatterStyler(fmt.ExcelFormatter):
"""
Extended ExcelFormatter class that accepts additional "cell_styles" matrix
"""
def __init__(self, *args, **kwargs):
"""
Extended constructor that accepts additional "cell_styles" matrix
"""
self.cell_styles = kwargs.pop('cell_styles')
super(ExcelFormatterStyler, self).__init__(*args, **kwargs)
if self.cell_styles is not None:
expected_n_rows = self.df.shape[0]
expected_n_cols = self.df.shape[1]
if self.cell_styles.shape[0] != expected_n_rows or self.cell_styles.shape[1] == expected_n_cols:
ValueError("Argument 'cell_styles' must have the same shape like the data frame: %dx%d"
% (expected_n_rows, expected_n_cols))
def _format_regular_rows(self):
"""
Extended function that handles formatting of regular cells also considering cell_styles matrix
"""
# get cell formatting from parent method
col_offset = -1 if self.index else 0
for cell in super(ExcelFormatterStyler, self)._format_regular_rows():
if self.cell_styles is not None and cell.row > 0 and cell.col > 0:
# consider cell style for this regular cell
st = self.cell_styles[cell.row - 1, cell.col + col_offset]
if st is not None:
if type(cell.style) != dict:
cell.style = st
else:
cell.style.update(st)
yield cell
class DataFrameExcelStyler(pd.DataFrame):
"""
Extended DataFrame class that accepts additional "cell_styles" matrix and uses the extended
ExcelFormatterStyler as formatter class.
"""
def to_excel(self, excel_writer, sheet_name='Sheet1', na_rep='',
cell_styles=None, # new argument
float_format=None, columns=None, header=True, index=True,
index_label=None, startrow=0, startcol=0, engine=None,
merge_cells=True, encoding=None, inf_rep='inf', verbose=True):
"""
Extended function that adds support for "cell_styles" argument
"""
from pandas.io.excel import ExcelWriter
need_save = False
if encoding is None:
encoding = 'ascii'
if isinstance(excel_writer, compat.string_types):
excel_writer = ExcelWriter(excel_writer, engine=engine)
need_save = True
# use the extended formatter class and pass the cell_styles argument
formatter = ExcelFormatterStyler(self, na_rep=na_rep, cols=columns,
header=header,
cell_styles=cell_styles, # new argument
float_format=float_format, index=index,
index_label=index_label,
merge_cells=merge_cells,
inf_rep=inf_rep)
formatted_cells = formatter.get_formatted_cells()
excel_writer.write_cells(formatted_cells, sheet_name,
startrow=startrow, startcol=startcol)
if need_save:
excel_writer.save()
#%% Utility functions
def create_style_for_validations(df, suffix='_valid', error_style='red', remove_validation_cols=False):
"""
Create a "cell_styles" matrix for a data frame with boolean validation result columns suffixed
with <suffix>.
"""
# check arguments
if type(error_style) == str:
error_style = {"pattern": {"pattern": "solid_fill", "fore_color": error_style}}
elif type(error_style) != dict:
raise ValueError("Argument 'error_style' must be either of type 'str' (background color) or a style 'dict'")
# create empty cell style matrix
cell_styles = np.empty((df.shape[0], df.shape[1]), dtype='object')
cell_styles.fill(None)
# iterate through the columns
for col_idx, colname in enumerate(df.columns.values):
if colname.endswith(suffix):
continue
validation_colname = colname + suffix
if validation_colname in df.columns.values: # found a validation result column
# set the style for all "invalid" cells
cell_styles[~df[validation_colname].values, col_idx] = error_style
if remove_validation_cols: # optionally remove the validation result column
# remove from cell_styles
validation_col_idx = np.nonzero(df.columns == validation_colname)[0][0]
cell_styles = np.delete(cell_styles, validation_col_idx, axis=1)
# remove from the original data frame
del df[validation_colname]
return cell_styles