Module 2 — Composants de saisie et d'affichage
Une application ne sert à rien tant qu'on ne peut ni y entrer une valeur ni lire un résultat. Ce module passe en revue les composants de saisie de Streamlit et ceux qui affichent des données, puis les assemble en un formulaire de scoring individuel : l'opérateur saisit les caractéristiques d'un client, l'application renvoie sa probabilité de résiliation. Ce formulaire est la deuxième brique du fil rouge, après la lecture du fichier réalisée au module 1.
Les composants de saisie qu'on utilise vraiment
Streamlit propose une trentaine de composants d'entrée. La grande majorité des applications réelles n'en emploie qu'une dizaine, ceux qui suivent. Chaque appel renvoie une valeur Python typée et prête à être utilisée immédiatement, sans transformation.
import streamlit as st
from datetime import date
# Texte libre, court ou long.
nom = st.text_input("Nom du client", value="")
commentaire = st.text_area("Commentaire commercial", height=120)
# Nombres, avec des bornes explicites.
age = st.number_input("Âge", min_value=18, max_value=100, value=42, step=1)
mensualite = st.slider("Mensualité en euros", min_value=10.0, max_value=200.0, value=45.0, step=0.5)
# Choix parmi une liste.
type_contrat = st.selectbox("Type de contrat", options=["Mensuel", "Annuel", "Deux ans"])
services = st.multiselect(
"Services souscrits",
options=["Internet", "Téléphonie", "Télévision", "Support prioritaire"],
default=["Internet"],
)
# Vrai ou faux.
paperless = st.checkbox("Facturation dématérialisée")
# Dates et heures.
debut = st.date_input("Date d'entrée en portefeuille", value=date(2024, 1, 15))
Trois nuances méritent d'être connues. Un st.slider fournit un vrai float ou int, jamais une chaîne : les décimales déclarées dans min_value fixent le type. Un st.selectbox renvoie l'élément lui-même, pas son index, ce qui évite le double décalage typique des interfaces classiques. Enfin, chaque composant accepte une clé key="..." unique qui l'identifie dans l'état de session ; c'est indispensable quand plusieurs composants du même type coexistent, sous peine de voir Streamlit les traiter comme un seul.
Les composants d'affichage
Symétriquement, une poignée de fonctions couvre l'immense majorité des besoins d'affichage. On distingue le texte, les données et les mesures.
Pour le texte structuré, st.title, st.header et st.subheader reprennent les niveaux de titre HTML. La fonction générale st.write accepte à peu près tout — chaîne, DataFrame, figure Plotly, dictionnaire — et choisit l'affichage adéquat, ce qui en fait un bon outil de mise au point. Le Markdown est reconnu par st.markdown avec l'argument unsafe_allow_html=True pour les cas particuliers.
Pour les données tabulaires, deux composants : st.dataframe produit un tableau interactif, triable et filtrable par l'utilisateur, tandis que st.table produit un tableau statique. On préfère st.dataframe dès que le tableau dépasse une dizaine de lignes ou qu'un tri est utile.
Pour les indicateurs synthétiques, st.metric affiche une grande valeur, un libellé et éventuellement une variation. C'est le composant idéal pour un tableau de bord :
st.metric(
label="Clients à risque",
value="127",
delta="+18 vs semaine dernière",
delta_color="inverse", # rouge pour une hausse, vert pour une baisse
)
L'argument delta_color="inverse" mérite qu'on s'y arrête : une hausse du nombre de clients à risque est une mauvaise nouvelle, donc elle doit s'afficher en rouge. Le rouge et le vert de Streamlit ont une convention de couleur inversable par indicateur ; oublier ce détail transmet le signal opposé de celui qu'on veut communiquer.
Assembler le formulaire de scoring
Voici la deuxième page du fil rouge, complète. L'opérateur saisit les données d'un client, l'application affiche la probabilité de résiliation renvoyée par le modèle (chargé pour l'instant sous forme d'une fonction factice ; le module 8 le remplacera par le vrai). L'objectif est de rendre l'ordre visuel naturel et de fournir des repères raisonnables.
import streamlit as st
st.title("Score de résiliation — Un client")
st.caption("Saisissez les caractéristiques du client pour obtenir sa probabilité de résiliation.")
col_a, col_b = st.columns(2)
with col_a:
anciennete = st.number_input("Ancienneté (mois)", min_value=0, max_value=120, value=24)
mensualite = st.slider("Mensualité (euros)", 10.0, 150.0, 45.0, step=0.5)
type_contrat = st.selectbox("Contrat", ["Mensuel", "Annuel", "Deux ans"])
with col_b:
fibre = st.checkbox("Fibre optique")
paperless = st.checkbox("Facturation dématérialisée", value=True)
services = st.multiselect(
"Services",
["Streaming", "Support prioritaire", "Antivirus"],
default=["Streaming"],
)
# Faux modèle pour le module 2 : le module 8 branchera le vrai.
def scorer(anciennete, mensualite, type_contrat, fibre, paperless, services):
base = 0.10 + 0.005 * mensualite - 0.008 * anciennete
if type_contrat == "Mensuel":
base += 0.15
if not paperless:
base += 0.05
return max(0.0, min(1.0, base))
if st.button("Calculer le score", type="primary"):
p = scorer(anciennete, mensualite, type_contrat, fibre, paperless, services)
col_m, col_j = st.columns([1, 2])
col_m.metric("Probabilité de résiliation", f"{p:.0%}")
if p >= 0.4:
col_j.error("Client à risque : proposer une offre de rétention.")
elif p >= 0.2:
col_j.warning("Vigilance : suivre l'évolution.")
else:
col_j.success("Risque faible.")
Deux points de méthode s'illustrent ici. Le premier : les bornes des composants numériques doivent refléter la réalité du domaine, pas des valeurs par défaut génériques. Une ancienneté de 240 mois n'a pas de sens pour un opérateur qui existe depuis dix ans ; laisser Streamlit accepter cette valeur revient à laisser passer une donnée absurde dans le modèle. Le second : les messages st.error, st.warning et st.success colorent la sortie selon le niveau de gravité. C'est un raccourci d'accessibilité précieux ; l'œil interprète la couleur avant même de lire le texte.
Le piège du bouton
L'exemple ci-dessus utilise st.button("Calculer le score") pour déclencher le calcul. Rappelez-vous du module 1 : à chaque clic, le script est réexécuté du haut en bas. La condition if st.button(...) est vraie seulement pour l'exécution qui suit le clic. Dès qu'un autre composant change — l'utilisateur modifie l'ancienneté après avoir vu le score, par exemple — le script est relancé, le bouton renvoie False, et le score disparaît de l'écran.
Ce comportement est logique mais il surprend systématiquement au premier essai. Les solutions figurent aux modules à venir : conserver le résultat dans st.session_state (module 6), ou envelopper la saisie dans un st.form qui ne déclenche l'exécution complète qu'à la soumission (module 6 aussi). Pour l'instant, le comportement présenté ci-dessus suffit à comprendre le mécanisme.
Il est tentant d'écrire options[st.selectbox("Choisir", range(len(options)))] pour retrouver un objet à partir de son index. Ne le faites pas : st.selectbox accepte directement une liste de n'importe quoi — dictionnaires, dataclasses, tuples — et renvoie l'élément lui-même. Passer par un index rend le code fragile au moindre réordonnancement des options et gêne la lecture.
En résumé
- Une dizaine de composants de saisie couvre la majorité des besoins :
text_input,number_input,slider,selectbox,multiselect,checkbox,date_input; chacun renvoie une valeur Python typée. - L'affichage repose sur
st.title,st.write,st.dataframe,st.tableetst.metric; l'argumentdelta_color="inverse"évite qu'une mauvaise nouvelle apparaisse en vert. - Les bornes des composants numériques doivent traduire la réalité du domaine, pour empêcher la saisie de valeurs absurdes en amont du modèle.
st.buttonrenvoieTrueuniquement pour l'exécution qui suit le clic ; conserver le résultat entre exécutions nécessite un formulaire oust.session_state, présentés au module 6.
Module suivant : organiser tous ces composants dans une mise en page lisible, avec colonnes, onglets et barre latérale.