-
-
Notifications
You must be signed in to change notification settings - Fork 18.5k
ENH: Styler.to_latex(): conditional styling with native latex format #40422
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 126 commits
bb501d3
6066449
6e2b788
2f74690
9c9405e
a6772e7
6505564
b25644f
2ba7e02
79b978b
ed8b405
f7d52f4
f35c7e1
521aee2
849656f
cce7285
26edba4
3d7c614
e2f0f0c
3243ed7
0decf2d
c8dc5e5
8c58538
5c9d50a
563aef6
99abe1c
5185f27
a824f13
f6211e8
b46a9af
7e3c5e1
80d3cac
9388dec
ccac263
de69c21
05f5b34
4d98615
f036d25
a9e0ce4
989278f
3a79966
9051bfe
938a0e8
0aed431
8af3c81
2e0aee6
5067ffc
ff4954e
d9836aa
5bdcb4e
6f4a44b
c211480
2d32556
816e62f
9c577ce
74721a8
7c7f0de
f381011
b9ff43d
2f5cdec
4eacb12
fd95e34
7126bda
25bcba9
5ec08d8
b74057b
a49deeb
228f146
44ead2d
0ae8c39
70067c7
3772cc8
19828c3
79ad3a6
03415e5
5697ff9
41760e0
92c11d1
6547c6d
3910fcf
0164d90
463a54b
4b7298f
47a31a9
c373bb6
8b00376
b1e230d
fd19d97
e31dba0
bdab13d
1074b62
394bc3a
034960f
8b62f8d
a0f1e79
2e714e9
38c62eb
f95c21e
c925958
2cc8ad2
7d962df
a6f6af2
dce66d6
8fc9f0b
bb4479b
e2ea5ae
518485d
d99245c
766ad49
ee92f37
b8e3418
2aca649
cf2f1e1
4d23e5e
1787084
9f71ac6
4c0d768
50afe1b
987b11d
5eee5e5
d271b0f
3675492
97aef70
f11ece9
2db0b17
a4e29b5
c9e27f5
e4c9f00
c00fece
c9f85ed
a406ff5
f3ce4ff
652c102
6c017c8
6f2f8a1
cbd189f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ | |
|
||
from pandas._typing import ( | ||
Axis, | ||
FilePathOrBuffer, | ||
FrameOrSeries, | ||
FrameOrSeriesUnion, | ||
IndexLabel, | ||
|
@@ -28,6 +29,7 @@ | |
from pandas.util._decorators import doc | ||
|
||
import pandas as pd | ||
from pandas import RangeIndex | ||
from pandas.api.types import is_list_like | ||
from pandas.core import generic | ||
import pandas.core.common as com | ||
|
@@ -37,6 +39,8 @@ | |
) | ||
from pandas.core.generic import NDFrame | ||
|
||
from pandas.io.formats.format import save_to_buffer | ||
|
||
jinja2 = import_optional_dependency("jinja2", extra="DataFrame.style requires jinja2.") | ||
|
||
from pandas.io.formats.style_render import ( | ||
|
@@ -383,6 +387,330 @@ def to_excel( | |
engine=engine, | ||
) | ||
|
||
def to_latex( | ||
self, | ||
buf: FilePathOrBuffer[str] | None = None, | ||
*, | ||
column_format: str | None = None, | ||
position: str | None = None, | ||
position_float: str | None = None, | ||
hrules: bool = False, | ||
label: str | None = None, | ||
caption: str | None = None, | ||
sparsify: bool | None = None, | ||
multirow_align: str = "c", | ||
multicol_align: str = "r", | ||
siunitx: bool = False, | ||
encoding: str | None = None, | ||
): | ||
r""" | ||
Write Styler to a file, buffer or string in LaTeX format. | ||
|
||
.. versionadded:: 1.3.0 | ||
|
||
Parameters | ||
---------- | ||
buf : str, Path, or StringIO-like, optional, default None | ||
Buffer to write to. If ``None``, the output is returned as a string. | ||
column_format : str, optional | ||
The LaTeX column specification placed in location: | ||
|
||
\\begin{tabular}{<column_format>} | ||
|
||
Defaults to 'l' for index and | ||
non-numeric data columns, and, for numeric data columns, | ||
to 'r' by default, or 'S' if ``siunitx`` is ``True``. | ||
position : str, optional | ||
The LaTeX positional argument (e.g. 'h!') for tables, placed in location: | ||
|
||
\\begin{table}[<position>] | ||
position_float : {"centering", "raggedleft", "raggedright"}, optional | ||
The LaTeX float command placed in location: | ||
|
||
\\begin{table}[<position>] | ||
|
||
\\<position_float> | ||
hrules : bool, default False | ||
Set to `True` to add \\toprule, \\midrule and \\bottomrule from the | ||
{booktabs} LaTeX package. | ||
label : str, optional | ||
The LaTeX label included as: \\label{<label>}. | ||
This is used with \\ref{<label>} in the main .tex file. | ||
caption : str, optional | ||
jreback marked this conversation as resolved.
Show resolved
Hide resolved
|
||
The LaTeX table caption included as: \\caption{<caption>}. | ||
sparsify : bool, optional | ||
Set to ``False`` to print every item of a hierarchical MultiIndex. Defaults | ||
to the pandas ``multi_sparse`` display option. | ||
multirow_align : {"c", "t", "b"} | ||
If sparsifying hierarchical MultiIndexes whether to align text centrally, | ||
at the top or bottom. | ||
multicol_align : {"r", "c", "l"} | ||
If sparsifying hierarchical MultiIndex columns whether to align text at | ||
the left, centrally, or at the right. | ||
siunitx : bool, default False | ||
Set to ``True`` to structure LaTeX compatible with the {siunitx} package. | ||
encoding : str, default "utf-8" | ||
Character encoding setting. | ||
|
||
Returns | ||
------- | ||
str or None | ||
If `buf` is None, returns the result as a string. Otherwise returns `None`. | ||
|
||
See Also | ||
-------- | ||
Styler.format: Format the text display value of cells. | ||
|
||
Notes | ||
----- | ||
**Latex Packages** | ||
|
||
For the following features we recommend the following LaTeX inclusions: | ||
|
||
===================== ========================================================== | ||
Feature Inclusion | ||
===================== ========================================================== | ||
sparse columns none: included within default {tabular} environment | ||
sparse rows \\usepackage{multirow} | ||
hrules \\usepackage{booktabs} | ||
colors \\usepackage[table]{xcolor} | ||
siunitx \\usepackage{siunitx} | ||
bold (with siunitx) | \\usepackage{etoolbox} | ||
| \\robustify\\bfseries | ||
| \\sisetup{detect-all = true} *(within {document})* | ||
italic (with siunitx) | \\usepackage{etoolbox} | ||
| \\robustify\\itshape | ||
| \\sisetup{detect-all = true} *(within {document})* | ||
===================== ========================================================== | ||
|
||
**Cell Styles** | ||
|
||
LaTeX styling can only be rendered if the accompanying styling functions have | ||
been constructed with appropriate LaTeX commands. All styling | ||
functionality is built around the concept of a CSS ``(<attribute>, <value>)`` | ||
pair (see `Table Visualization <../../user_guide/style.ipynb>`_), and this | ||
should be replaced by a LaTeX | ||
``(<command>, <options>)`` approach. Each cell will be styled individually | ||
using nested LaTeX commands with their accompanied options. | ||
|
||
For example the following code will highlight and bold a cell in HTML-CSS: | ||
|
||
>>> df = pd.DataFrame([[1,2], [3,4]]) | ||
>>> s = df.style.highlight_max(axis=None, | ||
... props='background-color:red; font-weight:bold;') | ||
>>> s.render() | ||
|
||
The equivalent using LaTeX only commands is the following: | ||
|
||
>>> s = df.style.highlight_max(axis=None, | ||
... props='cellcolor:{red}; bfseries: ;') | ||
rhshadrach marked this conversation as resolved.
Show resolved
Hide resolved
|
||
>>> s.to_latex() | ||
|
||
Internally these structured LaTeX ``(<command>, <options>)`` pairs | ||
are translated to the | ||
``display_value`` with the default structure: | ||
``\<command><options> <display_value>``. | ||
rhshadrach marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Where there are multiple commands the latter is nested recursively, so that | ||
the above example highlighed cell is rendered as | ||
``\cellcolor{red} \bfseries 4``. | ||
|
||
Occasionally this format does not suit the applied command, or | ||
combination of LaTeX packages that is in use, so additional flags can be | ||
added to the ``<options>``, within the tuple, to result in different | ||
positions of required braces (the **default** being the same as ``--nowrap``): | ||
|
||
=================================== ============================================ | ||
Tuple Format Output Structure | ||
=================================== ============================================ | ||
(<command>,<options>) \\<command><options> <display_value> | ||
(<command>,<options> ``--nowrap``) \\<command><options> <display_value> | ||
(<command>,<options> ``--rwrap``) \\<command><options>{<display_value>} | ||
(<command>,<options> ``--wrap``) {\\<command><options> <display_value>} | ||
(<command>,<options> ``--lwrap``) {\\<command><options>} <display_value> | ||
(<command>,<options> ``--dwrap``) {\\<command><options>}{<display_value>} | ||
=================================== ============================================ | ||
|
||
For example the `textbf` command for font-weight | ||
should always be used with `--rwrap` so ``('textbf', '--rwrap')`` will render a | ||
working cell, wrapped with braces, as ``\textbf{<display_value>}``. | ||
|
||
A more comprehensive example is as follows: | ||
|
||
>>> df = pd.DataFrame([[1, 2.2, "dogs"], [3, 4.4, "cats"], [2, 6.6, "cows"]], | ||
... index=["ix1", "ix2", "ix3"], | ||
... columns=["Integers", "Floats", "Strings"]) | ||
>>> s = df.style.highlight_max( | ||
... props='cellcolor:[HTML]{FFFF00}; color:{red};' | ||
... 'textit:--rwrap; textbf:--rwrap;' | ||
... ) | ||
>>> s.to_latex() | ||
|
||
.. figure:: ../../_static/style/latex_1.png | ||
|
||
**Table Styles** | ||
|
||
Internally Styler uses its ``table_styles`` object to parse the | ||
``column_format``, ``position``, ``position_float``, and ``label`` | ||
input arguments. These arguments are added to table styles in the format: | ||
|
||
.. code-block:: python | ||
|
||
set_table_styles([ | ||
{"selector": "column_format", "props": f":{column_format};"}, | ||
{"selector": "position", "props": f":{position};"}, | ||
{"selector": "position_float", "props": f":{position_float};"}, | ||
{"selector": "label", "props": f":{{{label.replace(':','§')}}};"} | ||
], overwrite=False) | ||
|
||
Exception is made for the ``hrules`` argument which, in fact, controls all three | ||
commands: ``toprule``, ``bottomrule`` and ``midrule`` simultaneously. Instead of | ||
setting ``hrules`` to ``True``, it is also possible to set each | ||
individual rule definition, by manually setting the ``table_styles``, | ||
for example below we set a regular ``toprule``, set an ``hline`` for | ||
``bottomrule`` and exclude the ``midrule``: | ||
|
||
.. code-block:: python | ||
|
||
set_table_styles([ | ||
{'selector': 'toprule', 'props': ':toprule;'}, | ||
{'selector': 'bottomrule', 'props': ':hline;'}, | ||
], overwrite=False) | ||
|
||
If other ``commands`` are added to table styles they will be detected, and | ||
positioned immediately above the '\\begin{tabular}' command. For example to | ||
add odd and even row coloring, from the {colortbl} package, in format | ||
``\rowcolors{1}{pink}{red}``, use: | ||
|
||
.. code-block:: python | ||
|
||
set_table_styles([ | ||
{'selector': 'rowcolors', 'props': ':{1}{pink}{red};'} | ||
], overwrite=False) | ||
|
||
A more comprehensive example using these arguments is as follows: | ||
|
||
>>> df.columns = pd.MultiIndex.from_tuples([ | ||
... ("Numeric", "Integers"), | ||
... ("Numeric", "Floats"), | ||
... ("Non-Numeric", "Strings") | ||
... ]) | ||
>>> df.index = pd.MultiIndex.from_tuples([ | ||
... ("L0", "ix1"), ("L0", "ix2"), ("L1", "ix3") | ||
... ]) | ||
>>> s = df.style.highlight_max( | ||
... props='cellcolor:[HTML]{FFFF00}; color:{red}; itshape:; bfseries:;' | ||
... ) | ||
>>> s.to_latex( | ||
... column_format="rrrrr", position="h", position_float="centering", | ||
... hrules=True, label="table:5", caption="Styled LaTeX Table", | ||
... multirow_align="t", multicol_align="r" | ||
... ) | ||
|
||
.. figure:: ../../_static/style/latex_2.png | ||
|
||
**Formatting** | ||
|
||
To format values :meth:`Styler.format` should be used prior to calling | ||
`Styler.to_latex`, as well as other methods such as :meth:`Styler.hide_index` | ||
or :meth:`Styler.hide_columns`, for example: | ||
|
||
>>> s.clear() | ||
>>> s.table_styles = [] | ||
>>> s.caption = None | ||
>>> s.format({ | ||
... ("Numeric", "Integers"): '\${}', | ||
... ("Numeric", "Floats"): '{:.3f}', | ||
... ("Non-Numeric", "Strings"): str.upper | ||
... }) | ||
>>> s.to_latex() | ||
\begin{tabular}{llrrl} | ||
{} & {} & \multicolumn{2}{r}{Numeric} & {Non-Numeric} \\ | ||
{} & {} & {Integers} & {Floats} & {Strings} \\ | ||
\multirow[c]{2}{*}{L0} & ix1 & \\$1 & 2.200 & DOGS \\ | ||
& ix2 & \$3 & 4.400 & CATS \\ | ||
L1 & ix3 & \$2 & 6.600 & COWS \\ | ||
\end{tabular} | ||
""" | ||
table_selectors = ( | ||
[style["selector"] for style in self.table_styles] | ||
if self.table_styles is not None | ||
else [] | ||
) | ||
|
||
if column_format is not None: | ||
# add more recent setting to table_styles | ||
self.set_table_styles( | ||
[{"selector": "column_format", "props": f":{column_format}"}], | ||
overwrite=False, | ||
) | ||
elif "column_format" in table_selectors: | ||
pass # adopt what has been previously set in table_styles | ||
else: | ||
rhshadrach marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# create a default: set float, complex, int cols to 'r' ('S'), index to 'l' | ||
_original_columns = self.data.columns | ||
self.data.columns = RangeIndex(stop=len(self.data.columns)) | ||
numeric_cols = self.data._get_numeric_data().columns.to_list() | ||
self.data.columns = _original_columns | ||
column_format = "" if self.hidden_index else "l" * self.data.index.nlevels | ||
for ci, _ in enumerate(self.data.columns): | ||
if ci not in self.hidden_columns: | ||
column_format += ( | ||
("r" if not siunitx else "S") if ci in numeric_cols else "l" | ||
) | ||
self.set_table_styles( | ||
[{"selector": "column_format", "props": f":{column_format}"}], | ||
overwrite=False, | ||
) | ||
|
||
if position: | ||
rhshadrach marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self.set_table_styles( | ||
[{"selector": "position", "props": f":{position}"}], | ||
overwrite=False, | ||
) | ||
|
||
if position_float: | ||
rhshadrach marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if position_float not in ["raggedright", "raggedleft", "centering"]: | ||
raise ValueError( | ||
f"`position_float` should be one of " | ||
f"'raggedright', 'raggedleft', 'centering', " | ||
f"got: '{position_float}'" | ||
) | ||
self.set_table_styles( | ||
[{"selector": "position_float", "props": f":{position_float}"}], | ||
overwrite=False, | ||
) | ||
|
||
if hrules: | ||
self.set_table_styles( | ||
[ | ||
{"selector": "toprule", "props": ":toprule"}, | ||
{"selector": "midrule", "props": ":midrule"}, | ||
{"selector": "bottomrule", "props": ":bottomrule"}, | ||
], | ||
overwrite=False, | ||
) | ||
|
||
if label: | ||
self.set_table_styles( | ||
[{"selector": "label", "props": f":{{{label.replace(':', '§')}}}"}], | ||
overwrite=False, | ||
) | ||
|
||
if caption: | ||
self.set_caption(caption) | ||
|
||
if sparsify is not None: | ||
with pd.option_context("display.multi_sparse", sparsify): | ||
latex = self._render_latex( | ||
multirow_align=multirow_align, multicol_align=multicol_align | ||
) | ||
else: | ||
latex = self._render_latex( | ||
multirow_align=multirow_align, multicol_align=multicol_align | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could maybe use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes good idea, would prefer to do this more generically as a follow-on: #41142 (logged an issue) |
||
|
||
return save_to_buffer(latex, buf=buf, encoding=encoding) | ||
|
||
def set_td_classes(self, classes: DataFrame) -> Styler: | ||
""" | ||
Set the DataFrame of strings added to the ``class`` attribute of ``<td>`` | ||
|
Uh oh!
There was an error while loading. Please reload this page.