From b475622c09712b91bcebe278ab6487d38f7abac9 Mon Sep 17 00:00:00 2001 From: "Samuel W. Flint" Date: Tue, 7 Nov 2023 10:29:49 -0600 Subject: [PATCH 1/5] Add in generation of header rules and row-group rules --- analyses/common/tables.py | 61 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/analyses/common/tables.py b/analyses/common/tables.py index 1b77fb0..1328285 100644 --- a/analyses/common/tables.py +++ b/analyses/common/tables.py @@ -45,6 +45,67 @@ def highlight_rows(styler: pandas.io.formats.style.Styler) -> pandas.io.formats. Tuple[RuleLineIndex, Union[CmidruleSpec, List[CmidruleSpec]]]] ConcreteRule = Tuple[RuleLineIndex, str] + +def auto_header_rules(df: pd.DataFrame, skip_index: bool=True, level: int=0, left_trim: TrimSpec=True, right_trim: TrimSpec=True) -> List[RuleSpecifier]: + """Generate post-header rule for DF, including cut rules for the column groups at LEVEL. + + If skip_index is False, simply return a specification for a regular \midrule after the column header(s). + Otherwise, generate an offset midrule or cmidrules for groups. + When grouping is performed, obey LEFT_TRIM and RIGHT_TRIM between \cmidrule s + """ + index_cols = 1 + if isinstance(df.index, pd.MultiIndex): + index_cols = df.index.nlevels + if not isinstance(df.columns, pd.MultiIndex): + if skip_index: + return [(1, (1 + index_cols, df.columns.size + index_cols, False, False))] + else: + return [1] + else: + if not skip_index: + return [df.columns.nlevels] + values = df.columns.get_level_values(level).array + starts = [] + cur = values[0] + cur_start = 1 + val = 1 + for label in values[1:]: + if label != cur: + cur = label + if cur_start == 1: + starts.append((cur_start + index_cols, val + index_cols, False, right_trim)) + else: + starts.append((cur_start + index_cols, val + index_cols, left_trim, right_trim)) + cur_start = val + 1 + val += 1 + starts.append((cur_start + index_cols, len(values) + index_cols, left_trim, False)) + return [(df.columns.nlevels, starts)] + +def auto_group_rules(df: pd.DataFrame, skip_index: bool=False, level: int=0) -> List[RuleSpecifier]: + """Generate post-row-group rules for DF for row-groups at LEVEL. + + If SKIP_INDEX is true, generate a cmidrule which does not include the columns of the index. + """ + assert isinstance(df.index, pd.MultiIndex), "Index must be a MultiIndex" + row_offset = 1 + num_cols = df.columns.size + if isinstance(df.columns, pd.MultiIndex): + row_offset = df.columns.nlevels + index_offset = df.index.nlevels + values = df.index.get_level_values(level).array + cur_row = 1 + cur_label = values[0] + rules = [] + for label in values[1:]: + if cur_label != label: + cur_label = label + if skip_index: + rules.append(cur_row + row_offset) + else: + rules.append((cur_row + row_offset, (index_offset + 1, num_cols + index_offset, False, False))) + cur_row += 1 + return rules + def _trim_spec(trim_left: TrimSpec, trim_right: TrimSpec) -> str: if trim_left or trim_right: trim_spec = '(' From 98da12c0d98a022b70c1e35bae46312856438705 Mon Sep 17 00:00:00 2001 From: "Samuel W. Flint" Date: Tue, 7 Nov 2023 15:01:58 -0600 Subject: [PATCH 2/5] Clean up formatting --- analyses/common/tables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analyses/common/tables.py b/analyses/common/tables.py index 1328285..7bc0c42 100644 --- a/analyses/common/tables.py +++ b/analyses/common/tables.py @@ -15,6 +15,7 @@ "highlight_rows", "save_table", ] + def get_styler(df: Union[pd.DataFrame, pd.Series], decimals: Optional[int]=2, thousands: Optional[str]=',') -> pandas.io.formats.style.Styler: '''Gets a Styler object for formatting a table. @@ -45,7 +46,6 @@ def highlight_rows(styler: pandas.io.formats.style.Styler) -> pandas.io.formats. Tuple[RuleLineIndex, Union[CmidruleSpec, List[CmidruleSpec]]]] ConcreteRule = Tuple[RuleLineIndex, str] - def auto_header_rules(df: pd.DataFrame, skip_index: bool=True, level: int=0, left_trim: TrimSpec=True, right_trim: TrimSpec=True) -> List[RuleSpecifier]: """Generate post-header rule for DF, including cut rules for the column groups at LEVEL. From d04d857427317accf6ea23198e649522faa0e15b Mon Sep 17 00:00:00 2001 From: "Samuel W. Flint" Date: Tue, 7 Nov 2023 15:09:15 -0600 Subject: [PATCH 3/5] Rename & export functions --- analyses/common/tables.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/analyses/common/tables.py b/analyses/common/tables.py index 7bc0c42..4be5b85 100644 --- a/analyses/common/tables.py +++ b/analyses/common/tables.py @@ -14,6 +14,8 @@ "highlight_cols", "highlight_rows", "save_table", + "generate_column_rules", + "generate_partition_rules", ] def get_styler(df: Union[pd.DataFrame, pd.Series], decimals: Optional[int]=2, thousands: Optional[str]=',') -> pandas.io.formats.style.Styler: @@ -46,7 +48,7 @@ def highlight_rows(styler: pandas.io.formats.style.Styler) -> pandas.io.formats. Tuple[RuleLineIndex, Union[CmidruleSpec, List[CmidruleSpec]]]] ConcreteRule = Tuple[RuleLineIndex, str] -def auto_header_rules(df: pd.DataFrame, skip_index: bool=True, level: int=0, left_trim: TrimSpec=True, right_trim: TrimSpec=True) -> List[RuleSpecifier]: +def generate_column_rules(df: pd.DataFrame, skip_index: bool=True, level: int=0, left_trim: TrimSpec=True, right_trim: TrimSpec=True) -> List[RuleSpecifier]: """Generate post-header rule for DF, including cut rules for the column groups at LEVEL. If skip_index is False, simply return a specification for a regular \midrule after the column header(s). @@ -81,7 +83,7 @@ def auto_header_rules(df: pd.DataFrame, skip_index: bool=True, level: int=0, lef starts.append((cur_start + index_cols, len(values) + index_cols, left_trim, False)) return [(df.columns.nlevels, starts)] -def auto_group_rules(df: pd.DataFrame, skip_index: bool=False, level: int=0) -> List[RuleSpecifier]: +def generate_partition_rules(df: pd.DataFrame, skip_index: bool=False, level: int=0) -> List[RuleSpecifier]: """Generate post-row-group rules for DF for row-groups at LEVEL. If SKIP_INDEX is true, generate a cmidrule which does not include the columns of the index. From baed92f5292cae0c7a0b77cd949dbc49561b7db7 Mon Sep 17 00:00:00 2001 From: "Samuel W. Flint" Date: Tue, 7 Nov 2023 15:10:19 -0600 Subject: [PATCH 4/5] Clean up generate_partition_rules --- analyses/common/tables.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/analyses/common/tables.py b/analyses/common/tables.py index 4be5b85..23a9e2d 100644 --- a/analyses/common/tables.py +++ b/analyses/common/tables.py @@ -89,15 +89,20 @@ def generate_partition_rules(df: pd.DataFrame, skip_index: bool=False, level: in If SKIP_INDEX is true, generate a cmidrule which does not include the columns of the index. """ assert isinstance(df.index, pd.MultiIndex), "Index must be a MultiIndex" - row_offset = 1 + num_cols = df.columns.size + row_offset = 1 if isinstance(df.columns, pd.MultiIndex): row_offset = df.columns.nlevels + index_offset = df.index.nlevels + values = df.index.get_level_values(level).array cur_row = 1 cur_label = values[0] + rules = [] + for label in values[1:]: if cur_label != label: cur_label = label @@ -106,6 +111,7 @@ def generate_partition_rules(df: pd.DataFrame, skip_index: bool=False, level: in else: rules.append((cur_row + row_offset, (index_offset + 1, num_cols + index_offset, False, False))) cur_row += 1 + return rules def _trim_spec(trim_left: TrimSpec, trim_right: TrimSpec) -> str: From 6f6e21094847ef7fd63027d3579bc091433ad315 Mon Sep 17 00:00:00 2001 From: "Samuel W. Flint" Date: Tue, 7 Nov 2023 15:12:38 -0600 Subject: [PATCH 5/5] Clean up generate_column_rules --- analyses/common/tables.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/analyses/common/tables.py b/analyses/common/tables.py index 23a9e2d..1b329c6 100644 --- a/analyses/common/tables.py +++ b/analyses/common/tables.py @@ -58,30 +58,31 @@ def generate_column_rules(df: pd.DataFrame, skip_index: bool=True, level: int=0, index_cols = 1 if isinstance(df.index, pd.MultiIndex): index_cols = df.index.nlevels + if not isinstance(df.columns, pd.MultiIndex): if skip_index: return [(1, (1 + index_cols, df.columns.size + index_cols, False, False))] - else: - return [1] + return [1] else: if not skip_index: return [df.columns.nlevels] + + cmidrules = [] values = df.columns.get_level_values(level).array - starts = [] cur = values[0] cur_start = 1 val = 1 + for label in values[1:]: if label != cur: cur = label - if cur_start == 1: - starts.append((cur_start + index_cols, val + index_cols, False, right_trim)) - else: - starts.append((cur_start + index_cols, val + index_cols, left_trim, right_trim)) + cmidrules.append((cur_start + index_cols, val + index_cols, False if cur_start==1 else left_trim, right_trim)) cur_start = val + 1 val += 1 - starts.append((cur_start + index_cols, len(values) + index_cols, left_trim, False)) - return [(df.columns.nlevels, starts)] + + cmidrules.append((cur_start + index_cols, len(values) + index_cols, left_trim, False)) + + return [(df.columns.nlevels, cmidrules)] def generate_partition_rules(df: pd.DataFrame, skip_index: bool=False, level: int=0) -> List[RuleSpecifier]: """Generate post-row-group rules for DF for row-groups at LEVEL.