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/doc/source/user_guide/style.ipynb b/doc/source/user_guide/style.ipynb
index 4de54c5d9471c..7b7df83274620 100644
--- a/doc/source/user_guide/style.ipynb
+++ b/doc/source/user_guide/style.ipynb
@@ -150,15 +150,14 @@
"\n",
"### Formatting Values\n",
"\n",
- "Before adding styles it is useful to show that the [Styler][styler] can distinguish the *display* value from the *actual* value. To control the display value, the text is printed in each cell, and we can use the [.format()][formatfunc] method to manipulate this according to a [format spec string][format] or a callable that takes a single value and returns a string. It is possible to define this for the whole table or for individual columns. \n",
+ "Before adding styles it is useful to show that the [Styler][styler] can distinguish the *display* value from the *actual* value, in both datavlaues and index or columns headers. To control the display value, the text is printed in each cell as string, and we can use the [.format()][formatfunc] and [.format_index()][formatfuncindex] methods to manipulate this according to a [format spec string][format] or a callable that takes a single value and returns a string. It is possible to define this for the whole table, or index, or for individual columns, or MultiIndex levels. \n",
"\n",
- "Additionally, the format function has a **precision** argument to specifically help formatting floats, as well as **decimal** and **thousands** separators to support other locales, an **na_rep** argument to display missing data, and an **escape** argument to help displaying safe-HTML or safe-LaTeX. The default formatter is configured to adopt pandas' regular `display.precision` option, controllable using `with pd.option_context('display.precision', 2):`\n",
- "\n",
- "Here is an example of using the multiple options to control the formatting generally and with specific column formatters.\n",
+ "Additionally, the format function has a **precision** argument to specifically help formatting floats, as well as **decimal** and **thousands** separators to support other locales, an **na_rep** argument to display missing data, and an **escape** argument to help displaying safe-HTML or safe-LaTeX. The default formatter is configured to adopt pandas' regular `display.precision` option, controllable using `with pd.option_context('display.precision', 2):` \n",
"\n",
"[styler]: ../reference/api/pandas.io.formats.style.Styler.rst\n",
"[format]: https://docs.python.org/3/library/string.html#format-specification-mini-language\n",
- "[formatfunc]: ../reference/api/pandas.io.formats.style.Styler.format.rst"
+ "[formatfunc]: ../reference/api/pandas.io.formats.style.Styler.format.rst\n",
+ "[formatfuncindex]: ../reference/api/pandas.io.formats.style.Styler.format_index.rst"
]
},
{
@@ -173,6 +172,49 @@
" })"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Using Styler to manipulate the display is a useful feature because maintaining the indexing and datavalues for other purposes gives greater control. You do not have to overwrite your DataFrame to display it how you like. Here is an example of using the formatting functions whilst still relying on the underlying data for indexing and calculations."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "weather_df = pd.DataFrame(np.random.rand(10,2)*5, \n",
+ " index=pd.date_range(start=\"2021-01-01\", periods=10),\n",
+ " columns=[\"Tokyo\", \"Beijing\"])\n",
+ "\n",
+ "def rain_condition(v): \n",
+ " if v < 1.75:\n",
+ " return \"Dry\"\n",
+ " elif v < 2.75:\n",
+ " return \"Rain\"\n",
+ " return \"Heavy Rain\"\n",
+ "\n",
+ "def make_pretty(styler):\n",
+ " styler.set_caption(\"Weather Conditions\")\n",
+ " styler.format(rain_condition)\n",
+ " styler.format_index(lambda v: v.strftime(\"%A\"))\n",
+ " styler.background_gradient(axis=None, vmin=1, vmax=5, cmap=\"YlGnBu\")\n",
+ " return styler\n",
+ "\n",
+ "weather_df"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "weather_df.loc[\"2021-01-04\":\"2021-01-08\"].style.pipe(make_pretty)"
+ ]
+ },
{
"cell_type": "markdown",
"metadata": {},
@@ -187,7 +229,7 @@
"\n",
"Hiding does not change the integer arrangement of CSS classes, e.g. hiding the first two columns of a DataFrame means the column class indexing will start at `col2`, since `col0` and `col1` are simply ignored.\n",
"\n",
- "We can update our `Styler` object to hide some data and format the values.\n",
+ "We can update our `Styler` object from before to hide some data and format the values.\n",
"\n",
"[hideidx]: ../reference/api/pandas.io.formats.style.Styler.hide_index.rst\n",
"[hidecols]: ../reference/api/pandas.io.formats.style.Styler.hide_columns.rst"
@@ -1974,7 +2016,6 @@
}
],
"metadata": {
- "celltoolbar": "Edit Metadata",
"kernelspec": {
"display_name": "Python 3",
"language": "python",
diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst
index eb1bc270a7d1b..f18b3b75ca3d2 100644
--- a/doc/source/whatsnew/v1.4.0.rst
+++ b/doc/source/whatsnew/v1.4.0.rst
@@ -72,7 +72,7 @@ Styler
:class:`.Styler` has been further developed in 1.4.0. The following enhancements have been made:
- - Styling of indexing has been added, with :meth:`.Styler.apply_index` and :meth:`.Styler.applymap_index`. These mirror the signature of the methods already used to style data values, and work with both HTML and LaTeX format (:issue:`41893`).
+ - Styling and formatting of indexes has been added, with :meth:`.Styler.apply_index`, :meth:`.Styler.applymap_index` and :meth:`.Styler.format_index`. These mirror the signature of the methods already used to style and format data values, and work with both HTML and LaTeX format (:issue:`41893`, :issue:`43101`).
- :meth:`.Styler.bar` introduces additional arguments to control alignment and display (:issue:`26070`, :issue:`36419`), and it also validates the input arguments ``width`` and ``height`` (:issue:`42511`).
- :meth:`.Styler.to_latex` introduces keyword argument ``environment``, which also allows a specific "longtable" entry through a separate jinja2 template (:issue:`41866`).
- :meth:`.Styler.to_html` introduces keyword arguments ``sparse_index``, ``sparse_columns``, ``bold_headers``, ``caption``, ``max_rows`` and ``max_columns`` (:issue:`41946`, :issue:`43149`, :issue:`42972`).
diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py
index 97126975dc194..2fbd9506e391a 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..0ec6a9b470b50 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,175 @@ 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
+
+ Notes
+ -----
+ This method assigns a formatting function, ``formatter``, to each level label
+ in the DataFrame's index or column headers. If ``formatter`` is ``None``,
+ then the default formatter is used.
+ If a callable then that function should take a label value as input and return
+ a displayable representation, such as a string. If ``formatter`` is
+ given as a string this is assumed to be a valid Python format specification
+ and is wrapped to a callable as ``string.format(x)``. If a ``dict`` is given,
+ keys should correspond to MultiIndex level numbers or names, and values should
+ be string or callable, as above.
+
+ The default formatter currently expresses floats and complex numbers with the
+ pandas display precision unless using the ``precision`` argument here. The
+ default formatter does not adjust the representation of missing values unless
+ the ``na_rep`` argument is used.
+
+ The ``level`` argument defines which levels of a MultiIndex to apply the
+ method to. If the ``formatter`` argument is given in dict form but does
+ not include all levels within the level argument then these unspecified levels
+ will have the default formatter applied. Any levels in the formatter dict
+ specifically excluded from the level argument will be ignored.
+
+ When using a ``formatter`` string the dtypes must be compatible, otherwise a
+ `ValueError` will be raised.
+
+ Examples
+ --------
+ Using ``na_rep`` and ``precision`` with the default ``formatter``
+
+ >>> df = pd.DataFrame([[1, 2, 3]], columns=[2.0, np.nan, 4.0]])
+ >>> df.style.format_index(axis=1, na_rep='MISS', precision=3) # doctest: +SKIP
+ 2.000 MISS 4.000
+ 0 1 2 3
+
+ Using a ``formatter`` specification on consistent dtypes in a level
+
+ >>> df.style.format_index('{:.2f}', axis=1, na_rep='MISS') # doctest: +SKIP
+ 2.00 MISS 4.00
+ 0 1 2 3
+
+ Using the default ``formatter`` for unspecified levels
+
+ >>> df = pd.DataFrame([[1, 2, 3]],
+ ... columns=pd.MultiIndex.from_arrays([["a", "a", "b"],[2, np.nan, 4]]))
+ >>> df.style.format_index({0: lambda v: upper(v)}, axis=1, precision=1)
+ ... # doctest: +SKIP
+ A B
+ 2.0 nan 4.0
+ 0 1 2 3
+
+ Using a callable ``formatter`` function.
+
+ >>> func = lambda s: 'STRING' if isinstance(s, str) else 'FLOAT'
+ >>> df.style.format_index(func, axis=1, na_rep='MISS')
+ ... # doctest: +SKIP
+ STRING STRING
+ FLOAT MISS FLOAT
+ 0 1 2 3
+
+ Using a ``formatter`` with HTML ``escape`` and ``na_rep``.
+
+ >>> df = pd.DataFrame([[1, 2, 3]], columns=['"A"', 'A&B', None])
+ >>> s = df.style.format_index('$ {0}', axis=1, escape="html", na_rep="NA")
+
$ "A" |
+ $ A&B |
+ NA
+ ...
+
+ Using a ``formatter`` with LaTeX ``escape``.
+
+ >>> df = pd.DataFrame([[1, 2, 3]], columns=["123", "~", "$%#"])
+ >>> df.style.format_index("\\textbf{{{}}}", escape="latex", axis=1).to_latex()
+ ... # doctest: +SKIP
+ \begin{tabular}{lrrr}
+ {} & {\textbf{123}} & {\textbf{\textasciitilde }} & {\textbf{\$\%\#}} \\
+ 0 & 1 & 2 & 3 \\
+ \end{tabular}
+ """
+ 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.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 %}
diff --git a/pandas/tests/io/formats/style/test_format.py b/pandas/tests/io/formats/style/test_format.py
index 7ecd8ffec3bea..5207be992d606 100644
--- a/pandas/tests/io/formats/style/test_format.py
+++ b/pandas/tests/io/formats/style/test_format.py
@@ -2,6 +2,7 @@
import pytest
from pandas import (
+ NA,
DataFrame,
IndexSlice,
MultiIndex,
@@ -36,12 +37,40 @@ def test_display_format(styler):
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"
@@ -77,6 +106,14 @@ def test_format_with_na_rep():
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(
@@ -92,12 +129,20 @@ def test_format_non_numeric_na():
assert ctx["body"][1][2]["display_value"] == "-"
-def test_format_clear(styler):
- assert (0, 0) not in styler._display_funcs # using default
- styler.format("{:.2f")
- assert (0, 0) in styler._display_funcs # formatter is specified
- styler.format()
- assert (0, 0) not in styler._display_funcs # formatter cleared to default
+@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(
@@ -125,6 +170,13 @@ def test_format_escape_html(escape, exp):
expected = f' | &{exp}& | '
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}&"
+
def test_format_escape_na_rep():
# tests the na_rep is not escaped
@@ -135,6 +187,14 @@ def test_format_escape_na_rep():
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
@@ -148,33 +208,66 @@ def test_format_escape_floats(styler):
@pytest.mark.parametrize("formatter", [5, True, [2.0]])
-def test_format_raises(styler, formatter):
+@pytest.mark.parametrize("func", ["format", "format_index"])
+def test_format_raises(styler, formatter, func):
with pytest.raises(TypeError, match="expected str or callable"):
- styler.format(formatter)
+ getattr(styler, func)(formatter)
-def test_format_with_precision():
+@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(data=[[1.0, 2.0090], [3.2121, 4.566]], columns=["a", "b"])
- s = Styler(df)
+ 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 = s.format(precision=1)._translate(True, True)
- assert ctx["body"][0][1]["display_value"] == "1.0"
- assert ctx["body"][0][2]["display_value"] == "2.0"
- assert ctx["body"][1][1]["display_value"] == "3.2"
- assert ctx["body"][1][2]["display_value"] == "4.6"
+ ctx = 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)
- ctx = s.format(precision=2)._translate(True, True)
- assert ctx["body"][0][1]["display_value"] == "1.00"
- assert ctx["body"][0][2]["display_value"] == "2.01"
- assert ctx["body"][1][1]["display_value"] == "3.21"
- assert ctx["body"][1][2]["display_value"] == "4.57"
+ 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)]
- ctx = s.format(precision=3)._translate(True, True)
- assert ctx["body"][0][1]["display_value"] == "1.000"
- assert ctx["body"][0][2]["display_value"] == "2.009"
- assert ctx["body"][1][1]["display_value"] == "3.212"
- assert ctx["body"][1][2]["display_value"] == "4.566"
+ assert expected == result
def test_format_subset():
@@ -212,41 +305,43 @@ def test_format_subset():
@pytest.mark.parametrize("formatter", [None, "{:,.1f}"])
@pytest.mark.parametrize("decimal", [".", "*"])
@pytest.mark.parametrize("precision", [None, 2])
-def test_format_thousands(formatter, decimal, precision):
- s = DataFrame([[1000000.123456789]]).style # test float
- result = s.format(
+@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][1]["display_value"]
+ assert "1_000_000" in result["body"][0][col]["display_value"]
- s = DataFrame([[1000000]]).style # test int
- result = s.format(
+ 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][1]["display_value"]
+ assert "1_000_000" in result["body"][0][col]["display_value"]
- s = DataFrame([[1 + 1000000.123456789j]]).style # test complex
- result = s.format(
+ 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][1]["display_value"]
+ 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])
-def test_format_decimal(formatter, thousands, precision):
- s = DataFrame([[1000000.123456789]]).style # test float
- result = s.format(
+@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][1]["display_value"]
+ assert "000_123" in result["body"][0][col]["display_value"]
- s = DataFrame([[1 + 1000000.123456789j]]).style # test complex
- result = s.format(
+ 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][1]["display_value"]
+ assert "000_123" in result["body"][0][col]["display_value"]
def test_str_escape_error():
@@ -335,7 +430,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