diff --git a/asv_bench/benchmarks/io/style.py b/asv_bench/benchmarks/io/style.py index a01610a69278b..82166a2a95c76 100644 --- a/asv_bench/benchmarks/io/style.py +++ b/asv_bench/benchmarks/io/style.py @@ -20,19 +20,19 @@ def setup(self, cols, rows): def time_apply_render(self, cols, rows): self._style_apply() - self.st._render_html() + self.st._render_html(True, True) def peakmem_apply_render(self, cols, rows): self._style_apply() - self.st._render_html() + self.st._render_html(True, True) def time_classes_render(self, cols, rows): self._style_classes() - self.st._render_html() + self.st._render_html(True, True) def peakmem_classes_render(self, cols, rows): self._style_classes() - self.st._render_html() + self.st._render_html(True, True) def time_format_render(self, cols, rows): self._style_format() diff --git a/doc/source/user_guide/options.rst b/doc/source/user_guide/options.rst index 278eb907102ed..aa8a8fae417be 100644 --- a/doc/source/user_guide/options.rst +++ b/doc/source/user_guide/options.rst @@ -482,6 +482,11 @@ plotting.backend matplotlib Change the plotting backend like Bokeh, Altair, etc. plotting.matplotlib.register_converters True Register custom converters with matplotlib. Set to False to de-register. +styler.sparse.index True "Sparsify" MultiIndex display for rows + in Styler output (don't display repeated + elements in outer levels within groups). +styler.sparse.columns True "Sparsify" MultiIndex display for columns + in Styler output. ======================================= ============ ================================== diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 1eb22436204a8..cf41cee3f194a 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -139,6 +139,7 @@ precision, and perform HTML escaping (:issue:`40437` :issue:`40134`). There have properly format HTML and eliminate some inconsistencies (:issue:`39942` :issue:`40356` :issue:`39807` :issue:`39889` :issue:`39627`) :class:`.Styler` has also been compatible with non-unique index or columns, at least for as many features as are fully compatible, others made only partially compatible (:issue:`41269`). +One also has greater control of the display through separate sparsification of the index or columns, using the new 'styler' options context (:issue:`41142`). Documentation has also seen major revisions in light of new features (:issue:`39720` :issue:`39317` :issue:`40493`) diff --git a/pandas/core/config_init.py b/pandas/core/config_init.py index baac872a6a466..a88bc8900ccdd 100644 --- a/pandas/core/config_init.py +++ b/pandas/core/config_init.py @@ -726,3 +726,26 @@ def register_converter_cb(key): validator=is_one_of_factory(["auto", True, False]), cb=register_converter_cb, ) + +# ------ +# Styler +# ------ + +styler_sparse_index_doc = """ +: bool + Whether to sparsify the display of a hierarchical index. Setting to False will + display each explicit level element in a hierarchical key for each row. +""" + +styler_sparse_columns_doc = """ +: bool + Whether to sparsify the display of hierarchical columns. Setting to False will + display each explicit level element in a hierarchical key for each column. +""" + +with cf.config_prefix("styler"): + cf.register_option("sparse.index", True, styler_sparse_index_doc, validator=bool) + + cf.register_option( + "sparse.columns", True, styler_sparse_columns_doc, validator=bool + ) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index a96196a698f43..c8726d9008e7d 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -17,6 +17,8 @@ import numpy as np +from pandas._config import get_option + from pandas._typing import ( Axis, FrameOrSeries, @@ -201,14 +203,27 @@ def _repr_html_(self) -> str: """ Hooks into Jupyter notebook rich display system. """ - return self._render_html() + return self.render() - def render(self, **kwargs) -> str: + def render( + self, + sparse_index: bool | None = None, + sparse_columns: bool | None = None, + **kwargs, + ) -> str: """ Render the ``Styler`` including all applied styles to HTML. Parameters ---------- + sparse_index : bool, optional + Whether to sparsify the display of a hierarchical index. Setting to False + will display each explicit level element in a hierarchical key for each row. + Defaults to ``pandas.options.styler.sparse.index`` value. + sparse_columns : bool, optional + Whether to sparsify the display of a hierarchical index. Setting to False + will display each explicit level element in a hierarchical key for each row. + Defaults to ``pandas.options.styler.sparse.columns`` value. **kwargs Any additional keyword arguments are passed through to ``self.template.render``. @@ -240,7 +255,11 @@ def render(self, **kwargs) -> str: * caption * table_attributes """ - return self._render_html(**kwargs) + if sparse_index is None: + sparse_index = get_option("styler.sparse.index") + if sparse_columns is None: + sparse_columns = get_option("styler.sparse.columns") + return self._render_html(sparse_index, sparse_columns, **kwargs) def set_tooltips( self, diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 6f7d298c7dec0..e5fcf024c2805 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -107,14 +107,14 @@ def __init__( tuple[int, int], Callable[[Any], str] ] = defaultdict(lambda: partial(_default_formatter, precision=def_precision)) - def _render_html(self, **kwargs) -> str: + def _render_html(self, sparse_index: bool, sparse_columns: bool, **kwargs) -> str: """ Renders the ``Styler`` including all applied styles to HTML. Generates a dict with necessary kwargs passed to jinja2 template. """ self._compute() # TODO: namespace all the pandas keys - d = self._translate() + d = self._translate(sparse_index, sparse_columns) d.update(kwargs) return self.template_html.render(**d) @@ -133,9 +133,7 @@ def _compute(self): r = func(self)(*args, **kwargs) return r - def _translate( - self, sparsify_index: bool | None = None, sparsify_cols: bool | None = None - ): + def _translate(self, sparse_index: bool, sparse_cols: bool): """ Process Styler data and settings into a dict for template rendering. @@ -144,10 +142,12 @@ def _translate( 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 + sparse_index : bool + Whether to sparsify the index or print all hierarchical index elements. + Upstream defaults are typically to `pandas.options.styler.sparse.index`. + sparse_cols : bool + Whether to sparsify the columns or print all hierarchical column elements. + Upstream defaults are typically to `pandas.options.styler.sparse.columns`. Returns ------- @@ -155,11 +155,6 @@ def _translate( 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" @@ -176,14 +171,14 @@ def _translate( } head = self._translate_header( - BLANK_CLASS, BLANK_VALUE, INDEX_NAME_CLASS, COL_HEADING_CLASS, sparsify_cols + BLANK_CLASS, BLANK_VALUE, INDEX_NAME_CLASS, COL_HEADING_CLASS, sparse_cols ) d.update({"head": head}) self.cellstyle_map: DefaultDict[tuple[CSSPair, ...], list[str]] = defaultdict( list ) - body = self._translate_body(DATA_CLASS, ROW_HEADING_CLASS, sparsify_index) + body = self._translate_body(DATA_CLASS, ROW_HEADING_CLASS, sparse_index) d.update({"body": body}) cellstyle: list[dict[str, CSSList | list[str]]] = [ diff --git a/pandas/tests/io/formats/style/test_format.py b/pandas/tests/io/formats/style/test_format.py index 0f3e5863a4a99..9db27689a53f5 100644 --- a/pandas/tests/io/formats/style/test_format.py +++ b/pandas/tests/io/formats/style/test_format.py @@ -28,20 +28,20 @@ def styler(df): def test_display_format(styler): - ctx = styler.format("{:0.1f}")._translate() + ctx = styler.format("{:0.1f}")._translate(True, True) assert all(["display_value" in c for c in row] for row in ctx["body"]) assert all([len(c["display_value"]) <= 3 for c in row[1:]] for row in ctx["body"]) assert len(ctx["body"][0][1]["display_value"].lstrip("-")) <= 3 def test_format_dict(styler): - ctx = styler.format({"A": "{:0.1f}", "B": "{0:.2%}"})._translate() + ctx = styler.format({"A": "{:0.1f}", "B": "{0:.2%}"})._translate(True, True) assert ctx["body"][0][1]["display_value"] == "0.0" assert ctx["body"][0][2]["display_value"] == "-60.90%" def test_format_string(styler): - ctx = styler.format("{:.2f}")._translate() + ctx = styler.format("{:.2f}")._translate(True, True) assert ctx["body"][0][1]["display_value"] == "0.00" assert ctx["body"][0][2]["display_value"] == "-0.61" assert ctx["body"][1][1]["display_value"] == "1.00" @@ -49,7 +49,7 @@ def test_format_string(styler): def test_format_callable(styler): - ctx = styler.format(lambda v: "neg" if v < 0 else "pos")._translate() + ctx = styler.format(lambda v: "neg" if v < 0 else "pos")._translate(True, True) assert ctx["body"][0][1]["display_value"] == "pos" assert ctx["body"][0][2]["display_value"] == "neg" assert ctx["body"][1][1]["display_value"] == "pos" @@ -60,17 +60,17 @@ def test_format_with_na_rep(): # GH 21527 28358 df = DataFrame([[None, None], [1.1, 1.2]], columns=["A", "B"]) - ctx = df.style.format(None, na_rep="-")._translate() + ctx = df.style.format(None, na_rep="-")._translate(True, True) assert ctx["body"][0][1]["display_value"] == "-" assert ctx["body"][0][2]["display_value"] == "-" - ctx = df.style.format("{:.2%}", na_rep="-")._translate() + ctx = df.style.format("{:.2%}", na_rep="-")._translate(True, True) assert ctx["body"][0][1]["display_value"] == "-" assert ctx["body"][0][2]["display_value"] == "-" assert ctx["body"][1][1]["display_value"] == "110.00%" assert ctx["body"][1][2]["display_value"] == "120.00%" - ctx = df.style.format("{:.2%}", na_rep="-", subset=["B"])._translate() + ctx = df.style.format("{:.2%}", na_rep="-", subset=["B"])._translate(True, True) assert ctx["body"][0][2]["display_value"] == "-" assert ctx["body"][1][2]["display_value"] == "120.00%" @@ -85,13 +85,13 @@ def test_format_non_numeric_na(): ) with tm.assert_produces_warning(FutureWarning): - ctx = df.style.set_na_rep("NA")._translate() + ctx = df.style.set_na_rep("NA")._translate(True, True) assert ctx["body"][0][1]["display_value"] == "NA" assert ctx["body"][0][2]["display_value"] == "NA" assert ctx["body"][1][1]["display_value"] == "NA" assert ctx["body"][1][2]["display_value"] == "NA" - ctx = df.style.format(None, na_rep="-")._translate() + ctx = df.style.format(None, na_rep="-")._translate(True, True) assert ctx["body"][0][1]["display_value"] == "-" assert ctx["body"][0][2]["display_value"] == "-" assert ctx["body"][1][1]["display_value"] == "-" @@ -150,19 +150,19 @@ def test_format_with_precision(): df = DataFrame(data=[[1.0, 2.0090], [3.2121, 4.566]], columns=["a", "b"]) s = Styler(df) - ctx = s.format(precision=1)._translate() + ctx = s.format(precision=1)._translate(True, True) assert ctx["body"][0][1]["display_value"] == "1.0" assert ctx["body"][0][2]["display_value"] == "2.0" assert ctx["body"][1][1]["display_value"] == "3.2" assert ctx["body"][1][2]["display_value"] == "4.6" - ctx = s.format(precision=2)._translate() + ctx = s.format(precision=2)._translate(True, True) assert ctx["body"][0][1]["display_value"] == "1.00" assert ctx["body"][0][2]["display_value"] == "2.01" assert ctx["body"][1][1]["display_value"] == "3.21" assert ctx["body"][1][2]["display_value"] == "4.57" - ctx = s.format(precision=3)._translate() + ctx = s.format(precision=3)._translate(True, True) assert ctx["body"][0][1]["display_value"] == "1.000" assert ctx["body"][0][2]["display_value"] == "2.009" assert ctx["body"][1][1]["display_value"] == "3.212" @@ -173,26 +173,28 @@ def test_format_subset(): df = DataFrame([[0.1234, 0.1234], [1.1234, 1.1234]], columns=["a", "b"]) ctx = df.style.format( {"a": "{:0.1f}", "b": "{0:.2%}"}, subset=IndexSlice[0, :] - )._translate() + )._translate(True, True) expected = "0.1" raw_11 = "1.123400" assert ctx["body"][0][1]["display_value"] == expected assert ctx["body"][1][1]["display_value"] == raw_11 assert ctx["body"][0][2]["display_value"] == "12.34%" - ctx = df.style.format("{:0.1f}", subset=IndexSlice[0, :])._translate() + ctx = df.style.format("{:0.1f}", subset=IndexSlice[0, :])._translate(True, True) assert ctx["body"][0][1]["display_value"] == expected assert ctx["body"][1][1]["display_value"] == raw_11 - ctx = df.style.format("{:0.1f}", subset=IndexSlice["a"])._translate() + ctx = df.style.format("{:0.1f}", subset=IndexSlice["a"])._translate(True, True) assert ctx["body"][0][1]["display_value"] == expected assert ctx["body"][0][2]["display_value"] == "0.123400" - ctx = df.style.format("{:0.1f}", subset=IndexSlice[0, "a"])._translate() + ctx = df.style.format("{:0.1f}", subset=IndexSlice[0, "a"])._translate(True, True) assert ctx["body"][0][1]["display_value"] == expected assert ctx["body"][1][1]["display_value"] == raw_11 - ctx = df.style.format("{:0.1f}", subset=IndexSlice[[0, 1], ["a"]])._translate() + ctx = df.style.format("{:0.1f}", subset=IndexSlice[[0, 1], ["a"]])._translate( + True, True + ) assert ctx["body"][0][1]["display_value"] == expected assert ctx["body"][1][1]["display_value"] == "1.1" assert ctx["body"][0][2]["display_value"] == "0.123400" @@ -206,19 +208,19 @@ def test_format_thousands(formatter, decimal, precision): s = DataFrame([[1000000.123456789]]).style # test float result = s.format( thousands="_", formatter=formatter, decimal=decimal, precision=precision - )._translate() + )._translate(True, True) assert "1_000_000" in result["body"][0][1]["display_value"] s = DataFrame([[1000000]]).style # test int result = s.format( thousands="_", formatter=formatter, decimal=decimal, precision=precision - )._translate() + )._translate(True, True) assert "1_000_000" in result["body"][0][1]["display_value"] s = DataFrame([[1 + 1000000.123456789j]]).style # test complex result = s.format( thousands="_", formatter=formatter, decimal=decimal, precision=precision - )._translate() + )._translate(True, True) assert "1_000_000" in result["body"][0][1]["display_value"] @@ -229,11 +231,11 @@ def test_format_decimal(formatter, thousands, precision): s = DataFrame([[1000000.123456789]]).style # test float result = s.format( decimal="_", formatter=formatter, thousands=thousands, precision=precision - )._translate() + )._translate(True, True) assert "000_123" in result["body"][0][1]["display_value"] s = DataFrame([[1 + 1000000.123456789j]]).style # test complex result = s.format( decimal="_", formatter=formatter, thousands=thousands, precision=precision - )._translate() + )._translate(True, True) assert "000_123" in result["body"][0][1]["display_value"] diff --git a/pandas/tests/io/formats/style/test_non_unique.py b/pandas/tests/io/formats/style/test_non_unique.py index 2dc7433009368..9cbc2db87fbde 100644 --- a/pandas/tests/io/formats/style/test_non_unique.py +++ b/pandas/tests/io/formats/style/test_non_unique.py @@ -108,7 +108,7 @@ def test_set_td_classes_non_unique_raises(styler): def test_hide_columns_non_unique(styler): - ctx = styler.hide_columns(["d"])._translate() + ctx = styler.hide_columns(["d"])._translate(True, True) assert ctx["head"][0][1]["display_value"] == "c" assert ctx["head"][0][1]["is_visible"] is True diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py index 31877b3f33482..c556081b5f562 100644 --- a/pandas/tests/io/formats/style/test_style.py +++ b/pandas/tests/io/formats/style/test_style.py @@ -6,7 +6,10 @@ import pytest import pandas as pd -from pandas import DataFrame +from pandas import ( + DataFrame, + MultiIndex, +) import pandas._testing as tm jinja2 = pytest.importorskip("jinja2") @@ -20,6 +23,99 @@ ) +@pytest.fixture +def mi_df(): + return DataFrame( + [[1, 2], [3, 4]], + index=MultiIndex.from_product([["i0"], ["i1_a", "i1_b"]]), + columns=MultiIndex.from_product([["c0"], ["c1_a", "c1_b"]]), + dtype=int, + ) + + +@pytest.fixture +def mi_styler(mi_df): + return Styler(mi_df, uuid_len=0) + + +@pytest.mark.parametrize( + "sparse_columns, exp_cols", + [ + ( + True, + [ + {"is_visible": True, "attributes": 'colspan="2"', "value": "c0"}, + {"is_visible": False, "attributes": "", "value": "c0"}, + ], + ), + ( + False, + [ + {"is_visible": True, "attributes": "", "value": "c0"}, + {"is_visible": True, "attributes": "", "value": "c0"}, + ], + ), + ], +) +def test_mi_styler_sparsify_columns(mi_styler, sparse_columns, exp_cols): + exp_l1_c0 = {"is_visible": True, "attributes": "", "display_value": "c1_a"} + exp_l1_c1 = {"is_visible": True, "attributes": "", "display_value": "c1_b"} + + ctx = mi_styler._translate(True, sparse_columns) + + assert exp_cols[0].items() <= ctx["head"][0][2].items() + assert exp_cols[1].items() <= ctx["head"][0][3].items() + assert exp_l1_c0.items() <= ctx["head"][1][2].items() + assert exp_l1_c1.items() <= ctx["head"][1][3].items() + + +@pytest.mark.parametrize( + "sparse_index, exp_rows", + [ + ( + True, + [ + {"is_visible": True, "attributes": 'rowspan="2"', "value": "i0"}, + {"is_visible": False, "attributes": "", "value": "i0"}, + ], + ), + ( + False, + [ + {"is_visible": True, "attributes": "", "value": "i0"}, + {"is_visible": True, "attributes": "", "value": "i0"}, + ], + ), + ], +) +def test_mi_styler_sparsify_index(mi_styler, sparse_index, exp_rows): + exp_l1_r0 = {"is_visible": True, "attributes": "", "display_value": "i1_a"} + exp_l1_r1 = {"is_visible": True, "attributes": "", "display_value": "i1_b"} + + ctx = mi_styler._translate(sparse_index, True) + + assert exp_rows[0].items() <= ctx["body"][0][0].items() + assert exp_rows[1].items() <= ctx["body"][1][0].items() + assert exp_l1_r0.items() <= ctx["body"][0][1].items() + assert exp_l1_r1.items() <= ctx["body"][1][1].items() + + +def test_mi_styler_sparsify_options(mi_styler): + with pd.option_context("styler.sparse.index", False): + html1 = mi_styler.render() + with pd.option_context("styler.sparse.index", True): + html2 = mi_styler.render() + + assert html1 != html2 + + with pd.option_context("styler.sparse.columns", False): + html1 = mi_styler.render() + with pd.option_context("styler.sparse.columns", True): + html2 = mi_styler.render() + + assert html1 != html2 + + class TestStyler: def setup_method(self, method): np.random.seed(24) @@ -256,7 +352,7 @@ def test_set_properties_subset(self): def test_empty_index_name_doesnt_display(self): # https://github.com/pandas-dev/pandas/pull/12090#issuecomment-180695902 df = DataFrame({"A": [1, 2], "B": [3, 4], "C": [5, 6]}) - result = df.style._translate() + result = df.style._translate(True, True) expected = [ [ @@ -300,7 +396,7 @@ def test_index_name(self): # https://github.com/pandas-dev/pandas/issues/11655 # TODO: this test can be minimised to address the test more directly df = DataFrame({"A": [1, 2], "B": [3, 4], "C": [5, 6]}) - result = df.set_index("A").style._translate() + result = df.set_index("A").style._translate(True, True) expected = [ [ @@ -359,7 +455,7 @@ def test_multiindex_name(self): # https://github.com/pandas-dev/pandas/issues/11655 # TODO: this test can be minimised to address the test more directly df = DataFrame({"A": [1, 2], "B": [3, 4], "C": [5, 6]}) - result = df.set_index(["A", "B"]).style._translate() + result = df.set_index(["A", "B"]).style._translate(True, True) expected = [ [ @@ -417,7 +513,7 @@ def test_numeric_columns(self): # https://github.com/pandas-dev/pandas/issues/12125 # smoke test for _translate df = DataFrame({0: [1, 2, 3]}) - df.style._translate() + df.style._translate(True, True) def test_apply_axis(self): df = DataFrame({"A": [0, 0], "B": [1, 1]}) @@ -510,8 +606,8 @@ def test_applymap_subset(self, slice_): def test_applymap_subset_multiindex(self, slice_): # GH 19861 # edited for GH 33562 - idx = pd.MultiIndex.from_product([["a", "b"], [1, 2]]) - col = pd.MultiIndex.from_product([["x", "y"], ["A", "B"]]) + idx = MultiIndex.from_product([["a", "b"], [1, 2]]) + col = MultiIndex.from_product([["x", "y"], ["A", "B"]]) df = DataFrame(np.random.rand(4, 4), columns=col, index=idx) df.style.applymap(lambda x: "color: red;", subset=slice_).render() @@ -519,7 +615,7 @@ def test_applymap_subset_multiindex_code(self): # https://github.com/pandas-dev/pandas/issues/25858 # Checks styler.applymap works with multindex when codes are provided codes = np.array([[0, 0, 1, 1], [0, 1, 0, 1]]) - columns = pd.MultiIndex( + columns = MultiIndex( levels=[["a", "b"], ["%", "#"]], codes=codes, names=["", ""] ) df = DataFrame( @@ -627,7 +723,7 @@ def test_empty(self): s = df.style s.ctx = {(0, 0): [("color", "red")], (1, 0): [("", "")]} - result = s._translate()["cellstyle"] + result = s._translate(True, True)["cellstyle"] expected = [ {"props": [("color", "red")], "selectors": ["row0_col0"]}, {"props": [("", "")], "selectors": ["row1_col0"]}, @@ -639,7 +735,7 @@ def test_duplicate(self): s = df.style s.ctx = {(0, 0): [("color", "red")], (1, 0): [("color", "red")]} - result = s._translate()["cellstyle"] + result = s._translate(True, True)["cellstyle"] expected = [ {"props": [("color", "red")], "selectors": ["row0_col0", "row1_col0"]} ] @@ -649,7 +745,7 @@ def test_init_with_na_rep(self): # GH 21527 28358 df = DataFrame([[None, None], [1.1, 1.2]], columns=["A", "B"]) - ctx = Styler(df, na_rep="NA")._translate() + ctx = Styler(df, na_rep="NA")._translate(True, True) assert ctx["body"][0][1]["display_value"] == "NA" assert ctx["body"][0][2]["display_value"] == "NA" @@ -658,7 +754,7 @@ def test_set_na_rep(self): df = DataFrame([[None, None], [1.1, 1.2]], columns=["A", "B"]) with tm.assert_produces_warning(FutureWarning): - ctx = df.style.set_na_rep("NA")._translate() + ctx = df.style.set_na_rep("NA")._translate(True, True) assert ctx["body"][0][1]["display_value"] == "NA" assert ctx["body"][0][2]["display_value"] == "NA" @@ -666,7 +762,7 @@ def test_set_na_rep(self): ctx = ( df.style.set_na_rep("NA") .format(None, na_rep="-", subset=["B"]) - ._translate() + ._translate(True, True) ) assert ctx["body"][0][1]["display_value"] == "NA" assert ctx["body"][0][2]["display_value"] == "-" @@ -722,7 +818,7 @@ def test_table_styles_multiple(self): {"selector": "th,td", "props": "color:red;"}, {"selector": "tr", "props": "color:green;"}, ] - )._translate()["table_styles"] + )._translate(True, True)["table_styles"] assert ctx == [ {"selector": "th", "props": [("color", "red")]}, {"selector": "td", "props": [("color", "red")]}, @@ -833,7 +929,7 @@ def f(x): df.style._apply(f, axis=None) def test_get_level_lengths(self): - index = pd.MultiIndex.from_product([["a", "b"], [0, 1, 2]]) + index = MultiIndex.from_product([["a", "b"], [0, 1, 2]]) expected = { (0, 0): 3, (0, 3): 3, @@ -865,7 +961,7 @@ def test_get_level_lengths(self): tm.assert_dict_equal(result, expected) def test_get_level_lengths_un_sorted(self): - index = pd.MultiIndex.from_arrays([[1, 1, 2, 1], ["a", "b", "b", "d"]]) + index = MultiIndex.from_arrays([[1, 1, 2, 1], ["a", "b", "b", "d"]]) expected = { (0, 0): 2, (0, 2): 1, @@ -891,97 +987,15 @@ def test_get_level_lengths_un_sorted(self): result = _get_level_lengths(index, sparsify=False) tm.assert_dict_equal(result, expected) - def test_mi_sparse(self): - df = DataFrame( - {"A": [1, 2]}, index=pd.MultiIndex.from_arrays([["a", "a"], [0, 1]]) - ) - - result = df.style._translate() - body_0 = result["body"][0][0] - expected_0 = { - "value": "a", - "display_value": "a", - "is_visible": True, - "type": "th", - "attributes": 'rowspan="2"', - "class": "row_heading level0 row0", - "id": "level0_row0", - } - assert body_0 == expected_0 - - body_1 = result["body"][0][1] - expected_1 = { - "value": 0, - "display_value": 0, - "is_visible": True, - "type": "th", - "class": "row_heading level1 row0", - "id": "level1_row0", - "attributes": "", - } - assert body_1 == expected_1 - - body_10 = result["body"][1][0] - expected_10 = { - "value": "a", - "display_value": "a", - "is_visible": False, - "type": "th", - "class": "row_heading level0 row1", - "id": "level0_row1", - "attributes": "", - } - assert body_10 == expected_10 - - head = result["head"][0] - expected = [ - { - "type": "th", - "class": "blank", - "value": self.blank_value, - "is_visible": True, - "display_value": self.blank_value, - }, - { - "type": "th", - "class": "blank level0", - "value": self.blank_value, - "is_visible": True, - "display_value": self.blank_value, - }, - { - "type": "th", - "class": "col_heading level0 col0", - "value": "A", - "is_visible": True, - "display_value": "A", - "attributes": "", - }, - ] - assert head == expected - - def test_mi_sparse_disabled(self): - df = DataFrame( - {"A": [1, 2]}, index=pd.MultiIndex.from_arrays([["a", "a"], [0, 1]]) - ) - result = df.style._translate()["body"] - assert 'rowspan="2"' in result[0][0]["attributes"] - assert result[1][0]["is_visible"] is False - - with pd.option_context("display.multi_sparse", False): - result = df.style._translate()["body"] - assert 'rowspan="2"' not in result[0][0]["attributes"] - assert result[1][0]["is_visible"] is True - def test_mi_sparse_index_names(self): # TODO this test is verbose can be minimised to more directly target test df = DataFrame( {"A": [1, 2]}, - index=pd.MultiIndex.from_arrays( + index=MultiIndex.from_arrays( [["a", "a"], [0, 1]], names=["idx_level_0", "idx_level_1"] ), ) - result = df.style._translate() + result = df.style._translate(True, True) head = result["head"][1] expected = [ { @@ -1013,15 +1027,15 @@ def test_mi_sparse_column_names(self): # TODO this test is verbose - could be minimised df = DataFrame( np.arange(16).reshape(4, 4), - index=pd.MultiIndex.from_arrays( + index=MultiIndex.from_arrays( [["a", "a", "b", "a"], [0, 1, 1, 2]], names=["idx_level_0", "idx_level_1"], ), - columns=pd.MultiIndex.from_arrays( + columns=MultiIndex.from_arrays( [["C1", "C1", "C2", "C2"], [1, 0, 1, 0]], names=["col_0", "col_1"] ), ) - result = df.style._translate() + result = df.style._translate(True, True) head = result["head"][1] expected = [ { @@ -1076,20 +1090,20 @@ def test_mi_sparse_column_names(self): def test_hide_single_index(self): # GH 14194 # single unnamed index - ctx = self.df.style._translate() + ctx = self.df.style._translate(True, True) assert ctx["body"][0][0]["is_visible"] assert ctx["head"][0][0]["is_visible"] - ctx2 = self.df.style.hide_index()._translate() + ctx2 = self.df.style.hide_index()._translate(True, True) assert not ctx2["body"][0][0]["is_visible"] assert not ctx2["head"][0][0]["is_visible"] # single named index - ctx3 = self.df.set_index("A").style._translate() + ctx3 = self.df.set_index("A").style._translate(True, True) assert ctx3["body"][0][0]["is_visible"] assert len(ctx3["head"]) == 2 # 2 header levels assert ctx3["head"][0][0]["is_visible"] - ctx4 = self.df.set_index("A").style.hide_index()._translate() + ctx4 = self.df.set_index("A").style.hide_index()._translate(True, True) assert not ctx4["body"][0][0]["is_visible"] assert len(ctx4["head"]) == 1 # only 1 header levels assert not ctx4["head"][0][0]["is_visible"] @@ -1098,11 +1112,11 @@ def test_hide_multiindex(self): # GH 14194 df = DataFrame( {"A": [1, 2]}, - index=pd.MultiIndex.from_arrays( + index=MultiIndex.from_arrays( [["a", "a"], [0, 1]], names=["idx_level_0", "idx_level_1"] ), ) - ctx1 = df.style._translate() + ctx1 = df.style._translate(True, True) # tests for 'a' and '0' assert ctx1["body"][0][0]["is_visible"] assert ctx1["body"][0][1]["is_visible"] @@ -1110,7 +1124,7 @@ def test_hide_multiindex(self): assert ctx1["head"][0][0]["is_visible"] assert ctx1["head"][0][1]["is_visible"] - ctx2 = df.style.hide_index()._translate() + ctx2 = df.style.hide_index()._translate(True, True) # tests for 'a' and '0' assert not ctx2["body"][0][0]["is_visible"] assert not ctx2["body"][0][1]["is_visible"] @@ -1121,7 +1135,7 @@ def test_hide_multiindex(self): def test_hide_columns_single_level(self): # GH 14194 # test hiding single column - ctx = self.df.style._translate() + ctx = self.df.style._translate(True, True) assert ctx["head"][0][1]["is_visible"] assert ctx["head"][0][1]["display_value"] == "A" assert ctx["head"][0][2]["is_visible"] @@ -1129,13 +1143,13 @@ def test_hide_columns_single_level(self): assert ctx["body"][0][1]["is_visible"] # col A, row 1 assert ctx["body"][1][2]["is_visible"] # col B, row 1 - ctx = self.df.style.hide_columns("A")._translate() + ctx = self.df.style.hide_columns("A")._translate(True, True) assert not ctx["head"][0][1]["is_visible"] assert not ctx["body"][0][1]["is_visible"] # col A, row 1 assert ctx["body"][1][2]["is_visible"] # col B, row 1 # test hiding mulitiple columns - ctx = self.df.style.hide_columns(["A", "B"])._translate() + ctx = self.df.style.hide_columns(["A", "B"])._translate(True, True) assert not ctx["head"][0][1]["is_visible"] assert not ctx["head"][0][2]["is_visible"] assert not ctx["body"][0][1]["is_visible"] # col A, row 1 @@ -1144,14 +1158,14 @@ def test_hide_columns_single_level(self): def test_hide_columns_mult_levels(self): # GH 14194 # setup dataframe with multiple column levels and indices - i1 = pd.MultiIndex.from_arrays( + i1 = MultiIndex.from_arrays( [["a", "a"], [0, 1]], names=["idx_level_0", "idx_level_1"] ) - i2 = pd.MultiIndex.from_arrays( + i2 = MultiIndex.from_arrays( [["b", "b"], [0, 1]], names=["col_level_0", "col_level_1"] ) df = DataFrame([[1, 2], [3, 4]], index=i1, columns=i2) - ctx = df.style._translate() + ctx = df.style._translate(True, True) # column headers assert ctx["head"][0][2]["is_visible"] assert ctx["head"][1][2]["is_visible"] @@ -1165,14 +1179,14 @@ def test_hide_columns_mult_levels(self): assert ctx["body"][1][3]["display_value"] == 4 # hide top column level, which hides both columns - ctx = df.style.hide_columns("b")._translate() + ctx = df.style.hide_columns("b")._translate(True, True) assert not ctx["head"][0][2]["is_visible"] # b assert not ctx["head"][1][2]["is_visible"] # 0 assert not ctx["body"][1][2]["is_visible"] # 3 assert ctx["body"][0][0]["is_visible"] # index # hide first column only - ctx = df.style.hide_columns([("b", 0)])._translate() + ctx = df.style.hide_columns([("b", 0)])._translate(True, True) assert ctx["head"][0][2]["is_visible"] # b assert not ctx["head"][1][2]["is_visible"] # 0 assert not ctx["body"][1][2]["is_visible"] # 3 @@ -1180,7 +1194,7 @@ def test_hide_columns_mult_levels(self): assert ctx["body"][1][3]["display_value"] == 4 # hide second column and index - ctx = df.style.hide_columns([("b", 1)]).hide_index()._translate() + ctx = df.style.hide_columns([("b", 1)]).hide_index()._translate(True, True) assert not ctx["body"][0][0]["is_visible"] # index assert ctx["head"][0][2]["is_visible"] # b assert ctx["head"][1][2]["is_visible"] # 0 @@ -1436,8 +1450,8 @@ def test_non_reducing_slice_on_multiindex(self): ) def test_non_reducing_multi_slice_on_multiindex(self, slice_): # GH 33562 - cols = pd.MultiIndex.from_product([["a", "b"], ["c", "d"], ["e", "f"]]) - idxs = pd.MultiIndex.from_product([["U", "V"], ["W", "X"], ["Y", "Z"]]) + cols = MultiIndex.from_product([["a", "b"], ["c", "d"], ["e", "f"]]) + idxs = MultiIndex.from_product([["U", "V"], ["W", "X"], ["Y", "Z"]]) df = DataFrame(np.arange(64).reshape(8, 8), columns=cols, index=idxs) expected = df.loc[slice_]