diff --git a/doc/source/reference/style.rst b/doc/source/reference/style.rst index a739993e4d376..dd7e2fe7434cd 100644 --- a/doc/source/reference/style.rst +++ b/doc/source/reference/style.rst @@ -27,6 +27,7 @@ Styler properties Styler.template_html_style Styler.template_html_table Styler.template_latex + Styler.template_string Styler.loader Style application @@ -74,5 +75,6 @@ Style export and import Styler.to_html Styler.to_latex Styler.to_excel + Styler.to_string Styler.export Styler.use diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 7f2a1a9305039..43aaf1068b6c9 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -14,10 +14,12 @@ including other versions of pandas. Enhancements ~~~~~~~~~~~~ -.. _whatsnew_150.enhancements.enhancement1: +.. _whatsnew_150.enhancements.styler: -enhancement1 -^^^^^^^^^^^^ +Styler +^^^^^^ + + - New method :meth:`.Styler.to_string` for alternative customisable output methods (:issue:`44502`) .. _whatsnew_150.enhancements.enhancement2: diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 29c1e35dbb546..266c5af47eead 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1072,6 +1072,73 @@ def to_html( html, buf=buf, encoding=(encoding if buf is not None else None) ) + def to_string( + self, + buf=None, + *, + encoding=None, + sparse_index: bool | None = None, + sparse_columns: bool | None = None, + max_rows: int | None = None, + max_columns: int | None = None, + delimiter: str = " ", + ): + """ + Write Styler to a file, buffer or string in text format. + + .. versionadded:: 1.5.0 + + Parameters + ---------- + buf : str, Path, or StringIO-like, optional, default None + Buffer to write to. If ``None``, the output is returned as a string. + encoding : str, optional + Character encoding setting for file output. + Defaults to ``pandas.options.styler.render.encoding`` value of "utf-8". + sparse_index : bool, optional + Whether to sparsify the display of a hierarchical index. Setting to False + will display each explicit level element in a hierarchical key for each row. + Defaults to ``pandas.options.styler.sparse.index`` value. + sparse_columns : bool, optional + Whether to sparsify the display of a hierarchical index. Setting to False + will display each explicit level element in a hierarchical key for each + column. Defaults to ``pandas.options.styler.sparse.columns`` value. + max_rows : int, optional + The maximum number of rows that will be rendered. Defaults to + ``pandas.options.styler.render.max_rows``, which is None. + max_columns : int, optional + The maximum number of columns that will be rendered. Defaults to + ``pandas.options.styler.render.max_columns``, which is None. + + Rows and columns may be reduced if the number of total elements is + large. This value is set to ``pandas.options.styler.render.max_elements``, + which is 262144 (18 bit browser rendering). + delimiter : str, default single space + The separator between data elements. + + Returns + ------- + str or None + If `buf` is None, returns the result as a string. Otherwise returns `None`. + """ + obj = self._copy(deepcopy=True) + + if sparse_index is None: + sparse_index = get_option("styler.sparse.index") + if sparse_columns is None: + sparse_columns = get_option("styler.sparse.columns") + + text = obj._render_string( + sparse_columns=sparse_columns, + sparse_index=sparse_index, + max_rows=max_rows, + max_cols=max_columns, + delimiter=delimiter, + ) + return save_to_buffer( + text, buf=buf, encoding=(encoding if buf is not None else None) + ) + def set_td_classes(self, classes: DataFrame) -> Styler: """ Set the DataFrame of strings added to the ``class`` attribute of ```` diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 2ff0a994ebb01..1fe36a34903ab 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -74,6 +74,7 @@ class StylerRenderer: template_html_table = _gl01_adjust(env.get_template("html_table.tpl")) template_html_style = _gl01_adjust(env.get_template("html_style.tpl")) template_latex = _gl01_adjust(env.get_template("latex.tpl")) + template_string = _gl01_adjust(env.get_template("string.tpl")) def __init__( self, @@ -183,6 +184,24 @@ def _render_latex( d.update(kwargs) return self.template_latex.render(**d) + def _render_string( + self, + sparse_index: bool, + sparse_columns: bool, + max_rows: int | None = None, + max_cols: int | None = None, + **kwargs, + ) -> str: + """ + Render a Styler in string format + """ + self._compute() + + d = self._translate(sparse_index, sparse_columns, max_rows, max_cols, blank="") + + d.update(kwargs) + return self.template_string.render(**d) + def _compute(self): """ Execute the style functions built up in `self._todo`. diff --git a/pandas/io/formats/templates/string.tpl b/pandas/io/formats/templates/string.tpl new file mode 100644 index 0000000000000..06aeb2b4e413c --- /dev/null +++ b/pandas/io/formats/templates/string.tpl @@ -0,0 +1,12 @@ +{% for r in head %} +{% for c in r %}{% if c["is_visible"] %} +{{ c["display_value"] }}{% if not loop.last %}{{ delimiter }}{% endif %} +{% endif %}{% endfor %} + +{% endfor %} +{% for r in body %} +{% for c in r %}{% if c["is_visible"] %} +{{ c["display_value"] }}{% if not loop.last %}{{ delimiter }}{% endif %} +{% endif %}{% endfor %} + +{% endfor %} diff --git a/pandas/tests/io/formats/style/test_to_string.py b/pandas/tests/io/formats/style/test_to_string.py new file mode 100644 index 0000000000000..5b3e0079bd95c --- /dev/null +++ b/pandas/tests/io/formats/style/test_to_string.py @@ -0,0 +1,42 @@ +from textwrap import dedent + +import pytest + +from pandas import DataFrame + +pytest.importorskip("jinja2") +from pandas.io.formats.style import Styler + + +@pytest.fixture +def df(): + return DataFrame({"A": [0, 1], "B": [-0.61, -1.22], "C": ["ab", "cd"]}) + + +@pytest.fixture +def styler(df): + return Styler(df, uuid_len=0, precision=2) + + +def test_basic_string(styler): + result = styler.to_string() + expected = dedent( + """\ + A B C + 0 0 -0.61 ab + 1 1 -1.22 cd + """ + ) + assert result == expected + + +def test_string_delimiter(styler): + result = styler.to_string(delimiter=";") + expected = dedent( + """\ + ;A;B;C + 0;0;-0.61;ab + 1;1;-1.22;cd + """ + ) + assert result == expected