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

Add a lazy form of GT #398

Open
machow opened this issue Jul 11, 2024 · 0 comments
Open

Add a lazy form of GT #398

machow opened this issue Jul 11, 2024 · 0 comments

Comments

@machow
Copy link
Collaborator

machow commented Jul 11, 2024

(From pairing with @schloerke)

Currently, when applying pieces like formatting (e.g. with GT.fmt_number()), the formatting is not applied right away, but just before rendering. Delaying applying things like formatting is important because it opens the possibility of separating two activities:

  • declaring the table output you want (e.g. formatting, styling)
  • feeding in data

For example, plotnine doesn't apply anything until it goes to render the plot, so its more like a recipe:

from plotnine import *
from plotnine.data import mtcars

# specify the plot (w/o data)
p = ggplot(aes("cyl", "mpg")) + geom_point()

# feed in data for plot
mtcars >> p

# feed in slightly different data
mtcars.head() >> p

Potential solutions

  1. Change existing GT: make current methods all behave lazily (still can do some eager validation if user has passed data)
  2. Wrap existing GT: Use the data decorator pattern to create class that simply records method calls.

Benefits to tools like Shiny

shiny is a dashboarding tool.

Before laziness

from shiny.express import input, render, ui
from great_tables import GT, exibble, style, loc

def create_table(data) -> GT:
    return (
        GT(data)
        .tab_style(style.fill("yellow"), loc.body("num", lambda df: df.num > 10))
    )

@render.data_frame
def frame():
    data = exibble.head()

    # NOTE: this DataGrid(<GT object>) currently unsupported, just imagine it is
    return render.DataGrid(create_table(data), editable=True)

Note that shiny DataGrid only sees the eagerly computed GT object (incl styles). However, if an edit is made, shiny wants to recompute that GT "pattern", as if create_table() were called over the edited object. For example, if you updated a row in num to be greater than 10, it should become yellow.

After laziness

In order for shiny to be able to re-apply the changes, it would need a method like GT.reapply(some_data) that would run the lazy GT pattern on some_data.

from shiny.express import input, render, ui
from great_tables import GT, exibble, style, loc

@render.data_frame
def frame():
    data = exibble.head()

    gt = (
        GT(data)
        .tab_style(style.fill("yellow"), loc.body("num", lambda df: df.num > 10))
    )

    # gt now has .reapply method, to rerender on passed in data
    return render.DataGrid(gt, editable=True)

Example

The same code should display styles, formatting as if they were computed on the edited data. For example...

  • I use .fmt_number("num", decimals = 1)
  • A row of num starts as 1.234, but gets rendered as 1.2
  • I edit that row from 1.2 to 5.678
  • Shiny re-runs the GT pattern, and updates that row value to 5.7
    • note that currently shiny prohibits editing the same cell until you see the updated table
    • however, editing a cell could produce changes in any part of the table
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant