From 7920037c6a0d3aa1b3bfaafbfcd5aef56967cc6f Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Fri, 3 Sep 2021 19:31:22 +0200 Subject: [PATCH 1/3] multirow naive implementation --- pandas/core/config_init.py | 2 +- pandas/io/formats/style.py | 5 +++-- pandas/io/formats/style_render.py | 4 ++++ .../tests/io/formats/style/test_to_latex.py | 21 +++++++++++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/pandas/core/config_init.py b/pandas/core/config_init.py index f594232f5fb6d..181b5e77de8cb 100644 --- a/pandas/core/config_init.py +++ b/pandas/core/config_init.py @@ -895,7 +895,7 @@ def register_converter_cb(key): "latex.multicol_align", "r", styler_multicol_align, - validator=is_one_of_factory(["r", "c", "l"]), + validator=is_one_of_factory(["r", "c", "l", "naive-l", "naive-r"]), ) cf.register_option( diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index e6f9503a1e6f2..abdaa803ba20f 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -524,10 +524,11 @@ def to_latex( If sparsifying hierarchical MultiIndexes whether to align text centrally, at the top or bottom. If not given defaults to ``pandas.options.styler.latex.multirow_align`` - multicol_align : {"r", "c", "l"}, optional + multicol_align : {"r", "c", "l", "naive-l", "naive-r"}, optional If sparsifying hierarchical MultiIndex columns whether to align text at the left, centrally, or at the right. If not given defaults to - ``pandas.options.styler.latex.multicol_align`` + ``pandas.options.styler.latex.multicol_align``. If a naive option is + given renders without multicol. siunitx : bool, default False Set to ``True`` to structure LaTeX compatible with the {siunitx} package. environment : str, optional diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 49952adf4fb4c..170662e8f1ce7 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -1411,6 +1411,10 @@ def _parse_latex_header_span( if 'colspan="' in attrs: colspan = attrs[attrs.find('colspan="') + 9 :] # len('colspan="') = 9 colspan = int(colspan[: colspan.find('"')]) + if "naive-l" == multicol_align: + return f"{{{display_val}}}" + " &" * (colspan - 1) + elif "naive-r" == multicol_align: + return "& " * (colspan - 1) + f"{{{display_val}}}" return f"\\multicolumn{{{colspan}}}{{{multicol_align}}}{{{display_val}}}" elif 'rowspan="' in attrs: rowspan = attrs[attrs.find('rowspan="') + 9 :] diff --git a/pandas/tests/io/formats/style/test_to_latex.py b/pandas/tests/io/formats/style/test_to_latex.py index 671219872bb34..f5171c92c7273 100644 --- a/pandas/tests/io/formats/style/test_to_latex.py +++ b/pandas/tests/io/formats/style/test_to_latex.py @@ -276,6 +276,27 @@ def test_multiindex_row_and_col(df): assert s.to_latex(sparse_index=False, sparse_columns=False) == expected +@pytest.mark.parametrize( + "multicol_align, exp", [("naive-l", "{A} & &"), ("naive-r", "& & {A}")] +) +def test_multicol_naive(df, multicol_align, exp): + ridx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("A", "c")]) + df = df.astype({"A": int}) + df.columns = ridx + expected = dedent( + f"""\ + \\begin{{tabular}}{{lrrl}} + {{}} & {exp} \\\\ + {{}} & {{a}} & {{b}} & {{c}} \\\\ + 0 & 0 & -0.61 & ab \\\\ + 1 & 1 & -1.22 & cd \\\\ + \\end{{tabular}} + """ + ) + s = df.style.format(precision=2) + assert expected == s.to_latex(multicol_align=multicol_align) + + def test_multi_options(df): cidx = MultiIndex.from_tuples([("Z", "a"), ("Z", "b"), ("Y", "c")]) ridx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("B", "c")]) From b1d8a27b1dc449a2349edbbd219bec2cd453a7e6 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Sat, 4 Sep 2021 19:38:23 +0200 Subject: [PATCH 2/3] amend tests for siunitx --- pandas/io/formats/style_render.py | 8 ++++++-- .../tests/io/formats/style/test_to_latex.py | 19 +++++++++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 0861e58a1c2da..076aa2eb2bb39 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -1412,9 +1412,13 @@ def _parse_latex_header_span( colspan = attrs[attrs.find('colspan="') + 9 :] # len('colspan="') = 9 colspan = int(colspan[: colspan.find('"')]) if "naive-l" == multicol_align: - return f"{{{display_val}}}" + " &" * (colspan - 1) + out = f"{{{display_val}}}" if wrap else f"{display_val}" + blanks = " & {}" if wrap else " &" + return out + blanks * (colspan - 1) elif "naive-r" == multicol_align: - return "& " * (colspan - 1) + f"{{{display_val}}}" + out = f"{{{display_val}}}" if wrap else f"{display_val}" + blanks = "{} & " if wrap else "& " + return blanks * (colspan - 1) + out return f"\\multicolumn{{{colspan}}}{{{multicol_align}}}{{{display_val}}}" elif 'rowspan="' in attrs: rowspan = attrs[attrs.find('rowspan="') + 9 :] diff --git a/pandas/tests/io/formats/style/test_to_latex.py b/pandas/tests/io/formats/style/test_to_latex.py index 6530a0c813ab4..459ca33de038a 100644 --- a/pandas/tests/io/formats/style/test_to_latex.py +++ b/pandas/tests/io/formats/style/test_to_latex.py @@ -278,24 +278,31 @@ def test_multiindex_row_and_col(df): @pytest.mark.parametrize( - "multicol_align, exp", [("naive-l", "{A} & &"), ("naive-r", "& & {A}")] + "multicol_align, siunitx, exp", + [ + ("naive-l", False, " & A & &"), + ("naive-r", False, " & & & A"), + ("naive-l", True, "{} & {A} & {} & {}"), + ("naive-r", True, "{} & {} & {} & {A}"), + ], ) -def test_multicol_naive(df, multicol_align, exp): +def test_multicol_naive(df, multicol_align, siunitx, exp): ridx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("A", "c")]) df = df.astype({"A": int}) df.columns = ridx + level1 = " & a & b & c" if not siunitx else "{} & {a} & {b} & {c}" expected = dedent( f"""\ - \\begin{{tabular}}{{lrrl}} - {{}} & {exp} \\\\ - {{}} & {{a}} & {{b}} & {{c}} \\\\ + \\begin{{tabular}}{{l{"SS" if siunitx else "rr"}l}} + {exp} \\\\ + {level1} \\\\ 0 & 0 & -0.61 & ab \\\\ 1 & 1 & -1.22 & cd \\\\ \\end{{tabular}} """ ) s = df.style.format(precision=2) - assert expected == s.to_latex(multicol_align=multicol_align) + assert expected == s.to_latex(multicol_align=multicol_align, siunitx=siunitx) def test_multi_options(df): From f730cffcdf2d67ec75333e65c111e53345299641 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Tue, 7 Sep 2021 20:40:08 +0200 Subject: [PATCH 3/3] ivanovmg req --- pandas/tests/io/formats/style/test_to_latex.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pandas/tests/io/formats/style/test_to_latex.py b/pandas/tests/io/formats/style/test_to_latex.py index ed780a086437e..40ba3ca26afa4 100644 --- a/pandas/tests/io/formats/style/test_to_latex.py +++ b/pandas/tests/io/formats/style/test_to_latex.py @@ -303,7 +303,7 @@ def test_multiindex_row_and_col(df_ext): @pytest.mark.parametrize( - "multicol_align, siunitx, exp", + "multicol_align, siunitx, header", [ ("naive-l", False, " & A & &"), ("naive-r", False, " & & & A"), @@ -311,23 +311,24 @@ def test_multiindex_row_and_col(df_ext): ("naive-r", True, "{} & {} & {} & {A}"), ], ) -def test_multicol_naive(df, multicol_align, siunitx, exp): +def test_multicol_naive(df, multicol_align, siunitx, header): ridx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("A", "c")]) - df = df.astype({"A": int}) df.columns = ridx level1 = " & a & b & c" if not siunitx else "{} & {a} & {b} & {c}" + col_format = "lrrl" if not siunitx else "lSSl" expected = dedent( f"""\ - \\begin{{tabular}}{{l{"SS" if siunitx else "rr"}l}} - {exp} \\\\ + \\begin{{tabular}}{{{col_format}}} + {header} \\\\ {level1} \\\\ 0 & 0 & -0.61 & ab \\\\ 1 & 1 & -1.22 & cd \\\\ \\end{{tabular}} """ ) - s = df.style.format(precision=2) - assert expected == s.to_latex(multicol_align=multicol_align, siunitx=siunitx) + styler = df.style.format(precision=2) + result = styler.to_latex(multicol_align=multicol_align, siunitx=siunitx) + assert expected == result def test_multi_options(df_ext):