Module 4 — Graphiques et tableaux
Un tableau de bord commercial vit de deux choses : des tableaux qu'on parcourt et des graphiques qui aident à décider en un coup d'œil. Ce module compare les trois familles de graphiques disponibles dans Streamlit — les graphiques natifs, Plotly et Altair — puis approfondit les tableaux interactifs et éditables. À la fin, la vue « Suivi hebdomadaire » du fil rouge sera réellement peuplée.
Trois familles de graphiques, trois usages
Les graphiques natifs. Streamlit expose trois raccourcis pour les cas les plus fréquents : st.line_chart, st.bar_chart et st.area_chart. Ils prennent un DataFrame ou un tableau NumPy et tracent un graphique responsive, adapté à la largeur de la colonne. Aucune configuration, aucun titre à ajouter — l'apparence est correcte par défaut. C'est le bon choix pour les cinquante premiers pour cent d'une application : les indicateurs de suivi, les histogrammes rapides, les répartitions par catégorie.
import streamlit as st
import pandas as pd
historique = pd.read_csv("donnees/historique_semaines.csv", parse_dates=["semaine"])
historique = historique.set_index("semaine")
st.line_chart(historique[["clients_a_risque", "clients_retenus"]])
Plotly. Dès qu'il faut de l'interactivité fine — zoom sur une plage de dates, sélection à la souris, infobulles personnalisées — Plotly est le choix par défaut. Streamlit l'affiche via st.plotly_chart en respectant la mise à l'échelle de la colonne.
import plotly.express as px
fig = px.scatter(
filtres,
x="anciennete_mois",
y="mensualite",
color="type_contrat",
size="score_risque",
hover_data=["identifiant"],
labels={
"anciennete_mois": "Ancienneté (mois)",
"mensualite": "Mensualité (EUR)",
"score_risque": "Risque",
},
title="Portefeuille clients — ancienneté contre mensualité",
)
fig.update_layout(height=460, legend_title_text="Contrat")
st.plotly_chart(fig, use_container_width=True)
Altair. Altair convient particulièrement bien aux graphiques statistiques et aux compositions multi-couches, avec une grammaire déclarative. Streamlit l'affiche via st.altair_chart. Sur des applications à forte densité analytique — segmentation, profils, comparaisons multi-facteurs — Altair reste plus concis que Plotly.
import altair as alt
barres = (
alt.Chart(filtres)
.mark_bar()
.encode(
x=alt.X("type_contrat:N", title="Contrat"),
y=alt.Y("count():Q", title="Nombre de clients"),
color=alt.Color("type_contrat:N", legend=None),
tooltip=["type_contrat:N", "count():Q"],
)
.properties(height=320)
)
st.altair_chart(barres, use_container_width=True)
La question du choix se tranche par les besoins réels de l'audience. Un tableau de bord destiné à une équipe commerciale, qui doit passer trente secondes par matin sur l'écran, gagne à rester sur les graphiques natifs et Plotly ; les compositions Altair complexes s'adressent plutôt à l'équipe data qui construit l'application.
L'argument use_container_width
Trois composants graphiques acceptent l'argument use_container_width=True : les graphiques natifs, st.plotly_chart et st.altair_chart. Il force le graphique à occuper toute la largeur de son conteneur, sans quoi une figure définie à 700 pixels resterait étroite dans une colonne plus large. Sur un tableau de bord réel, on l'active systématiquement ; sans lui, chaque changement de disposition demande un ajustement manuel de la largeur du graphique, ce qui devient rapidement invivable.
Les tableaux interactifs
st.dataframe produit un tableau triable, filtrable et redimensionnable, adossé au moteur AG Grid depuis les versions récentes de Streamlit. Trois options changent l'usage.
st.dataframe(
filtres,
use_container_width=True,
hide_index=True,
column_config={
"score_risque": st.column_config.ProgressColumn(
"Risque", min_value=0.0, max_value=1.0, format="%.0f%%"
),
"mensualite": st.column_config.NumberColumn(
"Mensualité", format="%.2f EUR"
),
"type_contrat": st.column_config.SelectboxColumn(
"Contrat", options=["Mensuel", "Annuel", "Deux ans"]
),
},
)
hide_index=True retire la colonne d'index de pandas, souvent inutile pour un utilisateur final. column_config accepte un descripteur par colonne : barre de progression pour un score entre zéro et un, format monétaire pour une mensualité, sélecteur déroulant pour une valeur catégorielle. Le résultat rend le tableau immédiatement compréhensible sans document d'accompagnement.
Les tableaux éditables
st.data_editor étend st.dataframe en autorisant la modification des cellules par l'utilisateur. Le composant renvoie le tableau modifié, prêt à être utilisé plus loin dans le script.
tableau_modifie = st.data_editor(
filtres,
use_container_width=True,
num_rows="dynamic", # l'utilisateur peut ajouter et supprimer des lignes
key="editeur_clients",
)
if not tableau_modifie.equals(filtres):
st.info(f"{(tableau_modifie != filtres).any(axis=1).sum()} ligne(s) modifiée(s).")
Ce composant est puissant, et il est facile d'en abuser. La règle utile : data_editor est adapté aux corrections ponctuelles — ajuster une étiquette manuellement, marquer un client comme prospect, invalider une ligne — jamais à la saisie de grands volumes. Pour cela, un formulaire ou un import CSV reste supérieur.
Peupler la vue « Suivi hebdomadaire »
Voici la vue de suivi complète, à placer dans l'onglet correspondant du module 3. Trois indicateurs, une courbe de tendance et un tableau détaillé.
import streamlit as st
import pandas as pd
import plotly.express as px
historique = pd.read_csv("donnees/historique_semaines.csv", parse_dates=["semaine"])
derniere = historique.iloc[-1]
avant_derniere = historique.iloc[-2]
c1, c2, c3 = st.columns(3)
c1.metric(
"Clients à risque",
int(derniere["clients_a_risque"]),
int(derniere["clients_a_risque"] - avant_derniere["clients_a_risque"]),
delta_color="inverse",
)
c2.metric(
"Nouveaux clients",
int(derniere["nouveaux_clients"]),
int(derniere["nouveaux_clients"] - avant_derniere["nouveaux_clients"]),
)
c3.metric(
"Résiliations effectives",
int(derniere["resiliations"]),
int(derniere["resiliations"] - avant_derniere["resiliations"]),
delta_color="inverse",
)
fig = px.line(
historique,
x="semaine",
y=["clients_a_risque", "resiliations"],
labels={"value": "Nombre de clients", "variable": "Indicateur", "semaine": "Semaine"},
title="Évolution hebdomadaire",
)
fig.update_layout(height=380, legend_title_text="")
st.plotly_chart(fig, use_container_width=True)
st.subheader("Détail par semaine")
st.dataframe(
historique.sort_values("semaine", ascending=False),
use_container_width=True,
hide_index=True,
column_config={
"semaine": st.column_config.DateColumn("Semaine", format="DD/MM/YYYY"),
"clients_a_risque": st.column_config.NumberColumn("À risque", format="%d"),
"resiliations": st.column_config.NumberColumn("Résiliations", format="%d"),
},
)
La tentation est forte, une fois le composant maîtrisé, d'ajouter un graphique par colonne du DataFrame. C'est le meilleur moyen de rendre un tableau de bord inutile. Chaque graphique doit répondre à une question précise que l'utilisateur se pose : « le nombre de clients à risque augmente-t-il ? », « quel est le profil d'ancienneté et de mensualité des clients à risque ? ». Un graphique qui ne répond à aucune question est un graphique à retirer.
En résumé
- Trois familles cohabitent : natifs pour les tableaux de bord simples, Plotly pour l'interactivité fine, Altair pour les compositions statistiques.
use_container_width=Trueévite qu'un graphique à largeur fixe reste étroit dans une colonne plus large ; à activer systématiquement.st.dataframeaveccolumn_configtransforme un tableau brut en tableau parlant, sans document d'accompagnement.st.data_editorsert aux corrections ponctuelles ; pour la saisie de volume, préférer un formulaire ou un import CSV.
Module suivant : la mise en cache, qui rend cette page instantanée même quand elle relit un modèle de plusieurs centaines de mégaoctets.