-
Notifications
You must be signed in to change notification settings - Fork 105
Feat: add grand_summary_rows()
method
#765
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
juleswg23
wants to merge
56
commits into
main
Choose a base branch
from
feat-grand-summary-rows
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
56 commits
Select commit
Hold shift + click to select a range
04f2f8e
a very very rough draft, does not work for polars yet
juleswg23 ff29302
set stub and boxhead to include all rows in final table
juleswg23 f3a26a1
add create_no_row_frame and refactor insert_row
juleswg23 c810a6a
add SummaryRows to GTData object
juleswg23 21996ad
refactor modify rows
juleswg23 aaa887c
add merge_summary_rows to append to body
juleswg23 6019d7e
replace len with n_rows
juleswg23 0e85363
update build step
juleswg23 059686f
add comments mapping out rendering plan
juleswg23 cd6299e
new locations for grand summary (and summary)
juleswg23 7e1985a
condensing locations
juleswg23 07d3574
add getter for summary rows
juleswg23 1acbabd
begin design on rendering top grand summary rows
juleswg23 afae957
refactor _create_row_component_h(), outside of create_body_component_…
juleswg23 dcbf84c
remove merge_summary_rows
juleswg23 f310f7e
remove commented out code
juleswg23 b832652
Change SummaryRowInfo values from list to dict
juleswg23 6e9af0a
reorder functions
juleswg23 986d8a6
remove dead code
juleswg23 aab38b9
support grand summary rows at bottom of table
juleswg23 bf372cf
location mask class var added
juleswg23 3765a48
support sum
juleswg23 ae9caf1
support opt_stylize and other tab_options
juleswg23 52741b5
Apply special classes to get double border
juleswg23 7e201df
clean up add summary and set up styling
juleswg23 801bd99
rename for clairity
juleswg23 e618668
experimenting with styles on grandSummaryStub
juleswg23 cf0d165
Merge branch 'main' into feat-grand-summary-rows
juleswg23 52e125a
remove prints
juleswg23 2c64419
Add stub column when none exists for summary rows (this approach does…
juleswg23 d6a5ecb
target location for LocGrandSummaryStub
juleswg23 d906e67
style LocGrandSummaryStub and LocGrandSummary
juleswg23 6672e4a
Handle special cases for summary rows with group stub columns
juleswg23 e4d038c
locations docstrings
juleswg23 3302cc4
adding new locs to quarto
juleswg23 5a88e85
more documentaiton
juleswg23 a06cd13
accept summaryFn and label
juleswg23 d8249ec
possible approach to fmt in grand Summary Rows, WIP
juleswg23 64b1cb0
refactor summary rows to mapping, and add summary rows grand attribut…
juleswg23 16cb29e
use eval_aggregate for summary rows
juleswg23 e532148
remove dead code
juleswg23 bcd89d4
docstring
juleswg23 0b4159b
testing docstring in site preview
juleswg23 3aab95d
fix build
juleswg23 4eecef0
refactor to rely on class attribute to determine if grand summary
juleswg23 1820fa7
Merge branch 'main' into feat-grand-summary-rows
juleswg23 b472da4
test eval_aggregate
juleswg23 4b8ed63
grand summary rows tests added
juleswg23 e6d0fff
fix kitchen sink example
juleswg23 954ad1f
ensure example compiles
juleswg23 e08069b
locations tests
juleswg23 f590355
snapshot updates, cover case in utils_render_html
juleswg23 f16d80a
main docstring
juleswg23 085aef0
get started documentation
juleswg23 86a4d9f
docs nitpicks
juleswg23 179a68f
remove unused attribute
juleswg23 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
|
||
import copy | ||
import re | ||
from collections.abc import Sequence | ||
from collections.abc import Mapping, Sequence | ||
from dataclasses import dataclass, field, replace | ||
from enum import Enum, auto | ||
from itertools import chain, product | ||
|
@@ -75,6 +75,8 @@ class GTData: | |
_spanners: Spanners | ||
_heading: Heading | ||
_stubhead: Stubhead | ||
_summary_rows: SummaryRows | ||
_summary_rows_grand: SummaryRows | ||
_source_notes: SourceNotes | ||
_footnotes: Footnotes | ||
_styles: Styles | ||
|
@@ -122,6 +124,8 @@ def from_data( | |
_spanners=Spanners([]), | ||
_heading=Heading(), | ||
_stubhead=None, | ||
_summary_rows=SummaryRows(), | ||
_summary_rows_grand=SummaryRows(_is_grand_summary=True), | ||
_source_notes=[], | ||
_footnotes=[], | ||
_styles=[], | ||
|
@@ -510,10 +514,12 @@ def _get_number_of_visible_data_columns(self) -> int: | |
|
||
# Obtain the number of visible columns in the built table; this should | ||
# account for the size of the stub in the final, built table | ||
def _get_effective_number_of_columns(self, stub: Stub, options: Options) -> int: | ||
def _get_effective_number_of_columns( | ||
self, stub: Stub, has_summary_rows: bool, options: Options | ||
) -> int: | ||
n_data_cols = self._get_number_of_visible_data_columns() | ||
|
||
stub_layout = stub._get_stub_layout(options=options) | ||
stub_layout = stub._get_stub_layout(has_summary_rows=has_summary_rows, options=options) | ||
# Once the stub is defined in the package, we need to account | ||
# for the width of the stub at build time to fully obtain the number | ||
# of visible columns in the built table | ||
|
@@ -674,7 +680,7 @@ def _stub_group_names_has_column(self, options: Options) -> bool: | |
|
||
return row_group_as_column | ||
|
||
def _get_stub_layout(self, options: Options) -> list[str]: | ||
def _get_stub_layout(self, has_summary_rows: bool, options: Options) -> list[str]: | ||
# Determine which stub components are potentially present as columns | ||
stub_rownames_is_column = "row_id" in self._get_stub_components() | ||
stub_groupnames_is_column = self._stub_group_names_has_column(options=options) | ||
|
@@ -684,14 +690,12 @@ def _get_stub_layout(self, options: Options) -> list[str]: | |
|
||
# Resolve the layout of the stub (i.e., the roles of columns if present) | ||
if n_stub_cols == 0: | ||
# TODO: If summary rows are present, we will use the `rowname` column | ||
# # for the summary row labels | ||
# if _summary_exists(data=data): | ||
# stub_layout = ["rowname"] | ||
# else: | ||
# stub_layout = [] | ||
|
||
stub_layout = [] | ||
# If summary rows are present, we will use the `rowname` column | ||
# for the summary row labels | ||
if has_summary_rows: | ||
stub_layout = ["rowname"] | ||
else: | ||
stub_layout = [] | ||
|
||
else: | ||
stub_layout = [ | ||
|
@@ -719,7 +723,7 @@ class GroupRowInfo: | |
indices: list[int] = field(default_factory=list) | ||
# row_start: int | None = None | ||
# row_end: int | None = None | ||
has_summary_rows: bool = False | ||
# has_summary_rows: bool = False # TODO: remove | ||
summary_row_side: str | None = None | ||
|
||
def defaulted_label(self) -> str: | ||
|
@@ -972,6 +976,141 @@ def __init__(self, func: FormatFns, cols: list[str], rows: list[int]): | |
Formats = list | ||
|
||
|
||
# Summary Rows --- | ||
|
||
# This can't conflict with actual group ids since we have a | ||
# seperate data structure for grand summary row infos | ||
|
||
|
||
@dataclass(frozen=True) | ||
class SummaryRowInfo: | ||
"""Information about a single summary row""" | ||
|
||
id: str | ||
label: str # For now, label and id are identical | ||
# The motivation for values as a dict is to ensure cols_* functions don't have to consider | ||
# the implications on existing SummaryRowInfo objects | ||
values: dict[str, Any] # TODO: consider datatype, series? | ||
side: Literal["top", "bottom"] # TODO: consider enum | ||
|
||
|
||
class SummaryRows(Mapping[str, list[SummaryRowInfo]]): | ||
"""A sequence of summary rows | ||
|
||
The following strctures should always be true about summary rows: | ||
- The id is also the label (often the same as the function name) | ||
- There is at most 1 row for each group and id pairing | ||
- If a summary row is added and no row exists for that group and id, add it | ||
- If a summary row is added and a row exists for that group and id pairing, | ||
then replace all cells (in values) that are numeric in the new version | ||
""" | ||
|
||
_d: dict[str, list[SummaryRowInfo]] | ||
_is_grand_summary: bool | ||
|
||
GRAND_SUMMARY_KEY = "grand" | ||
|
||
def __init__(self, _is_grand_summary: bool = False): | ||
self._d = {} | ||
self._is_grand_summary = _is_grand_summary | ||
|
||
def __bool__(self) -> bool: | ||
"""Return True if there are any summary rows, False otherwise.""" | ||
return len(self._d) > 0 | ||
|
||
def __getitem__(self, key: str | None) -> list[SummaryRowInfo]: | ||
if self._is_grand_summary: | ||
key = SummaryRows.GRAND_SUMMARY_KEY | ||
|
||
if not key: | ||
raise KeyError("Summary row group key must not be None for group summary rows.") | ||
|
||
if key not in self._d: | ||
raise KeyError(f"Group '{key}' not found in summary rows.") | ||
|
||
return self._d[key] | ||
|
||
def add_summary_row(self, summary_row: SummaryRowInfo, group_id: str | None = None) -> None: | ||
"""Add a summary row following the merging rules in the class docstring.""" | ||
|
||
if self._is_grand_summary: | ||
group_id = SummaryRows.GRAND_SUMMARY_KEY | ||
|
||
existing_group = self.get(group_id) | ||
|
||
if not existing_group: | ||
self._d[group_id] = [summary_row] | ||
return | ||
|
||
else: | ||
existing_index = None | ||
for i, existing_row in enumerate(existing_group): | ||
if existing_row.id == summary_row.id: | ||
existing_index = i | ||
break | ||
|
||
new_rows = existing_group | ||
|
||
if existing_index is None: | ||
# No existing row for this group and id, add it | ||
new_rows.append(summary_row) | ||
else: | ||
# Replace existing row, but merge numeric values from new version | ||
existing_row = new_rows[existing_index] | ||
|
||
# Start with existing values | ||
merged_values = existing_row.values.copy() | ||
|
||
# Replace with numeric values from new row | ||
for key, new_value in summary_row.values.items(): | ||
if isinstance(new_value, (int, float)): | ||
merged_values[key] = new_value | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can tell this is not the ideal way, since we can't guarantee that summary values have to be numeric. |
||
|
||
# Create merged row with new row's properties but merged values | ||
merged_row = SummaryRowInfo( | ||
id=summary_row.id, | ||
label=summary_row.label, | ||
values=merged_values, | ||
# Setting this to existing row instead of summary_row means original | ||
# side is fixed by whatever side is first assigned to this row | ||
side=existing_row.side, | ||
) | ||
|
||
new_rows[existing_index] = merged_row | ||
|
||
self._d[group_id] = new_rows | ||
|
||
return | ||
|
||
def get_summary_rows( | ||
self, group_id: str | None = None, side: str | None = None | ||
) -> list[SummaryRowInfo]: | ||
"""Get list of summary rows for that group. If side is None, do not filter by side. | ||
Sorts result with 'top' side first, then 'bottom'.""" | ||
|
||
result: list[SummaryRowInfo] = [] | ||
|
||
if self._is_grand_summary: | ||
group_id = SummaryRows.GRAND_SUMMARY_KEY | ||
|
||
summary_row_group = self.get(group_id) | ||
|
||
if summary_row_group: | ||
for summary_row in summary_row_group: | ||
if side is None or summary_row.side == side: | ||
result.append(summary_row) | ||
|
||
# Sort: 'top' first, then 'bottom' | ||
result.sort(key=lambda r: 0 if r.side == "top" else 1) # TODO: modify if enum for side | ||
return result | ||
|
||
def __iter__(self): | ||
raise NotImplementedError | ||
|
||
def __len__(self): | ||
raise NotImplementedError | ||
|
||
|
||
# Options ---- | ||
|
||
default_fonts_list = [ | ||
|
@@ -1130,25 +1269,25 @@ class Options: | |
# summary_row_border_style: OptionsInfo = OptionsInfo(True, "summary_row", "value", "solid") | ||
# summary_row_border_width: OptionsInfo = OptionsInfo(True, "summary_row", "px", "2px") | ||
# summary_row_border_color: OptionsInfo = OptionsInfo(True, "summary_row", "value", "#D3D3D3") | ||
# grand_summary_row_padding: OptionsInfo = OptionsInfo(True, "grand_summary_row", "px", "8px") | ||
# grand_summary_row_padding_horizontal: OptionsInfo = OptionsInfo( | ||
# True, "grand_summary_row", "px", "5px" | ||
# ) | ||
# grand_summary_row_background_color: OptionsInfo = OptionsInfo( | ||
# True, "grand_summary_row", "value", None | ||
# ) | ||
# grand_summary_row_text_transform: OptionsInfo = OptionsInfo( | ||
# True, "grand_summary_row", "value", "inherit" | ||
# ) | ||
# grand_summary_row_border_style: OptionsInfo = OptionsInfo( | ||
# True, "grand_summary_row", "value", "double" | ||
# ) | ||
# grand_summary_row_border_width: OptionsInfo = OptionsInfo( | ||
# True, "grand_summary_row", "px", "6px" | ||
# ) | ||
# grand_summary_row_border_color: OptionsInfo = OptionsInfo( | ||
# True, "grand_summary_row", "value", "#D3D3D3" | ||
# ) | ||
grand_summary_row_padding: OptionsInfo = OptionsInfo(True, "grand_summary_row", "px", "8px") | ||
grand_summary_row_padding_horizontal: OptionsInfo = OptionsInfo( | ||
True, "grand_summary_row", "px", "5px" | ||
) | ||
grand_summary_row_background_color: OptionsInfo = OptionsInfo( | ||
True, "grand_summary_row", "value", None | ||
) | ||
grand_summary_row_text_transform: OptionsInfo = OptionsInfo( | ||
True, "grand_summary_row", "value", "inherit" | ||
) | ||
grand_summary_row_border_style: OptionsInfo = OptionsInfo( | ||
True, "grand_summary_row", "value", "double" | ||
) | ||
grand_summary_row_border_width: OptionsInfo = OptionsInfo( | ||
True, "grand_summary_row", "px", "6px" | ||
) | ||
grand_summary_row_border_color: OptionsInfo = OptionsInfo( | ||
True, "grand_summary_row", "value", "#D3D3D3" | ||
) | ||
# footnotes_font_size: OptionsInfo = OptionsInfo(True, "footnotes", "px", "90%") | ||
# footnotes_padding: OptionsInfo = OptionsInfo(True, "footnotes", "px", "4px") | ||
# footnotes_padding_horizontal: OptionsInfo = OptionsInfo(True, "footnotes", "px", "5px") | ||
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like _is_grand_summary is used so that for methods like
__getitem__
andadd_summary_rows()
some arguments that are required for regular summary rows can be optional for the grand one.