From 8982b9090d601493347c309d5c16d1755754b464 Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Sun, 24 Feb 2019 23:10:28 -0700 Subject: [PATCH 01/22] ENH: added optional caption and label support to DataFrame.to_latex() (#25436) --- pandas/core/generic.py | 21 ++- pandas/io/formats/format.py | 8 +- pandas/io/formats/latex.py | 59 +++++++- pandas/tests/io/formats/test_to_latex.py | 163 +++++++++++++++++++++++ 4 files changed, 239 insertions(+), 12 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index d271081aeaa51..7ba307a21213c 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -2740,17 +2740,18 @@ class (index) object 'bird' 'bird' 'mammal' 'mammal' def to_latex(self, buf=None, columns=None, col_space=None, header=True, index=True, na_rep='NaN', formatters=None, float_format=None, sparsify=None, index_names=True, bold_rows=False, - column_format=None, longtable=None, escape=None, - encoding=None, decimal='.', multicolumn=None, - multicolumn_format=None, multirow=None): + column_format=None, longtable=None, caption=None, + label=None, escape=None, encoding=None, decimal='.', + multicolumn=None, multicolumn_format=None, multirow=None): r""" - Render an object to a LaTeX tabular environment table. + Render an object to a LaTeX tabular, longtable, or nested + table/tabular environment. Render an object to a tabular environment table. You can splice this into a LaTeX document. Requires \usepackage{booktabs}. - .. versionchanged:: 0.20.2 - Added to Series + .. versionchanged:: 0.25 + Added caption and label arguments. Parameters ---------- @@ -2790,6 +2791,11 @@ def to_latex(self, buf=None, columns=None, col_space=None, header=True, By default, the value will be read from the pandas config module. Use a longtable environment instead of tabular. Requires adding a \usepackage{longtable} to your LaTeX preamble. + caption : str, optional + The LaTeX caption to be placed inside \caption{} in the output. + label : str, optional + The LaTeX label to be placed inside \label{} in the output. + This is used with \ref{} in the main .tex file. escape : bool, optional By default, the value will be read from the pandas config module. When set to False prevents from escaping latex special @@ -2863,7 +2869,8 @@ def to_latex(self, buf=None, columns=None, col_space=None, header=True, index_names=index_names, escape=escape, decimal=decimal) formatter.to_latex(column_format=column_format, longtable=longtable, - encoding=encoding, multicolumn=multicolumn, + caption=caption, label=label, encoding=encoding, + multicolumn=multicolumn, multicolumn_format=multicolumn_format, multirow=multirow) diff --git a/pandas/io/formats/format.py b/pandas/io/formats/format.py index f8ee9c273fd59..d7ab39f339282 100644 --- a/pandas/io/formats/format.py +++ b/pandas/io/formats/format.py @@ -679,15 +679,17 @@ def _join_multiline(self, *strcols): st = ed return '\n\n'.join(str_lst) - def to_latex(self, column_format=None, longtable=False, encoding=None, - multicolumn=False, multicolumn_format=None, multirow=False): + def to_latex(self, column_format=None, longtable=False, caption=None, + label=None, encoding=None, multicolumn=False, + multicolumn_format=None, multirow=False): """ Render a DataFrame to a LaTeX tabular/longtable environment output. """ from pandas.io.formats.latex import LatexFormatter latex_renderer = LatexFormatter(self, column_format=column_format, - longtable=longtable, + longtable=longtable, caption=caption, + label=label, multicolumn=multicolumn, multicolumn_format=multicolumn_format, multirow=multirow) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index 90be3364932a2..45a8b4203395e 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -4,6 +4,8 @@ """ from __future__ import print_function +import warnings + import numpy as np from pandas.compat import map, range, u, zip @@ -34,19 +36,23 @@ class LatexFormatter(TableFormatter): """ def __init__(self, formatter, column_format=None, longtable=False, - multicolumn=False, multicolumn_format=None, multirow=False): + caption=None, label=None, multicolumn=False, + multicolumn_format=None, multirow=False): self.fmt = formatter self.frame = self.fmt.frame self.bold_rows = self.fmt.kwds.get('bold_rows', False) self.column_format = column_format self.longtable = longtable + self.caption = caption + self.label = label self.multicolumn = multicolumn self.multicolumn_format = multicolumn_format self.multirow = multirow def write_result(self, buf): """ - Render a DataFrame to a LaTeX tabular/longtable environment output. + Render a DataFrame to a LaTeX tabular, longtable, or table/tabular + environment output. """ # string representation of the columns @@ -106,13 +112,58 @@ def pad_empties(x): raise AssertionError('column_format must be str or unicode, ' 'not {typ}'.format(typ=type(column_format))) + use_table_env = False if not self.longtable: + if self.caption is None and self.label is None: + # then write output only in a tabular environment + pass + else: + # then write output in a nested table/tabular environment + use_table_env = True + + if self.caption is None: + caption_ = '' + else: + caption_ = '\n\\caption{{{}}}'.format(self.caption) + + if self.label is None: + label_ = '' + warnings.warn('no LaTeX label has been provided; ' + 'referencing with \\ref{} will not be available') + else: + label_ = '\n\\label{{{}}}'.format(self.label) + + buf.write('\\begin{{table}}\n\\centering{}{}\n'.format( + caption_, + label_ + )) + buf.write('\\begin{{tabular}}{{{fmt}}}\n' .format(fmt=column_format)) buf.write('\\toprule\n') else: buf.write('\\begin{{longtable}}{{{fmt}}}\n' .format(fmt=column_format)) + + if self.caption is None and self.label is None: + pass + else: + if self.caption is None: + pass + else: + buf.write('\\caption{{{}}}'.format(self.caption)) + + if self.label is None: + warnings.warn('no LaTeX label has been provided; ' + 'referencing with \\ref{} will not be available') + else: + buf.write('\\label{{{}}}'.format(self.label)) + + # 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('\\toprule\n') ilevels = self.frame.index.nlevels @@ -167,6 +218,10 @@ def pad_empties(x): if not self.longtable: buf.write('\\bottomrule\n') buf.write('\\end{tabular}\n') + if use_table_env: + buf.write('\\end{table}\n') + else: + pass else: buf.write('\\end{longtable}\n') diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index 1653e474aa7b0..e4e3494960db8 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -448,6 +448,169 @@ def test_to_latex_longtable(self, frame): with3columns_result = df.to_latex(index=False, longtable=True) assert r"\multicolumn{3}" in with3columns_result + def test_to_latex_caption_label(self, frame): + the_caption = 'a table in a \\texttt{table/tabular} environment' + the_label = 'tab:table_tabular' + + frame.to_latex( + caption=the_caption, + label=the_label + ) + + df = DataFrame({'a': [1, 2], 'b': ['b1', 'b2']}) + + # test when only the caption is provided + with tm.assert_produces_warning(UserWarning): + result_c = df.to_latex( + caption=the_caption + ) + + expected_c = r"""\begin{table} +\centering +\caption{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_c == expected_c + + # test when only the label is provided + result_l = df.to_latex( + label=the_label + ) + + expected_l = r"""\begin{table} +\centering +\label{tab:table_tabular} +\begin{tabular}{lrl} +\toprule +{} & a & b \\ +\midrule +0 & 1 & b1 \\ +1 & 2 & b2 \\ +\bottomrule +\end{tabular} +\end{table} +""" + assert result_l == expected_l + + # test when the caption and the label are provided + result_cl = df.to_latex( + caption=the_caption, + label=the_label + ) + + expected_cl = r"""\begin{table} +\centering +\caption{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 + + def test_to_latex_longtable_caption_label(self, frame): + the_caption = 'a table in a \\texttt{longtable} environment' + the_label = 'tab:longtable' + + frame.to_latex( + longtable=True, + caption=the_caption, + label=the_label + ) + + df = DataFrame({'a': [1, 2], 'b': ['b1', 'b2']}) + + # test when only the caption is provided + with tm.assert_produces_warning(): + result_c = df.to_latex( + longtable=True, + caption=the_caption + ) + + expected_c = r"""\begin{longtable}{lrl} +\caption{a table in a \texttt{longtable} environment}\\ +\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_c == expected_c + + # test when only the label is provided + result_l = df.to_latex( + longtable=True, + label=the_label, + ) + + expected_l = r"""\begin{longtable}{lrl} +\label{tab:longtable}\\ +\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_l == expected_l + + # test when the caption and the label are provided + result_cl = df.to_latex( + longtable=True, + caption=the_caption, + label=the_label, + ) + + expected_cl = r"""\begin{longtable}{lrl} +\caption{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 \\ +1 & 2 & b2 \\ +\end{longtable} +""" + assert result_cl == expected_cl + def test_to_latex_escape_special_chars(self): special_characters = ['&', '%', '$', '#', '_', '{', '}', '~', '^', '\\'] From 2ce8c675864979c01faf6115e6994748ec893977 Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Sun, 24 Feb 2019 23:16:31 -0700 Subject: [PATCH 02/22] PEP8 compliance for ENH #25436 --- pandas/io/formats/latex.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index 45a8b4203395e..f4e71e7f1bbcd 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -129,7 +129,8 @@ def pad_empties(x): if self.label is None: label_ = '' warnings.warn('no LaTeX label has been provided; ' - 'referencing with \\ref{} will not be available') + 'referencing with \\ref{} will not' + 'be available') else: label_ = '\n\\label{{{}}}'.format(self.label) @@ -155,7 +156,8 @@ def pad_empties(x): if self.label is None: warnings.warn('no LaTeX label has been provided; ' - 'referencing with \\ref{} will not be available') + 'referencing with \\ref{} will not' + 'be available') else: buf.write('\\label{{{}}}'.format(self.label)) From 0e9d6c7e117fbb5cbc86d8dcc5e6b84514fc27bb Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Fri, 1 Mar 2019 21:36:53 -0700 Subject: [PATCH 03/22] added versionadded lines in NDFrame.to_latex() docstring (#25436) --- pandas/core/generic.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 7ba307a21213c..8581c964dfe48 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -2750,7 +2750,7 @@ def to_latex(self, buf=None, columns=None, col_space=None, header=True, Render an object to a tabular environment table. You can splice this into a LaTeX document. Requires \usepackage{booktabs}. - .. versionchanged:: 0.25 + .. versionchanged:: 0.25.0 Added caption and label arguments. Parameters @@ -2793,9 +2793,11 @@ def to_latex(self, buf=None, columns=None, col_space=None, header=True, adding a \usepackage{longtable} to your LaTeX preamble. caption : str, optional The LaTeX caption to be placed inside \caption{} in the output. + .. versionadded:: 0.25.0 label : str, optional The LaTeX label to be placed inside \label{} in the output. This is used with \ref{} in the main .tex file. + .. versionadded:: 0.25.0 escape : bool, optional By default, the value will be read from the pandas config module. When set to False prevents from escaping latex special From 4e615d2807c7ba4f5aed1373c8ed673fca97d773 Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Fri, 1 Mar 2019 21:39:29 -0700 Subject: [PATCH 04/22] removed UserWarning when only caption is passed to NDFrame.to_latex() (#25436) --- pandas/io/formats/latex.py | 9 +-------- pandas/tests/io/formats/test_to_latex.py | 18 +++++++++--------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index f4e71e7f1bbcd..066f307750ac9 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -4,8 +4,6 @@ """ from __future__ import print_function -import warnings - import numpy as np from pandas.compat import map, range, u, zip @@ -128,9 +126,6 @@ def pad_empties(x): if self.label is None: label_ = '' - warnings.warn('no LaTeX label has been provided; ' - 'referencing with \\ref{} will not' - 'be available') else: label_ = '\n\\label{{{}}}'.format(self.label) @@ -155,9 +150,7 @@ def pad_empties(x): buf.write('\\caption{{{}}}'.format(self.caption)) if self.label is None: - warnings.warn('no LaTeX label has been provided; ' - 'referencing with \\ref{} will not' - 'be available') + pass else: buf.write('\\label{{{}}}'.format(self.label)) diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index e4e3494960db8..880857922e61a 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -449,6 +449,7 @@ def test_to_latex_longtable(self, frame): assert r"\multicolumn{3}" in with3columns_result def test_to_latex_caption_label(self, frame): + # GH 25436 the_caption = 'a table in a \\texttt{table/tabular} environment' the_label = 'tab:table_tabular' @@ -460,10 +461,9 @@ def test_to_latex_caption_label(self, frame): df = DataFrame({'a': [1, 2], 'b': ['b1', 'b2']}) # test when only the caption is provided - with tm.assert_produces_warning(UserWarning): - result_c = df.to_latex( - caption=the_caption - ) + result_c = df.to_latex( + caption=the_caption + ) expected_c = r"""\begin{table} \centering @@ -523,6 +523,7 @@ def test_to_latex_caption_label(self, frame): assert result_cl == expected_cl def test_to_latex_longtable_caption_label(self, frame): + # GH 25436 the_caption = 'a table in a \\texttt{longtable} environment' the_label = 'tab:longtable' @@ -535,11 +536,10 @@ def test_to_latex_longtable_caption_label(self, frame): df = DataFrame({'a': [1, 2], 'b': ['b1', 'b2']}) # test when only the caption is provided - with tm.assert_produces_warning(): - result_c = df.to_latex( - longtable=True, - caption=the_caption - ) + result_c = df.to_latex( + longtable=True, + caption=the_caption + ) expected_c = r"""\begin{longtable}{lrl} \caption{a table in a \texttt{longtable} environment}\\ From 911a49127adb2d5ec1b56dea97a448430b28eace Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Sun, 3 Mar 2019 21:54:25 -0700 Subject: [PATCH 05/22] moved code for table beginning and end writing into functions (#25436) --- pandas/core/generic.py | 2 + pandas/io/formats/latex.py | 134 +++++++++++++++++++++---------------- 2 files changed, 80 insertions(+), 56 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 8581c964dfe48..a1136ba1c0b05 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -2750,6 +2750,8 @@ def to_latex(self, buf=None, columns=None, col_space=None, header=True, Render an object to a tabular environment table. You can splice this into a LaTeX document. Requires \usepackage{booktabs}. + .. versionchanged:: 0.20.2 + Added to Series. .. versionchanged:: 0.25.0 Added caption and label arguments. diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index 066f307750ac9..e896e085ac462 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -110,56 +110,12 @@ def pad_empties(x): raise AssertionError('column_format must be str or unicode, ' 'not {typ}'.format(typ=type(column_format))) - use_table_env = False - if not self.longtable: - if self.caption is None and self.label is None: - # then write output only in a tabular environment - pass - else: - # then write output in a nested table/tabular environment - use_table_env = True - - if self.caption is None: - caption_ = '' - else: - caption_ = '\n\\caption{{{}}}'.format(self.caption) - - if self.label is None: - label_ = '' - else: - label_ = '\n\\label{{{}}}'.format(self.label) - - buf.write('\\begin{{table}}\n\\centering{}{}\n'.format( - caption_, - label_ - )) - - buf.write('\\begin{{tabular}}{{{fmt}}}\n' - .format(fmt=column_format)) - buf.write('\\toprule\n') + if self.longtable: + self._write_longtable_begin(buf, column_format) else: - buf.write('\\begin{{longtable}}{{{fmt}}}\n' - .format(fmt=column_format)) - - if self.caption is None and self.label is None: - pass - else: - if self.caption is None: - pass - else: - buf.write('\\caption{{{}}}'.format(self.caption)) + self._write_tabular_begin(buf, column_format) - if self.label is None: - pass - else: - buf.write('\\label{{{}}}'.format(self.label)) - - # 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('\\toprule\n') + buf.write('\\toprule\n') ilevels = self.frame.index.nlevels clevels = self.frame.columns.nlevels @@ -210,15 +166,10 @@ def pad_empties(x): if self.multirow and i < len(strrows) - 1: self._print_cline(buf, i, len(strcols)) - if not self.longtable: - buf.write('\\bottomrule\n') - buf.write('\\end{tabular}\n') - if use_table_env: - buf.write('\\end{table}\n') - else: - pass + if self.longtable: + self._write_longtable_end(buf) else: - buf.write('\\end{longtable}\n') + self._write_tabular_end(buf) def _format_multicolumn(self, row, ilevels): r""" @@ -294,3 +245,74 @@ def _print_cline(self, buf, i, icol): .format(cl=cl[1], icol=icol)) # remove entries that have been written to buffer self.clinebuf = [x for x in self.clinebuf if x[0] != i] + + def _write_tabular_begin(self, buf, column_format): + """ + write the beginning of a tabular environment or + nested table/tabular environments including caption and label + """ + if self.caption is None and self.label is None: + # then write output only in a tabular environment + pass + else: + # then write output in a nested table/tabular environment + if self.caption is None: + caption_ = '' + else: + caption_ = '\n\\caption{{{}}}'.format(self.caption) + + if self.label is None: + label_ = '' + else: + label_ = '\n\\label{{{}}}'.format(self.label) + + buf.write('\\begin{{table}}\n\\centering{}{}\n'.format( + caption_, + label_ + )) + + buf.write('\\begin{{tabular}}{{{fmt}}}\n'.format(fmt=column_format)) + + def _write_longtable_begin(self, buf, column_format): + """ + write the beginning of a longtable environment including caption and + label if provided by user + """ + buf.write('\\begin{{longtable}}{{{fmt}}}\n'.format(fmt=column_format)) + + if self.caption is None and self.label is None: + pass + else: + if self.caption is None: + pass + else: + buf.write('\\caption{{{}}}'.format(self.caption)) + + if self.label is None: + pass + else: + buf.write('\\label{{{}}}'.format(self.label)) + + # a double-backslash is required at the end of the line + # as discussed here: + # https://tex.stackexchange.com/questions/219138 + buf.write('\\\\\n') + + def _write_tabular_end(self, buf): + """ + write the end of a tabular environment or nested table/tabular + environment + """ + buf.write('\\bottomrule\n') + buf.write('\\end{tabular}\n') + if self.caption is None and self.label is None: + pass + else: + buf.write('\\end{table}\n') + + @staticmethod + def _write_longtable_end(buf): + """ + write the end of a longtable environment + """ + buf.write('\\end{longtable}\n') From 59e7f0499f71f7bf7165d6e5a0a44de87407a7bc Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Thu, 21 Mar 2019 21:53:45 -0600 Subject: [PATCH 06/22] removed use of pytest fixture 'frame' which was not doing anything (#25436) --- pandas/tests/io/formats/test_to_latex.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index 61326ff687bde..09079422861c8 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -391,9 +391,7 @@ def test_to_latex_special_escape(self): """ assert escaped_result == escaped_expected - def test_to_latex_longtable(self, frame): - frame.to_latex(longtable=True) - + def test_to_latex_longtable(self): df = DataFrame({'a': [1, 2], 'b': ['b1', 'b2']}) withindex_result = df.to_latex(longtable=True) withindex_expected = r"""\begin{longtable}{lrl} @@ -442,16 +440,11 @@ def test_to_latex_longtable(self, frame): with3columns_result = df.to_latex(index=False, longtable=True) assert r"\multicolumn{3}" in with3columns_result - def test_to_latex_caption_label(self, frame): + def test_to_latex_caption_label(self): # GH 25436 the_caption = 'a table in a \\texttt{table/tabular} environment' the_label = 'tab:table_tabular' - frame.to_latex( - caption=the_caption, - label=the_label - ) - df = DataFrame({'a': [1, 2], 'b': ['b1', 'b2']}) # test when only the caption is provided @@ -516,17 +509,11 @@ def test_to_latex_caption_label(self, frame): """ assert result_cl == expected_cl - def test_to_latex_longtable_caption_label(self, frame): + def test_to_latex_longtable_caption_label(self): # GH 25436 the_caption = 'a table in a \\texttt{longtable} environment' the_label = 'tab:longtable' - frame.to_latex( - longtable=True, - caption=the_caption, - label=the_label - ) - df = DataFrame({'a': [1, 2], 'b': ['b1', 'b2']}) # test when only the caption is provided From 3e9cf638673dc7ac9894c70887f719edefd81522 Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Thu, 11 Apr 2019 21:54:45 -0600 Subject: [PATCH 07/22] LatexFormatter methods for creating begin/end latex environments return a string; writing to buffer occurs in write_result() (#25436) --- pandas/core/generic.py | 26 +++++--- pandas/io/formats/latex.py | 128 +++++++++++++++++++------------------ 2 files changed, 81 insertions(+), 73 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 4c57e1b12f2ad..8f6293abae85b 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -2781,9 +2781,10 @@ class (index) object 'bird' 'bird' 'mammal' 'mammal' def to_latex(self, buf=None, columns=None, col_space=None, header=True, index=True, na_rep='NaN', formatters=None, float_format=None, sparsify=None, index_names=True, bold_rows=False, - column_format=None, longtable=None, caption=None, - label=None, escape=None, encoding=None, decimal='.', - multicolumn=None, multicolumn_format=None, multirow=None): + column_format=None, longtable=None, escape=None, + encoding=None, decimal='.', multicolumn=None, + multicolumn_format=None, multirow=None, caption=None, + label=None): r""" Render an object to a LaTeX tabular, longtable, or nested table/tabular environment. @@ -2793,6 +2794,7 @@ def to_latex(self, buf=None, columns=None, col_space=None, header=True, .. versionchanged:: 0.20.2 Added to Series. + .. versionchanged:: 0.25.0 Added caption and label arguments. @@ -2834,13 +2836,6 @@ def to_latex(self, buf=None, columns=None, col_space=None, header=True, By default, the value will be read from the pandas config module. Use a longtable environment instead of tabular. Requires adding a \usepackage{longtable} to your LaTeX preamble. - caption : str, optional - The LaTeX caption to be placed inside \caption{} in the output. - .. versionadded:: 0.25.0 - label : str, optional - The LaTeX label to be placed inside \label{} in the output. - This is used with \ref{} in the main .tex file. - .. versionadded:: 0.25.0 escape : bool, optional By default, the value will be read from the pandas config module. When set to False prevents from escaping latex special @@ -2871,6 +2866,17 @@ def to_latex(self, buf=None, columns=None, col_space=None, header=True, .. versionadded:: 0.20.0 + caption : str, optional + The LaTeX caption to be placed inside ``\caption{}`` in the output. + + .. versionadded:: 0.25.0 + + label : str, optional + The LaTeX label to be placed inside ``\label{}`` in the output. + This is used with ``\ref{}`` in the main ``.tex`` file. + + .. versionadded:: 0.25.0 + Returns ------- str or None diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index e896e085ac462..292e30e905820 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -110,10 +110,7 @@ def pad_empties(x): raise AssertionError('column_format must be str or unicode, ' 'not {typ}'.format(typ=type(column_format))) - if self.longtable: - self._write_longtable_begin(buf, column_format) - else: - self._write_tabular_begin(buf, column_format) + buf.write(self._build_latex_begin_env(column_format)) buf.write('\\toprule\n') @@ -166,10 +163,7 @@ def pad_empties(x): if self.multirow and i < len(strrows) - 1: self._print_cline(buf, i, len(strcols)) - if self.longtable: - self._write_longtable_end(buf) - else: - self._write_tabular_end(buf) + buf.write(self._build_latex_end_env()) def _format_multicolumn(self, row, ilevels): r""" @@ -246,73 +240,81 @@ def _print_cline(self, buf, i, icol): # remove entries that have been written to buffer self.clinebuf = [x for x in self.clinebuf if x[0] != i] - def _write_tabular_begin(self, buf, column_format): + def _build_latex_begin_env(self, column_format): """ - write the beginning of a tabular environment or - nested table/tabular environments including caption and label - """ - if self.caption is None and self.label is None: - # then write output only in a tabular environment - pass - else: - # then write output in a nested table/tabular environment - if self.caption is None: - caption_ = '' - else: - caption_ = '\n\\caption{{{}}}'.format(self.caption) + Write the beginning of the latex environment. - if self.label is None: - label_ = '' - else: - label_ = '\n\\label{{{}}}'.format(self.label) - - buf.write('\\begin{{table}}\n\\centering{}{}\n'.format( - caption_, - label_ - )) - - buf.write('\\begin{{tabular}}{{{fmt}}}\n'.format(fmt=column_format)) + This can be a tabular environment or nested table/tabular environments + including caption/label depending on the arguments passed to + ``LatexFormatter.__init__()``. - def _write_longtable_begin(self, buf, column_format): + :return: string to be written to ``buf`` """ - write the beginning of a longtable environment including caption and - label if provided by user - """ - buf.write('\\begin{{longtable}}{{{fmt}}}\n'.format(fmt=column_format)) + str_ = '' + if self.longtable: + str_ += '\\begin{{longtable}}{{{fmt}}}\n'.format(fmt=column_format) - if self.caption is None and self.label is None: - pass - else: - if self.caption is None: + if self.caption is None and self.label is None: pass else: - buf.write('\\caption{{{}}}'.format(self.caption)) - - if self.label is None: + if self.caption is None: + pass + else: + str_ += '\\caption{{{}}}'.format(self.caption) + + if self.label is None: + pass + else: + str_ += '\\label{{{}}}'.format(self.label) + + # a double-backslash is required at the end of the line + # as discussed here: + # https://tex.stackexchange.com/questions/219138 + str_ += '\\\\\n' + else: + if self.caption is None and self.label is None: + # then write output only in a tabular environment pass else: - buf.write('\\label{{{}}}'.format(self.label)) + # then write output in a nested table/tabular environment + if self.caption is None: + caption_ = '' + else: + caption_ = '\n\\caption{{{}}}'.format(self.caption) + + if self.label is None: + label_ = '' + else: + label_ = '\n\\label{{{}}}'.format(self.label) + + str_ += '\\begin{{table}}\n\\centering{}{}\n'.format( + caption_, + label_ + ) - # a double-backslash is required at the end of the line - # as discussed here: - # https://tex.stackexchange.com/questions/219138 - buf.write('\\\\\n') + str_ += '\\begin{{tabular}}{{{fmt}}}\n'.format(fmt=column_format) - def _write_tabular_end(self, buf): + return str_ + + def _build_latex_end_env(self): """ - write the end of a tabular environment or nested table/tabular - environment + Write the end of the latex environment. + + This can be a tabular environment or nested table/tabular environments + depending on the arguments passed to ``LatexFormatter.__init__()``. + + :return: string to be written to ``buf`` """ - buf.write('\\bottomrule\n') - buf.write('\\end{tabular}\n') - if self.caption is None and self.label is None: - pass + str_ = '' + + if self.longtable: + str_ += '\\end{longtable}\n' else: - buf.write('\\end{table}\n') + str_ += '\\bottomrule\n' + str_ += '\\end{tabular}\n' + if self.caption is None and self.label is None: + pass + else: + str_ += '\\end{table}\n' - @staticmethod - def _write_longtable_end(buf): - """ - write the end of a longtable environment - """ - buf.write('\\end{longtable}\n') + return str_ From 5e31aa565c126118a3fcf3bba8ceadd38a6673ce Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Thu, 11 Apr 2019 22:04:17 -0600 Subject: [PATCH 08/22] revised docstring for NDFrame.to_latex() (#25436) --- 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 8f6293abae85b..4ef43618221e4 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -2786,8 +2786,7 @@ def to_latex(self, buf=None, columns=None, col_space=None, header=True, multicolumn_format=None, multirow=None, caption=None, label=None): r""" - Render an object to a LaTeX tabular, longtable, or nested - table/tabular environment. + Render object to a LaTeX tabular, longtable, or nested table/tabular. Render an object to a tabular environment table. You can splice this into a LaTeX document. Requires \usepackage{booktabs}. From 86b3d31f747655d6262fe444b9a0ae6e105f56bd Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Fri, 26 Apr 2019 21:34:41 -0600 Subject: [PATCH 09/22] added whatsnew entry (#25436) --- doc/source/whatsnew/v0.25.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 91b1c30f98b7f..8c8a2e6504f15 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -35,6 +35,7 @@ Other Enhancements - :class:`RangeIndex` has gained :attr:`~RangeIndex.start`, :attr:`~RangeIndex.stop`, and :attr:`~RangeIndex.step` attributes (:issue:`25710`) - :class:`datetime.timezone` objects are now supported as arguments to timezone methods and constructors (:issue:`25065`) - :meth:`DataFrame.query` and :meth:`DataFrame.eval` now supports quoting column names with backticks to refer to names with spaces (:issue:`6508`) +- :meth:`DataFrame.to_latex` now accepts ``caption`` and ``label`` arguments (:issue:`25436`) .. _whatsnew_0250.api_breaking: From 3994a65358a2287efba53694bfd8aa523b2e372d Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Sun, 5 May 2019 16:47:26 -0600 Subject: [PATCH 10/22] revised docstrings to follow numpydoc format (#25436) --- pandas/io/formats/latex.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index 292e30e905820..77fa6131138fe 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -246,9 +246,12 @@ def _build_latex_begin_env(self, column_format): This can be a tabular environment or nested table/tabular environments including caption/label depending on the arguments passed to - ``LatexFormatter.__init__()``. + `LatexFormatter.__init__()`. - :return: string to be written to ``buf`` + Returns + ------- + string + to be written to `buf` """ str_ = '' if self.longtable: @@ -301,9 +304,12 @@ def _build_latex_end_env(self): Write the end of the latex environment. This can be a tabular environment or nested table/tabular environments - depending on the arguments passed to ``LatexFormatter.__init__()``. + depending on the arguments passed to `LatexFormatter.__init__()`. - :return: string to be written to ``buf`` + Returns + ------- + string + to be written to `buf` """ str_ = '' From 3eb433b07d288cf7b1260b8f005cd269e33c83d9 Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Wed, 26 Jun 2019 21:21:58 -0600 Subject: [PATCH 11/22] reverted to methodology of commit 911a491 (#25436) --- pandas/io/formats/latex.py | 134 +++++++++++++++++-------------------- 1 file changed, 63 insertions(+), 71 deletions(-) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index d0902079863fd..d68349b098dee 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -102,7 +102,10 @@ def pad_empties(x): raise AssertionError('column_format must be str or unicode, ' 'not {typ}'.format(typ=type(column_format))) - buf.write(self._build_latex_begin_env(column_format)) + if self.longtable: + self._write_longtable_begin(buf, column_format) + else: + self._write_tabular_begin(buf, column_format) buf.write('\\toprule\n') @@ -155,7 +158,10 @@ def pad_empties(x): if self.multirow and i < len(strrows) - 1: self._print_cline(buf, i, len(strcols)) - buf.write(self._build_latex_end_env()) + if self.longtable: + self._write_longtable_end(buf) + else: + self._write_tabular_end(buf) def _format_multicolumn(self, row, ilevels): r""" @@ -232,87 +238,73 @@ def _print_cline(self, buf, i, icol): # remove entries that have been written to buffer self.clinebuf = [x for x in self.clinebuf if x[0] != i] - def _build_latex_begin_env(self, column_format): + def _write_tabular_begin(self, buf, column_format): """ - Write the beginning of the latex environment. - - This can be a tabular environment or nested table/tabular environments - including caption/label depending on the arguments passed to - `LatexFormatter.__init__()`. - - Returns - ------- - string - to be written to `buf` + write the beginning of a tabular environment or + nested table/tabular environments including caption and label """ - str_ = '' - if self.longtable: - str_ += '\\begin{{longtable}}{{{fmt}}}\n'.format(fmt=column_format) - - if self.caption is None and self.label is None: - pass - else: - if self.caption is None: - pass - else: - str_ += '\\caption{{{}}}'.format(self.caption) - - if self.label is None: - pass - else: - str_ += '\\label{{{}}}'.format(self.label) - - # a double-backslash is required at the end of the line - # as discussed here: - # https://tex.stackexchange.com/questions/219138 - str_ += '\\\\\n' + if self.caption is None and self.label is None: + # then write output only in a tabular environment + pass else: - if self.caption is None and self.label is None: - # then write output only in a tabular environment - pass + # then write output in a nested table/tabular environment + if self.caption is None: + caption_ = '' else: - # then write output in a nested table/tabular environment - if self.caption is None: - caption_ = '' - else: - caption_ = '\n\\caption{{{}}}'.format(self.caption) - - if self.label is None: - label_ = '' - else: - label_ = '\n\\label{{{}}}'.format(self.label) + caption_ = '\n\\caption{{{}}}'.format(self.caption) - str_ += '\\begin{{table}}\n\\centering{}{}\n'.format( - caption_, - label_ - ) + if self.label is None: + label_ = '' + else: + label_ = '\n\\label{{{}}}'.format(self.label) - str_ += '\\begin{{tabular}}{{{fmt}}}\n'.format(fmt=column_format) + buf.write('\\begin{{table}}\n\\centering{}{}\n'.format( + caption_, + label_ + )) - return str_ + buf.write('\\begin{{tabular}}{{{fmt}}}\n'.format(fmt=column_format)) - def _build_latex_end_env(self): + def _write_longtable_begin(self, buf, column_format): """ - Write the end of the latex environment. - - This can be a tabular environment or nested table/tabular environments - depending on the arguments passed to `LatexFormatter.__init__()`. - - Returns - ------- - string - to be written to `buf` + write the beginning of a longtable environment including caption and + label if provided by user """ - str_ = '' + buf.write('\\begin{{longtable}}{{{fmt}}}\n'.format(fmt=column_format)) - if self.longtable: - str_ += '\\end{longtable}\n' + if self.caption is None and self.label is None: + pass else: - str_ += '\\bottomrule\n' - str_ += '\\end{tabular}\n' - if self.caption is None and self.label is None: + if self.caption is None: pass else: - str_ += '\\end{table}\n' + buf.write('\\caption{{{}}}'.format(self.caption)) + + if self.label is None: + pass + else: + buf.write('\\label{{{}}}'.format(self.label)) + + # a double-backslash is required at the end of the line + # as discussed here: + # https://tex.stackexchange.com/questions/219138 + buf.write('\\\\\n') - return str_ + def _write_tabular_end(self, buf): + """ + write the end of a tabular environment or nested table/tabular + environment + """ + buf.write('\\bottomrule\n') + buf.write('\\end{tabular}\n') + if self.caption is None and self.label is None: + pass + else: + buf.write('\\end{table}\n') + + @staticmethod + def _write_longtable_end(buf): + """ + write the end of a longtable environment + """ + buf.write('\\end{longtable}\n') From a7df686e87c1a9047773ce13839f1d1e3d42e274 Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Thu, 18 Jul 2019 21:38:26 -0600 Subject: [PATCH 12/22] added Parameters to docstrings in pandas/io/formats/latex.py (#25436) --- pandas/io/formats/latex.py | 50 ++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index d68349b098dee..e4df564142245 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -240,8 +240,19 @@ def _print_cline(self, buf, i, icol): def _write_tabular_begin(self, buf, column_format): """ - write the beginning of a tabular environment or - nested table/tabular environments including caption and label + Write the beginning of a tabular environment or + nested table/tabular environments including caption and label. + + Parameters + ---------- + buf : string or file handle + File path or object. If not specified, the result is returned as + a string. + column_format : str, default None + The columns format as specified in `LaTeX table format + `__ e.g 'rcl' + for 3 columns + """ if self.caption is None and self.label is None: # then write output only in a tabular environment @@ -267,8 +278,19 @@ def _write_tabular_begin(self, buf, column_format): def _write_longtable_begin(self, buf, column_format): """ - write the beginning of a longtable environment including caption and - label if provided by user + Write the beginning of a longtable environment including caption and + label if provided by user. + + Parameters + ---------- + buf : string or file handle + File path or object. If not specified, the result is returned as + a string. + column_format : str, default None + The columns format as specified in `LaTeX table format + `__ e.g 'rcl' + for 3 columns + """ buf.write('\\begin{{longtable}}{{{fmt}}}\n'.format(fmt=column_format)) @@ -292,8 +314,15 @@ def _write_longtable_begin(self, buf, column_format): def _write_tabular_end(self, buf): """ - write the end of a tabular environment or nested table/tabular - environment + Write the end of a tabular environment or nested table/tabular + environment. + + Parameters + ---------- + buf : string or file handle + File path or object. If not specified, the result is returned as + a string. + """ buf.write('\\bottomrule\n') buf.write('\\end{tabular}\n') @@ -305,6 +334,13 @@ def _write_tabular_end(self, buf): @staticmethod def _write_longtable_end(buf): """ - write the end of a longtable environment + Write the end of a longtable environment. + + Parameters + ---------- + buf : string or file handle + File path or object. If not specified, the result is returned as + a string. + """ buf.write('\\end{longtable}\n') From 4969d3be3831cc57c87fa00e545cc702d769e343 Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Thu, 18 Jul 2019 21:41:38 -0600 Subject: [PATCH 13/22] changed location of LatexFormatter._write_tabular_end() in source code (#25436) --- pandas/io/formats/latex.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index e4df564142245..efa0f1208ed35 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -276,6 +276,25 @@ def _write_tabular_begin(self, buf, column_format): buf.write('\\begin{{tabular}}{{{fmt}}}\n'.format(fmt=column_format)) + def _write_tabular_end(self, buf): + """ + Write the end of a tabular environment or nested table/tabular + environment. + + Parameters + ---------- + buf : string or file handle + File path or object. If not specified, the result is returned as + a string. + + """ + buf.write('\\bottomrule\n') + buf.write('\\end{tabular}\n') + if self.caption is None and self.label is None: + pass + else: + buf.write('\\end{table}\n') + def _write_longtable_begin(self, buf, column_format): """ Write the beginning of a longtable environment including caption and @@ -312,25 +331,6 @@ def _write_longtable_begin(self, buf, column_format): # https://tex.stackexchange.com/questions/219138 buf.write('\\\\\n') - def _write_tabular_end(self, buf): - """ - Write the end of a tabular environment or nested table/tabular - environment. - - Parameters - ---------- - buf : string or file handle - File path or object. If not specified, the result is returned as - a string. - - """ - buf.write('\\bottomrule\n') - buf.write('\\end{tabular}\n') - if self.caption is None and self.label is None: - pass - else: - buf.write('\\end{table}\n') - @staticmethod def _write_longtable_end(buf): """ From 1f4ef4529314615248bd01afe0437c7ed79c98fd Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Thu, 18 Jul 2019 21:48:35 -0600 Subject: [PATCH 14/22] negated logic used in if-statements in LatexFormatter._write_*() methods (#25436) --- pandas/io/formats/latex.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index efa0f1208ed35..3853db7f06145 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -254,10 +254,7 @@ def _write_tabular_begin(self, buf, column_format): for 3 columns """ - if self.caption is None and self.label is None: - # then write output only in a tabular environment - pass - else: + if self.caption is not None or self.label is not None: # then write output in a nested table/tabular environment if self.caption is None: caption_ = '' @@ -273,6 +270,9 @@ def _write_tabular_begin(self, buf, column_format): caption_, label_ )) + else: + # then write output only in a tabular environment + pass buf.write('\\begin{{tabular}}{{{fmt}}}\n'.format(fmt=column_format)) @@ -290,10 +290,10 @@ def _write_tabular_end(self, buf): """ buf.write('\\bottomrule\n') buf.write('\\end{tabular}\n') - if self.caption is None and self.label is None: - pass - else: + if self.caption is not None or self.label is not None: buf.write('\\end{table}\n') + else: + pass def _write_longtable_begin(self, buf, column_format): """ @@ -313,9 +313,7 @@ def _write_longtable_begin(self, buf, column_format): """ buf.write('\\begin{{longtable}}{{{fmt}}}\n'.format(fmt=column_format)) - if self.caption is None and self.label is None: - pass - else: + if self.caption is not None or self.label is not None: if self.caption is None: pass else: @@ -330,6 +328,8 @@ def _write_longtable_begin(self, buf, column_format): # as discussed here: # https://tex.stackexchange.com/questions/219138 buf.write('\\\\\n') + else: + pass @staticmethod def _write_longtable_end(buf): From b9640de09cd4758e4d929871c9c54e560ca3c828 Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Thu, 18 Jul 2019 22:05:59 -0600 Subject: [PATCH 15/22] revised docstring for NDFrame.to_latex() (#25436) --- pandas/core/generic.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 97e95f5538d32..22de9ce154c52 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -2781,8 +2781,9 @@ def to_latex(self, buf=None, columns=None, col_space=None, header=True, r""" Render object to a LaTeX tabular, longtable, or nested table/tabular. - Render an object to a tabular environment table. You can splice - this into a LaTeX document. Requires \usepackage{booktabs}. + Requires ``\usepackage{booktabs}``. The output can be copy/pasted + into a main LaTeX document or read from an external file + with ``\input{table.tex}``. .. versionchanged:: 0.20.2 Added to Series. From 4a1cd9d9d7a126aaec9bbd1ce2baad2884762c67 Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Mon, 29 Jul 2019 20:52:47 -0600 Subject: [PATCH 16/22] removed entry from whatsnew/v0.25.0.rst (#25436) --- doc/source/whatsnew/v0.25.0.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 35fe3e60f6d3d..42e756635e739 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -224,7 +224,6 @@ Other enhancements - :class:`RangeIndex` has gained :attr:`~RangeIndex.start`, :attr:`~RangeIndex.stop`, and :attr:`~RangeIndex.step` attributes (:issue:`25710`) - :class:`datetime.timezone` objects are now supported as arguments to timezone methods and constructors (:issue:`25065`) - :meth:`DataFrame.query` and :meth:`DataFrame.eval` now supports quoting column names with backticks to refer to names with spaces (:issue:`6508`) -- :meth:`DataFrame.to_latex` now accepts ``caption`` and ``label`` arguments (:issue:`25436`) - :func:`merge_asof` now gives a more clear error message when merge keys are categoricals that are not equal (:issue:`26136`) - :meth:`pandas.core.window.Rolling` supports exponential (or Poisson) window type (:issue:`21303`) - Error message for missing required imports now includes the original import error's text (:issue:`23868`) From b6a52aad0a26a316290b0997cf9fb4a0ea2daa7f Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Mon, 29 Jul 2019 20:54:46 -0600 Subject: [PATCH 17/22] revised version numbers in rst directives to 0.26.0 (#25436) --- pandas/core/generic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 8add9a284b5f7..9b6dcfc59b7b2 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -2914,7 +2914,7 @@ def to_latex( .. versionchanged:: 0.20.2 Added to Series. - .. versionchanged:: 0.25.0 + .. versionchanged:: 0.26.0 Added caption and label arguments. Parameters @@ -2988,13 +2988,13 @@ def to_latex( caption : str, optional The LaTeX caption to be placed inside ``\caption{}`` in the output. - .. versionadded:: 0.25.0 + .. versionadded:: 0.26.0 label : str, optional The LaTeX label to be placed inside ``\label{}`` in the output. This is used with ``\ref{}`` in the main ``.tex`` file. - .. versionadded:: 0.25.0 + .. versionadded:: 0.26.0 Returns ------- From 3d45fe5e86f332ccd2bb0e73e7067ad864969286 Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Mon, 29 Jul 2019 21:00:55 -0600 Subject: [PATCH 18/22] created whatsnew/v0.26.0.rst and added entry (#25436) --- doc/source/whatsnew/v0.26.0.rst | 177 ++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 doc/source/whatsnew/v0.26.0.rst diff --git a/doc/source/whatsnew/v0.26.0.rst b/doc/source/whatsnew/v0.26.0.rst new file mode 100644 index 0000000000000..de940b9e8c465 --- /dev/null +++ b/doc/source/whatsnew/v0.26.0.rst @@ -0,0 +1,177 @@ +:orphan: + +.. TODO. Remove the orphan tag. + +.. _whatsnew_0260: + +What's new in 0.26.0 +-------------------- + +Enhancements +~~~~~~~~~~~~ + +- :meth:`DataFrame.to_latex` now accepts ``caption`` and ``label`` arguments (:issue:`25436`) +- +- + +.. _whatsnew_0260.enhancements.other: + +Other enhancements +^^^^^^^^^^^^^^^^^^ + +- +- +- + +.. _whatsnew_0260.bug_fixes: + +Bug fixes +~~~~~~~~~ + + +Categorical +^^^^^^^^^^^ + +- +- +- + +Datetimelike +^^^^^^^^^^^^ + +- +- +- + +Timedelta +^^^^^^^^^ + +- +- +- + +Timezones +^^^^^^^^^ + + +- +- +- + +Numeric +^^^^^^^ + + +- +- +- + +Conversion +^^^^^^^^^^ + +- +- +- + +Strings +^^^^^^^ + +- +- +- + + +Interval +^^^^^^^^ + +- +- +- + +Indexing +^^^^^^^^ + +- +- +- + +Missing +^^^^^^^ + +- +- +- + +MultiIndex +^^^^^^^^^^ + +- +- +- + +I/O +^^^ + +- +- +- + +Plotting +^^^^^^^^ + +- +- +- + +Groupby/resample/rolling +^^^^^^^^^^^^^^^^^^^^^^^^ + + +- +- +- + +Reshaping +^^^^^^^^^ + +- +- +- + +Sparse +^^^^^^ + +- +- +- + + +Build Changes +^^^^^^^^^^^^^ + +- +- +- + +ExtensionArray +^^^^^^^^^^^^^^ + +- +- +- + +Other +^^^^^ + +- +- +- + +.. _whatsnew_0.260.contributors: + +Contributors +~~~~~~~~~~~~ + +.. TODO. Change to v0.26.0..HEAD + +.. contributors:: HEAD..HEAD From bf3404080d02786f99dbde73de16b3dd113a6cf7 Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Tue, 20 Aug 2019 20:48:50 -0600 Subject: [PATCH 19/22] moved whatsnew entry from v0.26.0.rst to v1.0.0.rst (#25436) --- doc/source/whatsnew/v0.26.0.rst | 177 -------------------------------- doc/source/whatsnew/v1.0.0.rst | 2 +- 2 files changed, 1 insertion(+), 178 deletions(-) delete mode 100644 doc/source/whatsnew/v0.26.0.rst diff --git a/doc/source/whatsnew/v0.26.0.rst b/doc/source/whatsnew/v0.26.0.rst deleted file mode 100644 index de940b9e8c465..0000000000000 --- a/doc/source/whatsnew/v0.26.0.rst +++ /dev/null @@ -1,177 +0,0 @@ -:orphan: - -.. TODO. Remove the orphan tag. - -.. _whatsnew_0260: - -What's new in 0.26.0 --------------------- - -Enhancements -~~~~~~~~~~~~ - -- :meth:`DataFrame.to_latex` now accepts ``caption`` and ``label`` arguments (:issue:`25436`) -- -- - -.. _whatsnew_0260.enhancements.other: - -Other enhancements -^^^^^^^^^^^^^^^^^^ - -- -- -- - -.. _whatsnew_0260.bug_fixes: - -Bug fixes -~~~~~~~~~ - - -Categorical -^^^^^^^^^^^ - -- -- -- - -Datetimelike -^^^^^^^^^^^^ - -- -- -- - -Timedelta -^^^^^^^^^ - -- -- -- - -Timezones -^^^^^^^^^ - - -- -- -- - -Numeric -^^^^^^^ - - -- -- -- - -Conversion -^^^^^^^^^^ - -- -- -- - -Strings -^^^^^^^ - -- -- -- - - -Interval -^^^^^^^^ - -- -- -- - -Indexing -^^^^^^^^ - -- -- -- - -Missing -^^^^^^^ - -- -- -- - -MultiIndex -^^^^^^^^^^ - -- -- -- - -I/O -^^^ - -- -- -- - -Plotting -^^^^^^^^ - -- -- -- - -Groupby/resample/rolling -^^^^^^^^^^^^^^^^^^^^^^^^ - - -- -- -- - -Reshaping -^^^^^^^^^ - -- -- -- - -Sparse -^^^^^^ - -- -- -- - - -Build Changes -^^^^^^^^^^^^^ - -- -- -- - -ExtensionArray -^^^^^^^^^^^^^^ - -- -- -- - -Other -^^^^^ - -- -- -- - -.. _whatsnew_0.260.contributors: - -Contributors -~~~~~~~~~~~~ - -.. TODO. Change to v0.26.0..HEAD - -.. contributors:: HEAD..HEAD diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index cc4bab8b9a923..9df4a3c572a81 100644 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -23,7 +23,7 @@ Enhancements .. _whatsnew_1000.enhancements.other: -- +- :meth:`DataFrame.to_latex` now accepts ``caption`` and ``label`` arguments (:issue:`25436`) - Other enhancements From 6c8bf3355dd7cced11cf8702a66fdfb2891d4584 Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Tue, 20 Aug 2019 20:54:58 -0600 Subject: [PATCH 20/22] ran black on latex.py and test_to_latex.py (#25436) --- pandas/io/formats/latex.py | 33 +++++++++---------- pandas/tests/io/formats/test_to_latex.py | 41 +++++++----------------- 2 files changed, 27 insertions(+), 47 deletions(-) diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py index 122107cd602ce..3edba68fc0636 100644 --- a/pandas/io/formats/latex.py +++ b/pandas/io/formats/latex.py @@ -121,7 +121,7 @@ def pad_empties(x): else: self._write_tabular_begin(buf, column_format) - buf.write('\\toprule\n') + buf.write("\\toprule\n") ilevels = self.frame.index.nlevels clevels = self.frame.columns.nlevels @@ -287,24 +287,21 @@ def _write_tabular_begin(self, buf, column_format): if self.caption is not None or self.label is not None: # then write output in a nested table/tabular environment if self.caption is None: - caption_ = '' + caption_ = "" else: - caption_ = '\n\\caption{{{}}}'.format(self.caption) + caption_ = "\n\\caption{{{}}}".format(self.caption) if self.label is None: - label_ = '' + label_ = "" else: - label_ = '\n\\label{{{}}}'.format(self.label) + label_ = "\n\\label{{{}}}".format(self.label) - buf.write('\\begin{{table}}\n\\centering{}{}\n'.format( - caption_, - label_ - )) + buf.write("\\begin{{table}}\n\\centering{}{}\n".format(caption_, label_)) else: # then write output only in a tabular environment pass - buf.write('\\begin{{tabular}}{{{fmt}}}\n'.format(fmt=column_format)) + buf.write("\\begin{{tabular}}{{{fmt}}}\n".format(fmt=column_format)) def _write_tabular_end(self, buf): """ @@ -318,10 +315,10 @@ def _write_tabular_end(self, buf): a string. """ - buf.write('\\bottomrule\n') - buf.write('\\end{tabular}\n') + buf.write("\\bottomrule\n") + buf.write("\\end{tabular}\n") if self.caption is not None or self.label is not None: - buf.write('\\end{table}\n') + buf.write("\\end{table}\n") else: pass @@ -341,23 +338,23 @@ def _write_longtable_begin(self, buf, column_format): for 3 columns """ - buf.write('\\begin{{longtable}}{{{fmt}}}\n'.format(fmt=column_format)) + buf.write("\\begin{{longtable}}{{{fmt}}}\n".format(fmt=column_format)) if self.caption is not None or self.label is not None: if self.caption is None: pass else: - buf.write('\\caption{{{}}}'.format(self.caption)) + buf.write("\\caption{{{}}}".format(self.caption)) if self.label is None: pass else: - buf.write('\\label{{{}}}'.format(self.label)) + buf.write("\\label{{{}}}".format(self.label)) # 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("\\\\\n") else: pass @@ -373,4 +370,4 @@ def _write_longtable_end(buf): a string. """ - buf.write('\\end{longtable}\n') + buf.write("\\end{longtable}\n") diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index f8f5bdcaf4299..9ffb54d23e37e 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -440,15 +440,13 @@ 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_label = 'tab:table_tabular' + the_caption = "a table in a \\texttt{table/tabular} environment" + the_label = "tab:table_tabular" - df = DataFrame({'a': [1, 2], 'b': ['b1', 'b2']}) + df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) # test when only the caption is provided - result_c = df.to_latex( - caption=the_caption - ) + result_c = df.to_latex(caption=the_caption) expected_c = r"""\begin{table} \centering @@ -466,9 +464,7 @@ def test_to_latex_caption_label(self): assert result_c == expected_c # test when only the label is provided - result_l = df.to_latex( - label=the_label - ) + result_l = df.to_latex(label=the_label) expected_l = r"""\begin{table} \centering @@ -486,10 +482,7 @@ def test_to_latex_caption_label(self): assert result_l == expected_l # test when the caption and the label are provided - result_cl = df.to_latex( - caption=the_caption, - label=the_label - ) + result_cl = df.to_latex(caption=the_caption, label=the_label) expected_cl = r"""\begin{table} \centering @@ -509,16 +502,13 @@ def test_to_latex_caption_label(self): def test_to_latex_longtable_caption_label(self): # GH 25436 - the_caption = 'a table in a \\texttt{longtable} environment' - the_label = 'tab:longtable' + the_caption = "a table in a \\texttt{longtable} environment" + the_label = "tab:longtable" - df = DataFrame({'a': [1, 2], 'b': ['b1', 'b2']}) + df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) # test when only the caption is provided - result_c = df.to_latex( - longtable=True, - caption=the_caption - ) + result_c = df.to_latex(longtable=True, caption=the_caption) expected_c = r"""\begin{longtable}{lrl} \caption{a table in a \texttt{longtable} environment}\\ @@ -540,10 +530,7 @@ def test_to_latex_longtable_caption_label(self): assert result_c == expected_c # test when only the label is provided - result_l = df.to_latex( - longtable=True, - label=the_label, - ) + result_l = df.to_latex(longtable=True, label=the_label) expected_l = r"""\begin{longtable}{lrl} \label{tab:longtable}\\ @@ -565,11 +552,7 @@ def test_to_latex_longtable_caption_label(self): assert result_l == expected_l # test when the caption and the label are provided - result_cl = df.to_latex( - longtable=True, - caption=the_caption, - label=the_label, - ) + result_cl = df.to_latex(longtable=True, caption=the_caption, label=the_label) expected_cl = r"""\begin{longtable}{lrl} \caption{a table in a \texttt{longtable} environment}\label{tab:longtable}\\ From 6da6760f5b979020be74277d05e54cc5cf629766 Mon Sep 17 00:00:00 2001 From: jeschwar <36767735+jeschwar@users.noreply.github.com> Date: Sat, 31 Aug 2019 13:40:31 -0600 Subject: [PATCH 21/22] corrected version number in NDFrame.to_latex() docstring (#25436) --- pandas/core/generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index b5e750d87b490..e8b100dd851cb 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -2937,7 +2937,7 @@ def to_latex( .. versionchanged:: 0.20.2 Added to Series. - .. versionchanged:: 0.26.0 + .. versionchanged:: 1.0.0 Added caption and label arguments. Parameters From 2c0f99351438a3b18aca8c5640badb361ebe58f6 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Tue, 3 Sep 2019 13:31:41 -0500 Subject: [PATCH 22/22] fix docstring --- pandas/core/generic.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 29c78faf240f9..b427b1f0ac858 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -3019,9 +3019,7 @@ def to_latex( This is used with ``\ref{}`` in the main ``.tex`` file. .. versionadded:: 1.0.0 - %(returns)s - See Also -------- DataFrame.to_string : Render a DataFrame to a console-friendly @@ -3033,7 +3031,7 @@ def to_latex( >>> df = pd.DataFrame({'name': ['Raphael', 'Donatello'], ... 'mask': ['red', 'purple'], ... 'weapon': ['sai', 'bo staff']}) - >>> print(df.to_latex(index=False)) # doctest: +NORMALIZE_WHITESPACE + >>> print(df.to_latex(index=False)) # doctest: +NORMALIZE_WHITESPACE \begin{tabular}{lll} \toprule name & mask & weapon \\