flatbread 0.1.5__tar.gz → 0.2.0__tar.gz
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.
- flatbread-0.2.0/.gitignore +10 -0
- {flatbread-0.1.5 → flatbread-0.2.0}/PKG-INFO +5 -2
- flatbread-0.2.0/flatbread/__init__.py +17 -0
- {flatbread-0.1.5 → flatbread-0.2.0}/flatbread/accessors/dataframe.py +113 -32
- {flatbread-0.1.5 → flatbread-0.2.0}/flatbread/accessors/series.py +114 -29
- flatbread-0.2.0/flatbread/axes.py +368 -0
- flatbread-0.2.0/flatbread/chaining.py +141 -0
- flatbread-0.2.0/flatbread/config/__init__.py +4 -0
- flatbread-0.2.0/flatbread/config/config.defaults.json +82 -0
- flatbread-0.2.0/flatbread/config/service.py +144 -0
- flatbread-0.2.0/flatbread/io/excel.py +209 -0
- flatbread-0.2.0/flatbread/output/excel/__init__.py +1 -0
- flatbread-0.2.0/flatbread/output/excel/excel.py +209 -0
- flatbread-0.2.0/flatbread/output/formats.py +102 -0
- flatbread-0.2.0/flatbread/output/html/__init__.py +2 -0
- flatbread-0.2.0/flatbread/output/html/constants.py +5 -0
- flatbread-0.2.0/flatbread/output/html/display.py +310 -0
- flatbread-0.2.0/flatbread/output/html/tablespec.py +265 -0
- {flatbread-0.1.5/flatbread/render → flatbread-0.2.0/flatbread/output/html/templates}/template.jinja.html +6 -4
- flatbread-0.2.0/flatbread/testing/dataframe.py +162 -0
- flatbread-0.2.0/flatbread/tooling.py +156 -0
- flatbread-0.2.0/flatbread/transforms/aggregation.py +209 -0
- flatbread-0.2.0/flatbread/transforms/percentages.py +419 -0
- flatbread-0.2.0/flatbread/transforms/totals.py +262 -0
- flatbread-0.2.0/flatbread/types.py +4 -0
- {flatbread-0.1.5 → flatbread-0.2.0}/pyproject.toml +5 -2
- flatbread-0.2.0/tests/transforms/__init__.py +0 -0
- flatbread-0.2.0/tests/transforms/test_percentages.py +223 -0
- flatbread-0.2.0/tests/transforms/test_totals.py +204 -0
- flatbread-0.2.0/uv.lock +2093 -0
- flatbread-0.1.5/.gitignore +0 -5
- flatbread-0.1.5/flatbread/__init__.py +0 -7
- flatbread-0.1.5/flatbread/agg/aggregation.py +0 -235
- flatbread-0.1.5/flatbread/agg/totals.py +0 -171
- flatbread-0.1.5/flatbread/chaining.py +0 -116
- flatbread-0.1.5/flatbread/config/config.defaults.json +0 -27
- flatbread-0.1.5/flatbread/config.py +0 -133
- flatbread-0.1.5/flatbread/percentages.py +0 -224
- flatbread-0.1.5/flatbread/render/config.py +0 -69
- flatbread-0.1.5/flatbread/render/constants.py +0 -62
- flatbread-0.1.5/flatbread/render/display.py +0 -231
- flatbread-0.1.5/flatbread/render/tablespec.py +0 -191
- flatbread-0.1.5/flatbread/render/template.py +0 -24
- flatbread-0.1.5/flatbread/tooling.py +0 -244
- flatbread-0.1.5/tests/aggregate/test_percentages.py +0 -45
- flatbread-0.1.5/tests/aggregate/test_totals.py +0 -159
- {flatbread-0.1.5 → flatbread-0.2.0}/environment.yml +0 -0
- {flatbread-0.1.5 → flatbread-0.2.0}/flatbread/accessors/index.py +0 -0
- {flatbread-0.1.5/tests → flatbread-0.2.0/flatbread/output}/__init__.py +0 -0
- /flatbread-0.1.5/flatbread/testing/dataframe.py → /flatbread-0.2.0/flatbread/testing/.ipynb_checkpoints/dataframe-checkpoint.py +0 -0
- {flatbread-0.1.5 → flatbread-0.2.0}/license.md +0 -0
- {flatbread-0.1.5 → flatbread-0.2.0}/readme.md +0 -0
- {flatbread-0.1.5/tests/aggregate → flatbread-0.2.0/tests}/__init__.py +0 -0
- {flatbread-0.1.5 → flatbread-0.2.0}/tests/test_axes.py +0 -0
- {flatbread-0.1.5 → flatbread-0.2.0}/tests/test_levels.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flatbread
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Pandas extension for aggregation and tabular display
|
|
5
|
-
Project-URL: Homepage, https://github.com/
|
|
5
|
+
Project-URL: Homepage, https://github.com/flatbread-dataframes/flatbread
|
|
6
6
|
Author-email: "L.C. Vriend" <vanboefer@gmail.com>
|
|
7
7
|
License: # GNU GENERAL PUBLIC LICENSE
|
|
8
8
|
|
|
@@ -693,6 +693,9 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
693
693
|
Requires-Python: >=3.10
|
|
694
694
|
Requires-Dist: jinja2
|
|
695
695
|
Requires-Dist: pandas>=2.0.0
|
|
696
|
+
Provides-Extra: dev
|
|
697
|
+
Requires-Dist: ipykernel; extra == 'dev'
|
|
698
|
+
Requires-Dist: jupyter; extra == 'dev'
|
|
696
699
|
Description-Content-Type: text/markdown
|
|
697
700
|
|
|
698
701
|
# Flatbread
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from flatbread.accessors.dataframe import PitaFrame
|
|
6
|
+
from flatbread.accessors.series import PitaSeries
|
|
7
|
+
|
|
8
|
+
class DataFrame(pd.DataFrame):
|
|
9
|
+
pita: PitaFrame
|
|
10
|
+
|
|
11
|
+
class Series(pd.Series):
|
|
12
|
+
pita: PitaSeries
|
|
13
|
+
|
|
14
|
+
from flatbread.config import DEFAULTS
|
|
15
|
+
import flatbread.accessors.dataframe
|
|
16
|
+
import flatbread.accessors.series
|
|
17
|
+
import flatbread.accessors.index
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
from typing import Any, Callable
|
|
2
|
+
from pathlib import Path
|
|
2
3
|
|
|
3
4
|
import pandas as pd
|
|
4
5
|
|
|
5
|
-
import flatbread.percentages as pct
|
|
6
|
-
import flatbread.
|
|
7
|
-
import flatbread.
|
|
8
|
-
import flatbread.
|
|
9
|
-
from flatbread.
|
|
6
|
+
import flatbread.transforms.percentages as pct
|
|
7
|
+
import flatbread.transforms.aggregation as agg
|
|
8
|
+
import flatbread.transforms.totals as totals
|
|
9
|
+
import flatbread.axes as axes
|
|
10
|
+
from flatbread.types import Axis, Level
|
|
11
|
+
from flatbread.output.html import PitaDisplayMixin
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
@pd.api.extensions.register_dataframe_accessor("pita")
|
|
@@ -19,8 +21,8 @@ class PitaFrame(PitaDisplayMixin):
|
|
|
19
21
|
self,
|
|
20
22
|
aggfunc: str|Callable,
|
|
21
23
|
*args,
|
|
22
|
-
axis:
|
|
23
|
-
label: str = None,
|
|
24
|
+
axis: Axis = 0,
|
|
25
|
+
label: str|None = None,
|
|
24
26
|
ignore_keys: str|list[str]|None = None,
|
|
25
27
|
_fill: str = '',
|
|
26
28
|
**kwargs,
|
|
@@ -32,7 +34,7 @@ class PitaFrame(PitaDisplayMixin):
|
|
|
32
34
|
----------
|
|
33
35
|
aggfunc (str|Callable):
|
|
34
36
|
Function to use for aggregating the data.
|
|
35
|
-
axis (int):
|
|
37
|
+
axis (int | Literal["index", "columns", "both"]):
|
|
36
38
|
Axis to aggregate. Default 0.
|
|
37
39
|
label (str|None):
|
|
38
40
|
Label for the aggregation row/column. Default None.
|
|
@@ -62,11 +64,12 @@ class PitaFrame(PitaDisplayMixin):
|
|
|
62
64
|
def add_subagg(
|
|
63
65
|
self,
|
|
64
66
|
aggfunc: str|Callable,
|
|
65
|
-
axis:
|
|
67
|
+
axis: Axis = 0,
|
|
66
68
|
level: int|str|list[int|str] = 0,
|
|
67
|
-
label: str = None,
|
|
69
|
+
label: str|None = None,
|
|
68
70
|
include_level_name: bool = False,
|
|
69
71
|
ignore_keys: str|list[str]|None = None,
|
|
72
|
+
skip_single_rows: bool = True,
|
|
70
73
|
_fill: str = '',
|
|
71
74
|
) -> pd.DataFrame:
|
|
72
75
|
"""
|
|
@@ -76,14 +79,18 @@ class PitaFrame(PitaDisplayMixin):
|
|
|
76
79
|
----------
|
|
77
80
|
aggfunc (str|Callable):
|
|
78
81
|
Function to use for aggregating the data.
|
|
79
|
-
axis (int):
|
|
82
|
+
axis (int | Literal["index", "columns", "both"]):
|
|
80
83
|
Axis to aggregate. Default 0.
|
|
81
84
|
levels (int|str|list[int|str]):
|
|
82
85
|
Levels to aggregate. Default 0.
|
|
83
86
|
label (str|None):
|
|
84
87
|
Label for the aggregation row/column. Default None.
|
|
88
|
+
include_level_name (bool):
|
|
89
|
+
Whether to add level name to subtotal label.
|
|
85
90
|
ignore_keys (str|list[str]|None):
|
|
86
|
-
Keys of rows to ignore when aggregating.
|
|
91
|
+
Keys of rows to ignore when aggregating. Default 'Totals'
|
|
92
|
+
skip_single_rows (bool):
|
|
93
|
+
Whether to skip single rows when aggregating. Default True.
|
|
87
94
|
*args:
|
|
88
95
|
Positional arguments to pass to func.
|
|
89
96
|
**kwargs:
|
|
@@ -102,18 +109,19 @@ class PitaFrame(PitaDisplayMixin):
|
|
|
102
109
|
label = label,
|
|
103
110
|
include_level_name = include_level_name,
|
|
104
111
|
ignore_keys = ignore_keys,
|
|
112
|
+
skip_single_rows = skip_single_rows,
|
|
105
113
|
_fill = _fill,
|
|
106
114
|
)
|
|
107
115
|
|
|
108
116
|
#region percentages
|
|
109
117
|
def as_percentages(
|
|
110
118
|
self,
|
|
111
|
-
axis:
|
|
119
|
+
axis: Axis = 2,
|
|
112
120
|
label_totals: str|None = None,
|
|
113
121
|
ignore_keys: str|list[str]|None = None,
|
|
114
122
|
ndigits: int|None = None,
|
|
115
123
|
base: int = 1,
|
|
116
|
-
apportioned_rounding: bool =
|
|
124
|
+
apportioned_rounding: bool|None = None,
|
|
117
125
|
) -> pd.DataFrame:
|
|
118
126
|
"""
|
|
119
127
|
Transform data to percentages based on specified axis.
|
|
@@ -122,7 +130,7 @@ class PitaFrame(PitaDisplayMixin):
|
|
|
122
130
|
----------
|
|
123
131
|
data (pd.DataFrame):
|
|
124
132
|
The input DataFrame.
|
|
125
|
-
axis (int):
|
|
133
|
+
axis (int | Literal["index", "columns", "both"]):
|
|
126
134
|
The axis along which percentages are calculated. Percentages are based on:
|
|
127
135
|
- when axis is 2 then grand total
|
|
128
136
|
- when axis is 1 then column totals
|
|
@@ -157,14 +165,14 @@ class PitaFrame(PitaDisplayMixin):
|
|
|
157
165
|
|
|
158
166
|
def add_percentages(
|
|
159
167
|
self,
|
|
160
|
-
axis:
|
|
168
|
+
axis: Axis = 2,
|
|
161
169
|
label_n: str|None = None,
|
|
162
170
|
label_pct: str|None = None,
|
|
163
171
|
label_totals: str|None = None,
|
|
164
172
|
ignore_keys: str|list[str]|None = None,
|
|
165
173
|
ndigits: int|None = None,
|
|
166
174
|
base: int = 1,
|
|
167
|
-
apportioned_rounding: bool =
|
|
175
|
+
apportioned_rounding: bool|None = None,
|
|
168
176
|
interleaf: bool = False,
|
|
169
177
|
) -> pd.DataFrame:
|
|
170
178
|
"""
|
|
@@ -174,7 +182,7 @@ class PitaFrame(PitaDisplayMixin):
|
|
|
174
182
|
----------
|
|
175
183
|
data (pd.DataFrame):
|
|
176
184
|
The input DataFrame.
|
|
177
|
-
axis (int):
|
|
185
|
+
axis (int | Literal["index", "columns", "both"]):
|
|
178
186
|
The axis along which percentages are calculated. Percentages are based on:
|
|
179
187
|
- when axis is 2 then grand total
|
|
180
188
|
- when axis is 1 then row totals
|
|
@@ -219,7 +227,7 @@ class PitaFrame(PitaDisplayMixin):
|
|
|
219
227
|
#region totals
|
|
220
228
|
def add_totals(
|
|
221
229
|
self,
|
|
222
|
-
axis:
|
|
230
|
+
axis: Axis = 2,
|
|
223
231
|
label: str|None = None,
|
|
224
232
|
ignore_keys: str|list[str]|None = None,
|
|
225
233
|
_fill: str = '',
|
|
@@ -229,7 +237,7 @@ class PitaFrame(PitaDisplayMixin):
|
|
|
229
237
|
|
|
230
238
|
Parameters
|
|
231
239
|
----------
|
|
232
|
-
axis (int):
|
|
240
|
+
axis (int | Literal["index", "columns", "both"]):
|
|
233
241
|
Axis to sum. If axis == 2 then add totals to both rows and columns. Default 2.
|
|
234
242
|
label (str|None):
|
|
235
243
|
Label for the totals row/column. Default 'Totals'.
|
|
@@ -241,7 +249,7 @@ class PitaFrame(PitaDisplayMixin):
|
|
|
241
249
|
pd.DataFrame:
|
|
242
250
|
Table with total rows/columns added.
|
|
243
251
|
"""
|
|
244
|
-
return
|
|
252
|
+
return totals.add_totals( # type: ignore
|
|
245
253
|
self._obj,
|
|
246
254
|
axis = axis,
|
|
247
255
|
label = label,
|
|
@@ -251,11 +259,12 @@ class PitaFrame(PitaDisplayMixin):
|
|
|
251
259
|
|
|
252
260
|
def add_subtotals(
|
|
253
261
|
self,
|
|
254
|
-
axis:
|
|
262
|
+
axis: Axis = 2,
|
|
255
263
|
level: int|str|list[int|str] = 0,
|
|
256
264
|
label: str|None = None,
|
|
257
265
|
include_level_name: bool = False,
|
|
258
266
|
ignore_keys: str|list[str]|None = None,
|
|
267
|
+
skip_single_rows: bool = True,
|
|
259
268
|
_fill: str = '',
|
|
260
269
|
) -> pd.DataFrame:
|
|
261
270
|
"""
|
|
@@ -263,47 +272,119 @@ class PitaFrame(PitaDisplayMixin):
|
|
|
263
272
|
|
|
264
273
|
Parameters
|
|
265
274
|
----------
|
|
266
|
-
axis (int):
|
|
275
|
+
axis (int | Literal["index", "columns", "both"]):
|
|
267
276
|
Axis to sum. If axis == 2 then add totals to both rows and columns. Default 2.
|
|
268
277
|
levels (int|str|list[int|str]):
|
|
269
278
|
Levels to sum with func. Default 0.
|
|
270
279
|
label (str|None):
|
|
271
280
|
Label for the subtotals row/column. Default 'Subtotals'.
|
|
281
|
+
include_level_name (bool):
|
|
282
|
+
Whether to add level name to subtotal label.
|
|
272
283
|
ignore_keys (str|list[str]|None):
|
|
273
284
|
Keys of rows to ignore when aggregating. Default 'Totals'
|
|
285
|
+
skip_single_rows (bool):
|
|
286
|
+
Whether to skip single rows when aggregating. Default True.
|
|
274
287
|
|
|
275
288
|
Returns
|
|
276
289
|
-------
|
|
277
290
|
pd.DataFrame:
|
|
278
291
|
Table with total rows/columns added.
|
|
279
292
|
"""
|
|
280
|
-
return
|
|
293
|
+
return totals.add_subtotals( # type: ignore
|
|
281
294
|
self._obj,
|
|
282
295
|
axis = axis,
|
|
283
296
|
level = level,
|
|
284
297
|
label = label,
|
|
285
298
|
include_level_name = include_level_name,
|
|
286
299
|
ignore_keys = ignore_keys,
|
|
300
|
+
skip_single_rows = skip_single_rows,
|
|
287
301
|
_fill = _fill,
|
|
288
302
|
)
|
|
289
303
|
|
|
290
304
|
def sort_totals(
|
|
291
305
|
self,
|
|
292
|
-
axis:
|
|
293
|
-
level:
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
306
|
+
axis: Axis = 0,
|
|
307
|
+
level: Level|list[Level]|None = None,
|
|
308
|
+
labels: list[str]|None = None,
|
|
309
|
+
totals_last: bool = True,
|
|
310
|
+
sort_remaining: bool = True,
|
|
311
|
+
) -> pd.DataFrame:
|
|
312
|
+
"""
|
|
313
|
+
Sort index/columns to position totals and subtotals at start or end within groups.
|
|
314
|
+
|
|
315
|
+
Convenience function that sorts common aggregate labels (totals, subtotals) to
|
|
316
|
+
their appropriate positions, while leaving other items in their existing order.
|
|
317
|
+
Uses default labels from flatbread configuration unless custom labels are provided.
|
|
318
|
+
|
|
319
|
+
Parameters
|
|
320
|
+
----------
|
|
321
|
+
axis : Axis, default 0
|
|
322
|
+
Axis to sort along:
|
|
323
|
+
- 0 or 'index': sort the index (rows)
|
|
324
|
+
- 1 or 'columns': sort the columns
|
|
325
|
+
level : Level | list[Level] | None, default None
|
|
326
|
+
Index level(s) to sort. Can be level number(s), level name(s), or None for all levels.
|
|
327
|
+
labels : list[str] | None, default None
|
|
328
|
+
Custom labels to treat as totals/subtotals. If None, uses default labels from
|
|
329
|
+
flatbread configuration ('Totals', 'Subtotals').
|
|
330
|
+
totals_last : bool, default True
|
|
331
|
+
Whether to place totals/subtotals at the end (True) or beginning (False) of each group.
|
|
332
|
+
sort_remaining : bool, default True
|
|
333
|
+
Whether to sort non-target levels alphabetically.
|
|
334
|
+
|
|
335
|
+
Returns
|
|
336
|
+
-------
|
|
337
|
+
pd.DataFrame
|
|
338
|
+
DataFrame with totals/subtotals repositioned according to the specified parameters.
|
|
339
|
+
"""
|
|
340
|
+
return axes.sort_totals( # type: ignore
|
|
297
341
|
self._obj,
|
|
298
342
|
axis = axis,
|
|
299
343
|
level = level,
|
|
300
|
-
|
|
344
|
+
labels = labels,
|
|
345
|
+
totals_last = totals_last,
|
|
346
|
+
sort_remaining = sort_remaining,
|
|
301
347
|
)
|
|
302
348
|
|
|
303
349
|
def drop_totals(
|
|
304
350
|
self
|
|
305
351
|
):
|
|
306
|
-
return
|
|
352
|
+
return totals.drop_totals(self._obj)
|
|
353
|
+
|
|
354
|
+
# region io
|
|
355
|
+
def export_excel(
|
|
356
|
+
self,
|
|
357
|
+
filepath: str | Path,
|
|
358
|
+
title: str | None = None,
|
|
359
|
+
number_formats: dict | None = None,
|
|
360
|
+
border_specs: dict | None = None,
|
|
361
|
+
**kwargs
|
|
362
|
+
) -> None:
|
|
363
|
+
"""
|
|
364
|
+
Export DataFrame to Excel with automatic formatting based on flatbread configuration.
|
|
365
|
+
|
|
366
|
+
Parameters
|
|
367
|
+
----------
|
|
368
|
+
filepath : str | Path
|
|
369
|
+
Path to save the Excel file
|
|
370
|
+
title : str, optional
|
|
371
|
+
Title for the worksheet
|
|
372
|
+
number_formats : dict, optional
|
|
373
|
+
Custom number formats (overrides auto-detected ones)
|
|
374
|
+
border_specs : dict, optional
|
|
375
|
+
Custom border specifications (merged with margin borders)
|
|
376
|
+
**kwargs
|
|
377
|
+
Additional arguments passed to pandasxl WorksheetManager
|
|
378
|
+
"""
|
|
379
|
+
from flatbread.output.excel import export_excel
|
|
380
|
+
return export_excel(
|
|
381
|
+
self._obj,
|
|
382
|
+
filepath,
|
|
383
|
+
title=title,
|
|
384
|
+
number_formats=number_formats,
|
|
385
|
+
border_specs=border_specs,
|
|
386
|
+
**kwargs
|
|
387
|
+
)
|
|
307
388
|
|
|
308
389
|
# region tooling
|
|
309
390
|
def add_level(
|
|
@@ -334,7 +415,7 @@ class PitaFrame(PitaDisplayMixin):
|
|
|
334
415
|
pd.DataFrame:
|
|
335
416
|
DataFrame with the new level added to the specified axis.
|
|
336
417
|
"""
|
|
337
|
-
return
|
|
418
|
+
return axes.add_level(
|
|
338
419
|
self._obj,
|
|
339
420
|
value = value,
|
|
340
421
|
level = level,
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
from typing import Any, Callable
|
|
1
|
+
from typing import Any, Callable, Hashable, Literal, TypeAlias
|
|
2
|
+
from pathlib import Path
|
|
2
3
|
|
|
3
4
|
import pandas as pd
|
|
4
5
|
|
|
5
|
-
import flatbread.percentages as pct
|
|
6
|
-
import flatbread.
|
|
7
|
-
import flatbread.
|
|
8
|
-
import flatbread.
|
|
9
|
-
from flatbread.
|
|
6
|
+
import flatbread.transforms.percentages as pct
|
|
7
|
+
import flatbread.transforms.aggregation as agg
|
|
8
|
+
import flatbread.transforms.totals as totals
|
|
9
|
+
import flatbread.axes as axes
|
|
10
|
+
from flatbread.types import Axis, Level
|
|
11
|
+
from flatbread.output.html import PitaDisplayMixin
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
@pd.api.extensions.register_series_accessor("pita")
|
|
@@ -19,7 +21,7 @@ class PitaSeries(PitaDisplayMixin):
|
|
|
19
21
|
self,
|
|
20
22
|
aggfunc: str|Callable,
|
|
21
23
|
*args,
|
|
22
|
-
label: str = None,
|
|
24
|
+
label: str|None = None,
|
|
23
25
|
ignore_keys: str|list[str]|None = None,
|
|
24
26
|
_fill: str = '',
|
|
25
27
|
**kwargs,
|
|
@@ -58,10 +60,11 @@ class PitaSeries(PitaDisplayMixin):
|
|
|
58
60
|
def add_subagg(
|
|
59
61
|
self,
|
|
60
62
|
aggfunc: str|Callable,
|
|
61
|
-
level:
|
|
62
|
-
label: str = None,
|
|
63
|
+
level: Level|list[Level] = 0,
|
|
64
|
+
label: str|None = None,
|
|
63
65
|
include_level_name: bool = False,
|
|
64
66
|
ignore_keys: str|list[str]|None = None,
|
|
67
|
+
skip_single_rows: bool = True,
|
|
65
68
|
_fill: str = '',
|
|
66
69
|
) -> pd.Series:
|
|
67
70
|
"""
|
|
@@ -71,12 +74,16 @@ class PitaSeries(PitaDisplayMixin):
|
|
|
71
74
|
----------
|
|
72
75
|
aggfunc (str|Callable):
|
|
73
76
|
Function to use for aggregating the data.
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
level (int|str|list[int|str]):
|
|
78
|
+
Level(s) to aggregate with func. Default 0.
|
|
76
79
|
label (str|None):
|
|
77
80
|
Label for the aggregated rows. Default None.
|
|
81
|
+
include_level_name (bool):
|
|
82
|
+
Whether to add level name to subtotal label.
|
|
78
83
|
ignore_keys (str|list[str]|None):
|
|
79
|
-
Keys of rows to ignore when aggregating.
|
|
84
|
+
Keys of rows to ignore when aggregating. Default 'Totals'
|
|
85
|
+
skip_single_rows (bool):
|
|
86
|
+
Whether to skip single rows when aggregating. Default True.
|
|
80
87
|
*args:
|
|
81
88
|
Positional arguments to pass to func.
|
|
82
89
|
**kwargs:
|
|
@@ -94,6 +101,7 @@ class PitaSeries(PitaDisplayMixin):
|
|
|
94
101
|
label = label,
|
|
95
102
|
include_level_name = include_level_name,
|
|
96
103
|
ignore_keys = ignore_keys,
|
|
104
|
+
skip_single_rows = skip_single_rows,
|
|
97
105
|
_fill = _fill,
|
|
98
106
|
)
|
|
99
107
|
|
|
@@ -131,7 +139,7 @@ class PitaSeries(PitaDisplayMixin):
|
|
|
131
139
|
Series reporting the count of each value in the original series.
|
|
132
140
|
"""
|
|
133
141
|
s = self._obj if fillna is None else self._obj.fillna(fillna)
|
|
134
|
-
result = s.value_counts().rename(label_n).pipe(
|
|
142
|
+
result = s.value_counts().rename(label_n).pipe(totals.add_totals)
|
|
135
143
|
if add_pct:
|
|
136
144
|
return result.pipe(
|
|
137
145
|
pct.add_percentages,
|
|
@@ -145,10 +153,11 @@ class PitaSeries(PitaDisplayMixin):
|
|
|
145
153
|
#region percentages
|
|
146
154
|
def as_percentages(
|
|
147
155
|
self,
|
|
148
|
-
label_pct: str = None,
|
|
156
|
+
label_pct: str|None = None,
|
|
149
157
|
label_totals: str|None = None,
|
|
150
|
-
ndigits: int = None,
|
|
158
|
+
ndigits: int|None = None,
|
|
151
159
|
base: int = 1,
|
|
160
|
+
apportioned_rounding: bool|None = None,
|
|
152
161
|
) -> pd.Series:
|
|
153
162
|
"""
|
|
154
163
|
Transform data into percentages.
|
|
@@ -177,6 +186,7 @@ class PitaSeries(PitaDisplayMixin):
|
|
|
177
186
|
label_totals = label_totals,
|
|
178
187
|
ndigits = ndigits,
|
|
179
188
|
base = base,
|
|
189
|
+
apportioned_rounding = apportioned_rounding,
|
|
180
190
|
)
|
|
181
191
|
|
|
182
192
|
def as_pct(self, *args, **kwargs):
|
|
@@ -189,6 +199,7 @@ class PitaSeries(PitaDisplayMixin):
|
|
|
189
199
|
label_totals: str|None = None,
|
|
190
200
|
ndigits: int|None = None,
|
|
191
201
|
base: int = 1,
|
|
202
|
+
apportioned_rounding: bool|None = None,
|
|
192
203
|
) -> pd.DataFrame:
|
|
193
204
|
"""
|
|
194
205
|
Add percentage column to a Series.
|
|
@@ -220,6 +231,7 @@ class PitaSeries(PitaDisplayMixin):
|
|
|
220
231
|
label_totals = label_totals,
|
|
221
232
|
ndigits = ndigits,
|
|
222
233
|
base = base,
|
|
234
|
+
apportioned_rounding = apportioned_rounding,
|
|
223
235
|
)
|
|
224
236
|
|
|
225
237
|
def add_pct(self, *args, **kwargs):
|
|
@@ -247,7 +259,7 @@ class PitaSeries(PitaDisplayMixin):
|
|
|
247
259
|
pd.Series:
|
|
248
260
|
Series with totals row added.
|
|
249
261
|
"""
|
|
250
|
-
return
|
|
262
|
+
return totals.add_totals( # type: ignore
|
|
251
263
|
self._obj,
|
|
252
264
|
label = label,
|
|
253
265
|
ignore_keys = ignore_keys,
|
|
@@ -256,10 +268,11 @@ class PitaSeries(PitaDisplayMixin):
|
|
|
256
268
|
|
|
257
269
|
def add_subtotals(
|
|
258
270
|
self,
|
|
259
|
-
level:
|
|
271
|
+
level: Level|list[Level] = 0,
|
|
260
272
|
label: str|None = None,
|
|
261
273
|
include_level_name: bool = False,
|
|
262
274
|
ignore_keys: str|list[str]|None = None,
|
|
275
|
+
skip_single_rows: bool = True,
|
|
263
276
|
_fill: str = '',
|
|
264
277
|
) -> pd.Series:
|
|
265
278
|
"""
|
|
@@ -267,38 +280,110 @@ class PitaSeries(PitaDisplayMixin):
|
|
|
267
280
|
|
|
268
281
|
Parameters
|
|
269
282
|
----------
|
|
270
|
-
|
|
271
|
-
|
|
283
|
+
level (int|str|list[int|str]):
|
|
284
|
+
Level(s) to add subtotals to. Default 0.
|
|
272
285
|
label (str|None):
|
|
273
286
|
Label for the subtotals rows. Default 'Subtotals'.
|
|
287
|
+
include_level_name (bool):
|
|
288
|
+
Whether to add level name to subtotal label.
|
|
274
289
|
ignore_keys (str|list[str]|None):
|
|
275
290
|
Keys of rows to ignore when aggregating. Default 'Totals'
|
|
291
|
+
skip_single_rows (bool):
|
|
292
|
+
Whether to skip single rows when aggregating. Default True.
|
|
276
293
|
|
|
277
294
|
Returns
|
|
278
295
|
-------
|
|
279
296
|
pd.Series:
|
|
280
297
|
Series with subtotal rows added.
|
|
281
298
|
"""
|
|
282
|
-
return
|
|
299
|
+
return totals.add_subtotals( # type: ignore
|
|
283
300
|
self._obj,
|
|
284
301
|
level = level,
|
|
285
302
|
label = label,
|
|
286
303
|
include_level_name = include_level_name,
|
|
287
304
|
ignore_keys = ignore_keys,
|
|
305
|
+
skip_single_rows = skip_single_rows,
|
|
288
306
|
_fill = _fill,
|
|
289
307
|
)
|
|
290
308
|
|
|
291
309
|
def sort_totals(
|
|
292
310
|
self,
|
|
293
|
-
axis:
|
|
294
|
-
level:
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
311
|
+
axis: Axis = 0,
|
|
312
|
+
level: Level|list[Level]|None = None,
|
|
313
|
+
labels: list[str]|None = None,
|
|
314
|
+
totals_last: bool = True,
|
|
315
|
+
sort_remaining: bool = True,
|
|
316
|
+
) -> pd.Series:
|
|
317
|
+
"""
|
|
318
|
+
Sort index/columns to position totals and subtotals at start or end within groups.
|
|
319
|
+
|
|
320
|
+
Convenience function that sorts common aggregate labels (totals, subtotals) to
|
|
321
|
+
their appropriate positions, while leaving other items in their existing order.
|
|
322
|
+
Uses default labels from flatbread configuration unless custom labels are provided.
|
|
323
|
+
|
|
324
|
+
Parameters
|
|
325
|
+
----------
|
|
326
|
+
axis : Axis, default 0
|
|
327
|
+
Axis to sort along:
|
|
328
|
+
- 0 or 'index': sort the index (rows)
|
|
329
|
+
- 1 or 'columns': sort the columns
|
|
330
|
+
level : Level | list[Level] | None, default None
|
|
331
|
+
Index level(s) to sort. Can be level number(s), level name(s), or None for all levels.
|
|
332
|
+
labels : list[str] | None, default None
|
|
333
|
+
Custom labels to treat as totals/subtotals. If None, uses default labels from
|
|
334
|
+
flatbread configuration ('Totals', 'Subtotals').
|
|
335
|
+
totals_last : bool, default True
|
|
336
|
+
Whether to place totals/subtotals at the end (True) or beginning (False) of each group.
|
|
337
|
+
sort_remaining : bool, default True
|
|
338
|
+
Whether to sort non-target levels alphabetically.
|
|
339
|
+
|
|
340
|
+
Returns
|
|
341
|
+
-------
|
|
342
|
+
pd.Series
|
|
343
|
+
Series with totals/subtotals repositioned according to the specified parameters.
|
|
344
|
+
"""
|
|
345
|
+
return axes.sort_totals( # type: ignore
|
|
298
346
|
self._obj,
|
|
299
347
|
axis = axis,
|
|
300
348
|
level = level,
|
|
301
|
-
|
|
349
|
+
labels = labels,
|
|
350
|
+
totals_last = totals_last,
|
|
351
|
+
sort_remaining = sort_remaining,
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
# region io
|
|
355
|
+
def export_excel(
|
|
356
|
+
self,
|
|
357
|
+
filepath: str | Path,
|
|
358
|
+
title: str | None = None,
|
|
359
|
+
number_formats: dict | None = None,
|
|
360
|
+
border_specs: dict | None = None,
|
|
361
|
+
**kwargs
|
|
362
|
+
) -> None:
|
|
363
|
+
"""
|
|
364
|
+
Export Series to Excel with automatic formatting based on flatbread configuration.
|
|
365
|
+
|
|
366
|
+
Parameters
|
|
367
|
+
----------
|
|
368
|
+
filepath : str | Path
|
|
369
|
+
Path to save the Excel file
|
|
370
|
+
title : str, optional
|
|
371
|
+
Title for the worksheet
|
|
372
|
+
number_formats : dict, optional
|
|
373
|
+
Custom number formats (overrides auto-detected ones)
|
|
374
|
+
border_specs : dict, optional
|
|
375
|
+
Custom border specifications (merged with margin borders)
|
|
376
|
+
**kwargs
|
|
377
|
+
Additional arguments passed to pandasxl WorksheetManager
|
|
378
|
+
"""
|
|
379
|
+
from flatbread.output.excel import export_excel
|
|
380
|
+
return export_excel(
|
|
381
|
+
self._obj,
|
|
382
|
+
filepath,
|
|
383
|
+
title=title,
|
|
384
|
+
number_formats=number_formats,
|
|
385
|
+
border_specs=border_specs,
|
|
386
|
+
**kwargs
|
|
302
387
|
)
|
|
303
388
|
|
|
304
389
|
# region tooling
|
|
@@ -307,7 +392,7 @@ class PitaSeries(PitaDisplayMixin):
|
|
|
307
392
|
value: Any,
|
|
308
393
|
level: int = 0,
|
|
309
394
|
level_name: Any = None,
|
|
310
|
-
axis:
|
|
395
|
+
axis: Axis = 0,
|
|
311
396
|
):
|
|
312
397
|
"""
|
|
313
398
|
Add a level containing the specified value to a Series index.
|
|
@@ -322,7 +407,7 @@ class PitaSeries(PitaDisplayMixin):
|
|
|
322
407
|
Position to insert the new level. Defaults to 0 (start).
|
|
323
408
|
level_name (Any, optional):
|
|
324
409
|
Name for the new level. Defaults to None.
|
|
325
|
-
axis (
|
|
410
|
+
axis (int | Literal["index", "columns", "both"]):
|
|
326
411
|
Added for symmetry with DataFrame method.
|
|
327
412
|
|
|
328
413
|
Returns
|
|
@@ -330,7 +415,7 @@ class PitaSeries(PitaDisplayMixin):
|
|
|
330
415
|
pd.Series:
|
|
331
416
|
Series with the new level added to the specified axis.
|
|
332
417
|
"""
|
|
333
|
-
return
|
|
418
|
+
return axes.add_level(
|
|
334
419
|
self._obj,
|
|
335
420
|
value = value,
|
|
336
421
|
level = level,
|