Skip to content
5 changes: 5 additions & 0 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2840,6 +2840,7 @@ def to_latex(
multirow=None,
caption=None,
label=None,
position=None,
):
r"""
Render object to a LaTeX tabular, longtable, or nested table/tabular.
Expand Down Expand Up @@ -2925,6 +2926,9 @@ def to_latex(
This is used with ``\ref{}`` in the main ``.tex`` file.

.. versionadded:: 1.0.0
position : str, optional
The LaTeX positional argument for tables, to be placed after
``\begin{}`` in the output.
%(returns)s
See Also
--------
Expand Down Expand Up @@ -2986,6 +2990,7 @@ def to_latex(
multirow=multirow,
caption=caption,
label=label,
position=position,
)

def to_csv(
Expand Down
2 changes: 2 additions & 0 deletions pandas/io/formats/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,7 @@ def to_latex(
multirow: bool = False,
caption: Optional[str] = None,
label: Optional[str] = None,
position: Optional[str] = None,
) -> Optional[str]:
"""
Render a DataFrame to a LaTeX tabular/longtable environment output.
Expand All @@ -946,6 +947,7 @@ def to_latex(
multirow=multirow,
caption=caption,
label=label,
position=position,
).get_result(buf=buf, encoding=encoding)

def _format_col(self, i: int) -> List[str]:
Expand Down
42 changes: 27 additions & 15 deletions pandas/io/formats/latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def __init__(
multirow: bool = False,
caption: Optional[str] = None,
label: Optional[str] = None,
position: Optional[str] = None,
):
self.fmt = formatter
self.frame = self.fmt.frame
Expand All @@ -50,6 +51,8 @@ def __init__(
self.caption = caption
self.label = label
self.escape = self.fmt.escape
self.position = position
self._table_float = any(p is not None for p in (caption, label, position))

def write_result(self, buf: IO[str]) -> None:
"""
Expand Down Expand Up @@ -284,7 +287,7 @@ def _write_tabular_begin(self, buf, column_format: str):
<https://en.wikibooks.org/wiki/LaTeX/Tables>`__ e.g 'rcl'
for 3 columns
"""
if self.caption is not None or self.label is not None:
if self._table_float:
# then write output in a nested table/tabular environment
if self.caption is None:
caption_ = ""
Expand All @@ -296,7 +299,12 @@ def _write_tabular_begin(self, buf, column_format: str):
else:
label_ = f"\n\\label{{{self.label}}}"

buf.write(f"\\begin{{table}}\n\\centering{caption_}{label_}\n")
if self.position is None:
position_ = ""
else:
position_ = f"[{self.position}]"

buf.write(f"\\begin{{table}}{position_}\n\\centering{caption_}{label_}\n")
else:
# then write output only in a tabular environment
pass
Expand All @@ -317,7 +325,7 @@ def _write_tabular_end(self, buf):
"""
buf.write("\\bottomrule\n")
buf.write("\\end{tabular}\n")
if self.caption is not None or self.label is not None:
if self._table_float:
buf.write("\\end{table}\n")
else:
pass
Expand All @@ -337,25 +345,29 @@ def _write_longtable_begin(self, buf, column_format: str):
<https://en.wikibooks.org/wiki/LaTeX/Tables>`__ e.g 'rcl'
for 3 columns
"""
buf.write(f"\\begin{{longtable}}{{{column_format}}}\n")
if self.caption is None:
caption_ = ""
else:
caption_ = f"\\caption{{{self.caption}}}"

if self.caption is not None or self.label is not None:
if self.caption is None:
pass
else:
buf.write(f"\\caption{{{self.caption}}}")
if self.label is None:
label_ = ""
else:
label_ = f"\\label{{{self.label}}}"

if self.label is None:
pass
else:
buf.write(f"\\label{{{self.label}}}")
if self.position is None:
position_ = ""
else:
position_ = f"[{self.position}]"

buf.write(
f"\\begin{{longtable}}{position_}{{{column_format}}}\n{caption_}{label_}"
)
if self.caption is not None or self.label is not None:
# a double-backslash is required at the end of the line
# as discussed here:
# https://tex.stackexchange.com/questions/219138
buf.write("\\\\\n")
else:
pass

@staticmethod
def _write_longtable_end(buf):
Expand Down
48 changes: 48 additions & 0 deletions pandas/tests/io/formats/test_to_latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,54 @@ def test_to_latex_longtable_caption_label(self):
"""
assert result_cl == expected_cl

def test_to_latex_position(self):
the_position = "h"

df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})

# test when only the position is provided
result_p = df.to_latex(position=the_position)

expected_p = r"""\begin{table}[h]
\centering
\begin{tabular}{lrl}
\toprule
{} & a & b \\
\midrule
0 & 1 & b1 \\
1 & 2 & b2 \\
\bottomrule
\end{tabular}
\end{table}
"""
assert result_p == expected_p

def test_to_latex_longtable_position(self):
the_position = "t"

df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})

# test when only the position is provided
result_p = df.to_latex(longtable=True, position=the_position)

expected_p = r"""\begin{longtable}[t]{lrl}
\toprule
{} & a & b \\
\midrule
\endhead
\midrule
\multicolumn{3}{r}{{Continued on next page}} \\
\midrule
\endfoot

\bottomrule
\endlastfoot
0 & 1 & b1 \\
1 & 2 & b2 \\
\end{longtable}
"""
assert result_p == expected_p

def test_to_latex_escape_special_chars(self):
special_characters = ["&", "%", "$", "#", "_", "{", "}", "~", "^", "\\"]
df = DataFrame(data=special_characters)
Expand Down