Quickstart
Keyten ships as a Python package for CPython 3.11+, 64-bit Linux, and macOS on Apple Silicon. See installation for exact wheel platforms and source builds.
The first query
import keyten as kt
trades = kt.DataFrame([
kt.Series.string("sym", ["a", "b", "a", "b"]),
kt.Series.int("qty", [1, 2, 3, 4]),
])
result = (
trades.lazy()
.filter(kt.col("qty") > 1)
.with_columns((kt.col("qty") * 10).alias("q10"))
.group_by("sym")
.agg([
kt.col("q10").sum(),
kt.col("qty").count().alias("n"),
])
.sort("sym")
.collect()
)
print(result)
assert result.column("q10").to_list() == [30, 60]
Everything before collect() builds a plan; collect() optimizes it and runs it outside the interpreter with the GIL released — projection and predicate pushdown, parallel execution, and disk spilling all happen in the engine, not in Python.
See what will run
The filter reached the scan, and the scan reads only the columns the query needs. explain() never executes anything.
Files
import keyten as kt
df = kt.DataFrame([
kt.Series.string("sym", ["ev", "od"]),
kt.Series.float("px", [1.5, 2.5]),
])
df.write_csv("data.csv")
out = kt.scan_csv("data.csv").filter(kt.col("px") > 2.0).collect()
assert out.shape == (1, 2)
df.write_native("data.k10dir")
assert kt.scan_native("data.k10dir").collect().shape == (2, 2)
scan_csv, scan_parquet, and scan_native are all lazy: building the plan reads at most a header, and only the columns the query references are ever parsed or mapped. For data you query repeatedly, the data in and out tutorial shows the ingest-once-into-native pattern.
Where next
- Writing queries — the lazy model, aggregation, and reading plans.
- Mutating a DataFrame — eager append, upsert, update, delete, and snapshot behavior.
- Joins and time series — the two big multi-frame workflows.
- The Python API reference — every method, with its exact contract.
- Remote and distributed execution — worker setup, plan shipping, and fallback rules.
- Cells and encodings and architecture — how the engine works underneath.