Skip to content

Commit dfbebd7

Browse files
authored
REF: styler.to_latex siunitx changes: render {} only when needed. (#43397)
1 parent dd61461 commit dfbebd7

File tree

5 files changed

+66
-42
lines changed

5 files changed

+66
-42
lines changed

pandas/io/formats/style.py

+1
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,7 @@ def to_latex(
861861
multicol_align=multicol_align,
862862
environment=environment,
863863
convert_css=convert_css,
864+
siunitx=siunitx,
864865
)
865866

866867
encoding = encoding or get_option("styler.render.encoding")

pandas/io/formats/templates/latex_longtable.tpl

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
\{{toprule}}
3535
{% endif %}
3636
{% for row in head %}
37-
{% for c in row %}{%- if not loop.first %} & {% endif %}{{parse_header(c, multirow_align, multicol_align, True)}}{% endfor %} \\
37+
{% for c in row %}{%- if not loop.first %} & {% endif %}{{parse_header(c, multirow_align, multicol_align, siunitx)}}{% endfor %} \\
3838
{% endfor %}
3939
{% set midrule = parse_table(table_styles, 'midrule') %}
4040
{% if midrule is not none %}
@@ -50,7 +50,7 @@
5050
\{{toprule}}
5151
{% endif %}
5252
{% for row in head %}
53-
{% for c in row %}{%- if not loop.first %} & {% endif %}{{parse_header(c, multirow_align, multicol_align, True)}}{% endfor %} \\
53+
{% for c in row %}{%- if not loop.first %} & {% endif %}{{parse_header(c, multirow_align, multicol_align, siunitx)}}{% endfor %} \\
5454
{% endfor %}
5555
{% if midrule is not none %}
5656
\{{midrule}}

pandas/io/formats/templates/latex_table.tpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
\{{toprule}}
3232
{% endif %}
3333
{% for row in head %}
34-
{% for c in row %}{%- if not loop.first %} & {% endif %}{{parse_header(c, multirow_align, multicol_align, True)}}{% endfor %} \\
34+
{% for c in row %}{%- if not loop.first %} & {% endif %}{{parse_header(c, multirow_align, multicol_align, siunitx)}}{% endfor %} \\
3535
{% endfor %}
3636
{% set midrule = parse_table(table_styles, 'midrule') %}
3737
{% if midrule is not none %}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def test_latex_non_unique(styler):
131131
assert result == dedent(
132132
"""\
133133
\\begin{tabular}{lrrr}
134-
{} & {c} & {d} & {d} \\\\
134+
& c & d & d \\\\
135135
i & 1.000000 & 2.000000 & 3.000000 \\\\
136136
j & 4.000000 & 5.000000 & 6.000000 \\\\
137137
j & 7.000000 & 8.000000 & 9.000000 \\\\

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

+61-38
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def test_minimal_latex_tabular(styler):
3333
expected = dedent(
3434
"""\
3535
\\begin{tabular}{lrrl}
36-
{} & {A} & {B} & {C} \\\\
36+
& A & B & C \\\\
3737
0 & 0 & -0.61 & ab \\\\
3838
1 & 1 & -1.22 & cd \\\\
3939
\\end{tabular}
@@ -47,7 +47,7 @@ def test_tabular_hrules(styler):
4747
"""\
4848
\\begin{tabular}{lrrl}
4949
\\toprule
50-
{} & {A} & {B} & {C} \\\\
50+
& A & B & C \\\\
5151
\\midrule
5252
0 & 0 & -0.61 & ab \\\\
5353
1 & 1 & -1.22 & cd \\\\
@@ -69,7 +69,7 @@ def test_tabular_custom_hrules(styler):
6969
"""\
7070
\\begin{tabular}{lrrl}
7171
\\hline
72-
{} & {A} & {B} & {C} \\\\
72+
& A & B & C \\\\
7373
0 & 0 & -0.61 & ab \\\\
7474
1 & 1 & -1.22 & cd \\\\
7575
\\otherline
@@ -170,7 +170,7 @@ def test_cell_styling(styler):
170170
expected = dedent(
171171
"""\
172172
\\begin{tabular}{lrrl}
173-
{} & {A} & {B} & {C} \\\\
173+
& A & B & C \\\\
174174
0 & 0 & \\itshape {\\Huge -0.61} & ab \\\\
175175
1 & \\itshape {\\Huge 1} & -1.22 & \\itshape {\\Huge cd} \\\\
176176
\\end{tabular}
@@ -185,8 +185,8 @@ def test_multiindex_columns(df):
185185
expected = dedent(
186186
"""\
187187
\\begin{tabular}{lrrl}
188-
{} & \\multicolumn{2}{r}{A} & {B} \\\\
189-
{} & {a} & {b} & {c} \\\\
188+
& \\multicolumn{2}{r}{A} & B \\\\
189+
& a & b & c \\\\
190190
0 & 0 & -0.61 & ab \\\\
191191
1 & 1 & -1.22 & cd \\\\
192192
\\end{tabular}
@@ -199,8 +199,8 @@ def test_multiindex_columns(df):
199199
expected = dedent(
200200
"""\
201201
\\begin{tabular}{lrrl}
202-
{} & {A} & {A} & {B} \\\\
203-
{} & {a} & {b} & {c} \\\\
202+
& A & A & B \\\\
203+
& a & b & c \\\\
204204
0 & 0 & -0.61 & ab \\\\
205205
1 & 1 & -1.22 & cd \\\\
206206
\\end{tabular}
@@ -218,7 +218,7 @@ def test_multiindex_row(df):
218218
expected = dedent(
219219
"""\
220220
\\begin{tabular}{llrrl}
221-
{} & {} & {A} & {B} & {C} \\\\
221+
& & A & B & C \\\\
222222
\\multirow[c]{2}{*}{A} & a & 0 & -0.61 & ab \\\\
223223
& b & 1 & -1.22 & cd \\\\
224224
B & c & 2 & -2.22 & de \\\\
@@ -232,7 +232,7 @@ def test_multiindex_row(df):
232232
expected = dedent(
233233
"""\
234234
\\begin{tabular}{llrrl}
235-
{} & {} & {A} & {B} & {C} \\\\
235+
& & A & B & C \\\\
236236
A & a & 0 & -0.61 & ab \\\\
237237
A & b & 1 & -1.22 & cd \\\\
238238
B & c & 2 & -2.22 & de \\\\
@@ -251,8 +251,8 @@ def test_multiindex_row_and_col(df):
251251
expected = dedent(
252252
"""\
253253
\\begin{tabular}{llrrl}
254-
{} & {} & \\multicolumn{2}{l}{Z} & {Y} \\\\
255-
{} & {} & {a} & {b} & {c} \\\\
254+
& & \\multicolumn{2}{l}{Z} & Y \\\\
255+
& & a & b & c \\\\
256256
\\multirow[b]{2}{*}{A} & a & 0 & -0.61 & ab \\\\
257257
& b & 1 & -1.22 & cd \\\\
258258
B & c & 2 & -2.22 & de \\\\
@@ -266,8 +266,8 @@ def test_multiindex_row_and_col(df):
266266
expected = dedent(
267267
"""\
268268
\\begin{tabular}{llrrl}
269-
{} & {} & {Z} & {Z} & {Y} \\\\
270-
{} & {} & {a} & {b} & {c} \\\\
269+
& & Z & Z & Y \\\\
270+
& & a & b & c \\\\
271271
A & a & 0 & -0.61 & ab \\\\
272272
A & b & 1 & -1.22 & cd \\\\
273273
B & c & 2 & -2.22 & de \\\\
@@ -287,15 +287,15 @@ def test_multi_options(df):
287287

288288
expected = dedent(
289289
"""\
290-
{} & {} & \\multicolumn{2}{r}{Z} & {Y} \\\\
291-
{} & {} & {a} & {b} & {c} \\\\
290+
& & \\multicolumn{2}{r}{Z} & Y \\\\
291+
& & a & b & c \\\\
292292
\\multirow[c]{2}{*}{A} & a & 0 & -0.61 & ab \\\\
293293
"""
294294
)
295295
assert expected in styler.to_latex()
296296

297297
with option_context("styler.latex.multicol_align", "l"):
298-
assert "{} & {} & \\multicolumn{2}{l}{Z} & {Y} \\\\" in styler.to_latex()
298+
assert " & & \\multicolumn{2}{l}{Z} & Y \\\\" in styler.to_latex()
299299

300300
with option_context("styler.latex.multirow_align", "b"):
301301
assert "\\multirow[b]{2}{*}{A} & a & 0 & -0.61 & ab \\\\" in styler.to_latex()
@@ -342,7 +342,7 @@ def test_hidden_index(styler):
342342
expected = dedent(
343343
"""\
344344
\\begin{tabular}{rrl}
345-
{A} & {B} & {C} \\\\
345+
A & B & C \\\\
346346
0 & -0.61 & ab \\\\
347347
1 & -1.22 & cd \\\\
348348
\\end{tabular}
@@ -385,8 +385,8 @@ def test_comprehensive(df, environment):
385385
\\rowcolors{3}{pink}{}
386386
\\begin{tabular}{rlrlr}
387387
\\toprule
388-
{} & {} & \\multicolumn{2}{r}{Z} & {Y} \\\\
389-
{} & {} & {a} & {b} & {c} \\\\
388+
& & \\multicolumn{2}{r}{Z} & Y \\\\
389+
& & a & b & c \\\\
390390
\\midrule
391391
\\multirow[c]{2}{*}{A} & a & 0 & \\textbf{\\cellcolor[rgb]{1,1,0.6}{-0.61}} & ab \\\\
392392
& b & 1 & -1.22 & cd \\\\
@@ -580,12 +580,12 @@ def test_longtable_comprehensive(styler):
580580
\\begin{longtable}{lrrl}
581581
\\caption[short]{full} \\label{fig:A} \\\\
582582
\\toprule
583-
{} & {A} & {B} & {C} \\\\
583+
& A & B & C \\\\
584584
\\midrule
585585
\\endfirsthead
586586
\\caption[]{full} \\\\
587587
\\toprule
588-
{} & {A} & {B} & {C} \\\\
588+
& A & B & C \\\\
589589
\\midrule
590590
\\endhead
591591
\\midrule
@@ -607,9 +607,9 @@ def test_longtable_minimal(styler):
607607
expected = dedent(
608608
"""\
609609
\\begin{longtable}{lrrl}
610-
{} & {A} & {B} & {C} \\\\
610+
& A & B & C \\\\
611611
\\endfirsthead
612-
{} & {A} & {B} & {C} \\\\
612+
& A & B & C \\\\
613613
\\endhead
614614
\\multicolumn{4}{r}{Continued on next page} \\\\
615615
\\endfoot
@@ -623,27 +623,34 @@ def test_longtable_minimal(styler):
623623

624624

625625
@pytest.mark.parametrize(
626-
"sparse, exp",
626+
"sparse, exp, siunitx",
627627
[
628-
(True, "{} & \\multicolumn{2}{r}{A} & {B}"),
629-
(False, "{} & {A} & {A} & {B}"),
628+
(True, "{} & \\multicolumn{2}{r}{A} & {B}", True),
629+
(False, "{} & {A} & {A} & {B}", True),
630+
(True, " & \\multicolumn{2}{r}{A} & B", False),
631+
(False, " & A & A & B", False),
630632
],
631633
)
632-
def test_longtable_multiindex_columns(df, sparse, exp):
634+
def test_longtable_multiindex_columns(df, sparse, exp, siunitx):
633635
cidx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("B", "c")])
634636
df.columns = cidx
637+
with_si = "{} & {a} & {b} & {c} \\\\"
638+
without_si = " & a & b & c \\\\"
635639
expected = dedent(
636640
f"""\
637-
\\begin{{longtable}}{{lrrl}}
641+
\\begin{{longtable}}{{l{"SS" if siunitx else "rr"}l}}
638642
{exp} \\\\
639-
{{}} & {{a}} & {{b}} & {{c}} \\\\
643+
{with_si if siunitx else without_si}
640644
\\endfirsthead
641645
{exp} \\\\
642-
{{}} & {{a}} & {{b}} & {{c}} \\\\
646+
{with_si if siunitx else without_si}
643647
\\endhead
644648
"""
645649
)
646-
assert expected in df.style.to_latex(environment="longtable", sparse_columns=sparse)
650+
result = df.style.to_latex(
651+
environment="longtable", sparse_columns=sparse, siunitx=siunitx
652+
)
653+
assert expected in result
647654

648655

649656
@pytest.mark.parametrize(
@@ -661,7 +668,7 @@ def test_longtable_caption_label(styler, caption, cap_exp, label, lab_exp):
661668
expected = dedent(
662669
f"""\
663670
{cap_exp1}{lab_exp} \\\\
664-
{{}} & {{A}} & {{B}} & {{C}} \\\\
671+
& A & B & C \\\\
665672
\\endfirsthead
666673
{cap_exp2} \\\\
667674
"""
@@ -672,8 +679,15 @@ def test_longtable_caption_label(styler, caption, cap_exp, label, lab_exp):
672679

673680

674681
@pytest.mark.parametrize("index", [True, False])
675-
@pytest.mark.parametrize("columns", [True, False])
676-
def test_apply_map_header_render_mi(df, index, columns):
682+
@pytest.mark.parametrize(
683+
"columns, siunitx",
684+
[
685+
(True, True),
686+
(True, False),
687+
(False, False),
688+
],
689+
)
690+
def test_apply_map_header_render_mi(df, index, columns, siunitx):
677691
cidx = MultiIndex.from_tuples([("Z", "a"), ("Z", "b"), ("Y", "c")])
678692
ridx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("B", "c")])
679693
df.loc[2, :] = [2, -2.22, "de"]
@@ -688,7 +702,7 @@ def test_apply_map_header_render_mi(df, index, columns):
688702
if columns:
689703
styler.applymap_index(func, axis="columns")
690704

691-
result = styler.to_latex()
705+
result = styler.to_latex(siunitx=siunitx)
692706

693707
expected_index = dedent(
694708
"""\
@@ -699,13 +713,17 @@ def test_apply_map_header_render_mi(df, index, columns):
699713
)
700714
assert (expected_index in result) is index
701715

702-
expected_columns = dedent(
716+
exp_cols_si = dedent(
703717
"""\
704718
{} & {} & \\multicolumn{2}{r}{\\bfseries{Z}} & {Y} \\\\
705719
{} & {} & {a} & {b} & {\\bfseries{c}} \\\\
706720
"""
707721
)
708-
assert (expected_columns in result) is columns
722+
exp_cols_no_si = """\
723+
& & \\multicolumn{2}{r}{\\bfseries{Z}} & Y \\\\
724+
& & a & b & \\bfseries{c} \\\\
725+
"""
726+
assert ((exp_cols_si if siunitx else exp_cols_no_si) in result) is columns
709727

710728

711729
def test_repr_option(styler):
@@ -714,3 +732,8 @@ def test_repr_option(styler):
714732
with option_context("styler.render.repr", "latex"):
715733
assert "\\begin{tabular}" in styler._repr_latex_()[:15]
716734
assert styler._repr_html_() is None
735+
736+
737+
def test_siunitx_basic_headers(styler):
738+
assert "{} & {A} & {B} & {C} \\\\" in styler.to_latex(siunitx=True)
739+
assert " & A & B & C \\\\" in styler.to_latex() # default siunitx=False

0 commit comments

Comments
 (0)