Cache · Estado · Layout · Componentes
2026-04-15
Cache · Estado · Layout · Componentes
@st.cache_data · session_state · st.spinner · st.progressst.columns · st.container · st.tabs · st.expander · st.emptyst.logo · st.image · st.popover · st.dialogst.metric · column_config · px.choropleth# Sin @st.cache_data
def cargar_datos(ruta):
return pd.read_csv(ruta)
df = cargar_datos("ventas.csv")
@st.cache_data — la solución# Con @st.cache_data ✅
@st.cache_data
def cargar_datos(ruta):
return pd.read_csv(ruta)
df = cargar_datos("ventas.csv")
cargar_datos("otro.csv"), calcula de nuevo.
@st.cache_data — parámetros clave@st.cache_data(
ttl=3600, # expira en 1 hora
max_entries=5, # máx 5 resultados guardados
show_spinner="Cargando...",
)
def obtener_datos(año: int):
# simulación de query lenta
time.sleep(2)
return df[df["año"] == año]
# Borrar caché manualmente
if st.button("Actualizar datos"):
obtener_datos.clear() # solo esta función
st.cache_data.clear() # toda la caché
| Parámetro | Para qué sirve |
|---|---|
ttl |
Tiempo de vida en segundos |
max_entries |
Límite de entradas |
show_spinner |
Texto de carga |
@st.cache_resourcecache_resource para objetos que no se deben copiar: conexiones a BD, modelos de ML, clientes de API.
st.session_state — el problema# Sin session_state ❌
if st.button("Incrementar"):
contador = contador + 1
# ↑ NameError: contador no existe
# o si lo defines arriba:
contador = 0 # se reinicia en cada rerun
if st.button("Incrementar"):
contador += 1
st.write(contador) # siempre muestra 0
st.session_state — la solución# Con session_state ✅
if "contador" not in st.session_state:
st.session_state.contador = 0
if st.button("Incrementar"):
st.session_state.contador += 1
st.write(st.session_state.contador)
# ↑ persiste entre reruns ✅
st.session_state es un diccionario que sobrevive a los reruns. Cada usuario tiene el suyo — no se comparte.
st.session_state — patrones comunes# Dos sintaxis equivalentes
st.session_state["clave"]
st.session_state.clave
# Inicializar antes de leer
if "filtro" not in st.session_state:
st.session_state.filtro = "Todos"
# Widget sincronizado automáticamente
st.selectbox("País", paises, key="pais_sel")
# → st.session_state.pais_sel tiene el valor
def init_state():
defaults = {
"pagina": "Datos",
"filtro": "Todos",
"busqueda": "",
}
for k, v in defaults.items():
if k not in st.session_state:
st.session_state[k] = v
init_state() # llamar al inicio
init_state() evita errores de clave no encontrada.st.spinner y st.progress# Spinner — bloque con indicador giratorio
with st.spinner("Procesando datos..."):
df = pd.read_csv("datos_grandes.csv")
df = limpiar(df)
# todo lo del bloque muestra el spinner
# Progress bar — para operaciones paso a paso
barra = st.progress(0, text="Iniciando...")
pasos = ["Leer CSV", "Limpiar", "Calcular"]
for i, paso in enumerate(pasos):
barra.progress((i+1)/len(pasos), text=paso)
time.sleep(0.5)
barra.empty() # quitar al terminar
# st.status — spinner expandible
with st.status("Procesando...", expanded=True) as s:
st.write("Paso 1: leyendo...")
time.sleep(1)
s.update(label="Listo ✅", state="complete")
st.spinner cuando no sabes cuánto va a tardar. Usa st.progress cuando tienes pasos contables.
st.columns — dividir en columnas# Métricas en 4 columnas — patrón más común del curso
col1, col2, col3, col4 = st.columns(4)
col1.metric("Ventas", "$45,200", "+12%")
col2.metric("Clientes", "1,234", "+8%")
col3.metric("Productos", "89", "-2")
col4.metric("Países", "23")
# Proporciones personalizadas [3, 2] = 60% / 40%
col_main, col_side = st.columns([3, 2])
with col_main:
st.plotly_chart(fig)
with col_side:
st.dataframe(resumen)
st.container — agrupar y ordenar# Insertar contenido fuera de orden
header = st.container() # reservar espacio ①
st.write("Este texto va segundo")
# Llenar el container después ②
header.metric("Total", len(df))
header.write("Este aparece arriba ✅")
# Container con borde visual
with st.container(border=True):
st.subheader("Panel de control")
st.slider("Año", 2018, 2024)
st.selectbox("País", paises)
st.tabs — pestañas de contenidotab1, tab2, tab3 = st.tabs([
"Tabla",
"Gráfico",
"Resumen"
])
with tab1:
st.dataframe(df)
with tab2:
st.plotly_chart(fig)
with tab3:
st.metric("Total", len(df))
st.write(df.describe())
st.expander y st.emptywith st.expander("Ver datos crudos"):
st.dataframe(df)
with st.expander("Filtros avanzados"):
st.multiselect("Columnas:", df.columns)
st.slider("Rango de fechas", ...)
ph = st.empty() # reservar espacio
with ph.container(): # llenar
st.info("Cargando...")
barra = st.progress(0)
ph.empty() # limpiar todo
st.logo y st.image# Logo en la parte superior del sidebar
st.logo(
"logo.png",
size="large",
link="https://miempresa.com"
)
# Imagen estándar
st.image(
"grafico_exportado.png",
caption="Gráfico exportado",
use_container_width=True
)
# Desde URL directa
st.image(
"https://ejemplo.com/imagen.jpg",
width=300
)
# Múltiples imágenes en columnas
col1, col2 = st.columns(2)
col1.image("antes.png", caption="Antes")
col2.image("despues.png", caption="Después")
st.logo es diferente a st.image — aparece fijo en la parte superior del sidebar, no en el flujo principal de la página.
st.popover — panel flotante# Botón que abre un panel flotante
with st.popover("⚙️ Configurar"):
st.checkbox("Mostrar valores", True)
st.slider("Opacidad", 0, 100, 80)
st.color_picker("Color", "#FF4B4B")
# En la misma línea con columnas
col1, col2, _ = st.columns([1, 1, 4])
with col1.popover("Filtros"):
año = st.selectbox("Año:", [2022,2023,2024])
with col2.popover("Columnas"):
cols = st.multiselect("Ver:", df.columns)
st.dialog — ventana modal# Decorar una función para convertirla en modal
@st.dialog("Confirmar acción", width="small")
def confirmar_borrado(nombre: str):
st.warning(f"¿Borrar '{nombre}'?")
col1, col2 = st.columns(2)
if col1.button("Sí, borrar", type="primary"):
borrar(nombre)
st.rerun()
if col2.button("Cancelar"):
st.rerun() # cierra el modal
# Invocar desde un botón
fila_sel = st.selectbox("Registro:", registros)
if st.button("Borrar registro"):
confirmar_borrado(fila_sel) # abre el modal
st.metric — KPIs con deltacol1, col2, col3, col4 = st.columns(4)
col1.metric("Ventas del mes", "$45,200", "+12%")
col2.metric("Clientes activos", "1,234", "+8%")
col3.metric("Productos", "89", "-2",
delta_color="inverse") # rojo si positivo
col4.metric("Países", "23")
delta_color="inverse" invierte la lógica de color — útil para métricas donde subir es malo (costos, errores, devoluciones).
column_config — tablas con superpoderes| Tipo | Cuándo usarlo | Ejemplo |
|---|---|---|
| TextColumn | Texto con ancho personalizado | TextColumn("Nombre", width="large") |
| NumberColumn | Números con formato moneda/unidad | NumberColumn("Precio", format="$%.2f") |
| DateColumn | Fechas con formato legible | DateColumn("Fecha", format="DD/MM/YYYY") |
| ProgressColumn | Valores relativos con barra visual | ProgressColumn("Ventas", max_value=1000) |
| LinkColumn | URLs clicables | LinkColumn("Fuente", display_text="Ver") |
| ImageColumn | Miniaturas de imágenes | ImageColumn("Foto", width="small") |
st.dataframe(df, column_config={
"producto": st.column_config.TextColumn("Producto", width="large"),
"precio": st.column_config.NumberColumn("Precio ($)", format="$%.2f"),
"fecha": st.column_config.DateColumn("Fecha", format="DD/MM/YYYY"),
"ventas": st.column_config.ProgressColumn("Ventas",
min_value=0, max_value=int(df.ventas.max())),
})
px.choropleth — mapa mundialimport plotly.express as px
# Necesitas códigos ISO alpha-3
df_paises = pd.DataFrame({
"pais": ["USA", "CHN", "DEU", "BRA"],
"valor": [100, 85, 62, 45]
})
fig = px.choropleth(
df_paises,
locations="pais", # columna con código ISO
color="valor", # columna a visualizar
hover_name="pais",
color_continuous_scale="Reds",
title="Distribución mundial"
)
fig.update_layout(
geo=dict(showframe=False,
bgcolor="rgba(0,0,0,0)"),
paper_bgcolor="rgba(0,0,0,0)",
font_color="#e8e8e8"
)
st.plotly_chart(fig, use_container_width=True)
USA, CHN, DEU — no texto libre como "United States".
import pycountry
def a_iso3(nombre):
try:
return pycountry.countries\
.search_fuzzy(nombre)[0].alpha_3
except:
return None
df["iso"] = df["pais"].apply(a_iso3)
Dataset: netflix_titles.csv de Kaggle · kaggle.com/datasets/shivamb/netflix-shows · 8803 filas · gratuito
# netflix_dashboard.py
import streamlit as st
import pandas as pd
import plotly.express as px
st.set_page_config(page_title="Netflix",
page_icon="🎬", layout="wide")
# ① Cache — carga una sola vez
@st.cache_data
def cargar(ruta):
df = pd.read_csv(ruta)
df["date_added"] = pd.to_datetime(
df["date_added"].str.strip(), errors="coerce")
df["año_agregado"] = df["date_added"].dt.year
df["pais"] = df["country"].str.split(",") \
.str[0].str.strip()
return df
# ② Dialog — detalle de un título
@st.dialog("Detalle", width="large")
def ver_titulo(titulo, df):
fila = df[df["title"] == titulo].iloc[0]
st.subheader(fila["title"])
st.caption(fila["type"]+" · "+str(fila["release_year"]))
st.write(fila["description"])
# ③ Session State
if "tipo" not in st.session_state:
st.session_state.tipo = "Todos"
# ④ Sidebar con filtros
with st.sidebar:
st.markdown("# 🎬 Netflix")
tipo = st.radio("Tipo:", ["Todos","Movie","TV Show"])
with st.popover("Más filtros"):
año = st.slider("Año:", 1925, 2021, (2000,2021))
# ⑤ Spinner + carga
with st.spinner("Cargando..."):
df = cargar("netflix_titles.csv")
# ⑥ Filtros aplicados
if tipo != "Todos": df = df[df["type"]==tipo]
# ⑦ KPIs
c1,c2,c3,c4 = st.columns(4)
c1.metric("Títulos", len(df))
c2.metric("Películas", (df.type=="Movie").sum())
c3.metric("Series", (df.type=="TV Show").sum())
c4.metric("Países", df["pais"].nunique())
# ⑧ Tabs con todo el contenido
t1,t2,t3 = st.tabs(["Overview","Películas","Países"])
with t1: ...
with t2: ...
with t3: ...
netflix_titles.csvpip install streamlit plotly pandas pycountry
localhost:8501
with st.spinner para duración desconocida. st.progress para pasos contables..empty().st.logo fijo en sidebar. st.image en el flujo normal de la página.@st.cache_data en todas las funciones de carga de datosst.session_state para filtros y estado globalst.spinner o st.progress al cargar datosst.tabs para organizar las vistas del dashboardst.columns(4) con st.metric para KPIs superioresst.expander o st.popover en la UIcolumn_config con al menos un tipo especial en la tabla¡A construir! 🧠
Módulo 3 — Completado
Preguntas antes de cerrar 🙋
Módulo 3 · Streamlit · Cache · Estado · Layout