|
21 | 21 |
|
22 | 22 | from pandas._typing import (
|
23 | 23 | Axis,
|
| 24 | + FilePathOrBuffer, |
24 | 25 | FrameOrSeries,
|
25 | 26 | FrameOrSeriesUnion,
|
26 | 27 | IndexLabel,
|
|
30 | 31 | from pandas.util._decorators import doc
|
31 | 32 |
|
32 | 33 | import pandas as pd
|
| 34 | +from pandas import RangeIndex |
33 | 35 | from pandas.api.types import is_list_like
|
34 | 36 | from pandas.core import generic
|
35 | 37 | import pandas.core.common as com
|
|
39 | 41 | )
|
40 | 42 | from pandas.core.generic import NDFrame
|
41 | 43 |
|
| 44 | +from pandas.io.formats.format import save_to_buffer |
| 45 | + |
42 | 46 | jinja2 = import_optional_dependency("jinja2", extra="DataFrame.style requires jinja2.")
|
43 | 47 |
|
44 | 48 | from pandas.io.formats.style_render import (
|
@@ -403,6 +407,338 @@ def to_excel(
|
403 | 407 | engine=engine,
|
404 | 408 | )
|
405 | 409 |
|
| 410 | + def to_latex( |
| 411 | + self, |
| 412 | + buf: FilePathOrBuffer[str] | None = None, |
| 413 | + *, |
| 414 | + column_format: str | None = None, |
| 415 | + position: str | None = None, |
| 416 | + position_float: str | None = None, |
| 417 | + hrules: bool = False, |
| 418 | + label: str | None = None, |
| 419 | + caption: str | None = None, |
| 420 | + sparse_index: bool | None = None, |
| 421 | + sparse_columns: bool | None = None, |
| 422 | + multirow_align: str = "c", |
| 423 | + multicol_align: str = "r", |
| 424 | + siunitx: bool = False, |
| 425 | + encoding: str | None = None, |
| 426 | + ): |
| 427 | + r""" |
| 428 | + Write Styler to a file, buffer or string in LaTeX format. |
| 429 | +
|
| 430 | + .. versionadded:: 1.3.0 |
| 431 | +
|
| 432 | + Parameters |
| 433 | + ---------- |
| 434 | + buf : str, Path, or StringIO-like, optional, default None |
| 435 | + Buffer to write to. If ``None``, the output is returned as a string. |
| 436 | + column_format : str, optional |
| 437 | + The LaTeX column specification placed in location: |
| 438 | +
|
| 439 | + \\begin{tabular}{<column_format>} |
| 440 | +
|
| 441 | + Defaults to 'l' for index and |
| 442 | + non-numeric data columns, and, for numeric data columns, |
| 443 | + to 'r' by default, or 'S' if ``siunitx`` is ``True``. |
| 444 | + position : str, optional |
| 445 | + The LaTeX positional argument (e.g. 'h!') for tables, placed in location: |
| 446 | +
|
| 447 | + \\begin{table}[<position>] |
| 448 | + position_float : {"centering", "raggedleft", "raggedright"}, optional |
| 449 | + The LaTeX float command placed in location: |
| 450 | +
|
| 451 | + \\begin{table}[<position>] |
| 452 | +
|
| 453 | + \\<position_float> |
| 454 | + hrules : bool, default False |
| 455 | + Set to `True` to add \\toprule, \\midrule and \\bottomrule from the |
| 456 | + {booktabs} LaTeX package. |
| 457 | + label : str, optional |
| 458 | + The LaTeX label included as: \\label{<label>}. |
| 459 | + This is used with \\ref{<label>} in the main .tex file. |
| 460 | + caption : str, optional |
| 461 | + The LaTeX table caption included as: \\caption{<caption>}. |
| 462 | + sparse_index : bool, optional |
| 463 | + Whether to sparsify the display of a hierarchical index. Setting to False |
| 464 | + will display each explicit level element in a hierarchical key for each row. |
| 465 | + Defaults to ``pandas.options.styler.sparse.index`` value. |
| 466 | + sparse_columns : bool, optional |
| 467 | + Whether to sparsify the display of a hierarchical index. Setting to False |
| 468 | + will display each explicit level element in a hierarchical key for each row. |
| 469 | + Defaults to ``pandas.options.styler.sparse.columns`` value. |
| 470 | + multirow_align : {"c", "t", "b"} |
| 471 | + If sparsifying hierarchical MultiIndexes whether to align text centrally, |
| 472 | + at the top or bottom. |
| 473 | + multicol_align : {"r", "c", "l"} |
| 474 | + If sparsifying hierarchical MultiIndex columns whether to align text at |
| 475 | + the left, centrally, or at the right. |
| 476 | + siunitx : bool, default False |
| 477 | + Set to ``True`` to structure LaTeX compatible with the {siunitx} package. |
| 478 | + encoding : str, default "utf-8" |
| 479 | + Character encoding setting. |
| 480 | +
|
| 481 | + Returns |
| 482 | + ------- |
| 483 | + str or None |
| 484 | + If `buf` is None, returns the result as a string. Otherwise returns `None`. |
| 485 | +
|
| 486 | + See Also |
| 487 | + -------- |
| 488 | + Styler.format: Format the text display value of cells. |
| 489 | +
|
| 490 | + Notes |
| 491 | + ----- |
| 492 | + **Latex Packages** |
| 493 | +
|
| 494 | + For the following features we recommend the following LaTeX inclusions: |
| 495 | +
|
| 496 | + ===================== ========================================================== |
| 497 | + Feature Inclusion |
| 498 | + ===================== ========================================================== |
| 499 | + sparse columns none: included within default {tabular} environment |
| 500 | + sparse rows \\usepackage{multirow} |
| 501 | + hrules \\usepackage{booktabs} |
| 502 | + colors \\usepackage[table]{xcolor} |
| 503 | + siunitx \\usepackage{siunitx} |
| 504 | + bold (with siunitx) | \\usepackage{etoolbox} |
| 505 | + | \\robustify\\bfseries |
| 506 | + | \\sisetup{detect-all = true} *(within {document})* |
| 507 | + italic (with siunitx) | \\usepackage{etoolbox} |
| 508 | + | \\robustify\\itshape |
| 509 | + | \\sisetup{detect-all = true} *(within {document})* |
| 510 | + ===================== ========================================================== |
| 511 | +
|
| 512 | + **Cell Styles** |
| 513 | +
|
| 514 | + LaTeX styling can only be rendered if the accompanying styling functions have |
| 515 | + been constructed with appropriate LaTeX commands. All styling |
| 516 | + functionality is built around the concept of a CSS ``(<attribute>, <value>)`` |
| 517 | + pair (see `Table Visualization <../../user_guide/style.ipynb>`_), and this |
| 518 | + should be replaced by a LaTeX |
| 519 | + ``(<command>, <options>)`` approach. Each cell will be styled individually |
| 520 | + using nested LaTeX commands with their accompanied options. |
| 521 | +
|
| 522 | + For example the following code will highlight and bold a cell in HTML-CSS: |
| 523 | +
|
| 524 | + >>> df = pd.DataFrame([[1,2], [3,4]]) |
| 525 | + >>> s = df.style.highlight_max(axis=None, |
| 526 | + ... props='background-color:red; font-weight:bold;') |
| 527 | + >>> s.render() |
| 528 | +
|
| 529 | + The equivalent using LaTeX only commands is the following: |
| 530 | +
|
| 531 | + >>> s = df.style.highlight_max(axis=None, |
| 532 | + ... props='cellcolor:{red}; bfseries: ;') |
| 533 | + >>> s.to_latex() |
| 534 | +
|
| 535 | + Internally these structured LaTeX ``(<command>, <options>)`` pairs |
| 536 | + are translated to the |
| 537 | + ``display_value`` with the default structure: |
| 538 | + ``\<command><options> <display_value>``. |
| 539 | + Where there are multiple commands the latter is nested recursively, so that |
| 540 | + the above example highlighed cell is rendered as |
| 541 | + ``\cellcolor{red} \bfseries 4``. |
| 542 | +
|
| 543 | + Occasionally this format does not suit the applied command, or |
| 544 | + combination of LaTeX packages that is in use, so additional flags can be |
| 545 | + added to the ``<options>``, within the tuple, to result in different |
| 546 | + positions of required braces (the **default** being the same as ``--nowrap``): |
| 547 | +
|
| 548 | + =================================== ============================================ |
| 549 | + Tuple Format Output Structure |
| 550 | + =================================== ============================================ |
| 551 | + (<command>,<options>) \\<command><options> <display_value> |
| 552 | + (<command>,<options> ``--nowrap``) \\<command><options> <display_value> |
| 553 | + (<command>,<options> ``--rwrap``) \\<command><options>{<display_value>} |
| 554 | + (<command>,<options> ``--wrap``) {\\<command><options> <display_value>} |
| 555 | + (<command>,<options> ``--lwrap``) {\\<command><options>} <display_value> |
| 556 | + (<command>,<options> ``--dwrap``) {\\<command><options>}{<display_value>} |
| 557 | + =================================== ============================================ |
| 558 | +
|
| 559 | + For example the `textbf` command for font-weight |
| 560 | + should always be used with `--rwrap` so ``('textbf', '--rwrap')`` will render a |
| 561 | + working cell, wrapped with braces, as ``\textbf{<display_value>}``. |
| 562 | +
|
| 563 | + A more comprehensive example is as follows: |
| 564 | +
|
| 565 | + >>> df = pd.DataFrame([[1, 2.2, "dogs"], [3, 4.4, "cats"], [2, 6.6, "cows"]], |
| 566 | + ... index=["ix1", "ix2", "ix3"], |
| 567 | + ... columns=["Integers", "Floats", "Strings"]) |
| 568 | + >>> s = df.style.highlight_max( |
| 569 | + ... props='cellcolor:[HTML]{FFFF00}; color:{red};' |
| 570 | + ... 'textit:--rwrap; textbf:--rwrap;' |
| 571 | + ... ) |
| 572 | + >>> s.to_latex() |
| 573 | +
|
| 574 | + .. figure:: ../../_static/style/latex_1.png |
| 575 | +
|
| 576 | + **Table Styles** |
| 577 | +
|
| 578 | + Internally Styler uses its ``table_styles`` object to parse the |
| 579 | + ``column_format``, ``position``, ``position_float``, and ``label`` |
| 580 | + input arguments. These arguments are added to table styles in the format: |
| 581 | +
|
| 582 | + .. code-block:: python |
| 583 | +
|
| 584 | + set_table_styles([ |
| 585 | + {"selector": "column_format", "props": f":{column_format};"}, |
| 586 | + {"selector": "position", "props": f":{position};"}, |
| 587 | + {"selector": "position_float", "props": f":{position_float};"}, |
| 588 | + {"selector": "label", "props": f":{{{label.replace(':','§')}}};"} |
| 589 | + ], overwrite=False) |
| 590 | +
|
| 591 | + Exception is made for the ``hrules`` argument which, in fact, controls all three |
| 592 | + commands: ``toprule``, ``bottomrule`` and ``midrule`` simultaneously. Instead of |
| 593 | + setting ``hrules`` to ``True``, it is also possible to set each |
| 594 | + individual rule definition, by manually setting the ``table_styles``, |
| 595 | + for example below we set a regular ``toprule``, set an ``hline`` for |
| 596 | + ``bottomrule`` and exclude the ``midrule``: |
| 597 | +
|
| 598 | + .. code-block:: python |
| 599 | +
|
| 600 | + set_table_styles([ |
| 601 | + {'selector': 'toprule', 'props': ':toprule;'}, |
| 602 | + {'selector': 'bottomrule', 'props': ':hline;'}, |
| 603 | + ], overwrite=False) |
| 604 | +
|
| 605 | + If other ``commands`` are added to table styles they will be detected, and |
| 606 | + positioned immediately above the '\\begin{tabular}' command. For example to |
| 607 | + add odd and even row coloring, from the {colortbl} package, in format |
| 608 | + ``\rowcolors{1}{pink}{red}``, use: |
| 609 | +
|
| 610 | + .. code-block:: python |
| 611 | +
|
| 612 | + set_table_styles([ |
| 613 | + {'selector': 'rowcolors', 'props': ':{1}{pink}{red};'} |
| 614 | + ], overwrite=False) |
| 615 | +
|
| 616 | + A more comprehensive example using these arguments is as follows: |
| 617 | +
|
| 618 | + >>> df.columns = pd.MultiIndex.from_tuples([ |
| 619 | + ... ("Numeric", "Integers"), |
| 620 | + ... ("Numeric", "Floats"), |
| 621 | + ... ("Non-Numeric", "Strings") |
| 622 | + ... ]) |
| 623 | + >>> df.index = pd.MultiIndex.from_tuples([ |
| 624 | + ... ("L0", "ix1"), ("L0", "ix2"), ("L1", "ix3") |
| 625 | + ... ]) |
| 626 | + >>> s = df.style.highlight_max( |
| 627 | + ... props='cellcolor:[HTML]{FFFF00}; color:{red}; itshape:; bfseries:;' |
| 628 | + ... ) |
| 629 | + >>> s.to_latex( |
| 630 | + ... column_format="rrrrr", position="h", position_float="centering", |
| 631 | + ... hrules=True, label="table:5", caption="Styled LaTeX Table", |
| 632 | + ... multirow_align="t", multicol_align="r" |
| 633 | + ... ) |
| 634 | +
|
| 635 | + .. figure:: ../../_static/style/latex_2.png |
| 636 | +
|
| 637 | + **Formatting** |
| 638 | +
|
| 639 | + To format values :meth:`Styler.format` should be used prior to calling |
| 640 | + `Styler.to_latex`, as well as other methods such as :meth:`Styler.hide_index` |
| 641 | + or :meth:`Styler.hide_columns`, for example: |
| 642 | +
|
| 643 | + >>> s.clear() |
| 644 | + >>> s.table_styles = [] |
| 645 | + >>> s.caption = None |
| 646 | + >>> s.format({ |
| 647 | + ... ("Numeric", "Integers"): '\${}', |
| 648 | + ... ("Numeric", "Floats"): '{:.3f}', |
| 649 | + ... ("Non-Numeric", "Strings"): str.upper |
| 650 | + ... }) |
| 651 | + >>> s.to_latex() |
| 652 | + \begin{tabular}{llrrl} |
| 653 | + {} & {} & \multicolumn{2}{r}{Numeric} & {Non-Numeric} \\ |
| 654 | + {} & {} & {Integers} & {Floats} & {Strings} \\ |
| 655 | + \multirow[c]{2}{*}{L0} & ix1 & \\$1 & 2.200 & DOGS \\ |
| 656 | + & ix2 & \$3 & 4.400 & CATS \\ |
| 657 | + L1 & ix3 & \$2 & 6.600 & COWS \\ |
| 658 | + \end{tabular} |
| 659 | + """ |
| 660 | + table_selectors = ( |
| 661 | + [style["selector"] for style in self.table_styles] |
| 662 | + if self.table_styles is not None |
| 663 | + else [] |
| 664 | + ) |
| 665 | + |
| 666 | + if column_format is not None: |
| 667 | + # add more recent setting to table_styles |
| 668 | + self.set_table_styles( |
| 669 | + [{"selector": "column_format", "props": f":{column_format}"}], |
| 670 | + overwrite=False, |
| 671 | + ) |
| 672 | + elif "column_format" in table_selectors: |
| 673 | + pass # adopt what has been previously set in table_styles |
| 674 | + else: |
| 675 | + # create a default: set float, complex, int cols to 'r' ('S'), index to 'l' |
| 676 | + _original_columns = self.data.columns |
| 677 | + self.data.columns = RangeIndex(stop=len(self.data.columns)) |
| 678 | + numeric_cols = self.data._get_numeric_data().columns.to_list() |
| 679 | + self.data.columns = _original_columns |
| 680 | + column_format = "" if self.hidden_index else "l" * self.data.index.nlevels |
| 681 | + for ci, _ in enumerate(self.data.columns): |
| 682 | + if ci not in self.hidden_columns: |
| 683 | + column_format += ( |
| 684 | + ("r" if not siunitx else "S") if ci in numeric_cols else "l" |
| 685 | + ) |
| 686 | + self.set_table_styles( |
| 687 | + [{"selector": "column_format", "props": f":{column_format}"}], |
| 688 | + overwrite=False, |
| 689 | + ) |
| 690 | + |
| 691 | + if position: |
| 692 | + self.set_table_styles( |
| 693 | + [{"selector": "position", "props": f":{position}"}], |
| 694 | + overwrite=False, |
| 695 | + ) |
| 696 | + |
| 697 | + if position_float: |
| 698 | + if position_float not in ["raggedright", "raggedleft", "centering"]: |
| 699 | + raise ValueError( |
| 700 | + f"`position_float` should be one of " |
| 701 | + f"'raggedright', 'raggedleft', 'centering', " |
| 702 | + f"got: '{position_float}'" |
| 703 | + ) |
| 704 | + self.set_table_styles( |
| 705 | + [{"selector": "position_float", "props": f":{position_float}"}], |
| 706 | + overwrite=False, |
| 707 | + ) |
| 708 | + |
| 709 | + if hrules: |
| 710 | + self.set_table_styles( |
| 711 | + [ |
| 712 | + {"selector": "toprule", "props": ":toprule"}, |
| 713 | + {"selector": "midrule", "props": ":midrule"}, |
| 714 | + {"selector": "bottomrule", "props": ":bottomrule"}, |
| 715 | + ], |
| 716 | + overwrite=False, |
| 717 | + ) |
| 718 | + |
| 719 | + if label: |
| 720 | + self.set_table_styles( |
| 721 | + [{"selector": "label", "props": f":{{{label.replace(':', '§')}}}"}], |
| 722 | + overwrite=False, |
| 723 | + ) |
| 724 | + |
| 725 | + if caption: |
| 726 | + self.set_caption(caption) |
| 727 | + |
| 728 | + if sparse_index is None: |
| 729 | + sparse_index = get_option("styler.sparse.index") |
| 730 | + if sparse_columns is None: |
| 731 | + sparse_columns = get_option("styler.sparse.columns") |
| 732 | + |
| 733 | + latex = self._render_latex( |
| 734 | + sparse_index=sparse_index, |
| 735 | + sparse_columns=sparse_columns, |
| 736 | + multirow_align=multirow_align, |
| 737 | + multicol_align=multicol_align, |
| 738 | + ) |
| 739 | + |
| 740 | + return save_to_buffer(latex, buf=buf, encoding=encoding) |
| 741 | + |
406 | 742 | def set_td_classes(self, classes: DataFrame) -> Styler:
|
407 | 743 | """
|
408 | 744 | Set the DataFrame of strings added to the ``class`` attribute of ``<td>``
|
|
0 commit comments