import copy import re import textwrap import numpy as np import pytest import pandas.util._test_decorators as td import pandas as pd from pandas import DataFrame import pandas._testing as tm jinja2 = pytest.importorskip("jinja2") from pandas.io.formats.style import Styler, _get_level_lengths # isort:skip class TestStyler: def setup_method(self, method): np.random.seed(24) self.s = DataFrame({"A": np.random.permutation(range(6))}) self.df = DataFrame({"A": [0, 1], "B": np.random.randn(2)}) self.f = lambda x: x self.g = lambda x: x def h(x, foo="bar"): return pd.Series(f"color: {foo}", index=x.index, name=x.name) self.h = h self.styler = Styler(self.df) self.attrs = DataFrame({"A": ["color: red", "color: blue"]}) self.dataframes = [ self.df, DataFrame( {"f": [1.0, 2.0], "o": ["a", "b"], "c": pd.Categorical(["a", "b"])} ), ] def test_init_non_pandas(self): msg = "``data`` must be a Series or DataFrame" with pytest.raises(TypeError, match=msg): Styler([1, 2, 3]) def test_init_series(self): result = Styler(pd.Series([1, 2])) assert result.data.ndim == 2 def test_repr_html_ok(self): self.styler._repr_html_() def test_repr_html_mathjax(self): # gh-19824 assert "tex2jax_ignore" not in self.styler._repr_html_() with pd.option_context("display.html.use_mathjax", False): assert "tex2jax_ignore" in self.styler._repr_html_() def test_update_ctx(self): self.styler._update_ctx(self.attrs) expected = {(0, 0): ["color: red"], (1, 0): ["color: blue"]} assert self.styler.ctx == expected def test_update_ctx_flatten_multi(self): attrs = DataFrame({"A": ["color: red; foo: bar", "color: blue; foo: baz"]}) self.styler._update_ctx(attrs) expected = { (0, 0): ["color: red", " foo: bar"], (1, 0): ["color: blue", " foo: baz"], } assert self.styler.ctx == expected def test_update_ctx_flatten_multi_traliing_semi(self): attrs = DataFrame({"A": ["color: red; foo: bar;", "color: blue; foo: baz;"]}) self.styler._update_ctx(attrs) expected = { (0, 0): ["color: red", " foo: bar"], (1, 0): ["color: blue", " foo: baz"], } assert self.styler.ctx == expected def test_copy(self): s2 = copy.copy(self.styler) assert self.styler is not s2 assert self.styler.ctx is s2.ctx # shallow assert self.styler._todo is s2._todo self.styler._update_ctx(self.attrs) self.styler.highlight_max() assert self.styler.ctx == s2.ctx assert self.styler._todo == s2._todo def test_deepcopy(self): s2 = copy.deepcopy(self.styler) assert self.styler is not s2 assert self.styler.ctx is not s2.ctx assert self.styler._todo is not s2._todo self.styler._update_ctx(self.attrs) self.styler.highlight_max() assert self.styler.ctx != s2.ctx assert s2._todo == [] assert self.styler._todo != s2._todo def test_clear(self): s = self.df.style.highlight_max()._compute() assert len(s.ctx) > 0 assert len(s._todo) > 0 s.clear() assert len(s.ctx) == 0 assert len(s._todo) == 0 def test_render(self): df = DataFrame({"A": [0, 1]}) style = lambda x: pd.Series(["color: red", "color: blue"], name=x.name) s = Styler(df, uuid="AB").apply(style) s.render() # it worked? def test_render_empty_dfs(self): empty_df = DataFrame() es = Styler(empty_df) es.render() # An index but no columns DataFrame(columns=["a"]).style.render() # A column but no index DataFrame(index=["a"]).style.render() # No IndexError raised? def test_render_double(self): df = DataFrame({"A": [0, 1]}) style = lambda x: pd.Series( ["color: red; border: 1px", "color: blue; border: 2px"], name=x.name ) s = Styler(df, uuid="AB").apply(style) s.render() # it worked? def test_set_properties(self): df = DataFrame({"A": [0, 1]}) result = df.style.set_properties(color="white", size="10px")._compute().ctx # order is deterministic v = ["color: white", "size: 10px"] expected = {(0, 0): v, (1, 0): v} assert result.keys() == expected.keys() for v1, v2 in zip(result.values(), expected.values()): assert sorted(v1) == sorted(v2) def test_set_properties_subset(self): df = DataFrame({"A": [0, 1]}) result = ( df.style.set_properties(subset=pd.IndexSlice[0, "A"], color="white") ._compute() .ctx ) expected = {(0, 0): ["color: white"]} assert result == expected 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() expected = [ [ { "class": "blank level0", "type": "th", "value": "", "is_visible": True, "display_value": "", }, { "class": "col_heading level0 col0", "display_value": "A", "type": "th", "value": "A", "is_visible": True, }, { "class": "col_heading level0 col1", "display_value": "B", "type": "th", "value": "B", "is_visible": True, }, { "class": "col_heading level0 col2", "display_value": "C", "type": "th", "value": "C", "is_visible": True, }, ] ] assert result["head"] == expected def test_index_name(self): # https://github.com/pandas-dev/pandas/issues/11655 df = DataFrame({"A": [1, 2], "B": [3, 4], "C": [5, 6]}) result = df.set_index("A").style._translate() expected = [ [ { "class": "blank level0", "type": "th", "value": "", "display_value": "", "is_visible": True, }, { "class": "col_heading level0 col0", "type": "th", "value": "B", "display_value": "B", "is_visible": True, }, { "class": "col_heading level0 col1", "type": "th", "value": "C", "display_value": "C", "is_visible": True, }, ], [ {"class": "index_name level0", "type": "th", "value": "A"}, {"class": "blank", "type": "th", "value": ""}, {"class": "blank", "type": "th", "value": ""}, ], ] assert result["head"] == expected def test_multiindex_name(self): # https://github.com/pandas-dev/pandas/issues/11655 df = DataFrame({"A": [1, 2], "B": [3, 4], "C": [5, 6]}) result = df.set_index(["A", "B"]).style._translate() expected = [ [ { "class": "blank", "type": "th", "value": "", "display_value": "", "is_visible": True, }, { "class": "blank level0", "type": "th", "value": "", "display_value": "", "is_visible": True, }, { "class": "col_heading level0 col0", "type": "th", "value": "C", "display_value": "C", "is_visible": True, }, ], [ {"class": "index_name level0", "type": "th", "value": "A"}, {"class": "index_name level1", "type": "th", "value": "B"}, {"class": "blank", "type": "th", "value": ""}, ], ] assert result["head"] == expected 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() def test_apply_axis(self): df = DataFrame({"A": [0, 0], "B": [1, 1]}) f = lambda x: [f"val: {x.max()}" for v in x] result = df.style.apply(f, axis=1) assert len(result._todo) == 1 assert len(result.ctx) == 0 result._compute() expected = { (0, 0): ["val: 1"], (0, 1): ["val: 1"], (1, 0): ["val: 1"], (1, 1): ["val: 1"], } assert result.ctx == expected result = df.style.apply(f, axis=0) expected = { (0, 0): ["val: 0"], (0, 1): ["val: 1"], (1, 0): ["val: 0"], (1, 1): ["val: 1"], } result._compute() assert result.ctx == expected result = df.style.apply(f) # default result._compute() assert result.ctx == expected def test_apply_subset(self): axes = [0, 1] slices = [ pd.IndexSlice[:], pd.IndexSlice[:, ["A"]], pd.IndexSlice[[1], :], pd.IndexSlice[[1], ["A"]], pd.IndexSlice[:2, ["A", "B"]], ] for ax in axes: for slice_ in slices: result = ( self.df.style.apply(self.h, axis=ax, subset=slice_, foo="baz") ._compute() .ctx ) expected = { (r, c): ["color: baz"] for r, row in enumerate(self.df.index) for c, col in enumerate(self.df.columns) if row in self.df.loc[slice_].index and col in self.df.loc[slice_].columns } assert result == expected def test_applymap_subset(self): def f(x): return "foo: bar" slices = [ pd.IndexSlice[:], pd.IndexSlice[:, ["A"]], pd.IndexSlice[[1], :], pd.IndexSlice[[1], ["A"]], pd.IndexSlice[:2, ["A", "B"]], ] for slice_ in slices: result = self.df.style.applymap(f, subset=slice_)._compute().ctx expected = { (r, c): ["foo: bar"] for r, row in enumerate(self.df.index) for c, col in enumerate(self.df.columns) if row in self.df.loc[slice_].index and col in self.df.loc[slice_].columns } assert result == expected def test_applymap_subset_multiindex(self): # GH 19861 # Smoke test for applymap def color_negative_red(val): """ Takes a scalar and returns a string with the css property `'color: red'` for negative strings, black otherwise. """ color = "red" if val < 0 else "black" return f"color: {color}" dic = { ("a", "d"): [-1.12, 2.11], ("a", "c"): [2.78, -2.88], ("b", "c"): [-3.99, 3.77], ("b", "d"): [4.21, -1.22], } idx = pd.IndexSlice df = DataFrame(dic, index=[0, 1]) (df.style.applymap(color_negative_red, subset=idx[:, idx["b", "d"]]).render()) 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( levels=[["a", "b"], ["%", "#"]], codes=codes, names=["", ""] ) df = DataFrame( [[1, -1, 1, 1], [-1, 1, 1, 1]], index=["hello", "world"], columns=columns ) pct_subset = pd.IndexSlice[:, pd.IndexSlice[:, "%":"%"]] def color_negative_red(val): color = "red" if val < 0 else "black" return f"color: {color}" df.loc[pct_subset] df.style.applymap(color_negative_red, subset=pct_subset) def test_where_with_one_style(self): # GH 17474 def f(x): return x > 0.5 style1 = "foo: bar" result = self.df.style.where(f, style1)._compute().ctx expected = { (r, c): [style1] for r, row in enumerate(self.df.index) for c, col in enumerate(self.df.columns) if f(self.df.loc[row, col]) } assert result == expected def test_where_subset(self): # GH 17474 def f(x): return x > 0.5 style1 = "foo: bar" style2 = "baz: foo" slices = [ pd.IndexSlice[:], pd.IndexSlice[:, ["A"]], pd.IndexSlice[[1], :], pd.IndexSlice[[1], ["A"]], pd.IndexSlice[:2, ["A", "B"]], ] for slice_ in slices: result = ( self.df.style.where(f, style1, style2, subset=slice_)._compute().ctx ) expected = { (r, c): [style1 if f(self.df.loc[row, col]) else style2] for r, row in enumerate(self.df.index) for c, col in enumerate(self.df.columns) if row in self.df.loc[slice_].index and col in self.df.loc[slice_].columns } assert result == expected def test_where_subset_compare_with_applymap(self): # GH 17474 def f(x): return x > 0.5 style1 = "foo: bar" style2 = "baz: foo" def g(x): return style1 if f(x) else style2 slices = [ pd.IndexSlice[:], pd.IndexSlice[:, ["A"]], pd.IndexSlice[[1], :], pd.IndexSlice[[1], ["A"]], pd.IndexSlice[:2, ["A", "B"]], ] for slice_ in slices: result = ( self.df.style.where(f, style1, style2, subset=slice_)._compute().ctx ) expected = self.df.style.applymap(g, subset=slice_)._compute().ctx assert result == expected def test_empty(self): df = DataFrame({"A": [1, 0]}) s = df.style s.ctx = {(0, 0): ["color: red"], (1, 0): [""]} result = s._translate()["cellstyle"] expected = [ {"props": [("color", " red")], "selectors": ["row0_col0"]}, {"props": [("", "")], "selectors": ["row1_col0"]}, ] assert result == expected def test_duplicate(self): df = DataFrame({"A": [1, 0]}) s = df.style s.ctx = {(0, 0): ["color: red"], (1, 0): ["color: red"]} result = s._translate()["cellstyle"] expected = [ {"props": [("color", " red")], "selectors": ["row0_col0", "row1_col0"]} ] assert result == expected def test_bar_align_left(self): df = DataFrame({"A": [0, 1, 2]}) result = df.style.bar()._compute().ctx expected = { (0, 0): ["width: 10em", " height: 80%"], (1, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(" "90deg,#d65f5f 50.0%, transparent 50.0%)", ], (2, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(" "90deg,#d65f5f 100.0%, transparent 100.0%)", ], } assert result == expected result = df.style.bar(color="red", width=50)._compute().ctx expected = { (0, 0): ["width: 10em", " height: 80%"], (1, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg,red 25.0%, transparent 25.0%)", ], (2, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg,red 50.0%, transparent 50.0%)", ], } assert result == expected df["C"] = ["a"] * len(df) result = df.style.bar(color="red", width=50)._compute().ctx assert result == expected df["C"] = df["C"].astype("category") result = df.style.bar(color="red", width=50)._compute().ctx assert result == expected def test_bar_align_left_0points(self): df = DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) result = df.style.bar()._compute().ctx expected = { (0, 0): ["width: 10em", " height: 80%"], (0, 1): ["width: 10em", " height: 80%"], (0, 2): ["width: 10em", " height: 80%"], (1, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg,#d65f5f 50.0%, transparent 50.0%)", ], (1, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg,#d65f5f 50.0%, transparent 50.0%)", ], (1, 2): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg,#d65f5f 50.0%, transparent 50.0%)", ], (2, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg,#d65f5f 100.0%" ", transparent 100.0%)", ], (2, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg,#d65f5f 100.0%" ", transparent 100.0%)", ], (2, 2): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg,#d65f5f 100.0%" ", transparent 100.0%)", ], } assert result == expected result = df.style.bar(axis=1)._compute().ctx expected = { (0, 0): ["width: 10em", " height: 80%"], (0, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg,#d65f5f 50.0%, transparent 50.0%)", ], (0, 2): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg,#d65f5f 100.0%" ", transparent 100.0%)", ], (1, 0): ["width: 10em", " height: 80%"], (1, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg,#d65f5f 50.0%" ", transparent 50.0%)", ], (1, 2): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg,#d65f5f 100.0%" ", transparent 100.0%)", ], (2, 0): ["width: 10em", " height: 80%"], (2, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg,#d65f5f 50.0%" ", transparent 50.0%)", ], (2, 2): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg,#d65f5f 100.0%" ", transparent 100.0%)", ], } assert result == expected def test_bar_align_mid_pos_and_neg(self): df = DataFrame({"A": [-10, 0, 20, 90]}) result = df.style.bar(align="mid", color=["#d65f5f", "#5fba7d"])._compute().ctx expected = { (0, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg," "#d65f5f 10.0%, transparent 10.0%)", ], (1, 0): ["width: 10em", " height: 80%"], (2, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 10.0%, #5fba7d 10.0%" ", #5fba7d 30.0%, transparent 30.0%)", ], (3, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 10.0%, " "#5fba7d 10.0%, #5fba7d 100.0%, " "transparent 100.0%)", ], } assert result == expected def test_bar_align_mid_all_pos(self): df = DataFrame({"A": [10, 20, 50, 100]}) result = df.style.bar(align="mid", color=["#d65f5f", "#5fba7d"])._compute().ctx expected = { (0, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg," "#5fba7d 10.0%, transparent 10.0%)", ], (1, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg," "#5fba7d 20.0%, transparent 20.0%)", ], (2, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg," "#5fba7d 50.0%, transparent 50.0%)", ], (3, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg," "#5fba7d 100.0%, transparent 100.0%)", ], } assert result == expected def test_bar_align_mid_all_neg(self): df = DataFrame({"A": [-100, -60, -30, -20]}) result = df.style.bar(align="mid", color=["#d65f5f", "#5fba7d"])._compute().ctx expected = { (0, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg," "#d65f5f 100.0%, transparent 100.0%)", ], (1, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 40.0%, " "#d65f5f 40.0%, #d65f5f 100.0%, " "transparent 100.0%)", ], (2, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 70.0%, " "#d65f5f 70.0%, #d65f5f 100.0%, " "transparent 100.0%)", ], (3, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 80.0%, " "#d65f5f 80.0%, #d65f5f 100.0%, " "transparent 100.0%)", ], } assert result == expected def test_bar_align_zero_pos_and_neg(self): # See https://github.com/pandas-dev/pandas/pull/14757 df = DataFrame({"A": [-10, 0, 20, 90]}) result = ( df.style.bar(align="zero", color=["#d65f5f", "#5fba7d"], width=90) ._compute() .ctx ) expected = { (0, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 40.0%, #d65f5f 40.0%, " "#d65f5f 45.0%, transparent 45.0%)", ], (1, 0): ["width: 10em", " height: 80%"], (2, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 45.0%, #5fba7d 45.0%, " "#5fba7d 55.0%, transparent 55.0%)", ], (3, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 45.0%, #5fba7d 45.0%, " "#5fba7d 90.0%, transparent 90.0%)", ], } assert result == expected def test_bar_align_left_axis_none(self): df = DataFrame({"A": [0, 1], "B": [2, 4]}) result = df.style.bar(axis=None)._compute().ctx expected = { (0, 0): ["width: 10em", " height: 80%"], (1, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg," "#d65f5f 25.0%, transparent 25.0%)", ], (0, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg," "#d65f5f 50.0%, transparent 50.0%)", ], (1, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg," "#d65f5f 100.0%, transparent 100.0%)", ], } assert result == expected def test_bar_align_zero_axis_none(self): df = DataFrame({"A": [0, 1], "B": [-2, 4]}) result = df.style.bar(align="zero", axis=None)._compute().ctx expected = { (0, 0): ["width: 10em", " height: 80%"], (1, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 50.0%, #d65f5f 50.0%, " "#d65f5f 62.5%, transparent 62.5%)", ], (0, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 25.0%, #d65f5f 25.0%, " "#d65f5f 50.0%, transparent 50.0%)", ], (1, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 50.0%, #d65f5f 50.0%, " "#d65f5f 100.0%, transparent 100.0%)", ], } assert result == expected def test_bar_align_mid_axis_none(self): df = DataFrame({"A": [0, 1], "B": [-2, 4]}) result = df.style.bar(align="mid", axis=None)._compute().ctx expected = { (0, 0): ["width: 10em", " height: 80%"], (1, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 33.3%, #d65f5f 33.3%, " "#d65f5f 50.0%, transparent 50.0%)", ], (0, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg," "#d65f5f 33.3%, transparent 33.3%)", ], (1, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 33.3%, #d65f5f 33.3%, " "#d65f5f 100.0%, transparent 100.0%)", ], } assert result == expected def test_bar_align_mid_vmin(self): df = DataFrame({"A": [0, 1], "B": [-2, 4]}) result = df.style.bar(align="mid", axis=None, vmin=-6)._compute().ctx expected = { (0, 0): ["width: 10em", " height: 80%"], (1, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 60.0%, #d65f5f 60.0%, " "#d65f5f 70.0%, transparent 70.0%)", ], (0, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 40.0%, #d65f5f 40.0%, " "#d65f5f 60.0%, transparent 60.0%)", ], (1, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 60.0%, #d65f5f 60.0%, " "#d65f5f 100.0%, transparent 100.0%)", ], } assert result == expected def test_bar_align_mid_vmax(self): df = DataFrame({"A": [0, 1], "B": [-2, 4]}) result = df.style.bar(align="mid", axis=None, vmax=8)._compute().ctx expected = { (0, 0): ["width: 10em", " height: 80%"], (1, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 20.0%, #d65f5f 20.0%, " "#d65f5f 30.0%, transparent 30.0%)", ], (0, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg," "#d65f5f 20.0%, transparent 20.0%)", ], (1, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 20.0%, #d65f5f 20.0%, " "#d65f5f 60.0%, transparent 60.0%)", ], } assert result == expected def test_bar_align_mid_vmin_vmax_wide(self): df = DataFrame({"A": [0, 1], "B": [-2, 4]}) result = df.style.bar(align="mid", axis=None, vmin=-3, vmax=7)._compute().ctx expected = { (0, 0): ["width: 10em", " height: 80%"], (1, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 30.0%, #d65f5f 30.0%, " "#d65f5f 40.0%, transparent 40.0%)", ], (0, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 10.0%, #d65f5f 10.0%, " "#d65f5f 30.0%, transparent 30.0%)", ], (1, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 30.0%, #d65f5f 30.0%, " "#d65f5f 70.0%, transparent 70.0%)", ], } assert result == expected def test_bar_align_mid_vmin_vmax_clipping(self): df = DataFrame({"A": [0, 1], "B": [-2, 4]}) result = df.style.bar(align="mid", axis=None, vmin=-1, vmax=3)._compute().ctx expected = { (0, 0): ["width: 10em", " height: 80%"], (1, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 25.0%, #d65f5f 25.0%, " "#d65f5f 50.0%, transparent 50.0%)", ], (0, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg," "#d65f5f 25.0%, transparent 25.0%)", ], (1, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 25.0%, #d65f5f 25.0%, " "#d65f5f 100.0%, transparent 100.0%)", ], } assert result == expected def test_bar_align_mid_nans(self): df = DataFrame({"A": [1, None], "B": [-1, 3]}) result = df.style.bar(align="mid", axis=None)._compute().ctx expected = { (0, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 25.0%, #d65f5f 25.0%, " "#d65f5f 50.0%, transparent 50.0%)", ], (0, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg," "#d65f5f 25.0%, transparent 25.0%)", ], (1, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 25.0%, #d65f5f 25.0%, " "#d65f5f 100.0%, transparent 100.0%)", ], } assert result == expected def test_bar_align_zero_nans(self): df = DataFrame({"A": [1, None], "B": [-1, 2]}) result = df.style.bar(align="zero", axis=None)._compute().ctx expected = { (0, 0): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 50.0%, #d65f5f 50.0%, " "#d65f5f 75.0%, transparent 75.0%)", ], (0, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 25.0%, #d65f5f 25.0%, " "#d65f5f 50.0%, transparent 50.0%)", ], (1, 1): [ "width: 10em", " height: 80%", "background: linear-gradient(90deg, " "transparent 50.0%, #d65f5f 50.0%, " "#d65f5f 100.0%, transparent 100.0%)", ], } assert result == expected def test_bar_bad_align_raises(self): df = DataFrame({"A": [-100, -60, -30, -20]}) msg = "`align` must be one of {'left', 'zero',' mid'}" with pytest.raises(ValueError, match=msg): df.style.bar(align="poorly", color=["#d65f5f", "#5fba7d"]) def test_format_with_na_rep(self): # GH 21527 28358 df = DataFrame([[None, None], [1.1, 1.2]], columns=["A", "B"]) ctx = df.style.format(None, na_rep="-")._translate() assert ctx["body"][0][1]["display_value"] == "-" assert ctx["body"][0][2]["display_value"] == "-" ctx = df.style.format("{:.2%}", na_rep="-")._translate() 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() assert ctx["body"][0][2]["display_value"] == "-" assert ctx["body"][1][2]["display_value"] == "120.00%" 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() assert ctx["body"][0][1]["display_value"] == "NA" assert ctx["body"][0][2]["display_value"] == "NA" def test_set_na_rep(self): # GH 21527 28358 df = DataFrame([[None, None], [1.1, 1.2]], columns=["A", "B"]) ctx = df.style.set_na_rep("NA")._translate() assert ctx["body"][0][1]["display_value"] == "NA" assert ctx["body"][0][2]["display_value"] == "NA" ctx = ( df.style.set_na_rep("NA") .format(None, na_rep="-", subset=["B"]) ._translate() ) assert ctx["body"][0][1]["display_value"] == "NA" assert ctx["body"][0][2]["display_value"] == "-" def test_format_non_numeric_na(self): # GH 21527 28358 df = DataFrame( { "object": [None, np.nan, "foo"], "datetime": [None, pd.NaT, pd.Timestamp("20120101")], } ) ctx = df.style.set_na_rep("NA")._translate() 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() assert ctx["body"][0][1]["display_value"] == "-" assert ctx["body"][0][2]["display_value"] == "-" assert ctx["body"][1][1]["display_value"] == "-" assert ctx["body"][1][2]["display_value"] == "-" def test_format_with_bad_na_rep(self): # GH 21527 28358 df = DataFrame([[None, None], [1.1, 1.2]], columns=["A", "B"]) msg = "Expected a string, got -1 instead" with pytest.raises(TypeError, match=msg): df.style.format(None, na_rep=-1) def test_highlight_null(self, null_color="red"): df = DataFrame({"A": [0, np.nan]}) result = df.style.highlight_null()._compute().ctx expected = {(1, 0): ["background-color: red"]} assert result == expected def test_highlight_null_subset(self): # GH 31345 df = DataFrame({"A": [0, np.nan], "B": [0, np.nan]}) result = ( df.style.highlight_null(null_color="red", subset=["A"]) .highlight_null(null_color="green", subset=["B"]) ._compute() .ctx ) expected = { (1, 0): ["background-color: red"], (1, 1): ["background-color: green"], } assert result == expected def test_nonunique_raises(self): df = DataFrame([[1, 2]], columns=["A", "A"]) msg = "style is not supported for non-unique indices." with pytest.raises(ValueError, match=msg): df.style with pytest.raises(ValueError, match=msg): Styler(df) def test_caption(self): styler = Styler(self.df, caption="foo") result = styler.render() assert all(["caption" in result, "foo" in result]) styler = self.df.style result = styler.set_caption("baz") assert styler is result assert styler.caption == "baz" def test_uuid(self): styler = Styler(self.df, uuid="abc123") result = styler.render() assert "abc123" in result styler = self.df.style result = styler.set_uuid("aaa") assert result is styler assert result.uuid == "aaa" def test_unique_id(self): # See https://github.com/pandas-dev/pandas/issues/16780 df = DataFrame({"a": [1, 3, 5, 6], "b": [2, 4, 12, 21]}) result = df.style.render(uuid="test") assert "test" in result ids = re.findall('id="(.*?)"', result) assert np.unique(ids).size == len(ids) def test_table_styles(self): style = [{"selector": "th", "props": [("foo", "bar")]}] styler = Styler(self.df, table_styles=style) result = " ".join(styler.render().split()) assert "th { foo: bar; }" in result styler = self.df.style result = styler.set_table_styles(style) assert styler is result assert styler.table_styles == style def test_table_attributes(self): attributes = 'class="foo" data-bar' styler = Styler(self.df, table_attributes=attributes) result = styler.render() assert 'class="foo" data-bar' in result result = self.df.style.set_table_attributes(attributes).render() assert 'class="foo" data-bar' in result def test_precision(self): with pd.option_context("display.precision", 10): s = Styler(self.df) assert s.precision == 10 s = Styler(self.df, precision=2) assert s.precision == 2 s2 = s.set_precision(4) assert s is s2 assert s.precision == 4 def test_apply_none(self): def f(x): return DataFrame( np.where(x == x.max(), "color: red", ""), index=x.index, columns=x.columns, ) result = DataFrame([[1, 2], [3, 4]]).style.apply(f, axis=None)._compute().ctx assert result[(1, 1)] == ["color: red"] def test_trim(self): result = self.df.style.render() # trim=True assert result.count("#") == 0 result = self.df.style.highlight_max().render() assert result.count("#") == len(self.df.columns) def test_highlight_max(self): df = DataFrame([[1, 2], [3, 4]], columns=["A", "B"]) # max(df) = min(-df) for max_ in [True, False]: if max_: attr = "highlight_max" else: df = -df attr = "highlight_min" result = getattr(df.style, attr)()._compute().ctx assert result[(1, 1)] == ["background-color: yellow"] result = getattr(df.style, attr)(color="green")._compute().ctx assert result[(1, 1)] == ["background-color: green"] result = getattr(df.style, attr)(subset="A")._compute().ctx assert result[(1, 0)] == ["background-color: yellow"] result = getattr(df.style, attr)(axis=0)._compute().ctx expected = { (1, 0): ["background-color: yellow"], (1, 1): ["background-color: yellow"], } assert result == expected result = getattr(df.style, attr)(axis=1)._compute().ctx expected = { (0, 1): ["background-color: yellow"], (1, 1): ["background-color: yellow"], } assert result == expected # separate since we can't negate the strs df["C"] = ["a", "b"] result = df.style.highlight_max()._compute().ctx expected = {(1, 1): ["background-color: yellow"]} result = df.style.highlight_min()._compute().ctx expected = {(0, 0): ["background-color: yellow"]} def test_export(self): f = lambda x: "color: red" if x > 0 else "color: blue" g = lambda x, z: f"color: {z}" if x > 0 else f"color: {z}" style1 = self.styler style1.applymap(f).applymap(g, z="b").highlight_max() result = style1.export() style2 = self.df.style style2.use(result) assert style1._todo == style2._todo style2.render() def test_display_format(self): df = DataFrame(np.random.random(size=(2, 2))) ctx = df.style.format("{:0.1f}")._translate() 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_display_format_raises(self): df = DataFrame(np.random.randn(2, 2)) msg = "Expected a template string or callable, got 5 instead" with pytest.raises(TypeError, match=msg): df.style.format(5) msg = "Expected a template string or callable, got True instead" with pytest.raises(TypeError, match=msg): df.style.format(True) def test_display_set_precision(self): # Issue #13257 df = DataFrame(data=[[1.0, 2.0090], [3.2121, 4.566]], columns=["a", "b"]) s = Styler(df) ctx = s.set_precision(1)._translate() assert s.precision == 1 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.set_precision(2)._translate() assert s.precision == 2 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.set_precision(3)._translate() assert s.precision == 3 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" assert ctx["body"][1][2]["display_value"] == "4.566" def test_display_subset(self): df = DataFrame([[0.1234, 0.1234], [1.1234, 1.1234]], columns=["a", "b"]) ctx = df.style.format( {"a": "{:0.1f}", "b": "{0:.2%}"}, subset=pd.IndexSlice[0, :] )._translate() 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=pd.IndexSlice[0, :])._translate() assert ctx["body"][0][1]["display_value"] == expected assert ctx["body"][1][1]["display_value"] == raw_11 ctx = df.style.format("{:0.1f}", subset=pd.IndexSlice["a"])._translate() assert ctx["body"][0][1]["display_value"] == expected assert ctx["body"][0][2]["display_value"] == "0.123400" ctx = df.style.format("{:0.1f}", subset=pd.IndexSlice[0, "a"])._translate() assert ctx["body"][0][1]["display_value"] == expected assert ctx["body"][1][1]["display_value"] == raw_11 ctx = df.style.format( "{:0.1f}", subset=pd.IndexSlice[[0, 1], ["a"]] )._translate() 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" assert ctx["body"][1][2]["display_value"] == raw_11 def test_display_dict(self): df = DataFrame([[0.1234, 0.1234], [1.1234, 1.1234]], columns=["a", "b"]) ctx = df.style.format({"a": "{:0.1f}", "b": "{0:.2%}"})._translate() assert ctx["body"][0][1]["display_value"] == "0.1" assert ctx["body"][0][2]["display_value"] == "12.34%" df["c"] = ["aaa", "bbb"] ctx = df.style.format({"a": "{:0.1f}", "c": str.upper})._translate() assert ctx["body"][0][1]["display_value"] == "0.1" assert ctx["body"][0][3]["display_value"] == "AAA" def test_bad_apply_shape(self): df = DataFrame([[1, 2], [3, 4]]) msg = "returned the wrong shape" with pytest.raises(ValueError, match=msg): df.style._apply(lambda x: "x", subset=pd.IndexSlice[[0, 1], :]) with pytest.raises(ValueError, match=msg): df.style._apply(lambda x: [""], subset=pd.IndexSlice[[0, 1], :]) with pytest.raises(ValueError, match=msg): df.style._apply(lambda x: ["", "", "", ""]) with pytest.raises(ValueError, match=msg): df.style._apply(lambda x: ["", "", ""], subset=1) msg = "Length mismatch: Expected axis has 3 elements" with pytest.raises(ValueError, match=msg): df.style._apply(lambda x: ["", "", ""], axis=1) def test_apply_bad_return(self): def f(x): return "" df = DataFrame([[1, 2], [3, 4]]) msg = "must return a DataFrame when passed to `Styler.apply` with axis=None" with pytest.raises(TypeError, match=msg): df.style._apply(f, axis=None) def test_apply_bad_labels(self): def f(x): return DataFrame(index=[1, 2], columns=["a", "b"]) df = DataFrame([[1, 2], [3, 4]]) msg = "must have identical index and columns as the input" with pytest.raises(ValueError, match=msg): df.style._apply(f, axis=None) def test_get_level_lengths(self): index = pd.MultiIndex.from_product([["a", "b"], [0, 1, 2]]) expected = { (0, 0): 3, (0, 3): 3, (1, 0): 1, (1, 1): 1, (1, 2): 1, (1, 3): 1, (1, 4): 1, (1, 5): 1, } result = _get_level_lengths(index) 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"]]) expected = { (0, 0): 2, (0, 2): 1, (0, 3): 1, (1, 0): 1, (1, 1): 1, (1, 2): 1, (1, 3): 1, } result = _get_level_lengths(index) 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", } tm.assert_dict_equal(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", } tm.assert_dict_equal(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", } tm.assert_dict_equal(body_10, expected_10) head = result["head"][0] expected = [ { "type": "th", "class": "blank", "value": "", "is_visible": True, "display_value": "", }, { "type": "th", "class": "blank level0", "value": "", "is_visible": True, "display_value": "", }, { "type": "th", "class": "col_heading level0 col0", "value": "A", "is_visible": True, "display_value": "A", }, ] assert head == expected def test_mi_sparse_disabled(self): with pd.option_context("display.multi_sparse", False): df = DataFrame( {"A": [1, 2]}, index=pd.MultiIndex.from_arrays([["a", "a"], [0, 1]]) ) result = df.style._translate() body = result["body"] for row in body: assert "attributes" not in row[0] def test_mi_sparse_index_names(self): df = DataFrame( {"A": [1, 2]}, index=pd.MultiIndex.from_arrays( [["a", "a"], [0, 1]], names=["idx_level_0", "idx_level_1"] ), ) result = df.style._translate() head = result["head"][1] expected = [ {"class": "index_name level0", "value": "idx_level_0", "type": "th"}, {"class": "index_name level1", "value": "idx_level_1", "type": "th"}, {"class": "blank", "value": "", "type": "th"}, ] assert head == expected def test_mi_sparse_column_names(self): df = DataFrame( np.arange(16).reshape(4, 4), index=pd.MultiIndex.from_arrays( [["a", "a", "b", "a"], [0, 1, 1, 2]], names=["idx_level_0", "idx_level_1"], ), columns=pd.MultiIndex.from_arrays( [["C1", "C1", "C2", "C2"], [1, 0, 1, 0]], names=["col_0", "col_1"] ), ) result = df.style._translate() head = result["head"][1] expected = [ { "class": "blank", "value": "", "display_value": "", "type": "th", "is_visible": True, }, { "class": "index_name level1", "value": "col_1", "display_value": "col_1", "is_visible": True, "type": "th", }, { "class": "col_heading level1 col0", "display_value": 1, "is_visible": True, "type": "th", "value": 1, }, { "class": "col_heading level1 col1", "display_value": 0, "is_visible": True, "type": "th", "value": 0, }, { "class": "col_heading level1 col2", "display_value": 1, "is_visible": True, "type": "th", "value": 1, }, { "class": "col_heading level1 col3", "display_value": 0, "is_visible": True, "type": "th", "value": 0, }, ] assert head == expected def test_hide_single_index(self): # GH 14194 # single unnamed index ctx = self.df.style._translate() assert ctx["body"][0][0]["is_visible"] assert ctx["head"][0][0]["is_visible"] ctx2 = self.df.style.hide_index()._translate() 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() 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() 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"] def test_hide_multiindex(self): # GH 14194 df = DataFrame( {"A": [1, 2]}, index=pd.MultiIndex.from_arrays( [["a", "a"], [0, 1]], names=["idx_level_0", "idx_level_1"] ), ) ctx1 = df.style._translate() # tests for 'a' and '0' assert ctx1["body"][0][0]["is_visible"] assert ctx1["body"][0][1]["is_visible"] # check for blank header rows assert ctx1["head"][0][0]["is_visible"] assert ctx1["head"][0][1]["is_visible"] ctx2 = df.style.hide_index()._translate() # tests for 'a' and '0' assert not ctx2["body"][0][0]["is_visible"] assert not ctx2["body"][0][1]["is_visible"] # check for blank header rows assert not ctx2["head"][0][0]["is_visible"] assert not ctx2["head"][0][1]["is_visible"] def test_hide_columns_single_level(self): # GH 14194 # test hiding single column ctx = self.df.style._translate() assert ctx["head"][0][1]["is_visible"] assert ctx["head"][0][1]["display_value"] == "A" assert ctx["head"][0][2]["is_visible"] assert ctx["head"][0][2]["display_value"] == "B" 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() 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() 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 assert not ctx["body"][1][2]["is_visible"] # col B, row 1 def test_hide_columns_mult_levels(self): # GH 14194 # setup dataframe with multiple column levels and indices i1 = pd.MultiIndex.from_arrays( [["a", "a"], [0, 1]], names=["idx_level_0", "idx_level_1"] ) i2 = pd.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() # column headers assert ctx["head"][0][2]["is_visible"] assert ctx["head"][1][2]["is_visible"] assert ctx["head"][1][3]["display_value"] == 1 # indices assert ctx["body"][0][0]["is_visible"] # data assert ctx["body"][1][2]["is_visible"] assert ctx["body"][1][2]["display_value"] == 3 assert ctx["body"][1][3]["is_visible"] assert ctx["body"][1][3]["display_value"] == 4 # hide top column level, which hides both columns ctx = df.style.hide_columns("b")._translate() 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() 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 assert ctx["body"][1][3]["is_visible"] assert ctx["body"][1][3]["display_value"] == 4 # hide second column and index ctx = df.style.hide_columns([("b", 1)]).hide_index()._translate() 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 assert not ctx["head"][1][3]["is_visible"] # 1 assert not ctx["body"][1][3]["is_visible"] # 4 assert ctx["body"][1][2]["is_visible"] assert ctx["body"][1][2]["display_value"] == 3 def test_pipe(self): def set_caption_from_template(styler, a, b): return styler.set_caption(f"Dataframe with a = {a} and b = {b}") styler = self.df.style.pipe(set_caption_from_template, "A", b="B") assert "Dataframe with a = A and b = B" in styler.render() # Test with an argument that is a (callable, keyword_name) pair. def f(a, b, styler): return (a, b, styler) styler = self.df.style result = styler.pipe((f, "styler"), a=1, b=2) assert result == (1, 2, styler) def test_no_cell_ids(self): # GH 35588 # GH 35663 df = DataFrame(data=[[0]]) styler = Styler(df, uuid="_", cell_ids=False) styler.render() s = styler.render() # render twice to ensure ctx is not updated assert s.find('