From 13d07ae9d5e28616e100f2c68718d3fe6156c5fd Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 11 Aug 2020 18:29:43 +0700 Subject: [PATCH 01/28] Extract helper method for caption & label macro Method _compose_caption_and_label_macro unifies creation of caption and label macros for both tabular and longtable envs. --- pandas/io/formats/latex.py | 65 ++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index 5d6f0a08ef2b5..b6868e27e5989 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -289,26 +289,13 @@ def _write_tabular_begin(self, buf, column_format: str): """ if self._table_float: # then write output in a nested table/tabular environment - if self.caption is None: - caption_ = "" - else: - caption_ = f"\n\\caption{{{self.caption}}}" - - if self.label is None: - label_ = "" - else: - label_ = f"\n\\label{{{self.label}}}" - 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 - + buf.write(f"\\begin{{table}}{position_}\n\\centering\n") + if self.caption or self.label: + buf.write(f"{self._compose_caption_and_label_macro()}\n") buf.write(f"\\begin{{tabular}}{{{column_format}}}\n") def _write_tabular_end(self, buf): @@ -345,29 +332,14 @@ def _write_longtable_begin(self, buf, column_format: str): `__ e.g 'rcl' for 3 columns """ - if self.caption is None: - caption_ = "" - else: - caption_ = f"\\caption{{{self.caption}}}" - - if self.label is None: - label_ = "" - else: - label_ = 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") + buf.write(f"\\begin{{longtable}}{position_}{{{column_format}}}\n") + if self.caption or self.label: + buf.write(f"{self._compose_caption_and_label_macro()}\n") @staticmethod def _write_longtable_end(buf): @@ -382,3 +354,28 @@ def _write_longtable_end(buf): """ buf.write("\\end{longtable}\n") + + def _compose_caption_and_label_macro(self): + caption_ = self._compose_caption_macro() + label_ = self._compose_label_macro() + if self.longtable: + # a double-backslash is required at the end of the line + # as discussed here: + # https://tex.stackexchange.com/questions/219138 + double_backslash = "\\\\" + parts = [caption_, label_, double_backslash] + return "".join([x for x in parts if x]) + else: + parts = [caption_, label_] + return "\n".join([x for x in parts if x]) + + def _compose_caption_macro(self): + if self.caption is None: + return "" + return f"\\caption{{{self.caption}}}" + + def _compose_label_macro(self): + if self.label is None: + return "" + else: + return f"\\label{{{self.label}}}" From 8912c8126867dd7d6c723226ad969d93336d7b67 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 11 Aug 2020 18:45:13 +0700 Subject: [PATCH 02/28] Enable short_caption for df.to_latex Kwarg short_caption allows one to add short caption to LaTeX \caption macro. The final caption macro would look like this: ``` \caption[short_caption]{caption} ``` --- pandas/core/generic.py | 13 +++- pandas/io/formats/format.py | 2 + pandas/io/formats/latex.py | 4 + pandas/tests/io/formats/test_to_latex.py | 94 ++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 1 deletion(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 520023050d49d..741c435ff8e34 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -2885,6 +2885,7 @@ def to_latex( multicolumn_format=None, multirow=None, caption=None, + short_caption=None, label=None, position=None, ): @@ -2966,7 +2967,10 @@ def to_latex( The LaTeX caption to be placed inside ``\caption{}`` in the output. .. versionadded:: 1.0.0 - + short_caption : str, optional + The LaTeX short caption. + Full caption output would look like this: + ``\caption[short_caption]{caption}``. label : str, optional The LaTeX label to be placed inside ``\label{}`` in the output. This is used with ``\ref{}`` in the main ``.tex`` file. @@ -3010,6 +3014,12 @@ def to_latex( multicolumn_format = config.get_option("display.latex.multicolumn_format") if multirow is None: multirow = config.get_option("display.latex.multirow") + if short_caption and not caption: + caption = short_caption + warnings.warn( + f'short_caption is provided, but caption is not provided.\n' + f'Using short_caption value instead.' + ) formatter = DataFrameFormatter( self, @@ -3035,6 +3045,7 @@ def to_latex( multicolumn_format=multicolumn_format, multirow=multirow, caption=caption, + short_caption=short_caption, label=label, position=position, ) diff --git a/pandas/io/formats/format.py b/pandas/io/formats/format.py index 81990b3d505e1..e7655f12e1a4c 100644 --- a/pandas/io/formats/format.py +++ b/pandas/io/formats/format.py @@ -930,6 +930,7 @@ def to_latex( 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, ) -> Optional[str]: @@ -946,6 +947,7 @@ def to_latex( multicolumn_format=multicolumn_format, multirow=multirow, caption=caption, + short_caption=short_caption, label=label, position=position, ).get_result(buf=buf, encoding=encoding) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index b6868e27e5989..79be89798c87f 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -37,6 +37,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, ): @@ -49,6 +50,7 @@ def __init__( self.multicolumn_format = multicolumn_format self.multirow = multirow self.caption = caption + self.short_caption = short_caption self.label = label self.escape = self.fmt.escape self.position = position @@ -372,6 +374,8 @@ def _compose_caption_and_label_macro(self): def _compose_caption_macro(self): if self.caption is None: return "" + if self.short_caption: + return f"\\caption[{self.short_caption}]{{{self.caption}}}" return f"\\caption{{{self.caption}}}" def _compose_label_macro(self): diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index 93ad3739e59c7..278ae1913af65 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -441,6 +441,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"]}) @@ -500,9 +501,75 @@ def test_to_latex_caption_label(self): """ assert result_cl == expected_cl + # test when the short_caption is provided alongside caption + result_cl = df.to_latex(caption=the_caption, short_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, + short_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 when the short_caption is provided but caption is not + warn_msg = 'short_caption is provided, but caption is not provided' + with pytest.warns(UserWarning, match=warn_msg): + result_cl = df.to_latex( + short_caption=the_short_caption, + label=the_label, + ) + + expected_cl = r"""\begin{table} +\centering +\caption[a table]{a table} +\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 + 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"]}) @@ -565,6 +632,33 @@ 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 + result_cl = df.to_latex( + longtable=True, + caption=the_caption, + short_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 \\ From 3f71b467da92ce9e996a221c3df6004c15d2dd37 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 11 Aug 2020 19:19:52 +0700 Subject: [PATCH 03/28] Replace unwanted pytest.warns with tm.assert... --- pandas/tests/io/formats/test_to_latex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index 278ae1913af65..41c11b0eed5b3 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -544,7 +544,7 @@ def test_to_latex_caption_label(self): # test when the short_caption is provided but caption is not warn_msg = 'short_caption is provided, but caption is not provided' - with pytest.warns(UserWarning, match=warn_msg): + with tm.assert_produces_warning(UserWarning): result_cl = df.to_latex( short_caption=the_short_caption, label=the_label, From 22d2ca48b342982fed077abc78fca632233a06b1 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 11 Aug 2020 19:20:11 +0700 Subject: [PATCH 04/28] Fix missing f-string placeholder --- pandas/core/generic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 741c435ff8e34..16f3878590378 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -3017,8 +3017,8 @@ def to_latex( if short_caption and not caption: caption = short_caption warnings.warn( - f'short_caption is provided, but caption is not provided.\n' - f'Using short_caption value instead.' + 'short_caption is provided, but caption is not provided.\n' + 'Using short_caption value instead.' ) formatter = DataFrameFormatter( From 01152c1aeacfc19ac8a9c86b3e8133286ea652bc Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 11 Aug 2020 19:23:33 +0700 Subject: [PATCH 05/28] Apply black --- pandas/core/generic.py | 4 ++-- pandas/tests/io/formats/test_to_latex.py | 10 ++-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 16f3878590378..d191234c13a45 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -3017,8 +3017,8 @@ def to_latex( if short_caption and not caption: caption = short_caption warnings.warn( - 'short_caption is provided, but caption is not provided.\n' - 'Using short_caption value instead.' + "short_caption is provided, but caption is not provided.\n" + "Using short_caption value instead." ) formatter = DataFrameFormatter( diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index 41c11b0eed5b3..8ae207d995ef2 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -521,9 +521,7 @@ def test_to_latex_caption_label(self): # test when the short_caption is provided alongside caption and label result_cl = df.to_latex( - caption=the_caption, - short_caption=the_short_caption, - label=the_label, + caption=the_caption, short_caption=the_short_caption, label=the_label, ) expected_cl = r"""\begin{table} @@ -543,12 +541,8 @@ def test_to_latex_caption_label(self): assert result_cl == expected_cl # test when the short_caption is provided but caption is not - warn_msg = 'short_caption is provided, but caption is not provided' with tm.assert_produces_warning(UserWarning): - result_cl = df.to_latex( - short_caption=the_short_caption, - label=the_label, - ) + result_cl = df.to_latex(short_caption=the_short_caption, label=the_label) expected_cl = r"""\begin{table} \centering From 6adb05c2487a886c3078dbaf0294d9709de9be5d Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 18 Aug 2020 21:37:00 +0700 Subject: [PATCH 06/28] Optionally unpack caption=(caption, short_caption) There was a discussion after pull request in regards to the new kwarg ``short_caption``. It was decided not to introduce the new kwarg into method ``pd.DataFrame.to_latex()``, but rather optionally unpack caption into a tuple (caption, short_caption). So, if caption = str, then short_caption is None and caption macros will look like this: ``` \caption{caption} ``` If caption = (long_caption, short_caption), then caption macros will look like this: ``` \caption[short_caption]{caption} ``` --- pandas/core/generic.py | 30 ++++++++++++++---------- pandas/tests/io/formats/test_to_latex.py | 27 +++------------------ 2 files changed, 20 insertions(+), 37 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index d191234c13a45..14cff6b81dace 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -2884,7 +2884,7 @@ def to_latex( multicolumn=None, multicolumn_format=None, multirow=None, - caption=None, + caption: Optional[Union[str, Tuple[str, str]]] = None, short_caption=None, label=None, position=None, @@ -2963,14 +2963,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 - short_caption : str, optional - The LaTeX short caption. - Full caption output would look like this: - ``\caption[short_caption]{caption}``. label : str, optional The LaTeX label to be placed inside ``\label{}`` in the output. This is used with ``\ref{}`` in the main ``.tex`` file. @@ -3014,12 +3012,18 @@ def to_latex( multicolumn_format = config.get_option("display.latex.multicolumn_format") if multirow is None: multirow = config.get_option("display.latex.multirow") - if short_caption and not caption: - caption = short_caption - warnings.warn( - "short_caption is provided, but caption is not provided.\n" - "Using short_caption value instead." - ) + + if caption: + 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, diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index 8ae207d995ef2..1025620401f8a 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -502,7 +502,7 @@ def test_to_latex_caption_label(self): assert result_cl == expected_cl # test when the short_caption is provided alongside caption - result_cl = df.to_latex(caption=the_caption, short_caption=the_short_caption) + result_cl = df.to_latex(caption=(the_caption, the_short_caption)) expected_cl = r"""\begin{table} \centering @@ -521,7 +521,7 @@ def test_to_latex_caption_label(self): # test when the short_caption is provided alongside caption and label result_cl = df.to_latex( - caption=the_caption, short_caption=the_short_caption, label=the_label, + caption=(the_caption, the_short_caption), label=the_label, ) expected_cl = r"""\begin{table} @@ -540,26 +540,6 @@ def test_to_latex_caption_label(self): """ assert result_cl == expected_cl - # test when the short_caption is provided but caption is not - with tm.assert_produces_warning(UserWarning): - result_cl = df.to_latex(short_caption=the_short_caption, label=the_label) - - expected_cl = r"""\begin{table} -\centering -\caption[a table]{a table} -\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 - def test_to_latex_longtable_caption_label(self): # GH 25436 the_caption = "a table in a \\texttt{longtable} environment" @@ -637,8 +617,7 @@ def test_to_latex_longtable_caption_label(self): # test when the caption, the short_caption and the label are provided result_cl = df.to_latex( longtable=True, - caption=the_caption, - short_caption=the_short_caption, + caption=(the_caption, the_short_caption), label=the_label, ) From a7b64c0c75fe225c6d675736049061e8f3e9dfe7 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 18 Aug 2020 22:03:25 +0700 Subject: [PATCH 07/28] Add edge cases for caption testing --- pandas/tests/io/formats/test_to_latex.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index 8ef8d5e1cac43..88586f1b362c9 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -537,6 +537,27 @@ def test_to_latex_caption_label(self): \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 From a2216a1c4b43d6e80dc588800e062e216f552c79 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 18 Aug 2020 22:05:40 +0700 Subject: [PATCH 08/28] Pass through black --- pandas/core/generic.py | 2 +- pandas/io/formats/latex.py | 2 +- pandas/tests/io/formats/test_to_latex.py | 8 +++----- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 3216cb6318c7a..975f7d43e08d3 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -3015,7 +3015,7 @@ def to_latex( multicolumn_format = config.get_option("display.latex.multicolumn_format") if multirow is None: multirow = config.get_option("display.latex.multirow") - + if caption: if isinstance(caption, str): short_caption = "" diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index ffd7333dac6e5..614155f829cd5 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -346,4 +346,4 @@ def _compose_label_macro(self): if self.label is None: return "" else: - return f"\n\\label{{{self.label}}}" \ No newline at end of file + return f"\n\\label{{{self.label}}}" diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index 88586f1b362c9..7ee382d4fbe46 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -542,10 +542,10 @@ def test_to_latex_caption_label(self): # test that wrong number of params is raised with pytest.raises(ValueError): - df.to_latex(caption=(the_caption, the_short_caption, 'extra_string')) + 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') + result_cl = df.to_latex(caption="xy") expected_cl = r"""\begin{table} \centering \caption{xy} @@ -638,9 +638,7 @@ def test_to_latex_longtable_caption_label(self): # test when the caption, the short_caption and the label are provided result_cl = df.to_latex( - longtable=True, - caption=(the_caption, the_short_caption), - label=the_label, + longtable=True, caption=(the_caption, the_short_caption), label=the_label, ) expected_cl = r"""\begin{longtable}{lrl} From 6c93de4c6691e7474768608ee624391f58b5397c Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Wed, 19 Aug 2020 20:37:26 +0700 Subject: [PATCH 09/28] Remove typing and short_caption from to_latex --- pandas/core/generic.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 975f7d43e08d3..add41864e1d3c 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -2887,8 +2887,7 @@ def to_latex( multicolumn=None, multicolumn_format=None, multirow=None, - caption: Optional[Union[str, Tuple[str, str]]] = None, - short_caption=None, + caption=None, label=None, position=None, ): From e70aafa92abc284492dfd4de2f35e97a7a17f3b8 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 8 Sep 2020 03:29:52 +0700 Subject: [PATCH 10/28] DOC: add parameters to LatexFormatter docstring --- pandas/io/formats/latex.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index 9309a01c12f33..fd7952b28f92d 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -601,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 `__ 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 + The LaTeX positional argument for tables, to be placed after + ``\begin{}`` in the output. See Also -------- From a0e3f53f3ad78cf34b1b631f41d1595dcb6972f0 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 8 Sep 2020 12:03:16 +0700 Subject: [PATCH 11/28] TYP: remove type ignore for column_format --- pandas/io/formats/latex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index fd7952b28f92d..610c29c6e9725 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -651,7 +651,7 @@ def __init__( self.fmt = formatter self.frame = self.fmt.frame self.longtable = longtable - self.column_format = column_format # type: ignore[assignment] + self.column_format = column_format self.multicolumn = multicolumn self.multicolumn_format = multicolumn_format self.multirow = multirow From 6725ca82255c5ea77b25f9816326500e55cbe89c Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 8 Sep 2020 12:25:02 +0700 Subject: [PATCH 12/28] REF: move short caption parsing to LatexFormatter --- pandas/core/generic.py | 19 +++--------------- pandas/io/formats/format.py | 4 +--- pandas/io/formats/latex.py | 40 ++++++++++++++++++++++++++----------- 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 27e34608a310b..a724e18a69977 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -3074,9 +3074,9 @@ 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, tuple, optional - Tuple (short_caption, full_caption), - which results in \caption[short_caption]{caption}; + caption : str or tuple, optional + Tuple (full_caption, short_caption), + which results in \caption[short_caption]{full_caption}; if a single string is passed, no short caption will be set. .. versionadded:: 1.0.0 @@ -3124,18 +3124,6 @@ def to_latex( if multirow is None: multirow = config.get_option("display.latex.multirow") - if caption: - 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, @@ -3160,7 +3148,6 @@ def to_latex( multicolumn_format=multicolumn_format, multirow=multirow, caption=caption, - short_caption=short_caption, label=label, position=position, ) diff --git a/pandas/io/formats/format.py b/pandas/io/formats/format.py index 1e1393de6f306..194b920941bc2 100644 --- a/pandas/io/formats/format.py +++ b/pandas/io/formats/format.py @@ -930,8 +930,7 @@ def to_latex( multicolumn: bool = False, multicolumn_format: Optional[str] = None, multirow: bool = False, - caption: Optional[str] = None, - short_caption: Optional[str] = None, + caption: Optional[Union[str, Tuple[str, str]]] = None, label: Optional[str] = None, position: Optional[str] = None, ) -> Optional[str]: @@ -948,7 +947,6 @@ def to_latex( multicolumn_format=multicolumn_format, multirow=multirow, caption=caption, - short_caption=short_caption, label=label, position=position, ) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index 610c29c6e9725..8151e5852fa32 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -2,7 +2,7 @@ Module for formatting output data in Latex. """ from abc import ABC, abstractmethod -from typing import IO, Iterator, List, Optional, Type +from typing import IO, Iterator, List, Optional, Tuple, Type, Union import numpy as np @@ -618,12 +618,10 @@ class LatexFormatter(TableFormatter): 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}``. + caption : str or tuple, optional + Tuple (full_caption, short_caption), + which results in \caption[short_caption]{full_caption}; + if a single string is passed, no short caption will be set. label : str, optional The LaTeX label to be placed inside ``\label{}`` in the output. position : str, optional @@ -643,8 +641,7 @@ def __init__( multicolumn: bool = False, multicolumn_format: Optional[str] = None, multirow: bool = False, - caption: Optional[str] = None, - short_caption: Optional[str] = None, + caption: Optional[Union[str, Tuple[str, str]]] = None, label: Optional[str] = None, position: Optional[str] = None, ): @@ -655,8 +652,7 @@ def __init__( self.multicolumn = multicolumn self.multicolumn_format = multicolumn_format self.multirow = multirow - self.caption = caption - self.short_caption = short_caption + self.caption = caption # type: ignore[assignment] self.label = label self.position = position @@ -698,7 +694,27 @@ def _select_builder(self) -> Type[TableBuilderAbstract]: return TabularBuilder @property - def column_format(self) -> str: + def caption(self) -> str: + return self._caption + + @caption.setter + def caption(self, caption: Optional[Union[str, Tuple[str, str]]]) -> None: + if caption: + if isinstance(caption, str): + self._caption = caption + self.short_caption = "" + else: + try: + self._caption, self.short_caption = caption + except ValueError as err: + msg = "caption must be either str or tuple of two strings" + raise ValueError(msg) from err + else: + self._caption = "" + self.short_caption = "" + + @property + def column_format(self) -> Optional[str]: """Column format.""" return self._column_format From 846642110bcd92ef3ce7837cb6ffc985b63fc5c4 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Thu, 10 Sep 2020 11:44:37 +0700 Subject: [PATCH 13/28] DOC: add whatsnew for position and short caption --- doc/source/whatsnew/v1.2.0.rst | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 9a778acba4764..90eeb61a38652 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -96,6 +96,33 @@ For example: buffer = io.BytesIO() data.to_csv(buffer, mode="w+b", encoding="utf-8", compression="gzip") + +Support for short caption and table position in ``to_latex`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:meth:`DataFrame.to_latex` now allows one to specify +a floating table position (:issue:`35281`) and a short caption. + +New keyword ``position`` is implemented to set the position. + +.. ipython:: python + + data = pd.DataFrame({'a': [1, 2], 'b': [3, 4]}) + table = data.to_latex(position='ht') + print(table) + +Usage of keyword ``caption`` is extended. +Besides taking a single string as an argument, +one can optionally provide a tuple of ``(full_caption, short_caption)`` +to add a short caption macro. + +.. ipython:: python + + data = pd.DataFrame({'a': [1, 2], 'b': [3, 4]}) + table = data.to_latex(caption=('the full long caption', 'short caption')) + print(table) + + .. _whatsnew_120.enhancements.other: Other enhancements From 0cc06644034e1c03f35b8eb47f4e9c5a05a261b1 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Thu, 10 Sep 2020 21:47:08 +0700 Subject: [PATCH 14/28] DOC: add issue number for short caption --- doc/source/whatsnew/v1.2.0.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 90eeb61a38652..72cc71f8cc675 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -101,7 +101,8 @@ Support for short caption and table position in ``to_latex`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :meth:`DataFrame.to_latex` now allows one to specify -a floating table position (:issue:`35281`) and a short caption. +a floating table position (:issue:`35281`) +and a short caption (:issue:`36267`). New keyword ``position`` is implemented to set the position. From 27891a38b0dbc280e8e60d40ee581acc666bb743 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Sun, 13 Sep 2020 21:18:49 +0700 Subject: [PATCH 15/28] CLN: update error message --- pandas/io/formats/latex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index 8151e5852fa32..0c3fb4cc0f7e9 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -707,7 +707,7 @@ def caption(self, caption: Optional[Union[str, Tuple[str, str]]]) -> None: try: self._caption, self.short_caption = caption except ValueError as err: - msg = "caption must be either str or tuple of two strings" + msg = "caption must be either a string or a tuple of two strings" raise ValueError(msg) from err else: self._caption = "" From e43b52f5bd9c925693e0eaaebd6283ed579abaa1 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Sun, 13 Sep 2020 21:19:01 +0700 Subject: [PATCH 16/28] TST: ensure that error message is tested --- pandas/tests/io/formats/test_to_latex.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index b4f4da547788c..45063afa7ccb5 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -559,7 +559,8 @@ def test_to_latex_caption_label(self): assert result_cl == expected_cl # test that wrong number of params is raised - with pytest.raises(ValueError): + msg = "caption must be either a string or a tuple of two strings" + with pytest.raises(ValueError, match=msg): df.to_latex(caption=(the_caption, the_short_caption, "extra_string")) # test that two chars caption is handled correctly From fbea9eb3ebed8947090ba5326b8cc2384565e6c4 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Sun, 13 Sep 2020 21:20:50 +0700 Subject: [PATCH 17/28] TST: add tests for bad tuples --- pandas/tests/io/formats/test_to_latex.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index 45063afa7ccb5..0d241f082f8e2 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -563,6 +563,12 @@ def test_to_latex_caption_label(self): with pytest.raises(ValueError, match=msg): df.to_latex(caption=(the_caption, the_short_caption, "extra_string")) + with pytest.raises(ValueError, match=msg): + df.to_latex(caption=(the_caption,)) + + with pytest.raises(ValueError, match=msg): + df.to_latex(caption=(None,)) + # test that two chars caption is handled correctly result_cl = df.to_latex(caption="xy") expected_cl = r"""\begin{table} From ed0132c87451a614240fb4864e38058099257ccd Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Sun, 13 Sep 2020 21:51:23 +0700 Subject: [PATCH 18/28] DOC: add/update versionadded, versionchanged tags --- pandas/core/generic.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index f7dfdc8ceff8d..c9772d347c9dc 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -3017,6 +3017,9 @@ def to_latex( .. versionchanged:: 1.0.0 Added caption and label arguments. + .. versionchanged:: 1.2.0 + Added position argument, changed meaning of caption argument. + Parameters ---------- buf : str, Path or StringIO-like, optional, default None @@ -3084,6 +3087,10 @@ def to_latex( if a single string is passed, no short caption will be set. .. versionadded:: 1.0.0 + + .. versionchanged:: 1.2.0 + Optionally allow caption to be a tuple (full_caption, short_caption). + label : str, optional The LaTeX label to be placed inside ``\label{}`` in the output. This is used with ``\ref{}`` in the main ``.tex`` file. @@ -3092,6 +3099,7 @@ def to_latex( position : str, optional The LaTeX positional argument for tables, to be placed after ``\begin{}`` in the output. + .. versionadded:: 1.2.0 %(returns)s See Also -------- From ed4b70580ed8d52ecef3d6da416186c6ab0edb0c Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Sun, 13 Sep 2020 22:53:21 +0700 Subject: [PATCH 19/28] TST: add assertions in caption setter to help mypy --- pandas/io/formats/latex.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index 0c3fb4cc0f7e9..8dfb414769a6e 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -712,6 +712,8 @@ def caption(self, caption: Optional[Union[str, Tuple[str, str]]]) -> None: else: self._caption = "" self.short_caption = "" + assert isinstance(self._caption, str) + assert isinstance(self.short_caption, str) @property def column_format(self) -> Optional[str]: From 3453d43942471c2cf0b4731669de9d8a7905f3f4 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Sun, 13 Sep 2020 22:54:00 +0700 Subject: [PATCH 20/28] DOC: add reason for caption type ignore --- pandas/io/formats/latex.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index 8dfb414769a6e..71ecc98be6b95 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -652,7 +652,13 @@ def __init__( self.multicolumn = multicolumn self.multicolumn_format = multicolumn_format self.multirow = multirow + + # Reason for ignoring typing in assignment: + # Inside caption setter we make sure that self._caption is str only. + # Meanwhile mypy would complain that it expects + # Union[str, Tuple[str, str], None]. self.caption = caption # type: ignore[assignment] + self.label = label self.position = position From 16884bc0837ddacdc3dfb90f2dbe574313d22169 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Sun, 13 Sep 2020 23:26:00 +0700 Subject: [PATCH 21/28] DOC: add reason for strrows arg-type ignore --- pandas/io/formats/latex.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index 71ecc98be6b95..234686712645b 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -41,6 +41,12 @@ def __init__( self.multirow = multirow self.clinebuf: List[List[int]] = [] self.strcols = self._get_strcols() + + # Here is a reason for ignoring typing. + # list() implicitly exhausts zip iterator into lists of strings. + # Hence, self.strrows is List[List[str]]. + # Meanwhile, mypy thinks we pass Iterator[Tuple[Any, ...]] to list(), + # but expected Iterable[List[str]]. self.strrows: List[List[str]] = ( list(zip(*self.strcols)) # type: ignore[arg-type] ) @@ -653,7 +659,7 @@ def __init__( self.multicolumn_format = multicolumn_format self.multirow = multirow - # Reason for ignoring typing in assignment: + # Here is a reason for ignoring typing in assignment. # Inside caption setter we make sure that self._caption is str only. # Meanwhile mypy would complain that it expects # Union[str, Tuple[str, str], None]. From 6fd52ca8625d44f0bb8c50d809a0797a4d3097ba Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Sun, 13 Sep 2020 23:50:21 +0700 Subject: [PATCH 22/28] DOC: add missing empty line before versionadded --- pandas/core/generic.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index c9772d347c9dc..e46e48193523b 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -3099,6 +3099,7 @@ def to_latex( position : str, optional The LaTeX positional argument for tables, to be placed after ``\begin{}`` in the output. + .. versionadded:: 1.2.0 %(returns)s See Also From ae1babe8bb1c09eb3a57e9bfe35763dea0897ad0 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 22 Sep 2020 15:30:07 +0700 Subject: [PATCH 23/28] REF: replace caption setter with initialize method --- pandas/io/formats/latex.py | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index 234686712645b..608ac00705038 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -658,13 +658,7 @@ def __init__( self.multicolumn = multicolumn self.multicolumn_format = multicolumn_format self.multirow = multirow - - # Here is a reason for ignoring typing in assignment. - # Inside caption setter we make sure that self._caption is str only. - # Meanwhile mypy would complain that it expects - # Union[str, Tuple[str, str], None]. - self.caption = caption # type: ignore[assignment] - + self.caption, self.short_caption = self._split_into_long_short_caption(caption) self.label = label self.position = position @@ -705,27 +699,23 @@ def _select_builder(self) -> Type[TableBuilderAbstract]: return RegularTableBuilder return TabularBuilder - @property - def caption(self) -> str: - return self._caption - - @caption.setter - def caption(self, caption: Optional[Union[str, Tuple[str, str]]]) -> None: + def _split_into_long_short_caption( + self, caption: Optional[Union[str, Tuple[str, str]]] + ) -> Tuple[str, str]: if caption: if isinstance(caption, str): - self._caption = caption - self.short_caption = "" + long_caption = caption + short_caption = "" else: try: - self._caption, self.short_caption = caption + long_caption, short_caption = caption except ValueError as err: msg = "caption must be either a string or a tuple of two strings" raise ValueError(msg) from err else: - self._caption = "" - self.short_caption = "" - assert isinstance(self._caption, str) - assert isinstance(self.short_caption, str) + long_caption = "" + short_caption = "" + return long_caption, short_caption @property def column_format(self) -> Optional[str]: From 15227b32f154f6f018bc8a02207f95b28d3957cf Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 22 Sep 2020 15:44:06 +0700 Subject: [PATCH 24/28] TST: align longtable test with the recent changes Test for longtable with shorcaption was changed to reflect the recent changes on master branch. The changes in longtable environment were introduced recently, see GH #34360. --- pandas/tests/io/formats/test_to_latex.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index 415d253524b62..abc927ff78525 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -700,6 +700,11 @@ def test_to_latex_longtable_caption_label(self): \toprule {} & a & b \\ \midrule +\endfirsthead +\caption[]{a table in a \texttt{longtable} environment} \\ +\toprule +{} & a & b \\ +\midrule \endhead \midrule \multicolumn{3}{r}{{Continued on next page}} \\ From b30d2d7bcc25e58345c1bde325106acca5a8198f Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Wed, 23 Sep 2020 16:48:42 +0700 Subject: [PATCH 25/28] TST: add for list [full_caption, short_caption] --- pandas/tests/io/formats/test_to_latex.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index 9c463c3bcbb72..a703820686ff0 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -684,12 +684,18 @@ def test_to_latex_caption_and_shortcaption( ) assert result == expected + def test_to_latex_caption_and_shortcaption_list_is_ok(self, df_short): + caption = ("Long-long-caption", "Short") + result_tuple = df_short.to_latex(caption=caption) + result_list = df_short.to_latex(caption=list(caption)) + assert result_tuple == result_list + def test_to_latex_caption_shortcaption_and_label( self, df_short, caption_table, short_caption, - label_table + label_table, ): # test when the short_caption is provided alongside caption and label result = df_short.to_latex( @@ -727,7 +733,7 @@ def test_to_latex_caption_shortcaption_and_label( ) def test_to_latex_bad_caption_raises(self, bad_caption): # test that wrong number of params is raised - df = pd.DataFrame({'a': [1]}) + df = pd.DataFrame({"a": [1]}) msg = "caption must be either a string or a tuple of two strings" with pytest.raises(ValueError, match=msg): df.to_latex(caption=bad_caption) From e957c37353625ee38e3896341307f7955c2eda2a Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Wed, 7 Oct 2020 15:23:00 +0700 Subject: [PATCH 26/28] REF: use string concat for caption macro --- pandas/io/formats/latex.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index c1d37d2157c49..5b6f68d89f850 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -397,7 +397,13 @@ def _caption_macro(self) -> str: \caption{caption_string}. """ if self.caption: - return f"\\caption{self._short_caption_macro}{{{self.caption}}}" + return "".join( + [ + r"\caption", + self._short_caption_macro, + f"{{{self.caption}}}", + ] + ) return "" @property From 09d9c85828a31318715d4c6edfba1e507ea2337c Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Wed, 7 Oct 2020 15:35:19 +0700 Subject: [PATCH 27/28] REF: move method to module level --- pandas/io/formats/latex.py | 53 ++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index 5b6f68d89f850..e8cffd9874a9f 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -11,6 +11,39 @@ from pandas.io.formats.format import DataFrameFormatter, TableFormatter +def _split_into_full_short_caption( + caption: Optional[Union[str, Tuple[str, str]]] +) -> Tuple[str, str]: + """Extract full and short captions from caption string/tuple. + + Parameters + ---------- + caption : str or tuple, optional + Either table caption string or tuple (full_caption, short_caption). + If string is provided, then it is treated as table full caption, + while short_caption is considered an empty string. + + Returns + ------- + full_caption, short_caption : tuple + Tuple of full_caption, short_caption strings. + """ + if caption: + if isinstance(caption, str): + full_caption = caption + short_caption = "" + else: + try: + full_caption, short_caption = caption + except ValueError as err: + msg = "caption must be either a string or a tuple of two strings" + raise ValueError(msg) from err + else: + full_caption = "" + short_caption = "" + return full_caption, short_caption + + class RowStringConverter(ABC): r"""Converter for dataframe rows into LaTeX strings. @@ -672,7 +705,7 @@ def __init__( self.multicolumn = multicolumn self.multicolumn_format = multicolumn_format self.multirow = multirow - self.caption, self.short_caption = self._split_into_long_short_caption(caption) + self.caption, self.short_caption = _split_into_full_short_caption(caption) self.label = label self.position = position @@ -713,24 +746,6 @@ def _select_builder(self) -> Type[TableBuilderAbstract]: return RegularTableBuilder return TabularBuilder - def _split_into_long_short_caption( - self, caption: Optional[Union[str, Tuple[str, str]]] - ) -> Tuple[str, str]: - if caption: - if isinstance(caption, str): - long_caption = caption - short_caption = "" - else: - try: - long_caption, short_caption = caption - except ValueError as err: - msg = "caption must be either a string or a tuple of two strings" - raise ValueError(msg) from err - else: - long_caption = "" - short_caption = "" - return long_caption, short_caption - @property def column_format(self) -> Optional[str]: """Column format.""" From 559ca2a25a800d9491bcc0fe4ed13e97256c0efa Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Wed, 7 Oct 2020 16:40:42 +0700 Subject: [PATCH 28/28] REF: drop property _short_caption_macro --- pandas/io/formats/latex.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index e8cffd9874a9f..2eee0ce73291f 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -433,18 +433,12 @@ def _caption_macro(self) -> str: return "".join( [ r"\caption", - self._short_caption_macro, + f"[{self.short_caption}]" if self.short_caption else "", f"{{{self.caption}}}", ] ) return "" - @property - def _short_caption_macro(self) -> str: - if self.short_caption: - return f"[{self.short_caption}]" - return "" - @property def _label_macro(self) -> str: r"""Label macro, extracted from self.label, like \label{ref}."""