From cacb041dd46309dc65d30feb573fb2d92e6d2b6e Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Sat, 29 May 2021 08:32:03 +0200 Subject: [PATCH 01/18] add latex environment variable and longtable template --- pandas/io/formats/style.py | 6 +++ pandas/io/formats/templates/latex.tpl | 52 ++----------------- .../io/formats/templates/latex_longtable.tpl | 37 +++++++++++++ pandas/io/formats/templates/latex_table.tpl | 49 +++++++++++++++++ 4 files changed, 96 insertions(+), 48 deletions(-) create mode 100644 pandas/io/formats/templates/latex_longtable.tpl create mode 100644 pandas/io/formats/templates/latex_table.tpl diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 73924631aea5c..dc57f0e196071 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -425,6 +425,7 @@ def to_latex( multirow_align: str = "c", multicol_align: str = "r", siunitx: bool = False, + environment: str = None, encoding: str | None = None, ): r""" @@ -478,6 +479,10 @@ def to_latex( the left, centrally, or at the right. siunitx : bool, default False Set to ``True`` to structure LaTeX compatible with the {siunitx} package. + environment : str, optional + If given the environment that will replace 'table' in ``\\begin{table}``. + If 'longtable' is specified then a custom, more suitable template, will be + rendered where the argument ``position_float`` has no effect. encoding : str, default "utf-8" Character encoding setting. @@ -738,6 +743,7 @@ def to_latex( sparse_columns=sparse_columns, multirow_align=multirow_align, multicol_align=multicol_align, + environment=environment, ) return save_to_buffer(latex, buf=buf, encoding=encoding) diff --git a/pandas/io/formats/templates/latex.tpl b/pandas/io/formats/templates/latex.tpl index e5db6ad8ca7f8..ae341bbc29823 100644 --- a/pandas/io/formats/templates/latex.tpl +++ b/pandas/io/formats/templates/latex.tpl @@ -1,49 +1,5 @@ -{% if parse_wrap(table_styles, caption) %} -\begin{table} -{%- set position = parse_table(table_styles, 'position') %} -{%- if position is not none %} -[{{position}}] -{%- endif %} - -{% set position_float = parse_table(table_styles, 'position_float') %} -{% if position_float is not none%} -\{{position_float}} -{% endif %} -{% if caption %} -\caption{% raw %}{{% endraw %}{{caption}}{% raw %}}{% endraw %} - -{% endif %} -{% for style in table_styles %} -{% if style['selector'] not in ['position', 'position_float', 'caption', 'toprule', 'midrule', 'bottomrule', 'column_format'] %} -\{{style['selector']}}{{parse_table(table_styles, style['selector'])}} -{% endif %} -{% endfor %} -{% endif %} -\begin{tabular} -{%- set column_format = parse_table(table_styles, 'column_format') %} -{% raw %}{{% endraw %}{{column_format}}{% raw %}}{% endraw %} - -{% set toprule = parse_table(table_styles, 'toprule') %} -{% if toprule is not none %} -\{{toprule}} -{% endif %} -{% for row in head %} -{% for c in row %}{%- if not loop.first %} & {% endif %}{{parse_header(c, multirow_align, multicol_align, True)}}{% endfor %} \\ -{% endfor %} -{% set midrule = parse_table(table_styles, 'midrule') %} -{% if midrule is not none %} -\{{midrule}} -{% endif %} -{% for row in body %} -{% for c in row %}{% if not loop.first %} & {% endif %} - {%- if c.type == 'th' %}{{parse_header(c, multirow_align, multicol_align)}}{% else %}{{parse_cell(c.cellstyle, c.display_value)}}{% endif %} -{%- endfor %} \\ -{% endfor %} -{% set bottomrule = parse_table(table_styles, 'bottomrule') %} -{% if bottomrule is not none %} -\{{bottomrule}} -{% endif %} -\end{tabular} -{% if parse_wrap(table_styles, caption) %} -\end{table} +{% if environment == "longtable" %} +{% include "latex_longtable.tpl" %} +{% else %} +{% include "latex_table.tpl" %} {% endif %} diff --git a/pandas/io/formats/templates/latex_longtable.tpl b/pandas/io/formats/templates/latex_longtable.tpl new file mode 100644 index 0000000000000..8070054ae4379 --- /dev/null +++ b/pandas/io/formats/templates/latex_longtable.tpl @@ -0,0 +1,37 @@ +\begin{longtable} +{%- set position = parse_table(table_styles, 'position') %} +{%- if position is not none %} +[{{position}}] +{%- endif %} +{%- set column_format = parse_table(table_styles, 'column_format') %} +{% raw %}{{% endraw %}{{column_format}}{% raw %}}{% endraw %} + +{% if caption %} +\caption{% raw %}{{% endraw %}{{caption}}{% raw %}}{% endraw %} \\ +{% endif %} +{% for style in table_styles %} +{% if style['selector'] not in ['position', 'position_float', 'caption', 'toprule', 'midrule', 'bottomrule', 'column_format'] %} +\{{style['selector']}}{{parse_table(table_styles, style['selector'])}} +{% endif %} +{% endfor %} +{% set toprule = parse_table(table_styles, 'toprule') %} +{% if toprule is not none %} +\{{toprule}} +{% endif %} +{% for row in head %} +{% for c in row %}{%- if not loop.first %} & {% endif %}{{parse_header(c, multirow_align, multicol_align, True)}}{% endfor %} \\ +{% endfor %} +{% set midrule = parse_table(table_styles, 'midrule') %} +{% if midrule is not none %} +\{{midrule}} +{% endif %} +{% for row in body %} +{% for c in row %}{% if not loop.first %} & {% endif %} + {%- if c.type == 'th' %}{{parse_header(c, multirow_align, multicol_align)}}{% else %}{{parse_cell(c.cellstyle, c.display_value)}}{% endif %} +{%- endfor %} \\ +{% endfor %} +{% set bottomrule = parse_table(table_styles, 'bottomrule') %} +{% if bottomrule is not none %} +\{{bottomrule}} +{% endif %} +\end{longtable} diff --git a/pandas/io/formats/templates/latex_table.tpl b/pandas/io/formats/templates/latex_table.tpl new file mode 100644 index 0000000000000..e5db6ad8ca7f8 --- /dev/null +++ b/pandas/io/formats/templates/latex_table.tpl @@ -0,0 +1,49 @@ +{% if parse_wrap(table_styles, caption) %} +\begin{table} +{%- set position = parse_table(table_styles, 'position') %} +{%- if position is not none %} +[{{position}}] +{%- endif %} + +{% set position_float = parse_table(table_styles, 'position_float') %} +{% if position_float is not none%} +\{{position_float}} +{% endif %} +{% if caption %} +\caption{% raw %}{{% endraw %}{{caption}}{% raw %}}{% endraw %} + +{% endif %} +{% for style in table_styles %} +{% if style['selector'] not in ['position', 'position_float', 'caption', 'toprule', 'midrule', 'bottomrule', 'column_format'] %} +\{{style['selector']}}{{parse_table(table_styles, style['selector'])}} +{% endif %} +{% endfor %} +{% endif %} +\begin{tabular} +{%- set column_format = parse_table(table_styles, 'column_format') %} +{% raw %}{{% endraw %}{{column_format}}{% raw %}}{% endraw %} + +{% set toprule = parse_table(table_styles, 'toprule') %} +{% if toprule is not none %} +\{{toprule}} +{% endif %} +{% for row in head %} +{% for c in row %}{%- if not loop.first %} & {% endif %}{{parse_header(c, multirow_align, multicol_align, True)}}{% endfor %} \\ +{% endfor %} +{% set midrule = parse_table(table_styles, 'midrule') %} +{% if midrule is not none %} +\{{midrule}} +{% endif %} +{% for row in body %} +{% for c in row %}{% if not loop.first %} & {% endif %} + {%- if c.type == 'th' %}{{parse_header(c, multirow_align, multicol_align)}}{% else %}{{parse_cell(c.cellstyle, c.display_value)}}{% endif %} +{%- endfor %} \\ +{% endfor %} +{% set bottomrule = parse_table(table_styles, 'bottomrule') %} +{% if bottomrule is not none %} +\{{bottomrule}} +{% endif %} +\end{tabular} +{% if parse_wrap(table_styles, caption) %} +\end{table} +{% endif %} From a4f75ab46c7a80b478b695f984c796b9a3e0b5ab Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Sat, 29 May 2021 08:37:03 +0200 Subject: [PATCH 02/18] add latex environment variable and longtable template --- pandas/io/formats/templates/latex_table.tpl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pandas/io/formats/templates/latex_table.tpl b/pandas/io/formats/templates/latex_table.tpl index e5db6ad8ca7f8..50fb90cb76045 100644 --- a/pandas/io/formats/templates/latex_table.tpl +++ b/pandas/io/formats/templates/latex_table.tpl @@ -1,5 +1,5 @@ -{% if parse_wrap(table_styles, caption) %} -\begin{table} +{% if environment or parse_wrap(table_styles, caption) %} +\begin{% raw %}{{% endraw %}{{environment if environment else "table"}}{% raw %}}{% endraw %} {%- set position = parse_table(table_styles, 'position') %} {%- if position is not none %} [{{position}}] @@ -44,6 +44,7 @@ \{{bottomrule}} {% endif %} \end{tabular} -{% if parse_wrap(table_styles, caption) %} -\end{table} +{% if environment or parse_wrap(table_styles, caption) %} +\end{% raw %}{{% endraw %}{{environment if environment else "table"}}{% raw %}}{% endraw %} + {% endif %} From 084b2f6add23d413fa38893708a646f3acd529a1 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Mon, 7 Jun 2021 23:04:46 +0200 Subject: [PATCH 03/18] longtable with captions --- .../io/formats/templates/latex_longtable.tpl | 42 +++++++++++++++++-- pandas/io/formats/templates/latex_table.tpl | 5 ++- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/pandas/io/formats/templates/latex_longtable.tpl b/pandas/io/formats/templates/latex_longtable.tpl index 8070054ae4379..5c978b51e22c4 100644 --- a/pandas/io/formats/templates/latex_longtable.tpl +++ b/pandas/io/formats/templates/latex_longtable.tpl @@ -6,14 +6,47 @@ {%- set column_format = parse_table(table_styles, 'column_format') %} {% raw %}{{% endraw %}{{column_format}}{% raw %}}{% endraw %} -{% if caption %} -\caption{% raw %}{{% endraw %}{{caption}}{% raw %}}{% endraw %} \\ -{% endif %} {% for style in table_styles %} -{% if style['selector'] not in ['position', 'position_float', 'caption', 'toprule', 'midrule', 'bottomrule', 'column_format'] %} +{% if style['selector'] not in ['position', 'position_float', 'caption', 'toprule', 'midrule', 'bottomrule', 'column_format', 'label'] %} \{{style['selector']}}{{parse_table(table_styles, style['selector'])}} {% endif %} {% endfor %} +{% if caption and caption is string %} +\caption{% raw %}{{% endraw %}{{caption}}{% raw %}}{% endraw %} +{%- set label = parse_table(table_styles, 'label') %} +{%- if label is not none %} + \label{% raw %}{{% endraw %}{{label}}{% raw %}}{% endraw %} +{%- endif %} \\ +{% elif caption and caption is sequence %} +\caption[{{caption[1]}}]{% raw %}{{% endraw %}{{caption[0]}}{% raw %}}{% endraw %} +{%- set label = parse_table(table_styles, 'label') %} +{%- if label is not none %} + \label{% raw %}{{% endraw %}{{label}}{% raw %}}{% endraw %} +{%- endif %} \\ +{% endif %} +{% set toprule = parse_table(table_styles, 'toprule') %} +{% if toprule is not none %} +\{{toprule}} +{% endif %} +{% for row in head %} +{% for c in row %}{%- if not loop.first %} & {% endif %}{{parse_header(c, multirow_align, multicol_align, True)}}{% endfor %} \\ +{% endfor %} +{% set midrule = parse_table(table_styles, 'midrule') %} +{% if midrule is not none %} +\{{midrule}} +{% endif %} +\endfirsthead +{% if caption and caption is string %} +\caption[]{% raw %}{{% endraw %}{{caption}}{% raw %}}{% endraw %} +{%- if label is not none %} + \label{% raw %}{{% endraw %}{{label}}{% raw %}}{% endraw %} +{%- endif %} \\ +{% elif caption and caption is sequence %} +\caption[]{% raw %}{{% endraw %}{{caption[0]}}{% raw %}}{% endraw %} +{%- if label is not none %} + \label{% raw %}{{% endraw %}{{label}}{% raw %}}{% endraw %} +{%- endif %} \\ +{% endif %} {% set toprule = parse_table(table_styles, 'toprule') %} {% if toprule is not none %} \{{toprule}} @@ -25,6 +58,7 @@ {% if midrule is not none %} \{{midrule}} {% endif %} +\endhead {% for row in body %} {% for c in row %}{% if not loop.first %} & {% endif %} {%- if c.type == 'th' %}{{parse_header(c, multirow_align, multicol_align)}}{% else %}{{parse_cell(c.cellstyle, c.display_value)}}{% endif %} diff --git a/pandas/io/formats/templates/latex_table.tpl b/pandas/io/formats/templates/latex_table.tpl index 50fb90cb76045..e870b01a67b29 100644 --- a/pandas/io/formats/templates/latex_table.tpl +++ b/pandas/io/formats/templates/latex_table.tpl @@ -9,9 +9,12 @@ {% if position_float is not none%} \{{position_float}} {% endif %} -{% if caption %} +{% if caption and caption is string %} \caption{% raw %}{{% endraw %}{{caption}}{% raw %}}{% endraw %} +{% elif caption and caption is sequence %} +\caption[{{caption[1]}}]{% raw %}{{% endraw %}{{caption[0]}}{% raw %}}{% endraw %} + {% endif %} {% for style in table_styles %} {% if style['selector'] not in ['position', 'position_float', 'caption', 'toprule', 'midrule', 'bottomrule', 'column_format'] %} From 3a054c3af21ef377ae5337617c7f2390e44939d7 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Tue, 8 Jun 2021 08:58:01 +0200 Subject: [PATCH 04/18] add tests for longtable --- .../io/formats/templates/latex_longtable.tpl | 33 ++--- .../tests/io/formats/style/test_to_latex.py | 116 ++++++++++++++++++ 2 files changed, 133 insertions(+), 16 deletions(-) diff --git a/pandas/io/formats/templates/latex_longtable.tpl b/pandas/io/formats/templates/latex_longtable.tpl index 5c978b51e22c4..de2436e185984 100644 --- a/pandas/io/formats/templates/latex_longtable.tpl +++ b/pandas/io/formats/templates/latex_longtable.tpl @@ -15,13 +15,13 @@ \caption{% raw %}{{% endraw %}{{caption}}{% raw %}}{% endraw %} {%- set label = parse_table(table_styles, 'label') %} {%- if label is not none %} - \label{% raw %}{{% endraw %}{{label}}{% raw %}}{% endraw %} + \label{{label}} {%- endif %} \\ {% elif caption and caption is sequence %} \caption[{{caption[1]}}]{% raw %}{{% endraw %}{{caption[0]}}{% raw %}}{% endraw %} {%- set label = parse_table(table_styles, 'label') %} {%- if label is not none %} - \label{% raw %}{{% endraw %}{{label}}{% raw %}}{% endraw %} + \label{{label}} {%- endif %} \\ {% endif %} {% set toprule = parse_table(table_styles, 'toprule') %} @@ -37,35 +37,36 @@ {% endif %} \endfirsthead {% if caption and caption is string %} -\caption[]{% raw %}{{% endraw %}{{caption}}{% raw %}}{% endraw %} -{%- if label is not none %} - \label{% raw %}{{% endraw %}{{label}}{% raw %}}{% endraw %} -{%- endif %} \\ +\caption[]{% raw %}{{% endraw %}{{caption}}{% raw %}}{% endraw %} \\ {% elif caption and caption is sequence %} -\caption[]{% raw %}{{% endraw %}{{caption[0]}}{% raw %}}{% endraw %} -{%- if label is not none %} - \label{% raw %}{{% endraw %}{{label}}{% raw %}}{% endraw %} -{%- endif %} \\ +\caption[]{% raw %}{{% endraw %}{{caption[0]}}{% raw %}}{% endraw %} \\ {% endif %} -{% set toprule = parse_table(table_styles, 'toprule') %} {% if toprule is not none %} \{{toprule}} {% endif %} {% for row in head %} {% for c in row %}{%- if not loop.first %} & {% endif %}{{parse_header(c, multirow_align, multicol_align, True)}}{% endfor %} \\ {% endfor %} -{% set midrule = parse_table(table_styles, 'midrule') %} {% if midrule is not none %} \{{midrule}} {% endif %} \endhead +{% if midrule is not none %} +\{{midrule}} +{% endif %} +\multicolumn{% raw %}{{% endraw %}{{column_format|length}}{% raw %}}{% endraw %}{r}{Continued on next page} \\ +{% if midrule is not none %} +\{{midrule}} +{% endif %} +\endfoot +{% set bottomrule = parse_table(table_styles, 'bottomrule') %} +{% if bottomrule is not none %} +\{{bottomrule}} +{% endif %} +\endlastfoot {% for row in body %} {% for c in row %}{% if not loop.first %} & {% endif %} {%- if c.type == 'th' %}{{parse_header(c, multirow_align, multicol_align)}}{% else %}{{parse_cell(c.cellstyle, c.display_value)}}{% endif %} {%- endfor %} \\ {% endfor %} -{% set bottomrule = parse_table(table_styles, 'bottomrule') %} -{% if bottomrule is not none %} -\{{bottomrule}} -{% endif %} \end{longtable} diff --git a/pandas/tests/io/formats/style/test_to_latex.py b/pandas/tests/io/formats/style/test_to_latex.py index 97347bddaa187..44c2958cd3e69 100644 --- a/pandas/tests/io/formats/style/test_to_latex.py +++ b/pandas/tests/io/formats/style/test_to_latex.py @@ -443,3 +443,119 @@ def test_parse_latex_table_wrapping(styler): def test_short_caption(styler): result = styler.to_latex(caption=("full cap", "short cap")) assert "\\caption[short cap]{full cap}" in result + + +def test_longtable_comprehensive(styler): + result = styler.to_latex( + environment="longtable", hrules=True, label="fig:A", caption=("full", "short") + ) + assert result == dedent( + """\ + \\begin{longtable}{lrrl} + \\caption[short]{full} \\label{fig:A} \\\\ + \\toprule + {} & {A} & {B} & {C} \\\\ + \\midrule + \\endfirsthead + \\caption[]{full} \\\\ + \\toprule + {} & {A} & {B} & {C} \\\\ + \\midrule + \\endhead + \\midrule + \\multicolumn{4}{r}{Continued on next page} \\\\ + \\midrule + \\endfoot + \\bottomrule + \\endlastfoot + 0 & 0 & -0.61 & ab \\\\ + 1 & 1 & -1.22 & cd \\\\ + \\end{longtable} + """ + ) + + +def test_longtable_minimal(styler): + result = styler.to_latex(environment="longtable") + assert result == dedent( + """\ + \\begin{longtable}{lrrl} + {} & {A} & {B} & {C} \\\\ + \\endfirsthead + {} & {A} & {B} & {C} \\\\ + \\endhead + \\multicolumn{4}{r}{Continued on next page} \\\\ + \\endfoot + \\endlastfoot + 0 & 0 & -0.61 & ab \\\\ + 1 & 1 & -1.22 & cd \\\\ + \\end{longtable} + """ + ) + + +def test_longtable_multiindex_columns(df): + cidx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("B", "c")]) + df.columns = cidx + expected = dedent( + """\ + \\begin{longtable}{lrrl} + {} & \\multicolumn{2}{r}{A} & {B} \\\\ + {} & {a} & {b} & {c} \\\\ + \\endfirsthead + {} & \\multicolumn{2}{r}{A} & {B} \\\\ + {} & {a} & {b} & {c} \\\\ + \\endhead + """ + ) + assert expected in df.style.to_latex(environment="longtable") + + # non-sparse + expected = dedent( + """\ + \\begin{longtable}{lrrl} + {} & {A} & {A} & {B} \\\\ + {} & {a} & {b} & {c} \\\\ + \\endfirsthead + {} & {A} & {A} & {B} \\\\ + {} & {a} & {b} & {c} \\\\ + \\endhead + """ + ) + assert expected in df.style.to_latex(environment="longtable", sparse_columns=False) + + +def test_longtable_caption_label(styler): + expected = dedent( + """\ + \\caption{full} \\\\ + {} & {A} & {B} & {C} \\\\ + \\endfirsthead + \\caption[]{full} \\\\ + """ + ) + assert expected in styler.to_latex(environment="longtable", caption="full") + + expected = dedent( + """\ + \\caption[short]{full} \\label{fig:A} \\\\ + {} & {A} & {B} & {C} \\\\ + \\endfirsthead + \\caption[]{full} \\\\ + """ + ) + assert expected in styler.to_latex( + environment="longtable", caption=("full", "short"), label="fig:A" + ) + + expected = dedent( + """\ + \\caption{full} \\label{fig:A} \\\\ + {} & {A} & {B} & {C} \\\\ + \\endfirsthead + \\caption[]{full} \\\\ + """ + ) + assert expected in styler.to_latex( + environment="longtable", caption="full", label="fig:A" + ) From bcf116896cce11c97d88fbea7910958fc9dcced9 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Tue, 8 Jun 2021 09:01:44 +0200 Subject: [PATCH 05/18] add tests for longtable --- pandas/tests/io/formats/style/test_to_latex.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pandas/tests/io/formats/style/test_to_latex.py b/pandas/tests/io/formats/style/test_to_latex.py index 44c2958cd3e69..c2ec07920072d 100644 --- a/pandas/tests/io/formats/style/test_to_latex.py +++ b/pandas/tests/io/formats/style/test_to_latex.py @@ -559,3 +559,11 @@ def test_longtable_caption_label(styler): assert expected in styler.to_latex( environment="longtable", caption="full", label="fig:A" ) + + +def test_latex_environment(styler): + result = styler.to_latex(environment="figure*") + assert "\\begin{table}" not in result + assert "\\end{table}" not in result + assert "\\begin{figure*}" in result + assert "\\end{figure*}" in result From 2529b1d2e8ed041ad071660c09b1fa07cf636176 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Tue, 8 Jun 2021 16:46:25 +0200 Subject: [PATCH 06/18] mypy fix --- 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 ed73a2766978d..feffbc69fdc08 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -425,7 +425,7 @@ def to_latex( multirow_align: str = "c", multicol_align: str = "r", siunitx: bool = False, - environment: str = None, + environment: str | None = None, encoding: str | None = None, ): r""" From c6e608b1613839e33a24d6864d1c09a20e8872e5 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Tue, 8 Jun 2021 18:32:37 +0200 Subject: [PATCH 07/18] test fix --- pandas/tests/io/formats/style/test_to_latex.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pandas/tests/io/formats/style/test_to_latex.py b/pandas/tests/io/formats/style/test_to_latex.py index c2ec07920072d..07884d3043d88 100644 --- a/pandas/tests/io/formats/style/test_to_latex.py +++ b/pandas/tests/io/formats/style/test_to_latex.py @@ -470,8 +470,7 @@ def test_longtable_comprehensive(styler): \\endlastfoot 0 & 0 & -0.61 & ab \\\\ 1 & 1 & -1.22 & cd \\\\ - \\end{longtable} - """ + \\end{longtable}""" ) @@ -489,8 +488,7 @@ def test_longtable_minimal(styler): \\endlastfoot 0 & 0 & -0.61 & ab \\\\ 1 & 1 & -1.22 & cd \\\\ - \\end{longtable} - """ + \\end{longtable}""" ) From dbe4154c2e6421cd2b6bd40d6948c64cb48869fd Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Wed, 9 Jun 2021 17:19:27 +0200 Subject: [PATCH 08/18] improve docs, and whats new --- doc/source/whatsnew/v1.3.0.rst | 2 +- pandas/io/formats/style.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 2945fc760e01a..b51589d7091c9 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -134,7 +134,7 @@ which has been revised and improved (:issue:`39720`, :issue:`39317`, :issue:`404 - Many features of the :class:`.Styler` class are now either partially or fully usable on a DataFrame with a non-unique indexes or columns (:issue:`41143`) - One has greater control of the display through separate sparsification of the index or columns using the :ref:`new styler options `, which are also usable via :func:`option_context` (:issue:`41142`) - Added the option ``styler.render.max_elements`` to avoid browser overload when styling large DataFrames (:issue:`40712`) - - Added the method :meth:`.Styler.to_latex` (:issue:`21673`) + - Added the method :meth:`.Styler.to_latex` (:issue:`21673`, :issue:`41866`) - Added the method :meth:`.Styler.to_html` (:issue:`13379`) .. _whatsnew_130.dataframe_honors_copy_with_dict: diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index aaf98e6192d25..5f322e1d83287 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -482,9 +482,10 @@ def to_latex( siunitx : bool, default False Set to ``True`` to structure LaTeX compatible with the {siunitx} package. environment : str, optional - If given the environment that will replace 'table' in ``\\begin{table}``. - If 'longtable' is specified then a custom, more suitable template, will be - rendered where the argument ``position_float`` has no effect. + If given, the environment that will replace 'table' in ``\\begin{table}``. + If 'longtable' is specified then a more suitable template is + rendered for which the ``position_float`` argument is nullified and does not + impact the result. encoding : str, default "utf-8" Character encoding setting. From 7ad880282f6304e6eb8750291067a7d49a6637ac Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Wed, 16 Jun 2021 09:51:49 +0200 Subject: [PATCH 09/18] merge into master and add extra needed tests --- .../io/formats/templates/latex_longtable.tpl | 3 +- pandas/io/formats/templates/latex_table.tpl | 2 +- .../tests/io/formats/style/test_to_latex.py | 34 +++++++++++++++++-- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/pandas/io/formats/templates/latex_longtable.tpl b/pandas/io/formats/templates/latex_longtable.tpl index de2436e185984..fdb45aea83c16 100644 --- a/pandas/io/formats/templates/latex_longtable.tpl +++ b/pandas/io/formats/templates/latex_longtable.tpl @@ -66,7 +66,8 @@ \endlastfoot {% for row in body %} {% for c in row %}{% if not loop.first %} & {% endif %} - {%- if c.type == 'th' %}{{parse_header(c, multirow_align, multicol_align)}}{% else %}{{parse_cell(c.cellstyle, c.display_value)}}{% endif %} + {%- if c.type == 'th' %}{{parse_header(c, multirow_align, multicol_align)}}{% else %}{{parse_cell(c.cellstyle, c.display_value, convert_css)}}{% endif %} {%- endfor %} \\ {% endfor %} \end{longtable} +{% raw %}{% endraw %} diff --git a/pandas/io/formats/templates/latex_table.tpl b/pandas/io/formats/templates/latex_table.tpl index e870b01a67b29..dfdd160351d02 100644 --- a/pandas/io/formats/templates/latex_table.tpl +++ b/pandas/io/formats/templates/latex_table.tpl @@ -39,7 +39,7 @@ {% endif %} {% for row in body %} {% for c in row %}{% if not loop.first %} & {% endif %} - {%- if c.type == 'th' %}{{parse_header(c, multirow_align, multicol_align)}}{% else %}{{parse_cell(c.cellstyle, c.display_value)}}{% endif %} + {%- if c.type == 'th' %}{{parse_header(c, multirow_align, multicol_align)}}{% else %}{{parse_cell(c.cellstyle, c.display_value, convert_css)}}{% endif %} {%- endfor %} \\ {% endfor %} {% set bottomrule = parse_table(table_styles, 'bottomrule') %} diff --git a/pandas/tests/io/formats/style/test_to_latex.py b/pandas/tests/io/formats/style/test_to_latex.py index f7c3b32140b96..01a545bb1ba5f 100644 --- a/pandas/tests/io/formats/style/test_to_latex.py +++ b/pandas/tests/io/formats/style/test_to_latex.py @@ -484,6 +484,34 @@ def test_parse_latex_css_conversion(css, expected): assert result == expected +@pytest.mark.parametrize("environment", ["tabular", "longtable"]) +def test_parse_latex_css_convert_minimal(styler, environment): + # parameters ensure longtable template is also tested + styler.highlight_max(props="font-weight:bold;") + result = styler.to_latex(convert_css=True, environment=environment) + assert ( + dedent( + f"""\ + 0 & 0 & \\bfseries -0.61 & ab \\\\ + 1 & \\bfseries 1 & -1.22 & \\bfseries cd \\\\ + \\end{{{environment}}} + """ + ) + in result + ) + result = styler.to_latex(convert_css=False, environment=environment) + assert ( + dedent( + f"""\ + 0 & 0 & \\font-weightbold -0.61 & ab \\\\ + 1 & \\font-weightbold 1 & -1.22 & \\font-weightbold cd \\\\ + \\end{{{environment}}} + """ + ) + in result + ) + + def test_parse_latex_css_conversion_option(): css = [("command", "option--latex--wrap")] expected = [("command", "option--wrap")] @@ -516,7 +544,8 @@ def test_longtable_comprehensive(styler): \\endlastfoot 0 & 0 & -0.61 & ab \\\\ 1 & 1 & -1.22 & cd \\\\ - \\end{longtable}""" + \\end{longtable} + """ ) @@ -534,7 +563,8 @@ def test_longtable_minimal(styler): \\endlastfoot 0 & 0 & -0.61 & ab \\\\ 1 & 1 & -1.22 & cd \\\\ - \\end{longtable}""" + \\end{longtable} + """ ) From 06962c67e0831ed4ca39fc5b2c474f357e4a60da Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Thu, 24 Jun 2021 08:02:00 +0200 Subject: [PATCH 10/18] parametrize multindex columns (ivan request) --- .../tests/io/formats/style/test_to_latex.py | 34 +++++++------------ 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/pandas/tests/io/formats/style/test_to_latex.py b/pandas/tests/io/formats/style/test_to_latex.py index 01a545bb1ba5f..8026592e8db66 100644 --- a/pandas/tests/io/formats/style/test_to_latex.py +++ b/pandas/tests/io/formats/style/test_to_latex.py @@ -568,35 +568,25 @@ def test_longtable_minimal(styler): ) -def test_longtable_multiindex_columns(df): +@pytest.mark.parametrize( + "sparse, exp", + [(True, "{} & \\multicolumn{2}{r}{A} & {B}"), (False, "{} & {A} & {A} & {B}")], +) +def test_longtable_multiindex_columns(df, sparse, exp): cidx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("B", "c")]) df.columns = cidx expected = dedent( - """\ - \\begin{longtable}{lrrl} - {} & \\multicolumn{2}{r}{A} & {B} \\\\ - {} & {a} & {b} & {c} \\\\ + f"""\ + \\begin{{longtable}}{{lrrl}} + {exp} \\\\ + {{}} & {{a}} & {{b}} & {{c}} \\\\ \\endfirsthead - {} & \\multicolumn{2}{r}{A} & {B} \\\\ - {} & {a} & {b} & {c} \\\\ - \\endhead - """ - ) - assert expected in df.style.to_latex(environment="longtable") - - # non-sparse - expected = dedent( - """\ - \\begin{longtable}{lrrl} - {} & {A} & {A} & {B} \\\\ - {} & {a} & {b} & {c} \\\\ - \\endfirsthead - {} & {A} & {A} & {B} \\\\ - {} & {a} & {b} & {c} \\\\ + {exp} \\\\ + {{}} & {{a}} & {{b}} & {{c}} \\\\ \\endhead """ ) - assert expected in df.style.to_latex(environment="longtable", sparse_columns=False) + assert expected in df.style.to_latex(environment="longtable", sparse_columns=sparse) def test_longtable_caption_label(styler): From 8e3ff5b68f7af23be8e5c881e19a7c94bc7c4729 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Thu, 24 Jun 2021 08:23:48 +0200 Subject: [PATCH 11/18] parametrize caption label (ivan request) --- .../tests/io/formats/style/test_to_latex.py | 43 +++++++------------ 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/pandas/tests/io/formats/style/test_to_latex.py b/pandas/tests/io/formats/style/test_to_latex.py index 8026592e8db66..82afc3b4b802d 100644 --- a/pandas/tests/io/formats/style/test_to_latex.py +++ b/pandas/tests/io/formats/style/test_to_latex.py @@ -589,39 +589,28 @@ def test_longtable_multiindex_columns(df, sparse, exp): assert expected in df.style.to_latex(environment="longtable", sparse_columns=sparse) -def test_longtable_caption_label(styler): - expected = dedent( - """\ - \\caption{full} \\\\ - {} & {A} & {B} & {C} \\\\ - \\endfirsthead - \\caption[]{full} \\\\ - """ - ) - assert expected in styler.to_latex(environment="longtable", caption="full") - - expected = dedent( - """\ - \\caption[short]{full} \\label{fig:A} \\\\ - {} & {A} & {B} & {C} \\\\ - \\endfirsthead - \\caption[]{full} \\\\ - """ - ) - assert expected in styler.to_latex( - environment="longtable", caption=("full", "short"), label="fig:A" - ) +@pytest.mark.parametrize("caption", ["full", ("full", "short")]) +@pytest.mark.parametrize("label", [None, "tab:A"]) +def test_longtable_caption_label(styler, caption, label): + if isinstance(caption, str): + cap1 = f"\\caption{{{caption}}}" + cap2 = f"\\caption[]{{{caption}}}" + elif isinstance(caption, tuple): + cap1 = f"\\caption[{caption[1]}]{{{caption[0]}}}" + cap2 = f"\\caption[]{{{caption[0]}}}" + + lab = "" if label is None else f" \\label{{{label}}}" expected = dedent( - """\ - \\caption{full} \\label{fig:A} \\\\ - {} & {A} & {B} & {C} \\\\ + f"""\ + {cap1}{lab} \\\\ + {{}} & {{A}} & {{B}} & {{C}} \\\\ \\endfirsthead - \\caption[]{full} \\\\ + {cap2} \\\\ """ ) assert expected in styler.to_latex( - environment="longtable", caption="full", label="fig:A" + environment="longtable", caption=caption, label=label ) From 67ffe24a196a838b7ccd0d7ec95281a3b0f01063 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Thu, 24 Jun 2021 08:36:21 +0200 Subject: [PATCH 12/18] more readable (ivan request) --- .../tests/io/formats/style/test_to_latex.py | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/pandas/tests/io/formats/style/test_to_latex.py b/pandas/tests/io/formats/style/test_to_latex.py index 82afc3b4b802d..1e88e727436bf 100644 --- a/pandas/tests/io/formats/style/test_to_latex.py +++ b/pandas/tests/io/formats/style/test_to_latex.py @@ -485,31 +485,21 @@ def test_parse_latex_css_conversion(css, expected): @pytest.mark.parametrize("environment", ["tabular", "longtable"]) -def test_parse_latex_css_convert_minimal(styler, environment): +@pytest.mark.parametrize( + "convert, exp", [(True, "bfseries"), (False, "font-weightbold")] +) +def test_parse_latex_css_convert_minimal(styler, environment, convert, exp): # parameters ensure longtable template is also tested styler.highlight_max(props="font-weight:bold;") - result = styler.to_latex(convert_css=True, environment=environment) - assert ( - dedent( - f"""\ - 0 & 0 & \\bfseries -0.61 & ab \\\\ - 1 & \\bfseries 1 & -1.22 & \\bfseries cd \\\\ - \\end{{{environment}}} - """ - ) - in result - ) - result = styler.to_latex(convert_css=False, environment=environment) - assert ( - dedent( - f"""\ - 0 & 0 & \\font-weightbold -0.61 & ab \\\\ - 1 & \\font-weightbold 1 & -1.22 & \\font-weightbold cd \\\\ + result = styler.to_latex(convert_css=convert, environment=environment) + expected = dedent( + f"""\ + 0 & 0 & \\{exp} -0.61 & ab \\\\ + 1 & \\{exp} 1 & -1.22 & \\{exp} cd \\\\ \\end{{{environment}}} """ - ) - in result ) + assert expected in result def test_parse_latex_css_conversion_option(): @@ -523,7 +513,7 @@ def test_longtable_comprehensive(styler): result = styler.to_latex( environment="longtable", hrules=True, label="fig:A", caption=("full", "short") ) - assert result == dedent( + expected = dedent( """\ \\begin{longtable}{lrrl} \\caption[short]{full} \\label{fig:A} \\\\ @@ -547,11 +537,12 @@ def test_longtable_comprehensive(styler): \\end{longtable} """ ) + assert result == expected def test_longtable_minimal(styler): result = styler.to_latex(environment="longtable") - assert result == dedent( + expected = dedent( """\ \\begin{longtable}{lrrl} {} & {A} & {B} & {C} \\\\ @@ -566,6 +557,7 @@ def test_longtable_minimal(styler): \\end{longtable} """ ) + assert result == expected @pytest.mark.parametrize( From 17e090fe0df144cf3ea5fdbf7dda2910b8858beb Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Mon, 28 Jun 2021 09:11:46 +0200 Subject: [PATCH 13/18] ivan requests --- .../tests/io/formats/style/test_to_latex.py | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/pandas/tests/io/formats/style/test_to_latex.py b/pandas/tests/io/formats/style/test_to_latex.py index 1e88e727436bf..18d35651d3188 100644 --- a/pandas/tests/io/formats/style/test_to_latex.py +++ b/pandas/tests/io/formats/style/test_to_latex.py @@ -562,7 +562,10 @@ def test_longtable_minimal(styler): @pytest.mark.parametrize( "sparse, exp", - [(True, "{} & \\multicolumn{2}{r}{A} & {B}"), (False, "{} & {A} & {A} & {B}")], + [ + (True, "{} & \\multicolumn{2}{r}{A} & {B}"), + (False, "{} & {A} & {A} & {B}"), + ], ) def test_longtable_multiindex_columns(df, sparse, exp): cidx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("B", "c")]) @@ -581,24 +584,24 @@ def test_longtable_multiindex_columns(df, sparse, exp): assert expected in df.style.to_latex(environment="longtable", sparse_columns=sparse) -@pytest.mark.parametrize("caption", ["full", ("full", "short")]) -@pytest.mark.parametrize("label", [None, "tab:A"]) -def test_longtable_caption_label(styler, caption, label): - if isinstance(caption, str): - cap1 = f"\\caption{{{caption}}}" - cap2 = f"\\caption[]{{{caption}}}" - elif isinstance(caption, tuple): - cap1 = f"\\caption[{caption[1]}]{{{caption[0]}}}" - cap2 = f"\\caption[]{{{caption[0]}}}" - - lab = "" if label is None else f" \\label{{{label}}}" +@pytest.mark.parametrize( + "caption, cap_exp", + [ + ("full", ("{full}", "")), + (("full", "short"), ("{full}", "[short]")), + ], +) +@pytest.mark.parametrize("label, lab_exp", [(None, ""), ("tab:A", " \\label{tab:A}")]) +def test_longtable_caption_label(styler, caption, cap_exp, label, lab_exp): + cap_exp1 = f"\\caption{cap_exp[1]}{cap_exp[0]}" + cap_exp2 = f"\\caption[]{cap_exp[0]}" expected = dedent( f"""\ - {cap1}{lab} \\\\ + {cap_exp1}{lab_exp} \\\\ {{}} & {{A}} & {{B}} & {{C}} \\\\ \\endfirsthead - {cap2} \\\\ + {cap_exp2} \\\\ """ ) assert expected in styler.to_latex( From 8164c2eccbadc29e4bfedf8f1f3d3b340bc9c354 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Wed, 30 Jun 2021 17:11:18 +0200 Subject: [PATCH 14/18] imporve tests (ivan request) --- .../tests/io/formats/style/test_to_latex.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/pandas/tests/io/formats/style/test_to_latex.py b/pandas/tests/io/formats/style/test_to_latex.py index 18d35651d3188..92e25ce80cd3d 100644 --- a/pandas/tests/io/formats/style/test_to_latex.py +++ b/pandas/tests/io/formats/style/test_to_latex.py @@ -322,7 +322,8 @@ def test_hidden_index(styler): assert styler.to_latex() == expected -def test_comprehensive(df): +@pytest.mark.parametrize("environment", ["table", "figure*", None]) +def test_comprehensive(df, environment): # test as many low level features simultaneously as possible cidx = MultiIndex.from_tuples([("Z", "a"), ("Z", "b"), ("Y", "c")]) ridx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("B", "c")]) @@ -367,8 +368,8 @@ def test_comprehensive(df): \\end{tabular} \\end{table} """ - ) - assert s.format(precision=2).to_latex() == expected + ).replace("table", environment if environment else "table") + assert s.format(precision=2).to_latex(environment=environment) == expected def test_parse_latex_table_styles(styler): @@ -484,19 +485,26 @@ def test_parse_latex_css_conversion(css, expected): assert result == expected -@pytest.mark.parametrize("environment", ["tabular", "longtable"]) +@pytest.mark.parametrize( + "env, inner_env", + [ + (None, "tabular"), + ("table", "tabular"), + ("longtable", "longtable"), + ], +) @pytest.mark.parametrize( "convert, exp", [(True, "bfseries"), (False, "font-weightbold")] ) -def test_parse_latex_css_convert_minimal(styler, environment, convert, exp): +def test_parse_latex_css_convert_minimal(styler, env, inner_env, convert, exp): # parameters ensure longtable template is also tested styler.highlight_max(props="font-weight:bold;") - result = styler.to_latex(convert_css=convert, environment=environment) + result = styler.to_latex(convert_css=convert, environment=env) expected = dedent( f"""\ 0 & 0 & \\{exp} -0.61 & ab \\\\ 1 & \\{exp} 1 & -1.22 & \\{exp} cd \\\\ - \\end{{{environment}}} + \\end{{{inner_env}}} """ ) assert expected in result @@ -607,11 +615,3 @@ def test_longtable_caption_label(styler, caption, cap_exp, label, lab_exp): assert expected in styler.to_latex( environment="longtable", caption=caption, label=label ) - - -def test_latex_environment(styler): - result = styler.to_latex(environment="figure*") - assert "\\begin{table}" not in result - assert "\\end{table}" not in result - assert "\\begin{figure*}" in result - assert "\\end{figure*}" in result From 175e1e3e2354383779d92b1f4fde15133293a548 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Wed, 30 Jun 2021 17:27:15 +0200 Subject: [PATCH 15/18] ValueError on position_float tests (simon request) --- pandas/io/formats/style.py | 9 +++++++-- pandas/tests/io/formats/style/test_to_latex.py | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index dac4b0ddb4e24..fc51d9a0ee986 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -459,6 +459,8 @@ def to_latex( \\begin{table}[] \\ + + Cannot be used if ``environment`` is "longtable". hrules : bool, default False Set to `True` to add \\toprule, \\midrule and \\bottomrule from the {booktabs} LaTeX package. @@ -488,8 +490,7 @@ def to_latex( environment : str, optional If given, the environment that will replace 'table' in ``\\begin{table}``. If 'longtable' is specified then a more suitable template is - rendered for which the ``position_float`` argument is nullified and does not - impact the result. + rendered. encoding : str, default "utf-8" Character encoding setting. convert_css : bool, default False @@ -755,6 +756,10 @@ def to_latex( ) if position_float: + if environment == "longtable": + raise ValueError( + "`position_float` cannot be used in 'longtable' `environment`" + ) if position_float not in ["raggedright", "raggedleft", "centering"]: raise ValueError( f"`position_float` should be one of " diff --git a/pandas/tests/io/formats/style/test_to_latex.py b/pandas/tests/io/formats/style/test_to_latex.py index e8b58b0247065..501d9b43ff106 100644 --- a/pandas/tests/io/formats/style/test_to_latex.py +++ b/pandas/tests/io/formats/style/test_to_latex.py @@ -120,6 +120,10 @@ def test_position_float_raises(styler): with pytest.raises(ValueError, match=msg): styler.to_latex(position_float="bad_string") + msg = "`position_float` cannot be used in 'longtable' `environment`" + with pytest.raises(ValueError, match=msg): + styler.to_latex(position_float="centering", environment="longtable") + @pytest.mark.parametrize("label", [(None, ""), ("text", "\\label{text}")]) @pytest.mark.parametrize("position", [(None, ""), ("h!", "{table}[h!]")]) From 42151ac12b21769f80289107070983a5c75a4133 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Mon, 5 Jul 2021 20:46:06 +0200 Subject: [PATCH 16/18] whatsnew 1.4.0 --- doc/source/whatsnew/v1.3.0.rst | 2 +- doc/source/whatsnew/v1.4.0.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 532c953dc3b5f..ed66861efad93 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -136,7 +136,7 @@ which has been revised and improved (:issue:`39720`, :issue:`39317`, :issue:`404 - Many features of the :class:`.Styler` class are now either partially or fully usable on a DataFrame with a non-unique indexes or columns (:issue:`41143`) - One has greater control of the display through separate sparsification of the index or columns using the :ref:`new styler options `, which are also usable via :func:`option_context` (:issue:`41142`) - Added the option ``styler.render.max_elements`` to avoid browser overload when styling large DataFrames (:issue:`40712`) - - Added the method :meth:`.Styler.to_latex` (:issue:`21673`, :issue:`42320`, :issue:`41866`), which also allows some limited CSS conversion (:issue:`40731`) + - Added the method :meth:`.Styler.to_latex` (:issue:`21673`, :issue:`42320`), which also allows some limited CSS conversion (:issue:`40731`) - Added the method :meth:`.Styler.to_html` (:issue:`13379`) - Added the method :meth:`.Styler.set_sticky` to make index and column headers permanently visible in scrolling HTML frames (:issue:`29072`) diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index 764a50e13586a..3c36f21299125 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -30,7 +30,7 @@ enhancement2 Other enhancements ^^^^^^^^^^^^^^^^^^ - :meth:`Series.sample`, :meth:`DataFrame.sample`, and :meth:`.GroupBy.sample` now accept a ``np.random.Generator`` as input to ``random_state``. A generator will be more performant, especially with ``replace=False`` (:issue:`38100`) -- +- Added keyword argument ``environment`` to :meth:`.Styler.to_latex` also allowing a specific "longtable" entry with a separate jinja2 template (:issue:`41866`) .. --------------------------------------------------------------------------- From 2311db26ba107d685b62e672f9ad5d783f5718cc Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Mon, 5 Jul 2021 20:47:15 +0200 Subject: [PATCH 17/18] whatsnew 1.4.0 --- pandas/io/formats/style.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index def367114fce4..e4d2b80ac0831 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -490,6 +490,8 @@ def to_latex( If given, the environment that will replace 'table' in ``\\begin{table}``. If 'longtable' is specified then a more suitable template is rendered. + + .. versionadded:: 1.4.0 encoding : str, default "utf-8" Character encoding setting. convert_css : bool, default False From 4cd8263d59b8cd0e9b13492ebae4003c499685f7 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Wed, 7 Jul 2021 08:11:37 +0200 Subject: [PATCH 18/18] add to doc packages --- pandas/io/formats/style.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index b0a3171602c26..9b10b9309c239 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -528,6 +528,8 @@ def to_latex( italic (with siunitx) | \\usepackage{etoolbox} | \\robustify\\itshape | \\sisetup{detect-all = true} *(within {document})* + environment \\usepackage{longtable} if arg is "longtable" + | or any other relevant environment package ===================== ========================================================== **Cell Styles**