C2MV commited on
Commit
fb288b0
1 Parent(s): 17c7220

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +680 -27
app.py CHANGED
@@ -45,11 +45,6 @@ def generar_tabla(n_filas, concentracion_inicial, unidad_medida, n_replicas):
45
  # Las columnas de promedio y desviación estándar se agregarán durante el análisis
46
  return df
47
 
48
- # Aquí deben estar definidas todas las funciones necesarias
49
- # Por motivos de espacio y para mantener la claridad, me centraré en la estructura general
50
- # y en asegurar que no haya bloques vacíos que causen errores de indentación
51
-
52
- # Funciones auxiliares
53
  def ajustar_decimales_evento(df, decimales):
54
  df = df.copy()
55
  # Ajustar decimales en todas las columnas numéricas
@@ -62,21 +57,262 @@ def ajustar_decimales_evento(df, decimales):
62
  return df
63
 
64
  def calcular_promedio_desviacion(df, n_replicas, unidad_medida):
65
- # Código para calcular promedio y desviación estándar
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  return df
67
 
68
  def generar_graficos(df_valid, n_replicas, unidad_medida, palette_puntos, estilo_puntos,
69
  palette_linea_ajuste, estilo_linea_ajuste,
70
  palette_linea_ideal, estilo_linea_ideal,
71
  palette_barras_error, mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos):
72
- # Código para generar gráficos
73
- return plt.figure()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
  def actualizar_analisis(df, n_replicas, unidad_medida, filas_seleccionadas):
76
- # Código para actualizar el análisis
77
- estado = "Análisis actualizado"
78
- fig = plt.figure()
79
- informe = "Informe generado"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  return estado, fig, informe, df
81
 
82
  def actualizar_graficos(df, n_replicas, unidad_medida,
@@ -86,32 +322,290 @@ def actualizar_graficos(df, n_replicas, unidad_medida,
86
  palette_barras_error,
87
  mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos,
88
  filas_seleccionadas):
89
- # Código para actualizar gráficos
90
- return plt.figure()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
  def cargar_ejemplo_ufc(n_replicas):
93
- # Código para cargar ejemplo UFC
94
  df = generar_tabla(7, 2000000, "UFC", n_replicas)
 
 
 
 
 
 
95
  return 2000000, "UFC", 7, df
96
 
97
  def cargar_ejemplo_od(n_replicas):
98
- # Código para cargar ejemplo OD
99
  df = generar_tabla(7, 1.0, "OD", n_replicas)
 
 
 
 
 
 
100
  return 1.0, "OD", 7, df
101
 
102
  def limpiar_datos(n_replicas):
103
- # Código para limpiar datos
104
  df = generar_tabla(7, 2000000, "UFC", n_replicas)
105
- return 2000000, "UFC", 7, df, "", None, ""
 
 
 
 
 
 
 
 
106
 
107
  def generar_datos_sinteticos_evento(df, n_replicas, unidad_medida):
108
- # Código para generar datos sintéticos
109
- return df
 
 
 
 
 
 
 
 
 
 
 
110
 
111
- def actualizar_tabla_evento(df, filas, conc, unidad, replicas):
112
- # Código para actualizar la tabla sin borrar datos existentes
113
  return df
114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  def actualizar_opciones_filas(df):
116
  if df is None or df.empty:
117
  return gr.update(choices=[], value=[])
@@ -130,10 +624,41 @@ def calcular_regresion(df, columna_conc, columna_abs):
130
  if df is None or df.empty or not columna_conc or not columna_abs:
131
  return "Datos insuficientes", None
132
 
133
- # Implementación de la regresión
134
- estado = "Regresión calculada exitosamente"
135
- fig = plt.figure()
136
- return estado, fig
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
  # Interfaz Gradio
139
  with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
@@ -305,7 +830,135 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
305
  outputs=[estado_regresion_output, grafico_regresion_output]
306
  )
307
 
308
- # Agregar aquí los demás eventos necesarios
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
 
310
  # Lanzar la interfaz
311
  if __name__ == "__main__":
 
45
  # Las columnas de promedio y desviación estándar se agregarán durante el análisis
46
  return df
47
 
 
 
 
 
 
48
  def ajustar_decimales_evento(df, decimales):
49
  df = df.copy()
50
  # Ajustar decimales en todas las columnas numéricas
 
57
  return df
58
 
59
  def calcular_promedio_desviacion(df, n_replicas, unidad_medida):
60
+ df = df.copy()
61
+ # Obtener las columnas de réplicas
62
+ col_replicas = [f"Concentración Real {i} ({unidad_medida})" for i in range(1, n_replicas + 1)]
63
+ # Convertir a numérico
64
+ for col in col_replicas:
65
+ df[col] = pd.to_numeric(df[col], errors='coerce')
66
+
67
+ # Calcular el promedio y la desviación estándar
68
+ df[f"Concentración Real Promedio ({unidad_medida})"] = df[col_replicas].mean(axis=1)
69
+
70
+ if n_replicas > 1:
71
+ df[f"Desviación Estándar ({unidad_medida})"] = df[col_replicas].std(ddof=1, axis=1)
72
+ else:
73
+ df[f"Desviación Estándar ({unidad_medida})"] = 0.0
74
+
75
  return df
76
 
77
  def generar_graficos(df_valid, n_replicas, unidad_medida, palette_puntos, estilo_puntos,
78
  palette_linea_ajuste, estilo_linea_ajuste,
79
  palette_linea_ideal, estilo_linea_ideal,
80
  palette_barras_error, mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos):
81
+ col_predicha_num = "Concentración Predicha Numérica"
82
+ col_real_promedio = f"Concentración Real Promedio ({unidad_medida})"
83
+ col_desviacion = f"Desviación Estándar ({unidad_medida})"
84
+
85
+ # Convertir a numérico
86
+ df_valid[col_predicha_num] = df_valid[col_predicha_num].astype(float)
87
+ df_valid[col_real_promedio] = df_valid[col_real_promedio].astype(float)
88
+ df_valid[col_desviacion] = df_valid[col_desviacion].fillna(0).astype(float)
89
+
90
+ # Calcular regresión lineal
91
+ slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha_num], df_valid[col_real_promedio])
92
+ df_valid['Ajuste Lineal'] = intercept + slope * df_valid[col_predicha_num]
93
+
94
+ # Configurar estilos
95
+ sns.set(style="whitegrid")
96
+ plt.rcParams.update({'figure.autolayout': True})
97
+
98
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
99
+
100
+ # Obtener colores de las paletas
101
+ colors_puntos = sns.color_palette(palette_puntos, as_cmap=False)
102
+ colors_linea_ajuste = sns.color_palette(palette_linea_ajuste, as_cmap=False)
103
+ colors_linea_ideal = sns.color_palette(palette_linea_ideal, as_cmap=False)
104
+ colors_barras_error = sns.color_palette(palette_barras_error, as_cmap=False)
105
+
106
+ # Seleccionar colores
107
+ color_puntos = colors_puntos[0]
108
+ color_linea_ajuste = colors_linea_ajuste[0]
109
+ color_linea_ideal = colors_linea_ideal[0]
110
+ color_barras_error = colors_barras_error[0]
111
+
112
+ # Gráfico de dispersión con línea de regresión
113
+ if mostrar_puntos:
114
+ if n_replicas > 1:
115
+ # Incluir barras de error
116
+ ax1.errorbar(
117
+ df_valid[col_predicha_num],
118
+ df_valid[col_real_promedio],
119
+ yerr=df_valid[col_desviacion],
120
+ fmt=estilo_puntos,
121
+ color=color_puntos,
122
+ ecolor=color_barras_error,
123
+ elinewidth=2,
124
+ capsize=3,
125
+ label='Datos Reales'
126
+ )
127
+ else:
128
+ ax1.scatter(
129
+ df_valid[col_predicha_num],
130
+ df_valid[col_real_promedio],
131
+ color=color_puntos,
132
+ s=100,
133
+ label='Datos Reales',
134
+ marker=estilo_puntos
135
+ )
136
+
137
+ # Línea de ajuste
138
+ if mostrar_linea_ajuste:
139
+ ax1.plot(
140
+ df_valid[col_predicha_num],
141
+ df_valid['Ajuste Lineal'],
142
+ color=color_linea_ajuste,
143
+ label='Ajuste Lineal',
144
+ linewidth=2,
145
+ linestyle=estilo_linea_ajuste
146
+ )
147
+
148
+ # Línea ideal
149
+ if mostrar_linea_ideal:
150
+ min_predicha = df_valid[col_predicha_num].min()
151
+ max_predicha = df_valid[col_predicha_num].max()
152
+ ax1.plot(
153
+ [min_predicha, max_predicha],
154
+ [min_predicha, max_predicha],
155
+ color=color_linea_ideal,
156
+ linestyle=estilo_linea_ideal,
157
+ label='Ideal'
158
+ )
159
+
160
+ ax1.set_title('Correlación entre Concentración Predicha y Real', fontsize=14)
161
+ ax1.set_xlabel('Concentración Predicha', fontsize=12)
162
+ ax1.set_ylabel('Concentración Real Promedio', fontsize=12)
163
+
164
+ # Añadir ecuación y R² en el gráfico
165
+ ax1.annotate(
166
+ f'y = {intercept:.2f} + {slope:.2f}x\n$R^2$ = {r_value**2:.4f}',
167
+ xy=(0.05, 0.95),
168
+ xycoords='axes fraction',
169
+ fontsize=12,
170
+ backgroundcolor='white',
171
+ verticalalignment='top'
172
+ )
173
+
174
+ # Posicionar la leyenda
175
+ ax1.legend(loc='lower right', fontsize=10)
176
+
177
+ # Gráfico de residuos
178
+ residuos = df_valid[col_real_promedio] - df_valid['Ajuste Lineal']
179
+ ax2.scatter(
180
+ df_valid[col_predicha_num],
181
+ residuos,
182
+ color=color_puntos,
183
+ s=100,
184
+ marker=estilo_puntos,
185
+ label='Residuos'
186
+ )
187
+
188
+ ax2.axhline(y=0, color='black', linestyle='--', linewidth=1)
189
+ ax2.set_title('Gráfico de Residuos', fontsize=14)
190
+ ax2.set_xlabel('Concentración Predicha', fontsize=12)
191
+ ax2.set_ylabel('Residuo', fontsize=12)
192
+ ax2.legend(loc='upper right', fontsize=10)
193
+
194
+ plt.tight_layout()
195
+ plt.savefig('grafico.png') # Guardar el gráfico para incluirlo en el informe
196
+ return fig
197
+
198
+ def evaluar_calidad_calibracion(df_valid, r_squared, rmse, cv_percent):
199
+ """Evaluar la calidad de la calibración y proporcionar recomendaciones"""
200
+ evaluacion = {
201
+ "calidad": "",
202
+ "recomendaciones": [],
203
+ "estado": "✅" if r_squared >= 0.95 and cv_percent <= 15 else "⚠️"
204
+ }
205
+
206
+ if r_squared >= 0.95:
207
+ evaluacion["calidad"] = "Excelente"
208
+ elif r_squared >= 0.90:
209
+ evaluacion["calidad"] = "Buena"
210
+ elif r_squared >= 0.85:
211
+ evaluacion["calidad"] = "Regular"
212
+ else:
213
+ evaluacion["calidad"] = "Deficiente"
214
+
215
+ if r_squared < 0.95:
216
+ evaluacion["recomendaciones"].append("- Considere repetir algunas mediciones para mejorar la correlación")
217
+
218
+ if cv_percent > 15:
219
+ evaluacion["recomendaciones"].append("- La variabilidad es alta. Revise el procedimiento de dilución")
220
+
221
+ if rmse > 0.1 * df_valid[df_valid.columns[-1]].astype(float).mean():
222
+ evaluacion["recomendaciones"].append("- El error de predicción es significativo. Verifique la técnica de medición")
223
+
224
+ return evaluacion
225
+
226
+ def generar_informe_completo(df_valid, n_replicas, unidad_medida):
227
+ """Generar un informe completo en formato markdown"""
228
+ col_predicha_num = "Concentración Predicha Numérica"
229
+ col_real_promedio = f"Concentración Real Promedio ({unidad_medida})"
230
+
231
+ # Convertir a numérico
232
+ df_valid[col_predicha_num] = df_valid[col_predicha_num].astype(float)
233
+ df_valid[col_real_promedio] = df_valid[col_real_promedio].astype(float)
234
+
235
+ # Calcular estadísticas
236
+ slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha_num], df_valid[col_real_promedio])
237
+ r_squared = r_value ** 2
238
+ rmse = np.sqrt(((df_valid[col_real_promedio] - (intercept + slope * df_valid[col_predicha_num])) ** 2).mean())
239
+ cv = (df_valid[col_real_promedio].std() / df_valid[col_real_promedio].mean()) * 100 # CV de los valores reales
240
+
241
+ # Evaluar calidad
242
+ evaluacion = evaluar_calidad_calibracion(df_valid, r_squared, rmse, cv)
243
+
244
+ informe = f"""# Informe de Calibración {evaluacion['estado']}
245
+ Fecha: {datetime.now().strftime('%d/%m/%Y %H:%M')}
246
+
247
+ ## Resumen Estadístico
248
+ - **Ecuación de Regresión**: y = {intercept:.4f} + {slope:.4f}x
249
+ - **Coeficiente de correlación (r)**: {r_value:.4f}
250
+ - **Coeficiente de determinación ($R^2$)**: {r_squared:.4f}
251
+ - **Valor p**: {p_value:.4e}
252
+ - **Error estándar de la pendiente**: {std_err:.4f}
253
+ - **Error cuadrático medio (RMSE)**: {rmse:.4f}
254
+ - **Coeficiente de variación (CV)**: {cv:.2f}%
255
+
256
+ ## Evaluación de Calidad
257
+ - **Calidad de la calibración**: {evaluacion['calidad']}
258
+
259
+ ## Recomendaciones
260
+ {chr(10).join(evaluacion['recomendaciones']) if evaluacion['recomendaciones'] else "No hay recomendaciones específicas. La calibración cumple con los criterios de calidad."}
261
+
262
+ ## Decisión
263
+ {("✅ 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")}
264
+
265
+ ---
266
+ *Nota: Este informe fue generado automáticamente. Por favor, revise los resultados y valide según sus criterios específicos.*
267
+ """
268
+ return informe, evaluacion['estado']
269
 
270
  def actualizar_analisis(df, n_replicas, unidad_medida, filas_seleccionadas):
271
+ if df is None or df.empty:
272
+ return "Error en los datos", None, "No se pueden generar análisis", df
273
+
274
+ # Convertir filas_seleccionadas a índices
275
+ if not filas_seleccionadas:
276
+ return "Se necesitan más datos", None, "No se han seleccionado filas para el análisis", df
277
+
278
+ indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas]
279
+
280
+ # Calcular promedio y desviación estándar dependiendo de las réplicas
281
+ df = calcular_promedio_desviacion(df, n_replicas, unidad_medida)
282
+
283
+ col_predicha_num = "Concentración Predicha Numérica"
284
+ col_real_promedio = f"Concentración Real Promedio ({unidad_medida})"
285
+
286
+ # Convertir columnas a numérico
287
+ df[col_predicha_num] = pd.to_numeric(df[col_predicha_num], errors='coerce')
288
+ df[col_real_promedio] = pd.to_numeric(df[col_real_promedio], errors='coerce')
289
+
290
+ df_valid = df.dropna(subset=[col_predicha_num, col_real_promedio])
291
+
292
+ # Resetear el índice para asegurar que sea secuencial
293
+ df_valid.reset_index(drop=True, inplace=True)
294
+
295
+ # Filtrar filas según las seleccionadas
296
+ df_valid = df_valid.loc[indices_seleccionados]
297
+
298
+ if len(df_valid) < 2:
299
+ return "Se necesitan más datos", None, "Se requieren al menos dos valores reales para el análisis", df
300
+
301
+ # Calcular la regresión y agregar 'Ajuste Lineal'
302
+ slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha_num], df_valid[col_real_promedio])
303
+ df_valid['Ajuste Lineal'] = intercept + slope * df_valid[col_predicha_num]
304
+
305
+ # Generar gráfico con opciones predeterminadas
306
+ fig = generar_graficos(
307
+ df_valid, n_replicas, unidad_medida,
308
+ palette_puntos='deep', estilo_puntos='o',
309
+ palette_linea_ajuste='muted', estilo_linea_ajuste='-',
310
+ palette_linea_ideal='bright', estilo_linea_ideal='--',
311
+ palette_barras_error='pastel',
312
+ mostrar_linea_ajuste=True, mostrar_linea_ideal=True, mostrar_puntos=True
313
+ )
314
+ informe, estado = generar_informe_completo(df_valid, n_replicas, unidad_medida)
315
+
316
  return estado, fig, informe, df
317
 
318
  def actualizar_graficos(df, n_replicas, unidad_medida,
 
322
  palette_barras_error,
323
  mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos,
324
  filas_seleccionadas):
325
+ if df is None or df.empty:
326
+ return None
327
+
328
+ # Asegurarse de que los cálculos estén actualizados
329
+ df = calcular_promedio_desviacion(df, n_replicas, unidad_medida)
330
+
331
+ col_predicha_num = "Concentración Predicha Numérica"
332
+ col_real_promedio = f"Concentración Real Promedio ({unidad_medida})"
333
+
334
+ # Convertir columnas a numérico
335
+ df[col_predicha_num] = pd.to_numeric(df[col_predicha_num], errors='coerce')
336
+ df[col_real_promedio] = pd.to_numeric(df[col_real_promedio], errors='coerce')
337
+
338
+ df_valid = df.dropna(subset=[col_predicha_num, col_real_promedio])
339
+
340
+ # Resetear el índice para asegurar que sea secuencial
341
+ df_valid.reset_index(drop=True, inplace=True)
342
+
343
+ # Convertir filas_seleccionadas a índices
344
+ if not filas_seleccionadas:
345
+ return None
346
+
347
+ indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas]
348
+
349
+ # Filtrar filas según las seleccionadas
350
+ df_valid = df_valid.loc[indices_seleccionados]
351
+
352
+ if len(df_valid) < 2:
353
+ return None
354
+
355
+ # Generar gráfico con opciones seleccionadas
356
+ fig = generar_graficos(
357
+ df_valid, n_replicas, unidad_medida,
358
+ palette_puntos, estilo_puntos,
359
+ palette_linea_ajuste, estilo_linea_ajuste,
360
+ palette_linea_ideal, estilo_linea_ideal,
361
+ palette_barras_error,
362
+ mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos
363
+ )
364
+
365
+ return fig
366
+
367
+ def exportar_informe_word(df_valid, informe_md, unidad_medida):
368
+ # Crear documento Word
369
+ doc = docx.Document()
370
+
371
+ # Estilos APA 7
372
+ style = doc.styles['Normal']
373
+ font = style.font
374
+ font.name = 'Times New Roman'
375
+ font.size = Pt(12)
376
+
377
+ # Título centrado
378
+ titulo = doc.add_heading('Informe de Calibración', 0)
379
+ titulo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
380
+
381
+ # Fecha
382
+ fecha = doc.add_paragraph(f"Fecha: {datetime.now().strftime('%d/%m/%Y %H:%M')}")
383
+ fecha.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
384
+
385
+ # Insertar gráfico
386
+ if os.path.exists('grafico.png'):
387
+ doc.add_picture('grafico.png', width=Inches(6))
388
+ ultimo_parrafo = doc.paragraphs[-1]
389
+ ultimo_parrafo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
390
+
391
+ # Leyenda del gráfico en estilo APA 7
392
+ leyenda = doc.add_paragraph('Figura 1. Gráfico de calibración.')
393
+ leyenda_format = leyenda.paragraph_format
394
+ leyenda_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
395
+ leyenda.style = doc.styles['Caption']
396
+
397
+ # Agregar contenido del informe
398
+ doc.add_heading('Resumen Estadístico', level=1)
399
+ for linea in informe_md.split('\n'):
400
+ if linea.startswith('##'):
401
+ doc.add_heading(linea.replace('##', '').strip(), level=2)
402
+ else:
403
+ doc.add_paragraph(linea)
404
+
405
+ # Añadir tabla de datos
406
+ doc.add_heading('Tabla de Datos de Calibración', level=1)
407
+
408
+ # Convertir DataFrame a lista de listas
409
+ tabla_datos = df_valid.reset_index(drop=True)
410
+ tabla_datos = tabla_datos.round(4) # Redondear a 4 decimales si es necesario
411
+ columnas = tabla_datos.columns.tolist()
412
+ registros = tabla_datos.values.tolist()
413
+
414
+ # Crear tabla en Word
415
+ tabla = doc.add_table(rows=1 + len(registros), cols=len(columnas))
416
+ tabla.style = 'Table Grid'
417
+
418
+ # Añadir los encabezados
419
+ hdr_cells = tabla.rows[0].cells
420
+ for idx, col_name in enumerate(columnas):
421
+ hdr_cells[idx].text = col_name
422
+
423
+ # Añadir los registros
424
+ for i, registro in enumerate(registros):
425
+ row_cells = tabla.rows[i + 1].cells
426
+ for j, valor in enumerate(registro):
427
+ row_cells[j].text = str(valor)
428
+
429
+ # Formatear fuente de la tabla
430
+ for row in tabla.rows:
431
+ for cell in row.cells:
432
+ for paragraph in cell.paragraphs:
433
+ paragraph.style = doc.styles['Normal']
434
+
435
+ # Guardar documento
436
+ filename = 'informe_calibracion.docx'
437
+ doc.save(filename)
438
+ return filename
439
+
440
+ def exportar_informe_latex(df_valid, informe_md):
441
+ # Generar código LaTeX
442
+ informe_tex = r"""\documentclass{article}
443
+ \usepackage[spanish]{babel}
444
+ \usepackage{amsmath}
445
+ \usepackage{graphicx}
446
+ \usepackage{booktabs}
447
+ \begin{document}
448
+ """
449
+ informe_tex += informe_md.replace('#', '').replace('**', '\\textbf{').replace('*', '\\textit{')
450
+ informe_tex += r"""
451
+ \end{document}
452
+ """
453
+ filename = 'informe_calibracion.tex'
454
+ with open(filename, 'w') as f:
455
+ f.write(informe_tex)
456
+ return filename
457
+
458
+ def exportar_word(df, informe_md, unidad_medida, filas_seleccionadas):
459
+ df_valid = df.copy()
460
+ col_predicha_num = "Concentración Predicha Numérica"
461
+ col_real_promedio = f"Concentración Real Promedio ({unidad_medida})"
462
+
463
+ # Convertir columnas a numérico
464
+ df_valid[col_predicha_num] = pd.to_numeric(df_valid[col_predicha_num], errors='coerce')
465
+ df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce')
466
+
467
+ df_valid = df_valid.dropna(subset=[col_predicha_num, col_real_promedio])
468
+
469
+ # Resetear el índice
470
+ df_valid.reset_index(drop=True, inplace=True)
471
+
472
+ # Convertir filas_seleccionadas a índices
473
+ if not filas_seleccionadas:
474
+ return None
475
+
476
+ indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas]
477
+
478
+ # Filtrar filas según las seleccionadas
479
+ df_valid = df_valid.loc[indices_seleccionados]
480
+
481
+ if df_valid.empty:
482
+ return None
483
+
484
+ filename = exportar_informe_word(df_valid, informe_md, unidad_medida)
485
+
486
+ return filename # Retornamos el nombre del archivo
487
+
488
+ def exportar_latex(df, informe_md, filas_seleccionadas):
489
+ df_valid = df.copy()
490
+ col_predicha_num = "Concentración Predicha Numérica"
491
+ col_real_promedio = [col for col in df_valid.columns if 'Real Promedio' in col][0]
492
+
493
+ # Convertir columnas a numérico
494
+ df_valid[col_predicha_num] = pd.to_numeric(df_valid[col_predicha_num], errors='coerce')
495
+ df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce')
496
+
497
+ df_valid = df_valid.dropna(subset=[col_predicha_num, col_real_promedio])
498
+
499
+ # Resetear el índice
500
+ df_valid.reset_index(drop=True, inplace=True)
501
+
502
+ # Convertir filas_seleccionadas a índices
503
+ if not filas_seleccionadas:
504
+ return None
505
+
506
+ indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas]
507
+
508
+ # Filtrar filas según las seleccionadas
509
+ df_valid = df_valid.loc[indices_seleccionados]
510
+
511
+ if df_valid.empty:
512
+ return None
513
+
514
+ filename = exportar_informe_latex(df_valid, informe_md)
515
+
516
+ return filename # Retornamos el nombre del archivo
517
 
518
  def cargar_ejemplo_ufc(n_replicas):
 
519
  df = generar_tabla(7, 2000000, "UFC", n_replicas)
520
+ # Valores reales de ejemplo
521
+ for i in range(1, n_replicas + 1):
522
+ valores_reales = [2000000 - (i - 1) * 10000, 1600000 - (i - 1) * 8000, 1200000 - (i - 1) * 6000,
523
+ 800000 - (i - 1) * 4000, 400000 - (i - 1) * 2000, 200000 - (i - 1) * 1000,
524
+ 100000 - (i - 1) * 500]
525
+ df[f"Concentración Real {i} (UFC)"] = valores_reales
526
  return 2000000, "UFC", 7, df
527
 
528
  def cargar_ejemplo_od(n_replicas):
 
529
  df = generar_tabla(7, 1.0, "OD", n_replicas)
530
+ # Valores reales de ejemplo
531
+ for i in range(1, n_replicas + 1):
532
+ valores_reales = [1.00 - (i - 1) * 0.05, 0.80 - (i - 1) * 0.04, 0.60 - (i - 1) * 0.03,
533
+ 0.40 - (i - 1) * 0.02, 0.20 - (i - 1) * 0.01, 0.10 - (i - 1) * 0.005,
534
+ 0.05 - (i - 1) * 0.002]
535
+ df[f"Concentración Real {i} (OD)"] = valores_reales
536
  return 1.0, "OD", 7, df
537
 
538
  def limpiar_datos(n_replicas):
 
539
  df = generar_tabla(7, 2000000, "UFC", n_replicas)
540
+ return (
541
+ 2000000, # Concentración Inicial
542
+ "UFC", # Unidad de Medida
543
+ 7, # Número de filas
544
+ df, # Tabla Output
545
+ "", # Estado Output
546
+ None, # Gráficos Output
547
+ "" # Informe Output
548
+ )
549
 
550
  def generar_datos_sinteticos_evento(df, n_replicas, unidad_medida):
551
+ df = df.copy()
552
+ col_predicha_num = "Concentración Predicha Numérica"
553
+
554
+ # Generar datos sintéticos para cada réplica
555
+ for i in range(1, n_replicas + 1):
556
+ col_real = f"Concentración Real {i} ({unidad_medida})"
557
+ df[col_predicha_num] = pd.to_numeric(df[col_predicha_num], errors='coerce')
558
+ desviacion_std = 0.05 * df[col_predicha_num].mean() # 5% de la media como desviación estándar
559
+ valores_predichos = df[col_predicha_num].astype(float).values
560
+ datos_sinteticos = valores_predichos + np.random.normal(0, desviacion_std, size=len(valores_predichos))
561
+ datos_sinteticos = np.maximum(0, datos_sinteticos) # Asegurar que no haya valores negativos
562
+ datos_sinteticos = np.round(datos_sinteticos, 2)
563
+ df[col_real] = datos_sinteticos
564
 
 
 
565
  return df
566
 
567
+ def actualizar_tabla_evento(df, n_filas, concentracion, unidad, n_replicas):
568
+ # Actualizar tabla sin borrar "Concentración Real"
569
+ df_new = generar_tabla(n_filas, concentracion, unidad, n_replicas)
570
+
571
+ # Mapear columnas
572
+ 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]
573
+ 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]
574
+
575
+ # Reemplazar valores existentes en "Concentración Real"
576
+ for col_new, col_old in zip(col_real_new, col_real_old):
577
+ df_new[col_new] = None
578
+ for idx in df_new.index:
579
+ if idx in df.index:
580
+ df_new.at[idx, col_new] = df.at[idx, col_old]
581
+
582
+ return df_new
583
+
584
+ def cargar_excel(file):
585
+ # Leer el archivo Excel
586
+ df_dict = pd.read_excel(file.name, sheet_name=None)
587
+
588
+ # Verificar que el archivo tenga al menos dos pestañas
589
+ if len(df_dict) < 2:
590
+ return "El archivo debe tener al menos dos pestañas.", None, None, None, None, None, None
591
+
592
+ # Obtener la primera pestaña como referencia
593
+ primera_pestaña = next(iter(df_dict.values()))
594
+ concentracion_inicial = primera_pestaña.iloc[0, 0]
595
+ unidad_medida = primera_pestaña.columns[0].split('(')[-1].split(')')[0]
596
+ n_filas = len(primera_pestaña)
597
+ n_replicas = len(df_dict)
598
+
599
+ # Generar la tabla base
600
+ df_base = generar_tabla(n_filas, concentracion_inicial, unidad_medida, n_replicas)
601
+
602
+ # Llenar la tabla con los datos de cada pestaña
603
+ for i, (sheet_name, sheet_df) in enumerate(df_dict.items(), start=1):
604
+ col_real = f"Concentración Real {i} ({unidad_medida})"
605
+ df_base[col_real] = sheet_df.iloc[:, 1].values
606
+
607
+ return concentracion_inicial, unidad_medida, n_filas, n_replicas, df_base, "", None, ""
608
+
609
  def actualizar_opciones_filas(df):
610
  if df is None or df.empty:
611
  return gr.update(choices=[], value=[])
 
624
  if df is None or df.empty or not columna_conc or not columna_abs:
625
  return "Datos insuficientes", None
626
 
627
+ try:
628
+ df_conc = pd.to_numeric(df[columna_conc], errors='coerce')
629
+ df_abs = pd.to_numeric(df[columna_abs], errors='coerce')
630
+ except KeyError:
631
+ return "Columnas seleccionadas no encontradas en la tabla", None
632
+
633
+ df_reg = pd.DataFrame({'Concentración': df_conc, 'Absorbancia': df_abs}).dropna()
634
+
635
+ if len(df_reg) < 2:
636
+ return "Se requieren al menos dos puntos para calcular la regresión", None
637
+
638
+ # Calcular regresión lineal
639
+ slope, intercept, r_value, p_value, std_err = stats.linregress(df_reg["Concentración"], df_reg["Absorbancia"])
640
+
641
+ # Generar gráfico
642
+ fig, ax = plt.subplots(figsize=(8, 6))
643
+ ax.scatter(df_reg["Concentración"], df_reg["Absorbancia"], color='blue', label='Datos')
644
+ x_vals = np.linspace(df_reg["Concentración"].min(), df_reg["Concentración"].max(), 100)
645
+ ax.plot(x_vals, intercept + slope * x_vals, 'r', label='Ajuste Lineal')
646
+ ax.set_xlabel('Concentración')
647
+ ax.set_ylabel('Absorbancia')
648
+ ax.set_title('Regresión Lineal: Absorbancia vs Concentración')
649
+ ax.legend()
650
+
651
+ # Añadir ecuación y R² en el gráfico
652
+ ax.annotate(
653
+ f'y = {intercept:.4f} + {slope:.4f}x\n$R^2$ = {r_value**2:.4f}',
654
+ xy=(0.05, 0.95),
655
+ xycoords='axes fraction',
656
+ fontsize=12,
657
+ backgroundcolor='white',
658
+ verticalalignment='top'
659
+ )
660
+
661
+ return "Regresión calculada exitosamente", fig
662
 
663
  # Interfaz Gradio
664
  with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
 
830
  outputs=[estado_regresion_output, grafico_regresion_output]
831
  )
832
 
833
+ graficar_btn.click(
834
+ fn=actualizar_graficos,
835
+ inputs=[
836
+ tabla_output, replicas_slider, unidad_input,
837
+ palette_puntos_dropdown, estilo_puntos_dropdown,
838
+ palette_linea_ajuste_dropdown, estilo_linea_ajuste_dropdown,
839
+ palette_linea_ideal_dropdown, estilo_linea_ideal_dropdown,
840
+ palette_barras_error_dropdown,
841
+ mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos,
842
+ filas_seleccionadas
843
+ ],
844
+ outputs=graficos_output
845
+ )
846
+
847
+ limpiar_btn.click(
848
+ fn=limpiar_datos,
849
+ inputs=[replicas_slider],
850
+ outputs=[concentracion_input, unidad_input, filas_slider, tabla_output, estado_output, graficos_output, informe_output]
851
+ )
852
+
853
+ ejemplo_ufc_btn.click(
854
+ fn=cargar_ejemplo_ufc,
855
+ inputs=[replicas_slider],
856
+ outputs=[concentracion_input, unidad_input, filas_slider, tabla_output]
857
+ )
858
+
859
+ ejemplo_od_btn.click(
860
+ fn=cargar_ejemplo_od,
861
+ inputs=[replicas_slider],
862
+ outputs=[concentracion_input, unidad_input, filas_slider, tabla_output]
863
+ )
864
+
865
+ sinteticos_btn.click(
866
+ fn=generar_datos_sinteticos_evento,
867
+ inputs=[tabla_output, replicas_slider, unidad_input],
868
+ outputs=tabla_output
869
+ )
870
+
871
+ cargar_excel_btn.upload(
872
+ fn=cargar_excel,
873
+ inputs=[cargar_excel_btn],
874
+ outputs=[concentracion_input, unidad_input, filas_slider, replicas_slider, tabla_output, estado_output, graficos_output, informe_output]
875
+ )
876
+
877
+ ajustar_decimales_btn.click(
878
+ fn=ajustar_decimales_evento,
879
+ inputs=[tabla_output, decimales_slider],
880
+ outputs=tabla_output
881
+ )
882
+
883
+ def actualizar_tabla_wrapper(df, filas, conc, unidad, replicas):
884
+ return actualizar_tabla_evento(df, filas, conc, unidad, replicas)
885
+
886
+ concentracion_input.change(
887
+ fn=actualizar_tabla_wrapper,
888
+ inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider],
889
+ outputs=tabla_output
890
+ )
891
+
892
+ unidad_input.change(
893
+ fn=actualizar_tabla_wrapper,
894
+ inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider],
895
+ outputs=tabla_output
896
+ )
897
+
898
+ filas_slider.change(
899
+ fn=actualizar_tabla_wrapper,
900
+ inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider],
901
+ outputs=tabla_output
902
+ )
903
+
904
+ replicas_slider.change(
905
+ fn=actualizar_tabla_wrapper,
906
+ inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider],
907
+ outputs=tabla_output
908
+ )
909
+
910
+ copiar_btn.click(
911
+ None,
912
+ [],
913
+ [],
914
+ js="""
915
+ function() {
916
+ const informeElement = document.querySelector('#informe_output');
917
+ const range = document.createRange();
918
+ range.selectNode(informeElement);
919
+ window.getSelection().removeAllRanges();
920
+ window.getSelection().addRange(range);
921
+ document.execCommand('copy');
922
+ window.getSelection().removeAllRanges();
923
+ alert('Informe copiado al portapapeles');
924
+ }
925
+ """
926
+ )
927
+
928
+ exportar_word_btn.click(
929
+ fn=exportar_word,
930
+ inputs=[tabla_output, informe_output, unidad_input, filas_seleccionadas],
931
+ outputs=exportar_word_file
932
+ )
933
+
934
+ exportar_latex_btn.click(
935
+ fn=exportar_latex,
936
+ inputs=[tabla_output, informe_output, filas_seleccionadas],
937
+ outputs=exportar_latex_file
938
+ )
939
+
940
+ def iniciar_con_ejemplo():
941
+ n_replicas = 1
942
+ df = generar_tabla(7, 2000000, "UFC", n_replicas)
943
+ # Valores reales de ejemplo
944
+ df[f"Concentración Real 1 (UFC)"] = [2000000, 1600000, 1200000, 800000, 400000, 200000, 100000]
945
+ filas_seleccionadas_inicial = [f"Fila {i+1}" for i in df.index]
946
+ estado, fig, informe, df = actualizar_analisis(df, n_replicas, "UFC", filas_seleccionadas_inicial)
947
+ return (
948
+ 2000000,
949
+ "UFC",
950
+ 7,
951
+ df,
952
+ estado,
953
+ fig,
954
+ informe,
955
+ filas_seleccionadas_inicial
956
+ )
957
+
958
+ interfaz.load(
959
+ fn=iniciar_con_ejemplo,
960
+ outputs=[concentracion_input, unidad_input, filas_slider, tabla_output, estado_output, graficos_output, informe_output, filas_seleccionadas]
961
+ )
962
 
963
  # Lanzar la interfaz
964
  if __name__ == "__main__":