From 41198e56be8c8443cfb1a1075979df9ba770a0ab Mon Sep 17 00:00:00 2001 From: 3w36zj6 <52315048+3w36zj6@users.noreply.github.com> Date: Sun, 19 Jan 2025 10:19:02 +0000 Subject: [PATCH 1/8] ENH: Add `to_typst` method to `Styler` --- pandas/io/formats/style.py | 93 +++++++++++++++++++++++++++ pandas/io/formats/style_render.py | 16 +++++ pandas/io/formats/templates/typst.tpl | 12 ++++ 3 files changed, 121 insertions(+) create mode 100644 pandas/io/formats/templates/typst.tpl diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 6f164c4b97514..53e99c7924c75 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1228,6 +1228,99 @@ def to_latex( ) return save_to_buffer(latex, buf=buf, encoding=encoding) + @overload + def to_typst( + self, + buf: FilePath | WriteBuffer[str], + *, + encoding: str | None = ..., + sparse_index: bool | None = ..., + sparse_columns: bool | None = ..., + max_rows: int | None = ..., + max_columns: int | None = ..., + ) -> None: ... + + @overload + def to_typst( + self, + buf: None = ..., + *, + encoding: str | None = ..., + sparse_index: bool | None = ..., + sparse_columns: bool | None = ..., + max_rows: int | None = ..., + max_columns: int | None = ..., + ) -> str: ... + + @Substitution(buf=buffering_args, encoding=encoding_args) + def to_typst( + self, + buf: FilePath | WriteBuffer[str] | None = None, + *, + encoding: str | None = None, + sparse_index: bool | None = None, + sparse_columns: bool | None = None, + max_rows: int | None = None, + max_columns: int | None = None, + ) -> str | None: + """ + Write Styler to a file, buffer or string in Typst format. + + Parameters + ---------- + %(buf)s + %(encoding)s + 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). + + Returns + ------- + str or None + If `buf` is None, returns the result as a string. Otherwise returns `None`. + + See Also + -------- + DataFrame.to_typst : Write a DataFrame to a file, buffer or string in Typst format. + + Examples + -------- + >>> df = pd.DataFrame({"A": [1, 2], "B": [3, 4]}) + >>> df.style.to_typst() + '#table(\\n columns: 3,\\n [], [A], [B],\\n\\n [0], [1], [3],\\n [1], [2], [4],\\n)' + """ + 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_typst( + sparse_columns=sparse_columns, + sparse_index=sparse_index, + max_rows=max_rows, + max_cols=max_columns, + ) + return save_to_buffer( + text, buf=buf, encoding=(encoding if buf is not None else None) + ) + @overload def to_html( self, diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index c0f0608f1ab32..2d1218b007d19 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -77,6 +77,7 @@ class StylerRenderer: template_html_table = env.get_template("html_table.tpl") template_html_style = env.get_template("html_style.tpl") template_latex = env.get_template("latex.tpl") + template_typst = env.get_template("typst.tpl") template_string = env.get_template("string.tpl") def __init__( @@ -232,6 +233,21 @@ def _render_latex( d.update(kwargs) return self.template_latex.render(**d) + def _render_typst( + self, + sparse_index: bool, + sparse_columns: bool, + max_rows: int | None = None, + max_cols: int | None = None, + **kwargs, + ) -> str: + """ + Render a Styler in typst format + """ + d = self._render(sparse_index, sparse_columns, max_rows, max_cols) + d.update(kwargs) + return self.template_typst.render(**d) + def _render_string( self, sparse_index: bool, diff --git a/pandas/io/formats/templates/typst.tpl b/pandas/io/formats/templates/typst.tpl new file mode 100644 index 0000000000000..66de8f31b405e --- /dev/null +++ b/pandas/io/formats/templates/typst.tpl @@ -0,0 +1,12 @@ +#table( + columns: {{ head[0] | length }}, +{% for r in head %} + {% for c in r %}[{% if c["is_visible"] %}{{ c["display_value"] }}{% endif %}],{% if not loop.last %} {% endif%}{% endfor %} + +{% endfor %} + +{% for r in body %} + {% for c in r %}[{% if c["is_visible"] %}{{ c["display_value"] }}{% endif %}],{% if not loop.last %} {% endif%}{% endfor %} + +{% endfor %} +) From 209b2df686b3f1b1c3ef4fd5b2e8fbb9e9b47242 Mon Sep 17 00:00:00 2001 From: 3w36zj6 <52315048+3w36zj6@users.noreply.github.com> Date: Sun, 19 Jan 2025 10:22:32 +0000 Subject: [PATCH 2/8] TST: Add `Styler.to_typst()` test cases --- .../tests/io/formats/style/test_to_typst.py | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 pandas/tests/io/formats/style/test_to_typst.py diff --git a/pandas/tests/io/formats/style/test_to_typst.py b/pandas/tests/io/formats/style/test_to_typst.py new file mode 100644 index 0000000000000..2365119c9c4dc --- /dev/null +++ b/pandas/tests/io/formats/style/test_to_typst.py @@ -0,0 +1,96 @@ +from textwrap import dedent + +import pytest + +from pandas import ( + DataFrame, + Series, +) + +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": Series(["ab", "cd"], dtype=object)} + ) + + +@pytest.fixture +def styler(df): + return Styler(df, uuid_len=0, precision=2) + + +def test_basic_table(styler): + result = styler.to_typst() + expected = dedent( + """\ + #table( + columns: 4, + [], [A], [B], [C], + + [0], [0], [-0.61], [ab], + [1], [1], [-1.22], [cd], + )""" + ) + assert result == expected + + +def test_concat(styler): + result = styler.concat(styler.data.agg(["sum"]).style).to_typst() + expected = dedent( + """\ + #table( + columns: 4, + [], [A], [B], [C], + + [0], [0], [-0.61], [ab], + [1], [1], [-1.22], [cd], + [sum], [1], [-1.830000], [abcd], + )""" + ) + assert result == expected + + +def test_concat_recursion(styler): + df = styler.data + styler1 = styler + styler2 = Styler(df.agg(["sum"]), uuid_len=0, precision=3) + styler3 = Styler(df.agg(["sum"]), uuid_len=0, precision=4) + result = styler1.concat(styler2.concat(styler3)).to_typst() + expected = dedent( + """\ + #table( + columns: 4, + [], [A], [B], [C], + + [0], [0], [-0.61], [ab], + [1], [1], [-1.22], [cd], + [sum], [1], [-1.830], [abcd], + [sum], [1], [-1.8300], [abcd], + )""" + ) + assert result == expected + + +def test_concat_chain(styler): + df = styler.data + styler1 = styler + styler2 = Styler(df.agg(["sum"]), uuid_len=0, precision=3) + styler3 = Styler(df.agg(["sum"]), uuid_len=0, precision=4) + result = styler1.concat(styler2).concat(styler3).to_typst() + expected = dedent( + """\ + #table( + columns: 4, + [], [A], [B], [C], + + [0], [0], [-0.61], [ab], + [1], [1], [-1.22], [cd], + [sum], [1], [-1.830], [abcd], + [sum], [1], [-1.8300], [abcd], + )""" + ) + assert result == expected From dda68e37ed9370844bc0f4f28f0d1b52aa40f3c7 Mon Sep 17 00:00:00 2001 From: 3w36zj6 <52315048+3w36zj6@users.noreply.github.com> Date: Sun, 19 Jan 2025 15:14:11 +0000 Subject: [PATCH 3/8] STY: Apply Ruff suggestions --- pandas/io/formats/style.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 53e99c7924c75..d7de7eacc2905 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1296,13 +1296,20 @@ def to_typst( See Also -------- - DataFrame.to_typst : Write a DataFrame to a file, buffer or string in Typst format. + DataFrame.to_typst : Write a DataFrame to a file, + buffer or string in Typst format. Examples -------- >>> df = pd.DataFrame({"A": [1, 2], "B": [3, 4]}) - >>> df.style.to_typst() - '#table(\\n columns: 3,\\n [], [A], [B],\\n\\n [0], [1], [3],\\n [1], [2], [4],\\n)' + >>> df.style.to_typst() # doctest: +SKIP + #table( + columns: 3, + [], [A], [B], + + [0], [1], [3], + [1], [2], [4], + ) """ obj = self._copy(deepcopy=True) From 47ff1bf8e40491d81909838b237965581cd4bb47 Mon Sep 17 00:00:00 2001 From: 3w36zj6 <52315048+3w36zj6@users.noreply.github.com> Date: Mon, 20 Jan 2025 01:34:20 +0900 Subject: [PATCH 4/8] DOC: Update What's new --- doc/source/whatsnew/v2.3.0.rst | 5 +++-- pandas/io/formats/style.py | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v2.3.0.rst b/doc/source/whatsnew/v2.3.0.rst index 9e0e095eb4de8..e408545716d0e 100644 --- a/doc/source/whatsnew/v2.3.0.rst +++ b/doc/source/whatsnew/v2.3.0.rst @@ -21,11 +21,12 @@ Upcoming changes in pandas 3.0 Enhancements ~~~~~~~~~~~~ -.. _whatsnew_230.enhancements.enhancement1: +.. _whatsnew_230.enhancements.styler: -enhancement1 +Styler ^^^^^^^^^^^^ +- The :meth:`.Styler.to_typst` function has been added to write Styler to a file, buffer or string in Typst format (:issue:`57617`) .. _whatsnew_230.enhancements.other: diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index d7de7eacc2905..ac57c3e7ec4b9 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1266,6 +1266,8 @@ def to_typst( """ Write Styler to a file, buffer or string in Typst format. + .. versionadded:: 2.3.0 + Parameters ---------- %(buf)s From becfb360b2e5f0cafaeb4a8a2f077bb4b790aa7a Mon Sep 17 00:00:00 2001 From: 3w36zj6 <52315048+3w36zj6@users.noreply.github.com> Date: Mon, 20 Jan 2025 01:35:16 +0900 Subject: [PATCH 5/8] DOC: Update reference --- doc/source/reference/style.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/source/reference/style.rst b/doc/source/reference/style.rst index 0e1d93841d52f..742263c788c2f 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_typst Styler.template_string Styler.loader @@ -77,6 +78,7 @@ Style export and import Styler.to_html Styler.to_latex + Styler.to_typst Styler.to_excel Styler.to_string Styler.export From 373254c25e163203a994a2be1bcb46347b0c1948 Mon Sep 17 00:00:00 2001 From: 3w36zj6 <52315048+3w36zj6@users.noreply.github.com> Date: Wed, 22 Jan 2025 04:47:51 +0900 Subject: [PATCH 6/8] CI: Add `Styler.template_typst` to validation ignore list --- scripts/validate_docstrings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/validate_docstrings.py b/scripts/validate_docstrings.py index 55acfaac4d843..944575dcc8659 100755 --- a/scripts/validate_docstrings.py +++ b/scripts/validate_docstrings.py @@ -45,6 +45,7 @@ "Styler.template_html_style", "Styler.template_html_table", "Styler.template_latex", + "Styler.template_typst", "Styler.template_string", "Styler.loader", "errors.InvalidComparison", From 8f149ab3289dda4995004edb9ec5e3d271a74ef6 Mon Sep 17 00:00:00 2001 From: 3w36zj6 <52315048+3w36zj6@users.noreply.github.com> Date: Wed, 22 Jan 2025 07:16:51 +0000 Subject: [PATCH 7/8] DOC: Update docstring format for `Styler.to_typst()` example --- pandas/io/formats/style.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index ac57c3e7ec4b9..53278453b594f 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1305,13 +1305,16 @@ def to_typst( -------- >>> df = pd.DataFrame({"A": [1, 2], "B": [3, 4]}) >>> df.style.to_typst() # doctest: +SKIP - #table( - columns: 3, - [], [A], [B], - [0], [1], [3], - [1], [2], [4], - ) + .. code-block:: typst + + #table( + columns: 3, + [], [A], [B], + + [0], [1], [3], + [1], [2], [4], + ) """ obj = self._copy(deepcopy=True) From 058327c222be70479217ed5b3a6d8ab83ccd33bd Mon Sep 17 00:00:00 2001 From: 3w36zj6 <52315048+3w36zj6@users.noreply.github.com> Date: Sat, 1 Feb 2025 22:42:56 +0900 Subject: [PATCH 8/8] DOC: Update versionadded for `Styler.to_typst()` to 3.0.0 in documentation --- doc/source/whatsnew/v2.3.0.rst | 5 ++--- doc/source/whatsnew/v3.0.0.rst | 1 + pandas/io/formats/style.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/source/whatsnew/v2.3.0.rst b/doc/source/whatsnew/v2.3.0.rst index 2d50c2f460017..8bdddb5b7f85d 100644 --- a/doc/source/whatsnew/v2.3.0.rst +++ b/doc/source/whatsnew/v2.3.0.rst @@ -21,12 +21,11 @@ Upcoming changes in pandas 3.0 Enhancements ~~~~~~~~~~~~ -.. _whatsnew_230.enhancements.styler: +.. _whatsnew_230.enhancements.enhancement1: -Styler +enhancement1 ^^^^^^^^^^^^ -- The :meth:`.Styler.to_typst` function has been added to write Styler to a file, buffer or string in Typst format (:issue:`57617`) .. _whatsnew_230.enhancements.other: diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index a7f63d75a047e..64f4a66a109f5 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -31,6 +31,7 @@ Other enhancements - :class:`pandas.api.typing.FrozenList` is available for typing the outputs of :attr:`MultiIndex.names`, :attr:`MultiIndex.codes` and :attr:`MultiIndex.levels` (:issue:`58237`) - :class:`pandas.api.typing.SASReader` is available for typing the output of :func:`read_sas` (:issue:`55689`) - :meth:`pandas.api.interchange.from_dataframe` now uses the `PyCapsule Interface `_ if available, only falling back to the Dataframe Interchange Protocol if that fails (:issue:`60739`) +- Added :meth:`.Styler.to_typst` to write Styler objects to file, buffer or string in Typst format (:issue:`57617`) - :class:`pandas.api.typing.NoDefault` is available for typing ``no_default`` - :func:`DataFrame.to_excel` now raises an ``UserWarning`` when the character count in a cell exceeds Excel's limitation of 32767 characters (:issue:`56954`) - :func:`pandas.merge` now validates the ``how`` parameter input (merge type) (:issue:`59435`) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 53278453b594f..3f37556867954 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1266,7 +1266,7 @@ def to_typst( """ Write Styler to a file, buffer or string in Typst format. - .. versionadded:: 2.3.0 + .. versionadded:: 3.0.0 Parameters ----------