From b0bfab7c43c58e8c4ee2799ebd4747caa4530d75 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Sun, 2 May 2021 13:18:42 +0200 Subject: [PATCH 01/16] ENH: make hide_index and hide_columns consistent in functionality --- pandas/io/formats/style.py | 172 +++++++++++++++++++++++++++--- pandas/io/formats/style_render.py | 69 ++++++------ 2 files changed, 197 insertions(+), 44 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 02e1369a05b93..753c385b1abe9 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -28,6 +28,7 @@ from pandas.util._decorators import doc import pandas as pd +from pandas import IndexSlice from pandas.api.types import is_list_like from pandas.core import generic import pandas.core.common as com @@ -640,7 +641,7 @@ def apply( def _applymap(self, func: Callable, subset=None, **kwargs) -> Styler: func = partial(func, **kwargs) # applymap doesn't take kwargs? if subset is None: - subset = pd.IndexSlice[:] + subset = IndexSlice[:] subset = non_reducing_slice(subset) result = self.data.loc[subset].applymap(func) self._update_ctx(result) @@ -1035,37 +1036,180 @@ def set_na_rep(self, na_rep: str) -> StylerRenderer: self.na_rep = na_rep return self.format(na_rep=na_rep, precision=self.precision) - def hide_index(self) -> Styler: + def hide_index(self, subset=None, show: bool = False) -> Styler: """ - Hide any indices from rendering. + Hide the entire index, or specific keys in the index from rendering. + + This method has dual functionality: + + - if ``subset`` is ``None`` then the entire index will be hidden whilst + displaying all data-rows. + - if a ``subset`` is given then those specific rows will be hidden whilst the + index itself remains visible. + + Parameters + ---------- + subset : IndexSlice + An argument to ``DataFrame.loc`` along the index, that identifies which + index key rows will be hidden. + show : bool + Indicates whether the function `hides` the index or selected rows, + by default, or operates inversely by exclusively showing them. Returns ------- self : Styler + + See Also + -------- + Styler.hide_columns: Hide the entire column headers row, or specific columns. + + Examples + -------- + Hide the index and retain the data values: + + >>> midx = pd.MultiIndex.from_product([["x", "y"], ["a", "b", "c"]]) + >>> df = pd.DataFrame(np.random.randn(6,6), index=midx, columns=midx) + >>> df.style.format("{:.1f}").hide_index() + x y + a b c a b c + 0.1 0.0 0.4 1.3 0.6 -1.4 + 0.7 1.0 1.3 1.5 -0.0 -0.2 + 1.4 -0.8 1.6 -0.2 -0.4 -0.3 + 0.4 1.0 -0.2 -0.8 -1.2 1.1 + -0.6 1.2 1.8 1.9 0.3 0.3 + 0.8 0.5 -0.3 1.2 2.2 -0.8 + + Hide specific rows but retain the index: + + >>> df.style.format("{:.1f}").hide_index(subset=(slice(None), ["a", "c"])) + x y + a b c a b c + x b 0.7 1.0 1.3 1.5 -0.0 -0.2 + y b -0.6 1.2 1.8 1.9 0.3 0.3 + + Hide specific rows and the index: + + >>> df.style.format("{:.1f}").hide_index(subset=(slice(None), ["a", "c"])) + ... .hide_index() + x y + a b c a b c + 0.7 1.0 1.3 1.5 -0.0 -0.2 + -0.6 1.2 1.8 1.9 0.3 0.3 + + Exclusively show specific rows: + + >>> df.style.format("{:.1f}") + ... .hide_index(subset=(slice(None), ["b"]), show=True) + x y + a b c a b c + x b 0.7 1.0 1.3 1.5 -0.0 -0.2 + y b -0.6 1.2 1.8 1.9 0.3 0.3 """ - self.hidden_index = True + if subset is None: + self.hidden_index = not show + else: + subset = IndexSlice[subset, :] + subset = non_reducing_slice(subset) + hide = self.data.loc[subset] + if show: # invert the display + hide = self.data.loc[~self.data.index.isin(hide.index.to_list()), :] + hrows = self.index.get_indexer_for(hide.index) + # error: Incompatible types in assignment (expression has type + # "ndarray", variable has type "Sequence[int]") + self.hidden_rows = hrows # type: ignore[assignment] return self - def hide_columns(self, subset) -> Styler: + def hide_columns(self, subset=None, show: bool = False) -> Styler: """ - Hide columns from rendering. + Hide the column headers or specific keys in the columns from rendering. + + This method has dual functionality: + + - if ``subset`` is ``None`` then the entire column headers row will be hidden + whilst the data-values remain visible. + - if a ``subset`` is given then those specific columns, including the + data-values will be hidden, whilst the column headers row remains visible. Parameters ---------- subset : IndexSlice - An argument to ``DataFrame.loc`` that identifies which columns - are hidden. + An argument to ``DataFrame.loc`` along the columns, that identifies which + columns keys will be hidden. + show : bool + Indicates whether the function `hides` the columns headers or selected + columns, by default, or operates inversely by exclusively showing them. Returns ------- self : Styler + + See Also + -------- + Styler.hide_index: Hide the entire index, or specific keys in the index. + + Examples + -------- + Hide column headers and retain the data values: + + >>> midx = pd.MultiIndex.from_product([["x", "y"], ["a", "b", "c"]]) + >>> df = pd.DataFrame(np.random.randn(6,6), index=midx, columns=midx) + >>> df.style.format("{:.1f}").hide_columns() + x d 0.1 0.0 0.4 1.3 0.6 -1.4 + e 0.7 1.0 1.3 1.5 -0.0 -0.2 + f 1.4 -0.8 1.6 -0.2 -0.4 -0.3 + y d 0.4 1.0 -0.2 -0.8 -1.2 1.1 + e -0.6 1.2 1.8 1.9 0.3 0.3 + f 0.8 0.5 -0.3 1.2 2.2 -0.8 + + Hide specific columns but retain the column headers: + + >>> df.style.format("{:.1f}").hide_columns(subset=(slice(None), ["a", "c"])) + x y + b b + x a 0.0 0.6 + b 1.0 -0.0 + c -0.8 -0.4 + y a 1.0 -1.2 + b 1.2 0.3 + c 0.5 2.2 + + Hide specific columns and the column headers: + + >>> df.style.format("{:.1f}").hide_columns(subset=(slice(None), ["a", "c"])) + ... .hide_columns() + x a 0.0 0.6 + b 1.0 -0.0 + c -0.8 -0.4 + y a 1.0 -1.2 + b 1.2 0.3 + c 0.5 2.2 + + Exclusively show specific columns: + + >>> df.style.format("{:.1f}") + ... .hide_columns(subset=(slice(None), ["b"]), show=True) + x y + b b + x a 0.0 0.6 + b 1.0 -0.0 + c -0.8 -0.4 + y a 1.0 -1.2 + b 1.2 0.3 + c 0.5 2.2 """ - subset = non_reducing_slice(subset) - hidden_df = self.data.loc[subset] - hcols = self.columns.get_indexer_for(hidden_df.columns) - # error: Incompatible types in assignment (expression has type - # "ndarray", variable has type "Sequence[int]") - self.hidden_columns = hcols # type: ignore[assignment] + if subset is None: + self.hidden_colheads = not show + else: + subset = IndexSlice[:, subset] + subset = non_reducing_slice(subset) + hide = self.data.loc[subset] + if show: # invert the display + hide = self.data.loc[:, ~self.data.columns.isin(hide.columns.to_list())] + hcols = self.columns.get_indexer_for(hide.columns) + # error: Incompatible types in assignment (expression has type + # "ndarray", variable has type "Sequence[int]") + self.hidden_columns = hcols # type: ignore[assignment] return self # ----------------------------------------------------------------------- diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 0253852fbb39a..7b34b67fb5040 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -98,6 +98,8 @@ def __init__( # add rendering variables self.hidden_index: bool = False + self.hidden_rows: Sequence[int] = [] + self.hidden_colheads: bool = False self.hidden_columns: Sequence[int] = [] self.ctx: DefaultDict[tuple[int, int], CSSList] = defaultdict(list) self.cell_context: DefaultDict[tuple[int, int], str] = defaultdict(str) @@ -209,37 +211,38 @@ def _translate_header( head = [] # 1) column headers - for r in range(self.data.columns.nlevels): - index_blanks = [ - _element("th", blank_class, blank_value, not self.hidden_index) - ] * (self.data.index.nlevels - 1) - - name = self.data.columns.names[r] - column_name = [ - _element( - "th", - f"{blank_class if name is None else index_name_class} level{r}", - name if name is not None else blank_value, - not self.hidden_index, - ) - ] - - if clabels: - column_headers = [ + if not self.hidden_colheads: + for r in range(self.data.columns.nlevels): + index_blanks = [ + _element("th", blank_class, blank_value, not self.hidden_index) + ] * (self.data.index.nlevels - 1) + + name = self.data.columns.names[r] + column_name = [ _element( "th", - f"{col_heading_class} level{r} col{c}", - value, - _is_visible(c, r, col_lengths), - attributes=( - f'colspan="{col_lengths.get((r, c), 0)}"' - if col_lengths.get((r, c), 0) > 1 - else "" - ), + f"{blank_class if name is None else index_name_class} level{r}", + name if name is not None else blank_value, + not self.hidden_index, ) - for c, value in enumerate(clabels[r]) ] - head.append(index_blanks + column_name + column_headers) + + if clabels: + column_headers = [ + _element( + "th", + f"{col_heading_class} level{r} col{c}", + value, + _is_visible(c, r, col_lengths), + attributes=( + f'colspan="{col_lengths.get((r, c), 0)}"' + if col_lengths.get((r, c), 0) > 1 + else "" + ), + ) + for c, value in enumerate(clabels[r]) + ] + head.append(index_blanks + column_name + column_headers) # 2) index names if ( @@ -281,7 +284,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, self.hidden_rows) rlabels = self.data.index.tolist() if self.data.index.nlevels == 1: @@ -316,7 +319,7 @@ def _translate_body(self, data_class, row_heading_class): "td", f"{data_class} row{r} col{c}{cls}", value, - (c not in self.hidden_columns), + (c not in self.hidden_columns and r not in self.hidden_rows), attributes="", display_value=self._display_funcs[(r, c)](value), ) @@ -565,7 +568,13 @@ def _get_level_lengths(index, hidden_elements=None): last_label = j lengths[(i, last_label)] = 0 elif j not in hidden_elements: - lengths[(i, last_label)] += 1 + if lengths[(i, last_label)] == 0: + # if the previous iteration was first-of-kind but hidden then offset + last_label = j + lengths[(i, last_label)] = 1 + else: + # 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 c615b08ae2ba27be9b643270640af69fce2e1557 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Sun, 2 May 2021 16:51:46 +0200 Subject: [PATCH 02/16] DOC: improve specificity --- pandas/io/formats/style.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 753c385b1abe9..0a3217148f487 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1050,8 +1050,8 @@ def hide_index(self, subset=None, show: bool = False) -> Styler: Parameters ---------- subset : IndexSlice - An argument to ``DataFrame.loc`` along the index, that identifies which - index key rows will be hidden. + An argument to ``DataFrame.loc[subset, :]``, i.e. along the index, that + identifies which index key rows will be hidden. show : bool Indicates whether the function `hides` the index or selected rows, by default, or operates inversely by exclusively showing them. @@ -1134,8 +1134,8 @@ def hide_columns(self, subset=None, show: bool = False) -> Styler: Parameters ---------- subset : IndexSlice - An argument to ``DataFrame.loc`` along the columns, that identifies which - columns keys will be hidden. + An argument to ``DataFrame.loc[:, subset]``, i.e. along the columns, that + identifies which columns keys will be hidden. show : bool Indicates whether the function `hides` the columns headers or selected columns, by default, or operates inversely by exclusively showing them. From 38b74075bdf028b5474cf6d901fda3d03c41dfe4 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Sun, 2 May 2021 17:37:10 +0200 Subject: [PATCH 03/16] TST: add tests for hide_index(subset=) --- pandas/tests/io/formats/style/test_style.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py index 3b614be770bc5..147fc27d653f0 100644 --- a/pandas/tests/io/formats/style/test_style.py +++ b/pandas/tests/io/formats/style/test_style.py @@ -1152,7 +1152,8 @@ def test_hide_columns_mult_levels(self): # hide first column only ctx = df.style.hide_columns([("b", 0)])._translate() - assert ctx["head"][0][2]["is_visible"] # b + assert not ctx["head"][0][2]["is_visible"] # b + assert ctx["head"][0][3]["is_visible"] # b assert not ctx["head"][1][2]["is_visible"] # 0 assert not ctx["body"][1][2]["is_visible"] # 3 assert ctx["body"][1][3]["is_visible"] @@ -1168,6 +1169,18 @@ def test_hide_columns_mult_levels(self): assert ctx["body"][1][2]["is_visible"] assert ctx["body"][1][2]["display_value"] == 3 + # hide top row level, which hides both rows + ctx = df.style.hide_index("a")._translate() + for i in [0, 1, 2, 3]: + assert not ctx["body"][0][i]["is_visible"] + assert not ctx["body"][1][i]["is_visible"] + + # hide first row only + ctx = df.style.hide_index(("a", 0))._translate() + for i in [0, 1, 2, 3]: + assert not ctx["body"][0][i]["is_visible"] + assert ctx["body"][1][i]["is_visible"] + def test_pipe(self): def set_caption_from_template(styler, a, b): return styler.set_caption(f"Dataframe with a = {a} and b = {b}") From 40504dba054fbf7b56c7df1bc27bb1ecbd88f0fa Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Sun, 2 May 2021 17:45:48 +0200 Subject: [PATCH 04/16] TST: add tests for hide_columns(subset=None) --- pandas/tests/io/formats/style/test_style.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py index 147fc27d653f0..8b9667cf8a872 100644 --- a/pandas/tests/io/formats/style/test_style.py +++ b/pandas/tests/io/formats/style/test_style.py @@ -1052,6 +1052,14 @@ def test_mi_sparse_column_names(self): ] assert head == expected + def test_hide_column_headers(self): + ctx = self.styler.hide_columns()._translate() + assert len(ctx["head"]) == 0 # no header entries with an unnamed index + + self.df.index.name = "some_name" + ctx = self.df.style.hide_columns()._translate() + assert len(ctx["head"]) == 1 # only a single row for index names: no col heads + def test_hide_single_index(self): # GH 14194 # single unnamed index @@ -1120,7 +1128,7 @@ def test_hide_columns_single_level(self): assert not ctx["body"][0][1]["is_visible"] # col A, row 1 assert not ctx["body"][1][2]["is_visible"] # col B, row 1 - def test_hide_columns_mult_levels(self): + def test_hide_columns_index_mult_levels(self): # GH 14194 # setup dataframe with multiple column levels and indices i1 = pd.MultiIndex.from_arrays( From 67598a4eeaf5b423570aecdf4171e5bd6d3f82cd Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Wed, 5 May 2021 16:43:15 +0200 Subject: [PATCH 05/16] remove show option --- pandas/io/formats/style.py | 31 ++++--------------------------- 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 0a3217148f487..84ee9bd9cff94 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1036,7 +1036,7 @@ def set_na_rep(self, na_rep: str) -> StylerRenderer: self.na_rep = na_rep return self.format(na_rep=na_rep, precision=self.precision) - def hide_index(self, subset=None, show: bool = False) -> Styler: + def hide_index(self, subset=None) -> Styler: """ Hide the entire index, or specific keys in the index from rendering. @@ -1052,9 +1052,6 @@ def hide_index(self, subset=None, show: bool = False) -> Styler: subset : IndexSlice An argument to ``DataFrame.loc[subset, :]``, i.e. along the index, that identifies which index key rows will be hidden. - show : bool - Indicates whether the function `hides` the index or selected rows, - by default, or operates inversely by exclusively showing them. Returns ------- @@ -1107,20 +1104,18 @@ def hide_index(self, subset=None, show: bool = False) -> Styler: y b -0.6 1.2 1.8 1.9 0.3 0.3 """ if subset is None: - self.hidden_index = not show + self.hidden_index = True else: subset = IndexSlice[subset, :] subset = non_reducing_slice(subset) hide = self.data.loc[subset] - if show: # invert the display - hide = self.data.loc[~self.data.index.isin(hide.index.to_list()), :] hrows = self.index.get_indexer_for(hide.index) # error: Incompatible types in assignment (expression has type # "ndarray", variable has type "Sequence[int]") self.hidden_rows = hrows # type: ignore[assignment] return self - def hide_columns(self, subset=None, show: bool = False) -> Styler: + def hide_columns(self, subset=None) -> Styler: """ Hide the column headers or specific keys in the columns from rendering. @@ -1136,9 +1131,6 @@ def hide_columns(self, subset=None, show: bool = False) -> Styler: subset : IndexSlice An argument to ``DataFrame.loc[:, subset]``, i.e. along the columns, that identifies which columns keys will be hidden. - show : bool - Indicates whether the function `hides` the columns headers or selected - columns, by default, or operates inversely by exclusively showing them. Returns ------- @@ -1184,28 +1176,13 @@ def hide_columns(self, subset=None, show: bool = False) -> Styler: y a 1.0 -1.2 b 1.2 0.3 c 0.5 2.2 - - Exclusively show specific columns: - - >>> df.style.format("{:.1f}") - ... .hide_columns(subset=(slice(None), ["b"]), show=True) - x y - b b - x a 0.0 0.6 - b 1.0 -0.0 - c -0.8 -0.4 - y a 1.0 -1.2 - b 1.2 0.3 - c 0.5 2.2 """ if subset is None: - self.hidden_colheads = not show + self.hidden_colheads = True else: subset = IndexSlice[:, subset] subset = non_reducing_slice(subset) hide = self.data.loc[subset] - if show: # invert the display - hide = self.data.loc[:, ~self.data.columns.isin(hide.columns.to_list())] hcols = self.columns.get_indexer_for(hide.columns) # error: Incompatible types in assignment (expression has type # "ndarray", variable has type "Sequence[int]") From f05757a96f97ef72c195bd33f546b5330d4fff29 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Wed, 5 May 2021 16:44:28 +0200 Subject: [PATCH 06/16] remove show option --- pandas/io/formats/style.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 84ee9bd9cff94..6e054212ac360 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1093,15 +1093,6 @@ def hide_index(self, subset=None) -> Styler: a b c a b c 0.7 1.0 1.3 1.5 -0.0 -0.2 -0.6 1.2 1.8 1.9 0.3 0.3 - - Exclusively show specific rows: - - >>> df.style.format("{:.1f}") - ... .hide_index(subset=(slice(None), ["b"]), show=True) - x y - a b c a b c - x b 0.7 1.0 1.3 1.5 -0.0 -0.2 - y b -0.6 1.2 1.8 1.9 0.3 0.3 """ if subset is None: self.hidden_index = True From 57aa0fcbb2ac834df4f13015e5cbf291d9ea9353 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Wed, 12 May 2021 11:17:40 +0200 Subject: [PATCH 07/16] type subset --- pandas/io/formats/style.py | 19 ++++++++++++------- pandas/io/formats/style_render.py | 11 ++++++++--- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 898057f8cf4e0..4b0dc20b47196 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -44,6 +44,7 @@ CSSProperties, CSSStyles, StylerRenderer, + Subset, Tooltips, maybe_convert_css_to_tuples, non_reducing_slice, @@ -1051,7 +1052,7 @@ def set_na_rep(self, na_rep: str) -> StylerRenderer: self.na_rep = na_rep return self.format(na_rep=na_rep, precision=self.precision) - def hide_index(self, subset=None) -> Styler: + def hide_index(self, subset: Subset | None = None) -> Styler: """ Hide the entire index, or specific keys in the index from rendering. @@ -1062,6 +1063,8 @@ def hide_index(self, subset=None) -> Styler: - if a ``subset`` is given then those specific rows will be hidden whilst the index itself remains visible. + .. versionchanged:: 1.3.0 + Parameters ---------- subset : IndexSlice @@ -1112,8 +1115,8 @@ def hide_index(self, subset=None) -> Styler: if subset is None: self.hidden_index = True else: - subset = IndexSlice[subset, :] - subset = non_reducing_slice(subset) + subset_ = IndexSlice[subset, :] # new var so mypy reads not Optional + subset = non_reducing_slice(subset_) hide = self.data.loc[subset] hrows = self.index.get_indexer_for(hide.index) # error: Incompatible types in assignment (expression has type @@ -1121,7 +1124,7 @@ def hide_index(self, subset=None) -> Styler: self.hidden_rows = hrows # type: ignore[assignment] return self - def hide_columns(self, subset=None) -> Styler: + def hide_columns(self, subset: Subset | None = None) -> Styler: """ Hide the column headers or specific keys in the columns from rendering. @@ -1132,6 +1135,8 @@ def hide_columns(self, subset=None) -> Styler: - if a ``subset`` is given then those specific columns, including the data-values will be hidden, whilst the column headers row remains visible. + .. versionchanged:: 1.3.0 + Parameters ---------- subset : IndexSlice @@ -1186,8 +1191,8 @@ def hide_columns(self, subset=None) -> Styler: if subset is None: self.hidden_colheads = True else: - subset = IndexSlice[:, subset] - subset = non_reducing_slice(subset) + subset_ = IndexSlice[:, subset] # new var so mypy reads not Optional + subset = non_reducing_slice(subset_) hide = self.data.loc[subset] hcols = self.columns.get_indexer_for(hide.columns) # error: Incompatible types in assignment (expression has type @@ -1779,7 +1784,7 @@ def highlight_between( def highlight_quantile( self, - subset: IndexLabel | None = None, + subset: Subset | None = None, color: str = "yellow", axis: Axis | None = 0, q_left: float = 0.0, diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 9cdc1b40317e6..36689e55b2b18 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -55,6 +55,7 @@ class CSSDict(TypedDict): CSSStyles = List[CSSDict] +Subset = Union[slice, Sequence] class StylerRenderer: @@ -338,7 +339,7 @@ def _translate_body(self, data_class, row_heading_class): def format( self, formatter: ExtFormatter | None = None, - subset: slice | Sequence[Any] | None = None, + subset: Subset | None = None, na_rep: str | None = None, precision: int | None = None, decimal: str = ".", @@ -702,7 +703,7 @@ def _maybe_wrap_formatter( return lambda x: na_rep if isna(x) else func_2(x) -def non_reducing_slice(slice_): +def non_reducing_slice(slice_: Subset): """ Ensure that a slice doesn't reduce to a Series or Scalar. @@ -739,7 +740,11 @@ def pred(part) -> bool: # slice(a, b, c) slice_ = [slice_] # to tuplize later else: - slice_ = [part if pred(part) else [part] for part in slice_] + # error: Item "slice" of "Union[slice, Sequence[Any]]" has no attribute + # "__iter__" (not iterable) + # this else branch is exclusively list_list so can iterate. + slice_ = [p if pred(p) else [p] for p in slice_] # type: ignore[union-attr] + return tuple(slice_) From 515707dca167bca9ebb35e35bd2dac8837b065be Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Thu, 20 May 2021 09:49:38 +0200 Subject: [PATCH 08/16] docstring for subset --- pandas/io/formats/style.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 87a818d62cdfa..0e5f983dfe477 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1071,9 +1071,10 @@ def hide_index(self, subset: Subset | None = None) -> Styler: Parameters ---------- - subset : IndexSlice - An argument to ``DataFrame.loc[subset, :]``, i.e. along the index, that - identifies which index key rows will be hidden. + subset : label, array-like, IndexSlice + A valid 1d input or single key along the index axis within + `DataFrame.loc[, :]`, to limit ``data`` to *before* applying + the function. Returns ------- @@ -1143,9 +1144,10 @@ def hide_columns(self, subset: Subset | None = None) -> Styler: Parameters ---------- - subset : IndexSlice - An argument to ``DataFrame.loc[:, subset]``, i.e. along the columns, that - identifies which columns keys will be hidden. + subset : label, array-like, IndexSlice + A valid 1d input or single key along the columns axis within + `DataFrame.loc[:, ]`, to limit ``data`` to *before* applying + the function. Returns ------- From fa8eceef422c2a6fd96c69b9fbc84b6dbf6c573c Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Thu, 20 May 2021 10:01:08 +0200 Subject: [PATCH 09/16] simpler examples --- pandas/io/formats/style.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 0e5f983dfe477..9f3e166b21aba 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1071,7 +1071,7 @@ def hide_index(self, subset: Subset | None = None) -> Styler: Parameters ---------- - subset : label, array-like, IndexSlice + subset : label, array-like, IndexSlice, optional A valid 1d input or single key along the index axis within `DataFrame.loc[, :]`, to limit ``data`` to *before* applying the function. @@ -1086,6 +1086,13 @@ def hide_index(self, subset: Subset | None = None) -> Styler: Examples -------- + Simple application hiding specific rows: + + >>> df = pd.DataFrame([[1,2], [3,4], [5,6]], index=["a", "b", "c"]) + >>> df.style.hide_index(["a", "b"]) + 0 1 + c 5 6 + Hide the index and retain the data values: >>> midx = pd.MultiIndex.from_product([["x", "y"], ["a", "b", "c"]]) @@ -1144,7 +1151,7 @@ def hide_columns(self, subset: Subset | None = None) -> Styler: Parameters ---------- - subset : label, array-like, IndexSlice + subset : label, array-like, IndexSlice, optional A valid 1d input or single key along the columns axis within `DataFrame.loc[:, ]`, to limit ``data`` to *before* applying the function. @@ -1159,6 +1166,14 @@ def hide_columns(self, subset: Subset | None = None) -> Styler: Examples -------- + Simple application hiding specific rows: + + >>> df = pd.DataFrame([[1, 2, 3], [4, 5, 6]], columns=["a", "b", "c"]) + >>> df.style.hide_columns(["a", "b"]) + c + 0 3 + 1 6 + Hide column headers and retain the data values: >>> midx = pd.MultiIndex.from_product([["x", "y"], ["a", "b", "c"]]) From 0704210e5ef0abfc3c576bc52ee286d60cee6670 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Thu, 20 May 2021 10:09:11 +0200 Subject: [PATCH 10/16] nomenclature: hide_columns_ --- pandas/io/formats/style.py | 2 +- pandas/io/formats/style_render.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 9f3e166b21aba..be4abbdec4ee6 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1210,7 +1210,7 @@ def hide_columns(self, subset: Subset | None = None) -> Styler: c 0.5 2.2 """ if subset is None: - self.hidden_colheads = True + self.hide_columns_ = True else: subset_ = IndexSlice[:, subset] # new var so mypy reads not Optional subset = non_reducing_slice(subset_) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 1b0b83b0ec167..a89c91a4708c9 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -97,8 +97,8 @@ def __init__( # add rendering variables self.hidden_index: bool = False + self.hide_columns_: bool = False self.hidden_rows: Sequence[int] = [] - self.hidden_colheads: bool = False self.hidden_columns: Sequence[int] = [] self.ctx: DefaultDict[tuple[int, int], CSSList] = defaultdict(list) self.cell_context: DefaultDict[tuple[int, int], str] = defaultdict(str) @@ -259,7 +259,7 @@ def _translate_header( head = [] # 1) column headers - if not self.hidden_colheads: + if not self.hide_columns_: for r in range(self.data.columns.nlevels): index_blanks = [ _element("th", blank_class, blank_value, not self.hidden_index) From cb58422af3868ee43d8a4ae5f53360b9fcf5d55d Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Thu, 20 May 2021 10:16:13 +0200 Subject: [PATCH 11/16] nomenclature: hide_index_ --- pandas/io/formats/style.py | 6 +++--- pandas/io/formats/style_render.py | 10 +++++----- pandas/tests/io/formats/style/test_style.py | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index be4abbdec4ee6..120decdc681c5 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -499,7 +499,7 @@ def _copy(self, deepcopy: bool = False) -> Styler: ) styler.uuid = self.uuid - styler.hidden_index = self.hidden_index + styler.hide_index_ = self.hide_index_ if deepcopy: styler.ctx = copy.deepcopy(self.ctx) @@ -538,7 +538,7 @@ def clear(self) -> None: self.cell_context.clear() self._todo.clear() - self.hidden_index = False + self.hide_index_ = False self.hidden_columns = [] # self.format and self.table_styles may be dependent on user # input in self.__init__() @@ -1125,7 +1125,7 @@ def hide_index(self, subset: Subset | None = None) -> Styler: -0.6 1.2 1.8 1.9 0.3 0.3 """ if subset is None: - self.hidden_index = True + self.hide_index_ = True else: subset_ = IndexSlice[subset, :] # new var so mypy reads not Optional subset = non_reducing_slice(subset_) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index a89c91a4708c9..ba965b49afab4 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -96,7 +96,7 @@ def __init__( self.cell_ids = cell_ids # add rendering variables - self.hidden_index: bool = False + self.hide_index_: bool = False self.hide_columns_: bool = False self.hidden_rows: Sequence[int] = [] self.hidden_columns: Sequence[int] = [] @@ -262,7 +262,7 @@ def _translate_header( if not self.hide_columns_: for r in range(self.data.columns.nlevels): index_blanks = [ - _element("th", blank_class, blank_value, not self.hidden_index) + _element("th", blank_class, blank_value, not self.hide_index_) ] * (self.data.index.nlevels - 1) name = self.data.columns.names[r] @@ -271,7 +271,7 @@ def _translate_header( "th", f"{blank_class if name is None else index_name_class} level{r}", name if name is not None else blank_value, - not self.hidden_index, + not self.hide_index_, ) ] @@ -296,7 +296,7 @@ def _translate_header( if ( self.data.index.names and com.any_not_none(*self.data.index.names) - and not self.hidden_index + and not self.hide_index_ ): index_names = [ _element( @@ -363,7 +363,7 @@ def _translate_body( "th", f"{row_heading_class} level{c} row{r}", value, - (_is_visible(r, c, idx_lengths) and not self.hidden_index), + (_is_visible(r, c, idx_lengths) and not self.hide_index_), id=f"level{c}_row{r}", attributes=( f'rowspan="{idx_lengths.get((c, r), 0)}"' diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py index 326f22845521d..281199c2c482b 100644 --- a/pandas/tests/io/formats/style/test_style.py +++ b/pandas/tests/io/formats/style/test_style.py @@ -85,7 +85,7 @@ def test_copy(self, do_changes, do_render): [{"selector": "th", "props": [("foo", "bar")]}] ) self.styler.set_table_attributes('class="foo" data-bar') - self.styler.hidden_index = not self.styler.hidden_index + self.styler.hide_index_ = not self.styler.hide_index_ self.styler.hide_columns("A") classes = DataFrame( [["favorite-val red", ""], [None, "blue my-val"]], @@ -156,7 +156,7 @@ def test_copy(self, do_changes, do_render): "table_styles", "table_attributes", "cell_ids", - "hidden_index", + "hide_index_", "hidden_columns", "cell_context", ] @@ -181,7 +181,7 @@ def test_clear(self): assert len(s._todo) > 0 assert s.tooltips assert len(s.cell_context) > 0 - assert s.hidden_index is True + assert s.hide_index_ is True assert len(s.hidden_columns) > 0 s = s._compute() @@ -195,7 +195,7 @@ def test_clear(self): assert len(s._todo) == 0 assert not s.tooltips assert len(s.cell_context) == 0 - assert s.hidden_index is False + assert s.hide_index_ is False assert len(s.hidden_columns) == 0 def test_render(self): From 4d9626d49528a53570a4dead20cd3bdc3d11b2e3 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Thu, 20 May 2021 10:17:40 +0200 Subject: [PATCH 12/16] nomenclature: comments --- pandas/io/formats/style_render.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index ba965b49afab4..f1a8317ca10c7 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -96,9 +96,9 @@ def __init__( self.cell_ids = cell_ids # add rendering variables - self.hide_index_: bool = False + self.hide_index_: bool = False # bools for hiding col/row headers self.hide_columns_: bool = False - self.hidden_rows: Sequence[int] = [] + self.hidden_rows: Sequence[int] = [] # sequence for specific hidden rows/cols self.hidden_columns: Sequence[int] = [] self.ctx: DefaultDict[tuple[int, int], CSSList] = defaultdict(list) self.cell_context: DefaultDict[tuple[int, int], str] = defaultdict(str) From c6c108646c4c5f6169a433d15e45e79157ef9161 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Sat, 22 May 2021 19:16:07 +0200 Subject: [PATCH 13/16] merge upstream master --- pandas/tests/io/formats/style/test_style.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py index b145a4831c9f2..342aaed9abe70 100644 --- a/pandas/tests/io/formats/style/test_style.py +++ b/pandas/tests/io/formats/style/test_style.py @@ -1088,11 +1088,11 @@ def test_mi_sparse_column_names(self): assert head == expected def test_hide_column_headers(self): - ctx = self.styler.hide_columns()._translate() + ctx = self.styler.hide_columns()._translate(True, True) assert len(ctx["head"]) == 0 # no header entries with an unnamed index self.df.index.name = "some_name" - ctx = self.df.style.hide_columns()._translate() + ctx = self.df.style.hide_columns()._translate(True, True) assert len(ctx["head"]) == 1 # only a single row for index names: no col heads def test_hide_single_index(self): From 16a4da3b3bc668e06e85ba1228e0c69252838203 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Sun, 23 May 2021 22:41:10 +0200 Subject: [PATCH 14/16] fix spelling --- pandas/io/formats/style.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 9f6400c100029..ef743fd321042 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1208,7 +1208,7 @@ def hide_columns(self, subset: Subset | None = None) -> Styler: Examples -------- - Simple application hiding specific rows: + Simple application hiding specific columns: >>> df = pd.DataFrame([[1, 2, 3], [4, 5, 6]], columns=["a", "b", "c"]) >>> df.style.hide_columns(["a", "b"]) From ec5817c2d71503da3c2399994b5affd02360a64f Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Mon, 24 May 2021 17:18:29 +0200 Subject: [PATCH 15/16] adjust to_lext components after recent merge --- pandas/io/formats/style.py | 2 +- pandas/io/formats/style_render.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index b6fc93243d219..bdb43792351a6 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -680,7 +680,7 @@ def to_latex( self.data.columns = RangeIndex(stop=len(self.data.columns)) numeric_cols = self.data._get_numeric_data().columns.to_list() self.data.columns = _original_columns - column_format = "" if self.hidden_index else "l" * self.data.index.nlevels + column_format = "" if self.hide_index_ else "l" * self.data.index.nlevels for ci, _ in enumerate(self.data.columns): if ci not in self.hidden_columns: column_format += ( diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 1bbb2945cddcc..fd961c8e0da1b 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -429,7 +429,7 @@ def _translate_latex(self, d: dict) -> None: d["head"] = [[col for col in row if col["is_visible"]] for row in d["head"]] body = [] for r, row in enumerate(d["body"]): - if self.hidden_index: + if self.hide_index_: row_body_headers = [] else: row_body_headers = [ From 0921804c89489b115bf8dc94cf97c89e0dccb448 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Sat, 5 Jun 2021 07:41:54 +0200 Subject: [PATCH 16/16] merge upstream master --- pandas/io/formats/style_render.py | 50 +++++++++++++++---------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index fe9d4a5a821ab..514597d27a92b 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -315,34 +315,34 @@ def _translate_header( ) ] - if clabels: - column_headers = [ - _element( - "th", - f"{col_heading_class} level{r} col{c}", - value, - _is_visible(c, r, col_lengths), - attributes=( - f'colspan="{col_lengths.get((r, c), 0)}"' - if col_lengths.get((r, c), 0) > 1 - else "" - ), - ) - for c, value in enumerate(clabels[r]) - ] - - if len(self.data.columns) > max_cols: - # add an extra column with `...` value to indicate trimming - column_headers.append( + if clabels: + column_headers = [ _element( "th", - f"{col_heading_class} level{r} {trimmed_col_class}", - "...", - True, - attributes="", + f"{col_heading_class} level{r} col{c}", + value, + _is_visible(c, r, col_lengths), + attributes=( + f'colspan="{col_lengths.get((r, c), 0)}"' + if col_lengths.get((r, c), 0) > 1 + else "" + ), ) - ) - head.append(index_blanks + column_name + column_headers) + for c, value in enumerate(clabels[r]) + ] + + if len(self.data.columns) > max_cols: + # add an extra column with `...` value to indicate trimming + column_headers.append( + _element( + "th", + f"{col_heading_class} level{r} {trimmed_col_class}", + "...", + True, + attributes="", + ) + ) + head.append(index_blanks + column_name + column_headers) # 2) index names if (