VisiData turns each new question about a table into a keystroke on the current column
The sheet is already loaded and every transform lands on the column under the cursor, so asking a new question costs a keystroke instead of a rewrite.
In pandas, the next question is another cell
You have a CSV of sales rows and a question: what is revenue by
region. In a notebook the shape of the work is fixed before you know
the answer. You write a cell that reads the file, computes a column,
groups, and sums; you run it; you read the result. If the grouping
was wrong, or you now want product broken out too, you edit the cell
and run it again. Each new question about the same data is another
round of write, run, reread. The data sits in memory the whole time.
What you keep paying is the round trip from question to typed code to
result. VisiData (vd), Saul Pwanson's
interactive multitool for tabular data,
attacks that round
trip rather than the compute, and its current release is v3.4, from
30 June 2026.1
Three keystrokes answer revenue by region
Open the file and the sheet is already there to work on:
vd sales.csv draws the rows and hands you a cursor. The
first question, revenue per row, is a column that does not exist yet.
Press = and VisiData prompts for a Python expression;
type total = Units * Unit_Cost and a new
total column appears, with the existing column names
available as variables.23 In pandas this is
df['total'] = df.Units * df.Unit_Cost; here it is one key
and the same expression, evaluated against every row.
Summing that column by region takes two more keys, each landing on
the column the cursor is on. On total, press
+ and choose sum from the list of
aggregators, which attaches a running sum to the
column.45 Move the cursor to
Region and press F. VisiData pushes a
frequency table grouped on the current column and carrying the
aggregations of the other columns, so each region gets a row with its
count and the summed total beside
it.6
$ vd sales.csv
Date | Region | Product | Units | Unit_Cost
2026-07-01 | West | Widget | 3 | 4.00
2026-07-01 | East | Gadget | 1 | 9.00
2026-07-02 | West | Gadget | 2 | 9.00
2026-07-02 | East | Widget | 5 | 4.00
2026-07-03 | West | Widget | 4 | 4.00
2026-07-03 | East | Gadget | 2 | 9.00
= new column expression: total = Units * Unit_Cost
Region | Product | Units | Unit_Cost | total
West | Widget | 3 | 4.00 | 12
East | Gadget | 1 | 9.00 | 9
West | Gadget | 2 | 9.00 | 18
East | Widget | 5 | 4.00 | 20
West | Widget | 4 | 4.00 | 16
East | Gadget | 2 | 9.00 | 18
+ (on total) choose aggregator: sum
F (on Region) Frequency Table
Region | count | total_sum
West | 3 | 46
East | 3 | 47
= computes total = Units * Unit_Cost,
+ sets a sum on that column, and F on
Region groups the rows and carries the sum. Keybindings
confirmed in source.53
That is the whole loop: no dataframe named twice, no cell rerun,
three keys after the file opened. The keys are not memorized up
front. Each acts on wherever the cursor sits, and the full command
list is one keypress away at Ctrl+H.7
W pivots the same sheet, and M puts it back
A grid of region against product is a different question, and in a
notebook it is a different call,
df.pivot_table(...). In VisiData it is the pivot key.
A pivot groups by whatever columns you have marked as keys, and
marking a column as a key is one more keystroke. With
Region marked as the key and the cursor on
Product, press W and VisiData opens a pivot
table that groups rows by the key column and summarizes the current
column: one column per product value, the summed total
in each cell.8
The inverse question, a wide table folded back to one row per
observation, is the melt key. Press M and VisiData opens
a melted sheet: the key columns stay, and every non-key column
collapses into Variable and Value
rows, the tall shape a database load or a plotting call
wants.9 The pivot and its
inverse, the two reshapes a pandas session spells out as
pivot_table and melt, are one keystroke each
on the sheet already in front of you.
W (Region key, cursor on Product) Pivot Table
Region | Widget | Gadget
West | 28 | 18
East | 20 | 27
M (on the pivot) Melted Sheet
Region | Variable | Value
West | Widget | 28
West | Gadget | 18
East | Widget | 20
East | Gadget | 27
W spreads Product across the top
and fills each cell with the summed total; M folds the
same sheet back to one row per region-product
pair.89
| Question | In pandas | In VisiData |
|---|---|---|
| revenue per row | df['total'] = df.Units * df.Unit_Cost |
= total = Units * Unit_Cost |
| revenue by region | df.groupby('Region').total.sum() |
+ sum, then F on Region |
| region against product | df.pivot_table(index='Region', columns='Product', values='total', aggfunc='sum') |
W on Product |
| wide back to tall | df.melt(id_vars='Region') |
M |
You can move through the file before it loads, but not total it
The sheet you are working is often not fully loaded. VisiData's base
sheet loads asynchronously by default. Its reload is
decorated @asyncthread and runs in a separate thread,
and a loader is a generator that yields rows one at a time. Rows
become available as they are parsed, not after a full
read.101112 Pwanson's own
account of the design: a sheet stays dormant until it is about to be
drawn, then loads in a background thread while the interface stays
responsive. Open 100 GB of CSVs with vd *.csv and only
the top sheet loads, the rest on
demand.13 On a large
file you can scroll, jump, and open a column the instant the first
rows arrive.
What you cannot do mid-load is trust a whole-column result.
Navigation and viewing are correct the moment rows appear, but a frequency count, a sort, or an aggregate sum reflects only the rows loaded so far; those results are final only once loading finishes.10
The loader is also not unbounded. A precious sheet stops at
options.max_rows, which defaults to 1,000,000,000 rows,
so past a billion rows the sheet is truncated, not merely
slow.11
What the keystrokes cost, and where a notebook still wins
The cost is the keys themselves. VisiData is modal and vim-flavored:
early on you look most commands up, and users report the tool pays
back only after that stretch, once the frequent keys are in the
hands.14 The five keys
here (=, +, F, W,
M), plus grouped variants like gF and
gM, are a fraction of the set, and each is a character to
learn rather than a phrase to read.
The boundary is not the learning. A notebook or a database still wins wherever the work outlives the exploration. A reproducible, version-controlled, multi-step pipeline belongs in code a colleague can rerun. Custom plotting belongs in a plotting library. A join or aggregation over data too large for one machine belongs in SQL.15 VisiData's command log replays a session and can be saved and rerun, which narrows the reproducibility gap without closing it. The changelog records that the log now stores long command names rather than raw keystrokes. That helps, but it is still a session record, not a program.16 VisiData is for the exploratory pass, the part of the work where you do not yet know which questions are worth writing down.
The loop is won by one person's GPL-3.0 project
The trade is clear once the keys are learned: the exploratory loop a notebook spends in write, run, and reread, VisiData spends in move the cursor and press a key, and on that pass it wins. The same design that makes it coherent is also its risk. VisiData is conceived and developed by Saul Pwanson, with Anja Kefala maintaining the documentation and packages; it is effectively a single-primary-author project, licensed GPL-3.0, with about 9,200 GitHub stars.17 Its releases are regular: v3.4 shipped in June 2026, roughly one minor release every two to three quarters across the 3.x line since late 2023, so the project is active rather than stalled.161 The judgment for a practitioner is not whether VisiData is maintained but whether a tool that lives on one person's attention is one to route a daily workflow through. For the pass before the pipeline, where the alternative is a stack of throwaway cells, the keystrokes earn it.
Sources
- visidata.org · VisiData home (version and description)
- VisiData docs · Columns (derivative columns)
- saulpw/visidata · visidata/expr.py
- saulpw/visidata · visidata/aggregators.py
- VisiData docs · Grouping data and descriptive statistics
- saulpw/visidata · visidata/freqtbl.py
- VisiData docs · documentation index
- saulpw/visidata · visidata/pivot.py
- saulpw/visidata · visidata/features/melt.py
- VisiData docs · Loaders (asynchronous by default)
- saulpw/visidata · visidata/sheets.py
- saulpw/visidata · visidata/loaders/csv.py
- visidata.org · "Unloaded Sheets" (Saul Pwanson, 2020)
- Hacker News · discussion, item 36502435
- Hacker News · discussion, item 28826348
- saulpw/visidata · CHANGELOG.md
- saulpw/visidata · GitHub repository