From b7edf809373855e7b72b0e894da847cf14c7d4bb Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Mon, 13 Sep 2021 18:19:11 +0200 Subject: [PATCH 1/5] add base changes --- doc/source/reference/style.rst | 1 + pandas/io/formats/style.py | 2 + pandas/io/formats/style_render.py | 98 ++++++++++++++++++++++ pandas/io/formats/templates/html_table.tpl | 4 +- 4 files changed, 103 insertions(+), 2 deletions(-) diff --git a/doc/source/reference/style.rst b/doc/source/reference/style.rst index 11d57e66c4773..e67813084e389 100644 --- a/doc/source/reference/style.rst +++ b/doc/source/reference/style.rst @@ -39,6 +39,7 @@ Style application Styler.apply_index Styler.applymap_index Styler.format + Styler.format_index Styler.hide_index Styler.hide_columns Styler.set_td_classes diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index ed142017a066b..f4cdb8e1d7173 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1184,6 +1184,8 @@ def _copy(self, deepcopy: bool = False) -> Styler: ] deep = [ # nested lists or dicts "_display_funcs", + "_display_funcs_index", + "_display_funcs_columns", "hidden_rows", "hidden_columns", "ctx", diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index a63396b5637bc..1bc5721e6b3c5 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -117,6 +117,12 @@ def __init__( self._display_funcs: DefaultDict[ # maps (row, col) -> format func tuple[int, int], Callable[[Any], str] ] = defaultdict(lambda: partial(_default_formatter, precision=precision)) + self._display_funcs_index: DefaultDict[ # maps (row, level) -> format func + tuple[int, int], Callable[[Any], str] + ] = defaultdict(lambda: partial(_default_formatter, precision=precision)) + self._display_funcs_columns: DefaultDict[ # maps (level, col) -> format func + tuple[int, int], Callable[[Any], str] + ] = defaultdict(lambda: partial(_default_formatter, precision=precision)) def _render_html( self, @@ -377,6 +383,7 @@ def _translate_header( f"{col_heading_class} level{r} col{c}", value, _is_visible(c, r, col_lengths), + display_value=self._display_funcs_columns[(r, c)](value), attributes=( f'colspan="{col_lengths.get((r, c), 0)}"' if col_lengths.get((r, c), 0) > 1 @@ -535,6 +542,7 @@ def _translate_body( f"{row_heading_class} level{c} row{r}", value, _is_visible(r, c, idx_lengths) and not self.hide_index_[c], + display_value=self._display_funcs_index[(r, c)](value), attributes=( f'rowspan="{idx_lengths.get((c, r), 0)}"' if idx_lengths.get((c, r), 0) > 1 @@ -834,6 +842,96 @@ def format( return self + def format_index( + self, + formatter: ExtFormatter | None = None, + axis: int | str = 0, + level: Level | list[Level] | None = None, + na_rep: str | None = None, + precision: int | None = None, + decimal: str = ".", + thousands: str | None = None, + escape: str | None = None, + ) -> StylerRenderer: + r""" + Format the text display value of index labels or column headers. + + .. versionadded:: 1.4.0 + + Parameters + ---------- + formatter : str, callable, dict or None + Object to define how values are displayed. See notes. + axis : {0, "index", 1, "columns"} + Whether to apply the formatter to the index or column headers. + level : int, str, list + The level(s) over which to apply the generic formatter. + na_rep : str, optional + Representation for missing values. + If ``na_rep`` is None, no special formatting is applied. + precision : int, optional + Floating point precision to use for display purposes, if not determined by + the specified ``formatter``. + decimal : str, default "." + Character used as decimal separator for floats, complex and integers + thousands : str, optional, default None + Character used as thousands separator for floats, complex and integers + escape : str, optional + Use 'html' to replace the characters ``&``, ``<``, ``>``, ``'``, and ``"`` + in cell display string with HTML-safe sequences. + Use 'latex' to replace the characters ``&``, ``%``, ``$``, ``#``, ``_``, + ``{``, ``}``, ``~``, ``^``, and ``\`` in the cell display string with + LaTeX-safe sequences. + Escaping is done before ``formatter``. + + Returns + ------- + self : Styler + """ + axis = self.data._get_axis_number(axis) + if axis == 0: + display_funcs_, obj = self._display_funcs_index, self.index + else: + display_funcs_, obj = self._display_funcs_columns, self.columns + levels_ = refactor_levels(level, obj) + + if all( + ( + formatter is None, + level is None, + precision is None, + decimal == ".", + thousands is None, + na_rep is None, + escape is None, + ) + ): + display_funcs_.clear() + return self # clear the formatter / revert to default and avoid looping + + if not isinstance(formatter, dict): + formatter = {level: formatter for level in levels_} + else: + formatter = { + obj._get_level_number(level): formatter_ + for level, formatter_ in formatter.items() + } + + for lvl in levels_: + format_func = _maybe_wrap_formatter( + formatter.get(lvl), + na_rep=na_rep, + precision=precision, + decimal=decimal, + thousands=thousands, + escape=escape, + ) + + for idx in [(i, lvl) if axis == 0 else (lvl, i) for i in range(len(obj))]: + display_funcs_[idx] = format_func + + return self + def _element( html_element: str, diff --git a/pandas/io/formats/templates/html_table.tpl b/pandas/io/formats/templates/html_table.tpl index 3ecd911c1ec07..17118d2bb21cc 100644 --- a/pandas/io/formats/templates/html_table.tpl +++ b/pandas/io/formats/templates/html_table.tpl @@ -21,13 +21,13 @@ {% if exclude_styles %} {% for c in r %} {% if c.is_visible != False %} - <{{c.type}} {{c.attributes}}>{{c.value}} + <{{c.type}} {{c.attributes}}>{{c.display_value}} {% endif %} {% endfor %} {% else %} {% for c in r %} {% if c.is_visible != False %} - <{{c.type}} {%- if c.id is defined %} id="T_{{uuid}}_{{c.id}}" {%- endif %} class="{{c.class}}" {{c.attributes}}>{{c.value}} + <{{c.type}} {%- if c.id is defined %} id="T_{{uuid}}_{{c.id}}" {%- endif %} class="{{c.class}}" {{c.attributes}}>{{c.display_value}} {% endif %} {% endfor %} {% endif %} From 2b914104ad5a2e4dccc7841e31f63e6359889619 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Mon, 13 Sep 2021 18:22:33 +0200 Subject: [PATCH 2/5] fix tests --- pandas/tests/io/formats/style/test_format.py | 4 ++-- pandas/tests/io/formats/style/test_style.py | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/pandas/tests/io/formats/style/test_format.py b/pandas/tests/io/formats/style/test_format.py index 7ecd8ffec3bea..f9fe8d76a0092 100644 --- a/pandas/tests/io/formats/style/test_format.py +++ b/pandas/tests/io/formats/style/test_format.py @@ -335,7 +335,7 @@ def test_1level_multiindex(): 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]["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]["display_value"] == "2" assert ctx["body"][1][0]["is_visible"] is True diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py index 2d2e2222c9050..6a09018c5b20b 100644 --- a/pandas/tests/io/formats/style/test_style.py +++ b/pandas/tests/io/formats/style/test_style.py @@ -54,6 +54,8 @@ def mi_styler_comp(mi_styler): mi_styler.hide_index(names=True) mi_styler.set_table_attributes('class="box"') mi_styler.format(na_rep="MISSING", precision=3) + mi_styler.format_index(precision=2, axis=0) + mi_styler.format_index(precision=4, axis=1) mi_styler.highlight_max(axis=None) mi_styler.applymap_index(lambda x: "color: white;", axis=0) mi_styler.applymap_index(lambda x: "color: black;", axis=1) @@ -1051,7 +1053,7 @@ def test_mi_sparse_column_names(self): }, { "class": "col_heading level1 col0", - "display_value": 1, + "display_value": "1", "is_visible": True, "type": "th", "value": 1, @@ -1059,7 +1061,7 @@ def test_mi_sparse_column_names(self): }, { "class": "col_heading level1 col1", - "display_value": 0, + "display_value": "0", "is_visible": True, "type": "th", "value": 0, @@ -1067,7 +1069,7 @@ def test_mi_sparse_column_names(self): }, { "class": "col_heading level1 col2", - "display_value": 1, + "display_value": "1", "is_visible": True, "type": "th", "value": 1, @@ -1075,7 +1077,7 @@ def test_mi_sparse_column_names(self): }, { "class": "col_heading level1 col3", - "display_value": 0, + "display_value": "0", "is_visible": True, "type": "th", "value": 0, @@ -1174,7 +1176,7 @@ def test_hide_columns_index_mult_levels(self): # column headers assert ctx["head"][0][2]["is_visible"] assert ctx["head"][1][2]["is_visible"] - assert ctx["head"][1][3]["display_value"] == 1 + assert ctx["head"][1][3]["display_value"] == "1" # indices assert ctx["body"][0][0]["is_visible"] # data From 10d85c21578da7a592855f4c85205ca5d52e3472 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Tue, 14 Sep 2021 18:42:10 +0200 Subject: [PATCH 3/5] test name chg --- doc/source/reference/style.rst | 2 +- pandas/io/formats/style_render.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/reference/style.rst b/doc/source/reference/style.rst index e67813084e389..5bf9a35617fe8 100644 --- a/doc/source/reference/style.rst +++ b/doc/source/reference/style.rst @@ -39,7 +39,7 @@ Style application Styler.apply_index Styler.applymap_index Styler.format - Styler.format_index + Styler.format_index2 Styler.hide_index Styler.hide_columns Styler.set_td_classes diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 1bc5721e6b3c5..25b5a18ea77c8 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -842,7 +842,7 @@ def format( return self - def format_index( + def format_index2( self, formatter: ExtFormatter | None = None, axis: int | str = 0, From 80e64cfa72d048d063bd35b7371e592a365ddd83 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Tue, 14 Sep 2021 18:43:20 +0200 Subject: [PATCH 4/5] test name chg --- pandas/tests/io/formats/style/test_style.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py index 6a09018c5b20b..694e88ad09db2 100644 --- a/pandas/tests/io/formats/style/test_style.py +++ b/pandas/tests/io/formats/style/test_style.py @@ -54,8 +54,8 @@ def mi_styler_comp(mi_styler): mi_styler.hide_index(names=True) mi_styler.set_table_attributes('class="box"') mi_styler.format(na_rep="MISSING", precision=3) - mi_styler.format_index(precision=2, axis=0) - mi_styler.format_index(precision=4, axis=1) + mi_styler.format_index2(precision=2, axis=0) + mi_styler.format_index2(precision=4, axis=1) mi_styler.highlight_max(axis=None) mi_styler.applymap_index(lambda x: "color: white;", axis=0) mi_styler.applymap_index(lambda x: "color: black;", axis=1) From 11613bfdf1bc40ea843a942fca3820a556547b96 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (MBP)" Date: Tue, 14 Sep 2021 20:48:33 +0200 Subject: [PATCH 5/5] name to format_axis --- doc/source/reference/style.rst | 2 +- pandas/io/formats/style_render.py | 2 +- pandas/tests/io/formats/style/test_style.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/source/reference/style.rst b/doc/source/reference/style.rst index 5bf9a35617fe8..4742d40cd9701 100644 --- a/doc/source/reference/style.rst +++ b/doc/source/reference/style.rst @@ -39,7 +39,7 @@ Style application Styler.apply_index Styler.applymap_index Styler.format - Styler.format_index2 + Styler.format_axis Styler.hide_index Styler.hide_columns Styler.set_td_classes diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 25b5a18ea77c8..df096ea606670 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -842,7 +842,7 @@ def format( return self - def format_index2( + def format_axis( self, formatter: ExtFormatter | None = None, axis: int | str = 0, diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py index 694e88ad09db2..626a32bb500d2 100644 --- a/pandas/tests/io/formats/style/test_style.py +++ b/pandas/tests/io/formats/style/test_style.py @@ -54,8 +54,8 @@ def mi_styler_comp(mi_styler): mi_styler.hide_index(names=True) mi_styler.set_table_attributes('class="box"') mi_styler.format(na_rep="MISSING", precision=3) - mi_styler.format_index2(precision=2, axis=0) - mi_styler.format_index2(precision=4, axis=1) + mi_styler.format_axis(precision=2, axis=0) + mi_styler.format_axis(precision=4, axis=1) mi_styler.highlight_max(axis=None) mi_styler.applymap_index(lambda x: "color: white;", axis=0) mi_styler.applymap_index(lambda x: "color: black;", axis=1)