Skip to content

Commit 9721375

Browse files
attack68yehoshuadimarsky
authored andcommitted
ENH: make Styler.concat compatible with to_latex output render (pandas-dev#46186)
1 parent be62b31 commit 9721375

File tree

4 files changed

+56
-15
lines changed

4 files changed

+56
-15
lines changed

doc/source/whatsnew/v1.5.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Styler
2121

2222
- New method :meth:`.Styler.to_string` for alternative customisable output methods (:issue:`44502`)
2323
- Added the ability to render ``border`` and ``border-{side}`` CSS properties in Excel (:issue:`42276`)
24-
- 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`)
24+
- 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`)
2525

2626
.. _whatsnew_150.enhancements.enhancement2:
2727

pandas/io/formats/style.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -300,10 +300,10 @@ def concat(self, other: Styler) -> Styler:
300300
``format_index`` will be preserved.
301301
302302
.. warning::
303-
Only the output methods ``to_html`` and ``to_string`` currently work with
304-
concatenated Stylers.
303+
Only the output methods ``to_html``, ``to_string`` and ``to_latex``
304+
currently work with concatenated Stylers.
305305
306-
The output methods ``to_latex`` and ``to_excel`` **do not** work with
306+
Other output methods, including ``to_excel``, **do not** work with
307307
concatenated Stylers.
308308
309309
The following should be noted:

pandas/io/formats/style_render.py

+32-10
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,10 @@ def _render(
158158
blank: str = "",
159159
):
160160
"""
161-
Computes and applies styles and then generates the general render dicts
161+
Computes and applies styles and then generates the general render dicts.
162+
163+
Also extends the `ctx` and `ctx_index` attributes with those of concatenated
164+
stylers for use within `_translate_latex`
162165
"""
163166
self._compute()
164167
dx = None
@@ -172,11 +175,17 @@ def _render(
172175
"row": f"{self.css['foot']}_{self.css['row']}",
173176
"foot": self.css["foot"],
174177
}
175-
dx, _ = self.concatenated._render(
178+
dx = self.concatenated._render(
176179
sparse_index, sparse_columns, max_rows, max_cols, blank
177180
)
181+
182+
for (r, c), v in self.concatenated.ctx.items():
183+
self.ctx[(r + len(self.index), c)] = v
184+
for (r, c), v in self.concatenated.ctx_index.items():
185+
self.ctx_index[(r + len(self.index), c)] = v
186+
178187
d = self._translate(sparse_index, sparse_columns, max_rows, max_cols, blank, dx)
179-
return d, dx
188+
return d
180189

181190
def _render_html(
182191
self,
@@ -190,7 +199,7 @@ def _render_html(
190199
Renders the ``Styler`` including all applied styles to HTML.
191200
Generates a dict with necessary kwargs passed to jinja2 template.
192201
"""
193-
d, _ = self._render(sparse_index, sparse_columns, max_rows, max_cols, " ")
202+
d = self._render(sparse_index, sparse_columns, max_rows, max_cols, " ")
194203
d.update(kwargs)
195204
return self.template_html.render(
196205
**d,
@@ -204,7 +213,7 @@ def _render_latex(
204213
"""
205214
Render a Styler in latex format
206215
"""
207-
d, _ = self._render(sparse_index, sparse_columns, None, None)
216+
d = self._render(sparse_index, sparse_columns, None, None)
208217
self._translate_latex(d, clines=clines)
209218
self.template_latex.globals["parse_wrap"] = _parse_latex_table_wrapping
210219
self.template_latex.globals["parse_table"] = _parse_latex_table_styles
@@ -224,7 +233,7 @@ def _render_string(
224233
"""
225234
Render a Styler in string format
226235
"""
227-
d, _ = self._render(sparse_index, sparse_columns, max_rows, max_cols)
236+
d = self._render(sparse_index, sparse_columns, max_rows, max_cols)
228237
d.update(kwargs)
229238
return self.template_string.render(**d)
230239

@@ -840,11 +849,24 @@ def _translate_latex(self, d: dict, clines: str | None) -> None:
840849
]
841850
for r, row in enumerate(d["head"])
842851
]
852+
853+
def concatenated_visible_rows(obj, n, row_indices):
854+
"""
855+
Extract all visible row indices recursively from concatenated stylers.
856+
"""
857+
row_indices.extend(
858+
[r + n for r in range(len(obj.index)) if r not in obj.hidden_rows]
859+
)
860+
return (
861+
row_indices
862+
if obj.concatenated is None
863+
else concatenated_visible_rows(
864+
obj.concatenated, n + len(obj.index), row_indices
865+
)
866+
)
867+
843868
body = []
844-
for r, row in zip(
845-
[r for r in range(len(self.data.index)) if r not in self.hidden_rows],
846-
d["body"],
847-
):
869+
for r, row in zip(concatenated_visible_rows(self, 0, []), d["body"]):
848870
# note: cannot enumerate d["body"] because rows were dropped if hidden
849871
# during _translate_body so must zip to acquire the true r-index associated
850872
# with the ctx obj which contains the cell styles.

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

+20-1
Original file line numberDiff line numberDiff line change
@@ -999,7 +999,6 @@ def test_col_format_len(styler):
999999
assert expected in result
10001000

10011001

1002-
@pytest.mark.xfail # concat not yet implemented for to_latex
10031002
def test_concat(styler):
10041003
result = styler.concat(styler.data.agg(["sum"]).style).to_latex()
10051004
expected = dedent(
@@ -1013,3 +1012,23 @@ def test_concat(styler):
10131012
"""
10141013
)
10151014
assert result == expected
1015+
1016+
1017+
def test_concat_recursion():
1018+
# tests hidden row recursion and applied styles
1019+
styler1 = DataFrame([[1], [9]]).style.hide([1]).highlight_min(color="red")
1020+
styler2 = DataFrame([[9], [2]]).style.hide([0]).highlight_min(color="green")
1021+
styler3 = DataFrame([[3], [9]]).style.hide([1]).highlight_min(color="blue")
1022+
1023+
result = styler1.concat(styler2.concat(styler3)).to_latex(convert_css=True)
1024+
expected = dedent(
1025+
"""\
1026+
\\begin{tabular}{lr}
1027+
& 0 \\\\
1028+
0 & {\\cellcolor{red}} 1 \\\\
1029+
1 & {\\cellcolor{green}} 2 \\\\
1030+
0 & {\\cellcolor{blue}} 3 \\\\
1031+
\\end{tabular}
1032+
"""
1033+
)
1034+
assert result == expected

0 commit comments

Comments
 (0)