diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index bd768f4f0a1d4..6917daaede2c6 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -132,11 +132,33 @@ def _compute(self): r = func(self)(*args, **kwargs) return r - def _translate(self): + 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") + 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 +175,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,10 +207,17 @@ def _translate(self): return d def _translate_header( - self, blank_class, blank_value, index_name_class, col_heading_class + self, + 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) | .. | .. | .. | @@ -196,9 +225,29 @@ 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(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,18 +317,36 @@ def _translate_header( return head - def _translate_body(self, data_class, row_heading_class): + 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) + idx_lengths = _get_level_lengths(self.index, sparsify_index) rlabels = self.data.index.tolist() if self.data.index.nlevels == 1: @@ -520,14 +587,26 @@ def _element( } -def _get_level_lengths(index, 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. - Optional argument is a list of index positions which - should not be visible. + 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_elements : sequence of int + Index positions of elements hidden from display in the index affecting + length - Result is a dictionary of (level, initial_position): span + 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 +625,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):