Filtering
Once you've selected rows (see Row Selection), press " to open a new filtered sheet containing only those rows. This two-step model — select, then filter — is non-destructive: your source data never changes.
Selection and filtering are two steps: (1) select rows with |, z|, or , — (2) open filtered sheet with ". This is safer than Excel's AutoFilter because the source sheet is never modified.
Open a Filtered Sheet (")
# 1. Select rows
| # move to column, press |, enter regex
# or
z| # select by Python expression
# or
, # select by current cell value
# 2. Open filtered sheet
"
| Key | Contents | Linked to source? |
|---|---|---|
" | Selected rows only | Yes — reflects source changes |
g" | All rows (no filter) | Yes — useful for working copy |
gz" | Selected rows, deep copied | No — fully independent |
Save the Filtered View
# After opening filtered sheet with "
Ctrl+S
# Save to file — only filtered rows are written
Filter by Regex (|)
Select rows where the current column matches a pattern.
| regex # select rows matching regex in current column
g| regex # select rows matching regex in ANY visible column
\ regex # unselect matching rows
Common patterns:
| Goal | Regex | Command |
|---|---|---|
| Contains 'error' | error | | error |
| Starts with 'SG' | ^SG | | ^SG |
| Ends with '.com' | \.com$ | | \.com$ |
| Exact match | ^active$ | | ^active$ |
| Contains digits | \d+ | | \d+ |
| Either X or Y | ^X$|^Y$ | | ^X$|^Y$ |
vd ~/github/practice-folder/visidata/01-loading/01-employees.csv
# Filter employees in Engineering
# Move to 'department' column
|
# Enter: Engineering
# → Engineering rows highlighted yellow
# Open filtered sheet
"
# → new sheet with only Engineering employees
# Save to CSV
Ctrl+S
# Enter: /tmp/engineering.csv
Filter by Python Expression (z|)
Select rows matching a Python condition. Column names are variables.
z| expr # select rows matching Python expression
z\ expr # unselect matching rows
# Filter: salary above 80000
z|
# Enter: salary > 80000
"
# Filter: engineering employees with high salary
z|
# Enter: department == "Engineering" and salary > 85000
"
# Filter: empty/null values
z|
# Enter: region is None
"
# Filter: text contains
z|
# Enter: "web" in hostname
Filter by Value Match (,)
Quick-select all rows sharing the current cell's value — no typing.
, # select all rows with same display value as current cell
z, # select by stored (not display) value
g, # select rows matching current row across ALL columns
# Move cursor to a cell containing 'sg'
,
# → all rows where current column = 'sg' are selected
"
# → filtered sheet with only sg rows
Multi-Condition Filtering
Equivalent to Excel's =FILTER(range, (cond1)*(cond2)*(cond3)):
# Excel: =FILTER(orders, (region="APAC")*(amount>1000)*(status="shipped"))
# VisiData:
z|
# Enter: region == 'APAC' and amount > 1000 and status == 'shipped'
"
Filter-Friendly (Custom — ff)
Natural-language column filter with is, is not, contains, starts with, ends with conditions and multi-value and/or/comma support.
Space ff Enter # prompt: filter friendly: <column> <op> <value> [and/or ...]
| Condition | Example |
|---|---|
is | status is pending,completed |
is not | status is not shipped |
contains | email contains gmail |
starts with | name starts with A |
ends with | email ends with .com |
vd shortcuts_template.csv
Space
# Type: ff
# Enter: status is idea
# → only rows where status column = "idea"
Uses the same _all_rows master list system as fk/fx/fclear — filter in-place, add rows, clear to restore.
Keyword Filter (Custom — fk)
This is a custom command defined in 35-filter.py under the common-config repository. Unlike the default filter methods that require selection first, fk directly prompts for keywords and filters in-place.
Space fk Enter # prompt: filter keywords (comma-separated): done,pending
Space fclear Enter # restore the previous row view
Type comma-separated keywords. Rows where any visible column contains any keyword are kept; all others are hidden. The filter is an OR across keywords and columns.
vd ~/github/practice-folder/visidata/04-cleaning/sales.csv
Space
# Type: fk
# Enter: pending,urgent
# → rows where any column contains "pending" OR "urgent"
Space
# Type: fclear
# Enter
# → previous row view restored
Expression Filter (Custom — fx)
Filter rows in-place using a Python expression. Column names are available as variables.
Space fx Enter # prompt: filter expression: amount > 500
Space fclear Enter # restore the previous row view
Only rows where the expression evaluates to True are kept. Same Python syntax as z| but hides non-matching rows directly instead of marking them.
vd ~/github/practice-folder/visidata/01-loading/01-employees.csv
Space
# Type: fx
# Enter: salary > 70000
# → only rows where salary > 70000 remain visible
Space
# Type: fclear
# Enter
# → previous row view restored
These patterns directly map to Notion's column filter operators — no special syntax needed, just Python.
Using this sample table:
| id | customer | status | priority | amount | |
|---|---|---|---|---|---|
| 1 | Alice | pending | high | 150.00 | alice@foo.com |
| 2 | Bob | shipped | low | 32.50 | bob@bar.com |
| 3 | Charlie | pending | low | 89.00 | charlie@gmail.com |
| 4 | Alice | completed | high | 200.00 | alice@foo.com |
| 5 | Diana | pending | medium | 512.00 | diana@gmail.com |
| 6 | Bob | cancelled | low | 15.00 | bob@bar.com |
| Notion operator | fx expression | Keeps rows | Explanation |
|---|---|---|---|
| is | status == "pending" | 1, 3, 5 | Exact match on column value |
| is-not | status != "shipped" | 1, 3, 4, 5, 6 | Not equal to value |
| contains | "gmail" in email | 3, 5 | Substring anywhere in column |
| not-contains | "gmail" not in email | 1, 2, 4, 6 | Substring absent from column |
| starts with | status.startswith("p") | 1, 3, 5 | Column value begins with text |
| ends with | email.endswith(".com") | 1, 2, 3, 4, 5, 6 | Column value ends with text |
Combining Conditions
Use and, or, and parentheses to build multi-column filters — all in a single fx expression. Using the same sample table:
fx expression | Keeps rows | Meaning |
|---|---|---|
status == "pending" and priority == "high" | 1 | pending AND high priority |
amount > 100 or priority == "high" | 1, 3, 4, 5 | amount > 100 OR high priority |
(status == "shipped" or status == "completed") and amount > 40 | 2 | shipped/completed AND amount > 40 |
"gmail" in email or customer == "Alice" | 1, 3, 4, 5 | gmail email OR customer is Alice |
# Quick reference — paste directly after `fx`:
amount > 500
status == 'done'
department != 'Engineering' and salary > 70000
"web" in hostname
region == 'sg' or region == 'kl'
Deduplication
# Step 1: Find duplicates on a key column
# Move to 'employee_id' column
Shift+F
# Step 2: Cast count column to int
#
# Step 3: Filter frequency table for duplicates
z|
# Enter: count > 1
# Step 4: Drill into source rows for a duplicate value
Enter
# Step 5: Select duplicate row to delete
s
# Step 6: Return to source and delete
q
gd
Top-N Filter
# Top 10 highest values (Excel Top 10 AutoFilter equivalent)
# Step 1: Cast to numeric type
# Move to 'amount' → %
# Step 2: Sort descending
]
# Step 3: Select top N rows
# Navigate to row N, then:
# Or use expression:
z|
# Enter: amount >= threshold
# Step 4: Open filtered sheet
"
Clear Selections
gu # unselect all rows
\ regex # unselect rows matching regex
z\ expr # unselect rows matching expression
Excel Filter Translation
| Excel / Google Sheets | VisiData | Description |
|---|---|---|
=FILTER(A:B, C:C="active") | | active → " | Filter by text match |
=FILTER(A:B, (C="X")*(D="Y")) | z| c == 'X' and d == 'Y' → " | Multi-condition |
=IF(A2>100, A2, "") | z| amount > 100 → " | Filter by condition |
| AutoFilter dropdown | | or , | Select by value |
| Advanced Filter (unique) | Shift+F | Unique value list |
| Remove duplicates | Shift+F → filter count>1 → gd | Deduplicate |
| Top 10 filter | ] sort → select → " | Sort descending first |
=UNIQUE(A:A) | Shift+F | Unique values |
| Slicers | | regex → " | Visual filter equivalent |
| Blank filter | z| value is None | Filter nulls |
| Number filter (> 100) | z| amount > 100 | Numeric condition |
=FILTER(A:B, ISNUMBER(SEARCH("keyword", A:A))) | Space fk → keyword | Keyword across all columns (OR) |
=FILTER(A:B, condition) | Space fx → expr | Python expression filter |
Quick Reference
| Key | Action |
|---|---|
" | Open filtered sheet (selected rows only) |
g" | Open sheet copy (all rows, linked) |
gz" | Open deep copy (selected rows, independent) |
| regex | Select rows matching regex in current column |
g| regex | Select rows matching regex in any column |
z| expr | Select rows matching Python expression |
\ regex | Unselect rows matching regex |
z\ expr | Unselect rows matching expression |
, | Select rows matching current cell value |
g, | Select rows matching current row (all columns) |
gu | Unselect all rows |
Space → ff → Enter | Filter-friendly: <col> <op> <value> (natural language) |
Space → fk → Enter | Filter rows by keyword (comma-separated, OR across columns) |
Space → fx → Enter | Filter rows by Python expression |
Space → fclear → Enter | Restore all rows (preserves added rows) |
Hands-On Practice
vd ~/github/practice-folder/visidata/01-loading/01-employees.csv
# 1. Filter by department: move to 'department' → | → Engineering → "
# 2. Save filtered: Ctrl+S → /tmp/engineering.csv
# 3. Press q → back to source
# 4. Filter by salary > 80000: z| → salary > 80000 → "
# 5. Press q → gu → clear all
# 6. Filter by value: move to 'Marketing' cell → , → "
What's Next
- Adding and Deleting Rows — add, delete, undo
- Copying and Pasting Rows — yank, paste, cut