Skip to content

Commit 777ce63

Browse files
committed
add new method to styler
1 parent 89898a6 commit 777ce63

File tree

3 files changed

+155
-4
lines changed

3 files changed

+155
-4
lines changed

pandas/io/formats/style.py

+2
Original file line numberDiff line numberDiff line change
@@ -1683,6 +1683,8 @@ def _copy(self, deepcopy: bool = False) -> Styler:
16831683
"_display_funcs",
16841684
"_display_funcs_index",
16851685
"_display_funcs_columns",
1686+
"_display_funcs_index_names",
1687+
"_display_funcs_column_names",
16861688
"hidden_rows",
16871689
"hidden_columns",
16881690
"ctx",

pandas/io/formats/style_render.py

+151-4
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,15 @@ def __init__(
140140
self._display_funcs_index: DefaultDict[ # maps (row, level) -> format func
141141
tuple[int, int], Callable[[Any], str]
142142
] = defaultdict(lambda: partial(_default_formatter, precision=precision))
143+
self._display_funcs_index_names: DefaultDict[ # maps index level -> format func
144+
tuple[int, int], Callable[[Any], str]
145+
] = defaultdict(lambda: partial(_default_formatter, precision=precision))
143146
self._display_funcs_columns: DefaultDict[ # maps (level, col) -> format func
144147
tuple[int, int], Callable[[Any], str]
145148
] = defaultdict(lambda: partial(_default_formatter, precision=precision))
149+
self._display_funcs_column_names: DefaultDict[ # maps col level -> format func
150+
tuple[int, int], Callable[[Any], str]
151+
] = defaultdict(lambda: partial(_default_formatter, precision=precision))
146152

147153
def _render(
148154
self,
@@ -460,6 +466,12 @@ def _generate_col_header_row(
460466
] * (self.index.nlevels - sum(self.hide_index_) - 1)
461467

462468
name = self.data.columns.names[r]
469+
470+
is_display = name is not None and not self.hide_column_names
471+
value = name if is_display else self.css["blank_value"]
472+
display_value = (
473+
self._display_funcs_column_names[r](value) if is_display else None
474+
)
463475
column_name = [
464476
_element(
465477
"th",
@@ -468,10 +480,9 @@ def _generate_col_header_row(
468480
if name is None
469481
else f"{self.css['index_name']} {self.css['level']}{r}"
470482
),
471-
name
472-
if (name is not None and not self.hide_column_names)
473-
else self.css["blank_value"],
483+
value,
474484
not all(self.hide_index_),
485+
display_value=display_value,
475486
)
476487
]
477488

@@ -553,6 +564,9 @@ def _generate_index_names_row(
553564
f"{self.css['index_name']} {self.css['level']}{c}",
554565
self.css["blank_value"] if name is None else name,
555566
not self.hide_index_[c],
567+
display_value=(
568+
None if name is None else self._display_funcs_index_names[c](name)
569+
),
556570
)
557571
for c, name in enumerate(self.data.index.names)
558572
]
@@ -1560,6 +1574,139 @@ def alias_(x, value):
15601574

15611575
return self
15621576

1577+
def format_names(
1578+
self,
1579+
formatter: ExtFormatter | None = None,
1580+
axis: Axis = 0,
1581+
level: Level | list[Level] | None = None,
1582+
na_rep: str | None = None,
1583+
precision: int | None = None,
1584+
decimal: str = ".",
1585+
thousands: str | None = None,
1586+
escape: str | None = None,
1587+
hyperlinks: str | None = None,
1588+
) -> StylerRenderer:
1589+
r"""
1590+
Format the text display value of index names or column names.
1591+
1592+
.. versionadded:: TODO
1593+
1594+
Parameters
1595+
----------
1596+
formatter : str, callable, dict or None
1597+
Object to define how values are displayed. See notes.
1598+
axis : {0, "index", 1, "columns"}
1599+
Whether to apply the formatter to the index or column headers.
1600+
level : int, str, list
1601+
The level(s) over which to apply the generic formatter.
1602+
na_rep : str, optional
1603+
Representation for missing values.
1604+
If ``na_rep`` is None, no special formatting is applied.
1605+
precision : int, optional
1606+
Floating point precision to use for display purposes, if not determined by
1607+
the specified ``formatter``.
1608+
decimal : str, default "."
1609+
Character used as decimal separator for floats, complex and integers.
1610+
thousands : str, optional, default None
1611+
Character used as thousands separator for floats, complex and integers.
1612+
escape : str, optional
1613+
Use 'html' to replace the characters ``&``, ``<``, ``>``, ``'``, and ``"``
1614+
in cell display string with HTML-safe sequences.
1615+
Use 'latex' to replace the characters ``&``, ``%``, ``$``, ``#``, ``_``,
1616+
``{``, ``}``, ``~``, ``^``, and ``\`` in the cell display string with
1617+
LaTeX-safe sequences.
1618+
Escaping is done before ``formatter``.
1619+
hyperlinks : {"html", "latex"}, optional
1620+
Convert string patterns containing https://, http://, ftp:// or www. to
1621+
HTML <a> tags as clickable URL hyperlinks if "html", or LaTeX \href
1622+
commands if "latex".
1623+
1624+
Returns
1625+
-------
1626+
Styler
1627+
1628+
See Also
1629+
--------
1630+
Styler.format_index: Format the text display value of index labels
1631+
or column headers.
1632+
1633+
Notes
1634+
-----
1635+
This method assigns a formatting function, ``formatter``, to each level label
1636+
in the DataFrame's index or column headers. If ``formatter`` is ``None``,
1637+
then the default formatter is used.
1638+
If a callable then that function should take a label value as input and return
1639+
a displayable representation, such as a string. If ``formatter`` is
1640+
given as a string this is assumed to be a valid Python format specification
1641+
and is wrapped to a callable as ``string.format(x)``. If a ``dict`` is given,
1642+
keys should correspond to MultiIndex level numbers or names, and values should
1643+
be string or callable, as above.
1644+
1645+
The default formatter currently expresses floats and complex numbers with the
1646+
pandas display precision unless using the ``precision`` argument here. The
1647+
default formatter does not adjust the representation of missing values unless
1648+
the ``na_rep`` argument is used.
1649+
1650+
The ``level`` argument defines which levels of a MultiIndex to apply the
1651+
method to. If the ``formatter`` argument is given in dict form but does
1652+
not include all levels within the level argument then these unspecified levels
1653+
will have the default formatter applied. Any levels in the formatter dict
1654+
specifically excluded from the level argument will be ignored.
1655+
1656+
When using a ``formatter`` string the dtypes must be compatible, otherwise a
1657+
`ValueError` will be raised.
1658+
1659+
.. warning::
1660+
`Styler.format_index` is ignored when using the output format
1661+
`Styler.to_excel`, since Excel and Python have inherrently different
1662+
formatting structures.
1663+
However, it is possible to use the `number-format` pseudo CSS attribute
1664+
to force Excel permissible formatting. See documentation for `Styler.format`.
1665+
"""
1666+
axis = self.data._get_axis_number(axis)
1667+
if axis == 0:
1668+
display_funcs_, obj = self._display_funcs_index_names, self.index
1669+
else:
1670+
display_funcs_, obj = self._display_funcs_column_names, self.columns
1671+
levels_ = refactor_levels(level, obj)
1672+
1673+
if all(
1674+
(
1675+
formatter is None,
1676+
level is None,
1677+
precision is None,
1678+
decimal == ".",
1679+
thousands is None,
1680+
na_rep is None,
1681+
escape is None,
1682+
hyperlinks is None,
1683+
)
1684+
):
1685+
display_funcs_.clear()
1686+
return self # clear the formatter / revert to default and avoid looping
1687+
1688+
if not isinstance(formatter, dict):
1689+
formatter = {level: formatter for level in levels_}
1690+
else:
1691+
formatter = {
1692+
obj._get_level_number(level): formatter_
1693+
for level, formatter_ in formatter.items()
1694+
}
1695+
1696+
for lvl in levels_:
1697+
format_func = _maybe_wrap_formatter(
1698+
formatter.get(lvl),
1699+
na_rep=na_rep,
1700+
precision=precision,
1701+
decimal=decimal,
1702+
thousands=thousands,
1703+
escape=escape,
1704+
hyperlinks=hyperlinks,
1705+
)
1706+
display_funcs_[lvl] = format_func
1707+
1708+
return self
1709+
15631710

15641711
def _element(
15651712
html_element: str,
@@ -1571,7 +1718,7 @@ def _element(
15711718
"""
15721719
Template to return container with information for a <td></td> or <th></th> element.
15731720
"""
1574-
if "display_value" not in kwargs:
1721+
if "display_value" not in kwargs or kwargs["display_value"] is None:
15751722
kwargs["display_value"] = value
15761723
return {
15771724
"type": html_element,

pandas/tests/io/formats/style/test_style.py

+2
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ def mi_styler_comp(mi_styler):
7777
columns=mi_styler.columns,
7878
)
7979
)
80+
mi_styler.format_names(escape="html", axis=0)
81+
mi_styler.format_names(escape="html", axis=1)
8082
return mi_styler
8183

8284

0 commit comments

Comments
 (0)