From 74889fcba116f9c8031b263af78a61750aee70fb Mon Sep 17 00:00:00 2001 From: Rebecca Sweger Date: Tue, 23 May 2017 11:35:16 -0700 Subject: [PATCH 1/4] ENH: Add to_latex() method to Series (#16180) This changeset adds _repr_latex_ to the Series class and moves the to_latex() method from the DataFrame class to the NDFrame class. --- doc/source/api.rst | 1 + doc/source/whatsnew/v0.20.2.txt | 1 + pandas/core/frame.py | 88 ------------------------------ pandas/core/generic.py | 91 +++++++++++++++++++++++++++++++- pandas/core/series.py | 10 ++++ pandas/tests/series/test_repr.py | 20 ++++++- 6 files changed, 121 insertions(+), 90 deletions(-) diff --git a/doc/source/api.rst b/doc/source/api.rst index cb5136df1ff8b..e7d12df56d260 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -724,6 +724,7 @@ Serialization / IO / Conversion Series.to_dense Series.to_string Series.to_clipboard + Series.to_latex Sparse ~~~~~~ diff --git a/doc/source/whatsnew/v0.20.2.txt b/doc/source/whatsnew/v0.20.2.txt index e24b0c229c46c..8c58a9e590f23 100644 --- a/doc/source/whatsnew/v0.20.2.txt +++ b/doc/source/whatsnew/v0.20.2.txt @@ -20,6 +20,7 @@ Enhancements ~~~~~~~~~~~~ - Unblocked access to additional compression types supported in pytables: 'blosc:blosclz, 'blosc:lz4', 'blosc:lz4hc', 'blosc:snappy', 'blosc:zlib', 'blosc:zstd' (:issue:`14478`) +- ``Series`` provides a ``to_latex`` method (:issue:`16180`) .. _whatsnew_0202.performance: diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 78a369761afc1..9f0b6fb993e5a 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -1663,94 +1663,6 @@ def to_html(self, buf=None, columns=None, col_space=None, header=True, if buf is None: return formatter.buf.getvalue() - @Substitution(header='Write out column names. If a list of string is given, \ -it is assumed to be aliases for the column names.') - @Appender(fmt.common_docstring + fmt.return_docstring, indents=1) - 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=True, - column_format=None, longtable=None, escape=None, - encoding=None, decimal='.', multicolumn=None, - multicolumn_format=None, multirow=None): - r""" - 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 - column_format : str, default None - The columns format as specified in `LaTeX table format - `__ e.g 'rcl' for 3 - columns - longtable : boolean, default will be read from the pandas config module - Default: False. - Use a longtable environment instead of tabular. Requires adding - a \usepackage{longtable} to your LaTeX preamble. - escape : boolean, default will be read from the pandas config module - Default: True. - When set to False prevents from escaping latex special - characters in column names. - encoding : str, default None - A string representing the encoding to use in the output file, - defaults to 'ascii' on Python 2 and 'utf-8' on Python 3. - decimal : string, default '.' - Character recognized as decimal separator, e.g. ',' in Europe. - - .. versionadded:: 0.18.0 - - multicolumn : boolean, default True - Use \multicolumn to enhance MultiIndex columns. - The default will be read from the config module. - - .. versionadded:: 0.20.0 - - multicolumn_format : str, default 'l' - The alignment for multicolumns, similar to `column_format` - The default will be read from the config module. - - .. versionadded:: 0.20.0 - - multirow : boolean, default False - Use \multirow to enhance MultiIndex rows. - Requires adding a \usepackage{multirow} to your LaTeX preamble. - Will print centered labels (instead of top-aligned) - across the contained rows, separating groups via clines. - The default will be read from the pandas config module. - - .. versionadded:: 0.20.0 - - """ - # Get defaults from the pandas config - if longtable is None: - longtable = get_option("display.latex.longtable") - if escape is None: - escape = get_option("display.latex.escape") - if multicolumn is None: - multicolumn = get_option("display.latex.multicolumn") - if multicolumn_format is None: - multicolumn_format = get_option("display.latex.multicolumn_format") - if multirow is None: - multirow = get_option("display.latex.multirow") - - formatter = fmt.DataFrameFormatter(self, buf=buf, columns=columns, - col_space=col_space, na_rep=na_rep, - header=header, index=index, - formatters=formatters, - float_format=float_format, - bold_rows=bold_rows, - sparsify=sparsify, - index_names=index_names, - escape=escape, decimal=decimal) - formatter.to_latex(column_format=column_format, longtable=longtable, - encoding=encoding, multicolumn=multicolumn, - multicolumn_format=multicolumn_format, - multirow=multirow) - - if buf is None: - return formatter.buf.getvalue() - def info(self, verbose=None, buf=None, max_cols=None, memory_usage=None, null_counts=None): """ diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 2e7d8693d48dd..2f484228b902b 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -45,7 +45,7 @@ import pandas.core.common as com import pandas.core.missing as missing from pandas.io.formats.printing import pprint_thing -from pandas.io.formats.format import format_percentiles +from pandas.io.formats.format import format_percentiles, DataFrameFormatter from pandas.tseries.frequencies import to_offset from pandas import compat from pandas.compat.numpy import function as nv @@ -1502,6 +1502,95 @@ def to_xarray(self): coords=coords, ) + _shared_docs['to_latex'] = """ + Render an object 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 + column_format : str, default None + The columns format as specified in `LaTeX table format + `__ e.g 'rcl' for 3 + columns + longtable : boolean, default will be read from the pandas config module + Default: False. + Use a longtable environment instead of tabular. Requires adding + a \\usepackage{longtable} to your LaTeX preamble. + escape : boolean, default will be read from the pandas config module + Default: True. + When set to False prevents from escaping latex special + characters in column names. + encoding : str, default None + A string representing the encoding to use in the output file, + defaults to 'ascii' on Python 2 and 'utf-8' on Python 3. + decimal : string, default '.' + Character recognized as decimal separator, e.g. ',' in Europe. + + .. versionadded:: 0.18.0 + + multicolumn : boolean, default True + Use \multicolumn to enhance MultiIndex columns. + The default will be read from the config module. + + .. versionadded:: 0.20.0 + + multicolumn_format : str, default 'l' + The alignment for multicolumns, similar to `column_format` + The default will be read from the config module. + + .. versionadded:: 0.20.0 + + multirow : boolean, default False + Use \multirow to enhance MultiIndex rows. + Requires adding a \\usepackage{multirow} to your LaTeX preamble. + Will print centered labels (instead of top-aligned) + across the contained rows, separating groups via clines. + The default will be read from the pandas config module. + + .. versionadded:: 0.20.0 + """ + + @Substitution(header='Write out column names. If a list of string is given, \ +it is assumed to be aliases for the column names.') + @Appender(_shared_docs['to_latex'] % _shared_doc_kwargs) + 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=True, + column_format=None, longtable=None, escape=None, + encoding=None, decimal='.', multicolumn=None, + multicolumn_format=None, multirow=None): + # Get defaults from the pandas config + if longtable is None: + longtable = config.get_option("display.latex.longtable") + if escape is None: + escape = config.get_option("display.latex.escape") + if multicolumn is None: + multicolumn = config.get_option("display.latex.multicolumn") + if multicolumn_format is None: + multicolumn_format = config.get_option( + "display.latex.multicolumn_format") + if multirow is None: + multirow = config.get_option("display.latex.multirow") + + formatter = DataFrameFormatter(self, buf=buf, columns=columns, + col_space=col_space, na_rep=na_rep, + header=header, index=index, + formatters=formatters, + float_format=float_format, + bold_rows=bold_rows, + sparsify=sparsify, + index_names=index_names, + escape=escape, decimal=decimal) + formatter.to_latex(column_format=column_format, longtable=longtable, + encoding=encoding, multicolumn=multicolumn, + multicolumn_format=multicolumn_format, + multirow=multirow) + + if buf is None: + return formatter.buf.getvalue() + # ---------------------------------------------------------------------- # Fancy Indexing diff --git a/pandas/core/series.py b/pandas/core/series.py index 129f291e5f843..f6daf336055cf 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -391,6 +391,16 @@ def get_values(self): """ same as values (but handles sparseness conversions); is a view """ return self._data.get_values() + def _repr_latex_(self): + """ + Returns a LaTeX representation for a particular Series. + Mainly for use with nbconvert (jupyter notebook conversion to pdf). + """ + if get_option('display.latex.repr'): + return self.to_frame().to_latex() + else: + return None + @property def asobject(self): """ diff --git a/pandas/tests/series/test_repr.py b/pandas/tests/series/test_repr.py index 3af61b0a902d3..c22e2ca8e0dc8 100644 --- a/pandas/tests/series/test_repr.py +++ b/pandas/tests/series/test_repr.py @@ -8,7 +8,7 @@ import numpy as np import pandas as pd -from pandas import (Index, Series, DataFrame, date_range) +from pandas import (Index, Series, DataFrame, date_range, option_context) from pandas.core.index import MultiIndex from pandas.compat import lrange, range, u @@ -180,3 +180,21 @@ def test_timeseries_repr_object_dtype(self): ts2 = ts.iloc[np.random.randint(0, len(ts) - 1, 400)] repr(ts2).splitlines()[-1] + + def test_latex_repr(self): + result = r"""\begin{tabular}{ll} +\toprule +{} & 0 \\ +\midrule +0 & $\alpha$ \\ +1 & b \\ +2 & c \\ +\bottomrule +\end{tabular} +""" + with option_context('display.latex.escape', False, + 'display.latex.repr', True): + s = Series([r'$\alpha$', 'b', 'c']) + assert result == s._repr_latex_() + + assert s._repr_latex_() is None From d8d2b6286e3b39a6fa242d02f6ac6960aa3ebc63 Mon Sep 17 00:00:00 2001 From: Rebecca Sweger Date: Thu, 25 May 2017 23:38:44 -0400 Subject: [PATCH 2/4] Add Series to_latex test --- pandas/tests/io/formats/test_to_latex.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index 2542deb0cedf1..4ee77abb32c26 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -3,7 +3,7 @@ import pytest import pandas as pd -from pandas import DataFrame, compat +from pandas import DataFrame, compat, Series from pandas.util import testing as tm from pandas.compat import u import codecs @@ -491,3 +491,18 @@ def test_to_latex_decimal(self, frame): """ assert withindex_result == withindex_expected + + def test_to_latex_series(self): + s = Series(['a', 'b', 'c']) + withindex_result = s.to_latex() + withindex_expected = r"""\begin{tabular}{ll} +\toprule +{} & 0 \\ +\midrule +0 & a \\ +1 & b \\ +2 & c \\ +\bottomrule +\end{tabular} +""" + assert withindex_result == withindex_expected From e57acc2edf355df0c1d36a5cffc6fc11052e5b80 Mon Sep 17 00:00:00 2001 From: Rebecca Sweger Date: Thu, 25 May 2017 23:39:25 -0400 Subject: [PATCH 3/4] Move _repr_latex_ to NDFrame Streamline things a bit by moving _repr_latex_ methods out of the Series and DataFrame classes --- pandas/core/frame.py | 10 ---------- pandas/core/generic.py | 12 ++++++++++++ pandas/core/series.py | 10 ---------- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 9f0b6fb993e5a..c1f13f8883bd5 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -634,16 +634,6 @@ def _repr_html_(self): else: return None - def _repr_latex_(self): - """ - Returns a LaTeX representation for a particular Dataframe. - Mainly for use with nbconvert (jupyter notebook conversion to pdf). - """ - if get_option('display.latex.repr'): - return self.to_latex() - else: - return None - @property def style(self): """ diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 2f484228b902b..d921fe3380ef3 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -1050,6 +1050,16 @@ def __setstate__(self, state): # ---------------------------------------------------------------------- # IO + def _repr_latex_(self): + """ + Returns a LaTeX representation for a particular object. + Mainly for use with nbconvert (jupyter notebook conversion to pdf). + """ + if config.get_option('display.latex.repr'): + return self.to_latex() + else: + return None + # ---------------------------------------------------------------------- # I/O Methods @@ -1562,6 +1572,8 @@ def to_latex(self, buf=None, columns=None, col_space=None, header=True, encoding=None, decimal='.', multicolumn=None, multicolumn_format=None, multirow=None): # Get defaults from the pandas config + if self.ndim == 1: + self = self.to_frame() if longtable is None: longtable = config.get_option("display.latex.longtable") if escape is None: diff --git a/pandas/core/series.py b/pandas/core/series.py index f6daf336055cf..129f291e5f843 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -391,16 +391,6 @@ def get_values(self): """ same as values (but handles sparseness conversions); is a view """ return self._data.get_values() - def _repr_latex_(self): - """ - Returns a LaTeX representation for a particular Series. - Mainly for use with nbconvert (jupyter notebook conversion to pdf). - """ - if get_option('display.latex.repr'): - return self.to_frame().to_latex() - else: - return None - @property def asobject(self): """ From 95ab5653cc9c1262bfd591f51ed38ba6181070a2 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 26 May 2017 06:45:24 -0500 Subject: [PATCH 4/4] DOC: Added versionchanged --- pandas/core/generic.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index d921fe3380ef3..594b637c7e5fa 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -1516,6 +1516,9 @@ def to_xarray(self): 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 + `to_latex`-specific options: bold_rows : boolean, default True