diff --git a/doc/source/v0.14.0.txt b/doc/source/v0.14.0.txt index 9a50df3bd1..cfee48d629 100644 --- a/doc/source/v0.14.0.txt +++ b/doc/source/v0.14.0.txt @@ -286,6 +286,7 @@ There are no deprecations of prior behavior in 0.14.0 Enhancements ~~~~~~~~~~~~ +- ``DataFrame.to_latex`` now takes a longtable keyword, which if True will return a table in a longtable environment. (:issue:`6617`) - pd.read_clipboard will, if 'sep' is unspecified, try to detect data copied from a spreadsheet and parse accordingly. (:issue:`6223`) - ``plot(legend='reverse')`` will now reverse the order of legend labels for diff --git a/pandas/core/format.py b/pandas/core/format.py index 537fdc6cd0..27e1234555 100644 --- a/pandas/core/format.py +++ b/pandas/core/format.py @@ -423,10 +423,12 @@ def _join_multiline(self, *strcols): st = ed return '\n\n'.join(str_lst) - def to_latex(self, force_unicode=None, column_format=None): + def to_latex(self, force_unicode=None, column_format=None, + longtable=False): """ - Render a DataFrame to a LaTeX tabular environment output. + Render a DataFrame to a LaTeX tabular/longtable environment output. """ + #TODO: column_format is not settable in df.to_latex def get_col_type(dtype): if issubclass(dtype.type, np.number): return 'r' @@ -460,14 +462,27 @@ def get_col_type(dtype): raise AssertionError('column_format must be str or unicode, not %s' % type(column_format)) - def write(buf, frame, column_format, strcols): - buf.write('\\begin{tabular}{%s}\n' % column_format) - buf.write('\\toprule\n') + def write(buf, frame, column_format, strcols, longtable=False): + if not longtable: + buf.write('\\begin{tabular}{%s}\n' % column_format) + buf.write('\\toprule\n') + else: + buf.write('\\begin{longtable}{%s}\n' % column_format) + buf.write('\\toprule\n') nlevels = frame.index.nlevels for i, row in enumerate(zip(*strcols)): if i == nlevels: buf.write('\\midrule\n') # End of header + if longtable: + buf.write('\\endhead\n') + buf.write('\\midrule\n') + buf.write('\\multicolumn{3}{r}{{Continued on next ' + 'page}} \\\\\n') + buf.write('\midrule\n') + buf.write('\endfoot\n\n') + buf.write('\\bottomrule\n') + buf.write('\\endlastfoot\n') crow = [(x.replace('\\', '\\textbackslash') # escape backslashes first .replace('_', '\\_') .replace('%', '\\%') @@ -481,14 +496,17 @@ def write(buf, frame, column_format, strcols): buf.write(' & '.join(crow)) buf.write(' \\\\\n') - buf.write('\\bottomrule\n') - buf.write('\\end{tabular}\n') + if not longtable: + buf.write('\\bottomrule\n') + buf.write('\\end{tabular}\n') + else: + buf.write('\\end{longtable}\n') if hasattr(self.buf, 'write'): - write(self.buf, frame, column_format, strcols) + write(self.buf, frame, column_format, strcols, longtable) elif isinstance(self.buf, compat.string_types): with open(self.buf, 'w') as f: - write(f, frame, column_format, strcols) + write(f, frame, column_format, strcols, longtable) else: raise TypeError('buf is not a file name and it has no write ' 'method') diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 6a8786229e..3c1c9952bd 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -1380,15 +1380,18 @@ def to_html(self, buf=None, columns=None, col_space=None, colSpace=None, def to_latex(self, buf=None, columns=None, col_space=None, colSpace=None, header=True, index=True, na_rep='NaN', formatters=None, float_format=None, sparsify=None, index_names=True, - bold_rows=True, force_unicode=None): + bold_rows=True, force_unicode=None, longtable=False): """ - Render a DataFrame to a tabular environment table. - You can splice this into a LaTeX document. + Render a DataFrame to a tabular environment table. You can splice + this into a LaTeX document. Requires \\usepackage(booktabs}. `to_latex`-specific options: bold_rows : boolean, default True Make the row labels bold in the output + longtable : boolean, default False + Use a longtable environment instead of tabular. Requires adding + a \\usepackage{longtable} to your LaTeX preamble. """ @@ -1409,7 +1412,7 @@ def to_latex(self, buf=None, columns=None, col_space=None, colSpace=None, bold_rows=bold_rows, sparsify=sparsify, index_names=index_names) - formatter.to_latex() + formatter.to_latex(longtable=longtable) if buf is None: return formatter.buf.getvalue() diff --git a/pandas/tests/test_format.py b/pandas/tests/test_format.py index 5296be15f2..8e1e47dbaa 100644 --- a/pandas/tests/test_format.py +++ b/pandas/tests/test_format.py @@ -1670,6 +1670,49 @@ def test_to_latex(self): """ self.assertEqual(withoutindex_result, withoutindex_expected) + def test_to_latex_longtable(self): + self.frame.to_latex(longtable=True) + + df = DataFrame({'a': [1, 2], + 'b': ['b1', 'b2']}) + withindex_result = df.to_latex(longtable=True) + withindex_expected = r"""\begin{longtable}{lrl} +\toprule +{} & a & b \\ +\midrule +\endhead +\midrule +\multicolumn{3}{r}{{Continued on next page}} \\ +\midrule +\endfoot + +\bottomrule +\endlastfoot +0 & 1 & b1 \\ +1 & 2 & b2 \\ +\end{longtable} +""" + self.assertEqual(withindex_result, withindex_expected) + + withoutindex_result = df.to_latex(index=False, longtable=True) + withoutindex_expected = r"""\begin{longtable}{rl} +\toprule + a & b \\ +\midrule +\endhead +\midrule +\multicolumn{3}{r}{{Continued on next page}} \\ +\midrule +\endfoot + +\bottomrule +\endlastfoot + 1 & b1 \\ + 2 & b2 \\ +\end{longtable} +""" + self.assertEqual(withoutindex_result, withoutindex_expected) + def test_to_latex_escape_special_chars(self): special_characters = ['&','%','$','#','_', '{','}','~','^','\\'] @@ -1791,7 +1834,7 @@ def test_csv_to_string(self): df = DataFrame({'col' : [1,2]}) expected = ',col\n0,1\n1,2\n' self.assertEqual(df.to_csv(), expected) - + class TestSeriesFormatting(tm.TestCase): _multiprocess_can_split_ = True