diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index 5601048c409e1..210dfc0050bf4 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -81,7 +81,7 @@ Styler - Naive sparsification is now possible for LaTeX without the multirow package (:issue:`43369`) - :meth:`.Styler.to_html` omits CSSStyle rules for hidden table elements (:issue:`43619`) - Custom CSS classes can now be directly specified without string replacement (:issue:`43686`) - - Bug where row trimming failed to reflect hidden rows (:issue:`43703`) + - Bug where row trimming failed to reflect hidden rows (:issue:`43703`, :issue:`44247`) - Update and expand the export and use mechanics (:issue:`40675`) - New method :meth:`.Styler.hide` added and deprecates :meth:`.Styler.hide_index` and :meth:`.Styler.hide_columns` (:issue:`43758`) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 27a5170e48949..a71dd6f33e3c8 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -1230,29 +1230,37 @@ def _get_level_lengths( return lengths for i, lvl in enumerate(levels): + visible_row_count = 0 # used to break loop due to display trimming for j, row in enumerate(lvl): - if j >= max_index: - # stop the loop due to display trimming + if visible_row_count > max_index: break if not sparsify: + # then lengths will always equal 1 since no aggregation. if j not in hidden_elements: lengths[(i, j)] = 1 + visible_row_count += 1 elif (row is not lib.no_default) and (j not in hidden_elements): + # this element has not been sparsified so must be the start of section last_label = j lengths[(i, last_label)] = 1 + visible_row_count += 1 elif row is not lib.no_default: - # even if its hidden, keep track of it in case - # length >1 and later elements are visible + # even if the above is hidden, keep track of it in case length > 1 and + # later elements are visible last_label = j lengths[(i, last_label)] = 0 elif j not in hidden_elements: + # then element must be part of sparsified section and is visible + visible_row_count += 1 if lengths[(i, last_label)] == 0: - # if the previous iteration was first-of-kind but hidden then offset + # if previous iteration was first-of-section but hidden then offset last_label = j lengths[(i, last_label)] = 1 else: - # else add to previous iteration - lengths[(i, last_label)] += 1 + # else add to previous iteration but do not extend more than max + lengths[(i, last_label)] = min( + max_index, 1 + lengths[(i, last_label)] + ) non_zero_lengths = { element: length for element, length in lengths.items() if length >= 1 diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py index cf2ec347015d1..8ac0dd03c9fd6 100644 --- a/pandas/tests/io/formats/style/test_style.py +++ b/pandas/tests/io/formats/style/test_style.py @@ -1577,3 +1577,26 @@ def test_row_trimming_hide_index(): assert len(ctx["body"]) == 3 for r, val in enumerate(["3", "4", "..."]): assert ctx["body"][r][1]["display_value"] == val + + +def test_row_trimming_hide_index_mi(): + # gh 44247 + df = DataFrame([[1], [2], [3], [4], [5]]) + df.index = MultiIndex.from_product([[0], [0, 1, 2, 3, 4]]) + with pd.option_context("styler.render.max_rows", 2): + ctx = df.style.hide([(0, 0), (0, 1)], axis="index")._translate(True, True) + assert len(ctx["body"]) == 3 + + # level 0 index headers (sparsified) + assert {"value": 0, "attributes": 'rowspan="2"', "is_visible": True}.items() <= ctx[ + "body" + ][0][0].items() + assert {"value": 0, "attributes": "", "is_visible": False}.items() <= ctx["body"][ + 1 + ][0].items() + assert {"value": "...", "is_visible": True}.items() <= ctx["body"][2][0].items() + + for r, val in enumerate(["2", "3", "..."]): + assert ctx["body"][r][1]["display_value"] == val # level 1 index headers + for r, val in enumerate(["3", "4", "..."]): + assert ctx["body"][r][2]["display_value"] == val # data values