Skip to main content

polars-gw

Native Polars computation engine for Graphic Walker.

Translates Graphic Walker IDataQueryPayload workflow steps directly into Polars operations — no DuckDB, no SQL intermediate.

Installation

pip install polars-gw              # core translator only
pip install 'polars-gw[viz]'       # core + built-in walk() UI

The core install only pulls in polars. The [viz] extra adds FastAPI, uvicorn and pydantic so you can launch a local Graphic Walker against a DataFrame in one line.

Interactive UI

import polars as pl
from polars_gw import walk

df = pl.read_parquet("sales.parquet")
handle = walk(df)          # opens http://127.0.0.1:<free-port> in your browser
# ...
handle.stop()

walk() starts a FastAPI server in a background daemon thread, serves the Graphic Walker UI bundled inside the wheel (no CDN, no network required), and wires its computation callback to execute_workflow.

Export / Import chart specs

Charts you build in the UI can be saved and restored across sessions:

# Export the current charts to a JSON file
handle.export("my_charts.json")

# Or just grab the spec as a Python list
spec = handle.export()

Next time, pass the file back to walk() to restore exactly where you left off:

handle = walk(df, spec_file="my_charts.json")

The spec file is a plain JSON array of Graphic Walker IChart objects — safe to version-control or share with collaborators.

Logs

By default walk() prints one line per compute call so you can see what GW is asking the backend for:

13:42:11  INFO    polars_gw.viz: Graphic Walker running on http://127.0.0.1:54221 — 12 000 000 rows x 19 cols, max_rows=1000000
13:42:18  INFO    polars_gw.viz: compute: 2 step(s) → 847 row(s) in 142.3 ms
13:42:24  INFO    polars_gw.viz: compute: 1 step(s) → 1000000 row(s) in 1284.7 ms [CAPPED]

[CAPPED] means the result hit max_rows and was truncated — Graphic Walker shows its "Data Limit Reached" toast in the UI when this happens. Default cap is 1 000 000 rows; tune it with max_rows= or disable with max_rows=None.

Quiet things down with log_level="warning", or get more detail with log_level="debug":

walk(df, log_level="warning")   # only warnings and errors
walk(df, max_rows=None)         # no row cap (use with care on big data)

If you've already configured Python logging yourself (logging.basicConfig, a framework, pytest's caplog, etc.), walk() won't overwrite it — it only attaches a console handler when nothing else owns logging.

Usage

import polars as pl
from polars_gw import execute_workflow, get_fields

df = pl.read_csv("data.csv")

# Get field definitions for Graphic Walker
fields = get_fields(df)
# [{"fid": "city", "name": "city", "semanticType": "nominal", "analyticType": "dimension"}, ...]

# Execute a Graphic Walker computation payload
payload = {
    "workflow": [
        {"type": "filter", "filters": [
            {"fid": "age", "rule": {"type": "range", "value": [18, 65]}}
        ]},
        {"type": "view", "query": [
            {"op": "aggregate", "groupBy": ["city"], "measures": [
                {"field": "salary", "agg": "mean", "asFieldKey": "avg_salary"}
            ]}
        ]},
        {"type": "sort", "sort": "descending", "by": ["avg_salary"]}
    ],
    "limit": 100
}
results = execute_workflow(df, payload)
# Returns list[dict] — ready to return as IRow[] to Graphic Walker

Supported Operations

Workflow Steps

Step Type Description
filter Range, temporal range, one of, not in, regexp
view/aggregate Group by + aggregation (sum, count, mean, median, min, max, variance, stdev, distinctCount)
view/fold Unpivot (wide to long)
view/bin Numeric binning
view/raw Column selection
sort Ascending/descending sort
transform Computed fields (bin, log/log2/log10, binCount, dateTimeDrill, dateTimeFeature, one, expr)

Field Inference

get_fields() maps Polars dtypes to Graphic Walker field types:

Polars Type Semantic Type Analytic Type
Float*, Decimal quantitative measure
Int*, UInt* quantitative dimension if ≤16 distinct, else measure — see classify_integers
Date, Datetime, Time, Duration temporal dimension
Utf8, Categorical, Boolean, etc. nominal dimension

Integer classification (classify_integers)

An integer column can be a dimension (a discrete key you group by) or a measure (a quantity you aggregate) depending on how many distinct values it holds. That count is cheap on a DataFrame but on a LazyFrame means running the plan — so the cost is yours to choose:

classify_integers Data access Rule
"sample" (default) first 1000 rows approximate distinct count; ≤16 → dimension, else measure
"scan" one full pass approximate distinct count over the whole frame; correct even on sorted/clustered columns where the head sample lies
"measure" none every integer is a measure — the escape hatch for lazy pipelines too expensive to touch
fields = get_fields(lf, classify_integers="scan")     # accurate, one pass
fields = get_fields(lf, classify_integers="measure")  # zero data access

Floats and Decimal are always measures; semanticType stays quantitative for every integer regardless of role.

Field overrides

Force any per-column key with field_overrides (typed as IMutFieldOverride):

fields = get_fields(df, field_overrides={
    "user_id": {"analyticType": "dimension"},   # an ID, not a quantity
    "rating":  {"semanticType": "ordinal"},
    "revenue": {"aggName": "mean"},
})

An override that pins analyticType also skips the distinct-count pass for that column, so overriding is free.

How It Differs from PyGWalker

Field-inference parity

The default classify_integers="sample" mirrors PyGWalker's field inference: it classifies from the first 1000 rows (df[:1000]) and treats an integer with ≤16 distinct values as a dimension, otherwise a measure. The distinct count uses Polars' approx_n_unique rather than an exact count, so classification matches PyGWalker in practice but is not guaranteed bit-for-bit at the ≤16 boundary. Deliberate differences: "scan" and "measure" are polars-gw additions PyGWalker has no equivalent for, and polars-gw does not force geo-named columns (lat/long/latitude/longitude) to dimensions the way PyGWalker does.

Compute path

PyGWalker (and panel-graphic-walker) always route through DuckDB — even for Polars DataFrames:

DataFrame → DuckDB → SQL → execute → dicts

polars-gw translates directly to Polars operations:

DataFrame → Polars expressions → execute → dicts

No DuckDB dependency. No SQL intermediate. Just Polars.

polars-gw vs PyGWalker — which should you use?

Different tools, different jobs. polars-gw is a focused compute engine

  • a standalone browser UI. PyGWalker is a broader product with richer UX surface area.

Use polars-gw when

  • You're already in the Polars ecosystem (e.g. LazyFrames, streaming, or tools like Flowfile) and don't want a DuckDB round-trip in the middle.
  • You need type fidelity for Categorical, Decimal, Duration, List, Struct — they pass through as-is instead of degrading through SQL.
  • You want a lean install (~40 MB core, no DuckDB).
  • You're building your own frontend and just need the translator (execute_workflow + get_fields).
  • You want debuggable query plans (Polars expressions, not generated SQL).
  • You need a standalone browser UI (walk(df)) without notebook dependencies.

Use PyGWalker when

  • You want the Jupyter inline widget — PyGWalker renders the UI inside a notebook cell via anywidget. polars_gw.walk() pops a browser tab.
  • You need first-party framework integrations (Streamlit, Gradio, Dash). PyGWalker has these; polars-gw does not.
  • You rely on chart persistence — saving/loading chart specs, exporting HTML/PNG, vis_spec round-tripping.
  • You want heterogeneous input support (pandas + Polars + parquet + SQL tables under one DuckDB layer).
  • Your data is too large to collect into memory and you need DuckDB's battle-tested out-of-core execution.
  • You're using Kanaries cloud features (sharing, cloud chat, etc.).

At a glance

Concern polars-gw PyGWalker
Backend Polars expressions DuckDB SQL
Install weight Lean (polars only) DuckDB + heavier deps
Jupyter inline widget ❌ (browser tab) ✅
Streamlit / Gradio / Dash ❌ ✅
Standalone browser UI ✅ (walk()) ✅
Chart save/load/export ❌ (defers to GW client) ✅
LazyFrame native ✅ via DuckDB
Polars Categorical/Decimal/Duration fidelity ✅ lossy through SQL
New GW payload ops Requires translator update Often works via SQL
Heterogeneous inputs (pandas/parquet/SQL) Polars-only ✅

Short version: if you're all-in on Polars and want the fast, native path, use polars-gw. If you want inline-notebook, Streamlit, or chart persistence out of the box, use PyGWalker.

Development

Python

uv sync --extra viz           # runtime + viz + dev deps
uv run pytest                 # 60+ tests
uv run ruff check .

Bundling the viz assets

The walk() UI ships a pre-built JS/CSS bundle under polars_gw/viz_assets/ (committed to the repo) so end users don't need Node to pip install polars-gw[viz].

Maintainers rebuild when bumping Graphic Walker:

cd js
npm install
npm run build                 # one-shot production build

Or iterate with watch mode (rebuilds JS + CSS on save, source maps on):

npm run dev                   # in one shell
uv run python example/walk_demo.py   # in another — refresh browser to pick up changes

Bundle layout:

  • graphic-walker.js — Graphic Walker + React 19.2.0, minified IIFE (~4.4 MB)
  • graphic-walker.css — Tailwind-compiled stylesheet (~57 KB)
  • versions.json — pinned npm versions + build mode + timestamp

See js/README.md for details (including why React is pinned to exactly 19.2.0).

License

MIT

Metadata

Release files for polars-gw 0.1.6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for polars-gw 0.1.6
File Size Uploaded
polars_gw-0.1.6.tar.gz 201.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for polars-gw 0.1.6
File Interpreter ABI Platform
polars_gw-0.1.6-py3-none-any.whl Python 3 none any Details

Total release size: 227.5 kB

Release files / polars_gw-0.1.6.tar.gz

Download URL polars_gw-0.1.6.tar.gz
Size 201.7 kB
Tags Source
SHA-256 checksum
How to use checksums
4c9b791f6d99d6e948d42361fb279e055705d99ffaa49b8567a06f765414ed0d
BLAKE2b-256 checksum
How to use checksums
3cd31981b63dadd5890f5d499c01b1d291d49f92674f4b782c17bc257df02fab
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 2, 2026.

Transparency log

Release files / polars_gw-0.1.6-py3-none-any.whl

Download URL polars_gw-0.1.6-py3-none-any.whl
Size 25.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f4df8db14a0c3e6e644df7ca192e282259361089c6134fa8fd5ba981426e50d5
BLAKE2b-256 checksum
How to use checksums
33d9af3f4853d7d8948484b1dde38a8bb33b107c84a2b91b67065a7ce0f5b2b2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 2, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.6 This release

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page