Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

To latex position #35284

Merged
merged 15 commits into from
Aug 7, 2020
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