Sandbox — LSD is in active testing. All systems operational·payments are test-mode (no real charges).
Overview Use Cases News Docs Tool Store Support Community Get LSD
All docs
Docs/ Examples/ Example: clean a CSV
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: Source is an injected reader bound to the file the user picks; src.read() returns a Table. Narrowing source_kinds to "file" keeps the picker to files.
  • @tool.shape — a transform. t: Table is an input port (wire it from the load node); min_age: int = 0 has 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_age is a boolean Column; filter keeps the rows where it is True.
  • @tool.deliver — a sink. null_report() returns a small column | nulls | pct table; .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 View with a slider for min_age and a Run button.

LearnLoad steps · Shape steps · Deliver steps

APITable · Source · Tool

ExamplesFile loader · Live node metrics

By LSD Team · Last updated Jul 18, 2026 Ask on Discord View as Markdown