From 87cb40b71f6d0f6117a6f428a7be72000cf3535f Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Wed, 1 Sep 2021 16:43:38 +0200 Subject: [PATCH 1/4] add hidden_idx_names control --- pandas/io/formats/style.py | 30 +++++++++++++++++++++ pandas/io/formats/style_render.py | 2 ++ pandas/tests/io/formats/style/test_style.py | 4 +++ 3 files changed, 36 insertions(+) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 7c3d7fe57b7b1..1f8c36e0cac63 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1109,6 +1109,8 @@ def _copy(self, deepcopy: bool = False) -> Styler: shallow = [ # simple string or boolean immutables "hide_index_", "hide_columns_", + "hide_column_names", + "hide_index_names", "table_attributes", "cell_ids", "caption", @@ -2019,6 +2021,7 @@ def hide_index( self, subset: Subset | None = None, level: Level | list[Level] | None = None, + names: bool = False, ) -> Styler: """ Hide the entire index, or specific keys in the index from rendering. @@ -2042,6 +2045,11 @@ def hide_index( The level(s) to hide in a MultiIndex if hiding the entire index. Cannot be used simultaneously with ``subset``. + .. versionadded:: 1.4.0 + names : bool + Whether to hide the index name(s), in the case the index or part of it + is visible. + .. versionadded:: 1.4.0 Returns @@ -2109,6 +2117,11 @@ def hide_index( raise ValueError("`subset` and `level` cannot be passed simultaneously") if subset is None: + if level is None and names: + # this combination implies user shows the index and hides just names + self.hide_index_names = True + return self + levels_ = _refactor_levels(level, self.index) self.hide_index_ = [ True if lev in levels_ else False for lev in range(self.index.nlevels) @@ -2121,12 +2134,16 @@ def hide_index( # error: Incompatible types in assignment (expression has type # "ndarray", variable has type "Sequence[int]") self.hidden_rows = hrows # type: ignore[assignment] + + if names: + self.hide_index_names = True return self def hide_columns( self, subset: Subset | None = None, level: Level | list[Level] | None = None, + names: bool = False, ) -> Styler: """ Hide the column headers or specific keys in the columns from rendering. @@ -2150,6 +2167,11 @@ def hide_columns( The level(s) to hide in a MultiIndex if hiding the entire column headers row. Cannot be used simultaneously with ``subset``. + .. versionadded:: 1.4.0 + names : bool + Whether to hide the column index name(s), in the case all column headers, + or some levels, are visible. + .. versionadded:: 1.4.0 Returns @@ -2221,6 +2243,11 @@ def hide_columns( raise ValueError("`subset` and `level` cannot be passed simultaneously") if subset is None: + if level is None and names: + # this combination implies user shows the column headers but hides names + self.hide_column_names = True + return self + levels_ = _refactor_levels(level, self.columns) self.hide_columns_ = [ True if lev in levels_ else False for lev in range(self.columns.nlevels) @@ -2233,6 +2260,9 @@ def hide_columns( # error: Incompatible types in assignment (expression has type # "ndarray", variable has type "Sequence[int]") self.hidden_columns = hcols # type: ignore[assignment] + + if names: + self.hide_column_names = True return self # ----------------------------------------------------------------------- diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index f51a1f5d9809d..32c70355c3de2 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -98,6 +98,8 @@ def __init__( self.cell_ids = cell_ids # add rendering variables + self.hide_index_names: bool = False + self.hide_column_names: bool = False self.hide_index_: list = [False] * self.index.nlevels self.hide_columns_: list = [False] * self.columns.nlevels self.hidden_rows: Sequence[int] = [] # sequence for specific hidden rows/cols diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py index dc8be68532f0e..beff3e65b7641 100644 --- a/pandas/tests/io/formats/style/test_style.py +++ b/pandas/tests/io/formats/style/test_style.py @@ -48,8 +48,10 @@ def mi_styler_comp(mi_styler): mi_styler.set_table_styles([{"selector": "a", "props": "a:v;"}]) mi_styler.hide_columns() mi_styler.hide_columns([("c0", "c1_a")]) + mi_styler.hide_columns(names=True) mi_styler.hide_index() mi_styler.hide_index([("i0", "i1_a")]) + mi_styler.hide_index(names=True) mi_styler.set_table_attributes('class="box"') mi_styler.format(na_rep="MISSING", precision=3) mi_styler.highlight_max(axis=None) @@ -239,6 +241,8 @@ def test_copy(comprehensive, render, deepcopy, mi_styler, mi_styler_comp): "cell_ids", "hide_index_", "hide_columns_", + "hide_index_names", + "hide_column_names", "table_attributes", ] for attr in shallow: From bac3acf78d9deb88894fc5fbf77edf1a2c953fe0 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Wed, 1 Sep 2021 17:57:03 +0200 Subject: [PATCH 2/4] add logic --- pandas/io/formats/style_render.py | 1 + pandas/tests/io/formats/style/test_style.py | 36 +++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 32c70355c3de2..14fb4bb0c7f88 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -385,6 +385,7 @@ def _translate_header( and com.any_not_none(*self.data.index.names) and not all(self.hide_index_) and not all(self.hide_columns_) + and not self.hide_index_names ): index_names = [ _element( diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py index beff3e65b7641..de16b20611193 100644 --- a/pandas/tests/io/formats/style/test_style.py +++ b/pandas/tests/io/formats/style/test_style.py @@ -1402,3 +1402,39 @@ def test_non_reducing_multi_slice_on_multiindex(self, slice_): with tm.assert_produces_warning(warn, match=msg): result = df.loc[non_reducing_slice(slice_)] tm.assert_frame_equal(result, expected) + + +def test_hidden_index_names(mi_df): + mi_df.index.names = ["Lev0", "Lev1"] + mi_styler = mi_df.style + ctx = mi_styler._translate(True, True) + assert len(ctx["head"]) == 3 # 2 column index levels + 1 index names row + + mi_styler.hide_index(names=True) + ctx = mi_styler._translate(True, True) + assert len(ctx["head"]) == 2 # index names row is unparsed + for i in range(4): + assert ctx["body"][0][i]["is_visible"] # 2 index levels + 2 data values visible + + mi_styler.hide_index(level=1) + ctx = mi_styler._translate(True, True) + assert len(ctx["head"]) == 2 # index names row is still hidden + assert ctx["body"][0][0]["is_visible"] is True + assert ctx["body"][0][1]["is_visible"] is False + + +def test_hidden_column_names(mi_df): + mi_df.columns.names = ["Lev0", "Lev1"] + mi_styler = mi_df.style + ctx = mi_styler._translate(True, True) + assert ctx["head"][0][1]["display_value"] == "Lev0" + assert ctx["head"][1][0]["display_value"] == "Lev1" + + mi_styler.hide_columns(names=True) + ctx = mi_styler._translate(True, True) + + mi_styler.hide_index(level=1) + ctx = mi_styler._translate(True, True) + assert len(ctx["head"]) == 2 # index names row is still hidden + assert ctx["body"][0][0]["is_visible"] is True + assert ctx["body"][0][1]["is_visible"] is False From 3660d8be8d78b48f4142e82c94e93d8fe5d324bc Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Wed, 1 Sep 2021 19:11:40 +0200 Subject: [PATCH 3/4] docs logic and tests finalise --- pandas/io/formats/style.py | 30 +++++++++++++++++++-- pandas/io/formats/style_render.py | 4 ++- pandas/tests/io/formats/style/test_style.py | 11 ++++---- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 45aafe0dea239..1966cf21034da 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -2053,7 +2053,7 @@ def hide_index( .. versionadded:: 1.4.0 names : bool Whether to hide the index name(s), in the case the index or part of it - is visible. + remains visible. .. versionadded:: 1.4.0 @@ -2108,7 +2108,7 @@ def hide_index( Hide a specific level: - >>> df.style.format("{:,.1f").hide_index(level=1) # doctest: +SKIP + >>> df.style.format("{:,.1f}").hide_index(level=1) # doctest: +SKIP x y a b c a b c x 0.1 0.0 0.4 1.3 0.6 -1.4 @@ -2117,6 +2117,19 @@ def hide_index( y 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 + + Hiding just the index level names: + + >>> df.index.names = ["lev0", "lev1"] + >>> df.style.format("{:,.1f}").hide_index(names=True) + x y + a b c a b c + x a 0.1 0.0 0.4 1.3 0.6 -1.4 + b 0.7 1.0 1.3 1.5 -0.0 -0.2 + c 1.4 -0.8 1.6 -0.2 -0.4 -0.3 + y a 0.4 1.0 -0.2 -0.8 -1.2 1.1 + b -0.6 1.2 1.8 1.9 0.3 0.3 + c 0.8 0.5 -0.3 1.2 2.2 -0.8 """ if level is not None and subset is not None: raise ValueError("`subset` and `level` cannot be passed simultaneously") @@ -2243,6 +2256,19 @@ def hide_columns( y a 0.4 1.0 -0.2 -0.8 -1.2 1.1 b -0.6 1.2 1.8 1.9 0.3 0.3 c 0.8 0.5 -0.3 1.2 2.2 -0.8 + + Hiding just the column level names: + + >>> df.columns.names = ["lev0", "lev1"] + >>> df.style.format("{:.1f").hide_columns(names=True) # doctest: +SKIP + x y + a b c a b c + x a 0.1 0.0 0.4 1.3 0.6 -1.4 + b 0.7 1.0 1.3 1.5 -0.0 -0.2 + c 1.4 -0.8 1.6 -0.2 -0.4 -0.3 + y a 0.4 1.0 -0.2 -0.8 -1.2 1.1 + b -0.6 1.2 1.8 1.9 0.3 0.3 + c 0.8 0.5 -0.3 1.2 2.2 -0.8 """ if level is not None and subset is not None: raise ValueError("`subset` and `level` cannot be passed simultaneously") diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 14fb4bb0c7f88..e94dc63530163 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -336,7 +336,9 @@ def _translate_header( _element( "th", f"{blank_class if name is None else index_name_class} level{r}", - name if name is not None else blank_value, + name + if (name is not None and not self.hide_column_names) + else blank_value, not all(self.hide_index_), ) ] diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py index de16b20611193..4fb8c90bbd77e 100644 --- a/pandas/tests/io/formats/style/test_style.py +++ b/pandas/tests/io/formats/style/test_style.py @@ -1428,13 +1428,14 @@ def test_hidden_column_names(mi_df): mi_styler = mi_df.style ctx = mi_styler._translate(True, True) assert ctx["head"][0][1]["display_value"] == "Lev0" - assert ctx["head"][1][0]["display_value"] == "Lev1" + assert ctx["head"][1][1]["display_value"] == "Lev1" mi_styler.hide_columns(names=True) ctx = mi_styler._translate(True, True) + assert ctx["head"][0][1]["display_value"] == " " + assert ctx["head"][1][1]["display_value"] == " " - mi_styler.hide_index(level=1) + mi_styler.hide_columns(level=0) ctx = mi_styler._translate(True, True) - assert len(ctx["head"]) == 2 # index names row is still hidden - assert ctx["body"][0][0]["is_visible"] is True - assert ctx["body"][0][1]["is_visible"] is False + assert len(ctx["head"]) == 1 # no index names and only one visible column headers + assert ctx["head"][0][1]["display_value"] == " " From 2e7d80be34d60db5ce2c944e2ae28574dbbabfd5 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Wed, 1 Sep 2021 19:22:34 +0200 Subject: [PATCH 4/4] skip doctest --- 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 1966cf21034da..9e0769f22e102 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -2121,7 +2121,7 @@ def hide_index( Hiding just the index level names: >>> df.index.names = ["lev0", "lev1"] - >>> df.style.format("{:,.1f}").hide_index(names=True) + >>> df.style.format("{:,.1f}").hide_index(names=True) # doctest: +SKIP x y a b c a b c x a 0.1 0.0 0.4 1.3 0.6 -1.4