-
-
Notifications
You must be signed in to change notification settings - Fork 18.4k
ENH: Enable short_caption in to_latex #35668
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
Changes from 12 commits
13d07ae
8912c81
3f71b46
22d2ca4
01152c1
6adb05c
4725d73
a7b64c0
a2216a1
6c93de4
45de6eb
e70aafa
a0e3f53
6725ca8
8466421
0cc0664
c60a705
27891a3
e43b52f
fbea9eb
29b37e2
ed0132c
ed4b705
3453d43
16884bc
6fd52ca
5acfc44
ae1babe
ddec116
15227b3
336bfb5
b30d2d7
f1781f6
f4b18ff
a18c85d
e957c37
09d9c85
559ca2a
0fd73e8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -3074,11 +3074,12 @@ def to_latex( | |||||
centered labels (instead of top-aligned) across the contained | ||||||
rows, separating groups via clines. The default will be read | ||||||
from the pandas config module. | ||||||
caption : str, optional | ||||||
The LaTeX caption to be placed inside ``\caption{}`` in the output. | ||||||
caption : str, tuple, optional | ||||||
Tuple (short_caption, full_caption), | ||||||
which results in \caption[short_caption]{caption}; | ||||||
if a single string is passed, no short caption will be set. | ||||||
|
||||||
.. versionadded:: 1.0.0 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But I think I should keep There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sure. can have both. and add a one-liner describing the changes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added these tags for caption, position (PR #35284) and |
||||||
|
||||||
label : str, optional | ||||||
The LaTeX label to be placed inside ``\label{}`` in the output. | ||||||
This is used with ``\ref{}`` in the main ``.tex`` file. | ||||||
|
@@ -3123,6 +3124,18 @@ def to_latex( | |||||
if multirow is None: | ||||||
multirow = config.get_option("display.latex.multirow") | ||||||
|
||||||
if caption: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. instead of doing this here, can you do it in the formatter itself (in the constructor is ok) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||||||
if isinstance(caption, str): | ||||||
short_caption = "" | ||||||
else: | ||||||
try: | ||||||
caption, short_caption = caption | ||||||
except ValueError as err: | ||||||
msg = "caption must be either str or tuple of two strings" | ||||||
raise ValueError(msg) from err | ||||||
else: | ||||||
short_caption = None | ||||||
|
||||||
formatter = DataFrameFormatter( | ||||||
self, | ||||||
columns=columns, | ||||||
|
@@ -3147,6 +3160,7 @@ def to_latex( | |||||
multicolumn_format=multicolumn_format, | ||||||
multirow=multirow, | ||||||
caption=caption, | ||||||
short_caption=short_caption, | ||||||
label=label, | ||||||
position=position, | ||||||
) | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -931,6 +931,7 @@ def to_latex( | |
multicolumn_format: Optional[str] = None, | ||
multirow: bool = False, | ||
caption: Optional[str] = None, | ||
short_caption: Optional[str] = None, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
label: Optional[str] = None, | ||
position: Optional[str] = None, | ||
) -> Optional[str]: | ||
|
@@ -947,6 +948,7 @@ def to_latex( | |
multicolumn_format=multicolumn_format, | ||
multirow=multirow, | ||
caption=caption, | ||
short_caption=short_caption, | ||
label=label, | ||
position=position, | ||
) | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -275,6 +275,8 @@ class TableBuilderAbstract(ABC): | |||||
Use multirow to enhance MultiIndex rows. | ||||||
caption: str, optional | ||||||
Table caption. | ||||||
short_caption: str, optional | ||||||
Table short caption. | ||||||
label: str, optional | ||||||
LaTeX label. | ||||||
position: str, optional | ||||||
|
@@ -289,6 +291,7 @@ def __init__( | |||||
multicolumn_format: Optional[str] = None, | ||||||
multirow: bool = False, | ||||||
caption: Optional[str] = None, | ||||||
short_caption: Optional[str] = None, | ||||||
label: Optional[str] = None, | ||||||
position: Optional[str] = None, | ||||||
): | ||||||
|
@@ -298,6 +301,7 @@ def __init__( | |||||
self.multicolumn_format = multicolumn_format | ||||||
self.multirow = multirow | ||||||
self.caption = caption | ||||||
self.short_caption = short_caption | ||||||
self.label = label | ||||||
self.position = position | ||||||
|
||||||
|
@@ -384,8 +388,23 @@ def _position_macro(self) -> str: | |||||
|
||||||
@property | ||||||
def _caption_macro(self) -> str: | ||||||
r"""Caption macro, extracted from self.caption, like \caption{cap}.""" | ||||||
return f"\\caption{{{self.caption}}}" if self.caption else "" | ||||||
r"""Caption macro, extracted from self.caption. | ||||||
|
||||||
With short caption: | ||||||
\caption[short_caption]{caption_string}. | ||||||
|
||||||
Without short caption: | ||||||
\caption{caption_string}. | ||||||
""" | ||||||
if self.caption: | ||||||
return f"\\caption{self._short_caption_macro}{{{self.caption}}}" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
This can be simplified to get rid of the property There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @WillAyd, does my answer make sense? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ivanovmg am looking now, we have 200 open PRs; these take time There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the way you have it is ok; the f-string actually is pretty complicated to grok on the substitutions. if you can make it simpler would take (e.g. use concatenation maybe) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made string joining (and substitution in place). And removed property |
||||||
return "" | ||||||
|
||||||
@property | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See above - I think can remove this altogether |
||||||
def _short_caption_macro(self) -> str: | ||||||
if self.short_caption: | ||||||
return f"[{self.short_caption}]" | ||||||
return "" | ||||||
|
||||||
@property | ||||||
def _label_macro(self) -> str: | ||||||
|
@@ -582,15 +601,34 @@ def env_end(self) -> str: | |||||
|
||||||
|
||||||
class LatexFormatter(TableFormatter): | ||||||
""" | ||||||
r""" | ||||||
Used to render a DataFrame to a LaTeX tabular/longtable environment output. | ||||||
|
||||||
Parameters | ||||||
---------- | ||||||
formatter : `DataFrameFormatter` | ||||||
longtable : bool, default False | ||||||
Use longtable environment. | ||||||
column_format : str, default None | ||||||
The columns format as specified in `LaTeX table format | ||||||
<https://en.wikibooks.org/wiki/LaTeX/Tables>`__ e.g 'rcl' for 3 columns | ||||||
multicolumn : bool, default False | ||||||
Use \multicolumn to enhance MultiIndex columns. | ||||||
multicolumn_format : str, default 'l' | ||||||
The alignment for multicolumns, similar to `column_format` | ||||||
multirow : bool, default False | ||||||
Use \multirow to enhance MultiIndex rows. | ||||||
caption : str, optional | ||||||
Full caption. | ||||||
Caption macro is to be rendered as ``\caption{caption}``. | ||||||
short_caption : str, optional | ||||||
Short caption. | ||||||
Caption macro is to be rendered as ``\caption[short_caption]{caption}``. | ||||||
label : str, optional | ||||||
The LaTeX label to be placed inside ``\label{}`` in the output. | ||||||
position : str, optional | ||||||
jreback marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
The LaTeX positional argument for tables, to be placed after | ||||||
``\begin{}`` in the output. | ||||||
|
||||||
See Also | ||||||
-------- | ||||||
|
@@ -606,6 +644,7 @@ def __init__( | |||||
multicolumn_format: Optional[str] = None, | ||||||
multirow: bool = False, | ||||||
caption: Optional[str] = None, | ||||||
short_caption: Optional[str] = None, | ||||||
label: Optional[str] = None, | ||||||
position: Optional[str] = None, | ||||||
): | ||||||
|
@@ -617,6 +656,7 @@ def __init__( | |||||
self.multicolumn_format = multicolumn_format | ||||||
self.multirow = multirow | ||||||
self.caption = caption | ||||||
self.short_caption = short_caption | ||||||
self.label = label | ||||||
self.position = position | ||||||
|
||||||
|
@@ -644,6 +684,7 @@ def builder(self) -> TableBuilderAbstract: | |||||
multicolumn_format=self.multicolumn_format, | ||||||
multirow=self.multirow, | ||||||
caption=self.caption, | ||||||
short_caption=self.short_caption, | ||||||
label=self.label, | ||||||
position=self.position, | ||||||
) | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -459,6 +459,7 @@ def test_to_latex_longtable(self): | |
def test_to_latex_caption_label(self): | ||
# GH 25436 | ||
the_caption = "a table in a \\texttt{table/tabular} environment" | ||
the_short_caption = "a table" | ||
the_label = "tab:table_tabular" | ||
|
||
df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) | ||
|
@@ -515,12 +516,73 @@ def test_to_latex_caption_label(self): | |
\bottomrule | ||
\end{tabular} | ||
\end{table} | ||
""" | ||
assert result_cl == expected_cl | ||
|
||
# test when the short_caption is provided alongside caption | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you split each comment here into a separate test? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did that. |
||
result_cl = df.to_latex(caption=(the_caption, the_short_caption)) | ||
|
||
expected_cl = r"""\begin{table} | ||
\centering | ||
\caption[a table]{a table in a \texttt{table/tabular} environment} | ||
\begin{tabular}{lrl} | ||
\toprule | ||
{} & a & b \\ | ||
\midrule | ||
0 & 1 & b1 \\ | ||
1 & 2 & b2 \\ | ||
\bottomrule | ||
\end{tabular} | ||
\end{table} | ||
""" | ||
assert result_cl == expected_cl | ||
|
||
# test when the short_caption is provided alongside caption and label | ||
result_cl = df.to_latex( | ||
caption=(the_caption, the_short_caption), label=the_label, | ||
) | ||
|
||
expected_cl = r"""\begin{table} | ||
\centering | ||
\caption[a table]{a table in a \texttt{table/tabular} environment} | ||
\label{tab:table_tabular} | ||
\begin{tabular}{lrl} | ||
\toprule | ||
{} & a & b \\ | ||
\midrule | ||
0 & 1 & b1 \\ | ||
1 & 2 & b2 \\ | ||
\bottomrule | ||
\end{tabular} | ||
\end{table} | ||
""" | ||
assert result_cl == expected_cl | ||
|
||
# test that wrong number of params is raised | ||
with pytest.raises(ValueError): | ||
df.to_latex(caption=(the_caption, the_short_caption, "extra_string")) | ||
|
||
# test that two chars caption is handled correctly | ||
result_cl = df.to_latex(caption="xy") | ||
expected_cl = r"""\begin{table} | ||
\centering | ||
\caption{xy} | ||
\begin{tabular}{lrl} | ||
\toprule | ||
{} & a & b \\ | ||
\midrule | ||
0 & 1 & b1 \\ | ||
1 & 2 & b2 \\ | ||
\bottomrule | ||
\end{tabular} | ||
\end{table} | ||
""" | ||
assert result_cl == expected_cl | ||
|
||
def test_to_latex_longtable_caption_label(self): | ||
# GH 25436 | ||
the_caption = "a table in a \\texttt{longtable} environment" | ||
the_short_caption = "a table" | ||
the_label = "tab:longtable" | ||
|
||
df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) | ||
|
@@ -584,6 +646,31 @@ def test_to_latex_longtable_caption_label(self): | |
\midrule | ||
\endfoot | ||
|
||
\bottomrule | ||
\endlastfoot | ||
0 & 1 & b1 \\ | ||
1 & 2 & b2 \\ | ||
\end{longtable} | ||
""" | ||
assert result_cl == expected_cl | ||
|
||
# test when the caption, the short_caption and the label are provided | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment here - modular tests are much easier to debug in case of future issues There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I absolutely agree. I would prefer this #36528 to be merged first, and then rebase on top of that with the separate tests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Split tests. |
||
result_cl = df.to_latex( | ||
longtable=True, caption=(the_caption, the_short_caption), label=the_label, | ||
) | ||
|
||
expected_cl = r"""\begin{longtable}{lrl} | ||
\caption[a table]{a table in a \texttt{longtable} environment} | ||
\label{tab:longtable}\\ | ||
\toprule | ||
{} & a & b \\ | ||
\midrule | ||
\endhead | ||
\midrule | ||
\multicolumn{3}{r}{{Continued on next page}} \\ | ||
\midrule | ||
\endfoot | ||
|
||
\bottomrule | ||
\endlastfoot | ||
0 & 1 & b1 \\ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
str or tuple, optional (change the signature as well)
Optional[Union[str, Tuple[str, str]]]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I updated typing on caption in DataFrameFormatter.to_latex() and LatexFormatter.