toolsos 0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- toolsos/__init__.py +0 -0
- toolsos/cbs_tools.py +95 -0
- toolsos/database_connection.py +114 -0
- toolsos/database_transfer.py +63 -0
- toolsos/download.py +98 -0
- toolsos/geo.py +85 -0
- toolsos/helpers.py +39 -0
- toolsos/huisstijl/__init__.py +0 -0
- toolsos/huisstijl/colors.py +48 -0
- toolsos/huisstijl/graphs/__init__.py +0 -0
- toolsos/huisstijl/graphs/bargraph.py +134 -0
- toolsos/huisstijl/graphs/linegraph.py +20 -0
- toolsos/huisstijl/graphs/piegraph.py +32 -0
- toolsos/huisstijl/graphs/styler.py +97 -0
- toolsos/huisstijl/tables/__init__.py +0 -0
- toolsos/huisstijl/tables/table_styles.py +35 -0
- toolsos/huisstijl/tables/tables.py +508 -0
- toolsos/polars_helpers.py +31 -0
- toolsos/tabellen.py +30 -0
- toolsos-0.1.dist-info/METADATA +45 -0
- toolsos-0.1.dist-info/RECORD +23 -0
- toolsos-0.1.dist-info/WHEEL +5 -0
- toolsos-0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import plotly.express as px
|
|
2
|
+
|
|
3
|
+
from .styler import BaseStyle
|
|
4
|
+
|
|
5
|
+
basestyle = BaseStyle()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def pie(
|
|
9
|
+
data,
|
|
10
|
+
names,
|
|
11
|
+
values,
|
|
12
|
+
hole: float = 0.4,
|
|
13
|
+
width=750,
|
|
14
|
+
height=490,
|
|
15
|
+
text_format: str = None,
|
|
16
|
+
**kwargs,
|
|
17
|
+
):
|
|
18
|
+
fig = px.pie(
|
|
19
|
+
data_frame=data,
|
|
20
|
+
names=names,
|
|
21
|
+
values=values,
|
|
22
|
+
width=width,
|
|
23
|
+
height=height,
|
|
24
|
+
hole=hole,
|
|
25
|
+
template=BaseStyle().get_base_template(),
|
|
26
|
+
**kwargs,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
if text_format:
|
|
30
|
+
fig.update_traces(texttemplate=text_format)
|
|
31
|
+
|
|
32
|
+
return fig
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import plotly.graph_objects as go
|
|
4
|
+
import requests
|
|
5
|
+
from requests import ConnectionError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseStyle:
|
|
9
|
+
style_url = (
|
|
10
|
+
"https://raw.githubusercontent.com/jbosga-ams/oistyle/main/base_style.json"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self.grab_styling()
|
|
15
|
+
|
|
16
|
+
def grab_styling(self, style_path: str = None):
|
|
17
|
+
if not style_path:
|
|
18
|
+
try:
|
|
19
|
+
res = requests.get(self.style_url).json()
|
|
20
|
+
except ConnectionError:
|
|
21
|
+
print("Failed grabbing basestyle from the interwebs")
|
|
22
|
+
# Add option to manually provide json file
|
|
23
|
+
else:
|
|
24
|
+
res = json.loads()
|
|
25
|
+
|
|
26
|
+
for k, v in res.items():
|
|
27
|
+
setattr(self, k, v)
|
|
28
|
+
|
|
29
|
+
def _get_axis_format(self):
|
|
30
|
+
self.gridline_color = "#dbdbdb" # Jorren vragen om deze aan te passen
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
"zerolinecolor": self.gridline_color,
|
|
34
|
+
"gridcolor": self.gridline_color,
|
|
35
|
+
"gridwidth": self.gridline_width,
|
|
36
|
+
"showline": True,
|
|
37
|
+
"linewidth": self.gridline_width,
|
|
38
|
+
"linecolor": self.gridline_color,
|
|
39
|
+
# "mirror": True,
|
|
40
|
+
"showgrid": False,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
def _get_base_template_layout(self):
|
|
44
|
+
return go.layout.Template(
|
|
45
|
+
layout={
|
|
46
|
+
"font": {"family": self.font, "size": self.font_size},
|
|
47
|
+
"plot_bgcolor": self.plot_bgcolor,
|
|
48
|
+
"colorway": self.colors["darkblue_lightblue_gradient_5"],
|
|
49
|
+
"separators": ",", # Jorren vragen om deze toe te voegen
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def get_base_template(
|
|
54
|
+
self, graph_type: str = None, orientation: str = None, colors: str = None
|
|
55
|
+
):
|
|
56
|
+
"""[summary]
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
graph_type (str, optional): Pick 'bar', 'line' or 'bar'. Defaults to None.
|
|
60
|
+
orientation (str, optional): [description]. Pick horizontal ('h') or vertical 'v'. Defaults to None.
|
|
61
|
+
colors (str, optional): Set basecolors. Defaults to None.
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
ValueError: [description]
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
[type]: [description]
|
|
68
|
+
"""
|
|
69
|
+
base_template = self._get_base_template_layout()
|
|
70
|
+
axis_format = self._get_axis_format()
|
|
71
|
+
|
|
72
|
+
if graph_type == "bar":
|
|
73
|
+
if orientation in ["v", "vertical"]:
|
|
74
|
+
base_template.layout.xaxis.update(axis_format)
|
|
75
|
+
base_template.layout.yaxis.update(zeroline=False)
|
|
76
|
+
elif orientation in ["h", "horizontal"]:
|
|
77
|
+
base_template.layout.yaxis.update(axis_format)
|
|
78
|
+
base_template.layout.xaxis.update(zeroline=False)
|
|
79
|
+
else:
|
|
80
|
+
raise ValueError(
|
|
81
|
+
"Orientation ('v'/'vertical' or 'h'/'horizontal') should be supplied with graph_type=='bar'"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
elif graph_type == "line":
|
|
85
|
+
base_template.layout.xaxis.update(axis_format)
|
|
86
|
+
|
|
87
|
+
if colors:
|
|
88
|
+
base_template.layout.update({"colorway": colors})
|
|
89
|
+
|
|
90
|
+
return base_template
|
|
91
|
+
|
|
92
|
+
def get_ois_colors(self, colorscale):
|
|
93
|
+
colorscale = self.colors.get(colorscale, [])
|
|
94
|
+
if not colorscale:
|
|
95
|
+
raise Exception(f"Kies uit {self.colors.keys()}")
|
|
96
|
+
|
|
97
|
+
return colorscale
|
|
File without changes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
STYLE_OLD = {
|
|
2
|
+
"blue_white": {
|
|
3
|
+
"fill": {"fill_type": "solid", "fgColor": "00a0e6"},
|
|
4
|
+
"font": {"color": "FFFFFF", "bold": False},
|
|
5
|
+
},
|
|
6
|
+
"light_blue": {
|
|
7
|
+
"fill": {"fill_type": "solid", "fgColor": "B1D9F5"},
|
|
8
|
+
"font": {"bold": False},
|
|
9
|
+
},
|
|
10
|
+
"calibri": {"font": {"name": "Calibri", "size": 9}},
|
|
11
|
+
"blue_border_bottom": {
|
|
12
|
+
"border_bottom": {"color": "00a0e6", "border_style": "medium"}
|
|
13
|
+
},
|
|
14
|
+
"left_align": {"alignment": {"horizontal": "left"}, "font": {"bold": False}},
|
|
15
|
+
"right_align": {"alignment": {"horizontal": "right"}},
|
|
16
|
+
"title_bold": {"font": {"bold": True, "name": "Calibri", "size": 9}},
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
STYLE_NEW = {
|
|
20
|
+
"blue_white": {
|
|
21
|
+
"fill": {"fill_type": "solid", "fgColor": "004699"},
|
|
22
|
+
"font": {"color": "FFFFFF", "bold": True},
|
|
23
|
+
},
|
|
24
|
+
"light_blue": {
|
|
25
|
+
"fill": {"fill_type": "solid", "fgColor": "b8bcdd"},
|
|
26
|
+
"font": {"bold": True},
|
|
27
|
+
},
|
|
28
|
+
"calibri": {"font": {"name": "Calibri", "size": 9}},
|
|
29
|
+
"blue_border_bottom": {
|
|
30
|
+
"border_bottom": {"color": "004699", "border_style": "medium"}
|
|
31
|
+
},
|
|
32
|
+
"left_align": {"alignment": {"horizontal": "left"}, "font": {"bold": True}},
|
|
33
|
+
"right_align": {"alignment": {"horizontal": "right"}},
|
|
34
|
+
"title_bold": {"font": {"bold": True, "name": "Calibri", "size": 9}},
|
|
35
|
+
}
|
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any, Callable, Dict
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from openpyxl import Workbook
|
|
9
|
+
from openpyxl.styles import Alignment, Border, Font, PatternFill, Protection, Side
|
|
10
|
+
from openpyxl.utils import get_column_letter
|
|
11
|
+
|
|
12
|
+
Fmt = list[list[dict[str, Any]]]
|
|
13
|
+
Mapping = Dict[str, Dict[str, str | int | bool]]
|
|
14
|
+
|
|
15
|
+
LOOKUP: dict[str, Callable] = {
|
|
16
|
+
"font": Font,
|
|
17
|
+
"fill": PatternFill,
|
|
18
|
+
"alignment": Alignment,
|
|
19
|
+
"border": Border,
|
|
20
|
+
"protection": Protection,
|
|
21
|
+
"side": Side,
|
|
22
|
+
"border": Border,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def set_global_style(style: str) -> None:
|
|
27
|
+
global STYLES
|
|
28
|
+
if style == "old":
|
|
29
|
+
from table_styles import STYLE_OLD
|
|
30
|
+
|
|
31
|
+
STYLES = STYLE_OLD
|
|
32
|
+
elif style == "new":
|
|
33
|
+
from table_styles import STYLE_NEW
|
|
34
|
+
|
|
35
|
+
STYLES = STYLE_NEW
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# We are currently in the process of switching to the new `huisstijl`. Therefore the
|
|
39
|
+
# styling is stored in a json. After loading is treated as a constant
|
|
40
|
+
# STYLES = get_table_style_from_json()
|
|
41
|
+
|
|
42
|
+
LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def df_to_array(df: pd.DataFrame) -> np.ndarray:
|
|
46
|
+
"""Turn dataframe into array that includes column names as the first row
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
df (pd.DataFrame): Dataframe to be turned into an array
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
np.array: Array that includes the data and the column names as the first row
|
|
53
|
+
"""
|
|
54
|
+
return np.vstack([df.columns, df.to_numpy()])
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_fmt_table(arr: np.ndarray) -> Fmt:
|
|
58
|
+
"""Create nested list with dictionary inside that is the same size as the original
|
|
59
|
+
dataframe including column names
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
df (pd.DataFrame): Dataframe to be written to excel
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Fmt: Return empty nest list that will later be used to store formatting info
|
|
66
|
+
"""
|
|
67
|
+
fmt = []
|
|
68
|
+
for _ in range(arr.shape[0] + 1):
|
|
69
|
+
row: list = []
|
|
70
|
+
for _ in range(arr.shape[1]):
|
|
71
|
+
row.append({})
|
|
72
|
+
fmt.append(row)
|
|
73
|
+
|
|
74
|
+
return fmt
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def update_format(fmt: Fmt, row_idx: int, col_idx: int, mapping: Mapping) -> Fmt:
|
|
78
|
+
"""Update the cell containing the formatting info
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
fmt (Fmt): nested list containing the formatting info
|
|
82
|
+
row_idx (int): row index
|
|
83
|
+
col_idx (int): column index
|
|
84
|
+
mapping (Mapping): formatting info
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Fmt: nested list containing the updated formatting info
|
|
88
|
+
"""
|
|
89
|
+
for fmt_type, args in mapping.items():
|
|
90
|
+
cell = fmt[row_idx][col_idx].get(fmt_type)
|
|
91
|
+
|
|
92
|
+
if not cell:
|
|
93
|
+
fmt[row_idx][col_idx][fmt_type] = args
|
|
94
|
+
else:
|
|
95
|
+
fmt[row_idx][col_idx][fmt_type] = cell | args
|
|
96
|
+
|
|
97
|
+
return fmt
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def set_style_all(fmt: Fmt, mapping: Mapping) -> Fmt:
|
|
101
|
+
"""Set the formatting for all cells
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
fmt (Fmt): nested list containing the formatting info
|
|
105
|
+
mapping (Mapping): formatting info
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Fmt: nested list containing the formatting info
|
|
109
|
+
"""
|
|
110
|
+
for row_idx, row in enumerate(fmt):
|
|
111
|
+
for col_idx, _ in enumerate(row):
|
|
112
|
+
update_format(fmt, row_idx, col_idx, mapping)
|
|
113
|
+
|
|
114
|
+
return fmt
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def set_style_row(
|
|
118
|
+
fmt: Fmt,
|
|
119
|
+
row_idxs: int | list,
|
|
120
|
+
mapping: Mapping,
|
|
121
|
+
exlude_col_ids: int | list | None = None,
|
|
122
|
+
) -> Fmt:
|
|
123
|
+
"""Set the formatting on a row
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
fmt (Fmt): nested list containing the formatting info
|
|
127
|
+
row_idxs (int | list): The indices of the rows to be updated
|
|
128
|
+
mapping (Mapping): formatting info
|
|
129
|
+
exlude_col_ids (int | list | None, optional): Indices of the cols to be excluded
|
|
130
|
+
when setting formatting for a row. Defaults to None.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Fmt: nested list containing the formatting info
|
|
134
|
+
"""
|
|
135
|
+
if isinstance(row_idxs, int):
|
|
136
|
+
row_idxs = [row_idxs]
|
|
137
|
+
|
|
138
|
+
if exlude_col_ids is not None:
|
|
139
|
+
if isinstance(exlude_col_ids, int):
|
|
140
|
+
exlude_col_ids = [exlude_col_ids]
|
|
141
|
+
else:
|
|
142
|
+
exlude_col_ids = []
|
|
143
|
+
|
|
144
|
+
for row_idx in row_idxs:
|
|
145
|
+
for col_idx, _ in enumerate(fmt[row_idx]):
|
|
146
|
+
if col_idx not in exlude_col_ids:
|
|
147
|
+
update_format(fmt, row_idx, col_idx, mapping)
|
|
148
|
+
|
|
149
|
+
return fmt
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def set_style_col(
|
|
153
|
+
fmt: Fmt,
|
|
154
|
+
col_idxs: int | list,
|
|
155
|
+
mapping: Mapping,
|
|
156
|
+
exlude_row_ids: int | list | None = None,
|
|
157
|
+
) -> Fmt:
|
|
158
|
+
"""Set the formatting on a row
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
fmt (Fmt): nested list containing the formatting info
|
|
162
|
+
row_idxs (int | list): The indices of the rows to be updated
|
|
163
|
+
mapping (Mapping): formatting info
|
|
164
|
+
exlude_row_ids (int | list | None, optional): Indices of the rows to be excluded
|
|
165
|
+
when setting formatting for a col. Defaults to None.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Fmt: nested list containing the formatting info
|
|
169
|
+
"""
|
|
170
|
+
if isinstance(col_idxs, int):
|
|
171
|
+
col_idxs = [col_idxs]
|
|
172
|
+
|
|
173
|
+
if exlude_row_ids is not None:
|
|
174
|
+
if isinstance(exlude_row_ids, int):
|
|
175
|
+
exlude_row_ids = [exlude_row_ids]
|
|
176
|
+
else:
|
|
177
|
+
exlude_row_ids = []
|
|
178
|
+
|
|
179
|
+
for col_idx in col_idxs:
|
|
180
|
+
for row_idx, _ in enumerate(fmt):
|
|
181
|
+
if row_idx not in exlude_row_ids:
|
|
182
|
+
|
|
183
|
+
update_format(fmt, row_idx, col_idx, mapping)
|
|
184
|
+
|
|
185
|
+
return fmt
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def excel_style(row: int, col: int) -> str:
|
|
189
|
+
"""Convert given row and column number to an Excel-style cell name."""
|
|
190
|
+
result: list = []
|
|
191
|
+
while col:
|
|
192
|
+
col, rem = divmod(col - 1, 26)
|
|
193
|
+
result[:0] = LETTERS[rem]
|
|
194
|
+
return "".join(result) + str(row)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def get_cols_id_with_pattern(df: pd.DataFrame, pattern: str) -> list[int]:
|
|
198
|
+
"""Get columns indices from columns matching a regex pattern
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
df (pd.DataFrame): Input dataframe
|
|
202
|
+
pattern (str): regex pattern to get columns indices when matching
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
list[int]: list with column indices matching pattern
|
|
206
|
+
"""
|
|
207
|
+
return [idx for idx, col in enumerate(df.columns) if re.findall(pattern, col)]
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def get_string_cols_ids(df: pd.DataFrame) -> list[int]:
|
|
211
|
+
"""Get column indices of string columns
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
df (pd.DataFrame): Input dataframe
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
list[int]: list with column indices of string columns
|
|
218
|
+
"""
|
|
219
|
+
return [i for i, dtype in enumerate(df.dtypes) if dtype == "O"]
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def get_numeric_col_ids(df: pd.DataFrame) -> list[int]:
|
|
223
|
+
"""Get column indices of numeric columns
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
df (pd.DataFrame): Input dataframe
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
list[int]: list with column indices of numeric columns
|
|
230
|
+
"""
|
|
231
|
+
num_cols = df.select_dtypes("number").columns
|
|
232
|
+
return [i for i, col in enumerate(df.columns) if col in num_cols]
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def cell_formatting(
|
|
236
|
+
arr: np.ndarray,
|
|
237
|
+
default_format: Mapping | None = None,
|
|
238
|
+
blue_row_ids: int | list | None = None,
|
|
239
|
+
light_blue_row_ids: int | list | None = None,
|
|
240
|
+
light_blue_col_ids: int | list | None = None,
|
|
241
|
+
left_align_ids: int | list | None = None,
|
|
242
|
+
right_align_ids: int | list | None = None,
|
|
243
|
+
perc_col_ids: int | list | None = None,
|
|
244
|
+
perc_col_format: str | None = None,
|
|
245
|
+
blue_border: bool | None = None,
|
|
246
|
+
number_format: str | None = None,
|
|
247
|
+
):
|
|
248
|
+
"""Function to create the nested list with the shape of the input data (including columns)
|
|
249
|
+
containing dictionaries with the formatting
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
arr (np.ndarray): array representing the data
|
|
253
|
+
default_format (Mapping | None, optional): Default format applied to all cells. Defaults to None.
|
|
254
|
+
blue_row_ids (int | list | None, optional): The ids of the rows to be colored blue. Defaults to None.
|
|
255
|
+
light_blue_row_ids (int | list | None, optional): _description_. Defaults to None.
|
|
256
|
+
light_blue_col_ids (int | list | None, optional): _description_. Defaults to None.
|
|
257
|
+
left_align_ids (int | list | None, optional): _description_. Defaults to None.
|
|
258
|
+
right_align_ids (int | list | None, optional): _description_. Defaults to None.
|
|
259
|
+
perc_col_ids (int | list | None, optional): _description_. Defaults to None.
|
|
260
|
+
perc_col_format (str | None, optional): _description_. Defaults to None.
|
|
261
|
+
blue_border (bool | None, optional): _description_. Defaults to None.
|
|
262
|
+
number_format (str | None, optional): _description_. Defaults to None.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
_type_: _description_
|
|
266
|
+
"""
|
|
267
|
+
fmt = get_fmt_table(arr)
|
|
268
|
+
|
|
269
|
+
if default_format:
|
|
270
|
+
fmt = set_style_all(fmt, default_format)
|
|
271
|
+
|
|
272
|
+
if number_format:
|
|
273
|
+
fmt = set_style_all(fmt, {"number_format": {"format": number_format}})
|
|
274
|
+
|
|
275
|
+
if blue_row_ids:
|
|
276
|
+
fmt = set_style_row(fmt, blue_row_ids, STYLES["blue_white"])
|
|
277
|
+
|
|
278
|
+
if light_blue_row_ids:
|
|
279
|
+
fmt = set_style_row(fmt, light_blue_row_ids, STYLES["light_blue"])
|
|
280
|
+
|
|
281
|
+
if light_blue_col_ids:
|
|
282
|
+
fmt = set_style_col(fmt, light_blue_col_ids, STYLES["light_blue"], blue_row_ids)
|
|
283
|
+
|
|
284
|
+
if left_align_ids:
|
|
285
|
+
fmt = set_style_col(fmt, left_align_ids, STYLES["left_align"])
|
|
286
|
+
|
|
287
|
+
if right_align_ids:
|
|
288
|
+
fmt = set_style_col(fmt, right_align_ids, STYLES["right_align"])
|
|
289
|
+
|
|
290
|
+
if perc_col_ids:
|
|
291
|
+
if not perc_col_format:
|
|
292
|
+
perc_col_format = "0.0%"
|
|
293
|
+
fmt = set_style_col(
|
|
294
|
+
fmt, perc_col_ids, {"number_format": {"format": perc_col_format}}
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
if blue_border:
|
|
298
|
+
fmt = set_style_row(fmt, arr.shape[0] - 1, STYLES["blue_border_bottom"])
|
|
299
|
+
|
|
300
|
+
return fmt
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def write_worksheet(
|
|
304
|
+
ws: Any,
|
|
305
|
+
arr: np.ndarray,
|
|
306
|
+
fmt: Fmt,
|
|
307
|
+
title: str | None = None,
|
|
308
|
+
col_filter: bool | None = None,
|
|
309
|
+
autofit_columns: bool | None = None,
|
|
310
|
+
) -> None:
|
|
311
|
+
"""Writing data to worksheet. Used for writing values to cells and formatting the cells
|
|
312
|
+
and
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
ws (Any): openpyxl worksheet
|
|
316
|
+
arr (np.ndarray): array containing the input data
|
|
317
|
+
fmt (Fmt): nested list containing dictionaries with the formatting info per cell
|
|
318
|
+
title (str | None, optional): Title to be inserted above the table. Defaults to None.
|
|
319
|
+
col_filter (bool | None, optional): Set column filter in excel. Defaults to None.
|
|
320
|
+
autofit_columns (bool | None, optional): Automatically fit column width. Defaults to None.
|
|
321
|
+
"""
|
|
322
|
+
for row_idx, row in enumerate(arr):
|
|
323
|
+
for col_idx, _ in enumerate(row):
|
|
324
|
+
value = arr[row_idx][col_idx]
|
|
325
|
+
# Cell indices are not zero-indexed but one-indexed
|
|
326
|
+
cell = ws.cell(row_idx + 1, col_idx + 1, value)
|
|
327
|
+
# Get formatting for specific cell
|
|
328
|
+
cell_fmt = fmt[row_idx][col_idx]
|
|
329
|
+
for t, kwa in cell_fmt.items():
|
|
330
|
+
# The api for setting different kind of formatting options is not
|
|
331
|
+
# consistent therefore depeding on the formatting we want to set we have
|
|
332
|
+
# to use a different strategy
|
|
333
|
+
if t == "number_format":
|
|
334
|
+
# cell.number_format = "0.0"
|
|
335
|
+
setattr(cell, t, kwa["format"])
|
|
336
|
+
elif t.startswith("border"):
|
|
337
|
+
# cell.border = Border(bottom=Side(color="00a0e6"))
|
|
338
|
+
type_, side = t.split("_")
|
|
339
|
+
side_spec = Side(**kwa)
|
|
340
|
+
setattr(cell, type_, LOOKUP[type_](**{side: side_spec}))
|
|
341
|
+
else:
|
|
342
|
+
# cell.font = Font(color="B1D9F5", bold=True)
|
|
343
|
+
setattr(cell, t, LOOKUP[t](**kwa))
|
|
344
|
+
|
|
345
|
+
if col_filter:
|
|
346
|
+
filters = ws.auto_filter
|
|
347
|
+
filters.ref = f"A1:{excel_style(len(fmt), len(fmt[0]))}"
|
|
348
|
+
|
|
349
|
+
if autofit_columns:
|
|
350
|
+
_autofit_columns(ws)
|
|
351
|
+
|
|
352
|
+
if title:
|
|
353
|
+
_insert_title(ws, title)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
# def _set_column_width(ws: Any, column_widths: list) -> None:
|
|
357
|
+
# for i, column_number in enumerate(range(ws.max_column)):
|
|
358
|
+
# column_letter = get_column_letter(column_letter)
|
|
359
|
+
# column_width = column_widths[i]
|
|
360
|
+
# ws.column_dimensions[column_letter].width = column_width
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _autofit_columns(ws: Any) -> None:
|
|
364
|
+
column_letters = tuple(
|
|
365
|
+
get_column_letter(col_number + 1) for col_number in range(ws.max_column)
|
|
366
|
+
)
|
|
367
|
+
for column_letter in column_letters:
|
|
368
|
+
ws.column_dimensions[column_letter].auto_fit = True
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _insert_title(ws: Any, title: str) -> None:
|
|
372
|
+
ws.insert_rows(0)
|
|
373
|
+
cell = ws.cell(1, 1, title)
|
|
374
|
+
for t, kwa in STYLES["title_bold"].items():
|
|
375
|
+
setattr(cell, t, LOOKUP[t](**kwa))
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def write_table(
|
|
379
|
+
data: pd.DataFrame | dict[str, pd.DataFrame],
|
|
380
|
+
file: str,
|
|
381
|
+
header_row: int = 0,
|
|
382
|
+
title: str | dict[str, str] | None = None,
|
|
383
|
+
total_row: bool | None = None,
|
|
384
|
+
total_col: bool | None = None,
|
|
385
|
+
right_align_ids: list | None = None,
|
|
386
|
+
right_align_pattern: str | None = None,
|
|
387
|
+
right_align_numeric: bool | None = True,
|
|
388
|
+
left_align_ids: list | None = None,
|
|
389
|
+
left_align_pattern: str | None = None,
|
|
390
|
+
left_align_string: bool | None = True,
|
|
391
|
+
perc_ids: list | None = None,
|
|
392
|
+
perc_pattern: str | None = None,
|
|
393
|
+
perc_col_format: str | None = None,
|
|
394
|
+
blue_border: bool | None = True,
|
|
395
|
+
number_format: str = "0.0",
|
|
396
|
+
autofit_columns: bool | None = False,
|
|
397
|
+
col_filter: bool | None = False,
|
|
398
|
+
style: str = "old",
|
|
399
|
+
):
|
|
400
|
+
"""_summary_
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
data (pd.DataFrame | dict[pd.DataFrame]): dataframe or dicts with dataframes
|
|
404
|
+
name (str): name of excel file
|
|
405
|
+
header_row (int): Set the number of rows to be dark blue (zero-indexed). Defaults to 0 (top row)
|
|
406
|
+
title (str): Set the title above the table. In the case of multiple tables provide a dict in
|
|
407
|
+
which te keys correspond to the sheet name. Defaults to none
|
|
408
|
+
total_row (bool, optional): Color bottom row blue
|
|
409
|
+
total_col (bool, optional): Color last column blue.
|
|
410
|
+
right_align_ids (list, optional): The ids of the columns to right align. Defaults to None
|
|
411
|
+
right_align_pattern (str, optional): Pattern of columns to right align. Defaults to None.
|
|
412
|
+
right_align_numeric (bool, optional): Right align numeric columns. Defaults to True.
|
|
413
|
+
left_align_ids (list, optional): The ids of the columns to left align. Defaults to None.
|
|
414
|
+
left_align_pattern (str, optional): Pattern of columns to left align. Defaults to None.
|
|
415
|
+
left_align_string (bool, optional): Left align string columns. Defaults to True.
|
|
416
|
+
perc_ids (list, optional): The ids of the columns to format as percentage. Defaults to None.
|
|
417
|
+
perc_pattern (str, optional): The pattern of columns to format as percentage. Defaults to None.
|
|
418
|
+
perc_col_format (str, optional): The formatting string of percentage columns. Defaults to None.
|
|
419
|
+
col_filter (bool, optional): Set filter on columns. Defaults to False.
|
|
420
|
+
"""
|
|
421
|
+
|
|
422
|
+
wb = Workbook()
|
|
423
|
+
# Empty sheet is created on Workbook creation
|
|
424
|
+
del wb["Sheet"]
|
|
425
|
+
|
|
426
|
+
set_global_style(style)
|
|
427
|
+
|
|
428
|
+
if not isinstance(data, dict):
|
|
429
|
+
data = {"Sheet1": data}
|
|
430
|
+
|
|
431
|
+
for sheet_name, df in data.items():
|
|
432
|
+
arr = df_to_array(df)
|
|
433
|
+
|
|
434
|
+
blue_rows = []
|
|
435
|
+
light_blue_rows = []
|
|
436
|
+
light_blue_cols = []
|
|
437
|
+
r_align_ids = []
|
|
438
|
+
l_align_ids = []
|
|
439
|
+
p_ids = []
|
|
440
|
+
title_tbl = None
|
|
441
|
+
|
|
442
|
+
if isinstance(header_row, int):
|
|
443
|
+
blue_rows.extend(list(range(0, header_row + 1)))
|
|
444
|
+
|
|
445
|
+
if title:
|
|
446
|
+
if isinstance(title, str):
|
|
447
|
+
title_tbl = title
|
|
448
|
+
elif isinstance(title, dict):
|
|
449
|
+
title_tbl = title.get(sheet_name)
|
|
450
|
+
|
|
451
|
+
if right_align_ids:
|
|
452
|
+
r_align_ids.extend(right_align_ids)
|
|
453
|
+
|
|
454
|
+
if right_align_pattern:
|
|
455
|
+
r_align_ids.extend(get_cols_id_with_pattern(df, right_align_pattern))
|
|
456
|
+
|
|
457
|
+
if right_align_numeric:
|
|
458
|
+
r_align_ids.extend(get_numeric_col_ids(df))
|
|
459
|
+
|
|
460
|
+
if left_align_ids:
|
|
461
|
+
r_align_ids.extend(left_align_ids)
|
|
462
|
+
|
|
463
|
+
if left_align_pattern:
|
|
464
|
+
l_align_ids.extend(get_cols_id_with_pattern(df, left_align_pattern))
|
|
465
|
+
|
|
466
|
+
if left_align_string:
|
|
467
|
+
l_align_ids.extend(get_string_cols_ids(df))
|
|
468
|
+
|
|
469
|
+
if perc_ids:
|
|
470
|
+
p_ids.extend(perc_ids)
|
|
471
|
+
|
|
472
|
+
if perc_pattern:
|
|
473
|
+
r_id = get_cols_id_with_pattern(df, perc_pattern)
|
|
474
|
+
p_ids.extend(r_id)
|
|
475
|
+
r_align_ids.extend(r_id)
|
|
476
|
+
|
|
477
|
+
if total_row:
|
|
478
|
+
light_blue_rows.append(arr.shape[0] - 1)
|
|
479
|
+
|
|
480
|
+
if total_col:
|
|
481
|
+
light_blue_cols.append(arr.shape[1] - 1)
|
|
482
|
+
|
|
483
|
+
ws = wb.create_sheet(sheet_name)
|
|
484
|
+
|
|
485
|
+
fmt = cell_formatting(
|
|
486
|
+
arr=arr,
|
|
487
|
+
default_format=STYLES["calibri"],
|
|
488
|
+
blue_row_ids=blue_rows,
|
|
489
|
+
light_blue_row_ids=light_blue_rows,
|
|
490
|
+
light_blue_col_ids=light_blue_cols,
|
|
491
|
+
left_align_ids=l_align_ids,
|
|
492
|
+
right_align_ids=r_align_ids,
|
|
493
|
+
perc_col_ids=p_ids,
|
|
494
|
+
perc_col_format=perc_col_format,
|
|
495
|
+
number_format=number_format,
|
|
496
|
+
blue_border=blue_border,
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
write_worksheet(
|
|
500
|
+
ws=ws,
|
|
501
|
+
arr=arr,
|
|
502
|
+
fmt=fmt,
|
|
503
|
+
title=title_tbl,
|
|
504
|
+
col_filter=col_filter,
|
|
505
|
+
autofit_columns=autofit_columns,
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
wb.save(file)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from functools import reduce
|
|
4
|
+
from typing import Iterable, Union
|
|
5
|
+
|
|
6
|
+
import polars as pl
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def agg_multiple(
|
|
10
|
+
cols: Union[str, list[str]], funcs: Union[str, list[str]]
|
|
11
|
+
) -> list[pl.Expr]:
|
|
12
|
+
if isinstance(cols, str):
|
|
13
|
+
cols = [cols]
|
|
14
|
+
if isinstance(funcs, str):
|
|
15
|
+
funcs = [funcs]
|
|
16
|
+
|
|
17
|
+
exprs = []
|
|
18
|
+
for col in cols:
|
|
19
|
+
for func in funcs:
|
|
20
|
+
exprs.append(getattr(pl.col(col), func)().alias(f"{col}_{func}"))
|
|
21
|
+
|
|
22
|
+
return exprs
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def complete(df: pl.DataFrame, cols: Iterable[str]) -> pl.DataFrame:
|
|
26
|
+
combs = reduce(
|
|
27
|
+
lambda x, y: x.join(y, how="cross"),
|
|
28
|
+
[df.select(pl.col(col).unique()) for col in cols],
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return combs.join(df, on=cols, how="left")
|