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

Implement GT.pipe() #363

Merged
merged 6 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/_quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,13 @@ quartodoc:
- GT.show
- GT.as_raw_html
- GT.as_latex
- title: Pipeline
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about putting pipe in the helper functions section. @rich-iannone WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it!

desc: >
Sometimes, you might want to programmatically manipulate the table while still benefiting
from the chained API that **Great Tables** offers. `pipe()` is designed to tackle this
issue.
contents:
- GT.pipe
- title: Value formatting functions
desc: >
If you have single values (or lists of them) in need of formatting, we have a set of
Expand Down
100 changes: 100 additions & 0 deletions great_tables/_pipe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Callable
from typing_extensions import ParamSpec

if TYPE_CHECKING:
from .gt import GT


P = ParamSpec("P")


def pipe(self: "GT", func: Callable[P, "GT"], *args: P.args, **kwargs: P.kwargs) -> "GT":
"""
Provide a structured way to chain a function for a GT object.

This function accepts a function that receives a GT object along with optional positional and
keyword arguments, returning a GT object. This allows users to easily integrate a function
into the chained API offered by **Great Tables**.

Parameters
----------
func
A function that receives a GT object along with optional positional and keyword arguments,
returning a GT object.

*args
Optional positional arguments to be passed to the function.

**kwargs
Optional keyword arguments to be passed to the function.

Returns
-------
gt
A GT object.

Examples:
------
Let's use the `name`, `land_area_km2`, and `density_2021` columns of the `towny` dataset to
create a table. First, we'll demonstrate using two consecutive calls to the `.tab_style()`
method to highlight the maximum value of the `land_area_km2` column with `"lightgray"` and the
maximum value of the `density_2021` column with `"lightblue"`.

```{python}
import polars as pl
from great_tables import GT, loc, style
from great_tables.data import towny


towny_mini = pl.from_pandas(towny).head(10)

(
GT(
towny_mini[["name", "land_area_km2", "density_2021"]],
rowname_col="name",
)
.tab_style(
style=style.fill(color="lightgray"),
locations=loc.body(
columns="land_area_km2",
rows=pl.col("land_area_km2").eq(pl.col("land_area_km2").max()),
),
)
.tab_style(
style=style.fill(color="lightblue"),
locations=loc.body(
columns="density_2021",
rows=pl.col("density_2021").eq(pl.col("density_2021").max()),
),
)
)
```

Next, we'll demonstrate how to achieve the same result using the `.pipe()` method to
programmatically style each column.

```{python}
columns = ["land_area_km2", "density_2021"]
colors = ["lightgray", "lightblue"]


def tbl_style(gtbl: GT, columns: list[str], colors: list[str]) -> GT:
for column, color in zip(columns, colors):
gtbl = gtbl.tab_style(
style=style.fill(color=color),
locations=loc.body(columns=column, rows=pl.col(column).eq(pl.col(column).max())),
)
return gtbl


(
GT(
towny_mini[["name", "land_area_km2", "density_2021"]],
rowname_col="name",
).pipe(tbl_style, columns, colors)
)
```
"""
return func(self, *args, **kwargs)
10 changes: 4 additions & 6 deletions great_tables/gt.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,7 @@
from ._gt_data import GTData
from ._heading import tab_header
from ._helpers import random_id
from ._modify_rows import (
row_group_order,
tab_stub,
with_id,
with_locale,
)
from ._modify_rows import row_group_order, tab_stub, with_id, with_locale
from ._options import (
opt_align_table_header,
opt_all_caps,
Expand All @@ -49,6 +44,7 @@
opt_vertical_padding,
tab_options,
)
from ._pipe import pipe
from ._render import infer_render_env_defaults
from ._source_notes import tab_source_note
from ._spanners import (
Expand Down Expand Up @@ -279,6 +275,8 @@ def __init__(
as_raw_html = as_raw_html
as_latex = as_latex

pipe = pipe

# -----

def _repr_html_(self):
Expand Down
17 changes: 17 additions & 0 deletions tests/test_pipe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import polars as pl

from great_tables import GT


def test_pipe():
columns = ["x", "y"]
label = "a spanner"
df = pl.DataFrame({"x": [1, 2, 3], "y": [3, 2, 1]})

def tab_spanner2(gt, label, columns):
return gt.tab_spanner(label=label, columns=columns)

gt1 = GT(df).tab_spanner(label, columns=columns)
gt2 = GT(df).pipe(tab_spanner2, label, columns=columns)

assert len(gt1._spanners) == len(gt2._spanners)
Loading