From d29cac1c01fcc1c288f73011045757eb19fc41c2 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Sun, 9 May 2021 09:46:18 +0200 Subject: [PATCH 1/4] add separate index/columns sparsify mechanics --- pandas/io/formats/style_render.py | 47 +++++++++++++++------ pandas/tests/io/formats/style/test_style.py | 34 ++++++++++++++- 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index bd768f4f0a1d4..6e46b6ec91dd9 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -132,11 +132,16 @@ def _compute(self): r = func(self)(*args, **kwargs) return r - def _translate(self): + def _translate(self, sparsify_index=None, sparsify_cols=None): """ Convert the DataFrame in `self.data` and the attrs from `_build_styles` into a dictionary of {head, body, uuid, cellstyle}. """ + if sparsify_index is None: + sparsify_index = get_option("display.multi_sparse") + if sparsify_cols is None: + sparsify_cols = get_option("display.multi_sparse") + ROW_HEADING_CLASS = "row_heading" COL_HEADING_CLASS = "col_heading" INDEX_NAME_CLASS = "index_name" @@ -153,14 +158,14 @@ def _translate(self): } head = self._translate_header( - BLANK_CLASS, BLANK_VALUE, INDEX_NAME_CLASS, COL_HEADING_CLASS + BLANK_CLASS, BLANK_VALUE, INDEX_NAME_CLASS, COL_HEADING_CLASS, sparsify_cols ) d.update({"head": head}) self.cellstyle_map: DefaultDict[tuple[CSSPair, ...], list[str]] = defaultdict( list ) - body = self._translate_body(DATA_CLASS, ROW_HEADING_CLASS) + body = self._translate_body(DATA_CLASS, ROW_HEADING_CLASS, sparsify_index) d.update({"body": body}) cellstyle: list[dict[str, CSSList | list[str]]] = [ @@ -185,7 +190,12 @@ def _translate(self): return d def _translate_header( - self, blank_class, blank_value, index_name_class, col_heading_class + self, + blank_class, + blank_value, + index_name_class, + col_heading_class, + sparsify_cols, ): """ Build each within table , using the structure: @@ -198,7 +208,9 @@ def _translate_header( +----------------------------+---------------+---------------------------+ """ # for sparsifying a MultiIndex - col_lengths = _get_level_lengths(self.columns, self.hidden_columns) + col_lengths = _get_level_lengths( + self.columns, sparsify_cols, self.hidden_columns + ) clabels = self.data.columns.tolist() if self.data.columns.nlevels == 1: @@ -268,7 +280,7 @@ def _translate_header( return head - def _translate_body(self, data_class, row_heading_class): + def _translate_body(self, data_class, row_heading_class, sparsify_index): """ Build each in table in the following format: +--------------------------------------------+---------------------------+ @@ -279,7 +291,7 @@ def _translate_body(self, data_class, row_heading_class): block """ # for sparsifying a MultiIndex - idx_lengths = _get_level_lengths(self.index) + idx_lengths = _get_level_lengths(self.index, sparsify_index) rlabels = self.data.index.tolist() if self.data.index.nlevels == 1: @@ -520,14 +532,23 @@ def _element( } -def _get_level_lengths(index, hidden_elements=None): +def _get_level_lengths(index, sparsify, hidden_elements=None): """ Given an index, find the level length for each element. - Optional argument is a list of index positions which - should not be visible. - - Result is a dictionary of (level, initial_position): span + Parameters + ---------- + index : Index + Index or columns to determine lengths of each element + sparsify : bool + Whether to hide or show each distinct element in a MultiIndex + hidden_element : list + Index positions of elements hidden from display in the index affecting + length + Returns + ------- + Dict : + Result is a dictionary of (level, initial_position): span """ if isinstance(index, MultiIndex): levels = index.format(sparsify=lib.no_default, adjoin=False) @@ -546,7 +567,7 @@ def _get_level_lengths(index, hidden_elements=None): for i, lvl in enumerate(levels): for j, row in enumerate(lvl): - if not get_option("display.multi_sparse"): + if not sparsify: lengths[(i, j)] = 1 elif (row is not lib.no_default) and (j not in hidden_elements): last_label = j diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py index 855def916c2cd..31877b3f33482 100644 --- a/pandas/tests/io/formats/style/test_style.py +++ b/pandas/tests/io/formats/style/test_style.py @@ -844,7 +844,24 @@ def test_get_level_lengths(self): (1, 4): 1, (1, 5): 1, } - result = _get_level_lengths(index) + result = _get_level_lengths(index, sparsify=True) + tm.assert_dict_equal(result, expected) + + expected = { + (0, 0): 1, + (0, 1): 1, + (0, 2): 1, + (0, 3): 1, + (0, 4): 1, + (0, 5): 1, + (1, 0): 1, + (1, 1): 1, + (1, 2): 1, + (1, 3): 1, + (1, 4): 1, + (1, 5): 1, + } + result = _get_level_lengths(index, sparsify=False) tm.assert_dict_equal(result, expected) def test_get_level_lengths_un_sorted(self): @@ -858,7 +875,20 @@ def test_get_level_lengths_un_sorted(self): (1, 2): 1, (1, 3): 1, } - result = _get_level_lengths(index) + result = _get_level_lengths(index, sparsify=True) + tm.assert_dict_equal(result, expected) + + expected = { + (0, 0): 1, + (0, 1): 1, + (0, 2): 1, + (0, 3): 1, + (1, 0): 1, + (1, 1): 1, + (1, 2): 1, + (1, 3): 1, + } + result = _get_level_lengths(index, sparsify=False) tm.assert_dict_equal(result, expected) def test_mi_sparse(self): From 546595388af06a16c6ee1a70ebcdc1421c68f544 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Tue, 11 May 2021 07:03:04 +0200 Subject: [PATCH 2/4] fix docstring --- pandas/io/formats/style_render.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 6e46b6ec91dd9..fa4ed7236a36c 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -538,17 +538,18 @@ def _get_level_lengths(index, sparsify, hidden_elements=None): Parameters ---------- - index : Index - Index or columns to determine lengths of each element - sparsify : bool - Whether to hide or show each distinct element in a MultiIndex - hidden_element : list - Index positions of elements hidden from display in the index affecting - length + index : Index + Index or columns to determine lengths of each element + sparsify : bool + Whether to hide or show each distinct element in a MultiIndex + hidden_elements : list + Index positions of elements hidden from display in the index affecting + length + Returns ------- - Dict : - Result is a dictionary of (level, initial_position): span + Dict : + Result is a dictionary of (level, initial_position): span """ if isinstance(index, MultiIndex): levels = index.format(sparsify=lib.no_default, adjoin=False) From 1744a5e469598f3be6946c8587e9336b04ce9608 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Wed, 12 May 2021 11:40:25 +0200 Subject: [PATCH 3/4] type _get_level_lengths --- pandas/io/formats/style_render.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index fa4ed7236a36c..f60b2732a7071 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -532,7 +532,9 @@ def _element( } -def _get_level_lengths(index, sparsify, hidden_elements=None): +def _get_level_lengths( + index: Index, sparsify: bool, hidden_elements: Sequence[int] | None = None +): """ Given an index, find the level length for each element. @@ -542,7 +544,7 @@ def _get_level_lengths(index, sparsify, hidden_elements=None): Index or columns to determine lengths of each element sparsify : bool Whether to hide or show each distinct element in a MultiIndex - hidden_elements : list + hidden_elements : sequence of int Index positions of elements hidden from display in the index affecting length From 08b51740cff7eaeda52dc3f6c56a8fd62de6334c Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Wed, 12 May 2021 12:13:37 +0200 Subject: [PATCH 4/4] add docs and typing to _translates --- pandas/io/formats/style_render.py | 77 ++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 11 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index f60b2732a7071..6917daaede2c6 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -132,10 +132,27 @@ def _compute(self): r = func(self)(*args, **kwargs) return r - def _translate(self, sparsify_index=None, sparsify_cols=None): + def _translate( + self, sparsify_index: bool | None = None, sparsify_cols: bool | None = None + ): """ - Convert the DataFrame in `self.data` and the attrs from `_build_styles` - into a dictionary of {head, body, uuid, cellstyle}. + Process Styler data and settings into a dict for template rendering. + + Convert data and settings from ``Styler`` attributes such as ``self.data``, + ``self.tooltips`` including applying any methods in ``self._todo``. + + Parameters + ---------- + sparsify_index : bool, optional + Whether to sparsify the index or print all hierarchical index elements + sparsify_cols : bool, optional + Whether to sparsify the columns or print all hierarchical column elements + + Returns + ------- + d : dict + The following structure: {uuid, table_styles, caption, head, body, + cellstyle, table_attributes} """ if sparsify_index is None: sparsify_index = get_option("display.multi_sparse") @@ -191,14 +208,16 @@ def _translate(self, sparsify_index=None, sparsify_cols=None): def _translate_header( self, - blank_class, - blank_value, - index_name_class, - col_heading_class, - sparsify_cols, + blank_class: str, + blank_value: str, + index_name_class: str, + col_heading_class: str, + sparsify_cols: bool, ): """ - Build each within table , using the structure: + Build each within table as a list + + Using the structure: +----------------------------+---------------+---------------------------+ | index_blanks ... | column_name_0 | column_headers (level_0) | 1) | .. | .. | .. | @@ -206,6 +225,24 @@ def _translate_header( +----------------------------+---------------+---------------------------+ 2) | index_names (level_0 to level_n) ... | column_blanks ... | +----------------------------+---------------+---------------------------+ + + Parameters + ---------- + blank_class : str + CSS class added to elements within blank sections of the structure. + blank_value : str + HTML display value given to elements within blank sections of the structure. + index_name_class : str + CSS class added to elements within the index_names section of the structure. + col_heading_class : str + CSS class added to elements within the column_names section of structure. + sparsify_cols : bool + Whether column_headers section will add colspan attributes (>1) to elements. + + Returns + ------- + head : list + The associated HTML elements needed for template rendering. """ # for sparsifying a MultiIndex col_lengths = _get_level_lengths( @@ -280,15 +317,33 @@ def _translate_header( return head - def _translate_body(self, data_class, row_heading_class, sparsify_index): + def _translate_body( + self, data_class: str, row_heading_class: str, sparsify_index: bool + ): """ - Build each in table in the following format: + Build each within table as a list + + Use the following structure: +--------------------------------------------+---------------------------+ | index_header_0 ... index_header_n | data_by_column | +--------------------------------------------+---------------------------+ Also add elements to the cellstyle_map for more efficient grouped elements in block + + Parameters + ---------- + data_class : str + CSS class added to elements within data_by_column sections of the structure. + row_heading_class : str + CSS class added to elements within the index_header section of structure. + sparsify_index : bool + Whether index_headers section will add rowspan attributes (>1) to elements. + + Returns + ------- + body : list + The associated HTML elements needed for template rendering. """ # for sparsifying a MultiIndex idx_lengths = _get_level_lengths(self.index, sparsify_index)