From b3c55af66702f11f2a097ae4511f80f159c653b9 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Thu, 6 Jan 2022 20:26:03 +0100 Subject: [PATCH 01/14] setup new kwarg --- pandas/io/formats/style_render.py | 39 +++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 2ff0a994ebb01..dd85611122fae 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -1044,6 +1044,7 @@ def format_index( thousands: str | None = None, escape: str | None = None, hyperlinks: str | None = None, + aliases: list[str] | None = None, ) -> StylerRenderer: r""" Format the text display value of index labels or column headers. @@ -1055,9 +1056,10 @@ def format_index( 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. + Whether to apply the ``formatter`` or ``aliases`` to the index or column + headers. level : int, str, list - The level(s) over which to apply the generic formatter. + The level(s) over which to apply the generic ``formatter``, or ``aliases``. na_rep : str, optional Representation for missing values. If ``na_rep`` is None, no special formatting is applied. @@ -1079,6 +1081,14 @@ def format_index( Convert string patterns containing https://, http://, ftp:// or www. to HTML tags as clickable URL hyperlinks if "html", or LaTeX \href commands if "latex". + aliases : list of strings + This list will replace the existing index or column headers. It will also + collapse a MultiIndex to a single level displaying the alias, + which is specified by the ``level`` argument. + Cannot be used simultaneously with ``formatter`` and the associated + arguments; ``thousands``, ``decimal``, ``escape``, ``hyperlinks``, + ``na_rep`` and ``precision``. + Must be of length equal to the number of visible columns, see examples. Returns ------- @@ -1163,6 +1173,11 @@ def format_index( {} & {\textbf{123}} & {\textbf{\textasciitilde }} & {\textbf{\$\%\#}} \\ 0 & 1 & 2 & 3 \\ \end{tabular} + + Using ``aliases`` to overwrite column names. + >>> df = pd.DataFrame([[1, 2, 3]], columns=[1, 2, 3]) + >>> df.style.format_index(axis=1, aliases=["A", "B", "C"]) + """ axis = self.data._get_axis_number(axis) if axis == 0: @@ -1171,10 +1186,9 @@ def format_index( display_funcs_, obj = self._display_funcs_columns, self.columns levels_ = refactor_levels(level, obj) - if all( + formatting_args_unset = all( ( formatter is None, - level is None, precision is None, decimal == ".", thousands is None, @@ -1182,10 +1196,25 @@ def format_index( escape is None, hyperlinks is None, ) - ): + ) + + aliases_unset = aliases is None + + if formatting_args_unset and level is None and aliases_unset: display_funcs_.clear() return self # clear the formatter / revert to default and avoid looping + if not aliases_unset: + if not formatting_args_unset: + raise ValueError( + "``aliases`` cannot be supplied together with any of " + "``formatter``, ``precision``, ``decimal``, ``na_rep``, " + "``escape``, or ``hyperlinks``." + ) + else: + pass + # do the alias formatting + if not isinstance(formatter, dict): formatter = {level: formatter for level in levels_} else: From c68944a19c784aeefc5349f4e4d658c22ba3f142 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Fri, 7 Jan 2022 18:07:32 +0100 Subject: [PATCH 02/14] method and raise on error --- pandas/io/formats/style_render.py | 76 +++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 23 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index dd85611122fae..a877fa487d32a 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -1182,8 +1182,10 @@ def format_index( axis = self.data._get_axis_number(axis) if axis == 0: display_funcs_, obj = self._display_funcs_index, self.index + hidden_lvls, hidden_labels = self.hide_index_, self.hidden_rows else: display_funcs_, obj = self._display_funcs_columns, self.columns + hidden_lvls, hidden_labels = self.hide_columns_, self.hidden_columns levels_ = refactor_levels(level, obj) formatting_args_unset = all( @@ -1201,10 +1203,10 @@ def format_index( aliases_unset = aliases is None if formatting_args_unset and level is None and aliases_unset: + # clear the formatter / revert to default and avoid looping display_funcs_.clear() - return self # clear the formatter / revert to default and avoid looping - if not aliases_unset: + elif not aliases_unset: # then apply a formatting function from arg: aliases if not formatting_args_unset: raise ValueError( "``aliases`` cannot be supplied together with any of " @@ -1212,30 +1214,58 @@ def format_index( "``escape``, or ``hyperlinks``." ) else: - pass - # do the alias formatting + if level is None: + level = obj.nlevels - 1 # default to last level - 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() - } + if not isinstance(level, (str, int)): + raise ValueError("``level`` must identify only a single level") + else: + if len(aliases) != len(obj) - len(set(hidden_labels)): + raise ValueError( + "``aliases`` must have length equal to the " + "number of visible labels along ``axis``" + ) - for lvl in levels_: - format_func = _maybe_wrap_formatter( - formatter.get(lvl), - na_rep=na_rep, - precision=precision, - decimal=decimal, - thousands=thousands, - escape=escape, - hyperlinks=hyperlinks, - ) + def alias(x, value): + return value + + level = obj._get_level_number(level) + for lvl in range(obj.nlevels): + if lvl != level: # hide unidentified levels using + hidden_lvls[lvl] = True # alias: works on Index and MultiI + for ai, idx in enumerate( + [ + (i, level) if axis == 0 else (level, i) + for i in range(len(obj)) + if i not in hidden_labels + ] + ): + display_funcs_[idx] = partial(alias, value=aliases[ai]) + + else: # then apply a formatting function from arg: formatter + 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, + hyperlinks=hyperlinks, + ) - for idx in [(i, lvl) if axis == 0 else (lvl, i) for i in range(len(obj))]: - display_funcs_[idx] = format_func + for idx in [ + (i, lvl) if axis == 0 else (lvl, i) for i in range(len(obj)) + ]: + display_funcs_[idx] = format_func return self From fcac539c23fe7ec44753ed61c02f84b431d390e0 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Sun, 9 Jan 2022 11:50:24 +0100 Subject: [PATCH 03/14] add basic tests --- pandas/tests/io/formats/style/test_format.py | 34 ++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pandas/tests/io/formats/style/test_format.py b/pandas/tests/io/formats/style/test_format.py index 5207be992d606..38de3632eb960 100644 --- a/pandas/tests/io/formats/style/test_format.py +++ b/pandas/tests/io/formats/style/test_format.py @@ -434,3 +434,37 @@ def test_1level_multiindex(): 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_basic_alias(styler): + styler.format_index(axis=1, aliases=["alias1", "alias2"]) + ctx = styler._translate(True, True) + assert ctx["head"][0][1]["value"] == "A" + assert ctx["head"][0][1]["display_value"] == "alias1" # alias + assert ctx["head"][0][2]["value"] == "B" + assert ctx["head"][0][2]["display_value"] == "alias2" # alias + + +def test_basic_alias_hidden_column(styler): + styler.hide(subset="A", axis=1) + styler.format_index(axis=1, aliases=["alias2"]) + ctx = styler._translate(True, True) + assert ctx["head"][0][1]["value"] == "A" + assert ctx["head"][0][1]["display_value"] == "A" # no alias for hidden + assert ctx["head"][0][2]["value"] == "B" + assert ctx["head"][0][2]["display_value"] == "alias2" # alias + + +@pytest.mark.parametrize("level", [None, 0, 1]) +def test_alias_collapse_levels(df, level): + df.columns = MultiIndex.from_tuples([("X", "A"), ("Y", "B")]) + styler = Styler(df, cell_ids=False, uuid_len=0) + styler.format_index(axis=1, level=level, aliases=["alias1", "alias2"]) + ctx = styler._translate(True, True) + print(ctx["head"]) + assert len(ctx["head"]) == 1 # MultiIndex collapsed to one level + + level = 1 if level is None else level # defaults to last + assert f"level{level}" in ctx["head"][0][1]["class"] + assert ctx["head"][0][1]["display_value"] == "alias1" + assert ctx["head"][0][2]["display_value"] == "alias2" From 55010b5811ba80496c4d635586909451881747da Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Sun, 9 Jan 2022 19:11:39 +0100 Subject: [PATCH 04/14] add basic tests --- pandas/io/formats/style_render.py | 71 +++++++++++++------- pandas/tests/io/formats/style/test_format.py | 56 +++++++++++++-- 2 files changed, 97 insertions(+), 30 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index a877fa487d32a..c50fde124e13e 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -1060,6 +1060,8 @@ def format_index( headers. level : int, str, list The level(s) over which to apply the generic ``formatter``, or ``aliases``. + In the case of ``aliases`` defaults to the last level of a MultiIndex, + for the reason that the last level is never sparsified. na_rep : str, optional Representation for missing values. If ``na_rep`` is None, no special formatting is applied. @@ -1081,14 +1083,15 @@ def format_index( Convert string patterns containing https://, http://, ftp:// or www. to HTML tags as clickable URL hyperlinks if "html", or LaTeX \href commands if "latex". - aliases : list of strings - This list will replace the existing index or column headers. It will also - collapse a MultiIndex to a single level displaying the alias, - which is specified by the ``level`` argument. + aliases : list of str, list of list of str + Values to replace the existing index or column headers. If specifying + more than one ``level`` then this should be a list containing sub-lists for + each identified level, in the respective order. Cannot be used simultaneously with ``formatter`` and the associated arguments; ``thousands``, ``decimal``, ``escape``, ``hyperlinks``, ``na_rep`` and ``precision``. - Must be of length equal to the number of visible columns, see examples. + This list (or each sub-list) must be of length equal to the number of + visible columns, see examples. Returns ------- @@ -1176,16 +1179,31 @@ def format_index( Using ``aliases`` to overwrite column names. >>> df = pd.DataFrame([[1, 2, 3]], columns=[1, 2, 3]) - >>> df.style.format_index(axis=1, aliases=["A", "B", "C"]) + >>> df.style.format_index(axis=1, aliases=["A", "B", "C"]) # doctest: +SKIP + A B C + 0 1 2 3 + Using ``aliases`` to overwrite column names of remaining **visible** items. + >>> df = pd.DataFrame([[1, 2, 3]], + ... columns=pd.MultiIndex.from_product([[1, 2, 3], ["X"]])) + >>> styler = df.style + 1 2 3 + X X X + 0 1 2 3 + + >>> styler.hide([2], axis=1) # hides a column as a `subset` hide + ... .hide(level=1, axis=1) # hides the entire axis level + ... .format_index(axis=1, aliases=["A", "C"], level=0) # doctest: +SKIP + A C + 0 1 3 """ axis = self.data._get_axis_number(axis) if axis == 0: display_funcs_, obj = self._display_funcs_index, self.index - hidden_lvls, hidden_labels = self.hide_index_, self.hidden_rows + hidden_labels = self.hidden_rows else: display_funcs_, obj = self._display_funcs_columns, self.columns - hidden_lvls, hidden_labels = self.hide_columns_, self.hidden_columns + hidden_labels = self.hidden_columns levels_ = refactor_levels(level, obj) formatting_args_unset = all( @@ -1214,33 +1232,36 @@ def format_index( "``escape``, or ``hyperlinks``." ) else: + visible_len = len(obj) - len(set(hidden_labels)) if level is None: - level = obj.nlevels - 1 # default to last level + levels_ = [obj.nlevels - 1] # default to last level + elif len(levels_) > 1 and len(aliases) != len(levels_): + raise ValueError( + f"``level`` specifies {len(levels_)} levels but the length of " + f"``aliases``, {len(aliases)}, does not match." + ) - if not isinstance(level, (str, int)): - raise ValueError("``level`` must identify only a single level") - else: - if len(aliases) != len(obj) - len(set(hidden_labels)): + def alias(x, value): + return value + + for i, lvl in enumerate(levels_): + level_aliases = aliases[i] if len(levels_) > 1 else aliases + if len(level_aliases) != visible_len: raise ValueError( - "``aliases`` must have length equal to the " - "number of visible labels along ``axis``" + "``aliases`` must be of length equal to the number of " + "visible labels along ``axis``. If ``level`` is given and " + "contains more than one level ``aliases`` should be a " + "list of lists with each sub-list having length equal to" + "the number of visible labels along ``axis``." ) - - def alias(x, value): - return value - - level = obj._get_level_number(level) - for lvl in range(obj.nlevels): - if lvl != level: # hide unidentified levels using - hidden_lvls[lvl] = True # alias: works on Index and MultiI for ai, idx in enumerate( [ - (i, level) if axis == 0 else (level, i) + (i, lvl) if axis == 0 else (lvl, i) for i in range(len(obj)) if i not in hidden_labels ] ): - display_funcs_[idx] = partial(alias, value=aliases[ai]) + display_funcs_[idx] = partial(alias, value=level_aliases[ai]) else: # then apply a formatting function from arg: formatter if not isinstance(formatter, dict): diff --git a/pandas/tests/io/formats/style/test_format.py b/pandas/tests/io/formats/style/test_format.py index 38de3632eb960..667e18d2d3701 100644 --- a/pandas/tests/io/formats/style/test_format.py +++ b/pandas/tests/io/formats/style/test_format.py @@ -456,15 +456,61 @@ def test_basic_alias_hidden_column(styler): @pytest.mark.parametrize("level", [None, 0, 1]) -def test_alias_collapse_levels(df, level): +def test_alias_single_levels(df, level): df.columns = MultiIndex.from_tuples([("X", "A"), ("Y", "B")]) styler = Styler(df, cell_ids=False, uuid_len=0) styler.format_index(axis=1, level=level, aliases=["alias1", "alias2"]) ctx = styler._translate(True, True) print(ctx["head"]) - assert len(ctx["head"]) == 1 # MultiIndex collapsed to one level + assert len(ctx["head"]) == 2 # MultiIndex levels level = 1 if level is None else level # defaults to last - assert f"level{level}" in ctx["head"][0][1]["class"] - assert ctx["head"][0][1]["display_value"] == "alias1" - assert ctx["head"][0][2]["display_value"] == "alias2" + assert f"level{level}" in ctx["head"][level][1]["class"] + assert ctx["head"][level][1]["display_value"] == "alias1" + assert ctx["head"][level][2]["display_value"] == "alias2" + + +@pytest.mark.parametrize("level", [[0, 1], [1, 0]]) +def test_alias_multi_levels_order(df, level): + df.columns = MultiIndex.from_tuples([("X", "A"), ("Y", "B")]) + styler = Styler(df, cell_ids=False, uuid_len=0) + styler.format_index(axis=1, level=level, aliases=[["a1", "a2"], ["b1", "b2"]]) + ctx = styler._translate(True, True) + + assert ctx["head"][1 - level[1]][1]["display_value"] == "a1" + assert ctx["head"][1 - level[1]][2]["display_value"] == "a2" + assert ctx["head"][1 - level[0]][1]["display_value"] == "b1" + assert ctx["head"][1 - level[0]][2]["display_value"] == "b2" + + +@pytest.mark.parametrize( + "level, aliases", + [ + ([0, 1], ["alias1", "alias2"]), # no sublists + ([0], ["alias1"]), # too short + (None, ["alias1", "alias2", "alias3"]), # too long + ([0, 1], [["alias1", "alias2"], ["alias1"]]), # sublist too short + ([0, 1], [["a1", "a2"], ["a1", "a2", "a3"]]), # sublist too long + ], +) +def test_alias_warning(df, level, aliases): + df.columns = MultiIndex.from_tuples([("X", "A"), ("Y", "B")]) + styler = Styler(df, cell_ids=False, uuid_len=0) + msg = "``aliases`` must be of length equal to" + with pytest.raises(ValueError, match=msg): + styler.format_index(axis=1, level=level, aliases=aliases) + + +@pytest.mark.parametrize( + "level, aliases", + [ + ([0, 1], [["a1", "a2"]]), # too few sublists + ([0, 1], [["a1", "a2"], ["a1", "a2"], ["a1", "a2"]]), # too many sublists + ], +) +def test_alias_warning2(df, level, aliases): + df.columns = MultiIndex.from_tuples([("X", "A"), ("Y", "B")]) + styler = Styler(df, cell_ids=False, uuid_len=0) + msg = "``level`` specifies 2 levels but the length of" + with pytest.raises(ValueError, match=msg): + styler.format_index(axis=1, level=level, aliases=aliases) From 9500c06c08815c91665013f6486e1bf02ef68015 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Sun, 9 Jan 2022 19:19:11 +0100 Subject: [PATCH 05/14] extend docs --- pandas/io/formats/style_render.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index c50fde124e13e..069908161214b 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -1093,6 +1093,8 @@ def format_index( This list (or each sub-list) must be of length equal to the number of visible columns, see examples. + .. versionadded:: 1.5.0 + Returns ------- self : Styler @@ -1123,6 +1125,10 @@ def format_index( When using a ``formatter`` string the dtypes must be compatible, otherwise a `ValueError` will be raised. + Since it is not possible to apply a generic function which will return an + arbitrary set of column aliases, the argument ``aliases`` provides the + ability to automate this, across individual index levels if necessary. + Examples -------- Using ``na_rep`` and ``precision`` with the default ``formatter`` From 38ea09c7f44c2ac5db76d4021ecbd29cef95d1a2 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Sun, 9 Jan 2022 19:25:13 +0100 Subject: [PATCH 06/14] mypy fix --- pandas/io/formats/style_render.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 069908161214b..b2c771bcc64c4 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -1044,7 +1044,7 @@ def format_index( thousands: str | None = None, escape: str | None = None, hyperlinks: str | None = None, - aliases: list[str] | None = None, + aliases: list[str] | list[list[str]] | None = None, ) -> StylerRenderer: r""" Format the text display value of index labels or column headers. @@ -1224,13 +1224,11 @@ def format_index( ) ) - aliases_unset = aliases is None - - if formatting_args_unset and level is None and aliases_unset: + if formatting_args_unset and level is None and aliases is None: # clear the formatter / revert to default and avoid looping display_funcs_.clear() - elif not aliases_unset: # then apply a formatting function from arg: aliases + elif aliases is not None: # then apply a formatting function from arg: aliases if not formatting_args_unset: raise ValueError( "``aliases`` cannot be supplied together with any of " From f48b43c2517e23a2845f995ade4c8cba2d028c8a Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Sun, 9 Jan 2022 21:19:32 +0100 Subject: [PATCH 07/14] whats new --- doc/source/whatsnew/v1.5.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index e723918ad8b4b..a46852f75fe18 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -20,6 +20,7 @@ Styler ^^^^^^ - New method :meth:`.Styler.to_string` for alternative customisable output methods (:issue:`44502`) + - New keyword argument ``aliases`` added to :meth:`.Styler.format_index` to allow simple label string replacement (:issue:`45288`) .. _whatsnew_150.enhancements.enhancement2: From 9bcc32095f1ad17694945fd8c42acce065d0ff32 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Mon, 10 Jan 2022 06:53:11 +0100 Subject: [PATCH 08/14] doctest skip --- pandas/io/formats/style_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 67c793081804e..74cec3be7bd6d 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -1211,7 +1211,7 @@ def format_index( Using ``aliases`` to overwrite column names of remaining **visible** items. >>> df = pd.DataFrame([[1, 2, 3]], ... columns=pd.MultiIndex.from_product([[1, 2, 3], ["X"]])) - >>> styler = df.style + >>> styler = df.style # doctest: +SKIP 1 2 3 X X X 0 1 2 3 From a6952bbf20e216a88502e3fd8c5a51eafed52d91 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Sun, 16 Jan 2022 15:02:39 +0100 Subject: [PATCH 09/14] doc string fixes --- pandas/io/formats/style_render.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 74cec3be7bd6d..f3705c199f426 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -1203,12 +1203,14 @@ def format_index( \end{tabular} Using ``aliases`` to overwrite column names. + >>> df = pd.DataFrame([[1, 2, 3]], columns=[1, 2, 3]) >>> df.style.format_index(axis=1, aliases=["A", "B", "C"]) # doctest: +SKIP A B C 0 1 2 3 Using ``aliases`` to overwrite column names of remaining **visible** items. + >>> df = pd.DataFrame([[1, 2, 3]], ... columns=pd.MultiIndex.from_product([[1, 2, 3], ["X"]])) >>> styler = df.style # doctest: +SKIP From 48b2202a220a9689eacaf8663cb67efd5c31c00a Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Wed, 2 Feb 2022 19:15:35 +0100 Subject: [PATCH 10/14] refactor --- pandas/io/formats/style_render.py | 42 ++++++++++---------- pandas/tests/io/formats/style/test_format.py | 22 +++++----- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index f3705c199f426..bc2197c4324e7 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -1063,7 +1063,7 @@ def format_index( thousands: str | None = None, escape: str | None = None, hyperlinks: str | None = None, - aliases: list[str] | list[list[str]] | None = None, + alias: list[str] | list[list[str]] | None = None, ) -> StylerRenderer: r""" Format the text display value of index labels or column headers. @@ -1075,11 +1075,11 @@ def format_index( 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`` or ``aliases`` to the index or column + Whether to apply the ``formatter`` or ``alias`` to the index or column headers. level : int, str, list - The level(s) over which to apply the generic ``formatter``, or ``aliases``. - In the case of ``aliases`` defaults to the last level of a MultiIndex, + The level(s) over which to apply the generic ``formatter``, or ``alias``. + In the case of ``alias`` defaults to the last level of a MultiIndex, for the reason that the last level is never sparsified. na_rep : str, optional Representation for missing values. @@ -1102,7 +1102,7 @@ def format_index( Convert string patterns containing https://, http://, ftp:// or www. to HTML tags as clickable URL hyperlinks if "html", or LaTeX \href commands if "latex". - aliases : list of str, list of list of str + alias : list of str, list of list of str Values to replace the existing index or column headers. If specifying more than one ``level`` then this should be a list containing sub-lists for each identified level, in the respective order. @@ -1145,7 +1145,7 @@ def format_index( `ValueError` will be raised. Since it is not possible to apply a generic function which will return an - arbitrary set of column aliases, the argument ``aliases`` provides the + arbitrary set of column aliases, the argument ``alias`` provides the ability to automate this, across individual index levels if necessary. Examples @@ -1202,14 +1202,14 @@ def format_index( 0 & 1 & 2 & 3 \\ \end{tabular} - Using ``aliases`` to overwrite column names. + Using ``alias`` to overwrite column names. >>> df = pd.DataFrame([[1, 2, 3]], columns=[1, 2, 3]) - >>> df.style.format_index(axis=1, aliases=["A", "B", "C"]) # doctest: +SKIP + >>> df.style.format_index(axis=1, alias=["A", "B", "C"]) # doctest: +SKIP A B C 0 1 2 3 - Using ``aliases`` to overwrite column names of remaining **visible** items. + Using ``alias`` to overwrite column names of remaining **visible** items. >>> df = pd.DataFrame([[1, 2, 3]], ... columns=pd.MultiIndex.from_product([[1, 2, 3], ["X"]])) @@ -1220,7 +1220,7 @@ def format_index( >>> styler.hide([2], axis=1) # hides a column as a `subset` hide ... .hide(level=1, axis=1) # hides the entire axis level - ... .format_index(axis=1, aliases=["A", "C"], level=0) # doctest: +SKIP + ... .format_index(axis=1, alias=["A", "C"], level=0) # doctest: +SKIP A C 0 1 3 """ @@ -1245,14 +1245,14 @@ def format_index( ) ) - if formatting_args_unset and level is None and aliases is None: + if formatting_args_unset and level is None and alias is None: # clear the formatter / revert to default and avoid looping display_funcs_.clear() - elif aliases is not None: # then apply a formatting function from arg: aliases + elif alias is not None: # then apply a formatting function from arg: alias if not formatting_args_unset: raise ValueError( - "``aliases`` cannot be supplied together with any of " + "``alias`` cannot be supplied together with any of " "``formatter``, ``precision``, ``decimal``, ``na_rep``, " "``escape``, or ``hyperlinks``." ) @@ -1260,22 +1260,22 @@ def format_index( visible_len = len(obj) - len(set(hidden_labels)) if level is None: levels_ = [obj.nlevels - 1] # default to last level - elif len(levels_) > 1 and len(aliases) != len(levels_): + elif len(levels_) > 1 and len(alias) != len(levels_): raise ValueError( f"``level`` specifies {len(levels_)} levels but the length of " - f"``aliases``, {len(aliases)}, does not match." + f"``alias``, {len(alias)}, does not match." ) - def alias(x, value): + def alias_(x, value): return value for i, lvl in enumerate(levels_): - level_aliases = aliases[i] if len(levels_) > 1 else aliases - if len(level_aliases) != visible_len: + level_alias = alias[i] if len(levels_) > 1 else alias + if len(level_alias) != visible_len: raise ValueError( - "``aliases`` must be of length equal to the number of " + "``alias`` must be of length equal to the number of " "visible labels along ``axis``. If ``level`` is given and " - "contains more than one level ``aliases`` should be a " + "contains more than one level ``alias`` should be a " "list of lists with each sub-list having length equal to" "the number of visible labels along ``axis``." ) @@ -1286,7 +1286,7 @@ def alias(x, value): if i not in hidden_labels ] ): - display_funcs_[idx] = partial(alias, value=level_aliases[ai]) + display_funcs_[idx] = partial(alias_, value=level_alias[ai]) else: # then apply a formatting function from arg: formatter if not isinstance(formatter, dict): diff --git a/pandas/tests/io/formats/style/test_format.py b/pandas/tests/io/formats/style/test_format.py index 667e18d2d3701..ddf4b60cf1638 100644 --- a/pandas/tests/io/formats/style/test_format.py +++ b/pandas/tests/io/formats/style/test_format.py @@ -437,7 +437,7 @@ def test_1level_multiindex(): def test_basic_alias(styler): - styler.format_index(axis=1, aliases=["alias1", "alias2"]) + styler.format_index(axis=1, alias=["alias1", "alias2"]) ctx = styler._translate(True, True) assert ctx["head"][0][1]["value"] == "A" assert ctx["head"][0][1]["display_value"] == "alias1" # alias @@ -447,7 +447,7 @@ def test_basic_alias(styler): def test_basic_alias_hidden_column(styler): styler.hide(subset="A", axis=1) - styler.format_index(axis=1, aliases=["alias2"]) + styler.format_index(axis=1, alias=["alias2"]) ctx = styler._translate(True, True) assert ctx["head"][0][1]["value"] == "A" assert ctx["head"][0][1]["display_value"] == "A" # no alias for hidden @@ -459,7 +459,7 @@ def test_basic_alias_hidden_column(styler): def test_alias_single_levels(df, level): df.columns = MultiIndex.from_tuples([("X", "A"), ("Y", "B")]) styler = Styler(df, cell_ids=False, uuid_len=0) - styler.format_index(axis=1, level=level, aliases=["alias1", "alias2"]) + styler.format_index(axis=1, level=level, alias=["alias1", "alias2"]) ctx = styler._translate(True, True) print(ctx["head"]) assert len(ctx["head"]) == 2 # MultiIndex levels @@ -474,7 +474,7 @@ def test_alias_single_levels(df, level): def test_alias_multi_levels_order(df, level): df.columns = MultiIndex.from_tuples([("X", "A"), ("Y", "B")]) styler = Styler(df, cell_ids=False, uuid_len=0) - styler.format_index(axis=1, level=level, aliases=[["a1", "a2"], ["b1", "b2"]]) + styler.format_index(axis=1, level=level, alias=[["a1", "a2"], ["b1", "b2"]]) ctx = styler._translate(True, True) assert ctx["head"][1 - level[1]][1]["display_value"] == "a1" @@ -484,7 +484,7 @@ def test_alias_multi_levels_order(df, level): @pytest.mark.parametrize( - "level, aliases", + "level, alias", [ ([0, 1], ["alias1", "alias2"]), # no sublists ([0], ["alias1"]), # too short @@ -493,24 +493,24 @@ def test_alias_multi_levels_order(df, level): ([0, 1], [["a1", "a2"], ["a1", "a2", "a3"]]), # sublist too long ], ) -def test_alias_warning(df, level, aliases): +def test_alias_warning(df, level, alias): df.columns = MultiIndex.from_tuples([("X", "A"), ("Y", "B")]) styler = Styler(df, cell_ids=False, uuid_len=0) - msg = "``aliases`` must be of length equal to" + msg = "``alias`` must be of length equal to" with pytest.raises(ValueError, match=msg): - styler.format_index(axis=1, level=level, aliases=aliases) + styler.format_index(axis=1, level=level, alias=alias) @pytest.mark.parametrize( - "level, aliases", + "level, alias", [ ([0, 1], [["a1", "a2"]]), # too few sublists ([0, 1], [["a1", "a2"], ["a1", "a2"], ["a1", "a2"]]), # too many sublists ], ) -def test_alias_warning2(df, level, aliases): +def test_alias_warning2(df, level, alias): df.columns = MultiIndex.from_tuples([("X", "A"), ("Y", "B")]) styler = Styler(df, cell_ids=False, uuid_len=0) msg = "``level`` specifies 2 levels but the length of" with pytest.raises(ValueError, match=msg): - styler.format_index(axis=1, level=level, aliases=aliases) + styler.format_index(axis=1, level=level, alias=alias) From 85b7f6dca3b5f95bebc3bb0842321d464b213eaa Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Wed, 2 Feb 2022 19:17:40 +0100 Subject: [PATCH 11/14] whats new update --- doc/source/whatsnew/v1.5.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index d6af6c6a8464a..692eeb60d79c0 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -20,7 +20,7 @@ Styler ^^^^^^ - New method :meth:`.Styler.to_string` for alternative customisable output methods (:issue:`44502`) - - New keyword argument ``aliases`` added to :meth:`.Styler.format_index` to allow simple label string replacement (:issue:`45288`) + - New keyword argument ``alias`` added to :meth:`.Styler.format_index` to allow simple label string replacement (:issue:`45288`) - Various bug fixes, see below. .. _whatsnew_150.enhancements.enhancement2: From ece8b101f23ac8368346b81e15240b028684c584 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (MBP)" Date: Wed, 2 Mar 2022 16:32:43 +0100 Subject: [PATCH 12/14] change arg `alias` to `rename` --- pandas/io/formats/style_render.py | 36 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index ad3185cdac0b4..a087bbcae05ec 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -1145,7 +1145,7 @@ def format_index( thousands: str | None = None, escape: str | None = None, hyperlinks: str | None = None, - alias: list[str] | list[list[str]] | None = None, + rename: list[str] | list[list[str]] | None = None, ) -> StylerRenderer: r""" Format the text display value of index labels or column headers. @@ -1157,11 +1157,11 @@ def format_index( 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`` or ``alias`` to the index or column + Whether to apply the ``formatter`` or ``rename`` to the index or column headers. level : int, str, list - The level(s) over which to apply the generic ``formatter``, or ``alias``. - In the case of ``alias`` defaults to the last level of a MultiIndex, + The level(s) over which to apply the generic ``formatter``, or ``rename``. + In the case of ``rename`` defaults to the last level of a MultiIndex, for the reason that the last level is never sparsified. na_rep : str, optional Representation for missing values. @@ -1184,7 +1184,7 @@ def format_index( Convert string patterns containing https://, http://, ftp:// or www. to HTML tags as clickable URL hyperlinks if "html", or LaTeX \href commands if "latex". - alias : list of str, list of list of str + rename : list of str, list of list of str Values to replace the existing index or column headers. If specifying more than one ``level`` then this should be a list containing sub-lists for each identified level, in the respective order. @@ -1227,7 +1227,7 @@ def format_index( `ValueError` will be raised. Since it is not possible to apply a generic function which will return an - arbitrary set of column aliases, the argument ``alias`` provides the + arbitrary set of column aliases, the argument ``rename`` provides the ability to automate this, across individual index levels if necessary. Examples @@ -1284,14 +1284,14 @@ def format_index( 0 & 1 & 2 & 3 \\ \end{tabular} - Using ``alias`` to overwrite column names. + Using ``rename`` to overwrite column names. >>> df = pd.DataFrame([[1, 2, 3]], columns=[1, 2, 3]) - >>> df.style.format_index(axis=1, alias=["A", "B", "C"]) # doctest: +SKIP + >>> df.style.format_index(axis=1, rename=["A", "B", "C"]) # doctest: +SKIP A B C 0 1 2 3 - Using ``alias`` to overwrite column names of remaining **visible** items. + Using ``rename`` to overwrite column names of remaining **visible** items. >>> df = pd.DataFrame([[1, 2, 3]], ... columns=pd.MultiIndex.from_product([[1, 2, 3], ["X"]])) @@ -1302,7 +1302,7 @@ def format_index( >>> styler.hide([2], axis=1) # hides a column as a `subset` hide ... .hide(level=1, axis=1) # hides the entire axis level - ... .format_index(axis=1, alias=["A", "C"], level=0) # doctest: +SKIP + ... .format_index(axis=1, rename=["A", "C"], level=0) # doctest: +SKIP A C 0 1 3 """ @@ -1327,14 +1327,14 @@ def format_index( ) ) - if formatting_args_unset and level is None and alias is None: + if formatting_args_unset and level is None and rename is None: # clear the formatter / revert to default and avoid looping display_funcs_.clear() - elif alias is not None: # then apply a formatting function from arg: alias + elif rename is not None: # then apply a formatting function from arg: rename if not formatting_args_unset: raise ValueError( - "``alias`` cannot be supplied together with any of " + "``rename`` cannot be supplied together with any of " "``formatter``, ``precision``, ``decimal``, ``na_rep``, " "``escape``, or ``hyperlinks``." ) @@ -1342,22 +1342,22 @@ def format_index( visible_len = len(obj) - len(set(hidden_labels)) if level is None: levels_ = [obj.nlevels - 1] # default to last level - elif len(levels_) > 1 and len(alias) != len(levels_): + elif len(levels_) > 1 and len(rename) != len(levels_): raise ValueError( f"``level`` specifies {len(levels_)} levels but the length of " - f"``alias``, {len(alias)}, does not match." + f"``rename``, {len(rename)}, does not match." ) def alias_(x, value): return value for i, lvl in enumerate(levels_): - level_alias = alias[i] if len(levels_) > 1 else alias + level_alias = rename[i] if len(levels_) > 1 else rename if len(level_alias) != visible_len: raise ValueError( - "``alias`` must be of length equal to the number of " + "``rename`` must be of length equal to the number of " "visible labels along ``axis``. If ``level`` is given and " - "contains more than one level ``alias`` should be a " + "contains more than one level ``rename`` should be a " "list of lists with each sub-list having length equal to" "the number of visible labels along ``axis``." ) From 33d89b51ce3b430104257ebd341b33209e415ce4 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (MBP)" Date: Wed, 2 Mar 2022 16:35:43 +0100 Subject: [PATCH 13/14] change arg `alias` to `rename` --- doc/source/whatsnew/v1.5.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index e99a49c492ce8..78e600593d799 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -20,7 +20,7 @@ Styler ^^^^^^ - New method :meth:`.Styler.to_string` for alternative customisable output methods (:issue:`44502`) - - New keyword argument ``alias`` added to :meth:`.Styler.format_index` to allow simple label string replacement (:issue:`45288`) + - New keyword argument ``rename`` added to :meth:`.Styler.format_index` to allow simple label string replacement (:issue:`45288`) - Various bug fixes, see below. - Added the ability to render ``border`` and ``border-{side}`` CSS properties in Excel (:issue:`42276`) - Added a new method :meth:`.Styler.concat` which allows adding customised footer rows to visualise additional calculations on the data, e.g. totals and counts etc. (:issue:`43875`, :issue:`46186`) From 6eabbc3aaf7b169f9cc5eb1fdcb66708ba95d687 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (MBP)" Date: Wed, 2 Mar 2022 16:48:16 +0100 Subject: [PATCH 14/14] change arg `alias` to `rename` --- pandas/tests/io/formats/style/test_format.py | 31 ++++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/pandas/tests/io/formats/style/test_format.py b/pandas/tests/io/formats/style/test_format.py index ddf4b60cf1638..829785e54e4a0 100644 --- a/pandas/tests/io/formats/style/test_format.py +++ b/pandas/tests/io/formats/style/test_format.py @@ -436,8 +436,8 @@ def test_1level_multiindex(): assert ctx["body"][1][0]["is_visible"] is True -def test_basic_alias(styler): - styler.format_index(axis=1, alias=["alias1", "alias2"]) +def test_basic_rename(styler): + styler.format_index(axis=1, rename=["alias1", "alias2"]) ctx = styler._translate(True, True) assert ctx["head"][0][1]["value"] == "A" assert ctx["head"][0][1]["display_value"] == "alias1" # alias @@ -445,9 +445,9 @@ def test_basic_alias(styler): assert ctx["head"][0][2]["display_value"] == "alias2" # alias -def test_basic_alias_hidden_column(styler): +def test_basic_rename_hidden_column(styler): styler.hide(subset="A", axis=1) - styler.format_index(axis=1, alias=["alias2"]) + styler.format_index(axis=1, rename=["alias2"]) ctx = styler._translate(True, True) assert ctx["head"][0][1]["value"] == "A" assert ctx["head"][0][1]["display_value"] == "A" # no alias for hidden @@ -456,12 +456,11 @@ def test_basic_alias_hidden_column(styler): @pytest.mark.parametrize("level", [None, 0, 1]) -def test_alias_single_levels(df, level): +def test_rename_single_levels(df, level): df.columns = MultiIndex.from_tuples([("X", "A"), ("Y", "B")]) styler = Styler(df, cell_ids=False, uuid_len=0) - styler.format_index(axis=1, level=level, alias=["alias1", "alias2"]) + styler.format_index(axis=1, level=level, rename=["alias1", "alias2"]) ctx = styler._translate(True, True) - print(ctx["head"]) assert len(ctx["head"]) == 2 # MultiIndex levels level = 1 if level is None else level # defaults to last @@ -471,10 +470,10 @@ def test_alias_single_levels(df, level): @pytest.mark.parametrize("level", [[0, 1], [1, 0]]) -def test_alias_multi_levels_order(df, level): +def test_rename_multi_levels_order(df, level): df.columns = MultiIndex.from_tuples([("X", "A"), ("Y", "B")]) styler = Styler(df, cell_ids=False, uuid_len=0) - styler.format_index(axis=1, level=level, alias=[["a1", "a2"], ["b1", "b2"]]) + styler.format_index(axis=1, level=level, rename=[["a1", "a2"], ["b1", "b2"]]) ctx = styler._translate(True, True) assert ctx["head"][1 - level[1]][1]["display_value"] == "a1" @@ -484,7 +483,7 @@ def test_alias_multi_levels_order(df, level): @pytest.mark.parametrize( - "level, alias", + "level, rename", [ ([0, 1], ["alias1", "alias2"]), # no sublists ([0], ["alias1"]), # too short @@ -493,24 +492,24 @@ def test_alias_multi_levels_order(df, level): ([0, 1], [["a1", "a2"], ["a1", "a2", "a3"]]), # sublist too long ], ) -def test_alias_warning(df, level, alias): +def test_rename_warning(df, level, rename): df.columns = MultiIndex.from_tuples([("X", "A"), ("Y", "B")]) styler = Styler(df, cell_ids=False, uuid_len=0) - msg = "``alias`` must be of length equal to" + msg = "``rename`` must be of length equal to" with pytest.raises(ValueError, match=msg): - styler.format_index(axis=1, level=level, alias=alias) + styler.format_index(axis=1, level=level, rename=rename) @pytest.mark.parametrize( - "level, alias", + "level, rename", [ ([0, 1], [["a1", "a2"]]), # too few sublists ([0, 1], [["a1", "a2"], ["a1", "a2"], ["a1", "a2"]]), # too many sublists ], ) -def test_alias_warning2(df, level, alias): +def test_rename_warning2(df, level, rename): df.columns = MultiIndex.from_tuples([("X", "A"), ("Y", "B")]) styler = Styler(df, cell_ids=False, uuid_len=0) msg = "``level`` specifies 2 levels but the length of" with pytest.raises(ValueError, match=msg): - styler.format_index(axis=1, level=level, alias=alias) + styler.format_index(axis=1, level=level, rename=rename)