Lattice Grid Buy a licence

task guide

Edit a pandas DataFrame in the browser

A DataFrame becomes an editable grid, a person changes a cell, and the value lands back in the frame cast to the column's own dtype. The one type that needs a stated rule is a timestamp, so that rule is stated here, with a script you can run yourself.

The script, and what it printed

This is the whole thing: build a frame with a text column, a number column and a datetime column, hand it to LatticeGridWidget, then apply two edits the same way a click in the grid would. offline=True carries the grid's own JS and CSS inside the package, so nothing is fetched from a CDN and no licence key is needed to run this on your own machine.

import pandas as pd
from lattice_grid_jupyter import LatticeGridWidget

df = pd.DataFrame({
    "ticker": ["AAPL", "MSFT", "TSLA"],
    "shares": [120, 45, 30],
    "last_trade": pd.to_datetime([
        "2026-09-23 09:31:00",
        "2026-09-23 09:32:15",
        "2026-09-23 09:33:40",
    ]),  # naive: this wrapper treats naive as UTC, both ways
})

grid = LatticeGridWidget(df, offline=True)   # offline=True: no licence needed on your own machine

# a person editing the grid in the browser sends the same message this line
# sends by hand -- apply_edit replays the exact payload an edit emits:
grid.apply_edit(key="1", col_id="shares", value=50)
grid.apply_edit(key="2", col_id="last_trade", value="2026-09-23T09:40:00Z")

print(grid.df)
print(grid.df.dtypes)

Run that cell and the two print calls give this, unedited:

  ticker  shares          last_trade
0   AAPL     120 2026-09-23 09:31:00
1   MSFT      50 2026-09-23 09:32:15
2   TSLA      30 2026-09-23 09:40:00
ticker                   str
shares                 int64
last_trade    datetime64[us]
dtype: object

Row "1"'s shares is 50, the number the edit sent, still an int64. Row "2"'s last_trade moved to the timestamp the edit named, still a datetime64. Nothing was exported, parsed back, or given a second schema to agree with; grid.df is the same frame the widget was built from, mutated in place.

The datetime contract

A timestamp is the one type where "just send the value" is ambiguous, because a naive Python datetime carries no timezone to be unambiguous with. This wrapper picks one rule and holds it in both directions: a naive datetime64 column is read as UTC when it goes to the grid, and an edit coming back is written as UTC into that same naive column, never coerced to the machine's local zone. A timezone-aware column keeps its own zone: it travels as UTC on the wire and lands back converted to the zone it already had, so a Europe/London column reads in Europe/London after the edit, the same as before it.

In the script above, editing last_trade with the ISO string "2026-09-23T09:40:00Z" and reading back 2026-09-23 09:40:00 with no offset printed is that rule working: the column is naive, so the instant is stored and shown without one, and it is the same instant a browser clock and this frame agree on.

Every other dtype

A boolean column reads and writes as True / False. An integer column casts an edit with int(), so a decimal typed into a whole-number column is truncated the way int() truncates it, not rounded. A float column casts with float(). A named index, including a multi-level one, arrives as read-only leading columns, so editing an index value is refused rather than silently accepted. Casting is best-effort: a value that cannot be cast is written back exactly as sent rather than raising out of an edit a user is in the middle of making.

What next

For the notebook version of this with append_rows and delete_rows as well, see a live grid in a notebook cell. For the same round trip driven by a Dash callback instead of a print statement, see a grid in a Dash app. Every constructor argument and method, with its signature and return value, is in the Python reference.