Skip to content

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.

pip install keyten

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

q = trades.lazy().filter(kt.col("qty") > 1).select("sym")
print(q.explain())
SCAN table cols=[sym, qty] predicate=(qty > 1)

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