<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Millwright · Python</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="site.css">
</head><body><header class="top">
<div class="wrap">
<div class="brand"><a href="../index.html"><span class="mark">⚙</span>millwright</a><span class="ver">docs</span></div>
<nav>
<a href="index.html">home</a>
<a href="data.html">data & EDA</a>
<a href="pipelines.html">pipelines</a>
<a href="insight.html">insight</a>
<a href="deploy.html">deploy</a>
<a href="python.html" class="active">python</a>
<a href="../index.html">design brief</a>
<a class="repo" href="https://github.com/mi7plus/millwright">GitHub ↗</a>
</nav>
</div>
</header>
<main>
<div class="wrap">
<div class="hero">
<div class="eyebrow">05 · python</div>
<h1>The same engine,<br>a Pythonic API.</h1>
<p class="lede"><code class="inl">pip install millwright</code> — a Pythonic pipeline over the same Rust engine, shipped on <a href="https://pypi.org/project/millwright/">PyPI</a> as an abi3 wheel built with maturin. Run it at Rust speed from a notebook.</p>
</div>
</div>
<section id="python">
<div class="wrap">
<div class="head col">
<div class="eyebrow">Install & use</div>
<h2>A pipeline, from Python.</h2>
</div>
<pre>pip install millwright</pre>
<pre><span class="k">import</span> millwright <span class="k">as</span> mw
train = mw.Frame.from_pandas(df) <span class="c"># or from_numpy / from_rows</span>
pipe = (mw.<span class="f">Pipeline</span>()
.step(<span class="s">"impute"</span>, mw.SimpleImputer.median())
.step(<span class="s">"scale"</span>, mw.StandardScaler())
.estimator(<span class="s">"rf"</span>, mw.RandomForest(n_trees=<span class="k">200</span>, max_depth=<span class="k">8</span>)))
pipe.fit(train, y_train)
preds = pipe.predict(test)
metrics = pipe.evaluate(test, y_test) <span class="c"># -> {"accuracy": …, "f1": …}</span></pre>
<p>The transformer / estimator objects (<code class="inl">StandardScaler</code>, <code class="inl">MinMaxScaler</code>, <code class="inl">SimpleImputer</code>, <code class="inl">OneHotEncoder</code>, <code class="inl">RandomForest</code>, <code class="inl">LinearRegression</code>, <code class="inl">Knn</code>, <code class="inl">Svc</code>, <code class="inl">NaiveBayes</code>) are the same engines as Rust. The older builder form — <code class="inl">pipe.standard_scaler()</code>, <code class="inl">pipe.random_forest()</code> — still works.</p>
</div>
</section>
<section id="ingest">
<div class="wrap">
<div class="head col">
<div class="eyebrow">Ingest & EDA</div>
<h2>numpy, pandas, or a typed table.</h2>
</div>
<pre><span class="c"># a Frame reads arrays and DataFrames directly</span>
train = mw.Frame.from_numpy(X) <span class="c"># or from_pandas(df) / from_rows(rows)</span>
<span class="c"># or the dtype-aware Table (strings, dates, nulls) + automated EDA</span>
data = mw.Table.from_csv(<span class="s">"churn.csv"</span>)
mw.Profile.of_with_target(data, <span class="s">"churned"</span>).to_html(<span class="s">"eda.html"</span>)
train = data.to_frame()</pre>
</div>
</section>
<section id="tune">
<div class="wrap">
<div class="head col">
<div class="eyebrow">Tune, explain, export</div>
<h2>The whole lifecycle.</h2>
</div>
<pre><span class="c"># grid search + stratified CV over the pipeline</span>
best = (mw.GridSearch(pipe, {<span class="s">"rf__max_depth"</span>: [<span class="k">4</span>, <span class="k">8</span>, <span class="k">16</span>]})
.cv(mw.StratifiedKFold(<span class="k">5</span>)).scoring(<span class="s">"f1"</span>)
.fit(train, y_train))
best.best_score; best.best_params()
<span class="c"># manual ensembles and full AutoML are first-class APIs too</span>
another_pipe = mw.Pipeline().estimator(<span class="s">"rf"</span>, mw.RandomForest(n_trees=<span class="k">120</span>))
vote = (mw.Voting(<span class="s">"hard"</span>, <span class="s">"classification"</span>)
.add(<span class="s">"rf1"</span>, pipe)
.add(<span class="s">"rf2"</span>, another_pipe))
vote.fit(train, y_train)
soft_vote = (mw.Voting(<span class="s">"soft"</span>, <span class="s">"classification"</span>)
.add(<span class="s">"lr1"</span>, mw.Pipeline().estimator(<span class="s">"lr"</span>, mw.LogisticRegression()))
.add(<span class="s">"lr2"</span>, mw.Pipeline().estimator(<span class="s">"lr"</span>, mw.LogisticRegression(l2=<span class="k">0.01</span>))))
soft_vote.fit(train, y_train)
probabilities = soft_vote.predict_proba(test)
probability_pipe = mw.Pipeline().estimator(<span class="s">"lr"</span>, mw.LogisticRegression())
probability_pipe.fit(train, y_train)
probabilities = probability_pipe.predict_proba(test)
auto = (mw.AutoML.classifier().budget_trials(<span class="k">40</span>)
.deployability(<span class="s">"onnx"</span>) <span class="c"># use "any" for KNN/NB/SVC too</span>
.ensemble_kinds([<span class="s">"voting"</span>, <span class="s">"bagging"</span>, <span class="s">"boosting"</span>, <span class="s">"stacking"</span>])
.fit(train, y_train))
rows = auto.leaderboard_entries() <span class="c"># [(config, score), …]</span>
failed = auto.candidate_failures() <span class="c"># candidates skipped safely</span>
refit_fallbacks = auto.refit_failures() <span class="c"># ranked winners that failed full refit</span>
winner = auto.best_model() <span class="c"># pipeline or ensemble</span>
auto.elapsed_seconds <span class="c"># measured search + refit time</span>
auto.completed_trials, auto.budget_exhausted <span class="c"># budget diagnostics</span>
if auto.supports_proba:
probabilities = auto.predict_proba(test) <span class="c"># direct winner probabilities</span>
auto.export_onnx(<span class="s">"automl.onnx"</span>)
<span class="c"># SHAP importance, and one portable ONNX artifact</span>
pipe.fit(train, y_train)
pipe.explain(test) <span class="c"># [(feature, mean|shap|), …]</span>
pipe.export_onnx(<span class="s">"churn.onnx"</span>)
<span class="c"># consume an external sklearn / PyTorch model (exported to ONNX) as a step</span>
ext = mw.<span class="f">Pipeline</span>().estimator(<span class="s">"onnx"</span>, mw.OnnxModel(<span class="s">"model.onnx"</span>))</pre>
<div class="callout"><b>Note.</b> ONNX export folds affine preprocessing (scalers) into the graph; a non-affine step (impute, one-hot) raises, naming the step. Fit / predict / evaluate / explain work with any steps.</div>
<p class="tiny"><code class="inl">python</code> is deliberately not part of <code class="inl">full</code>: pyo3's <code class="inl">extension-module</code> defers libpython symbols, so a plain <code class="inl">cargo test</code> can't link it. It is built and tested the way it ships — as a wheel. To build from source, from a virtualenv: <code class="inl">maturin develop --features python</code>. The wheel bundles EDA, model selection, ensembles, AutoML, explainability, and ONNX.</p>
</div>
</section>
<div class="wrap">
<div class="pager">
<a href="deploy.html"><span class="dir">← prev</span><b>Deploy</b></a>
<a class="next" href="index.html"><span class="dir">back to →</span><b>Docs home</b></a>
</div>
</div>
</main>
<footer>
<div class="wrap">
<span class="mono">⚙ millwright docs</span>
<span class="mono"><a href="../index.html">design brief</a> · <a href="https://crates.io/crates/millwright">crates.io</a> · <a href="https://pypi.org/project/millwright/">PyPI</a> · <a href="https://docs.rs/millwright">docs.rs</a> · <a href="https://github.com/mi7plus/millwright">GitHub</a></span>
</div>
</footer>
</body></html>