7 · An interactive prediction app
Now wire widgets to the model: read feature values from inputs, assemble them into an ordered vector, call `model.predict`, and show the result with `st.metric`. Wrap the inputs in `st.form` so the script reruns only when the user clicks submit — not on every keystroke.
Build a predictor the Streamlit way: widget calls read each feature into a variable, you assemble them into an ORDERED vector (same trained order as the Flask route), call `model.predict`, and display it with `st.metric`/`st.write`. Wrap the inputs in `st.form` + `st.form_submit_button` so the rerun-and-predict happens ONCE on submit — not on every keystroke.
Without this:
Without a form, every keystroke reruns the script and re-predicts mid-typing on half-entered values; and without assembling the vector in trained order, you'd feed features into the wrong slots — the same silent bug as the Flask API.
Lesson 6 gave you widgets and the rerun model. Now we connect them to the model to build a real interactive predictor — the Streamlit equivalent of the Flask /predict route, but for a human instead of a machine.
Read each feature from a widget. Thanks to the rerun model, reading input is trivial: assign each widget call to a variable. tenure = st.slider("Tenure", 0, 72, 12) gives you the current tenure; charges = st.number_input("Monthly charges", value=80.0) gives the current charges; a st.selectbox for a categorical, and so on. After the script reruns, each variable already holds the latest value the user chose.
Assemble the feature vector — order still matters. This is the same discipline as the Flask API: the model was trained on features in a specific order, so you must build the vector in that exact order. Pull each value by name from your widgets in the trained order: x = np.array([[tenure, charges, calls, is_premium]]) — with the columns lined up to FEATURES. Get the order wrong and you feed "charges" into the "tenure" slot: a confident, meaningless prediction.
Predict, then display. Call model.predict_proba(x) (or model.predict), then show the result with a display widget. st.metric("Churn probability", "83%") renders a big, dashboard-style number; st.write(...) shows free-form text or data; st.success(...) / st.error(...) give a colored status banner. This is the human-facing payoff — a readable prediction, not raw JSON.
The form problem — and st.form. Here's a subtlety the rerun model creates. By default, every widget interaction reruns the whole script — so if you have five inputs, the script reruns (and re-predicts) the instant the user touches any one of them, often on a half-entered value. That's wasteful and jumpy. The fix is st.form: you group the inputs inside a with st.form("..."): block ending in st.form_submit_button("Predict"). Inside a form, changing the widgets does NOT trigger a rerun — Streamlit holds the values until the user clicks the submit button, and only then reruns the script once with all the inputs together. So the user fills everything in, clicks once, and gets exactly one prediction. st.form_submit_button(...) returns True on that submit run, which is your cue to predict.
When to use a form vs bare widgets. Use bare widgets when you want instant, live reactivity — a slider that updates a chart as you drag it feels great. Use a form when inputs should be batched — a multi-field prediction where re-predicting mid-entry is pointless and you want one deliberate "Predict" action. A prediction app is the textbook case for a form.
The read-along cell shows the full interactive predictor: a st.form of inputs → an ordered vector → model.predict → st.metric.
A Streamlit prediction app: `st.form` batches the inputs so the script reruns only on submit, the widgets are assembled into an ordered feature vector, `model.predict_proba` runs, and `st.metric`/`st.success`/`st.error` display the result.
In a Streamlit predictor, how does the code get the value the user chose in `tenure = st.slider("Tenure", 0, 72, 12)`?
- Read features by assigning widget calls to variables, then assemble them into a vector in the EXACT trained order — the same order discipline as the Flask API.
- Predict with `model.predict`/`predict_proba` and display the result with human-friendly widgets like `st.metric`, `st.success`, and `st.error` — not raw JSON.
- Wrap multi-input predictions in `st.form` + `st.form_submit_button` so the script reruns once on submit, not on every keystroke — batching the inputs into one deliberate action.
This widgets→vector→`predict`→`st.metric` pattern is how nearly every Streamlit ML demo works — it's the fastest path from a trained model to a clickable prediction tool a stakeholder can actually use.
If you remove it: Without batching inputs in a form, the app re-predicts on every keystroke over half-entered values; without ordered assembly, the prediction is confidently wrong.