Skip to content

Commit fe8276b

Browse files
authored
ENH: Styler.apply(map)_index made compatible with Styler.to_latex (#41993)
1 parent ab5322c commit fe8276b

File tree

3 files changed

+59
-16
lines changed

3 files changed

+59
-16
lines changed

doc/source/whatsnew/v1.4.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Other enhancements
3838
- :meth:`Series.ewm`, :meth:`DataFrame.ewm`, now support a ``method`` argument with a ``'table'`` option that performs the windowing operation over an entire :class:`DataFrame`. See :ref:`Window Overview <window.overview>` for performance and functional benefits (:issue:`42273`)
3939
- Added ``sparse_index`` and ``sparse_columns`` keyword arguments to :meth:`.Styler.to_html` (:issue:`41946`)
4040
- Added keyword argument ``environment`` to :meth:`.Styler.to_latex` also allowing a specific "longtable" entry with a separate jinja2 template (:issue:`41866`)
41-
- :meth:`.Styler.apply_index` and :meth:`.Styler.applymap_index` added to allow conditional styling of index and column header values (:issue:`41893`)
41+
- :meth:`.Styler.apply_index` and :meth:`.Styler.applymap_index` added to allow conditional styling of index and column header values for HTML and LaTeX (:issue:`41893`)
4242
- :meth:`.GroupBy.cummin` and :meth:`.GroupBy.cummax` now support the argument ``skipna`` (:issue:`34047`)
4343
-
4444

pandas/io/formats/style_render.py

+15-12
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,14 @@ def _translate_latex(self, d: dict) -> None:
570570
- Remove hidden indexes or reinsert missing th elements if part of multiindex
571571
or multirow sparsification (so that \multirow and \multicol work correctly).
572572
"""
573-
d["head"] = [[col for col in row if col["is_visible"]] for row in d["head"]]
573+
d["head"] = [
574+
[
575+
{**col, "cellstyle": self.ctx_columns[r, c - self.index.nlevels]}
576+
for c, col in enumerate(row)
577+
if col["is_visible"]
578+
]
579+
for r, row in enumerate(d["head"])
580+
]
574581
body = []
575582
for r, row in enumerate(d["body"]):
576583
if all(self.hide_index_):
@@ -582,8 +589,9 @@ def _translate_latex(self, d: dict) -> None:
582589
"display_value": col["display_value"]
583590
if col["is_visible"]
584591
else "",
592+
"cellstyle": self.ctx_index[r, c] if col["is_visible"] else [],
585593
}
586-
for col in row
594+
for c, col in enumerate(row)
587595
if col["type"] == "th"
588596
]
589597

@@ -1378,26 +1386,21 @@ def _parse_latex_header_span(
13781386
>>> _parse_latex_header_span(cell, 't', 'c')
13791387
'\\multicolumn{3}{c}{text}'
13801388
"""
1389+
display_val = _parse_latex_cell_styles(cell["cellstyle"], cell["display_value"])
13811390
if "attributes" in cell:
13821391
attrs = cell["attributes"]
13831392
if 'colspan="' in attrs:
13841393
colspan = attrs[attrs.find('colspan="') + 9 :] # len('colspan="') = 9
13851394
colspan = int(colspan[: colspan.find('"')])
1386-
return (
1387-
f"\\multicolumn{{{colspan}}}{{{multicol_align}}}"
1388-
f"{{{cell['display_value']}}}"
1389-
)
1395+
return f"\\multicolumn{{{colspan}}}{{{multicol_align}}}{{{display_val}}}"
13901396
elif 'rowspan="' in attrs:
13911397
rowspan = attrs[attrs.find('rowspan="') + 9 :]
13921398
rowspan = int(rowspan[: rowspan.find('"')])
1393-
return (
1394-
f"\\multirow[{multirow_align}]{{{rowspan}}}{{*}}"
1395-
f"{{{cell['display_value']}}}"
1396-
)
1399+
return f"\\multirow[{multirow_align}]{{{rowspan}}}{{*}}{{{display_val}}}"
13971400
if wrap:
1398-
return f"{{{cell['display_value']}}}"
1401+
return f"{{{display_val}}}"
13991402
else:
1400-
return cell["display_value"]
1403+
return display_val
14011404

14021405

14031406
def _parse_latex_options_strip(value: str | int | float, arg: str) -> str:

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

+43-3
Original file line numberDiff line numberDiff line change
@@ -414,17 +414,20 @@ def test_parse_latex_cell_styles_braces(wrap_arg, expected):
414414

415415

416416
def test_parse_latex_header_span():
417-
cell = {"attributes": 'colspan="3"', "display_value": "text"}
417+
cell = {"attributes": 'colspan="3"', "display_value": "text", "cellstyle": []}
418418
expected = "\\multicolumn{3}{Y}{text}"
419419
assert _parse_latex_header_span(cell, "X", "Y") == expected
420420

421-
cell = {"attributes": 'rowspan="5"', "display_value": "text"}
421+
cell = {"attributes": 'rowspan="5"', "display_value": "text", "cellstyle": []}
422422
expected = "\\multirow[X]{5}{*}{text}"
423423
assert _parse_latex_header_span(cell, "X", "Y") == expected
424424

425-
cell = {"display_value": "text"}
425+
cell = {"display_value": "text", "cellstyle": []}
426426
assert _parse_latex_header_span(cell, "X", "Y") == "text"
427427

428+
cell = {"display_value": "text", "cellstyle": [("bfseries", "--rwrap")]}
429+
assert _parse_latex_header_span(cell, "X", "Y") == "\\bfseries{text}"
430+
428431

429432
def test_parse_latex_table_wrapping(styler):
430433
styler.set_table_styles(
@@ -635,3 +638,40 @@ def test_longtable_caption_label(styler, caption, cap_exp, label, lab_exp):
635638
assert expected in styler.to_latex(
636639
environment="longtable", caption=caption, label=label
637640
)
641+
642+
643+
@pytest.mark.parametrize("index", [True, False])
644+
@pytest.mark.parametrize("columns", [True, False])
645+
def test_apply_map_header_render_mi(df, index, columns):
646+
cidx = MultiIndex.from_tuples([("Z", "a"), ("Z", "b"), ("Y", "c")])
647+
ridx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("B", "c")])
648+
df.loc[2, :] = [2, -2.22, "de"]
649+
df = df.astype({"A": int})
650+
df.index, df.columns = ridx, cidx
651+
styler = df.style
652+
653+
func = lambda v: "bfseries: --rwrap" if "A" in v or "Z" in v or "c" in v else None
654+
655+
if index:
656+
styler.applymap_index(func, axis="index")
657+
if columns:
658+
styler.applymap_index(func, axis="columns")
659+
660+
result = styler.to_latex()
661+
662+
expected_index = dedent(
663+
"""\
664+
\\multirow[c]{2}{*}{\\bfseries{A}} & a & 0 & -0.610000 & ab \\\\
665+
& b & 1 & -1.220000 & cd \\\\
666+
B & \\bfseries{c} & 2 & -2.220000 & de \\\\
667+
"""
668+
)
669+
assert (expected_index in result) is index
670+
671+
expected_columns = dedent(
672+
"""\
673+
{} & {} & \\multicolumn{2}{r}{\\bfseries{Z}} & {Y} \\\\
674+
{} & {} & {a} & {b} & {\\bfseries{c}} \\\\
675+
"""
676+
)
677+
assert (expected_columns in result) is columns

0 commit comments

Comments
 (0)