steppe ships a small Python package that wraps the same GPU engines the command line drives, so you can build f2 caches and run fits from a script or a notebook instead of the shell — and get results back as objects and pandas DataFrames instead of CSV text.
import steppe gives you a thin, pandas-friendly facade over the compiled extension. Every command-line tool has a matching function (steppe.qpadm, steppe.extract_f2, steppe.f4, …), and each returns a typed result object with tidy accessors. pandas is an optional dependency: import steppe works without it, and the .weights / .to_dataframe() accessors only import it when you actually call them.
Like the CLI, this is a GPU product — there is no CPU fallback. On a machine without a CUDA 13 device, the fit calls raise a clear "no CUDA device" error.
The Python API comes from the wheel (install route 3 in the README):
pip install steppe-0.2.0-cp312-abi3-linux_x86_64.whl
python -c "import steppe; print(steppe.__version__)"Needs Python 3.12+ and the CUDA 13 runtime; numpy is the only hard dependency. Install pandas too if you want DataFrame output (pip install pandas).
Build or load an f2 cache once, then run as many fits against it as you like — the handle keeps the GPU-resident tensor alive, so repeated fits don't re-upload anything. This is the same "compute once, fit many" shape as extract-f2, just in-process.
import steppe
f2 = steppe.read_f2("f2_dir/") # load an existing STPF2BK1 f2 dir
res = steppe.qpadm(
f2,
target="England_BellBeaker",
left=["Czechia_EBA_CordedWare", "Turkey_N"],
right=["Mbuti", "Israel_Natufian", "Iran_GanjDareh_N", "Han", "Papuan", "Karitiana"],
)
print(res.weights) # pandas DataFrame: [target, left, weight, se, z]
print(res.p, res.chisq, res.f4rank)To go straight from genotypes, build the cache in-process instead of loading one:
f2 = steppe.extract_f2(
"/path/to/v66.p1_HO.aadr.patch.PUB", # reads PREFIX.{geno,snp,ind}
pops=["England_BellBeaker", "Czechia_EBA_CordedWare", "Turkey_N",
"Mbuti", "Israel_Natufian", "Iran_GanjDareh_N", "Han", "Papuan", "Karitiana"],
)
res = steppe.qpadm(f2, target="England_BellBeaker",
left=["Czechia_EBA_CordedWare", "Turkey_N"],
right=["Mbuti", "Israel_Natufian", "Iran_GanjDareh_N", "Han", "Papuan", "Karitiana"])extract_f2(..., out="f2_dir/") writes a reusable f2 dir to disk and returns the path (then read_f2("f2_dir/") reloads it); with no out=, it returns an in-memory handle you feed straight to the fit functions.
Each maps to a command-line tool; the feature pages explain the statistics themselves.
| Function | Does |
|---|---|
read_f2(dir) |
Load an existing f2 dir into a handle |
extract_f2(prefix, pops=…) |
Build an f2 cache from genotypes (extract-f2) |
qpfstats(prefix, pops=…) |
Build a smoothed f2 cache (qpfstats) |
qpadm(f2, target=, left=, right=) |
Admixture-model fit (qpadm) |
qpwave(f2, left=, right=) |
Rank-sufficiency test (qpwave) |
qpadm_search(f2, target=, pool=, right=) |
Rotation over a source pool (qpadm-rotate) |
qpgraph(f2, graph=) / qpgraph_search(f2, pops=) |
Fit / search admixture graphs (qpgraph) |
f4 / f3 / f4ratio / qpdstat / dstat |
Standalone f-statistics |
dates(prefix, target=, left=) |
Admixture dating (dates) |
export_f2_rds(dir, out) / import_f2_rds(dir, out) |
The RDS bridge to/from ADMIXTOOLS 2 |
Fit functions return typed result objects, not raw dicts:
qpadm(...)→ a result with.weights(a DataFrame[target, left, weight, se, z]), the summary scalars.p/.chisq/.f4rank/.feasible, and the.rankdrop/.popdropdiagnostic tables.qpwave(...)→.per_rank(the ascending-rank sweep) and.rankdrop.- Each result also has
.to_dataframe()for the whole thing as one frame.
Because pandas is imported lazily, the DataFrame accessors raise a clear message if pandas isn't installed — everything else works without it.
- GPU required. No CUDA 13 device → the fit calls raise a clear error; there is no CPU path.
pandasis optional.import steppeand the raw fits work without it; only the DataFrame accessors need it.extract_f2has two return modes — an in-memory handle (noout=) vs. a written f2 dir (out="dir/"returns the path). Use the handle when you'll run fits right away; write a dir when you want to reuse the cache later or from the CLI.blgsizeis in Morgans (0.05= 5 cM), same footgun as everywhere else.popsorder doesn't matter forextract_f2— the population axis is sorted internally (matchingpops.txt), so pass them in any order.