From b3bc28b24e788e64b8832cfa3444085b19db81ce Mon Sep 17 00:00:00 2001 From: Tsvika S Date: Thu, 20 Oct 2022 17:39:45 +0300 Subject: [PATCH 01/18] allow chaining of style.concat --- pandas/io/formats/style.py | 2 +- pandas/io/formats/style_render.py | 43 +++++++------- pandas/tests/io/formats/style/test_html.py | 57 +++++++++++++++++++ .../tests/io/formats/style/test_to_latex.py | 20 +++++++ .../tests/io/formats/style/test_to_string.py | 34 +++++++++++ 5 files changed, 135 insertions(+), 21 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 30cd707926e05..75b8f4c17656f 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -367,7 +367,7 @@ def concat(self, other: Styler) -> Styler: "number of index levels must be same in `other` " "as in `Styler`. See documentation for suggestions." ) - self.concatenated = other + self.concatenated.append(other) return self def _repr_html_(self) -> str | None: diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 0f93027f3f775..e2cc01e90d96f 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -119,7 +119,7 @@ def __init__( "blank": "blank", "foot": "foot", } - self.concatenated: StylerRenderer | None = None + self.concatenated: list[StylerRenderer] = [] # add rendering variables self.hide_index_names: bool = False self.hide_column_names: bool = False @@ -161,27 +161,30 @@ def _render( stylers for use within `_translate_latex` """ self._compute() - dx = None - if self.concatenated is not None: - self.concatenated.hide_index_ = self.hide_index_ - self.concatenated.hidden_columns = self.hidden_columns - self.concatenated.css = { + dxs = [] + for concatenated in self.concatenated: + concatenated.hide_index_ = self.hide_index_ + concatenated.hidden_columns = self.hidden_columns + concatenated.css = { **self.css, "data": f"{self.css['foot']}_{self.css['data']}", "row_heading": f"{self.css['foot']}_{self.css['row_heading']}", "row": f"{self.css['foot']}_{self.css['row']}", "foot": self.css["foot"], } - dx = self.concatenated._render( + dx = concatenated._render( sparse_index, sparse_columns, max_rows, max_cols, blank ) + dxs.append(dx) - for (r, c), v in self.concatenated.ctx.items(): + for (r, c), v in concatenated.ctx.items(): self.ctx[(r + len(self.index), c)] = v - for (r, c), v in self.concatenated.ctx_index.items(): + for (r, c), v in concatenated.ctx_index.items(): self.ctx_index[(r + len(self.index), c)] = v - d = self._translate(sparse_index, sparse_columns, max_rows, max_cols, blank, dx) + d = self._translate( + sparse_index, sparse_columns, max_rows, max_cols, blank, dxs + ) return d def _render_html( @@ -258,7 +261,7 @@ def _translate( max_rows: int | None = None, max_cols: int | None = None, blank: str = " ", - dx: dict | None = None, + dxs: list[dict] | None = None, ): """ Process Styler data and settings into a dict for template rendering. @@ -278,8 +281,8 @@ def _translate( Specific max rows and cols. max_elements always take precedence in render. blank : str Entry to top-left blank cells. - dx : dict - The render dict of the concatenated Styler. + dxs : list[dict] + The render dicts of the concatenated Stylers. Returns ------- @@ -287,6 +290,8 @@ def _translate( The following structure: {uuid, table_styles, caption, head, body, cellstyle, table_attributes} """ + if dxs is None: + dxs = [] self.css["blank_value"] = blank # construct render dict @@ -340,7 +345,7 @@ def _translate( ] d.update({k: map}) - if dx is not None: # self.concatenated is not None + for dx in dxs: # self.concatenated is not empty d["body"].extend(dx["body"]) # type: ignore[union-attr] d["cellstyle"].extend(dx["cellstyle"]) # type: ignore[union-attr] d["cellstyle_index"].extend(dx["cellstyle"]) # type: ignore[union-attr] @@ -854,13 +859,11 @@ def concatenated_visible_rows(obj, n, row_indices): row_indices.extend( [r + n for r in range(len(obj.index)) if r not in obj.hidden_rows] ) - return ( - row_indices - if obj.concatenated is None - else concatenated_visible_rows( - obj.concatenated, n + len(obj.index), row_indices + for concatenated in obj.concatenated: + row_indices = concatenated_visible_rows( + concatenated, n + len(obj.index), row_indices ) - ) + return row_indices body = [] for r, row in zip(concatenated_visible_rows(self, 0, []), d["body"]): diff --git a/pandas/tests/io/formats/style/test_html.py b/pandas/tests/io/formats/style/test_html.py index 43eb4cb0502a1..93781f7cc9342 100644 --- a/pandas/tests/io/formats/style/test_html.py +++ b/pandas/tests/io/formats/style/test_html.py @@ -838,3 +838,60 @@ def test_concat(styler): """ ) assert expected in result + + +def test_concat_recursion(styler): + styler1 = styler + styler2 = styler.data.agg(["mean"]).style + styler3 = styler.data.agg(["mean"]).style + styler1.concat(styler2.concat(styler3)).set_uuid("X") + result = styler.to_html() + # notice that the second concat (last of the output html), + # there are two `foot_` in the id and class + s = "foot_foot" + expected = dedent( + f"""\ + + b + 2.690000 + + + mean + 2.650000 + + + mean + 2.650000 + + + + """ + ) + assert expected in result + + +def test_concat_chain(styler): + styler1 = styler + styler2 = styler.data.agg(["mean"]).style + styler3 = styler.data.agg(["mean"]).style + styler1.concat(styler2).concat(styler3).set_uuid("X") + result = styler.to_html() + expected = dedent( + """\ + + b + 2.690000 + + + mean + 2.650000 + + + mean + 2.650000 + + + + """ + ) + assert expected in result diff --git a/pandas/tests/io/formats/style/test_to_latex.py b/pandas/tests/io/formats/style/test_to_latex.py index b295c955a8967..1c67d125664f8 100644 --- a/pandas/tests/io/formats/style/test_to_latex.py +++ b/pandas/tests/io/formats/style/test_to_latex.py @@ -1034,6 +1034,26 @@ def test_concat_recursion(): assert result == expected +def test_concat_chain(): + # tests hidden row recursion and applied styles + styler1 = DataFrame([[1], [9]]).style.hide([1]).highlight_min(color="red") + styler2 = DataFrame([[9], [2]]).style.hide([0]).highlight_min(color="green") + styler3 = DataFrame([[3], [9]]).style.hide([1]).highlight_min(color="blue") + + result = styler1.concat(styler2).concat(styler3).to_latex(convert_css=True) + expected = dedent( + """\ + \\begin{tabular}{lr} + & 0 \\\\ + 0 & {\\cellcolor{red}} 1 \\\\ + 1 & {\\cellcolor{green}} 2 \\\\ + 0 & {\\cellcolor{blue}} 3 \\\\ + \\end{tabular} + """ + ) + assert result == expected + + @pytest.mark.parametrize( "df, expected", [ diff --git a/pandas/tests/io/formats/style/test_to_string.py b/pandas/tests/io/formats/style/test_to_string.py index fcac304b8c3bb..8a0ed1830e571 100644 --- a/pandas/tests/io/formats/style/test_to_string.py +++ b/pandas/tests/io/formats/style/test_to_string.py @@ -53,3 +53,37 @@ def test_concat(styler): """ ) assert result == expected + + +def test_concat_recursion(styler): + styler1 = styler + styler2 = styler.data.agg(["sum"]).style + styler3 = styler.data.agg(["sum"]).style + result = styler1.concat(styler2.concat(styler3)).to_string() + expected = dedent( + """\ + A B C + 0 0 -0.61 ab + 1 1 -1.22 cd + sum 1 -1.830000 abcd + sum 1 -1.830000 abcd + """ + ) + assert result == expected + + +def test_concat_chain(styler): + styler1 = styler + styler2 = styler.data.agg(["sum"]).style + styler3 = styler.data.agg(["sum"]).style + result = styler1.concat(styler2).concat(styler3).to_string() + expected = dedent( + """\ + A B C + 0 0 -0.61 ab + 1 1 -1.22 cd + sum 1 -1.830000 abcd + sum 1 -1.830000 abcd + """ + ) + assert result == expected From 4d77906bfe3b49781ddf039b8aa2ed78884432ed Mon Sep 17 00:00:00 2001 From: Tsvika S Date: Thu, 20 Oct 2022 19:14:54 +0300 Subject: [PATCH 02/18] updated what's new --- doc/source/whatsnew/v1.5.2.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v1.5.2.rst b/doc/source/whatsnew/v1.5.2.rst index 540ca2b12165c..f31c65ef920aa 100644 --- a/doc/source/whatsnew/v1.5.2.rst +++ b/doc/source/whatsnew/v1.5.2.rst @@ -27,6 +27,7 @@ Fixed regressions Bug fixes ~~~~~~~~~ - Bug in the Copy-on-Write implementation losing track of views in certain chained indexing cases (:issue:`48996`) +- Bug when chaining several :meth:`.Styler.concat` calls only the last styler was concated (:issue:`49207`) - .. --------------------------------------------------------------------------- From aa1484f0a770e6a638ff3757f2aeb66359db7905 Mon Sep 17 00:00:00 2001 From: Tsvika S Date: Tue, 8 Nov 2022 16:43:31 +0200 Subject: [PATCH 03/18] tests: different styles used, to verify that they are all apllied correctly --- pandas/tests/io/formats/style/test_html.py | 18 ++++++++++-------- .../tests/io/formats/style/test_to_string.py | 18 ++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/pandas/tests/io/formats/style/test_html.py b/pandas/tests/io/formats/style/test_html.py index 93781f7cc9342..d53ebccf640d9 100644 --- a/pandas/tests/io/formats/style/test_html.py +++ b/pandas/tests/io/formats/style/test_html.py @@ -841,9 +841,10 @@ def test_concat(styler): def test_concat_recursion(styler): + df = styler.data styler1 = styler - styler2 = styler.data.agg(["mean"]).style - styler3 = styler.data.agg(["mean"]).style + styler2 = Styler(df.agg(["mean"]), uuid_len=0, precision=3) + styler3 = Styler(df.agg(["mean"]), uuid_len=0, precision=4) styler1.concat(styler2.concat(styler3)).set_uuid("X") result = styler.to_html() # notice that the second concat (last of the output html), @@ -857,11 +858,11 @@ def test_concat_recursion(styler): mean - 2.650000 + 2.650 mean - 2.650000 + 2.6500 @@ -871,9 +872,10 @@ def test_concat_recursion(styler): def test_concat_chain(styler): + df = styler.data styler1 = styler - styler2 = styler.data.agg(["mean"]).style - styler3 = styler.data.agg(["mean"]).style + styler2 = Styler(df.agg(["mean"]), uuid_len=0, precision=3) + styler3 = Styler(df.agg(["mean"]), uuid_len=0, precision=4) styler1.concat(styler2).concat(styler3).set_uuid("X") result = styler.to_html() expected = dedent( @@ -884,11 +886,11 @@ def test_concat_chain(styler): mean - 2.650000 + 2.650 mean - 2.650000 + 2.6500 diff --git a/pandas/tests/io/formats/style/test_to_string.py b/pandas/tests/io/formats/style/test_to_string.py index 8a0ed1830e571..913857396446c 100644 --- a/pandas/tests/io/formats/style/test_to_string.py +++ b/pandas/tests/io/formats/style/test_to_string.py @@ -56,34 +56,36 @@ def test_concat(styler): def test_concat_recursion(styler): + df = styler.data styler1 = styler - styler2 = styler.data.agg(["sum"]).style - styler3 = styler.data.agg(["sum"]).style + styler2 = Styler(df.agg(["sum"]), uuid_len=0, precision=3) + styler3 = Styler(df.agg(["sum"]), uuid_len=0, precision=4) result = styler1.concat(styler2.concat(styler3)).to_string() expected = dedent( """\ A B C 0 0 -0.61 ab 1 1 -1.22 cd - sum 1 -1.830000 abcd - sum 1 -1.830000 abcd + sum 1 -1.830 abcd + sum 1 -1.8300 abcd """ ) assert result == expected def test_concat_chain(styler): + df = styler.data styler1 = styler - styler2 = styler.data.agg(["sum"]).style - styler3 = styler.data.agg(["sum"]).style + styler2 = Styler(df.agg(["sum"]), uuid_len=0, precision=3) + styler3 = Styler(df.agg(["sum"]), uuid_len=0, precision=4) result = styler1.concat(styler2).concat(styler3).to_string() expected = dedent( """\ A B C 0 0 -0.61 ab 1 1 -1.22 cd - sum 1 -1.830000 abcd - sum 1 -1.830000 abcd + sum 1 -1.830 abcd + sum 1 -1.8300 abcd """ ) assert result == expected From 6de19440ec2eabe6dcf2ff8aa75a899e966797d3 Mon Sep 17 00:00:00 2001 From: Tsvika S Date: Tue, 8 Nov 2022 17:39:47 +0200 Subject: [PATCH 04/18] updated docs --- pandas/io/formats/style.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 75b8f4c17656f..1f057d88078e0 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -316,6 +316,10 @@ def concat(self, other: Styler) -> Styler: inherited from the original Styler and not ``other``. - hidden columns and hidden index levels will be inherited from the original Styler + - ``css`` will be inherited from the original Styler, and the value of + keys ``data``, ``row_heading`` and ``row`` will be prepended with + ``foot_``. if several stylers are chained, they will all have only one + ``foot_`` prepended. A common use case is to concatenate user defined functions with ``DataFrame.agg`` or with described statistics via ``DataFrame.describe``. From 8d412108126d3cc0b13fb5ce161908d4c0bff16a Mon Sep 17 00:00:00 2001 From: Tsvika S Date: Tue, 8 Nov 2022 18:46:13 +0200 Subject: [PATCH 05/18] fix: adding the concatenated to self.ctx correctly --- pandas/io/formats/style_render.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index e2cc01e90d96f..fcf6b24429317 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -162,6 +162,7 @@ def _render( """ self._compute() dxs = [] + ctx_len = len(self.index) for concatenated in self.concatenated: concatenated.hide_index_ = self.hide_index_ concatenated.hidden_columns = self.hidden_columns @@ -178,9 +179,21 @@ def _render( dxs.append(dx) for (r, c), v in concatenated.ctx.items(): - self.ctx[(r + len(self.index), c)] = v + self.ctx[(r + ctx_len, c)] = v for (r, c), v in concatenated.ctx_index.items(): - self.ctx_index[(r + len(self.index), c)] = v + self.ctx_index[(r + ctx_len, c)] = v + + ctx_len += ( + max( + max(r for r, c in concatenated.ctx.keys()) + if concatenated.ctx.keys() + else -1, + max(r for r, c in concatenated.ctx_index.keys()) + if concatenated.ctx_index.keys() + else -1, + ) + + 1 + ) d = self._translate( sparse_index, sparse_columns, max_rows, max_cols, blank, dxs @@ -852,21 +865,25 @@ def _translate_latex(self, d: dict, clines: str | None) -> None: for r, row in enumerate(d["head"]) ] - def concatenated_visible_rows(obj, n, row_indices): + def _concatenated_visible_rows(obj, n, row_indices): """ Extract all visible row indices recursively from concatenated stylers. """ row_indices.extend( [r + n for r in range(len(obj.index)) if r not in obj.hidden_rows] ) + n += len(obj.index) for concatenated in obj.concatenated: - row_indices = concatenated_visible_rows( - concatenated, n + len(obj.index), row_indices - ) + n = _concatenated_visible_rows(concatenated, n, row_indices) + return n + + def concatenated_visible_rows(obj): + row_indices = [] + _concatenated_visible_rows(obj, 0, row_indices) return row_indices body = [] - for r, row in zip(concatenated_visible_rows(self, 0, []), d["body"]): + for r, row in zip(concatenated_visible_rows(self), d["body"]): # note: cannot enumerate d["body"] because rows were dropped if hidden # during _translate_body so must zip to acquire the true r-index associated # with the ctx obj which contains the cell styles. From dd4d29d6e156e3a941075ded46f160eda0749797 Mon Sep 17 00:00:00 2001 From: Tsvika S Date: Wed, 9 Nov 2022 11:19:21 +0200 Subject: [PATCH 06/18] added type hint --- 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 fcf6b24429317..7358bd1f2ac3a 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -878,7 +878,7 @@ def _concatenated_visible_rows(obj, n, row_indices): return n def concatenated_visible_rows(obj): - row_indices = [] + row_indices: list[int] = [] _concatenated_visible_rows(obj, 0, row_indices) return row_indices From 0d5a258a1b91b2d36f804fb2a431496113d2960e Mon Sep 17 00:00:00 2001 From: Tsvika Shapira Date: Tue, 15 Nov 2022 11:33:17 +0200 Subject: [PATCH 07/18] Update doc/source/whatsnew/v1.5.2.rst Co-authored-by: JHM Darbyshire <24256554+attack68@users.noreply.github.com> --- doc/source/whatsnew/v1.5.2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.5.2.rst b/doc/source/whatsnew/v1.5.2.rst index f31c65ef920aa..79cc181324e32 100644 --- a/doc/source/whatsnew/v1.5.2.rst +++ b/doc/source/whatsnew/v1.5.2.rst @@ -27,7 +27,7 @@ Fixed regressions Bug fixes ~~~~~~~~~ - Bug in the Copy-on-Write implementation losing track of views in certain chained indexing cases (:issue:`48996`) -- Bug when chaining several :meth:`.Styler.concat` calls only the last styler was concated (:issue:`49207`) +- Bug when chaining several :meth:`.Styler.concat` calls, only the last styler was concatenated (:issue:`49207`) - .. --------------------------------------------------------------------------- From 8cfc69c37f73dcec2f6062ad8982c567f3e5da86 Mon Sep 17 00:00:00 2001 From: Tsvika S Date: Wed, 9 Nov 2022 11:55:23 +0200 Subject: [PATCH 08/18] minor: change case in docstring --- pandas/io/formats/style.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 1f057d88078e0..f33354bc15ca7 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -318,7 +318,7 @@ def concat(self, other: Styler) -> Styler: original Styler - ``css`` will be inherited from the original Styler, and the value of keys ``data``, ``row_heading`` and ``row`` will be prepended with - ``foot_``. if several stylers are chained, they will all have only one + ``foot_``. If several stylers are chained, they will all have only one ``foot_`` prepended. A common use case is to concatenate user defined functions with From 28d937ec1eb38b18cb7cacc9c35ddebd6c2cf127 Mon Sep 17 00:00:00 2001 From: Tsvika S Date: Wed, 16 Nov 2022 01:01:08 +0200 Subject: [PATCH 09/18] removed unneeded uuid_len in tests --- pandas/tests/io/formats/style/test_html.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/tests/io/formats/style/test_html.py b/pandas/tests/io/formats/style/test_html.py index d53ebccf640d9..bf7e33f307b87 100644 --- a/pandas/tests/io/formats/style/test_html.py +++ b/pandas/tests/io/formats/style/test_html.py @@ -843,8 +843,8 @@ def test_concat(styler): def test_concat_recursion(styler): df = styler.data styler1 = styler - styler2 = Styler(df.agg(["mean"]), uuid_len=0, precision=3) - styler3 = Styler(df.agg(["mean"]), uuid_len=0, precision=4) + styler2 = Styler(df.agg(["mean"]), precision=3) + styler3 = Styler(df.agg(["mean"]), precision=4) styler1.concat(styler2.concat(styler3)).set_uuid("X") result = styler.to_html() # notice that the second concat (last of the output html), @@ -874,8 +874,8 @@ def test_concat_recursion(styler): def test_concat_chain(styler): df = styler.data styler1 = styler - styler2 = Styler(df.agg(["mean"]), uuid_len=0, precision=3) - styler3 = Styler(df.agg(["mean"]), uuid_len=0, precision=4) + styler2 = Styler(df.agg(["mean"]), precision=3) + styler3 = Styler(df.agg(["mean"]), precision=4) styler1.concat(styler2).concat(styler3).set_uuid("X") result = styler.to_html() expected = dedent( From 6c85396d69b9abe381f98b5c0a3b77371837b6dc Mon Sep 17 00:00:00 2001 From: Tsvika S Date: Wed, 16 Nov 2022 01:04:47 +0200 Subject: [PATCH 10/18] different css classes for different concatenated stylers --- pandas/io/formats/style_render.py | 11 ++++---- pandas/tests/io/formats/style/test_html.py | 30 ++++++++++++---------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 7358bd1f2ac3a..0d8190c5b64f8 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -163,15 +163,16 @@ def _render( self._compute() dxs = [] ctx_len = len(self.index) - for concatenated in self.concatenated: + for i, concatenated in enumerate(self.concatenated): concatenated.hide_index_ = self.hide_index_ concatenated.hidden_columns = self.hidden_columns + foot = f"{self.css['foot']}{i}" concatenated.css = { **self.css, - "data": f"{self.css['foot']}_{self.css['data']}", - "row_heading": f"{self.css['foot']}_{self.css['row_heading']}", - "row": f"{self.css['foot']}_{self.css['row']}", - "foot": self.css["foot"], + "data": f"{foot}_data", + "row_heading": f"{foot}_row_heading", + "row": f"{foot}_row", + "foot": f"{foot}_foot", } dx = concatenated._render( sparse_index, sparse_columns, max_rows, max_cols, blank diff --git a/pandas/tests/io/formats/style/test_html.py b/pandas/tests/io/formats/style/test_html.py index bf7e33f307b87..8cae4c965c28f 100644 --- a/pandas/tests/io/formats/style/test_html.py +++ b/pandas/tests/io/formats/style/test_html.py @@ -823,15 +823,16 @@ def test_concat(styler): other = styler.data.agg(["mean"]).style styler.concat(other).set_uuid("X") result = styler.to_html() + fp = "foot0_" expected = dedent( - """\ + f"""\ b 2.690000 - mean - 2.650000 + mean + 2.650000 @@ -849,7 +850,8 @@ def test_concat_recursion(styler): result = styler.to_html() # notice that the second concat (last of the output html), # there are two `foot_` in the id and class - s = "foot_foot" + fp1 = "foot0_" + fp2 = "foot0_foot0_" expected = dedent( f"""\ @@ -857,12 +859,12 @@ def test_concat_recursion(styler): 2.690000 - mean - 2.650 + mean + 2.650 - mean - 2.6500 + mean + 2.6500 @@ -878,19 +880,21 @@ def test_concat_chain(styler): styler3 = Styler(df.agg(["mean"]), precision=4) styler1.concat(styler2).concat(styler3).set_uuid("X") result = styler.to_html() + fp1 = "foot0_" + fp2 = "foot1_" expected = dedent( - """\ + f"""\ b 2.690000 - mean - 2.650 + mean + 2.650 - mean - 2.6500 + mean + 2.6500 From 5703836c14ccc6a5cc1a4907762b318607e7fe76 Mon Sep 17 00:00:00 2001 From: Tsvika S Date: Wed, 16 Nov 2022 01:49:24 +0200 Subject: [PATCH 11/18] fixed bug with duplicated css styles, added a relevant test --- pandas/io/formats/style_render.py | 4 +- pandas/tests/io/formats/style/test_html.py | 77 +++++++++++++++++++++- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 0d8190c5b64f8..934bdeaf3f0ed 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -362,7 +362,9 @@ def _translate( for dx in dxs: # self.concatenated is not empty d["body"].extend(dx["body"]) # type: ignore[union-attr] d["cellstyle"].extend(dx["cellstyle"]) # type: ignore[union-attr] - d["cellstyle_index"].extend(dx["cellstyle"]) # type: ignore[union-attr] + d["cellstyle_index"].extend( + dx["cellstyle_index"] + ) # type: ignore[union-attr] table_attr = self.table_attributes if not get_option("styler.html.mathjax"): diff --git a/pandas/tests/io/formats/style/test_html.py b/pandas/tests/io/formats/style/test_html.py index 8cae4c965c28f..d878d82f55e51 100644 --- a/pandas/tests/io/formats/style/test_html.py +++ b/pandas/tests/io/formats/style/test_html.py @@ -1,4 +1,7 @@ -from textwrap import dedent +from textwrap import ( + dedent, + indent, +) import numpy as np import pytest @@ -901,3 +904,75 @@ def test_concat_chain(styler): """ ) assert expected in result + + +def test_concat_combined(): + def html_lines(foot_prefix: str): + assert foot_prefix.endswith("_") or foot_prefix == "" + fp = foot_prefix + return indent( + dedent( + f"""\ + + a + 2.610000 + + + b + 2.690000 + + """ + ), + prefix=" " * 4, + ) + + df = DataFrame([[2.61], [2.69]], index=["a", "b"], columns=["A"]) + s1 = df.style.highlight_max(color="red") + s2 = df.style.highlight_max(color="green") + s3 = df.style.highlight_max(color="blue") + s4 = df.style.highlight_max(color="yellow") + + result = s1.concat(s2).concat(s3.concat(s4)).set_uuid("X").to_html() + expected_css = dedent( + """\ + + """ + ) + expected_table = ( + dedent( + """\ + + + + + + + + + """ + ) + + html_lines("") + + html_lines("foot0_") + + html_lines("foot1_") + + html_lines("foot1_foot0_") + + dedent( + """\ + +
 A
+ """ + ) + ) + assert expected_css + expected_table == result From 45323961cfddaad91de7308d5cd335566d55d4b6 Mon Sep 17 00:00:00 2001 From: Tsvika S Date: Wed, 16 Nov 2022 02:35:52 +0200 Subject: [PATCH 12/18] refactor: ctx_len is calculated from the concatenated length --- pandas/io/formats/style_render.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 934bdeaf3f0ed..3198e5b1243a7 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -184,17 +184,7 @@ def _render( for (r, c), v in concatenated.ctx_index.items(): self.ctx_index[(r + ctx_len, c)] = v - ctx_len += ( - max( - max(r for r, c in concatenated.ctx.keys()) - if concatenated.ctx.keys() - else -1, - max(r for r, c in concatenated.ctx_index.keys()) - if concatenated.ctx_index.keys() - else -1, - ) - + 1 - ) + ctx_len += len(concatenated.index) d = self._translate( sparse_index, sparse_columns, max_rows, max_cols, blank, dxs From fe4be54911f279a1333c4ab9236097223a91ebb3 Mon Sep 17 00:00:00 2001 From: Tsvika Shapira Date: Thu, 17 Nov 2022 21:07:26 +0200 Subject: [PATCH 13/18] updated documentation to explain the css of chained concats --- pandas/io/formats/style.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index f33354bc15ca7..53dc01add50fe 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -318,8 +318,10 @@ def concat(self, other: Styler) -> Styler: original Styler - ``css`` will be inherited from the original Styler, and the value of keys ``data``, ``row_heading`` and ``row`` will be prepended with - ``foot_``. If several stylers are chained, they will all have only one - ``foot_`` prepended. + ``foot0_``. If more concats are chained, their styles will be prepended + with ``foot1_``, ''foot_2'', etc., and if a concatenated style have + another concatanated style, the second style will be prepended with + ``foot{parent}_foot{child}_``. A common use case is to concatenate user defined functions with ``DataFrame.agg`` or with described statistics via ``DataFrame.describe``. From 20ce639837af0048899a4d3a0ac598e9726581d8 Mon Sep 17 00:00:00 2001 From: Tsvika S Date: Tue, 22 Nov 2022 11:34:00 +0200 Subject: [PATCH 14/18] moved change to 1.5.3 --- doc/source/whatsnew/v1.5.2.rst | 2 -- doc/source/whatsnew/v1.5.3.rst | 41 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 doc/source/whatsnew/v1.5.3.rst diff --git a/doc/source/whatsnew/v1.5.2.rst b/doc/source/whatsnew/v1.5.2.rst index c73ca8a4a1645..6397016d827f2 100644 --- a/doc/source/whatsnew/v1.5.2.rst +++ b/doc/source/whatsnew/v1.5.2.rst @@ -29,8 +29,6 @@ Bug fixes ~~~~~~~~~ - Bug in the Copy-on-Write implementation losing track of views in certain chained indexing cases (:issue:`48996`) - Fixed memory leak in :meth:`.Styler.to_excel` (:issue:`49751`) -- Bug when chaining several :meth:`.Styler.concat` calls, only the last styler was concatenated (:issue:`49207`) -- .. --------------------------------------------------------------------------- .. _whatsnew_152.other: diff --git a/doc/source/whatsnew/v1.5.3.rst b/doc/source/whatsnew/v1.5.3.rst new file mode 100644 index 0000000000000..64b8bb2ad790c --- /dev/null +++ b/doc/source/whatsnew/v1.5.3.rst @@ -0,0 +1,41 @@ +.. _whatsnew_153: + +What's new in 1.5.3 (??) +--------------------------------------- + +These are the changes in pandas 1.5.3. See :ref:`release` for a full changelog +including other versions of pandas. + +{{ header }} + +.. --------------------------------------------------------------------------- +.. _whatsnew_153.regressions: + +Fixed regressions +~~~~~~~~~~~~~~~~~ +- +- + +.. --------------------------------------------------------------------------- +.. _whatsnew_153.bug_fixes: + +Bug fixes +~~~~~~~~~ +- Bug when chaining several :meth:`.Styler.concat` calls, only the last styler was concatenated (:issue:`49207`) +- + +.. --------------------------------------------------------------------------- +.. _whatsnew_153.other: + +Other +~~~~~ +- +- + +.. --------------------------------------------------------------------------- +.. _whatsnew_153.contributors: + +Contributors +~~~~~~~~~~~~ + +.. contributors:: v1.5.2..v1.5.3|HEAD From 4bc20d135acfb15600993cc9dfc12b1c7dea9708 Mon Sep 17 00:00:00 2001 From: Tsvika S Date: Tue, 22 Nov 2022 20:09:39 +0200 Subject: [PATCH 15/18] fix mypy complaint --- pandas/io/formats/style_render.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index eb3b2c0fcf9b8..fc3994bded122 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -352,9 +352,9 @@ def _translate( for dx in dxs: # self.concatenated is not empty d["body"].extend(dx["body"]) # type: ignore[union-attr] d["cellstyle"].extend(dx["cellstyle"]) # type: ignore[union-attr] - d["cellstyle_index"].extend( + d["cellstyle_index"].extend( # type: ignore[union-attr] dx["cellstyle_index"] - ) # type: ignore[union-attr] + ) table_attr = self.table_attributes if not get_option("styler.html.mathjax"): From 2698deb9ce57f651e1f4d5afaa9feaf599beec8b Mon Sep 17 00:00:00 2001 From: JHM Darbyshire <24256554+attack68@users.noreply.github.com> Date: Fri, 25 Nov 2022 20:26:10 +0100 Subject: [PATCH 16/18] Update pandas/io/formats/style.py --- pandas/io/formats/style.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index fd685c2363e8b..1f3886b92785c 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -319,9 +319,9 @@ def concat(self, other: Styler) -> Styler: - ``css`` will be inherited from the original Styler, and the value of keys ``data``, ``row_heading`` and ``row`` will be prepended with ``foot0_``. If more concats are chained, their styles will be prepended - with ``foot1_``, ''foot_2'', etc., and if a concatenated style have - another concatanated style, the second style will be prepended with - ``foot{parent}_foot{child}_``. + with ``foot1_``, ''foot_2'', etc., and if a concatenated style have + another concatanated style, the second style will be prepended with + ``foot{parent}_foot{child}_``. A common use case is to concatenate user defined functions with ``DataFrame.agg`` or with described statistics via ``DataFrame.describe``. From 9cfb970e4b22ec6efa3c5e86a2f8765a6ac768bf Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Thu, 8 Dec 2022 12:21:42 -0800 Subject: [PATCH 17/18] Update pandas/io/formats/style_render.py Co-authored-by: JHM Darbyshire <24256554+attack68@users.noreply.github.com> --- 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 fc3994bded122..1ef83c72789ad 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -873,6 +873,8 @@ def _concatenated_visible_rows(obj, n, row_indices): def concatenated_visible_rows(obj): row_indices: list[int] = [] _concatenated_visible_rows(obj, 0, row_indices) + # TODO try to consolidate the concat visible rows + # methods to a single function / recursion for simplicity return row_indices body = [] From e416060b5c5a37f2027fc766c86f791903481e6b Mon Sep 17 00:00:00 2001 From: Tsvika S Date: Sat, 10 Dec 2022 23:15:15 +0200 Subject: [PATCH 18/18] style: remove space in EOL --- 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 1ef83c72789ad..e7b5bc6bbbdbf 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -873,7 +873,7 @@ def _concatenated_visible_rows(obj, n, row_indices): def concatenated_visible_rows(obj): row_indices: list[int] = [] _concatenated_visible_rows(obj, 0, row_indices) - # TODO try to consolidate the concat visible rows + # TODO try to consolidate the concat visible rows # methods to a single function / recursion for simplicity return row_indices