Skip to content

ENH: Styler.format_index split PR base components #43552

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/reference/style.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Style application
Styler.apply_index
Styler.applymap_index
Styler.format
Styler.format_axis
Styler.hide_index
Styler.hide_columns
Styler.set_td_classes
Expand Down
2 changes: 2 additions & 0 deletions pandas/io/formats/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
98 changes: 98 additions & 0 deletions pandas/io/formats/style_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -834,6 +842,96 @@ def format(

return self

def format_axis(
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,
Expand Down
4 changes: 2 additions & 2 deletions pandas/io/formats/templates/html_table.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -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.type}} {{c.attributes}}>{{c.display_value}}</{{c.type}}>
{% 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}}>
<{{c.type}} {%- if c.id is defined %} id="T_{{uuid}}_{{c.id}}" {%- endif %} class="{{c.class}}" {{c.attributes}}>{{c.display_value}}</{{c.type}}>
{% endif %}
{% endfor %}
{% endif %}
Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/io/formats/style/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
12 changes: 7 additions & 5 deletions pandas/tests/io/formats/style/test_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_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)
Expand Down Expand Up @@ -1051,31 +1053,31 @@ 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,
"attributes": "",
},
{
"class": "col_heading level1 col1",
"display_value": 0,
"display_value": "0",
"is_visible": True,
"type": "th",
"value": 0,
"attributes": "",
},
{
"class": "col_heading level1 col2",
"display_value": 1,
"display_value": "1",
"is_visible": True,
"type": "th",
"value": 1,
"attributes": "",
},
{
"class": "col_heading level1 col3",
"display_value": 0,
"display_value": "0",
"is_visible": True,
"type": "th",
"value": 0,
Expand Down Expand Up @@ -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
Expand Down