From 98c916450a7148479a639cd827bbcc10d93f6d03 Mon Sep 17 00:00:00 2001 From: parthava-adabala Date: Fri, 24 Oct 2025 19:30:05 -0500 Subject: [PATCH] ENH: Add centering option to DataFrame.to_latex --- doc/source/whatsnew/v3.0.0.rst | 1 + pandas/core/generic.py | 8 +++++++ pandas/io/formats/style.py | 7 ++++++ pandas/io/formats/style_render.py | 20 ++++++++++++---- .../io/formats/templates/latex_longtable.tpl | 1 + pandas/io/formats/templates/latex_table.tpl | 1 + pandas/tests/io/formats/test_to_latex.py | 24 +++++++++++++++++++ 7 files changed, 57 insertions(+), 5 deletions(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 14a9fd4074ba8..f3e44c2d49b8e 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -189,6 +189,7 @@ Other enhancements - :meth:`DataFrame.fillna` and :meth:`Series.fillna` can now accept ``value=None``; for non-object dtype the corresponding NA value will be used (:issue:`57723`) - :meth:`DataFrame.pivot_table` and :func:`pivot_table` now allow the passing of keyword arguments to ``aggfunc`` through ``**kwargs`` (:issue:`57884`) - :meth:`DataFrame.to_json` now encodes ``Decimal`` as strings instead of floats (:issue:`60698`) +- :meth:`DataFrame.to_latex` and :meth:`.Styler.to_latex` now support a ``centering`` parameter to center the table in the LaTeX output (:issue:`62733`) - :meth:`Series.cummin` and :meth:`Series.cummax` now supports :class:`CategoricalDtype` (:issue:`52335`) - :meth:`Series.plot` now correctly handle the ``ylabel`` parameter for pie charts, allowing for explicit control over the y-axis label (:issue:`58239`) - :meth:`DataFrame.plot.scatter` argument ``c`` now accepts a column of strings, where rows with the same string are colored identically (:issue:`16827` and :issue:`16485`) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 43078ef3a263c..9ea18b78e6a3f 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -3309,6 +3309,7 @@ def to_latex( caption: str | tuple[str, str] | None = ..., label: str | None = ..., position: str | None = ..., + centering: bool = ..., ) -> str: ... @overload @@ -3336,6 +3337,7 @@ def to_latex( caption: str | tuple[str, str] | None = ..., label: str | None = ..., position: str | None = ..., + centering: bool = ..., ) -> None: ... @final @@ -3363,6 +3365,7 @@ def to_latex( caption: str | tuple[str, str] | None = None, label: str | None = None, position: str | None = None, + centering: bool = False, ) -> str | None: r""" Render object to a LaTeX tabular, longtable, or nested table. @@ -3469,6 +3472,10 @@ def to_latex( The LaTeX positional argument for tables, to be placed after ``\begin{}`` in the output. + centering : bool, default False + Whether to add the ``\centering`` command to center the table + inside the table environment. + Returns ------- str or None @@ -3614,6 +3621,7 @@ def _wrap(x, alt_format_): if (multirow and isinstance(self.index, MultiIndex)) else None, "bold_rows": bold_rows, + "centering": centering, } return self._to_latex_via_styler( diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 9bf497af77855..f3d96fa3bfd9f 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -637,6 +637,7 @@ def to_latex( multicol_align: str | None = ..., siunitx: bool = ..., environment: str | None = ..., + centering: bool = ..., encoding: str | None = ..., convert_css: bool = ..., ) -> None: ... @@ -659,6 +660,7 @@ def to_latex( multicol_align: str | None = ..., siunitx: bool = ..., environment: str | None = ..., + centering: bool = ..., encoding: str | None = ..., convert_css: bool = ..., ) -> str: ... @@ -680,6 +682,7 @@ def to_latex( multicol_align: str | None = None, siunitx: bool = False, environment: str | None = None, + centering: bool = False, encoding: str | None = None, convert_css: bool = False, ) -> str | None: @@ -778,6 +781,9 @@ def to_latex( ``pandas.options.styler.latex.environment``, which is `None`. .. versionadded:: 1.4.0 + centering : bool, default False + Whether to add the ``\centering`` command to center the table + inside the table environment. encoding : str, optional Character encoding setting. Defaults to ``pandas.options.styler.render.encoding``, which is "utf-8". @@ -1227,6 +1233,7 @@ def to_latex( convert_css=convert_css, siunitx=siunitx, clines=clines, + centering=centering, ) encoding = ( diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index ba9b3aff01d7b..d11fb30bff86d 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -226,12 +226,16 @@ def _render_latex( """ Render a Styler in latex format """ + centering = kwargs.pop("centering", False) d = self._render(sparse_index, sparse_columns, None, None) self._translate_latex(d, clines=clines) - self.template_latex.globals["parse_wrap"] = _parse_latex_table_wrapping + self.template_latex.globals["parse_wrap"] = partial( + _parse_latex_table_wrapping, centering=centering + ) self.template_latex.globals["parse_table"] = _parse_latex_table_styles self.template_latex.globals["parse_cell"] = _parse_latex_cell_styles self.template_latex.globals["parse_header"] = _parse_latex_header_span + d["centering"] = centering d.update(kwargs) return self.template_latex.render(**d) @@ -2329,7 +2333,9 @@ def _translate(self, styler: StylerRenderer, d: dict): return d -def _parse_latex_table_wrapping(table_styles: CSSStyles, caption: str | None) -> bool: +def _parse_latex_table_wrapping( + table_styles: CSSStyles, caption: str | None, centering: bool = False +) -> bool: """ Indicate whether LaTeX {tabular} should be wrapped with a {table} environment. @@ -2340,9 +2346,13 @@ def _parse_latex_table_wrapping(table_styles: CSSStyles, caption: str | None) -> IGNORED_WRAPPERS = ["toprule", "midrule", "bottomrule", "column_format"] # ignored selectors are included with {tabular} so do not need wrapping return ( - table_styles is not None - and any(d["selector"] not in IGNORED_WRAPPERS for d in table_styles) - ) or caption is not None + ( + table_styles is not None + and any(d["selector"] not in IGNORED_WRAPPERS for d in table_styles) + ) + or caption is not None + or centering + ) def _parse_latex_table_styles(table_styles: CSSStyles, selector: str) -> str | None: diff --git a/pandas/io/formats/templates/latex_longtable.tpl b/pandas/io/formats/templates/latex_longtable.tpl index b97843eeb918d..07d4b4a1bcc60 100644 --- a/pandas/io/formats/templates/latex_longtable.tpl +++ b/pandas/io/formats/templates/latex_longtable.tpl @@ -29,6 +29,7 @@ \label{{label}} \\ {% endif %} {% endif %} +{%- if centering %}\centering{% endif -%} {% set toprule = parse_table(table_styles, 'toprule') %} {% if toprule is not none %} \{{toprule}} diff --git a/pandas/io/formats/templates/latex_table.tpl b/pandas/io/formats/templates/latex_table.tpl index 7858cb4c94553..abb6ed294e2b3 100644 --- a/pandas/io/formats/templates/latex_table.tpl +++ b/pandas/io/formats/templates/latex_table.tpl @@ -9,6 +9,7 @@ {% if position_float is not none%} \{{position_float}} {% endif %} +{%- if centering %}\centering{% endif -%} {% if caption and caption is string %} \caption{% raw %}{{% endraw %}{{caption}}{% raw %}}{% endraw %} diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index 6cf4771961cbf..ed678d4674ec6 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -182,6 +182,30 @@ def test_to_latex_midrule_location(self): ) assert result == expected + def test_to_latex_centering(self): + # test for \centering command #GH ENH #62733 + df = DataFrame({"a": [1], "b": [2]}) + + # test with DataFrame.to_latex (centering=True) + latex_centered = df.to_latex(centering=True) + assert r"\centering" in latex_centered + # test with DataFrame.to_latex (centering=False) + latex_default = df.to_latex(centering=False) + assert r"\centering" not in latex_default + # test with DataFrame.to_latex with longtable (centering=True) + latex_long_centered = df.to_latex(centering=True, longtable=True) + assert r"\centering" in latex_long_centered + # test with DataFrame.to_latex with longtable (centering=False) + latex_long_default = df.to_latex(centering=False, longtable=True) + assert r"\centering" not in latex_long_default + # test with Styler.to_latex (centering=True) + styler = df.style + latex_styler_centered = styler.to_latex(centering=True) + assert r"\centering" in latex_styler_centered + # test with Styler.to_latex (centering=False) + latex_styler_default = styler.to_latex(centering=False) + assert r"\centering" not in latex_styler_default + class TestToLatexLongtable: def test_to_latex_empty_longtable(self):