Skip to content
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

Allow left/center as table cell alignment format for notebooks #15822

Open
lisphilar opened this issue Apr 21, 2024 · 4 comments · May be fixed by #15915
Open

Allow left/center as table cell alignment format for notebooks #15822

lisphilar opened this issue Apr 21, 2024 · 4 comments · May be fixed by #15915
Labels
enhancement New feature or an improvement of an existing feature

Comments

@lisphilar
Copy link

Description

Description

We use polars.Config.set_tbl_cell_alignment() to change the table cell alignment format for print(pl.DataFrame()).
However, this configuration does not work when calling display(pl.DataFrame()) on Jupyter Notebook and Google Colab.

It will be useful for notebook users to allow left/center as formats of display(pl.DataFrame()).

With polars version 0.20.22 on Google Colab:
https://gist.github.com/lisphilar/6036542413e2b5991751d706541a7795
image

Solution

If I understand the codes correctly, we can add this feature by updating the following lines, especially 165 line ("text-align: right;").

class NotebookFormatter(HTMLFormatter):
"""
Class for formatting output data in HTML for display in Jupyter Notebooks.
This class is intended for functionality specific to DataFrame._repr_html_().
"""
def write_style(self) -> None:
style = """\
<style>
.dataframe > thead > tr,
.dataframe > tbody > tr {
text-align: right;
white-space: pre-wrap;
}
</style>
"""
self.write(dedent(style))
def render(self) -> list[str]:
"""Return the lines needed to render a HTML table."""
with Tag(self.elements, "div"):
self.write_style()
super().render()
return self.elements

(I'd like to try my first PR, if acceptable.)

@lisphilar lisphilar added the enhancement New feature or an improvement of an existing feature label Apr 21, 2024
@lisphilar
Copy link
Author

Related codes

  • polars.Config.set_tbl_cell_alignment() and polars.Config.set_tbl_cell_numeric_alignment() changes environment variables ("POLARS_FMT_TABLE_CELL_ALIGNMENT", "POLARS_FMT_TABLE_CELL_NUMERIC_ALIGNMENT")
    @classmethod
    def set_tbl_cell_alignment(
    cls, format: Literal["LEFT", "CENTER", "RIGHT"] | None
    ) -> type[Config]:
    """
    Set table cell alignment.
    Parameters
    ----------
    format : str
    * "LEFT": left aligned
    * "CENTER": center aligned
    * "RIGHT": right aligned
    Examples
    --------
    >>> df = pl.DataFrame(
    ... {"column_abc": [1.0, 2.5, 5.0], "column_xyz": [True, False, True]}
    ... )
    >>> pl.Config.set_tbl_cell_alignment("RIGHT") # doctest: +IGNORE_RESULT
    >>> print(df)
    shape: (3, 2)
    ┌────────────┬────────────┐
    │ column_abc ┆ column_xyz │
    │ --- ┆ --- │
    │ f64 ┆ bool │
    ╞════════════╪════════════╡
    │ 1.0 ┆ true │
    │ 2.5 ┆ false │
    │ 5.0 ┆ true │
    └────────────┴────────────┘
    Raises
    ------
    ValueError: if alignment string not recognised.
    """
    if format is None:
    os.environ.pop("POLARS_FMT_TABLE_CELL_ALIGNMENT", None)
    elif format not in {"LEFT", "CENTER", "RIGHT"}:
    msg = f"invalid alignment: {format!r}"
    raise ValueError(msg)
    else:
    os.environ["POLARS_FMT_TABLE_CELL_ALIGNMENT"] = format
    return cls
    @classmethod
    def set_tbl_cell_numeric_alignment(
    cls, format: Literal["LEFT", "CENTER", "RIGHT"] | None
    ) -> type[Config]:
    """
    Set table cell alignment for numeric columns.
    Parameters
    ----------
    format : str
    * "LEFT": left aligned
    * "CENTER": center aligned
    * "RIGHT": right aligned
    Examples
    --------
    >>> from datetime import date
    >>> df = pl.DataFrame(
    ... {
    ... "abc": [11, 2, 333],
    ... "mno": [date(2023, 10, 29), None, date(2001, 7, 5)],
    ... "xyz": [True, False, None],
    ... }
    ... )
    >>> pl.Config.set_tbl_cell_numeric_alignment("RIGHT") # doctest: +IGNORE_RESULT
    >>> print(df)
    shape: (3, 3)
    ┌─────┬────────────┬───────┐
    │ abc ┆ mno ┆ xyz │
    │ --- ┆ --- ┆ --- │
    │ i64 ┆ date ┆ bool │
    ╞═════╪════════════╪═══════╡
    │ 11 ┆ 2023-10-29 ┆ true │
    │ 2 ┆ null ┆ false │
    │ 333 ┆ 2001-07-05 ┆ null │
    └─────┴────────────┴───────┘
    Raises
    ------
    KeyError: if alignment string not recognised.
    """
    if format is None:
    os.environ.pop("POLARS_FMT_TABLE_CELL_NUMERIC_ALIGNMENT", None)
    elif format not in {"LEFT", "CENTER", "RIGHT"}:
    msg = f"invalid alignment: {format!r}"
    raise ValueError(msg)
    else:
    os.environ["POLARS_FMT_TABLE_CELL_NUMERIC_ALIGNMENT"] = format
    return cls
  • display(pl.DataFrame()) calls NotebookFormatter.write_style() viaNotebookFormatter.write_render() and pl.DataFrame._repr_html_
    def _repr_html_(self, *, _from_series: bool = False) -> str:
    """
    Format output data in HTML for display in Jupyter Notebooks.
    Output rows and columns can be modified by setting the following ENVIRONMENT
    variables:
    * POLARS_FMT_MAX_COLS: set the number of columns
    * POLARS_FMT_MAX_ROWS: set the number of rows
    """
    max_cols = int(os.environ.get("POLARS_FMT_MAX_COLS", default=75))
    if max_cols < 0:
    max_cols = self.width
    max_rows = int(os.environ.get("POLARS_FMT_MAX_ROWS", default=10))
    if max_rows < 0:
    max_rows = self.height
    return "".join(
    NotebookFormatter(
    self,
    max_cols=max_cols,
    max_rows=max_rows,
    from_series=_from_series,
    ).render()
    )
  • At the current version, NotebookFormatter.write_style() does not read the environment variables.

@alexander-beedie
Copy link
Collaborator

alexander-beedie commented Apr 22, 2024

I think this is a good idea; in other areas1 of the NotebookFormatter code we do read the related table environment variables, and I think should do the same here.

貢献を歓迎します :)

Footnotes

  1. https://github.com/pola-rs/polars/blob/fe190b3aecf56a558eb0de375be0773bcef40e99/py-polars/polars/dataframe/_html.py#L87

@lisphilar
Copy link
Author

lisphilar commented Apr 22, 2024

@alexander-beedie
Thank you for your acceptance! ご快諾いただきありがとうございます!

Just a draft, but I created PR #15830 to use "POLARS_FMT_TABLE_CELL_NUMERIC_ALIGNMENT" in NotebookFormatter.write_style() method and to update polars.Config.set_tbl_cell_alignment() docstring.

I'm trying the following unchecked tasks.

To-do

  • NotebookFormatter.write_style() selects text-align reading "POLARS_FMT_TABLE_CELL_ALIGNMENT" env variable
  • NotebookFormatter.write_style() selects text-align reading "POLARS_FMT_TABLE_CELL_NUMERIC_ALIGNMENT"
  • update docs of polars.Config.set_tbl_cell_alignment()
  • update docs of polars.Config.set_tbl_cell_numeric_alignment()
  • add additional test for display (if possible, reading HTML directly?)

lisphilar added a commit to lisphilar/polars that referenced this issue Apr 26, 2024
…tebooks pola-rs#15822

* feat: set text_align with POLARS_FMT_TABLE_CELL_ALIGNMENT env variable

* docs: mention expected impact of set_tbl_cell_alignment in docstring

* fix: KeyError when un-assigned

* docs: fix W505 Doc line too long (111 > 88)

* docs: fix D205 1 blank line required between summary line and description

* fix: text_align tag is not lower case

* feat: set numeric text align with POLARS_FMT_TABLE_CELL_NUMERIC_ALIGNMENT env variable

* docs: mention expected impact of set_tbl_cell_numeric_alignment in docstring

* docs: fix D417 Missing argument description in the docstring for `get_attributes`: `col_idx`

* docs: update docstring for clarification

* refactor: access environment variables at DataFrame level

* docs: fix D213 [*] Multi-line docstring summary should start at the second line

* docs: fix W505 Doc line too long (99 > 88)

* fix: F821 Undefined name `numeric_align`

* test: add tests with _repr_html and table cell alignment settings

* fix: AttributeError

* fix: lines too long

* test: fix F541 [*] f-string without any placeholders

* fix: lines too long

* refactor: fix errors suggested by lint

* refactor: reorder variable assignment

* refactor: fix errors suggested by lint

* refactor: fix error suggested by lint

* test: remove unnecessary characters

* test: fix errors suggested by lint

* test: fix errors suggested by lint

* test: fix errors suggested by lint

* test: fix typo

* fix: ignore[arg-type] for mypy

* fix:  Return value expected  [return-value]

* refactor: get attributes at once

* fix: KeyError: '\n              text-align'

* fix:  Value of type "set[dict[str, str] | None]" is not indexable

* fix: KeyError by f-string with unescaped {}

* refactor: fix format

* fix: Value of type "Set[Dict[str, str]]" is not indexable

* refactor

* fix: used set mistakenly

* fix: Key expression in dictionary comprehension has incompatible type "int"; expected type "str"  [misc]

* refactor

* fix: Argument 3 to "Tag" has incompatible type "**Dict[str, str]"; expected "Optional[Dict[str, str]]"  [arg-type]

* fix: TypeError: Tag.__init__() got an unexpected keyword argument 'align'

* fix: Value expression in dictionary comprehension has incompatible type "Optional[Dict[str, str]]"; expected type "Dict[str, str]"  [misc]

* fix: Value of type "dict[int, dict[str, str]] | None" is not indexable  [index]

* fix: repeated code outside conditional statement

* refactor: accept code_reginement

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>

* refactor: accept code_reginement

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>

* Revert "refactor: accept code_reginement"

This reverts commit 3b4a9b5.

* Revert "refactor: accept code_reginement"

This reverts commit 4e0d734.

---------

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
@lisphilar
Copy link
Author

lisphilar commented Apr 26, 2024

@alexander-beedie
I created #15915 for review.
Could you review it when you get some time?

I struggled to use pre-commit on my PC today, but could not install ruff check with errors.
I will try installation later or use my forked repository for repeated tests.

#15915 does not change any Rust codes, but coverage-rust check failed with the following error message.
Revision of the workflow may be required with another PR.

libunwind: stepWithCompactEncoding - invalid compact unwind encoding
error: test failed, to rerun pass `-p polars-sql --test statements`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or an improvement of an existing feature
Projects
None yet
2 participants