- 
          
- 
                Notifications
    You must be signed in to change notification settings 
- Fork 19.2k
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 133 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 ( | ||
|  | @@ -384,6 +388,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 | ||
| ) | ||
|          | ||
|  | ||
| 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.