Skip to content

Commit

Permalink
ENH: Enable short_caption in to_latex (#35668)
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanovmg authored Oct 17, 2020
1 parent 7eb1063 commit f1b2bb1
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 13 deletions.
26 changes: 26 additions & 0 deletions doc/source/whatsnew/v1.2.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,32 @@ 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 (:issue:`36267`).

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.read_csv_table_precision_default:

Change in default floating precision for ``read_csv`` and ``read_table``
Expand Down
18 changes: 14 additions & 4 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3013,6 +3013,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
Expand Down Expand Up @@ -3074,11 +3077,16 @@ 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 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
.. 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.
Expand All @@ -3087,6 +3095,8 @@ 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}
See Also
--------
Expand All @@ -3097,8 +3107,8 @@ def to_latex(
Examples
--------
>>> df = pd.DataFrame(dict(name=['Raphael', 'Donatello'],
... mask=['red', 'purple'],
... weapon=['sai', 'bo staff']))
... mask=['red', 'purple'],
... weapon=['sai', 'bo staff']))
>>> print(df.to_latex(index=False)) # doctest: +NORMALIZE_WHITESPACE
\begin{{tabular}}{{lll}}
\toprule
Expand Down
2 changes: 1 addition & 1 deletion pandas/io/formats/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -1021,7 +1021,7 @@ def to_latex(
multicolumn: bool = False,
multicolumn_format: Optional[str] = None,
multirow: bool = False,
caption: Optional[str] = None,
caption: Optional[Union[str, Tuple[str, str]]] = None,
label: Optional[str] = None,
position: Optional[str] = None,
) -> Optional[str]:
Expand Down
86 changes: 78 additions & 8 deletions pandas/io/formats/latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.
Expand Down Expand Up @@ -275,6 +308,8 @@ class TableBuilderAbstract(ABC):
Use multirow to enhance MultiIndex rows.
caption: str, optional
Table caption.
short_caption: str, optional
Table short caption.
label: str, optional
LaTeX label.
position: str, optional
Expand All @@ -289,6 +324,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,
):
Expand All @@ -298,6 +334,7 @@ def __init__(
self.multicolumn_format = multicolumn_format
self.multirow = multirow
self.caption = caption
self.short_caption = short_caption
self.label = label
self.position = position

Expand Down Expand Up @@ -384,8 +421,23 @@ def _position_macro(self) -> str:

@property
def _caption_macro(self) -> str:
r"""Caption macro, extracted from self.caption, like \caption{cap}."""
return f"\\caption{{{self.caption}}}" if self.caption else ""
r"""Caption macro, extracted from self.caption.
With short caption:
\caption[short_caption]{caption_string}.
Without short caption:
\caption{caption_string}.
"""
if self.caption:
return "".join(
[
r"\caption",
f"[{self.short_caption}]" if self.short_caption else "",
f"{{{self.caption}}}",
]
)
return ""

@property
def _label_macro(self) -> str:
Expand Down Expand Up @@ -596,15 +648,32 @@ def env_end(self) -> str:


class LatexFormatter(TableFormatter):
"""
r"""
Used to render a DataFrame to a LaTeX tabular/longtable environment output.
Parameters
----------
formatter : `DataFrameFormatter`
longtable : bool, default False
Use longtable environment.
column_format : str, default None
The columns format as specified in `LaTeX table format
<https://en.wikibooks.org/wiki/LaTeX/Tables>`__ e.g 'rcl' for 3 columns
multicolumn : bool, default False
Use \multicolumn to enhance MultiIndex columns.
multicolumn_format : str, default 'l'
The alignment for multicolumns, similar to `column_format`
multirow : bool, default False
Use \multirow to enhance MultiIndex rows.
caption : str 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
The LaTeX positional argument for tables, to be placed after
``\begin{}`` in the output.
See Also
--------
Expand All @@ -619,18 +688,18 @@ def __init__(
multicolumn: bool = False,
multicolumn_format: Optional[str] = None,
multirow: bool = False,
caption: Optional[str] = None,
caption: Optional[Union[str, Tuple[str, str]]] = None,
label: Optional[str] = None,
position: Optional[str] = None,
):
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
self.caption = caption
self.caption, self.short_caption = _split_into_full_short_caption(caption)
self.label = label
self.position = position

Expand Down Expand Up @@ -658,6 +727,7 @@ def builder(self) -> TableBuilderAbstract:
multicolumn_format=self.multicolumn_format,
multirow=self.multirow,
caption=self.caption,
short_caption=self.short_caption,
label=self.label,
position=self.position,
)
Expand All @@ -671,7 +741,7 @@ def _select_builder(self) -> Type[TableBuilderAbstract]:
return TabularBuilder

@property
def column_format(self) -> str:
def column_format(self) -> Optional[str]:
"""Column format."""
return self._column_format

Expand Down
Loading

0 comments on commit f1b2bb1

Please sign in to comment.