done
This commit is contained in:
		| @ -0,0 +1,359 @@ | ||||
| import io | ||||
|  | ||||
| import numpy as np | ||||
| import pytest | ||||
|  | ||||
| from pandas import ( | ||||
|     NA, | ||||
|     DataFrame, | ||||
|     read_csv, | ||||
| ) | ||||
|  | ||||
| pytest.importorskip("jinja2") | ||||
|  | ||||
|  | ||||
| def bar_grad(a=None, b=None, c=None, d=None): | ||||
|     """Used in multiple tests to simplify formatting of expected result""" | ||||
|     ret = [("width", "10em")] | ||||
|     if all(x is None for x in [a, b, c, d]): | ||||
|         return ret | ||||
|     return ret + [ | ||||
|         ( | ||||
|             "background", | ||||
|             f"linear-gradient(90deg,{','.join([x for x in [a, b, c, d] if x])})", | ||||
|         ) | ||||
|     ] | ||||
|  | ||||
|  | ||||
| def no_bar(): | ||||
|     return bar_grad() | ||||
|  | ||||
|  | ||||
| def bar_to(x, color="#d65f5f"): | ||||
|     return bar_grad(f" {color} {x:.1f}%", f" transparent {x:.1f}%") | ||||
|  | ||||
|  | ||||
| def bar_from_to(x, y, color="#d65f5f"): | ||||
|     return bar_grad( | ||||
|         f" transparent {x:.1f}%", | ||||
|         f" {color} {x:.1f}%", | ||||
|         f" {color} {y:.1f}%", | ||||
|         f" transparent {y:.1f}%", | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def df_pos(): | ||||
|     return DataFrame([[1], [2], [3]]) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def df_neg(): | ||||
|     return DataFrame([[-1], [-2], [-3]]) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def df_mix(): | ||||
|     return DataFrame([[-3], [1], [2]]) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "align, exp", | ||||
|     [ | ||||
|         ("left", [no_bar(), bar_to(50), bar_to(100)]), | ||||
|         ("right", [bar_to(100), bar_from_to(50, 100), no_bar()]), | ||||
|         ("mid", [bar_to(33.33), bar_to(66.66), bar_to(100)]), | ||||
|         ("zero", [bar_from_to(50, 66.7), bar_from_to(50, 83.3), bar_from_to(50, 100)]), | ||||
|         ("mean", [bar_to(50), no_bar(), bar_from_to(50, 100)]), | ||||
|         (2.0, [bar_to(50), no_bar(), bar_from_to(50, 100)]), | ||||
|         (np.median, [bar_to(50), no_bar(), bar_from_to(50, 100)]), | ||||
|     ], | ||||
| ) | ||||
| def test_align_positive_cases(df_pos, align, exp): | ||||
|     # test different align cases for all positive values | ||||
|     result = df_pos.style.bar(align=align)._compute().ctx | ||||
|     expected = {(0, 0): exp[0], (1, 0): exp[1], (2, 0): exp[2]} | ||||
|     assert result == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "align, exp", | ||||
|     [ | ||||
|         ("left", [bar_to(100), bar_to(50), no_bar()]), | ||||
|         ("right", [no_bar(), bar_from_to(50, 100), bar_to(100)]), | ||||
|         ("mid", [bar_from_to(66.66, 100), bar_from_to(33.33, 100), bar_to(100)]), | ||||
|         ("zero", [bar_from_to(33.33, 50), bar_from_to(16.66, 50), bar_to(50)]), | ||||
|         ("mean", [bar_from_to(50, 100), no_bar(), bar_to(50)]), | ||||
|         (-2.0, [bar_from_to(50, 100), no_bar(), bar_to(50)]), | ||||
|         (np.median, [bar_from_to(50, 100), no_bar(), bar_to(50)]), | ||||
|     ], | ||||
| ) | ||||
| def test_align_negative_cases(df_neg, align, exp): | ||||
|     # test different align cases for all negative values | ||||
|     result = df_neg.style.bar(align=align)._compute().ctx | ||||
|     expected = {(0, 0): exp[0], (1, 0): exp[1], (2, 0): exp[2]} | ||||
|     assert result == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "align, exp", | ||||
|     [ | ||||
|         ("left", [no_bar(), bar_to(80), bar_to(100)]), | ||||
|         ("right", [bar_to(100), bar_from_to(80, 100), no_bar()]), | ||||
|         ("mid", [bar_to(60), bar_from_to(60, 80), bar_from_to(60, 100)]), | ||||
|         ("zero", [bar_to(50), bar_from_to(50, 66.66), bar_from_to(50, 83.33)]), | ||||
|         ("mean", [bar_to(50), bar_from_to(50, 66.66), bar_from_to(50, 83.33)]), | ||||
|         (-0.0, [bar_to(50), bar_from_to(50, 66.66), bar_from_to(50, 83.33)]), | ||||
|         (np.nanmedian, [bar_to(50), no_bar(), bar_from_to(50, 62.5)]), | ||||
|     ], | ||||
| ) | ||||
| @pytest.mark.parametrize("nans", [True, False]) | ||||
| def test_align_mixed_cases(df_mix, align, exp, nans): | ||||
|     # test different align cases for mixed positive and negative values | ||||
|     # also test no impact of NaNs and no_bar | ||||
|     expected = {(0, 0): exp[0], (1, 0): exp[1], (2, 0): exp[2]} | ||||
|     if nans: | ||||
|         df_mix.loc[3, :] = np.nan | ||||
|         expected.update({(3, 0): no_bar()}) | ||||
|     result = df_mix.style.bar(align=align)._compute().ctx | ||||
|     assert result == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "align, exp", | ||||
|     [ | ||||
|         ( | ||||
|             "left", | ||||
|             { | ||||
|                 "index": [[no_bar(), no_bar()], [bar_to(100), bar_to(100)]], | ||||
|                 "columns": [[no_bar(), bar_to(100)], [no_bar(), bar_to(100)]], | ||||
|                 "none": [[no_bar(), bar_to(33.33)], [bar_to(66.66), bar_to(100)]], | ||||
|             }, | ||||
|         ), | ||||
|         ( | ||||
|             "mid", | ||||
|             { | ||||
|                 "index": [[bar_to(33.33), bar_to(50)], [bar_to(100), bar_to(100)]], | ||||
|                 "columns": [[bar_to(50), bar_to(100)], [bar_to(75), bar_to(100)]], | ||||
|                 "none": [[bar_to(25), bar_to(50)], [bar_to(75), bar_to(100)]], | ||||
|             }, | ||||
|         ), | ||||
|         ( | ||||
|             "zero", | ||||
|             { | ||||
|                 "index": [ | ||||
|                     [bar_from_to(50, 66.66), bar_from_to(50, 75)], | ||||
|                     [bar_from_to(50, 100), bar_from_to(50, 100)], | ||||
|                 ], | ||||
|                 "columns": [ | ||||
|                     [bar_from_to(50, 75), bar_from_to(50, 100)], | ||||
|                     [bar_from_to(50, 87.5), bar_from_to(50, 100)], | ||||
|                 ], | ||||
|                 "none": [ | ||||
|                     [bar_from_to(50, 62.5), bar_from_to(50, 75)], | ||||
|                     [bar_from_to(50, 87.5), bar_from_to(50, 100)], | ||||
|                 ], | ||||
|             }, | ||||
|         ), | ||||
|         ( | ||||
|             2, | ||||
|             { | ||||
|                 "index": [ | ||||
|                     [bar_to(50), no_bar()], | ||||
|                     [bar_from_to(50, 100), bar_from_to(50, 100)], | ||||
|                 ], | ||||
|                 "columns": [ | ||||
|                     [bar_to(50), no_bar()], | ||||
|                     [bar_from_to(50, 75), bar_from_to(50, 100)], | ||||
|                 ], | ||||
|                 "none": [ | ||||
|                     [bar_from_to(25, 50), no_bar()], | ||||
|                     [bar_from_to(50, 75), bar_from_to(50, 100)], | ||||
|                 ], | ||||
|             }, | ||||
|         ), | ||||
|     ], | ||||
| ) | ||||
| @pytest.mark.parametrize("axis", ["index", "columns", "none"]) | ||||
| def test_align_axis(align, exp, axis): | ||||
|     # test all axis combinations with positive values and different aligns | ||||
|     data = DataFrame([[1, 2], [3, 4]]) | ||||
|     result = ( | ||||
|         data.style.bar(align=align, axis=None if axis == "none" else axis) | ||||
|         ._compute() | ||||
|         .ctx | ||||
|     ) | ||||
|     expected = { | ||||
|         (0, 0): exp[axis][0][0], | ||||
|         (0, 1): exp[axis][0][1], | ||||
|         (1, 0): exp[axis][1][0], | ||||
|         (1, 1): exp[axis][1][1], | ||||
|     } | ||||
|     assert result == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "values, vmin, vmax", | ||||
|     [ | ||||
|         ("positive", 1.5, 2.5), | ||||
|         ("negative", -2.5, -1.5), | ||||
|         ("mixed", -2.5, 1.5), | ||||
|     ], | ||||
| ) | ||||
| @pytest.mark.parametrize("nullify", [None, "vmin", "vmax"])  # test min/max separately | ||||
| @pytest.mark.parametrize("align", ["left", "right", "zero", "mid"]) | ||||
| def test_vmin_vmax_clipping(df_pos, df_neg, df_mix, values, vmin, vmax, nullify, align): | ||||
|     # test that clipping occurs if any vmin > data_values or vmax < data_values | ||||
|     if align == "mid":  # mid acts as left or right in each case | ||||
|         if values == "positive": | ||||
|             align = "left" | ||||
|         elif values == "negative": | ||||
|             align = "right" | ||||
|     df = {"positive": df_pos, "negative": df_neg, "mixed": df_mix}[values] | ||||
|     vmin = None if nullify == "vmin" else vmin | ||||
|     vmax = None if nullify == "vmax" else vmax | ||||
|  | ||||
|     clip_df = df.where(df <= (vmax if vmax else 999), other=vmax) | ||||
|     clip_df = clip_df.where(clip_df >= (vmin if vmin else -999), other=vmin) | ||||
|  | ||||
|     result = ( | ||||
|         df.style.bar(align=align, vmin=vmin, vmax=vmax, color=["red", "green"]) | ||||
|         ._compute() | ||||
|         .ctx | ||||
|     ) | ||||
|     expected = clip_df.style.bar(align=align, color=["red", "green"])._compute().ctx | ||||
|     assert result == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "values, vmin, vmax", | ||||
|     [ | ||||
|         ("positive", 0.5, 4.5), | ||||
|         ("negative", -4.5, -0.5), | ||||
|         ("mixed", -4.5, 4.5), | ||||
|     ], | ||||
| ) | ||||
| @pytest.mark.parametrize("nullify", [None, "vmin", "vmax"])  # test min/max separately | ||||
| @pytest.mark.parametrize("align", ["left", "right", "zero", "mid"]) | ||||
| def test_vmin_vmax_widening(df_pos, df_neg, df_mix, values, vmin, vmax, nullify, align): | ||||
|     # test that widening occurs if any vmax > data_values or vmin < data_values | ||||
|     if align == "mid":  # mid acts as left or right in each case | ||||
|         if values == "positive": | ||||
|             align = "left" | ||||
|         elif values == "negative": | ||||
|             align = "right" | ||||
|     df = {"positive": df_pos, "negative": df_neg, "mixed": df_mix}[values] | ||||
|     vmin = None if nullify == "vmin" else vmin | ||||
|     vmax = None if nullify == "vmax" else vmax | ||||
|  | ||||
|     expand_df = df.copy() | ||||
|     expand_df.loc[3, :], expand_df.loc[4, :] = vmin, vmax | ||||
|  | ||||
|     result = ( | ||||
|         df.style.bar(align=align, vmin=vmin, vmax=vmax, color=["red", "green"]) | ||||
|         ._compute() | ||||
|         .ctx | ||||
|     ) | ||||
|     expected = expand_df.style.bar(align=align, color=["red", "green"])._compute().ctx | ||||
|     assert result.items() <= expected.items() | ||||
|  | ||||
|  | ||||
| def test_numerics(): | ||||
|     # test data is pre-selected for numeric values | ||||
|     data = DataFrame([[1, "a"], [2, "b"]]) | ||||
|     result = data.style.bar()._compute().ctx | ||||
|     assert (0, 1) not in result | ||||
|     assert (1, 1) not in result | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "align, exp", | ||||
|     [ | ||||
|         ("left", [no_bar(), bar_to(100, "green")]), | ||||
|         ("right", [bar_to(100, "red"), no_bar()]), | ||||
|         ("mid", [bar_to(25, "red"), bar_from_to(25, 100, "green")]), | ||||
|         ("zero", [bar_from_to(33.33, 50, "red"), bar_from_to(50, 100, "green")]), | ||||
|     ], | ||||
| ) | ||||
| def test_colors_mixed(align, exp): | ||||
|     data = DataFrame([[-1], [3]]) | ||||
|     result = data.style.bar(align=align, color=["red", "green"])._compute().ctx | ||||
|     assert result == {(0, 0): exp[0], (1, 0): exp[1]} | ||||
|  | ||||
|  | ||||
| def test_bar_align_height(): | ||||
|     # test when keyword height is used 'no-repeat center' and 'background-size' present | ||||
|     data = DataFrame([[1], [2]]) | ||||
|     result = data.style.bar(align="left", height=50)._compute().ctx | ||||
|     bg_s = "linear-gradient(90deg, #d65f5f 100.0%, transparent 100.0%) no-repeat center" | ||||
|     expected = { | ||||
|         (0, 0): [("width", "10em")], | ||||
|         (1, 0): [ | ||||
|             ("width", "10em"), | ||||
|             ("background", bg_s), | ||||
|             ("background-size", "100% 50.0%"), | ||||
|         ], | ||||
|     } | ||||
|     assert result == expected | ||||
|  | ||||
|  | ||||
| def test_bar_value_error_raises(): | ||||
|     df = DataFrame({"A": [-100, -60, -30, -20]}) | ||||
|  | ||||
|     msg = "`align` should be in {'left', 'right', 'mid', 'mean', 'zero'} or" | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         df.style.bar(align="poorly", color=["#d65f5f", "#5fba7d"]).to_html() | ||||
|  | ||||
|     msg = r"`width` must be a value in \[0, 100\]" | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         df.style.bar(width=200).to_html() | ||||
|  | ||||
|     msg = r"`height` must be a value in \[0, 100\]" | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         df.style.bar(height=200).to_html() | ||||
|  | ||||
|  | ||||
| def test_bar_color_and_cmap_error_raises(): | ||||
|     df = DataFrame({"A": [1, 2, 3, 4]}) | ||||
|     msg = "`color` and `cmap` cannot both be given" | ||||
|     # Test that providing both color and cmap raises a ValueError | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         df.style.bar(color="#d65f5f", cmap="viridis").to_html() | ||||
|  | ||||
|  | ||||
| def test_bar_invalid_color_type_error_raises(): | ||||
|     df = DataFrame({"A": [1, 2, 3, 4]}) | ||||
|     msg = ( | ||||
|         r"`color` must be string or list or tuple of 2 strings," | ||||
|         r"\(eg: color=\['#d65f5f', '#5fba7d'\]\)" | ||||
|     ) | ||||
|     # Test that providing an invalid color type raises a ValueError | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         df.style.bar(color=123).to_html() | ||||
|  | ||||
|     # Test that providing a color list with more than two elements raises a ValueError | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         df.style.bar(color=["#d65f5f", "#5fba7d", "#abcdef"]).to_html() | ||||
|  | ||||
|  | ||||
| def test_styler_bar_with_NA_values(): | ||||
|     df1 = DataFrame({"A": [1, 2, NA, 4]}) | ||||
|     df2 = DataFrame([[NA, NA], [NA, NA]]) | ||||
|     expected_substring = "style type=" | ||||
|     html_output1 = df1.style.bar(subset="A").to_html() | ||||
|     html_output2 = df2.style.bar(align="left", axis=None).to_html() | ||||
|     assert expected_substring in html_output1 | ||||
|     assert expected_substring in html_output2 | ||||
|  | ||||
|  | ||||
| def test_style_bar_with_pyarrow_NA_values(): | ||||
|     pytest.importorskip("pyarrow") | ||||
|     data = """name,age,test1,test2,teacher | ||||
|         Adam,15,95.0,80,Ashby | ||||
|         Bob,16,81.0,82,Ashby | ||||
|         Dave,16,89.0,84,Jones | ||||
|         Fred,15,,88,Jones""" | ||||
|     df = read_csv(io.StringIO(data), dtype_backend="pyarrow") | ||||
|     expected_substring = "style type=" | ||||
|     html_output = df.style.bar(subset="test1").to_html() | ||||
|     assert expected_substring in html_output | ||||
| @ -0,0 +1,44 @@ | ||||
| import pytest | ||||
|  | ||||
| jinja2 = pytest.importorskip("jinja2") | ||||
|  | ||||
| from pandas import ( | ||||
|     DataFrame, | ||||
|     MultiIndex, | ||||
| ) | ||||
|  | ||||
| from pandas.io.formats.style import Styler | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def df(): | ||||
|     return DataFrame( | ||||
|         data=[[0, -0.609], [1, -1.228]], | ||||
|         columns=["A", "B"], | ||||
|         index=["x", "y"], | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def styler(df): | ||||
|     return Styler(df, uuid_len=0) | ||||
|  | ||||
|  | ||||
| def test_concat_bad_columns(styler): | ||||
|     msg = "`other.data` must have same columns as `Styler.data" | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         styler.concat(DataFrame([[1, 2]]).style) | ||||
|  | ||||
|  | ||||
| def test_concat_bad_type(styler): | ||||
|     msg = "`other` must be of type `Styler`" | ||||
|     with pytest.raises(TypeError, match=msg): | ||||
|         styler.concat(DataFrame([[1, 2]])) | ||||
|  | ||||
|  | ||||
| def test_concat_bad_index_levels(styler, df): | ||||
|     df = df.copy() | ||||
|     df.index = MultiIndex.from_tuples([(0, 0), (1, 1)]) | ||||
|     msg = "number of index levels must be same in `other`" | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         styler.concat(df.style) | ||||
| @ -0,0 +1,562 @@ | ||||
| import numpy as np | ||||
| import pytest | ||||
|  | ||||
| from pandas import ( | ||||
|     NA, | ||||
|     DataFrame, | ||||
|     IndexSlice, | ||||
|     MultiIndex, | ||||
|     NaT, | ||||
|     Timestamp, | ||||
|     option_context, | ||||
| ) | ||||
|  | ||||
| pytest.importorskip("jinja2") | ||||
| from pandas.io.formats.style import Styler | ||||
| from pandas.io.formats.style_render import _str_escape | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def df(): | ||||
|     return DataFrame( | ||||
|         data=[[0, -0.609], [1, -1.228]], | ||||
|         columns=["A", "B"], | ||||
|         index=["x", "y"], | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def styler(df): | ||||
|     return Styler(df, uuid_len=0) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def df_multi(): | ||||
|     return DataFrame( | ||||
|         data=np.arange(16).reshape(4, 4), | ||||
|         columns=MultiIndex.from_product([["A", "B"], ["a", "b"]]), | ||||
|         index=MultiIndex.from_product([["X", "Y"], ["x", "y"]]), | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def styler_multi(df_multi): | ||||
|     return Styler(df_multi, uuid_len=0) | ||||
|  | ||||
|  | ||||
| def test_display_format(styler): | ||||
|     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 | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("index", [True, False]) | ||||
| @pytest.mark.parametrize("columns", [True, False]) | ||||
| def test_display_format_index(styler, index, columns): | ||||
|     exp_index = ["x", "y"] | ||||
|     if index: | ||||
|         styler.format_index(lambda v: v.upper(), axis=0)  # test callable | ||||
|         exp_index = ["X", "Y"] | ||||
|  | ||||
|     exp_columns = ["A", "B"] | ||||
|     if columns: | ||||
|         styler.format_index("*{}*", axis=1)  # test string | ||||
|         exp_columns = ["*A*", "*B*"] | ||||
|  | ||||
|     ctx = styler._translate(True, True) | ||||
|  | ||||
|     for r, row in enumerate(ctx["body"]): | ||||
|         assert row[0]["display_value"] == exp_index[r] | ||||
|  | ||||
|     for c, col in enumerate(ctx["head"][1:]): | ||||
|         assert col["display_value"] == exp_columns[c] | ||||
|  | ||||
|  | ||||
| def test_format_dict(styler): | ||||
|     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_index_dict(styler): | ||||
|     ctx = styler.format_index({0: lambda v: v.upper()})._translate(True, True) | ||||
|     for i, val in enumerate(["X", "Y"]): | ||||
|         assert ctx["body"][i][0]["display_value"] == val | ||||
|  | ||||
|  | ||||
| def test_format_string(styler): | ||||
|     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" | ||||
|     assert ctx["body"][1][2]["display_value"] == "-1.23" | ||||
|  | ||||
|  | ||||
| def test_format_callable(styler): | ||||
|     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" | ||||
|     assert ctx["body"][1][2]["display_value"] == "neg" | ||||
|  | ||||
|  | ||||
| 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(True, True) | ||||
|     assert ctx["body"][0][1]["display_value"] == "-" | ||||
|     assert ctx["body"][0][2]["display_value"] == "-" | ||||
|  | ||||
|     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(True, True) | ||||
|     assert ctx["body"][0][2]["display_value"] == "-" | ||||
|     assert ctx["body"][1][2]["display_value"] == "120.00%" | ||||
|  | ||||
|  | ||||
| def test_format_index_with_na_rep(): | ||||
|     df = DataFrame([[1, 2, 3, 4, 5]], columns=["A", None, np.nan, NaT, NA]) | ||||
|     ctx = df.style.format_index(None, na_rep="--", axis=1)._translate(True, True) | ||||
|     assert ctx["head"][0][1]["display_value"] == "A" | ||||
|     for i in [2, 3, 4, 5]: | ||||
|         assert ctx["head"][0][i]["display_value"] == "--" | ||||
|  | ||||
|  | ||||
| def test_format_non_numeric_na(): | ||||
|     # GH 21527 28358 | ||||
|     df = DataFrame( | ||||
|         { | ||||
|             "object": [None, np.nan, "foo"], | ||||
|             "datetime": [None, NaT, Timestamp("20120101")], | ||||
|         } | ||||
|     ) | ||||
|     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"] == "-" | ||||
|     assert ctx["body"][1][2]["display_value"] == "-" | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "func, attr, kwargs", | ||||
|     [ | ||||
|         ("format", "_display_funcs", {}), | ||||
|         ("format_index", "_display_funcs_index", {"axis": 0}), | ||||
|         ("format_index", "_display_funcs_columns", {"axis": 1}), | ||||
|     ], | ||||
| ) | ||||
| def test_format_clear(styler, func, attr, kwargs): | ||||
|     assert (0, 0) not in getattr(styler, attr)  # using default | ||||
|     getattr(styler, func)("{:.2f}", **kwargs) | ||||
|     assert (0, 0) in getattr(styler, attr)  # formatter is specified | ||||
|     getattr(styler, func)(**kwargs) | ||||
|     assert (0, 0) not in getattr(styler, attr)  # formatter cleared to default | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "escape, exp", | ||||
|     [ | ||||
|         ("html", "<>&"%$#_{}~^\\~ ^ \\ "), | ||||
|         ( | ||||
|             "latex", | ||||
|             '<>\\&"\\%\\$\\#\\_\\{\\}\\textasciitilde \\textasciicircum ' | ||||
|             "\\textbackslash \\textasciitilde \\space \\textasciicircum \\space " | ||||
|             "\\textbackslash \\space ", | ||||
|         ), | ||||
|     ], | ||||
| ) | ||||
| def test_format_escape_html(escape, exp): | ||||
|     chars = '<>&"%$#_{}~^\\~ ^ \\ ' | ||||
|     df = DataFrame([[chars]]) | ||||
|  | ||||
|     s = Styler(df, uuid_len=0).format("&{0}&", escape=None) | ||||
|     expected = f'<td id="T__row0_col0" class="data row0 col0" >&{chars}&</td>' | ||||
|     assert expected in s.to_html() | ||||
|  | ||||
|     # only the value should be escaped before passing to the formatter | ||||
|     s = Styler(df, uuid_len=0).format("&{0}&", escape=escape) | ||||
|     expected = f'<td id="T__row0_col0" class="data row0 col0" >&{exp}&</td>' | ||||
|     assert expected in s.to_html() | ||||
|  | ||||
|     # also test format_index() | ||||
|     styler = Styler(DataFrame(columns=[chars]), uuid_len=0) | ||||
|     styler.format_index("&{0}&", escape=None, axis=1) | ||||
|     assert styler._translate(True, True)["head"][0][1]["display_value"] == f"&{chars}&" | ||||
|     styler.format_index("&{0}&", escape=escape, axis=1) | ||||
|     assert styler._translate(True, True)["head"][0][1]["display_value"] == f"&{exp}&" | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "chars, expected", | ||||
|     [ | ||||
|         ( | ||||
|             r"$ \$&%#_{}~^\ $ &%#_{}~^\ $", | ||||
|             "".join( | ||||
|                 [ | ||||
|                     r"$ \$&%#_{}~^\ $ ", | ||||
|                     r"\&\%\#\_\{\}\textasciitilde \textasciicircum ", | ||||
|                     r"\textbackslash \space \$", | ||||
|                 ] | ||||
|             ), | ||||
|         ), | ||||
|         ( | ||||
|             r"\( &%#_{}~^\ \) &%#_{}~^\ \(", | ||||
|             "".join( | ||||
|                 [ | ||||
|                     r"\( &%#_{}~^\ \) ", | ||||
|                     r"\&\%\#\_\{\}\textasciitilde \textasciicircum ", | ||||
|                     r"\textbackslash \space \textbackslash (", | ||||
|                 ] | ||||
|             ), | ||||
|         ), | ||||
|         ( | ||||
|             r"$\&%#_{}^\$", | ||||
|             r"\$\textbackslash \&\%\#\_\{\}\textasciicircum \textbackslash \$", | ||||
|         ), | ||||
|         ( | ||||
|             r"$ \frac{1}{2} $ \( \frac{1}{2} \)", | ||||
|             "".join( | ||||
|                 [ | ||||
|                     r"$ \frac{1}{2} $", | ||||
|                     r" \textbackslash ( \textbackslash frac\{1\}\{2\} \textbackslash )", | ||||
|                 ] | ||||
|             ), | ||||
|         ), | ||||
|     ], | ||||
| ) | ||||
| def test_format_escape_latex_math(chars, expected): | ||||
|     # GH 51903 | ||||
|     # latex-math escape works for each DataFrame cell separately. If we have | ||||
|     # a combination of dollar signs and brackets, the dollar sign would apply. | ||||
|     df = DataFrame([[chars]]) | ||||
|     s = df.style.format("{0}", escape="latex-math") | ||||
|     assert s._translate(True, True)["body"][0][1]["display_value"] == expected | ||||
|  | ||||
|  | ||||
| def test_format_escape_na_rep(): | ||||
|     # tests the na_rep is not escaped | ||||
|     df = DataFrame([['<>&"', None]]) | ||||
|     s = Styler(df, uuid_len=0).format("X&{0}>X", escape="html", na_rep="&") | ||||
|     ex = '<td id="T__row0_col0" class="data row0 col0" >X&<>&">X</td>' | ||||
|     expected2 = '<td id="T__row0_col1" class="data row0 col1" >&</td>' | ||||
|     assert ex in s.to_html() | ||||
|     assert expected2 in s.to_html() | ||||
|  | ||||
|     # also test for format_index() | ||||
|     df = DataFrame(columns=['<>&"', None]) | ||||
|     styler = Styler(df, uuid_len=0) | ||||
|     styler.format_index("X&{0}>X", escape="html", na_rep="&", axis=1) | ||||
|     ctx = styler._translate(True, True) | ||||
|     assert ctx["head"][0][1]["display_value"] == "X&<>&">X" | ||||
|     assert ctx["head"][0][2]["display_value"] == "&" | ||||
|  | ||||
|  | ||||
| def test_format_escape_floats(styler): | ||||
|     # test given formatter for number format is not impacted by escape | ||||
|     s = styler.format("{:.1f}", escape="html") | ||||
|     for expected in [">0.0<", ">1.0<", ">-1.2<", ">-0.6<"]: | ||||
|         assert expected in s.to_html() | ||||
|     # tests precision of floats is not impacted by escape | ||||
|     s = styler.format(precision=1, escape="html") | ||||
|     for expected in [">0<", ">1<", ">-1.2<", ">-0.6<"]: | ||||
|         assert expected in s.to_html() | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("formatter", [5, True, [2.0]]) | ||||
| @pytest.mark.parametrize("func", ["format", "format_index"]) | ||||
| def test_format_raises(styler, formatter, func): | ||||
|     with pytest.raises(TypeError, match="expected str or callable"): | ||||
|         getattr(styler, func)(formatter) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "precision, expected", | ||||
|     [ | ||||
|         (1, ["1.0", "2.0", "3.2", "4.6"]), | ||||
|         (2, ["1.00", "2.01", "3.21", "4.57"]), | ||||
|         (3, ["1.000", "2.009", "3.212", "4.566"]), | ||||
|     ], | ||||
| ) | ||||
| def test_format_with_precision(precision, expected): | ||||
|     # Issue #13257 | ||||
|     df = DataFrame([[1.0, 2.0090, 3.2121, 4.566]], columns=[1.0, 2.0090, 3.2121, 4.566]) | ||||
|     styler = Styler(df) | ||||
|     styler.format(precision=precision) | ||||
|     styler.format_index(precision=precision, axis=1) | ||||
|  | ||||
|     ctx = styler._translate(True, True) | ||||
|     for col, exp in enumerate(expected): | ||||
|         assert ctx["body"][0][col + 1]["display_value"] == exp  # format test | ||||
|         assert ctx["head"][0][col + 1]["display_value"] == exp  # format_index test | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("axis", [0, 1]) | ||||
| @pytest.mark.parametrize( | ||||
|     "level, expected", | ||||
|     [ | ||||
|         (0, ["X", "X", "_", "_"]),  # level int | ||||
|         ("zero", ["X", "X", "_", "_"]),  # level name | ||||
|         (1, ["_", "_", "X", "X"]),  # other level int | ||||
|         ("one", ["_", "_", "X", "X"]),  # other level name | ||||
|         ([0, 1], ["X", "X", "X", "X"]),  # both levels | ||||
|         ([0, "zero"], ["X", "X", "_", "_"]),  # level int and name simultaneous | ||||
|         ([0, "one"], ["X", "X", "X", "X"]),  # both levels as int and name | ||||
|         (["one", "zero"], ["X", "X", "X", "X"]),  # both level names, reversed | ||||
|     ], | ||||
| ) | ||||
| def test_format_index_level(axis, level, expected): | ||||
|     midx = MultiIndex.from_arrays([["_", "_"], ["_", "_"]], names=["zero", "one"]) | ||||
|     df = DataFrame([[1, 2], [3, 4]]) | ||||
|     if axis == 0: | ||||
|         df.index = midx | ||||
|     else: | ||||
|         df.columns = midx | ||||
|  | ||||
|     styler = df.style.format_index(lambda v: "X", level=level, axis=axis) | ||||
|     ctx = styler._translate(True, True) | ||||
|  | ||||
|     if axis == 0:  # compare index | ||||
|         result = [ctx["body"][s][0]["display_value"] for s in range(2)] | ||||
|         result += [ctx["body"][s][1]["display_value"] for s in range(2)] | ||||
|     else:  # compare columns | ||||
|         result = [ctx["head"][0][s + 1]["display_value"] for s in range(2)] | ||||
|         result += [ctx["head"][1][s + 1]["display_value"] for s in range(2)] | ||||
|  | ||||
|     assert expected == result | ||||
|  | ||||
|  | ||||
| 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(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(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(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(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( | ||||
|         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" | ||||
|     assert ctx["body"][1][2]["display_value"] == raw_11 | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("formatter", [None, "{:,.1f}"]) | ||||
| @pytest.mark.parametrize("decimal", [".", "*"]) | ||||
| @pytest.mark.parametrize("precision", [None, 2]) | ||||
| @pytest.mark.parametrize("func, col", [("format", 1), ("format_index", 0)]) | ||||
| def test_format_thousands(formatter, decimal, precision, func, col): | ||||
|     styler = DataFrame([[1000000.123456789]], index=[1000000.123456789]).style | ||||
|     result = getattr(styler, func)(  # testing float | ||||
|         thousands="_", formatter=formatter, decimal=decimal, precision=precision | ||||
|     )._translate(True, True) | ||||
|     assert "1_000_000" in result["body"][0][col]["display_value"] | ||||
|  | ||||
|     styler = DataFrame([[1000000]], index=[1000000]).style | ||||
|     result = getattr(styler, func)(  # testing int | ||||
|         thousands="_", formatter=formatter, decimal=decimal, precision=precision | ||||
|     )._translate(True, True) | ||||
|     assert "1_000_000" in result["body"][0][col]["display_value"] | ||||
|  | ||||
|     styler = DataFrame([[1 + 1000000.123456789j]], index=[1 + 1000000.123456789j]).style | ||||
|     result = getattr(styler, func)(  # testing complex | ||||
|         thousands="_", formatter=formatter, decimal=decimal, precision=precision | ||||
|     )._translate(True, True) | ||||
|     assert "1_000_000" in result["body"][0][col]["display_value"] | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("formatter", [None, "{:,.4f}"]) | ||||
| @pytest.mark.parametrize("thousands", [None, ",", "*"]) | ||||
| @pytest.mark.parametrize("precision", [None, 4]) | ||||
| @pytest.mark.parametrize("func, col", [("format", 1), ("format_index", 0)]) | ||||
| def test_format_decimal(formatter, thousands, precision, func, col): | ||||
|     styler = DataFrame([[1000000.123456789]], index=[1000000.123456789]).style | ||||
|     result = getattr(styler, func)(  # testing float | ||||
|         decimal="_", formatter=formatter, thousands=thousands, precision=precision | ||||
|     )._translate(True, True) | ||||
|     assert "000_123" in result["body"][0][col]["display_value"] | ||||
|  | ||||
|     styler = DataFrame([[1 + 1000000.123456789j]], index=[1 + 1000000.123456789j]).style | ||||
|     result = getattr(styler, func)(  # testing complex | ||||
|         decimal="_", formatter=formatter, thousands=thousands, precision=precision | ||||
|     )._translate(True, True) | ||||
|     assert "000_123" in result["body"][0][col]["display_value"] | ||||
|  | ||||
|  | ||||
| def test_str_escape_error(): | ||||
|     msg = "`escape` only permitted in {'html', 'latex', 'latex-math'}, got " | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         _str_escape("text", "bad_escape") | ||||
|  | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         _str_escape("text", []) | ||||
|  | ||||
|     _str_escape(2.00, "bad_escape")  # OK since dtype is float | ||||
|  | ||||
|  | ||||
| def test_long_int_formatting(): | ||||
|     df = DataFrame(data=[[1234567890123456789]], columns=["test"]) | ||||
|     styler = df.style | ||||
|     ctx = styler._translate(True, True) | ||||
|     assert ctx["body"][0][1]["display_value"] == "1234567890123456789" | ||||
|  | ||||
|     styler = df.style.format(thousands="_") | ||||
|     ctx = styler._translate(True, True) | ||||
|     assert ctx["body"][0][1]["display_value"] == "1_234_567_890_123_456_789" | ||||
|  | ||||
|  | ||||
| def test_format_options(): | ||||
|     df = DataFrame({"int": [2000, 1], "float": [1.009, None], "str": ["&<", "&~"]}) | ||||
|     ctx = df.style._translate(True, True) | ||||
|  | ||||
|     # test option: na_rep | ||||
|     assert ctx["body"][1][2]["display_value"] == "nan" | ||||
|     with option_context("styler.format.na_rep", "MISSING"): | ||||
|         ctx_with_op = df.style._translate(True, True) | ||||
|         assert ctx_with_op["body"][1][2]["display_value"] == "MISSING" | ||||
|  | ||||
|     # test option: decimal and precision | ||||
|     assert ctx["body"][0][2]["display_value"] == "1.009000" | ||||
|     with option_context("styler.format.decimal", "_"): | ||||
|         ctx_with_op = df.style._translate(True, True) | ||||
|         assert ctx_with_op["body"][0][2]["display_value"] == "1_009000" | ||||
|     with option_context("styler.format.precision", 2): | ||||
|         ctx_with_op = df.style._translate(True, True) | ||||
|         assert ctx_with_op["body"][0][2]["display_value"] == "1.01" | ||||
|  | ||||
|     # test option: thousands | ||||
|     assert ctx["body"][0][1]["display_value"] == "2000" | ||||
|     with option_context("styler.format.thousands", "_"): | ||||
|         ctx_with_op = df.style._translate(True, True) | ||||
|         assert ctx_with_op["body"][0][1]["display_value"] == "2_000" | ||||
|  | ||||
|     # test option: escape | ||||
|     assert ctx["body"][0][3]["display_value"] == "&<" | ||||
|     assert ctx["body"][1][3]["display_value"] == "&~" | ||||
|     with option_context("styler.format.escape", "html"): | ||||
|         ctx_with_op = df.style._translate(True, True) | ||||
|         assert ctx_with_op["body"][0][3]["display_value"] == "&<" | ||||
|     with option_context("styler.format.escape", "latex"): | ||||
|         ctx_with_op = df.style._translate(True, True) | ||||
|         assert ctx_with_op["body"][1][3]["display_value"] == "\\&\\textasciitilde " | ||||
|     with option_context("styler.format.escape", "latex-math"): | ||||
|         ctx_with_op = df.style._translate(True, True) | ||||
|         assert ctx_with_op["body"][1][3]["display_value"] == "\\&\\textasciitilde " | ||||
|  | ||||
|     # test option: formatter | ||||
|     with option_context("styler.format.formatter", {"int": "{:,.2f}"}): | ||||
|         ctx_with_op = df.style._translate(True, True) | ||||
|         assert ctx_with_op["body"][0][1]["display_value"] == "2,000.00" | ||||
|  | ||||
|  | ||||
| def test_precision_zero(df): | ||||
|     styler = Styler(df, precision=0) | ||||
|     ctx = styler._translate(True, True) | ||||
|     assert ctx["body"][0][2]["display_value"] == "-1" | ||||
|     assert ctx["body"][1][2]["display_value"] == "-1" | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "formatter, exp", | ||||
|     [ | ||||
|         (lambda x: f"{x:.3f}", "9.000"), | ||||
|         ("{:.2f}", "9.00"), | ||||
|         ({0: "{:.1f}"}, "9.0"), | ||||
|         (None, "9"), | ||||
|     ], | ||||
| ) | ||||
| def test_formatter_options_validator(formatter, exp): | ||||
|     df = DataFrame([[9]]) | ||||
|     with option_context("styler.format.formatter", formatter): | ||||
|         assert f" {exp} " in df.style.to_latex() | ||||
|  | ||||
|  | ||||
| def test_formatter_options_raises(): | ||||
|     msg = "Value must be an instance of" | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         with option_context("styler.format.formatter", ["bad", "type"]): | ||||
|             DataFrame().style.to_latex() | ||||
|  | ||||
|  | ||||
| def test_1level_multiindex(): | ||||
|     # GH 43383 | ||||
|     midx = MultiIndex.from_product([[1, 2]], names=[""]) | ||||
|     df = DataFrame(-1, index=midx, columns=[0, 1]) | ||||
|     ctx = df.style._translate(True, True) | ||||
|     assert ctx["body"][0][0]["display_value"] == "1" | ||||
|     assert ctx["body"][0][0]["is_visible"] is True | ||||
|     assert ctx["body"][1][0]["display_value"] == "2" | ||||
|     assert ctx["body"][1][0]["is_visible"] is True | ||||
|  | ||||
|  | ||||
| def test_boolean_format(): | ||||
|     # gh 46384: booleans do not collapse to integer representation on display | ||||
|     df = DataFrame([[True, False]]) | ||||
|     ctx = df.style._translate(True, True) | ||||
|     assert ctx["body"][0][1]["display_value"] is True | ||||
|     assert ctx["body"][0][2]["display_value"] is False | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "hide, labels", | ||||
|     [ | ||||
|         (False, [1, 2]), | ||||
|         (True, [1, 2, 3, 4]), | ||||
|     ], | ||||
| ) | ||||
| def test_relabel_raise_length(styler_multi, hide, labels): | ||||
|     if hide: | ||||
|         styler_multi.hide(axis=0, subset=[("X", "x"), ("Y", "y")]) | ||||
|     with pytest.raises(ValueError, match="``labels`` must be of length equal"): | ||||
|         styler_multi.relabel_index(labels=labels) | ||||
|  | ||||
|  | ||||
| def test_relabel_index(styler_multi): | ||||
|     labels = [(1, 2), (3, 4)] | ||||
|     styler_multi.hide(axis=0, subset=[("X", "x"), ("Y", "y")]) | ||||
|     styler_multi.relabel_index(labels=labels) | ||||
|     ctx = styler_multi._translate(True, True) | ||||
|     assert {"value": "X", "display_value": 1}.items() <= ctx["body"][0][0].items() | ||||
|     assert {"value": "y", "display_value": 2}.items() <= ctx["body"][0][1].items() | ||||
|     assert {"value": "Y", "display_value": 3}.items() <= ctx["body"][1][0].items() | ||||
|     assert {"value": "x", "display_value": 4}.items() <= ctx["body"][1][1].items() | ||||
|  | ||||
|  | ||||
| def test_relabel_columns(styler_multi): | ||||
|     labels = [(1, 2), (3, 4)] | ||||
|     styler_multi.hide(axis=1, subset=[("A", "a"), ("B", "b")]) | ||||
|     styler_multi.relabel_index(axis=1, labels=labels) | ||||
|     ctx = styler_multi._translate(True, True) | ||||
|     assert {"value": "A", "display_value": 1}.items() <= ctx["head"][0][3].items() | ||||
|     assert {"value": "B", "display_value": 3}.items() <= ctx["head"][0][4].items() | ||||
|     assert {"value": "b", "display_value": 2}.items() <= ctx["head"][1][3].items() | ||||
|     assert {"value": "a", "display_value": 4}.items() <= ctx["head"][1][4].items() | ||||
|  | ||||
|  | ||||
| def test_relabel_roundtrip(styler): | ||||
|     styler.relabel_index(["{}", "{}"]) | ||||
|     ctx = styler._translate(True, True) | ||||
|     assert {"value": "x", "display_value": "x"}.items() <= ctx["body"][0][0].items() | ||||
|     assert {"value": "y", "display_value": "y"}.items() <= ctx["body"][1][0].items() | ||||
| @ -0,0 +1,218 @@ | ||||
| import numpy as np | ||||
| import pytest | ||||
|  | ||||
| from pandas import ( | ||||
|     NA, | ||||
|     DataFrame, | ||||
|     IndexSlice, | ||||
| ) | ||||
|  | ||||
| pytest.importorskip("jinja2") | ||||
|  | ||||
| from pandas.io.formats.style import Styler | ||||
|  | ||||
|  | ||||
| @pytest.fixture(params=[(None, "float64"), (NA, "Int64")]) | ||||
| def df(request): | ||||
|     # GH 45804 | ||||
|     return DataFrame( | ||||
|         {"A": [0, np.nan, 10], "B": [1, request.param[0], 2]}, dtype=request.param[1] | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def styler(df): | ||||
|     return Styler(df, uuid_len=0) | ||||
|  | ||||
|  | ||||
| def test_highlight_null(styler): | ||||
|     result = styler.highlight_null()._compute().ctx | ||||
|     expected = { | ||||
|         (1, 0): [("background-color", "red")], | ||||
|         (1, 1): [("background-color", "red")], | ||||
|     } | ||||
|     assert result == expected | ||||
|  | ||||
|  | ||||
| def test_highlight_null_subset(styler): | ||||
|     # GH 31345 | ||||
|     result = ( | ||||
|         styler.highlight_null(color="red", subset=["A"]) | ||||
|         .highlight_null(color="green", subset=["B"]) | ||||
|         ._compute() | ||||
|         .ctx | ||||
|     ) | ||||
|     expected = { | ||||
|         (1, 0): [("background-color", "red")], | ||||
|         (1, 1): [("background-color", "green")], | ||||
|     } | ||||
|     assert result == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("f", ["highlight_min", "highlight_max"]) | ||||
| def test_highlight_minmax_basic(df, f): | ||||
|     expected = { | ||||
|         (0, 1): [("background-color", "red")], | ||||
|         # ignores NaN row, | ||||
|         (2, 0): [("background-color", "red")], | ||||
|     } | ||||
|     if f == "highlight_min": | ||||
|         df = -df | ||||
|     result = getattr(df.style, f)(axis=1, color="red")._compute().ctx | ||||
|     assert result == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("f", ["highlight_min", "highlight_max"]) | ||||
| @pytest.mark.parametrize( | ||||
|     "kwargs", | ||||
|     [ | ||||
|         {"axis": None, "color": "red"},  # test axis | ||||
|         {"axis": 0, "subset": ["A"], "color": "red"},  # test subset and ignores NaN | ||||
|         {"axis": None, "props": "background-color: red"},  # test props | ||||
|     ], | ||||
| ) | ||||
| def test_highlight_minmax_ext(df, f, kwargs): | ||||
|     expected = {(2, 0): [("background-color", "red")]} | ||||
|     if f == "highlight_min": | ||||
|         df = -df | ||||
|     result = getattr(df.style, f)(**kwargs)._compute().ctx | ||||
|     assert result == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("f", ["highlight_min", "highlight_max"]) | ||||
| @pytest.mark.parametrize("axis", [None, 0, 1]) | ||||
| def test_highlight_minmax_nulls(f, axis): | ||||
|     # GH 42750 | ||||
|     expected = { | ||||
|         (1, 0): [("background-color", "yellow")], | ||||
|         (1, 1): [("background-color", "yellow")], | ||||
|     } | ||||
|     if axis == 1: | ||||
|         expected.update({(2, 1): [("background-color", "yellow")]}) | ||||
|  | ||||
|     if f == "highlight_max": | ||||
|         df = DataFrame({"a": [NA, 1, None], "b": [np.nan, 1, -1]}) | ||||
|     else: | ||||
|         df = DataFrame({"a": [NA, -1, None], "b": [np.nan, -1, 1]}) | ||||
|  | ||||
|     result = getattr(df.style, f)(axis=axis)._compute().ctx | ||||
|     assert result == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "kwargs", | ||||
|     [ | ||||
|         {"left": 0, "right": 1},  # test basic range | ||||
|         {"left": 0, "right": 1, "props": "background-color: yellow"},  # test props | ||||
|         {"left": -100, "right": 100, "subset": IndexSlice[[0, 1], :]},  # test subset | ||||
|         {"left": 0, "subset": IndexSlice[[0, 1], :]},  # test no right | ||||
|         {"right": 1},  # test no left | ||||
|         {"left": [0, 0, 11], "axis": 0},  # test left as sequence | ||||
|         {"left": DataFrame({"A": [0, 0, 11], "B": [1, 1, 11]}), "axis": None},  # axis | ||||
|         {"left": 0, "right": [0, 1], "axis": 1},  # test sequence right | ||||
|     ], | ||||
| ) | ||||
| def test_highlight_between(styler, kwargs): | ||||
|     expected = { | ||||
|         (0, 0): [("background-color", "yellow")], | ||||
|         (0, 1): [("background-color", "yellow")], | ||||
|     } | ||||
|     result = styler.highlight_between(**kwargs)._compute().ctx | ||||
|     assert result == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "arg, map, axis", | ||||
|     [ | ||||
|         ("left", [1, 2], 0),  # 0 axis has 3 elements not 2 | ||||
|         ("left", [1, 2, 3], 1),  # 1 axis has 2 elements not 3 | ||||
|         ("left", np.array([[1, 2], [1, 2]]), None),  # df is (2,3) not (2,2) | ||||
|         ("right", [1, 2], 0),  # same tests as above for 'right' not 'left' | ||||
|         ("right", [1, 2, 3], 1),  # .. | ||||
|         ("right", np.array([[1, 2], [1, 2]]), None),  # .. | ||||
|     ], | ||||
| ) | ||||
| def test_highlight_between_raises(arg, styler, map, axis): | ||||
|     msg = f"supplied '{arg}' is not correct shape" | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         styler.highlight_between(**{arg: map, "axis": axis})._compute() | ||||
|  | ||||
|  | ||||
| def test_highlight_between_raises2(styler): | ||||
|     msg = "values can be 'both', 'left', 'right', or 'neither'" | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         styler.highlight_between(inclusive="badstring")._compute() | ||||
|  | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         styler.highlight_between(inclusive=1)._compute() | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "inclusive, expected", | ||||
|     [ | ||||
|         ( | ||||
|             "both", | ||||
|             { | ||||
|                 (0, 0): [("background-color", "yellow")], | ||||
|                 (0, 1): [("background-color", "yellow")], | ||||
|             }, | ||||
|         ), | ||||
|         ("neither", {}), | ||||
|         ("left", {(0, 0): [("background-color", "yellow")]}), | ||||
|         ("right", {(0, 1): [("background-color", "yellow")]}), | ||||
|     ], | ||||
| ) | ||||
| def test_highlight_between_inclusive(styler, inclusive, expected): | ||||
|     kwargs = {"left": 0, "right": 1, "subset": IndexSlice[[0, 1], :]} | ||||
|     result = styler.highlight_between(**kwargs, inclusive=inclusive)._compute() | ||||
|     assert result.ctx == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "kwargs", | ||||
|     [ | ||||
|         {"q_left": 0.5, "q_right": 1, "axis": 0},  # base case | ||||
|         {"q_left": 0.5, "q_right": 1, "axis": None},  # test axis | ||||
|         {"q_left": 0, "q_right": 1, "subset": IndexSlice[2, :]},  # test subset | ||||
|         {"q_left": 0.5, "axis": 0},  # test no high | ||||
|         {"q_right": 1, "subset": IndexSlice[2, :], "axis": 1},  # test no low | ||||
|         {"q_left": 0.5, "axis": 0, "props": "background-color: yellow"},  # tst prop | ||||
|     ], | ||||
| ) | ||||
| def test_highlight_quantile(styler, kwargs): | ||||
|     expected = { | ||||
|         (2, 0): [("background-color", "yellow")], | ||||
|         (2, 1): [("background-color", "yellow")], | ||||
|     } | ||||
|     result = styler.highlight_quantile(**kwargs)._compute().ctx | ||||
|     assert result == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "f,kwargs", | ||||
|     [ | ||||
|         ("highlight_min", {"axis": 1, "subset": IndexSlice[1, :]}), | ||||
|         ("highlight_max", {"axis": 0, "subset": [0]}), | ||||
|         ("highlight_quantile", {"axis": None, "q_left": 0.6, "q_right": 0.8}), | ||||
|         ("highlight_between", {"subset": [0]}), | ||||
|     ], | ||||
| ) | ||||
| @pytest.mark.parametrize( | ||||
|     "df", | ||||
|     [ | ||||
|         DataFrame([[0, 10], [20, 30]], dtype=int), | ||||
|         DataFrame([[0, 10], [20, 30]], dtype=float), | ||||
|         DataFrame([[0, 10], [20, 30]], dtype="datetime64[ns]"), | ||||
|         DataFrame([[0, 10], [20, 30]], dtype=str), | ||||
|         DataFrame([[0, 10], [20, 30]], dtype="timedelta64[ns]"), | ||||
|     ], | ||||
| ) | ||||
| def test_all_highlight_dtypes(f, kwargs, df): | ||||
|     if f == "highlight_quantile" and isinstance(df.iloc[0, 0], (str)): | ||||
|         return None  # quantile incompatible with str | ||||
|     if f == "highlight_between": | ||||
|         kwargs["left"] = df.iloc[1, 0]  # set the range low for testing | ||||
|  | ||||
|     expected = {(1, 0): [("background-color", "yellow")]} | ||||
|     result = getattr(df.style, f)(**kwargs)._compute().ctx | ||||
|     assert result == expected | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -0,0 +1,335 @@ | ||||
| import gc | ||||
|  | ||||
| import numpy as np | ||||
| import pytest | ||||
|  | ||||
| from pandas import ( | ||||
|     DataFrame, | ||||
|     IndexSlice, | ||||
|     Series, | ||||
| ) | ||||
|  | ||||
| pytest.importorskip("matplotlib") | ||||
| pytest.importorskip("jinja2") | ||||
|  | ||||
| import matplotlib as mpl | ||||
|  | ||||
| from pandas.io.formats.style import Styler | ||||
|  | ||||
|  | ||||
| @pytest.fixture(autouse=True) | ||||
| def mpl_cleanup(): | ||||
|     # matplotlib/testing/decorators.py#L24 | ||||
|     # 1) Resets units registry | ||||
|     # 2) Resets rc_context | ||||
|     # 3) Closes all figures | ||||
|     mpl = pytest.importorskip("matplotlib") | ||||
|     mpl_units = pytest.importorskip("matplotlib.units") | ||||
|     plt = pytest.importorskip("matplotlib.pyplot") | ||||
|     orig_units_registry = mpl_units.registry.copy() | ||||
|     with mpl.rc_context(): | ||||
|         mpl.use("template") | ||||
|         yield | ||||
|     mpl_units.registry.clear() | ||||
|     mpl_units.registry.update(orig_units_registry) | ||||
|     plt.close("all") | ||||
|     # https://matplotlib.org/stable/users/prev_whats_new/whats_new_3.6.0.html#garbage-collection-is-no-longer-run-on-figure-close  # noqa: E501 | ||||
|     gc.collect(1) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def df(): | ||||
|     return DataFrame([[1, 2], [2, 4]], columns=["A", "B"]) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def styler(df): | ||||
|     return Styler(df, uuid_len=0) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def df_blank(): | ||||
|     return DataFrame([[0, 0], [0, 0]], columns=["A", "B"], index=["X", "Y"]) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def styler_blank(df_blank): | ||||
|     return Styler(df_blank, uuid_len=0) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("f", ["background_gradient", "text_gradient"]) | ||||
| def test_function_gradient(styler, f): | ||||
|     for c_map in [None, "YlOrRd"]: | ||||
|         result = getattr(styler, f)(cmap=c_map)._compute().ctx | ||||
|         assert all("#" in x[0][1] for x in result.values()) | ||||
|         assert result[(0, 0)] == result[(0, 1)] | ||||
|         assert result[(1, 0)] == result[(1, 1)] | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("f", ["background_gradient", "text_gradient"]) | ||||
| def test_background_gradient_color(styler, f): | ||||
|     result = getattr(styler, f)(subset=IndexSlice[1, "A"])._compute().ctx | ||||
|     if f == "background_gradient": | ||||
|         assert result[(1, 0)] == [("background-color", "#fff7fb"), ("color", "#000000")] | ||||
|     elif f == "text_gradient": | ||||
|         assert result[(1, 0)] == [("color", "#fff7fb")] | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "axis, expected", | ||||
|     [ | ||||
|         (0, ["low", "low", "high", "high"]), | ||||
|         (1, ["low", "high", "low", "high"]), | ||||
|         (None, ["low", "mid", "mid", "high"]), | ||||
|     ], | ||||
| ) | ||||
| @pytest.mark.parametrize("f", ["background_gradient", "text_gradient"]) | ||||
| def test_background_gradient_axis(styler, axis, expected, f): | ||||
|     if f == "background_gradient": | ||||
|         colors = { | ||||
|             "low": [("background-color", "#f7fbff"), ("color", "#000000")], | ||||
|             "mid": [("background-color", "#abd0e6"), ("color", "#000000")], | ||||
|             "high": [("background-color", "#08306b"), ("color", "#f1f1f1")], | ||||
|         } | ||||
|     elif f == "text_gradient": | ||||
|         colors = { | ||||
|             "low": [("color", "#f7fbff")], | ||||
|             "mid": [("color", "#abd0e6")], | ||||
|             "high": [("color", "#08306b")], | ||||
|         } | ||||
|     result = getattr(styler, f)(cmap="Blues", axis=axis)._compute().ctx | ||||
|     for i, cell in enumerate([(0, 0), (0, 1), (1, 0), (1, 1)]): | ||||
|         assert result[cell] == colors[expected[i]] | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "cmap, expected", | ||||
|     [ | ||||
|         ( | ||||
|             "PuBu", | ||||
|             { | ||||
|                 (4, 5): [("background-color", "#86b0d3"), ("color", "#000000")], | ||||
|                 (4, 6): [("background-color", "#83afd3"), ("color", "#f1f1f1")], | ||||
|             }, | ||||
|         ), | ||||
|         ( | ||||
|             "YlOrRd", | ||||
|             { | ||||
|                 (4, 8): [("background-color", "#fd913e"), ("color", "#000000")], | ||||
|                 (4, 9): [("background-color", "#fd8f3d"), ("color", "#f1f1f1")], | ||||
|             }, | ||||
|         ), | ||||
|         ( | ||||
|             None, | ||||
|             { | ||||
|                 (7, 0): [("background-color", "#48c16e"), ("color", "#f1f1f1")], | ||||
|                 (7, 1): [("background-color", "#4cc26c"), ("color", "#000000")], | ||||
|             }, | ||||
|         ), | ||||
|     ], | ||||
| ) | ||||
| def test_text_color_threshold(cmap, expected): | ||||
|     # GH 39888 | ||||
|     df = DataFrame(np.arange(100).reshape(10, 10)) | ||||
|     result = df.style.background_gradient(cmap=cmap, axis=None)._compute().ctx | ||||
|     for k in expected.keys(): | ||||
|         assert result[k] == expected[k] | ||||
|  | ||||
|  | ||||
| def test_background_gradient_vmin_vmax(): | ||||
|     # GH 12145 | ||||
|     df = DataFrame(range(5)) | ||||
|     ctx = df.style.background_gradient(vmin=1, vmax=3)._compute().ctx | ||||
|     assert ctx[(0, 0)] == ctx[(1, 0)] | ||||
|     assert ctx[(4, 0)] == ctx[(3, 0)] | ||||
|  | ||||
|  | ||||
| def test_background_gradient_int64(): | ||||
|     # GH 28869 | ||||
|     df1 = Series(range(3)).to_frame() | ||||
|     df2 = Series(range(3), dtype="Int64").to_frame() | ||||
|     ctx1 = df1.style.background_gradient()._compute().ctx | ||||
|     ctx2 = df2.style.background_gradient()._compute().ctx | ||||
|     assert ctx2[(0, 0)] == ctx1[(0, 0)] | ||||
|     assert ctx2[(1, 0)] == ctx1[(1, 0)] | ||||
|     assert ctx2[(2, 0)] == ctx1[(2, 0)] | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "axis, gmap, expected", | ||||
|     [ | ||||
|         ( | ||||
|             0, | ||||
|             [1, 2], | ||||
|             { | ||||
|                 (0, 0): [("background-color", "#fff7fb"), ("color", "#000000")], | ||||
|                 (1, 0): [("background-color", "#023858"), ("color", "#f1f1f1")], | ||||
|                 (0, 1): [("background-color", "#fff7fb"), ("color", "#000000")], | ||||
|                 (1, 1): [("background-color", "#023858"), ("color", "#f1f1f1")], | ||||
|             }, | ||||
|         ), | ||||
|         ( | ||||
|             1, | ||||
|             [1, 2], | ||||
|             { | ||||
|                 (0, 0): [("background-color", "#fff7fb"), ("color", "#000000")], | ||||
|                 (1, 0): [("background-color", "#fff7fb"), ("color", "#000000")], | ||||
|                 (0, 1): [("background-color", "#023858"), ("color", "#f1f1f1")], | ||||
|                 (1, 1): [("background-color", "#023858"), ("color", "#f1f1f1")], | ||||
|             }, | ||||
|         ), | ||||
|         ( | ||||
|             None, | ||||
|             np.array([[2, 1], [1, 2]]), | ||||
|             { | ||||
|                 (0, 0): [("background-color", "#023858"), ("color", "#f1f1f1")], | ||||
|                 (1, 0): [("background-color", "#fff7fb"), ("color", "#000000")], | ||||
|                 (0, 1): [("background-color", "#fff7fb"), ("color", "#000000")], | ||||
|                 (1, 1): [("background-color", "#023858"), ("color", "#f1f1f1")], | ||||
|             }, | ||||
|         ), | ||||
|     ], | ||||
| ) | ||||
| def test_background_gradient_gmap_array(styler_blank, axis, gmap, expected): | ||||
|     # tests when gmap is given as a sequence and converted to ndarray | ||||
|     result = styler_blank.background_gradient(axis=axis, gmap=gmap)._compute().ctx | ||||
|     assert result == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "gmap, axis", [([1, 2, 3], 0), ([1, 2], 1), (np.array([[1, 2], [1, 2]]), None)] | ||||
| ) | ||||
| def test_background_gradient_gmap_array_raises(gmap, axis): | ||||
|     # test when gmap as converted ndarray is bad shape | ||||
|     df = DataFrame([[0, 0, 0], [0, 0, 0]]) | ||||
|     msg = "supplied 'gmap' is not correct shape" | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         df.style.background_gradient(gmap=gmap, axis=axis)._compute() | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "gmap", | ||||
|     [ | ||||
|         DataFrame(  # reverse the columns | ||||
|             [[2, 1], [1, 2]], columns=["B", "A"], index=["X", "Y"] | ||||
|         ), | ||||
|         DataFrame(  # reverse the index | ||||
|             [[2, 1], [1, 2]], columns=["A", "B"], index=["Y", "X"] | ||||
|         ), | ||||
|         DataFrame(  # reverse the index and columns | ||||
|             [[1, 2], [2, 1]], columns=["B", "A"], index=["Y", "X"] | ||||
|         ), | ||||
|         DataFrame(  # add unnecessary columns | ||||
|             [[1, 2, 3], [2, 1, 3]], columns=["A", "B", "C"], index=["X", "Y"] | ||||
|         ), | ||||
|         DataFrame(  # add unnecessary index | ||||
|             [[1, 2], [2, 1], [3, 3]], columns=["A", "B"], index=["X", "Y", "Z"] | ||||
|         ), | ||||
|     ], | ||||
| ) | ||||
| @pytest.mark.parametrize( | ||||
|     "subset, exp_gmap",  # exp_gmap is underlying map DataFrame should conform to | ||||
|     [ | ||||
|         (None, [[1, 2], [2, 1]]), | ||||
|         (["A"], [[1], [2]]),  # slice only column "A" in data and gmap | ||||
|         (["B", "A"], [[2, 1], [1, 2]]),  # reverse the columns in data | ||||
|         (IndexSlice["X", :], [[1, 2]]),  # slice only index "X" in data and gmap | ||||
|         (IndexSlice[["Y", "X"], :], [[2, 1], [1, 2]]),  # reverse the index in data | ||||
|     ], | ||||
| ) | ||||
| def test_background_gradient_gmap_dataframe_align(styler_blank, gmap, subset, exp_gmap): | ||||
|     # test gmap given as DataFrame that it aligns to the data including subset | ||||
|     expected = styler_blank.background_gradient(axis=None, gmap=exp_gmap, subset=subset) | ||||
|     result = styler_blank.background_gradient(axis=None, gmap=gmap, subset=subset) | ||||
|     assert expected._compute().ctx == result._compute().ctx | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "gmap, axis, exp_gmap", | ||||
|     [ | ||||
|         (Series([2, 1], index=["Y", "X"]), 0, [[1, 1], [2, 2]]),  # revrse the index | ||||
|         (Series([2, 1], index=["B", "A"]), 1, [[1, 2], [1, 2]]),  # revrse the cols | ||||
|         (Series([1, 2, 3], index=["X", "Y", "Z"]), 0, [[1, 1], [2, 2]]),  # add idx | ||||
|         (Series([1, 2, 3], index=["A", "B", "C"]), 1, [[1, 2], [1, 2]]),  # add col | ||||
|     ], | ||||
| ) | ||||
| def test_background_gradient_gmap_series_align(styler_blank, gmap, axis, exp_gmap): | ||||
|     # test gmap given as Series that it aligns to the data including subset | ||||
|     expected = styler_blank.background_gradient(axis=None, gmap=exp_gmap)._compute() | ||||
|     result = styler_blank.background_gradient(axis=axis, gmap=gmap)._compute() | ||||
|     assert expected.ctx == result.ctx | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "gmap, axis", | ||||
|     [ | ||||
|         (DataFrame([[1, 2], [2, 1]], columns=["A", "B"], index=["X", "Y"]), 1), | ||||
|         (DataFrame([[1, 2], [2, 1]], columns=["A", "B"], index=["X", "Y"]), 0), | ||||
|     ], | ||||
| ) | ||||
| def test_background_gradient_gmap_wrong_dataframe(styler_blank, gmap, axis): | ||||
|     # test giving a gmap in DataFrame but with wrong axis | ||||
|     msg = "'gmap' is a DataFrame but underlying data for operations is a Series" | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         styler_blank.background_gradient(gmap=gmap, axis=axis)._compute() | ||||
|  | ||||
|  | ||||
| def test_background_gradient_gmap_wrong_series(styler_blank): | ||||
|     # test giving a gmap in Series form but with wrong axis | ||||
|     msg = "'gmap' is a Series but underlying data for operations is a DataFrame" | ||||
|     gmap = Series([1, 2], index=["X", "Y"]) | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         styler_blank.background_gradient(gmap=gmap, axis=None)._compute() | ||||
|  | ||||
|  | ||||
| def test_background_gradient_nullable_dtypes(): | ||||
|     # GH 50712 | ||||
|     df1 = DataFrame([[1], [0], [np.nan]], dtype=float) | ||||
|     df2 = DataFrame([[1], [0], [None]], dtype="Int64") | ||||
|  | ||||
|     ctx1 = df1.style.background_gradient()._compute().ctx | ||||
|     ctx2 = df2.style.background_gradient()._compute().ctx | ||||
|     assert ctx1 == ctx2 | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "cmap", | ||||
|     ["PuBu", mpl.colormaps["PuBu"]], | ||||
| ) | ||||
| def test_bar_colormap(cmap): | ||||
|     data = DataFrame([[1, 2], [3, 4]]) | ||||
|     ctx = data.style.bar(cmap=cmap, axis=None)._compute().ctx | ||||
|     pubu_colors = { | ||||
|         (0, 0): "#d0d1e6", | ||||
|         (1, 0): "#056faf", | ||||
|         (0, 1): "#73a9cf", | ||||
|         (1, 1): "#023858", | ||||
|     } | ||||
|     for k, v in pubu_colors.items(): | ||||
|         assert v in ctx[k][1][1] | ||||
|  | ||||
|  | ||||
| def test_bar_color_raises(df): | ||||
|     msg = "`color` must be string or list or tuple of 2 strings" | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         df.style.bar(color={"a", "b"}).to_html() | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         df.style.bar(color=["a", "b", "c"]).to_html() | ||||
|  | ||||
|     msg = "`color` and `cmap` cannot both be given" | ||||
|     with pytest.raises(ValueError, match=msg): | ||||
|         df.style.bar(color="something", cmap="something else").to_html() | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "plot_method", | ||||
|     ["scatter", "hexbin"], | ||||
| ) | ||||
| def test_pass_colormap_instance(df, plot_method): | ||||
|     # https://github.com/pandas-dev/pandas/issues/49374 | ||||
|     cmap = mpl.colors.ListedColormap([[1, 1, 1], [0, 0, 0]]) | ||||
|     df["c"] = df.A + df.B | ||||
|     kwargs = {"x": "A", "y": "B", "c": "c", "colormap": cmap} | ||||
|     if plot_method == "hexbin": | ||||
|         kwargs["C"] = kwargs.pop("c") | ||||
|     getattr(df.plot, plot_method)(**kwargs) | ||||
| @ -0,0 +1,140 @@ | ||||
| from textwrap import dedent | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| from pandas import ( | ||||
|     DataFrame, | ||||
|     IndexSlice, | ||||
| ) | ||||
|  | ||||
| pytest.importorskip("jinja2") | ||||
|  | ||||
| from pandas.io.formats.style import Styler | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def df(): | ||||
|     return DataFrame( | ||||
|         [[1, 2, 3], [4, 5, 6], [7, 8, 9]], | ||||
|         index=["i", "j", "j"], | ||||
|         columns=["c", "d", "d"], | ||||
|         dtype=float, | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def styler(df): | ||||
|     return Styler(df, uuid_len=0) | ||||
|  | ||||
|  | ||||
| def test_format_non_unique(df): | ||||
|     # GH 41269 | ||||
|  | ||||
|     # test dict | ||||
|     html = df.style.format({"d": "{:.1f}"}).to_html() | ||||
|     for val in ["1.000000<", "4.000000<", "7.000000<"]: | ||||
|         assert val in html | ||||
|     for val in ["2.0<", "3.0<", "5.0<", "6.0<", "8.0<", "9.0<"]: | ||||
|         assert val in html | ||||
|  | ||||
|     # test subset | ||||
|     html = df.style.format(precision=1, subset=IndexSlice["j", "d"]).to_html() | ||||
|     for val in ["1.000000<", "4.000000<", "7.000000<", "2.000000<", "3.000000<"]: | ||||
|         assert val in html | ||||
|     for val in ["5.0<", "6.0<", "8.0<", "9.0<"]: | ||||
|         assert val in html | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("func", ["apply", "map"]) | ||||
| def test_apply_map_non_unique_raises(df, func): | ||||
|     # GH 41269 | ||||
|     if func == "apply": | ||||
|         op = lambda s: ["color: red;"] * len(s) | ||||
|     else: | ||||
|         op = lambda v: "color: red;" | ||||
|  | ||||
|     with pytest.raises(KeyError, match="`Styler.apply` and `.map` are not"): | ||||
|         getattr(df.style, func)(op)._compute() | ||||
|  | ||||
|  | ||||
| def test_table_styles_dict_non_unique_index(styler): | ||||
|     styles = styler.set_table_styles( | ||||
|         {"j": [{"selector": "td", "props": "a: v;"}]}, axis=1 | ||||
|     ).table_styles | ||||
|     assert styles == [ | ||||
|         {"selector": "td.row1", "props": [("a", "v")]}, | ||||
|         {"selector": "td.row2", "props": [("a", "v")]}, | ||||
|     ] | ||||
|  | ||||
|  | ||||
| def test_table_styles_dict_non_unique_columns(styler): | ||||
|     styles = styler.set_table_styles( | ||||
|         {"d": [{"selector": "td", "props": "a: v;"}]}, axis=0 | ||||
|     ).table_styles | ||||
|     assert styles == [ | ||||
|         {"selector": "td.col1", "props": [("a", "v")]}, | ||||
|         {"selector": "td.col2", "props": [("a", "v")]}, | ||||
|     ] | ||||
|  | ||||
|  | ||||
| def test_tooltips_non_unique_raises(styler): | ||||
|     # ttips has unique keys | ||||
|     ttips = DataFrame([["1", "2"], ["3", "4"]], columns=["c", "d"], index=["a", "b"]) | ||||
|     styler.set_tooltips(ttips=ttips)  # OK | ||||
|  | ||||
|     # ttips has non-unique columns | ||||
|     ttips = DataFrame([["1", "2"], ["3", "4"]], columns=["c", "c"], index=["a", "b"]) | ||||
|     with pytest.raises(KeyError, match="Tooltips render only if `ttips` has unique"): | ||||
|         styler.set_tooltips(ttips=ttips) | ||||
|  | ||||
|     # ttips has non-unique index | ||||
|     ttips = DataFrame([["1", "2"], ["3", "4"]], columns=["c", "d"], index=["a", "a"]) | ||||
|     with pytest.raises(KeyError, match="Tooltips render only if `ttips` has unique"): | ||||
|         styler.set_tooltips(ttips=ttips) | ||||
|  | ||||
|  | ||||
| def test_set_td_classes_non_unique_raises(styler): | ||||
|     # classes has unique keys | ||||
|     classes = DataFrame([["1", "2"], ["3", "4"]], columns=["c", "d"], index=["a", "b"]) | ||||
|     styler.set_td_classes(classes=classes)  # OK | ||||
|  | ||||
|     # classes has non-unique columns | ||||
|     classes = DataFrame([["1", "2"], ["3", "4"]], columns=["c", "c"], index=["a", "b"]) | ||||
|     with pytest.raises(KeyError, match="Classes render only if `classes` has unique"): | ||||
|         styler.set_td_classes(classes=classes) | ||||
|  | ||||
|     # classes has non-unique index | ||||
|     classes = DataFrame([["1", "2"], ["3", "4"]], columns=["c", "d"], index=["a", "a"]) | ||||
|     with pytest.raises(KeyError, match="Classes render only if `classes` has unique"): | ||||
|         styler.set_td_classes(classes=classes) | ||||
|  | ||||
|  | ||||
| def test_hide_columns_non_unique(styler): | ||||
|     ctx = styler.hide(["d"], axis="columns")._translate(True, True) | ||||
|  | ||||
|     assert ctx["head"][0][1]["display_value"] == "c" | ||||
|     assert ctx["head"][0][1]["is_visible"] is True | ||||
|  | ||||
|     assert ctx["head"][0][2]["display_value"] == "d" | ||||
|     assert ctx["head"][0][2]["is_visible"] is False | ||||
|  | ||||
|     assert ctx["head"][0][3]["display_value"] == "d" | ||||
|     assert ctx["head"][0][3]["is_visible"] is False | ||||
|  | ||||
|     assert ctx["body"][0][1]["is_visible"] is True | ||||
|     assert ctx["body"][0][2]["is_visible"] is False | ||||
|     assert ctx["body"][0][3]["is_visible"] is False | ||||
|  | ||||
|  | ||||
| def test_latex_non_unique(styler): | ||||
|     result = styler.to_latex() | ||||
|     assert result == dedent( | ||||
|         """\ | ||||
|         \\begin{tabular}{lrrr} | ||||
|          & c & d & d \\\\ | ||||
|         i & 1.000000 & 2.000000 & 3.000000 \\\\ | ||||
|         j & 4.000000 & 5.000000 & 6.000000 \\\\ | ||||
|         j & 7.000000 & 8.000000 & 9.000000 \\\\ | ||||
|         \\end{tabular} | ||||
|     """ | ||||
|     ) | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -0,0 +1,96 @@ | ||||
| from textwrap import dedent | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| from pandas import ( | ||||
|     DataFrame, | ||||
|     Series, | ||||
| ) | ||||
|  | ||||
| pytest.importorskip("jinja2") | ||||
| from pandas.io.formats.style import Styler | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def df(): | ||||
|     return DataFrame( | ||||
|         {"A": [0, 1], "B": [-0.61, -1.22], "C": Series(["ab", "cd"], dtype=object)} | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def styler(df): | ||||
|     return Styler(df, uuid_len=0, precision=2) | ||||
|  | ||||
|  | ||||
| def test_basic_string(styler): | ||||
|     result = styler.to_string() | ||||
|     expected = dedent( | ||||
|         """\ | ||||
|      A B C | ||||
|     0 0 -0.61 ab | ||||
|     1 1 -1.22 cd | ||||
|     """ | ||||
|     ) | ||||
|     assert result == expected | ||||
|  | ||||
|  | ||||
| def test_string_delimiter(styler): | ||||
|     result = styler.to_string(delimiter=";") | ||||
|     expected = dedent( | ||||
|         """\ | ||||
|     ;A;B;C | ||||
|     0;0;-0.61;ab | ||||
|     1;1;-1.22;cd | ||||
|     """ | ||||
|     ) | ||||
|     assert result == expected | ||||
|  | ||||
|  | ||||
| def test_concat(styler): | ||||
|     result = styler.concat(styler.data.agg(["sum"]).style).to_string() | ||||
|     expected = dedent( | ||||
|         """\ | ||||
|      A B C | ||||
|     0 0 -0.61 ab | ||||
|     1 1 -1.22 cd | ||||
|     sum 1 -1.830000 abcd | ||||
|     """ | ||||
|     ) | ||||
|     assert result == expected | ||||
|  | ||||
|  | ||||
| def test_concat_recursion(styler): | ||||
|     df = styler.data | ||||
|     styler1 = styler | ||||
|     styler2 = Styler(df.agg(["sum"]), uuid_len=0, precision=3) | ||||
|     styler3 = Styler(df.agg(["sum"]), uuid_len=0, precision=4) | ||||
|     result = styler1.concat(styler2.concat(styler3)).to_string() | ||||
|     expected = dedent( | ||||
|         """\ | ||||
|      A B C | ||||
|     0 0 -0.61 ab | ||||
|     1 1 -1.22 cd | ||||
|     sum 1 -1.830 abcd | ||||
|     sum 1 -1.8300 abcd | ||||
|     """ | ||||
|     ) | ||||
|     assert result == expected | ||||
|  | ||||
|  | ||||
| def test_concat_chain(styler): | ||||
|     df = styler.data | ||||
|     styler1 = styler | ||||
|     styler2 = Styler(df.agg(["sum"]), uuid_len=0, precision=3) | ||||
|     styler3 = Styler(df.agg(["sum"]), uuid_len=0, precision=4) | ||||
|     result = styler1.concat(styler2).concat(styler3).to_string() | ||||
|     expected = dedent( | ||||
|         """\ | ||||
|      A B C | ||||
|     0 0 -0.61 ab | ||||
|     1 1 -1.22 cd | ||||
|     sum 1 -1.830 abcd | ||||
|     sum 1 -1.8300 abcd | ||||
|     """ | ||||
|     ) | ||||
|     assert result == expected | ||||
| @ -0,0 +1,85 @@ | ||||
| import numpy as np | ||||
| import pytest | ||||
|  | ||||
| from pandas import DataFrame | ||||
|  | ||||
| pytest.importorskip("jinja2") | ||||
| from pandas.io.formats.style import Styler | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def df(): | ||||
|     return DataFrame( | ||||
|         data=[[0, 1, 2], [3, 4, 5], [6, 7, 8]], | ||||
|         columns=["A", "B", "C"], | ||||
|         index=["x", "y", "z"], | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def styler(df): | ||||
|     return Styler(df, uuid_len=0) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     "ttips", | ||||
|     [ | ||||
|         DataFrame(  # Test basic reindex and ignoring blank | ||||
|             data=[["Min", "Max"], [np.nan, ""]], | ||||
|             columns=["A", "C"], | ||||
|             index=["x", "y"], | ||||
|         ), | ||||
|         DataFrame(  # Test non-referenced columns, reversed col names, short index | ||||
|             data=[["Max", "Min", "Bad-Col"]], columns=["C", "A", "D"], index=["x"] | ||||
|         ), | ||||
|     ], | ||||
| ) | ||||
| def test_tooltip_render(ttips, styler): | ||||
|     # GH 21266 | ||||
|     result = styler.set_tooltips(ttips).to_html() | ||||
|  | ||||
|     # test tooltip table level class | ||||
|     assert "#T_ .pd-t {\n  visibility: hidden;\n" in result | ||||
|  | ||||
|     # test 'Min' tooltip added | ||||
|     assert "#T_ #T__row0_col0:hover .pd-t {\n  visibility: visible;\n}" in result | ||||
|     assert '#T_ #T__row0_col0 .pd-t::after {\n  content: "Min";\n}' in result | ||||
|     assert 'class="data row0 col0" >0<span class="pd-t"></span></td>' in result | ||||
|  | ||||
|     # test 'Max' tooltip added | ||||
|     assert "#T_ #T__row0_col2:hover .pd-t {\n  visibility: visible;\n}" in result | ||||
|     assert '#T_ #T__row0_col2 .pd-t::after {\n  content: "Max";\n}' in result | ||||
|     assert 'class="data row0 col2" >2<span class="pd-t"></span></td>' in result | ||||
|  | ||||
|     # test Nan, empty string and bad column ignored | ||||
|     assert "#T_ #T__row1_col0:hover .pd-t {\n  visibility: visible;\n}" not in result | ||||
|     assert "#T_ #T__row1_col1:hover .pd-t {\n  visibility: visible;\n}" not in result | ||||
|     assert "#T_ #T__row0_col1:hover .pd-t {\n  visibility: visible;\n}" not in result | ||||
|     assert "#T_ #T__row1_col2:hover .pd-t {\n  visibility: visible;\n}" not in result | ||||
|     assert "Bad-Col" not in result | ||||
|  | ||||
|  | ||||
| def test_tooltip_ignored(styler): | ||||
|     # GH 21266 | ||||
|     result = styler.to_html()  # no set_tooltips() creates no <span> | ||||
|     assert '<style type="text/css">\n</style>' in result | ||||
|     assert '<span class="pd-t"></span>' not in result | ||||
|  | ||||
|  | ||||
| def test_tooltip_css_class(styler): | ||||
|     # GH 21266 | ||||
|     result = styler.set_tooltips( | ||||
|         DataFrame([["tooltip"]], index=["x"], columns=["A"]), | ||||
|         css_class="other-class", | ||||
|         props=[("color", "green")], | ||||
|     ).to_html() | ||||
|     assert "#T_ .other-class {\n  color: green;\n" in result | ||||
|     assert '#T_ #T__row0_col0 .other-class::after {\n  content: "tooltip";\n' in result | ||||
|  | ||||
|     # GH 39563 | ||||
|     result = styler.set_tooltips(  # set_tooltips overwrites previous | ||||
|         DataFrame([["tooltip"]], index=["x"], columns=["A"]), | ||||
|         css_class="another-class", | ||||
|         props="color:green;color:red;", | ||||
|     ).to_html() | ||||
|     assert "#T_ .another-class {\n  color: green;\n  color: red;\n}" in result | ||||
		Reference in New Issue
	
	Block a user