đŸ’» Streamlit

Boostez le développement de vos applis data !

Corentin DUCLOUX

20/03/2024

Présentation

Un framework python récent, lancé en 2019.

Et avant Streamlit, qu’est ce qu’il y avait ?

  • Flask \(\Rightarrow\) Prise en main trĂšs complexe, plus adaptĂ©e pour les Software Engineers.

  • Plotly Dash \(\Rightarrow\) Prise en main plus simple que Flask, concept assez similaire Ă  Shiny avec sĂ©paration des composants UI et server.

Pourquoi Streamlit ?

Objectif : Simplifier au maximum le dĂ©veloppement d’applications Data.

User Interface ?

Inputs ?

Outputs ?

Interactions ?

Callbacks ?

Et pourquoi pas juste un script ?

Installation

On passe à l’installation ?

  1. Lancement d’un terminal :
  2. Et ensuite on installe via pip avec la commande
python -m pip install streamlit

Prise en main

\(\Rightarrow\) Lançons la démo pour voir de quoi Streamlit est capable.

👋 Hello Streamlit

python -m streamlit hello

Un premier jet !

app.py
import streamlit as st

st.set_page_config(page_icon="🐍", page_title="PyApp")
st.title("😎 Ma premiĂšre app super stylĂ©e")

st.markdown("Du *markdown* dans l'app ? **Rien de plus simple !**")
st.markdown("- Lien vers [`streamlit`](https://streamlit.io/)")
st.markdown(
    """
    > Des couleurs ? :orange[orange], :red[rouge], :green[vert]
    """
)

st.code(
    """
    [[i for i in range(5)] for j in range(2)] 
    # Du code non-exécutable
    """,
    language="python"
)

st.divider()

🚀 et pour lancer l’appli :

python -m streamlit run app.py

Ce qu’on a vu jusqu’ici

  • st.set_page_config() pour configurer le favicon et le titre de l’onglet dans le navigateur

  • st.title() pour donner un titre Ă  notre app

  • st.markdown() pour ajouter du texte avec diffĂ©rents types de formattage : italique, gras, ajout de liens, etc.

  • st.code() pour inclure du code non-exĂ©cutable provenant de diffĂ©rents langages : python, SQL, CSS, etc.

  • st.divider() pour tracer une ligne horizontale

Intégration de LaTeX

IdentitĂ© D’Euler

\[ e^{i \pi} + 1 = 0 \]

Ajoutons ces quelques lignes de code.

st.header("Ajoutons du LaTeX")
st.subheader("Identité d'*Euler*", divider="blue")
st.latex("e^{i \pi} + 1 = 0")
st.caption(
    """
    L'identité d'*Euler* est souvent citée comme
    un exemple de beauté mathématique.
    """
)

Présentation de widgets

Testons quelques widgets !

  • st.selectbox()
  • st.button()
fruit = st.selectbox(
    "Fruit",
    ("🍓 Fraise", "🍊 Orange", "đŸ„­ Mangue", "🍌 Banane", "🍏 Pomme"),
    index = None,
    placeholder = "Sélectionner un fruit"
)

bouton = st.button("Voir les détails du fruit sélectionné")

if bouton:
    st.write(f"T'as la dalle ! Tu veux manger une **{fruit}**")

Options de layout

  • st.columns() \(\Rightarrow\) Colonnes
  • st.container() \(\Rightarrow\) Conteneur
  • st.expander() \(\Rightarrow\) Expandeur
  • st.tabs() \(\Rightarrow\) Tabs
  • st.sidebar() \(\Rightarrow\) Sidebar
  • st.popover() \(\Rightarrow\) Popover

Colonnes

colonne_1, colonne_2 = st.columns(2)

Explication visuelle par Donald J. Trump

Tabs

On va ajouter quelques onglets (Tabs) à notre application pour diversifier l’interface.

tab_1, tab_2, tab_3 = st.tabs(
    ["🔎 Infos sur l'annĂ©e", "📄 DataFrame", "📊 Graphiques"]
)

if prenom:
    with tab_1:
        if reussite:
            st.balloons()
            st.write(f"FĂ©licitations pour ton annĂ©e *{prenom}* ! 🎈")
        else:
            st.snow()
            st.write(
                f"**Aie**... đŸ„¶ c'est un travail insuffisant *{prenom}*"
            )

Des messages de statut

  • â„č st.info()
  • ❌ st.error()
  • ⚠ st.warning()
  • ✅ st.success()
with st.sidebar:
    with st.expander("On regarde quelques messages ?"):
        st.info(
            f"Ton épanouissement en master : {epanouissement}/10",
            icon="đŸ‘šâ€đŸ«"
        )
        st.error(
            f"Ta note en Concurrence et Innovation : {note_pf}",
            icon="👀"
        )
        st.warning("Ceci est un avertissement gĂ©nĂ©rique", icon="⚠")
        st.success("Message de rĂ©ussite.", icon="✅")

Et avec des vraies données ? (I)

On a vu tout un tas d’élĂ©ments d’UI, mais on a pas vraiment interragi avec des donnĂ©es dignes de ce nom.

\(\Rightarrow\) Morale de l’histoire : Installe polars

Et avec des vraies données ? (II)

Exemple avec des données de cas de COVID-19 aux Etats-Unis entre 2020 et 2022 : + 2.5 millions de lignes !

import polars as pl

df_covid = pl.read_csv(
    "https://raw.githubusercontent.com/nytimes/covid-19-data/master/us-counties.csv"
)

ProblĂšmes

  • L’import met trois ans
  • En plus, chaque action dans l’appli relance l’import


On est pas rendus


Solution (I)

Deux décorateurs trÚs utiles :

@st.cache_data et @st.cache_resource

Solution (II)

@st.cache_data
def import_covid_usa(link: str) -> pl.DataFrame:
    """Fonction d'import des données optimisée."""
    return pl.read_csv(link)

💡 Et maintenant, observons la diffĂ©rence !

df_covid = import_covid_usa(
    "https://raw.githubusercontent.com/nytimes/covid-19-data/master/us-counties.csv"
)

Note

On attend une seule fois pour l’import des donnĂ©es, ce qui est beaucoup plus satisfaisant pour l’utilisateur.

  • Solution la plus optimale ici : base de donnĂ©es

Affichage d’un tableau

On a maintenant envie d’afficher nos donnĂ©es sous forme de tableau.

with tab_2:
    st.dataframe(
        df_covid,
        hide_index=True,
        use_container_width=True,
        column_config={
            "date": st.column_config.DateColumn("📅 Date", format="DD/MM/YYYY")
        },
    )

\(\Rightarrow\) Grande flexibilitĂ© dans l’affichage.

📊 Et pour les graphiques ?

On va chercher à visualiser le nombre de morts par état à une certaine date.

with tab_3:
    st.subheader("Nombre de personnes mortes de COVID-19 *(Noël 2020)*")

    deaths_by_state_christmas = (
        df_covid.filter(pl.col("date") == "2020-12-25")
        .group_by("state")
        .agg(pl.col("deaths").sum())
    )

    st.bar_chart(deaths_by_state_christmas, x="state", y="deaths")

De nombreuses options de visualisation à la volée :

  • st.line_chart()
  • st.scatter_chart()
  • st.map()
  • etc.

DeltaGenerator

Mais comment tout ça marche ?

Au coeur de tous ces composants permettant de crĂ©er l’interface utilisateur, il y a une classe : le DeltaGenerator.

  • SystĂšme basĂ© sur protobuf, crĂ©e par GOOGLE

Pour aller + loin

Secrets management

def check_password():
    """Returns `True` if the user had the correct password."""

    def password_entered():
        """Checks whether a password entered by the user is correct."""
        if hmac.compare_digest(st.session_state["password"], st.secrets["password"]):
            st.session_state["password_correct"] = True
            del st.session_state["password"]  # Don't store the password.
        else:
            st.session_state["password_correct"] = False

    # Return True if the password is validated.
    if st.session_state.get("password_correct", False):
        return True

    # Show input for password.
    st.text_input(
        "Mot de passe",
        type="password",
        on_change=password_entered,
        key="password",
        placeholder="Veuillez insérer le mot de passe pour accéder à l'application.",
    )
    if "password_correct" in st.session_state:
        st.error("😕 Mot de passe incorrect")
    return False


if not check_password():
    st.stop()

Exemples concrets


API Reference : https://docs.streamlit.io/library/api-reference

FIN

(Un dernier meme pour la route)