get started
Hello grid in Python: your first pandas data grid
Start with a DataFrame you already have and finish with a grid you can scroll, sort, filter and edit, in the notebook or in a Dash app. Follow the steps in order and you will have one running in a few minutes.
There are two ways to work, and this page covers both. Start with the notebook widget if you live in Jupyter or VS Code. Move to the Dash track when you want the same grid inside a web app your whole team can open. The two share the same DataFrame handling, so what you learn on one carries straight over to the other.
In a notebook: prerequisites
You need Python 3.9 or newer with pip, and a notebook environment to run cells in. If
you do not already have one, the install step below brings in JupyterLab for you. If you prefer VS
Code, use its built-in notebook support with the same interpreter. The widget renders wherever
ipywidgets do, so any of those is fine. On your own machine it runs free with no key.
pandas 1.5 or newer is what the grid reads its data from. You do not have to install it first; the next step brings it in for you.
1. Install JupyterLab and the grid widget
One command installs JupyterLab and the grid widget together. It brings in everything the widget needs to render in a notebook, including anywidget, ipywidgets, pandas and numpy, alongside the shared pandas layer both Python packages use.
pip install jupyterlab lattice-grid-jupyter
2. Start a notebook
Launch JupyterLab from the same environment you installed into, then create a new notebook, for
example hello_grid.ipynb.
jupyter lab
If you work in VS Code instead, open a new .ipynb file and pick the same Python
interpreter you installed the package into. Either way, you now have an empty cell to type in.
3. Render a DataFrame
Paste this into the first cell and run it. It builds a small DataFrame and hands it to
LatticeGridWidget. Evaluating the widget on the last line of the cell is what draws it:
a notebook shows the value of a cell's final expression, and here that value is the interactive
grid.
import pandas as pd
from lattice_grid_jupyter import LatticeGridWidget
df = pd.DataFrame({"name": ["Ada", "Grace"], "score": [91, 88]})
grid = LatticeGridWidget(df) # evaluate the widget in a cell to render it
grid # anywidget renders the interactive grid in the notebook
You should see a grid with two rows, Ada and Grace, and two columns, name and score. Click a column header to sort, and open a column's filter to narrow the rows. The score column arrives as a number, not text, because the grid follows your DataFrame's dtypes.
4. Edit a cell, then read it back
Double-click a score in the grid, type a new number, and commit the edit. Now read the frame back in a new cell:
grid.df # read-only property -> the live DataFrame (reflects your edits)
grid.df is the live frame, so it already carries your edit. The value comes back cast
to the column's dtype, so a number stays a number: an integer column stays an integer, not a string
you would have to parse. There is no export step and no second copy to keep in step by hand.
5. Change it from Python too
The grid works both ways. As well as editing in the browser, you can drive it from code, and the grid repaints to match. Each call below is one you can run in a cell.
grid.apply_edit(key="0", col_id="score", value=100) # returns None; row key "0" score -> 100 (int64 preserved)
grid.append_rows([{"name": "Alan", "score": 77}]) # returns ['2'] (the new row key(s))
grid.delete_rows(["2"]) # returns the list of row keys removed
grid.set_data(pd.DataFrame({"name": ["X"], "score": [1]})) # returns None; replaces the frame and repaints
apply_edit(key, col_id, value)sets one cell by its row key and column, and returns nothing.append_rows(rows)adds rows from a list of dicts or a DataFrame, and returns the new row keys as strings.delete_rows(keys)removes rows by key, and returns the list of keys it removed.set_data(df)replaces the whole frame and repaints, and returns nothing.
Row keys are strings, starting at "0". Read the current frame at any point from
grid.df.
6. Get the selection and the filtered view back
What you do in the grid comes back to Python as DataFrames. Click a row, Ctrl-click another, then
read selected in the next cell. Type in the grid's search box and view
is the matching rows, in the order you see them, so exporting what is on screen is one line.
w = LatticeGridWidget(df)
w # click a row, Ctrl-click another
w.selected # the selected rows: a DataFrame with your original index and dtypes
w.view # the rows passing the grid's filters, in the grid's sort order
w.view.to_csv("shown.csv") # export exactly what is on screen
def on_select(change):
print(len(change["new"]), "rows selected")
w.observe(on_select, names="selected")
selected keeps your original index and dtypes, and is an empty frame when nothing is
selected. Both update once per change you make, not once per keystroke. w.state holds the
filter, sort, column, group and pivot state as a plain dict you can save and set again later.
7. Histograms, profiling and pivot
Pass grid options straight from Python. Header histograms show each column's distribution and a click on a bar filters on it. The Statistics panel profiles a column as you click its cells, and follows your filters. Mistyped options come back as Python warnings by name, so a typo never fails silently.
w = LatticeGridWidget(
df,
options={"row_height": 32, "stripe": True},
columns=[{"field": "score", "title": "Score", "width": 120}],
histograms=True, # a distribution in every column header
profile=True, # the Statistics panel
pivot="region", # pivot on a field
)
w.options = {"row_height": 44} # updates the live grid
8. Ten million rows
From 100,000 rows up, the widget no longer sends the frame to the browser. The grid asks Python for each window of rows as you scroll, and sorting, filtering, search, group subtotals, filter-menu values, histograms and profiles are computed over the whole frame. Parquet files and Polars frames open the same way.
pip install "lattice-grid-jupyter[large]"
w = LatticeGridWidget(big_df) # 10M rows: scrolls in windows automatically
w = LatticeGridWidget("events.parquet") # a Parquet file, read by DuckDB
w = LatticeGridWidget(polars_df) # a Polars frame
v = w.view # the rows passing the grid's filters, as a lazy handle
len(v) # how many rows that is
v.head(20) # the first 20, in the grid's sort order, as a DataFrame
v.slice(1000, 1100) # any range of the view
9. Charts in the notebook
LatticeChart draws a chart of the grid's own data. It follows the grid's filters, search and
selection live in the browser, with no round trip to Python. You can also chart a DataFrame directly.
from ipywidgets import HBox
from lattice_grid_jupyter import LatticeGridWidget, LatticeChart
grid = LatticeGridWidget(df)
roc = LatticeChart(grid, type="roc", label="churned", score="score")
HBox([grid, roc]) # filter the grid: the chart redraws
10. The data router in the notebook
Declare several DataFrames as sources and let the grid join them: look up a label, collect child rows,
roll totals up onto the parent row, spread attribute rows into columns. Updating a source refreshes
only the rows it touches, and view hands the shaped rows back as a DataFrame, so a feature
table goes straight into a model.
from lattice_grid_jupyter import LatticeGridWidget, LatticeRouter
router = LatticeRouter(
sources={"customer": {"data": customers, "key": "customer_id",
"join": [{"from": "txn", "many": True, "foreign_key": "customer_id",
"aggregate": {"spend": {"fn": "sum", "field": "amount"}}}]},
"txn": {"data": txns, "key": "txn_id"}},
routes={"customer": "customer"},
)
w = LatticeGridWidget(router, route="customer") # one row per customer, with spend rolled up
router.update("txn", new_txns) # only the affected customers refresh
w.view # the shaped rows as a DataFrame
What next
That is a working notebook grid. When you want the same grid in a web app, follow the Dash track below. For every constructor argument and method, with its signature and return, see the Python reference.
In a Dash app: prerequisites
You need Python 3.9 or newer with pip. That is all: the Dash track below runs a small
app on your own machine, where the grid renders free with no key.
1. Install the component
One command again. It brings in Dash and the shared pandas layer, so you have both the app framework and the grid after this step.
pip install lattice-grid-dash
2. Write app.py
Create a file called app.py and paste this in. It puts the grid over a DataFrame, and
wires a callback so that every committed edit updates the server-side frame and flows back to the
grid, cast to the column's dtype.
import pandas as pd
from dash import Dash, callback, Input, Output
import lattice_grid_dash
from lattice_grid_dash import dataframe_to_data, apply_cell_edit
df = pd.DataFrame({"name": ["Ada", "Grace"], "score": [91, 88]})
app = Dash(__name__)
app.layout = lattice_grid_dash.LatticeGrid(
id="grid",
data=dataframe_to_data(df),
options={"edit": True},
)
@callback(Output("grid", "data"), Input("grid", "cellChanged"), prevent_initial_call=True)
def on_edit(edit):
global df
df = apply_cell_edit(df, edit) # dtype preserved
return dataframe_to_data(df)
if __name__ == "__main__":
app.run(debug=True)
The grid's front-end code ships inside the package, so this works offline with no build step and no request to a CDN at render time.
3. Run it and edit a cell
Start the app from the same environment you installed into:
python app.py
Open the address Dash prints, http://127.0.0.1:8050/, in your browser. Double-click a
score, type a new value and commit it. That edit arrives at the cellChanged prop,
which runs your callback; apply_cell_edit applies it to the DataFrame with the column's
dtype preserved, and dataframe_to_data sends the updated frame back to the grid. The
DataFrame on the server and the grid in the browser stay in step, typed, with one round-trip.
Troubleshooting
-
An install or import fails. Check your Python is 3.9 or newer with
python --version, and that you installed into the same environment you are running. -
The notebook cell runs but no grid appears, or
jupyteris not found. The widget needs JupyterLab and anywidget, whichpip install jupyterlab lattice-grid-jupyterpulls in. Run that command, make sure the notebook kernel is the environment you installed into, then re-run the cell so the widget is the last expression in it. - The Dash app will not start on its port. If something already holds port 8050, stop it, or run the app on another port.
See also the Python data grid overview and the full Python reference.