Spaces:
Sleeping
Sleeping
import gradio as gr | |
import pandas as pd | |
import numpy as np | |
import matplotlib.pyplot as plt | |
import seaborn as sns | |
from scipy import stats | |
from datetime import datetime | |
import docx | |
from docx.shared import Inches, Pt | |
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT | |
import os | |
def generar_tabla(n_filas, concentracion_inicial, unidad_medida, n_replicas): | |
valores_base = [1.000, 0.800, 0.600, 0.400, 0.200, 0.100, 0.050] | |
if n_filas <= 7: | |
solucion_inoculo = valores_base[:n_filas] | |
agua = [round(1 - x, 3) for x in solucion_inoculo] | |
else: | |
solucion_inoculo = valores_base.copy() | |
ultimo_valor = valores_base[-1] | |
for _ in range(n_filas - 7): | |
nuevo_valor = round(ultimo_valor / 2, 3) | |
solucion_inoculo.append(nuevo_valor) | |
ultimo_valor = nuevo_valor | |
agua = [round(1 - x, 3) for x in solucion_inoculo] | |
data = { | |
f"Solución de inóculo ({concentracion_inicial} {unidad_medida})": solucion_inoculo, | |
"H2O": agua | |
} | |
df = pd.DataFrame(data) | |
nombre_columna = f"Solución de inóculo ({concentracion_inicial} {unidad_medida})" | |
df["Factor de Dilución"] = df[nombre_columna].apply(lambda x: round(1 / x, 3)) | |
df["Concentración Predicha Numérica"] = df["Factor de Dilución"].apply( | |
lambda x: concentracion_inicial / x | |
) | |
df[f"Concentración Predicha ({unidad_medida})"] = df["Concentración Predicha Numérica"].round(3).astype(str) | |
# Añadir columnas para las réplicas de "Concentración Real" | |
for i in range(1, n_replicas + 1): | |
df[f"Concentración Real {i} ({unidad_medida})"] = None | |
# Las columnas de promedio y desviación estándar se agregarán durante el análisis | |
return df | |
def ajustar_decimales_evento(df, decimales): | |
df = df.copy() | |
# Ajustar decimales en todas las columnas numéricas | |
for col in df.columns: | |
try: | |
df[col] = pd.to_numeric(df[col], errors='ignore') | |
df[col] = df[col].round(decimales) | |
except: | |
pass | |
return df | |
def calcular_promedio_desviacion(df, n_replicas, unidad_medida, decimales): | |
df = df.copy() | |
# Obtener las columnas de réplicas | |
col_replicas = [f"Concentración Real {i} ({unidad_medida})" for i in range(1, n_replicas + 1)] | |
# Convertir a numérico | |
for col in col_replicas: | |
df[col] = pd.to_numeric(df[col], errors='coerce') | |
# Calcular el promedio y la desviación estándar | |
df[f"Concentración Real Promedio ({unidad_medida})"] = df[col_replicas].mean(axis=1) | |
if n_replicas > 1: | |
df[f"Desviación Estándar ({unidad_medida})"] = df[col_replicas].std(ddof=1, axis=1) | |
else: | |
df[f"Desviación Estándar ({unidad_medida})"] = 0.0 | |
# Redondear al número de decimales especificado | |
df[f"Concentración Real Promedio ({unidad_medida})"] = df[f"Concentración Real Promedio ({unidad_medida})"].round(decimales) | |
df[f"Desviación Estándar ({unidad_medida})"] = df[f"Desviación Estándar ({unidad_medida})"].round(decimales) | |
return df | |
def generar_graficos(df_valid, n_replicas, unidad_medida, palette_puntos, estilo_puntos, | |
palette_linea_ajuste, estilo_linea_ajuste, | |
palette_linea_ideal, estilo_linea_ideal, | |
palette_barras_error, mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos): | |
col_predicha_num = "Concentración Predicha Numérica" | |
col_real_promedio = f"Concentración Real Promedio ({unidad_medida})" | |
col_desviacion = f"Desviación Estándar ({unidad_medida})" | |
# Convertir a numérico | |
df_valid[col_predicha_num] = df_valid[col_predicha_num].astype(float) | |
df_valid[col_real_promedio] = df_valid[col_real_promedio].astype(float) | |
df_valid[col_desviacion] = df_valid[col_desviacion].fillna(0).astype(float) | |
# Calcular regresión lineal | |
slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha_num], df_valid[col_real_promedio]) | |
df_valid['Ajuste Lineal'] = intercept + slope * df_valid[col_predicha_num] | |
# Configurar estilos | |
sns.set(style="whitegrid") | |
plt.rcParams.update({'figure.autolayout': True}) | |
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6)) | |
# Obtener colores de las paletas | |
colors_puntos = sns.color_palette(palette_puntos, as_cmap=False) | |
colors_linea_ajuste = sns.color_palette(palette_linea_ajuste, as_cmap=False) | |
colors_linea_ideal = sns.color_palette(palette_linea_ideal, as_cmap=False) | |
colors_barras_error = sns.color_palette(palette_barras_error, as_cmap=False) | |
# Seleccionar colores | |
color_puntos = colors_puntos[0] | |
color_linea_ajuste = colors_linea_ajuste[0] | |
color_linea_ideal = colors_linea_ideal[0] | |
color_barras_error = colors_barras_error[0] | |
# Gráfico de dispersión con línea de regresión | |
if mostrar_puntos: | |
if n_replicas > 1: | |
# Incluir barras de error | |
ax1.errorbar( | |
df_valid[col_predicha_num], | |
df_valid[col_real_promedio], | |
yerr=df_valid[col_desviacion], | |
fmt=estilo_puntos, | |
color=color_puntos, | |
ecolor=color_barras_error, | |
elinewidth=2, | |
capsize=3, | |
label='Datos Reales' | |
) | |
else: | |
ax1.scatter( | |
df_valid[col_predicha_num], | |
df_valid[col_real_promedio], | |
color=color_puntos, | |
s=100, | |
label='Datos Reales', | |
marker=estilo_puntos | |
) | |
# Línea de ajuste | |
if mostrar_linea_ajuste: | |
ax1.plot( | |
df_valid[col_predicha_num], | |
df_valid['Ajuste Lineal'], | |
color=color_linea_ajuste, | |
label='Ajuste Lineal', | |
linewidth=2, | |
linestyle=estilo_linea_ajuste | |
) | |
# Línea ideal | |
if mostrar_linea_ideal: | |
min_predicha = df_valid[col_predicha_num].min() | |
max_predicha = df_valid[col_predicha_num].max() | |
ax1.plot( | |
[min_predicha, max_predicha], | |
[min_predicha, max_predicha], | |
color=color_linea_ideal, | |
linestyle=estilo_linea_ideal, | |
label='Ideal' | |
) | |
ax1.set_title('Correlación entre Concentración Predicha y Real', fontsize=14) | |
ax1.set_xlabel('Concentración Predicha', fontsize=12) | |
ax1.set_ylabel('Concentración Real Promedio', fontsize=12) | |
# Añadir ecuación y R² en el gráfico | |
ax1.annotate( | |
f'y = {intercept:.3f} + {slope:.3f}x\n$R^2$ = {r_value**2:.4f}', | |
xy=(0.05, 0.95), | |
xycoords='axes fraction', | |
fontsize=12, | |
backgroundcolor='white', | |
verticalalignment='top' | |
) | |
# Posicionar la leyenda | |
ax1.legend(loc='lower right', fontsize=10) | |
# Gráfico de residuos | |
residuos = df_valid[col_real_promedio] - df_valid['Ajuste Lineal'] | |
ax2.scatter( | |
df_valid[col_predicha_num], | |
residuos, | |
color=color_puntos, | |
s=100, | |
marker=estilo_puntos, | |
label='Residuos' | |
) | |
ax2.axhline(y=0, color='black', linestyle='--', linewidth=1) | |
ax2.set_title('Gráfico de Residuos', fontsize=14) | |
ax2.set_xlabel('Concentración Predicha', fontsize=12) | |
ax2.set_ylabel('Residuo', fontsize=12) | |
ax2.legend(loc='upper right', fontsize=10) | |
plt.tight_layout() | |
plt.savefig('grafico.png') # Guardar el gráfico para incluirlo en el informe | |
return fig | |
def evaluar_calidad_calibracion(df_valid, r_squared, rmse, cv_percent): | |
"""Evaluar la calidad de la calibración y proporcionar recomendaciones""" | |
evaluacion = { | |
"calidad": "", | |
"recomendaciones": [], | |
"estado": "✅" if r_squared >= 0.95 and cv_percent <= 15 else "⚠️" | |
} | |
if r_squared >= 0.95: | |
evaluacion["calidad"] = "Excelente" | |
elif r_squared >= 0.90: | |
evaluacion["calidad"] = "Buena" | |
elif r_squared >= 0.85: | |
evaluacion["calidad"] = "Regular" | |
else: | |
evaluacion["calidad"] = "Deficiente" | |
if r_squared < 0.95: | |
evaluacion["recomendaciones"].append("- Considere repetir algunas mediciones para mejorar la correlación") | |
if cv_percent > 15: | |
evaluacion["recomendaciones"].append("- La variabilidad es alta. Revise el procedimiento de dilución") | |
if rmse > 0.1 * df_valid[df_valid.columns[-1]].astype(float).mean(): | |
evaluacion["recomendaciones"].append("- El error de predicción es significativo. Verifique la técnica de medición") | |
return evaluacion | |
def generar_informe_completo(df_valid, n_replicas, unidad_medida): | |
"""Generar un informe completo en formato markdown""" | |
col_predicha_num = "Concentración Predicha Numérica" | |
col_real_promedio = f"Concentración Real Promedio ({unidad_medida})" | |
# Convertir a numérico | |
df_valid[col_predicha_num] = df_valid[col_predicha_num].astype(float) | |
df_valid[col_real_promedio] = df_valid[col_real_promedio].astype(float) | |
# Calcular estadísticas | |
slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha_num], df_valid[col_real_promedio]) | |
r_squared = r_value ** 2 | |
rmse = np.sqrt(((df_valid[col_real_promedio] - (intercept + slope * df_valid[col_predicha_num])) ** 2).mean()) | |
cv = (df_valid[col_real_promedio].std() / df_valid[col_real_promedio].mean()) * 100 # CV de los valores reales | |
# Evaluar calidad | |
evaluacion = evaluar_calidad_calibracion(df_valid, r_squared, rmse, cv) | |
informe = f"""# Informe de Calibración {evaluacion['estado']} | |
Fecha: {datetime.now().strftime('%d/%m/%Y %H:%M')} | |
## Resumen Estadístico | |
- **Ecuación de Regresión**: y = {intercept:.4f} + {slope:.4f}x | |
- **Coeficiente de correlación (r)**: {r_value:.4f} | |
- **Coeficiente de determinación ($R^2$)**: {r_squared:.4f} | |
- **Valor p**: {p_value:.4e} | |
- **Error estándar de la pendiente**: {std_err:.4f} | |
- **Error cuadrático medio (RMSE)**: {rmse:.4f} | |
- **Coeficiente de variación (CV)**: {cv:.2f}% | |
## Evaluación de Calidad | |
- **Calidad de la calibración**: {evaluacion['calidad']} | |
## Recomendaciones | |
{chr(10).join(evaluacion['recomendaciones']) if evaluacion['recomendaciones'] else "No hay recomendaciones específicas. La calibración cumple con los criterios de calidad."} | |
## Decisión | |
{("✅ APROBADO - La calibración cumple con los criterios de calidad establecidos" if evaluacion['estado'] == "✅" else "⚠️ REQUIERE REVISIÓN - La calibración necesita ajustes según las recomendaciones anteriores")} | |
--- | |
*Nota: Este informe fue generado automáticamente. Por favor, revise los resultados y valide según sus criterios específicos.* | |
""" | |
return informe, evaluacion['estado'] | |
def actualizar_analisis(df, n_replicas, unidad_medida, filas_seleccionadas, decimales): | |
if df is None or df.empty: | |
return "Error en los datos", None, "No se pueden generar análisis", df | |
# Convertir filas_seleccionadas a índices | |
if not filas_seleccionadas: | |
return "Se necesitan más datos", None, "No se han seleccionado filas para el análisis", df | |
indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas] | |
# Calcular promedio y desviación estándar dependiendo de las réplicas | |
df = calcular_promedio_desviacion(df, n_replicas, unidad_medida, decimales) | |
col_predicha_num = "Concentración Predicha Numérica" | |
col_real_promedio = f"Concentración Real Promedio ({unidad_medida})" | |
# Convertir columnas a numérico | |
df[col_predicha_num] = pd.to_numeric(df[col_predicha_num], errors='coerce') | |
df[col_real_promedio] = pd.to_numeric(df[col_real_promedio], errors='coerce') | |
df_valid = df.dropna(subset=[col_predicha_num, col_real_promedio]) | |
# Resetear el índice para asegurar que sea secuencial | |
df_valid.reset_index(drop=True, inplace=True) | |
# Filtrar filas según las seleccionadas | |
df_valid = df_valid.loc[indices_seleccionados] | |
if len(df_valid) < 2: | |
return "Se necesitan más datos", None, "Se requieren al menos dos valores reales para el análisis", df | |
# Calcular la regresión y agregar 'Ajuste Lineal' | |
slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha_num], df_valid[col_real_promedio]) | |
df_valid['Ajuste Lineal'] = intercept + slope * df_valid[col_predicha_num] | |
# Generar gráfico con opciones predeterminadas | |
fig = generar_graficos( | |
df_valid, n_replicas, unidad_medida, | |
palette_puntos='deep', estilo_puntos='o', | |
palette_linea_ajuste='muted', estilo_linea_ajuste='-', | |
palette_linea_ideal='bright', estilo_linea_ideal='--', | |
palette_barras_error='pastel', | |
mostrar_linea_ajuste=True, | |
mostrar_linea_ideal=False, # Línea Ideal desmarcada por defecto | |
mostrar_puntos=True | |
) | |
informe, estado = generar_informe_completo(df_valid, n_replicas, unidad_medida) | |
return estado, fig, informe, df | |
def actualizar_graficos(df, n_replicas, unidad_medida, | |
palette_puntos, estilo_puntos, | |
palette_linea_ajuste, estilo_linea_ajuste, | |
palette_linea_ideal, estilo_linea_ideal, | |
palette_barras_error, | |
mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos, | |
filas_seleccionadas, decimales): | |
if df is None or df.empty: | |
return None | |
# Asegurarse de que los cálculos estén actualizados | |
df = calcular_promedio_desviacion(df, n_replicas, unidad_medida, decimales) | |
col_predicha_num = "Concentración Predicha Numérica" | |
col_real_promedio = f"Concentración Real Promedio ({unidad_medida})" | |
# Convertir columnas a numérico | |
df[col_predicha_num] = pd.to_numeric(df[col_predicha_num], errors='coerce') | |
df[col_real_promedio] = pd.to_numeric(df[col_real_promedio], errors='coerce') | |
df_valid = df.dropna(subset=[col_predicha_num, col_real_promedio]) | |
# Resetear el índice para asegurar que sea secuencial | |
df_valid.reset_index(drop=True, inplace=True) | |
# Convertir filas_seleccionadas a índices | |
if not filas_seleccionadas: | |
return None | |
indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas] | |
# Filtrar filas según las seleccionadas | |
df_valid = df_valid.loc[indices_seleccionados] | |
if len(df_valid) < 2: | |
return None | |
# Generar gráfico con opciones seleccionadas | |
fig = generar_graficos( | |
df_valid, n_replicas, unidad_medida, | |
palette_puntos, estilo_puntos, | |
palette_linea_ajuste, estilo_linea_ajuste, | |
palette_linea_ideal, estilo_linea_ideal, | |
palette_barras_error, | |
mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos | |
) | |
return fig | |
def exportar_informe_word(df_valid, informe_md, unidad_medida): | |
# Crear documento Word | |
doc = docx.Document() | |
# Estilos APA 7 | |
style = doc.styles['Normal'] | |
font = style.font | |
font.name = 'Times New Roman' | |
font.size = Pt(12) | |
# Título centrado | |
titulo = doc.add_heading('Informe de Calibración', 0) | |
titulo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER | |
# Fecha | |
fecha = doc.add_paragraph(f"Fecha: {datetime.now().strftime('%d/%m/%Y %H:%M')}") | |
fecha.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER | |
# Insertar gráfico | |
if os.path.exists('grafico.png'): | |
doc.add_picture('grafico.png', width=Inches(6)) | |
ultimo_parrafo = doc.paragraphs[-1] | |
ultimo_parrafo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER | |
# Leyenda del gráfico en estilo APA 7 | |
leyenda = doc.add_paragraph('Figura 1. Gráfico de calibración.') | |
leyenda_format = leyenda.paragraph_format | |
leyenda_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER | |
leyenda.style = doc.styles['Caption'] | |
# Agregar contenido del informe | |
doc.add_heading('Resumen Estadístico', level=1) | |
for linea in informe_md.split('\n'): | |
if linea.startswith('##'): | |
doc.add_heading(linea.replace('##', '').strip(), level=2) | |
else: | |
doc.add_paragraph(linea) | |
# Añadir tabla de datos | |
doc.add_heading('Tabla de Datos de Calibración', level=1) | |
# Convertir DataFrame a lista de listas | |
tabla_datos = df_valid.reset_index(drop=True) | |
tabla_datos = tabla_datos.round(4) # Redondear a 4 decimales si es necesario | |
columnas = tabla_datos.columns.tolist() | |
registros = tabla_datos.values.tolist() | |
# Crear tabla en Word | |
tabla = doc.add_table(rows=1 + len(registros), cols=len(columnas)) | |
tabla.style = 'Table Grid' | |
# Añadir los encabezados | |
hdr_cells = tabla.rows[0].cells | |
for idx, col_name in enumerate(columnas): | |
hdr_cells[idx].text = col_name | |
# Añadir los registros | |
for i, registro in enumerate(registros): | |
row_cells = tabla.rows[i + 1].cells | |
for j, valor in enumerate(registro): | |
row_cells[j].text = str(valor) | |
# Formatear fuente de la tabla | |
for row in tabla.rows: | |
for cell in row.cells: | |
for paragraph in cell.paragraphs: | |
paragraph.style = doc.styles['Normal'] | |
# Guardar documento | |
filename = 'informe_calibracion.docx' | |
doc.save(filename) | |
return filename | |
def exportar_informe_latex(df_valid, informe_md): | |
# Generar código LaTeX | |
informe_tex = r"""\documentclass{article} | |
\usepackage[spanish]{babel} | |
\usepackage{amsmath} | |
\usepackage{graphicx} | |
\usepackage{booktabs} | |
\begin{document} | |
""" | |
informe_tex += informe_md.replace('#', '').replace('**', '\\textbf{').replace('*', '\\textit{') | |
informe_tex += r""" | |
\end{document} | |
""" | |
filename = 'informe_calibracion.tex' | |
with open(filename, 'w') as f: | |
f.write(informe_tex) | |
return filename | |
def exportar_word(df, informe_md, unidad_medida, filas_seleccionadas): | |
df_valid = df.copy() | |
col_predicha_num = "Concentración Predicha Numérica" | |
col_real_promedio = f"Concentración Real Promedio ({unidad_medida})" | |
# Convertir columnas a numérico | |
df_valid[col_predicha_num] = pd.to_numeric(df_valid[col_predicha_num], errors='coerce') | |
df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce') | |
df_valid = df_valid.dropna(subset=[col_predicha_num, col_real_promedio]) | |
# Resetear el índice | |
df_valid.reset_index(drop=True, inplace=True) | |
# Convertir filas_seleccionadas a índices | |
if not filas_seleccionadas: | |
return None | |
indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas] | |
# Filtrar filas según las seleccionadas | |
df_valid = df_valid.loc[indices_seleccionados] | |
if df_valid.empty: | |
return None | |
filename = exportar_informe_word(df_valid, informe_md, unidad_medida) | |
return filename # Retornamos el nombre del archivo | |
def exportar_latex(df, informe_md, filas_seleccionadas): | |
df_valid = df.copy() | |
col_predicha_num = "Concentración Predicha Numérica" | |
col_real_promedio = [col for col in df_valid.columns if 'Real Promedio' in col][0] | |
# Convertir columnas a numérico | |
df_valid[col_predicha_num] = pd.to_numeric(df_valid[col_predicha_num], errors='coerce') | |
df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce') | |
df_valid = df_valid.dropna(subset=[col_predicha_num, col_real_promedio]) | |
# Resetear el índice | |
df_valid.reset_index(drop=True, inplace=True) | |
# Convertir filas_seleccionadas a índices | |
if not filas_seleccionadas: | |
return None | |
indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas] | |
# Filtrar filas según las seleccionadas | |
df_valid = df_valid.loc[indices_seleccionados] | |
if df_valid.empty: | |
return None | |
filename = exportar_informe_latex(df_valid, informe_md) | |
return filename # Retornamos el nombre del archivo | |
def cargar_ejemplo_ufc(n_replicas): | |
df = generar_tabla(7, 2000000, "UFC", n_replicas) | |
# Valores reales de ejemplo | |
for i in range(1, n_replicas + 1): | |
valores_reales = [2000000 - (i - 1) * 10000, 1600000 - (i - 1) * 8000, 1200000 - (i - 1) * 6000, | |
800000 - (i - 1) * 4000, 400000 - (i - 1) * 2000, 200000 - (i - 1) * 1000, | |
100000 - (i - 1) * 500] | |
df[f"Concentración Real {i} (UFC)"] = valores_reales | |
return 2000000, "UFC", 7, df | |
def cargar_ejemplo_od(n_replicas): | |
df = generar_tabla(7, 1.000, "OD", n_replicas) | |
# Valores reales de ejemplo | |
for i in range(1, n_replicas + 1): | |
valores_reales = [1.000 - (i - 1) * 0.050, 0.800 - (i - 1) * 0.040, 0.600 - (i - 1) * 0.030, | |
0.400 - (i - 1) * 0.020, 0.200 - (i - 1) * 0.010, 0.100 - (i - 1) * 0.005, | |
0.050 - (i - 1) * 0.002] | |
df[f"Concentración Real {i} (OD)"] = valores_reales | |
return 1.000, "OD", 7, df | |
def limpiar_datos(n_replicas): | |
df = generar_tabla(7, 2000000, "UFC", n_replicas) | |
return ( | |
2000000, # Concentración Inicial | |
"UFC", # Unidad de Medida | |
7, # Número de filas | |
df, # Tabla Output | |
"", # Estado Output | |
None, # Gráficos Output | |
"" # Informe Output | |
) | |
def generar_datos_sinteticos_evento(df, n_replicas, unidad_medida): | |
df = df.copy() | |
col_predicha_num = "Concentración Predicha Numérica" | |
# Generar datos sintéticos para cada réplica | |
for i in range(1, n_replicas + 1): | |
col_real = f"Concentración Real {i} ({unidad_medida})" | |
df[col_predicha_num] = pd.to_numeric(df[col_predicha_num], errors='coerce') | |
desviacion_std = 0.05 * df[col_predicha_num].mean() # 5% de la media como desviación estándar | |
valores_predichos = df[col_predicha_num].astype(float).values | |
datos_sinteticos = valores_predichos + np.random.normal(0, desviacion_std, size=len(valores_predichos)) | |
datos_sinteticos = np.maximum(0, datos_sinteticos) # Asegurar que no haya valores negativos | |
datos_sinteticos = np.round(datos_sinteticos, 3) | |
df[col_real] = datos_sinteticos | |
return df | |
def actualizar_tabla_evento(df, n_filas, concentracion, unidad, n_replicas, decimales): | |
# Actualizar tabla sin borrar "Concentración Real" | |
df_new = generar_tabla(n_filas, concentracion, unidad, n_replicas) | |
# Mapear columnas | |
col_real_new = [col for col in df_new.columns if 'Concentración Real' in col and 'Promedio' not in col and 'Desviación' not in col] | |
col_real_old = [col for col in df.columns if 'Concentración Real' in col and 'Promedio' not in col and 'Desviación' not in col] | |
# Reemplazar valores existentes en "Concentración Real" | |
for col_new, col_old in zip(col_real_new, col_real_old): | |
df_new[col_new] = None | |
for idx in df_new.index: | |
if idx in df.index: | |
df_new.at[idx, col_new] = df.at[idx, col_old] | |
# Ajustar decimales | |
df_new = ajustar_decimales_evento(df_new, decimales) | |
return df_new | |
def cargar_excel(file): | |
# Leer el archivo Excel | |
df = pd.read_excel(file.name, sheet_name=None) | |
# Verificar que el archivo tenga al menos dos pestañas | |
if len(df) < 2: | |
return "El archivo debe tener al menos dos pestañas.", None, None, None, None, None, None | |
# Obtener la primera pestaña como referencia | |
primera_pestaña = next(iter(df.values())) | |
concentracion_inicial = primera_pestaña.iloc[0, 0] | |
unidad_medida = primera_pestaña.columns[0].split('(')[-1].split(')')[0] | |
n_filas = len(primera_pestaña) | |
n_replicas = len(df) | |
# Generar la tabla base | |
df_base = generar_tabla(n_filas, concentracion_inicial, unidad_medida, n_replicas) | |
# Llenar la tabla con los datos de cada pestaña | |
for i, (sheet_name, sheet_df) in enumerate(df.items(), start=1): | |
col_real = f"Concentración Real {i} ({unidad_medida})" | |
df_base[col_real] = sheet_df.iloc[:, 1].values | |
return concentracion_inicial, unidad_medida, n_filas, n_replicas, df_base, "", None, "" | |
def calcular_regresion_tabla_principal(df, unidad_medida, filas_seleccionadas_regresion, | |
palette_puntos, estilo_puntos, | |
palette_linea_ajuste, estilo_linea_ajuste, | |
mostrar_linea_ajuste, mostrar_puntos, | |
legend_location, decimales, | |
titulo_grafico_original, titulo_grafico_personalizado, | |
eje_x_original, eje_y_original, | |
eje_x_personalizado, eje_y_personalizado): | |
if df is None or df.empty: | |
return "Datos insuficientes", None, None, None | |
col_concentracion = "Concentración Predicha Numérica" | |
col_absorbancia = f"Concentración Real Promedio ({unidad_medida})" | |
col_desviacion = f"Desviación Estándar ({unidad_medida})" | |
# Calcular promedio y desviación estándar si es necesario | |
n_replicas = len([col for col in df.columns if 'Concentración Real' in col and 'Promedio' not in col and 'Desviación' not in col]) | |
df = calcular_promedio_desviacion(df, n_replicas, unidad_medida, decimales) | |
# Convertir columnas a numérico | |
df[col_concentracion] = pd.to_numeric(df[col_concentracion], errors='coerce') | |
df[col_absorbancia] = pd.to_numeric(df[col_absorbancia], errors='coerce') | |
df[col_desviacion] = pd.to_numeric(df[col_desviacion], errors='coerce') | |
df_valid = df.dropna(subset=[col_concentracion, col_absorbancia]) | |
# Resetear el índice para asegurar que sea secuencial | |
df_valid.reset_index(drop=True, inplace=True) | |
# Asegurar que el gráfico original tenga todos los puntos | |
df_original = df_valid.copy() | |
# Convertir filas_seleccionadas a índices | |
if not filas_seleccionadas_regresion: | |
return "Se necesitan más datos", None, None, None | |
indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas_regresion] | |
# Filtrar filas según las seleccionadas para el gráfico personalizado | |
df_valid = df_valid.loc[indices_seleccionados] | |
if len(df_valid) < 2: | |
return "Se requieren al menos dos puntos para calcular la regresión", None, None, None | |
# Calcular regresión lineal para el gráfico personalizado | |
slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_concentracion], df_valid[col_absorbancia]) | |
# Generar gráfico original (con todos los puntos) | |
sns.set(style="whitegrid") | |
fig_original, ax_original = plt.subplots(figsize=(8, 6)) | |
ax_original.errorbar( | |
df_original[col_concentracion], | |
df_original[col_absorbancia], | |
yerr=df_original[col_desviacion], | |
fmt='o', | |
color='blue', | |
ecolor='gray', | |
elinewidth=1, | |
capsize=3, | |
label='Datos' | |
) | |
# Calcular regresión para todos los puntos | |
slope_all, intercept_all, r_value_all, p_value_all, std_err_all = stats.linregress(df_original[col_concentracion], df_original[col_absorbancia]) | |
ax_original.plot( | |
df_original[col_concentracion], | |
intercept_all + slope_all * df_original[col_concentracion], | |
color='red', | |
linestyle='-', | |
label='Ajuste Lineal' | |
) | |
# Título y etiquetas personalizadas para el gráfico original | |
ax_original.set_xlabel(eje_x_original if eje_x_original else 'Concentración Predicha Numérica') | |
ax_original.set_ylabel(eje_y_original if eje_y_original else f'Concentración Real Promedio ({unidad_medida})') | |
ax_original.set_title(titulo_grafico_original if titulo_grafico_original else 'Regresión Lineal: Concentración Real vs Concentración Predicha (Original)') | |
# Posicionar la leyenda según la opción seleccionada (por defecto 'lower right') | |
ax_original.legend(loc=legend_location) | |
# Añadir ecuación y R² en el gráfico | |
ax_original.annotate( | |
f'y = {intercept_all:.4f} + {slope_all:.4f}x\n$R^2$ = {r_value_all**2:.4f}', | |
xy=(0.05, 0.95), | |
xycoords='axes fraction', | |
fontsize=12, | |
backgroundcolor='white', | |
verticalalignment='top' | |
) | |
# Generar gráfico personalizado | |
sns.set(style="whitegrid") | |
fig_personalizado, ax_personalizado = plt.subplots(figsize=(8, 6)) | |
# Obtener colores de las paletas | |
colors_puntos = sns.color_palette(palette_puntos, as_cmap=False) | |
colors_linea_ajuste = sns.color_palette(palette_linea_ajuste, as_cmap=False) | |
color_puntos = colors_puntos[0] | |
color_linea_ajuste = colors_linea_ajuste[0] | |
if mostrar_puntos: | |
ax_personalizado.errorbar( | |
df_valid[col_concentracion], | |
df_valid[col_absorbancia], | |
yerr=df_valid[col_desviacion], | |
fmt=estilo_puntos, | |
color=color_puntos, | |
ecolor='gray', | |
elinewidth=1, | |
capsize=3, | |
label='Datos' | |
) | |
if mostrar_linea_ajuste: | |
ax_personalizado.plot( | |
df_valid[col_concentracion], | |
intercept + slope * df_valid[col_concentracion], | |
color=color_linea_ajuste, | |
linestyle=estilo_linea_ajuste, | |
label='Ajuste Lineal' | |
) | |
# Título y etiquetas personalizadas para el gráfico personalizado | |
ax_personalizado.set_xlabel(eje_x_personalizado if eje_x_personalizado else 'Concentración Predicha Numérica') | |
ax_personalizado.set_ylabel(eje_y_personalizado if eje_y_personalizado else f'Concentración Real Promedio ({unidad_medida})') | |
ax_personalizado.set_title(titulo_grafico_personalizado if titulo_grafico_personalizado else 'Regresión Lineal Personalizada') | |
# Posicionar la leyenda según la opción seleccionada | |
ax_personalizado.legend(loc=legend_location) | |
# Añadir ecuación y R² en el gráfico | |
ax_personalizado.annotate( | |
f'y = {intercept:.4f} + {slope:.4f}x\n$R^2$ = {r_value**2:.4f}', | |
xy=(0.05, 0.95), | |
xycoords='axes fraction', | |
fontsize=12, | |
backgroundcolor='white', | |
verticalalignment='top' | |
) | |
# Crear tabla resumida | |
df_resumen = df_valid[[col_concentracion, col_absorbancia, col_desviacion]].copy() | |
df_resumen.columns = ['Concentración Predicha', 'Absorbancia Promedio', 'Desviación Estándar'] | |
return "Regresión calculada exitosamente", fig_original, fig_personalizado, df_resumen | |
# Función corregida para actualizar las opciones de filas | |
def actualizar_opciones_filas(df): | |
if df is None or df.empty: | |
update = gr.update(choices=[], value=[]) | |
else: | |
opciones = [f"Fila {i+1}" for i in df.index] | |
update = gr.update(choices=opciones, value=opciones) | |
return update, update | |
# Interfaz Gradio | |
with gr.Blocks(theme=gr.themes.Soft()) as interfaz: | |
gr.Markdown(""" | |
# 📊 Sistema Avanzado de Calibración con Análisis Estadístico | |
Configure los parámetros, edite los valores en la tabla y luego presione "Calcular" para obtener el análisis. | |
""") | |
with gr.Tab("📝 Datos de Calibración"): | |
with gr.Row(): | |
concentracion_input = gr.Number( | |
value=2000000, | |
label="Concentración Inicial", | |
precision=0 | |
) | |
unidad_input = gr.Textbox( | |
value="UFC", | |
label="Unidad de Medida", | |
placeholder="UFC, OD, etc..." | |
) | |
filas_slider = gr.Slider( | |
minimum=1, | |
maximum=20, | |
value=7, | |
step=1, | |
label="Número de filas" | |
) | |
decimales_slider = gr.Slider( | |
minimum=0, | |
maximum=5, | |
value=3, | |
step=1, | |
label="Número de Decimales" | |
) | |
replicas_slider = gr.Slider( | |
minimum=1, | |
maximum=10, | |
value=1, | |
step=1, | |
label="Número de Réplicas" | |
) | |
with gr.Row(): | |
calcular_btn = gr.Button("🔄 Calcular", variant="primary") | |
limpiar_btn = gr.Button("🗑 Limpiar Datos", variant="secondary") | |
ajustar_decimales_btn = gr.Button("🛠 Ajustar Decimales", variant="secondary") | |
with gr.Row(): | |
ejemplo_ufc_btn = gr.Button("📋 Cargar Ejemplo UFC", variant="secondary") | |
ejemplo_od_btn = gr.Button("📋 Cargar Ejemplo OD", variant="secondary") | |
sinteticos_btn = gr.Button("🧪 Generar Datos Sintéticos", variant="secondary") | |
cargar_excel_btn = gr.UploadButton("📂 Cargar Excel", file_types=[".xlsx"], variant="secondary") | |
tabla_output = gr.DataFrame( | |
wrap=True, | |
label="Tabla de Datos", | |
interactive=True, | |
type="pandas", | |
) | |
with gr.Tab("📊 Análisis y Reporte"): | |
estado_output = gr.Textbox(label="Estado", interactive=False) | |
graficos_output = gr.Plot(label="Gráficos de Análisis") | |
# Reemplazar Multiselect por CheckboxGroup | |
filas_seleccionadas = gr.CheckboxGroup( | |
label="Seleccione las filas a incluir en el análisis", | |
choices=[], | |
value=[], | |
) | |
# Opciones y botones debajo del gráfico | |
with gr.Row(): | |
# Paletas de colores disponibles en Seaborn | |
paletas_colores = ["deep", "muted", "pastel", "bright", "dark", "colorblind", "Set1", "Set2", "Set3"] | |
palette_puntos_dropdown = gr.Dropdown( | |
choices=paletas_colores, | |
value="deep", | |
label="Paleta para Puntos" | |
) | |
estilo_puntos_dropdown = gr.Dropdown( | |
choices=["o", "s", "^", "D", "v", "<", ">", "h", "H", "p", "*", "X", "d"], | |
value="o", | |
label="Estilo de Puntos" | |
) | |
palette_linea_ajuste_dropdown = gr.Dropdown( | |
choices=paletas_colores, | |
value="muted", | |
label="Paleta Línea de Ajuste" | |
) | |
estilo_linea_ajuste_dropdown = gr.Dropdown( | |
choices=["-", "--", "-.", ":"], | |
value="-", | |
label="Estilo Línea de Ajuste" | |
) | |
with gr.Row(): | |
palette_linea_ideal_dropdown = gr.Dropdown( | |
choices=paletas_colores, | |
value="bright", | |
label="Paleta Línea Ideal" | |
) | |
estilo_linea_ideal_dropdown = gr.Dropdown( | |
choices=["--", "-", "-.", ":"], | |
value="--", | |
label="Estilo Línea Ideal" | |
) | |
palette_barras_error_dropdown = gr.Dropdown( | |
choices=paletas_colores, | |
value="pastel", | |
label="Paleta Barras de Error" | |
) | |
mostrar_linea_ajuste = gr.Checkbox(value=True, label="Mostrar Línea de Ajuste") | |
mostrar_linea_ideal = gr.Checkbox(value=False, label="Mostrar Línea Ideal") # Desmarcado por defecto | |
mostrar_puntos = gr.Checkbox(value=True, label="Mostrar Puntos") | |
graficar_btn = gr.Button("📊 Graficar", variant="primary") | |
with gr.Row(): | |
copiar_btn = gr.Button("📋 Copiar Informe", variant="secondary") | |
exportar_word_btn = gr.Button("💾 Exportar Informe Word", variant="primary") | |
exportar_latex_btn = gr.Button("💾 Exportar Informe LaTeX", variant="primary") | |
with gr.Row(): | |
exportar_word_file = gr.File(label="Informe en Word") | |
exportar_latex_file = gr.File(label="Informe en LaTeX") | |
# Informe al final | |
informe_output = gr.Markdown(elem_id="informe_output") | |
with gr.Tab("📈 Regresión Absorbancia vs Concentración"): | |
gr.Markdown("## Ajuste de Regresión utilizando datos de la Tabla Principal") | |
# Casillas para seleccionar filas | |
filas_seleccionadas_regresion = gr.CheckboxGroup( | |
label="Seleccione las filas a incluir en el análisis de regresión", | |
choices=[], | |
value=[], | |
) | |
# Opciones de personalización | |
with gr.Row(): | |
paletas_colores = ["deep", "muted", "pastel", "bright", "dark", "colorblind", "Set1", "Set2", "Set3"] | |
palette_puntos_regresion = gr.Dropdown( | |
choices=paletas_colores, | |
value="deep", | |
label="Paleta para Puntos" | |
) | |
estilo_puntos_regresion = gr.Dropdown( | |
choices=["o", "s", "^", "D", "v", "<", ">", "h", "H", "p", "*", "X", "d"], | |
value="o", | |
label="Estilo de Puntos" | |
) | |
palette_linea_ajuste_regresion = gr.Dropdown( | |
choices=paletas_colores, | |
value="muted", | |
label="Paleta Línea de Ajuste" | |
) | |
estilo_linea_ajuste_regresion = gr.Dropdown( | |
choices=["-", "--", "-.", ":"], | |
value="-", | |
label="Estilo Línea de Ajuste" | |
) | |
mostrar_linea_ajuste_regresion = gr.Checkbox(value=True, label="Mostrar Línea de Ajuste") | |
mostrar_puntos_regresion = gr.Checkbox(value=True, label="Mostrar Puntos") | |
with gr.Row(): | |
legend_location_dropdown = gr.Dropdown( | |
choices=[ | |
'best', 'upper right', 'upper left', 'lower left', 'lower right', | |
'right', 'center left', 'center right', 'lower center', | |
'upper center', 'center' | |
], | |
value='lower right', # Por defecto 'lower right' | |
label='Ubicación de la Leyenda' | |
) | |
# Campos de texto para personalizar título y ejes | |
with gr.Row(): | |
titulo_grafico_original = gr.Textbox( | |
label="Título del Gráfico Original", | |
placeholder="Regresión Lineal: Concentración Real vs Concentración Predicha (Original)" | |
) | |
titulo_grafico_personalizado = gr.Textbox( | |
label="Título del Gráfico Personalizado", | |
placeholder="Regresión Lineal Personalizada" | |
) | |
with gr.Row(): | |
eje_x_original = gr.Textbox( | |
label="Etiqueta del Eje X (Gráfico Original)", | |
placeholder="Concentración Predicha Numérica" | |
) | |
eje_y_original = gr.Textbox( | |
label="Etiqueta del Eje Y (Gráfico Original)", | |
placeholder=f"Concentración Real Promedio ({unidad_input.value})" | |
) | |
with gr.Row(): | |
eje_x_personalizado = gr.Textbox( | |
label="Etiqueta del Eje X (Gráfico Personalizado)", | |
placeholder="Concentración Predicha Numérica" | |
) | |
eje_y_personalizado = gr.Textbox( | |
label="Etiqueta del Eje Y (Gráfico Personalizado)", | |
placeholder=f"Concentración Real Promedio ({unidad_input.value})" | |
) | |
calcular_regresion_btn = gr.Button("Calcular Regresión") | |
# Salidas | |
estado_regresion_output = gr.Textbox(label="Estado de la Regresión", interactive=False) | |
grafico_original_output = gr.Plot(label="Gráfico Original") | |
grafico_personalizado_output = gr.Plot(label="Gráfico Personalizado") | |
tabla_resumen_output = gr.DataFrame(label="Tabla Resumida") | |
# Eventos para actualizar las opciones de filas | |
tabla_output.change( | |
fn=actualizar_opciones_filas, | |
inputs=[tabla_output], | |
outputs=[filas_seleccionadas, filas_seleccionadas_regresion] | |
) | |
# Evento al presionar el botón Calcular | |
calcular_btn.click( | |
fn=actualizar_analisis, | |
inputs=[tabla_output, replicas_slider, unidad_input, filas_seleccionadas, decimales_slider], | |
outputs=[estado_output, graficos_output, informe_output, tabla_output] | |
) | |
# Evento para graficar con opciones seleccionadas | |
graficar_btn.click( | |
fn=actualizar_graficos, | |
inputs=[ | |
tabla_output, replicas_slider, unidad_input, | |
palette_puntos_dropdown, estilo_puntos_dropdown, | |
palette_linea_ajuste_dropdown, estilo_linea_ajuste_dropdown, | |
palette_linea_ideal_dropdown, estilo_linea_ideal_dropdown, | |
palette_barras_error_dropdown, | |
mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos, | |
filas_seleccionadas, decimales_slider | |
], | |
outputs=graficos_output | |
) | |
# Asegurar que la línea ideal esté desmarcada por defecto | |
def resetear_linea_ideal(): | |
return gr.update(value=False) | |
# Desmarcar 'Mostrar Línea Ideal' en eventos de botones | |
calcular_btn.click( | |
fn=resetear_linea_ideal, | |
outputs=mostrar_linea_ideal | |
) | |
limpiar_btn.click( | |
fn=resetear_linea_ideal, | |
outputs=mostrar_linea_ideal | |
) | |
ajustar_decimales_btn.click( | |
fn=resetear_linea_ideal, | |
outputs=mostrar_linea_ideal | |
) | |
sinteticos_btn.click( | |
fn=resetear_linea_ideal, | |
outputs=mostrar_linea_ideal | |
) | |
# Eventos de los botones adicionales, como limpiar, cargar ejemplos, ajustar decimales, etc. | |
# Evento para limpiar datos | |
limpiar_btn.click( | |
fn=limpiar_datos, | |
inputs=[replicas_slider], | |
outputs=[concentracion_input, unidad_input, filas_slider, tabla_output, estado_output, graficos_output, informe_output] | |
) | |
# Eventos de los botones de ejemplo | |
ejemplo_ufc_btn.click( | |
fn=cargar_ejemplo_ufc, | |
inputs=[replicas_slider], | |
outputs=[concentracion_input, unidad_input, filas_slider, tabla_output] | |
) | |
ejemplo_od_btn.click( | |
fn=cargar_ejemplo_od, | |
inputs=[replicas_slider], | |
outputs=[concentracion_input, unidad_input, filas_slider, tabla_output] | |
) | |
# Evento para generar datos sintéticos | |
sinteticos_btn.click( | |
fn=generar_datos_sinteticos_evento, | |
inputs=[tabla_output, replicas_slider, unidad_input], | |
outputs=tabla_output | |
) | |
# Evento para cargar archivo Excel | |
cargar_excel_btn.upload( | |
fn=cargar_excel, | |
inputs=[cargar_excel_btn], | |
outputs=[concentracion_input, unidad_input, filas_slider, replicas_slider, tabla_output, estado_output, graficos_output, informe_output] | |
) | |
# Evento al presionar el botón Ajustar Decimales | |
ajustar_decimales_btn.click( | |
fn=ajustar_decimales_evento, | |
inputs=[tabla_output, decimales_slider], | |
outputs=tabla_output | |
) | |
# Actualizar tabla al cambiar los parámetros (sin borrar "Concentración Real") | |
def actualizar_tabla_wrapper(df, filas, conc, unidad, replicas, decimales): | |
return actualizar_tabla_evento(df, filas, conc, unidad, replicas, decimales) | |
concentracion_input.change( | |
fn=actualizar_tabla_wrapper, | |
inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider, decimales_slider], | |
outputs=tabla_output | |
) | |
unidad_input.change( | |
fn=actualizar_tabla_wrapper, | |
inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider, decimales_slider], | |
outputs=tabla_output | |
) | |
filas_slider.change( | |
fn=actualizar_tabla_wrapper, | |
inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider, decimales_slider], | |
outputs=tabla_output | |
) | |
replicas_slider.change( | |
fn=actualizar_tabla_wrapper, | |
inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider, decimales_slider], | |
outputs=tabla_output | |
) | |
decimales_slider.change( | |
fn=ajustar_decimales_evento, | |
inputs=[tabla_output, decimales_slider], | |
outputs=tabla_output | |
) | |
# Evento de copiar informe utilizando JavaScript | |
copiar_btn.click( | |
None, | |
[], | |
[], | |
js=""" | |
function() { | |
const informeElement = document.querySelector('#informe_output'); | |
const range = document.createRange(); | |
range.selectNode(informeElement); | |
window.getSelection().removeAllRanges(); | |
window.getSelection().addRange(range); | |
document.execCommand('copy'); | |
window.getSelection().removeAllRanges(); | |
alert('Informe copiado al portapapeles'); | |
} | |
""" | |
) | |
# Eventos de exportar informes | |
exportar_word_btn.click( | |
fn=exportar_word, | |
inputs=[tabla_output, informe_output, unidad_input, filas_seleccionadas], | |
outputs=exportar_word_file | |
) | |
exportar_latex_btn.click( | |
fn=exportar_latex, | |
inputs=[tabla_output, informe_output, filas_seleccionadas], | |
outputs=exportar_latex_file | |
) | |
# Inicializar la interfaz con el ejemplo base | |
def iniciar_con_ejemplo(): | |
n_replicas = 1 | |
df = generar_tabla(7, 2000000, "UFC", n_replicas) | |
# Valores reales de ejemplo | |
df[f"Concentración Real 1 (UFC)"] = [2000000, 1600000, 1200000, 800000, 400000, 200000, 100000] | |
# Calcular promedio y desviación estándar | |
df = calcular_promedio_desviacion(df, n_replicas, "UFC", 3) | |
filas_seleccionadas_inicial = [f"Fila {i+1}" for i in df.index] | |
estado, fig, informe, df = actualizar_analisis(df, n_replicas, "UFC", filas_seleccionadas_inicial, 3) | |
return ( | |
2000000, | |
"UFC", | |
7, | |
df, | |
estado, | |
fig, | |
informe, | |
filas_seleccionadas_inicial, | |
3 # Número de decimales | |
) | |
interfaz.load( | |
fn=iniciar_con_ejemplo, | |
outputs=[concentracion_input, unidad_input, filas_slider, tabla_output, estado_output, graficos_output, informe_output, filas_seleccionadas, decimales_slider] | |
) | |
# Evento al presionar el botón de calcular regresión | |
calcular_regresion_btn.click( | |
fn=calcular_regresion_tabla_principal, | |
inputs=[ | |
tabla_output, unidad_input, filas_seleccionadas_regresion, | |
palette_puntos_regresion, estilo_puntos_regresion, | |
palette_linea_ajuste_regresion, estilo_linea_ajuste_regresion, | |
mostrar_linea_ajuste_regresion, mostrar_puntos_regresion, | |
legend_location_dropdown, decimales_slider, | |
titulo_grafico_original, titulo_grafico_personalizado, | |
eje_x_original, eje_y_original, | |
eje_x_personalizado, eje_y_personalizado | |
], | |
outputs=[estado_regresion_output, grafico_original_output, grafico_personalizado_output, tabla_resumen_output] | |
) | |
# Lanzar la interfaz | |
if __name__ == "__main__": | |
interfaz.launch() | |