st.data_editor: edición de datos en tabla
st.data_editor extiende las capacidades de st.dataframe añadiendo la posibilidad de editar celdas directamente en la interfaz. Mientras que st.dataframe es de solo lectura, st.data_editor devuelve un nuevo DataFrame con todos los cambios que el usuario haya aplicado, lo que permite construir flujos de trabajo donde los datos se modifican de forma interactiva sin necesidad de formularios separados.
Este componente es especialmente útil para aplicaciones de gestión de inventario, configuración de parámetros, corrección de datos y cualquier escenario donde el usuario necesite ajustar valores tabulares de forma directa e inmediata:
import streamlit as st
import pandas as pd
df_original = pd.DataFrame({
"nombre": ["Ana García", "Luis Martín", "María López"],
"departamento": ["Ventas", "IT", "RRHH"],
"salario": [45000, 62000, 38000],
"activo": [True, True, False]
})
df_editado = st.data_editor(df_original, use_container_width=True, hide_index=True)
if not df_editado.equals(df_original):
st.info("Los datos han sido modificados.")
st.write("DataFrame actualizado:", df_editado)
flowchart LR
A[DataFrame original] --> B[st.data_editor]
B --> C[Renderizado celdas editables]
C --> D[Usuario edita celda]
D --> E[Streamlit dispara rerun]
E --> F[Devuelve DataFrame editado]
F --> G{Comparar con original}
G -->|Igual| H[Sin cambios]
G -->|Distinto| I[Detectar cambios]
I --> J[Anadir filas si num_rows dynamic]
I --> K[Eliminar filas con boton borrar]
I --> L[Editar valores celdas]
J --> M[Persistir BD o CSV]
K --> M
L --> M
Columnas con configuración específica de edición
import streamlit as st
import pandas as pd
from datetime import date
df = pd.DataFrame({
"producto": ["Laptop", "Monitor", "Teclado"],
"precio": [1299.99, 349.50, 129.00],
"categoria": ["Informatica", "Perifericos", "Perifericos"],
"stock": [25, 12, 78],
"disponible": [True, True, False],
"alta": [date(2024, 1, 15), date(2023, 8, 10), date(2025, 3, 1)]
})
df_ed = st.data_editor(
df,
use_container_width=True,
hide_index=True,
column_config={
"producto": st.column_config.TextColumn(
"Producto",
help="Nombre del producto en catálogo",
required=True,
max_chars=100
),
"precio": st.column_config.NumberColumn(
"Precio (€)",
min_value=0.0,
max_value=10000.0,
step=0.01,
format="€%.2f"
),
"categoria": st.column_config.SelectboxColumn(
"Categoría",
options=["Informatica", "Perifericos", "Audio", "Accesorios"],
required=True
),
"stock": st.column_config.NumberColumn("Stock", min_value=0, step=1),
"disponible": st.column_config.CheckboxColumn("Disponible"),
"alta": st.column_config.DateColumn("Fecha de alta", format="DD/MM/YYYY")
}
)
La combinación de SelectboxColumn con required=True asegura que el usuario solo pueda seleccionar valores válidos de una lista predefinida, lo que actúa como una capa de validación incorporada en la propia tabla.
num_rows="dynamic": añadir y eliminar filas
El modo dinámico permite al usuario no solo editar valores existentes, sino también añadir nuevas filas (mediante un botón "+" en la parte inferior) y eliminar filas seleccionándolas y pulsando la tecla Suprimir. Este modo transforma el editor en una herramienta CRUD completa dentro de la propia interfaz:
import streamlit as st
import pandas as pd
st.title("Gestión de presupuesto")
df_presupuesto = pd.DataFrame({
"concepto": ["Servidores", "Licencias", "Formación"],
"importe": [12000.0, 8500.0, 3000.0],
"aprobado": [True, True, False]
})
df_resultado = st.data_editor(
df_presupuesto,
num_rows="dynamic", # Permite añadir filas con el botón + y eliminar con la casilla
use_container_width=True,
hide_index=True,
column_config={
"concepto": st.column_config.TextColumn("Concepto", required=True),
"importe": st.column_config.NumberColumn("Importe (€)", min_value=0, format="€%.2f"),
"aprobado": st.column_config.CheckboxColumn("Aprobado")
}
)
# Calcular totales a partir del DataFrame editado
total = df_resultado["importe"].sum()
aprobado = df_resultado[df_resultado["aprobado"] == True]["importe"].sum()
col1, col2, col3 = st.columns(3)
col1.metric("Total presupuesto", f"€ {total:,.2f}")
col2.metric("Aprobado", f"€ {aprobado:,.2f}")
col3.metric("Pendiente", f"€ {total - aprobado:,.2f}")
El parámetro
num_rowsacepta"fixed"(predeterminado, no permite añadir ni eliminar) y"dynamic"(permite ambas operaciones). El modo dinámico resulta ideal para listas de configuración, registros de inventario y cualquier tabla donde el número de filas no sea fijo.
Columnas deshabilitadas (solo lectura)
En muchos escenarios es necesario proteger ciertas columnas de la edición, como claves primarias, valores calculados o datos que provienen de fuentes externas. El parámetro disabled acepta una lista con los nombres de las columnas que deben permanecer en modo de solo lectura:
import streamlit as st
import pandas as pd
import numpy as np
df = pd.DataFrame({
"id": range(1, 6), # No editable (clave primaria)
"nombre": ["A", "B", "C", "D", "E"],
"valor_calculado": np.random.rand(5).round(4), # No editable
"ajuste_manual": [0.0] * 5 # Editable
})
df_ed = st.data_editor(
df,
use_container_width=True,
hide_index=True,
disabled=["id", "valor_calculado"], # Estas columnas no son editables
column_config={
"id": st.column_config.NumberColumn("ID", format="%d"),
"nombre": st.column_config.TextColumn("Nombre"),
"valor_calculado": st.column_config.NumberColumn("Valor calculado", format="%.4f"),
"ajuste_manual": st.column_config.NumberColumn("Ajuste", min_value=-1.0, max_value=1.0, step=0.01, format="%.2f")
}
)
# Calcular valor final
df_ed["valor_final"] = df_ed["valor_calculado"] + df_ed["ajuste_manual"]
st.write("Valores con ajuste aplicado:")
st.dataframe(df_ed[["nombre", "valor_calculado", "ajuste_manual", "valor_final"]], hide_index=True)
Guardar cambios con session_state
import streamlit as st
import pandas as pd
if "df_inventario" not in st.session_state:
st.session_state.df_inventario = pd.DataFrame({
"articulo": ["Papel A4", "Bolígrafos", "Carpetas"],
"cantidad": [500, 150, 30],
"minimo": [100, 50, 10]
})
st.title("Gestión de inventario")
df_editado = st.data_editor(
st.session_state.df_inventario,
use_container_width=True,
hide_index=True,
num_rows="dynamic"
)
col1, col2 = st.columns([1, 4])
with col1:
if st.button("Guardar cambios", type="primary"):
st.session_state.df_inventario = df_editado
st.success("Inventario actualizado.")
# Alertas de stock bajo
bajo_minimo = df_editado[df_editado["cantidad"] < df_editado["minimo"]]
if not bajo_minimo.empty:
st.warning(f"⚠️ {len(bajo_minimo)} artículo(s) por debajo del mínimo: {', '.join(bajo_minimo['articulo'])}")
Contexto: editor interactivo vs. formularios tradicionales
Antes de st.data_editor, editar datos en Streamlit requería construir formularios con un widget por cada campo, más la lógica de sincronización con el DataFrame. Para tablas con muchas filas era inviable: imagina un panel de administración con 50 productos donde cada producto tiene 5 columnas editables. El editor tabular cambia completamente el paradigma: el usuario ve todos los datos de una vez, hace clic en la celda que quiere modificar, escribe el nuevo valor y Streamlit devuelve el DataFrame con los cambios aplicados.
Internamente, st.data_editor se basa en el mismo motor de tablas interactivas que st.dataframe pero añade un modo de edición en celda, validación por tipo, soporte para añadir y eliminar filas, y un mecanismo de diff interno que rastrea qué cambió. Este último punto es útil cuando quieres persistir solo las modificaciones a una base de datos en lugar de reescribir la tabla completa.
Explicación línea por línea del ejemplo de presupuesto
- Se crea un DataFrame inicial con tres conceptos presupuestarios.
st.data_editor(..., num_rows="dynamic")activa el modo dinámico: el usuario ve un botón "+" al final de la tabla para añadir filas y puede eliminar filas seleccionándolas con la casilla izquierda.column_configconfigura cada columna: texto requerido para el concepto, número con formato de euro para el importe, checkbox para aprobado.- El DataFrame devuelto (
df_resultado) contiene todas las filas, incluyendo las nuevas añadidas y sin las eliminadas. - Los cálculos (
total,aprobado,pendiente) se hacen sobre el DataFrame editado, lo que garantiza que los KPIs se actualizan en tiempo real a medida que el usuario modifica los datos.
Tabla de tipos de column_config
| Tipo | Uso | Parámetros clave |
|------|-----|------------------|
| TextColumn | Texto libre | required, max_chars, validate |
| NumberColumn | Números | min_value, max_value, step, format |
| CheckboxColumn | Booleanos | default |
| SelectboxColumn | Selección de lista | options, required |
| DateColumn | Fechas | format, min_value, max_value |
| DatetimeColumn | Fecha + hora | format, timezone |
| TimeColumn | Solo hora | format |
| LinkColumn | URLs navegables | display_text, validate |
| ImageColumn | Imágenes | (solo lectura, URL o base64) |
| ProgressColumn | Barras de progreso | min_value, max_value, format |
| BarChartColumn | Mini-gráfico de barras | y_min, y_max |
| LineChartColumn | Mini-gráfico de líneas | y_min, y_max |
Errores comunes
Cambios perdidos al rerun. Si no guardas el DataFrame devuelto en st.session_state, al recargar la página los cambios desaparecen. Almacena siempre el resultado del editor en session_state o escríbelo a base de datos.
Columnas con tipos mixtos. Si una columna tiene valores mezclados (números y strings), pandas la tratará como object y el editor no podrá aplicar validaciones numéricas. Convierte las columnas al tipo correcto con .astype() antes de pasarlas al editor.
Forgetar required=True. Sin esta marca, el usuario puede dejar celdas vacías al añadir filas en modo dinámico, lo que luego rompe cálculos posteriores.
SelectboxColumn con valores fuera de la lista. Si el DataFrame original contiene valores que no están en options, el editor los aceptará pero el usuario no podrá reutilizarlos desde el desplegable. Asegúrate de que las opciones cubren todos los valores existentes.
Cambios no detectados. Comparar DataFrames con == no funciona con NaN. Usa df_original.equals(df_editado) o compara después de fillna.
Mejores prácticas
- Para tablas grandes (más de 1000 filas), considera paginar o filtrar antes de pasar los datos al editor: el rendimiento se degrada con demasiadas filas.
- Combina el editor con un botón "Guardar cambios" que escribe a base de datos solo cuando el usuario lo solicita. Esto evita llamadas innecesarias a la DB en cada edición.
- Usa
disabledpara proteger columnas técnicas (IDs, timestamps, valores calculados) y permite editar solo las columnas de negocio. - Añade un indicador visual (
st.infoost.warning) cuando detectes cambios no guardados para que el usuario no los pierda. - En entornos colaborativos, considera bloquear el editor si otro usuario está editando el mismo recurso, usando un lock en base de datos o Redis.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Streamlit es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Streamlit
Explora más contenido relacionado con Streamlit y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Crear tablas editables con st.data_editor que devuelven el DataFrame modificado. Configurar qué columnas son editables y cuáles son solo de lectura. Habilitar añadir y eliminar filas con num_rows=\"dynamic\". Usar column_config con SelectboxColumn para columnas de selección en la tabla. Detectar las filas añadidas, modificadas y eliminadas por el usuario.