From 37b50875d20db909fb3064cd5afe12a49100ff84 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Sun, 31 Oct 2021 07:16:47 +0100 Subject: [PATCH 1/8] col trim on headers --- pandas/io/formats/style_render.py | 37 ++++++++++++--------- pandas/tests/io/formats/style/test_style.py | 11 ++++++ 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 27a5170e48949..c3e95876fb097 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -316,7 +316,7 @@ def _translate_header(self, sparsify_cols: bool, max_cols: int): self.columns, sparsify_cols, max_cols, self.hidden_columns ) - clabels = self.data.columns.tolist()[:max_cols] # slice to allow trimming + clabels = self.data.columns.tolist() if self.data.columns.nlevels == 1: clabels = [[x] for x in clabels] clabels = list(zip(*clabels)) @@ -389,9 +389,28 @@ def _generate_col_header_row(self, iter: tuple, max_cols: int, col_lengths: dict ) ] - column_headers = [] + column_headers, col_count = [], 0 for c, value in enumerate(clabels[r]): header_element_visible = _is_visible(c, r, col_lengths) + + if header_element_visible: + col_count += 1 + if col_count > max_cols: + # add an extra column with `...` value to indicate trimming + column_headers.append( + _element( + "th", + ( + f"{self.css['col_heading']} {self.css['level']}{r} " + f"{self.css['col_trim']}" + ), + "...", + True, + attributes="", + ) + ) + break + header_element = _element( "th", ( @@ -422,20 +441,6 @@ def _generate_col_header_row(self, iter: tuple, max_cols: int, col_lengths: dict column_headers.append(header_element) - if len(self.data.columns) > max_cols: - # add an extra column with `...` value to indicate trimming - column_headers.append( - _element( - "th", - ( - f"{self.css['col_heading']} {self.css['level']}{r} " - f"{self.css['col_trim']}" - ), - "...", - True, - attributes="", - ) - ) return index_blanks + column_name + column_headers def _generate_index_names_row(self, iter: tuple, max_cols): diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py index cf2ec347015d1..a7bc7c1df697e 100644 --- a/pandas/tests/io/formats/style/test_style.py +++ b/pandas/tests/io/formats/style/test_style.py @@ -1577,3 +1577,14 @@ 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_col_trimming_hide_columns(): + df = DataFrame([[1, 2, 3, 4, 5]]) + with pd.option_context("styler.render.max_columns", 2): + ctx = df.style.hide([0, 1], axis="columns")._translate(True, True) + + assert len(ctx["head"][0]) == 6 # blank, [0, 1 (hidden)], [2 ,3 (visible)], + trim + for c, vals in enumerate([(1, False), (2, True), (3, True), ("...", True)]): + assert ctx["head"][0][c + 2]["value"] == vals[0] + assert ctx["head"][0][c + 2]["is_visible"] == vals[1] From 870a1862176cc965b4e0f4f95319251a49e33690 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Sun, 31 Oct 2021 08:11:38 +0100 Subject: [PATCH 2/8] col trim on index names --- pandas/io/formats/style_render.py | 50 ++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index c3e95876fb097..82d570bcdba04 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -339,7 +339,9 @@ def _translate_header(self, sparsify_cols: bool, max_cols: int): and not all(self.hide_index_) and not self.hide_index_names ): - index_names_row = self._generate_index_names_row(clabels, max_cols) + index_names_row = self._generate_index_names_row( + clabels, max_cols, col_lengths + ) head.append(index_names_row) return head @@ -443,7 +445,7 @@ def _generate_col_header_row(self, iter: tuple, max_cols: int, col_lengths: dict return index_blanks + column_name + column_headers - def _generate_index_names_row(self, iter: tuple, max_cols): + def _generate_index_names_row(self, iter: tuple, max_cols: int, col_lengths: dict): """ Generate the row containing index names @@ -475,22 +477,36 @@ def _generate_index_names_row(self, iter: tuple, max_cols): for c, name in enumerate(self.data.index.names) ] - if not clabels: - blank_len = 0 - elif len(self.data.columns) <= max_cols: - blank_len = len(clabels[0]) - else: - blank_len = len(clabels[0]) + 1 # to allow room for `...` trim col + column_blanks, col_count = [], 0 + if clabels: + for c, value in enumerate(clabels[self.columns.nlevels - 1]): + header_element_visible = _is_visible(c, 0, col_lengths) + + if header_element_visible: + col_count += 1 + if col_count > max_cols: + column_blanks.append( + _element( + "th", + ( + f"{self.css['blank']} {self.css['col']}{c} " + f"{self.css['col_trim']}" + ), + self.css["blank_value"], + True, + attributes="", + ) + ) + break + column_blanks.append( + _element( + "th", + f"{self.css['blank']} {self.css['col']}{c}", + self.css["blank_value"], + c not in self.hidden_columns, + ) + ) - column_blanks = [ - _element( - "th", - f"{self.css['blank']} {self.css['col']}{c}", - self.css["blank_value"], - c not in self.hidden_columns, - ) - for c in range(blank_len) - ] return index_names + column_blanks def _translate_body(self, sparsify_index: bool, max_rows: int, max_cols: int): From 1551538b432c35b2ffa3a3cac6a7bcfdf4bbf9d6 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Mon, 1 Nov 2021 18:57:15 +0100 Subject: [PATCH 3/8] add col trim to data elements --- pandas/io/formats/style_render.py | 71 +++++++++++++++++-------------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 610799f087565..e4046ce01c7e4 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -391,13 +391,12 @@ def _generate_col_header_row(self, iter: tuple, max_cols: int, col_lengths: dict ) ] - column_headers, col_count = [], 0 + column_headers, visible_col_count = [], 0 for c, value in enumerate(clabels[r]): header_element_visible = _is_visible(c, r, col_lengths) - if header_element_visible: - col_count += 1 - if col_count > max_cols: + visible_col_count += col_lengths.get((r, c), 0) + if visible_col_count > max_cols: # add an extra column with `...` value to indicate trimming column_headers.append( _element( @@ -477,14 +476,14 @@ def _generate_index_names_row(self, iter: tuple, max_cols: int, col_lengths: dic for c, name in enumerate(self.data.index.names) ] - column_blanks, col_count = [], 0 + column_blanks, visible_col_count = [], 0 if clabels: - for c, value in enumerate(clabels[self.columns.nlevels - 1]): - header_element_visible = _is_visible(c, 0, col_lengths) - + last_level = self.columns.nlevels - 1 # use last level since never sparsed + for c, value in enumerate(clabels[last_level]): + header_element_visible = _is_visible(c, last_level, col_lengths) if header_element_visible: - col_count += 1 - if col_count > max_cols: + visible_col_count += 1 + if visible_col_count > max_cols: column_blanks.append( _element( "th", @@ -498,6 +497,7 @@ def _generate_index_names_row(self, iter: tuple, max_cols: int, col_lengths: dic ) ) break + column_blanks.append( _element( "th", @@ -582,31 +582,36 @@ def _generate_trimmed_row(self, max_cols: int) -> list: for c in range(self.data.index.nlevels) ] - data = [ - _element( - "td", - f"{self.css['data']} {self.css['col']}{c} {self.css['row_trim']}", - "...", - (c not in self.hidden_columns), - attributes="", - ) - for c in range(max_cols) - ] + data, visible_col_count = [], 0 + for c, _ in enumerate(self.columns): + data_element_visible = c not in self.hidden_columns + if data_element_visible: + visible_col_count += 1 + if visible_col_count > max_cols: + data.append( + _element( + "td", + ( + f"{self.css['data']} {self.css['row_trim']} " + f"{self.css['col_trim']}" + ), + "...", + True, + attributes="", + ) + ) + break - if len(self.data.columns) > max_cols: - # columns are also trimmed so we add the final element data.append( _element( "td", - ( - f"{self.css['data']} {self.css['row_trim']} " - f"{self.css['col_trim']}" - ), + f"{self.css['data']} {self.css['col']}{c} {self.css['row_trim']}", "...", - True, + data_element_visible, attributes="", ) ) + return index_headers + data def _generate_body_row( @@ -675,9 +680,14 @@ def _generate_body_row( index_headers.append(header_element) - data = [] + data, visible_col_count = [], 0 for c, value in enumerate(row_tup[1:]): - if c >= max_cols: + data_element_visible = ( + c not in self.hidden_columns and r not in self.hidden_rows + ) + if data_element_visible: + visible_col_count += 1 + if visible_col_count > max_cols: data.append( _element( "td", @@ -697,9 +707,6 @@ def _generate_body_row( if (r, c) in self.cell_context: cls = " " + self.cell_context[r, c] - data_element_visible = ( - c not in self.hidden_columns and r not in self.hidden_rows - ) data_element = _element( "td", ( From 7c2d8d54f8174481eec582605f39b091e55d4e13 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Mon, 1 Nov 2021 19:37:13 +0100 Subject: [PATCH 4/8] fix visible_row_count in level lengths on sparsify --- pandas/io/formats/style_render.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index e4046ce01c7e4..ae4e05160e70a 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -1280,15 +1280,15 @@ def _get_level_lengths( elif j not in hidden_elements: # then element must be part of sparsified section and is visible visible_row_count += 1 + if visible_row_count > max_index: + break # do not add a length since the render trim limit reached if lengths[(i, last_label)] == 0: # 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 but do not extend more than max - lengths[(i, last_label)] = min( - max_index, 1 + lengths[(i, last_label)] - ) + # else add to previous iteration + lengths[(i, last_label)] += 1 non_zero_lengths = { element: length for element, length in lengths.items() if length >= 1 From ae50d58c0ecf8030a600e4bc4fc1b9b090776acf Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Mon, 1 Nov 2021 23:33:11 +0100 Subject: [PATCH 5/8] edit tests after changes --- pandas/tests/io/formats/style/test_style.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py index ea869007dd521..134af5a9efa5c 100644 --- a/pandas/tests/io/formats/style/test_style.py +++ b/pandas/tests/io/formats/style/test_style.py @@ -216,9 +216,6 @@ def test_render_trimming_mi(): assert {"class": "data row_trim col_trim"}.items() <= ctx["body"][2][4].items() assert len(ctx["body"]) == 3 # 2 data rows + trimming row - assert len(ctx["head"][0]) == 5 # 2 indexes + 2 column headers + trimming col - assert {"attributes": 'colspan="2"'}.items() <= ctx["head"][0][2].items() - def test_render_empty_mi(): # GH 43305 @@ -1603,6 +1600,7 @@ def test_row_trimming_hide_index_mi(): def test_col_trimming_hide_columns(): + df = DataFrame([[1, 2, 3, 4, 5]]) with pd.option_context("styler.render.max_columns", 2): ctx = df.style.hide([0, 1], axis="columns")._translate(True, True) From 5a4a7fab00019f039104677dc906d99a6791d883 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Tue, 2 Nov 2021 19:19:44 +0100 Subject: [PATCH 6/8] add tests --- pandas/tests/io/formats/style/test_html.py | 97 +++++++++++++++++++++ pandas/tests/io/formats/style/test_style.py | 2 +- 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/pandas/tests/io/formats/style/test_html.py b/pandas/tests/io/formats/style/test_html.py index e15283e558479..b2aceb76caf07 100644 --- a/pandas/tests/io/formats/style/test_html.py +++ b/pandas/tests/io/formats/style/test_html.py @@ -3,6 +3,7 @@ import numpy as np import pytest +import pandas as pd from pandas import ( DataFrame, MultiIndex, @@ -668,3 +669,99 @@ def test_hiding_index_columns_multiindex_alignment(): """ ) assert result == expected + + +def test_hiding_index_columns_multiindex_trimming(): + # gh 44272 + df = DataFrame(np.arange(64).reshape(8, 8)) + df.columns = MultiIndex.from_product([[0, 1, 2, 3], [0, 1]]) + df.index = MultiIndex.from_product([[0, 1, 2, 3], [0, 1]]) + df.index.names, df.columns.names = ["a", "b"], ["c", "d"] + styler = Styler(df, cell_ids=False, uuid_len=0) + styler.hide([(0, 0), (0, 1), (1, 0)], axis=1).hide([(0, 0), (0, 1), (1, 0)], axis=0) + with option_context("styler.render.max_rows", 4, "styler.render.max_columns", 4): + result = styler.to_html() + + expected = dedent( + """\ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 c123
 d1010...
ab     
1127282930...
2035363738...
143444546...
3051525354...
.....................
+ """ + ) + + assert result == expected diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py index 134af5a9efa5c..1cedb76a77ecd 100644 --- a/pandas/tests/io/formats/style/test_style.py +++ b/pandas/tests/io/formats/style/test_style.py @@ -1600,7 +1600,7 @@ def test_row_trimming_hide_index_mi(): def test_col_trimming_hide_columns(): - + # gh 44272 df = DataFrame([[1, 2, 3, 4, 5]]) with pd.option_context("styler.render.max_columns", 2): ctx = df.style.hide([0, 1], axis="columns")._translate(True, True) From ada08c0d677596fc2cdabac18d696fa70d067443 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Tue, 2 Nov 2021 19:58:46 +0100 Subject: [PATCH 7/8] edit test --- pandas/tests/io/formats/style/test_style.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py index 1cedb76a77ecd..281ab0d8b8e56 100644 --- a/pandas/tests/io/formats/style/test_style.py +++ b/pandas/tests/io/formats/style/test_style.py @@ -1609,3 +1609,5 @@ def test_col_trimming_hide_columns(): for c, vals in enumerate([(1, False), (2, True), (3, True), ("...", True)]): assert ctx["head"][0][c + 2]["value"] == vals[0] assert ctx["head"][0][c + 2]["is_visible"] == vals[1] + + assert len(ctx["body"][0]) == 6 # index + 2 hidden + 2 visible + trimming col From 197dedf349974dfc287632f7bbe54c9fa504df1a Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Tue, 2 Nov 2021 20:01:10 +0100 Subject: [PATCH 8/8] fix lint --- pandas/tests/io/formats/style/test_html.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/tests/io/formats/style/test_html.py b/pandas/tests/io/formats/style/test_html.py index b2aceb76caf07..b778d18618bf4 100644 --- a/pandas/tests/io/formats/style/test_html.py +++ b/pandas/tests/io/formats/style/test_html.py @@ -3,7 +3,6 @@ import numpy as np import pytest -import pandas as pd from pandas import ( DataFrame, MultiIndex,