All docs
Examples
Example: clean a CSV
Load a CSV through a Source, drop bad rows, rename and sort columns, print a null report, then write a clean CSV — a full three-step pipeline.
TL;DRA complete tool.py with three nodes — a load step that reads a file the user picks, a shape step that renames/filters/sorts, and a deliver step that logs a null report and writes a clean CSV. Wire it and run it from Python with an Engine, or drop it in a project folder and use the desktop.
The everyday job: read a messy CSV, tidy it, and write a clean one. This is a full package — a load, a shape, and a deliver step — that you can run headless or drop into a project folder.
The code#
Python
# tool.py
from lsdtools import Tool, Source, Table
tool = Tool("csvclean", label="CSV Clean")
@tool.load(source_kinds=("file",))
def load_people(src: Source) -> Table:
"""Read whatever CSV/Parquet file the user picks."""
return src.read()
@tool.shape
def clean(t: Table, min_age: int = 0) -> Table:
"""Rename messy headers, drop rows under `min_age`, then sort by name."""
t = t.rename({"full_name": "name", "yrs": "age"})
t = t.filter(t["age"] >= min_age)
return t.sort("name")
@tool.deliver
def write_clean(t: Table, path: str = "clean.csv") -> None:
"""Log a per-column null report, then write the cleaned table to CSV."""
t.null_report().print()
t.write_csv(path)
What each line does#
@tool.load(source_kinds=("file",))— a node that produces data.src: Sourceis an injected reader bound to the file the user picks;src.read()returns aTable. Narrowingsource_kindsto"file"keeps the picker to files.@tool.shape— a transform.t: Tableis an input port (wire it from the load node);min_age: int = 0has a default, so it is an optional parameter and renders as a number field.t.rename({"full_name": "name", ...})— rename columns with an{old: new}map; unlisted columns pass through.t.filter(t["age"] >= min_age)—t["age"] >= min_ageis a booleanColumn;filterkeeps the rows where it isTrue.@tool.deliver— a sink.null_report()returns a smallcolumn | nulls | pcttable;.print()logs it and returns the table.write_csv(path)writes the file.
Run it in Python#
No desktop needed — build a sample file, wire the three steps, and run with an
Engine:
Python
from lsdtools import Engine, Source, Table
# make a sample CSV to read
Table({
"full_name": ["Zoe", "Ada", "Kai"],
"yrs": [17, 34, 22],
}).write_csv("people.csv")
src = load_people(Source.file("people.csv"))
Engine().run(write_clean(clean(src, min_age=18), path="clean.csv"))
# → null report prints; clean.csv now holds only Ada and Kai (Zoe is under 18), sorted by name
Table.read_csv("clean.csv").print()
# → name age
# Ada 34
# Kai 22
clean(src, min_age=18) wires the shape's port to the load node src; write_clean(...) wires the
deliver to the shape. Engine().run(...) executes and caches the chain.
Try changing it#
- Add a second deliver that keeps a Parquet copy:
t.write_parquet("clean.parquet"). - Drop rows with any null instead of a fixed age — filter on
t["age"].is_valid(). - Add a control view — a
Viewwith a slider formin_ageand a Run button.
Related#
Learn — Load steps · Shape steps · Deliver steps
Examples — File loader · Live node metrics
By LSD Team · Last updated Jul 18, 2026
Ask on Discord
View as Markdown