Skip to content

To latex position #35284

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

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
28 changes: 24 additions & 4 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,7 @@ def __init__(
self.caption = caption
self.label = label
self.escape = self.fmt.escape
self.position = position

def write_result(self, buf: IO[str]) -> None:
"""
Expand Down Expand Up @@ -284,8 +286,13 @@ 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.caption is not None
or self.label is not None
or self.position is not None
):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this test grows, it would be better to have self._table_float = any([p is not None for p in (caption, label, position)]) inside __init__.py, to be used both here and in _write_tabular_end.

# then write output in a nested table/tabular environment
buf.write(f"\\begin{{table}}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any obvious reason why this was moved up here? I would find buf.write(f"\\begin{{table}}{position_}") more readable that the write below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I did this was only because the line was getting too long, but I can write it as it was for sure.
Maybe we should unify the way it's done in _write_tabular_begin and _write_longtable_begin ? But it may be nitpicking

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I did this was only because the line was getting too long, but I can write it as it was for sure.

I do find it more readable - but if you want to break the line, you could do so at \n, so after the position.

Maybe we should unify the way it's done in _write_tabular_begin and _write_longtable_begin ?

Wow... indeed the difference between _write_tabular_begin and _write_longtable_begin seems to amount to the environment name: unless I'm mistaken, the merge is definitely worth doing (same for _end). By the way, if the function are unified feel free to move back the any([p is not None... down there.

if self.caption is None:
caption_ = ""
else:
Expand All @@ -296,7 +303,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"{position_}\n\\centering{caption_}{label_}\n")
else:
# then write output only in a tabular environment
pass
Expand All @@ -317,7 +329,11 @@ 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.caption is not None
or self.label is not None
or self.position is not None
):
buf.write("\\end{table}\n")
else:
pass
Expand All @@ -337,7 +353,11 @@ 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.position is None:
position_ = ""
else:
position_ = f"[{self.position}]"
buf.write(f"\\begin{{longtable}}{position_}{{{column_format}}}\n")

if self.caption is not None or self.label is not None:
if self.caption is None:
Expand Down
44 changes: 42 additions & 2 deletions pandas/tests/io/formats/test_to_latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,10 +438,11 @@ def test_to_latex_longtable(self):
with3columns_result = df.to_latex(index=False, longtable=True)
assert r"\multicolumn{3}" in with3columns_result

def test_to_latex_caption_label(self):
def test_to_latex_caption_label_position(self):
# GH 25436
the_caption = "a table in a \\texttt{table/tabular} environment"
the_label = "tab:table_tabular"
the_position = "h"

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

Expand Down Expand Up @@ -481,6 +482,23 @@ def test_to_latex_caption_label(self):
"""
assert result_l == expected_l

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you create this as its own test? Same comment with one below


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

# test when the caption and the label are provided
result_cl = df.to_latex(caption=the_caption, label=the_label)

Expand All @@ -500,10 +518,11 @@ def test_to_latex_caption_label(self):
"""
assert result_cl == expected_cl

def test_to_latex_longtable_caption_label(self):
def test_to_latex_longtable_caption_label_position(self):
# GH 25436
the_caption = "a table in a \\texttt{longtable} environment"
the_label = "tab:longtable"
the_position = "t"

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

Expand Down Expand Up @@ -551,6 +570,27 @@ def test_to_latex_longtable_caption_label(self):
"""
assert result_l == expected_l

# test when only the caption 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

# test when the caption and the label are provided
result_cl = df.to_latex(longtable=True, caption=the_caption, label=the_label)

Expand Down