C2MV commited on
Commit
330f424
verified
1 Parent(s): b960f5f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +185 -296
app.py CHANGED
@@ -10,44 +10,8 @@ from docx.shared import Inches, Pt
10
  from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
11
  import os
12
 
13
- def generar_tabla(n_filas, concentracion_inicial, unidad_medida, n_replicas):
14
- valores_base = [1.000, 0.800, 0.600, 0.400, 0.200, 0.100, 0.050]
15
-
16
- if n_filas <= 7:
17
- solucion_inoculo = valores_base[:n_filas]
18
- agua = [round(1 - x, 3) for x in solucion_inoculo]
19
- else:
20
- solucion_inoculo = valores_base.copy()
21
- ultimo_valor = valores_base[-1]
22
- for _ in range(n_filas - 7):
23
- nuevo_valor = round(ultimo_valor / 2, 3)
24
- solucion_inoculo.append(nuevo_valor)
25
- ultimo_valor = nuevo_valor
26
- agua = [round(1 - x, 3) for x in solucion_inoculo]
27
-
28
- data = {
29
- f"Soluci贸n de in贸culo ({concentracion_inicial} {unidad_medida})": solucion_inoculo,
30
- "H2O": agua
31
- }
32
- df = pd.DataFrame(data)
33
-
34
- nombre_columna = f"Soluci贸n de in贸culo ({concentracion_inicial} {unidad_medida})"
35
- df["Factor de Diluci贸n"] = df[nombre_columna].apply(lambda x: round(1 / x, 3))
36
- df["Concentraci贸n Predicha Num茅rica"] = df["Factor de Diluci贸n"].apply(
37
- lambda x: concentracion_inicial / x
38
- )
39
- df[f"Concentraci贸n Predicha ({unidad_medida})"] = df["Concentraci贸n Predicha Num茅rica"].round(3).astype(str)
40
-
41
- # A帽adir columnas para las r茅plicas de "Concentraci贸n Real"
42
- for i in range(1, n_replicas + 1):
43
- df[f"Concentraci贸n Real {i} ({unidad_medida})"] = None
44
-
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
51
  for col in df.columns:
52
  try:
53
  df[col] = pd.to_numeric(df[col], errors='ignore')
@@ -58,21 +22,20 @@ def ajustar_decimales_evento(df, decimales):
58
 
59
  def calcular_promedio_desviacion(df, n_replicas, unidad_medida, decimales):
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
- # Redondear al n煤mero de decimales especificado
76
  df[f"Concentraci贸n Real Promedio ({unidad_medida})"] = df[f"Concentraci贸n Real Promedio ({unidad_medida})"].round(decimales)
77
  df[f"Desviaci贸n Est谩ndar ({unidad_medida})"] = df[f"Desviaci贸n Est谩ndar ({unidad_medida})"].round(decimales)
78
 
@@ -86,37 +49,35 @@ def generar_graficos(df_valid, n_replicas, unidad_medida, palette_puntos, estilo
86
  col_real_promedio = f"Concentraci贸n Real Promedio ({unidad_medida})"
87
  col_desviacion = f"Desviaci贸n Est谩ndar ({unidad_medida})"
88
 
89
- # Convertir a num茅rico
90
- df_valid[col_predicha_num] = df_valid[col_predicha_num].astype(float)
91
- df_valid[col_real_promedio] = df_valid[col_real_promedio].astype(float)
92
- df_valid[col_desviacion] = df_valid[col_desviacion].fillna(0).astype(float)
 
 
 
 
93
 
94
- # Calcular regresi贸n lineal
95
  slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha_num], df_valid[col_real_promedio])
96
  df_valid['Ajuste Lineal'] = intercept + slope * df_valid[col_predicha_num]
97
 
98
- # Configurar estilos
99
  sns.set(style="whitegrid")
100
  plt.rcParams.update({'figure.autolayout': True})
101
 
102
  fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
103
 
104
- # Obtener colores de las paletas
105
  colors_puntos = sns.color_palette(palette_puntos, as_cmap=False)
106
  colors_linea_ajuste = sns.color_palette(palette_linea_ajuste, as_cmap=False)
107
  colors_linea_ideal = sns.color_palette(palette_linea_ideal, as_cmap=False)
108
  colors_barras_error = sns.color_palette(palette_barras_error, as_cmap=False)
109
 
110
- # Seleccionar colores
111
  color_puntos = colors_puntos[0]
112
  color_linea_ajuste = colors_linea_ajuste[0]
113
  color_linea_ideal = colors_linea_ideal[0]
114
  color_barras_error = colors_barras_error[0]
115
 
116
- # Gr谩fico de dispersi贸n con l铆nea de regresi贸n
117
  if mostrar_puntos:
118
  if n_replicas > 1:
119
- # Incluir barras de error
120
  ax1.errorbar(
121
  df_valid[col_predicha_num],
122
  df_valid[col_real_promedio],
@@ -138,7 +99,6 @@ def generar_graficos(df_valid, n_replicas, unidad_medida, palette_puntos, estilo
138
  marker=estilo_puntos
139
  )
140
 
141
- # L铆nea de ajuste
142
  if mostrar_linea_ajuste:
143
  ax1.plot(
144
  df_valid[col_predicha_num],
@@ -149,7 +109,6 @@ def generar_graficos(df_valid, n_replicas, unidad_medida, palette_puntos, estilo
149
  linestyle=estilo_linea_ajuste
150
  )
151
 
152
- # L铆nea ideal
153
  if mostrar_linea_ideal:
154
  min_predicha = df_valid[col_predicha_num].min()
155
  max_predicha = df_valid[col_predicha_num].max()
@@ -165,7 +124,6 @@ def generar_graficos(df_valid, n_replicas, unidad_medida, palette_puntos, estilo
165
  ax1.set_xlabel('Concentraci贸n Predicha', fontsize=12)
166
  ax1.set_ylabel('Concentraci贸n Real Promedio', fontsize=12)
167
 
168
- # A帽adir ecuaci贸n y R虏 en el gr谩fico
169
  ax1.annotate(
170
  f'y = {intercept:.3f} + {slope:.3f}x\n$R^2$ = {r_value**2:.4f}',
171
  xy=(0.05, 0.95),
@@ -175,10 +133,8 @@ def generar_graficos(df_valid, n_replicas, unidad_medida, palette_puntos, estilo
175
  verticalalignment='top'
176
  )
177
 
178
- # Posicionar la leyenda
179
  ax1.legend(loc='lower right', fontsize=10)
180
 
181
- # Gr谩fico de residuos
182
  residuos = df_valid[col_real_promedio] - df_valid['Ajuste Lineal']
183
  ax2.scatter(
184
  df_valid[col_predicha_num],
@@ -196,11 +152,10 @@ def generar_graficos(df_valid, n_replicas, unidad_medida, palette_puntos, estilo
196
  ax2.legend(loc='upper right', fontsize=10)
197
 
198
  plt.tight_layout()
199
- plt.savefig('grafico.png') # Guardar el gr谩fico para incluirlo en el informe
200
  return fig
201
 
202
  def evaluar_calidad_calibracion(df_valid, r_squared, rmse, cv_percent):
203
- """Evaluar la calidad de la calibraci贸n y proporcionar recomendaciones"""
204
  evaluacion = {
205
  "calidad": "",
206
  "recomendaciones": [],
@@ -222,27 +177,30 @@ def evaluar_calidad_calibracion(df_valid, r_squared, rmse, cv_percent):
222
  if cv_percent > 15:
223
  evaluacion["recomendaciones"].append("- La variabilidad es alta. Revise el procedimiento de diluci贸n")
224
 
225
- if rmse > 0.1 * df_valid[df_valid.columns[-1]].astype(float).mean():
 
 
 
226
  evaluacion["recomendaciones"].append("- El error de predicci贸n es significativo. Verifique la t茅cnica de medici贸n")
227
 
228
  return evaluacion
229
 
230
  def generar_informe_completo(df_valid, n_replicas, unidad_medida):
231
- """Generar un informe completo en formato markdown"""
232
  col_predicha_num = "Concentraci贸n Predicha Num茅rica"
233
  col_real_promedio = f"Concentraci贸n Real Promedio ({unidad_medida})"
234
 
235
- # Convertir a num茅rico
236
- df_valid[col_predicha_num] = df_valid[col_predicha_num].astype(float)
237
- df_valid[col_real_promedio] = df_valid[col_real_promedio].astype(float)
 
 
 
238
 
239
- # Calcular estad铆sticas
240
  slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha_num], df_valid[col_real_promedio])
241
  r_squared = r_value ** 2
242
  rmse = np.sqrt(((df_valid[col_real_promedio] - (intercept + slope * df_valid[col_predicha_num])) ** 2).mean())
243
- cv = (df_valid[col_real_promedio].std() / df_valid[col_real_promedio].mean()) * 100 # CV de los valores reales
244
 
245
- # Evaluar calidad
246
  evaluacion = evaluar_calidad_calibracion(df_valid, r_squared, rmse, cv)
247
 
248
  informe = f"""# Informe de Calibraci贸n {evaluacion['estado']}
@@ -275,38 +233,30 @@ def actualizar_analisis(df, n_replicas, unidad_medida, filas_seleccionadas, deci
275
  if df is None or df.empty:
276
  return "Error en los datos", None, "No se pueden generar an谩lisis", df
277
 
278
- # Convertir filas_seleccionadas a 铆ndices
279
  if not filas_seleccionadas:
280
  return "Se necesitan m谩s datos", None, "No se han seleccionado filas para el an谩lisis", df
281
 
282
  indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas]
283
 
284
- # Calcular promedio y desviaci贸n est谩ndar dependiendo de las r茅plicas
285
  df = calcular_promedio_desviacion(df, n_replicas, unidad_medida, decimales)
286
 
287
  col_predicha_num = "Concentraci贸n Predicha Num茅rica"
288
  col_real_promedio = f"Concentraci贸n Real Promedio ({unidad_medida})"
289
 
290
- # Convertir columnas a num茅rico
291
  df[col_predicha_num] = pd.to_numeric(df[col_predicha_num], errors='coerce')
292
  df[col_real_promedio] = pd.to_numeric(df[col_real_promedio], errors='coerce')
293
 
294
  df_valid = df.dropna(subset=[col_predicha_num, col_real_promedio])
295
-
296
- # Resetear el 铆ndice para asegurar que sea secuencial
297
  df_valid.reset_index(drop=True, inplace=True)
298
 
299
- # Filtrar filas seg煤n las seleccionadas
300
  df_valid = df_valid.loc[indices_seleccionados]
301
 
302
  if len(df_valid) < 2:
303
  return "Se necesitan m谩s datos", None, "Se requieren al menos dos valores reales para el an谩lisis", df
304
 
305
- # Calcular la regresi贸n y agregar 'Ajuste Lineal'
306
  slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha_num], df_valid[col_real_promedio])
307
  df_valid['Ajuste Lineal'] = intercept + slope * df_valid[col_predicha_num]
308
 
309
- # Generar gr谩fico con opciones predeterminadas
310
  fig = generar_graficos(
311
  df_valid, n_replicas, unidad_medida,
312
  palette_puntos='deep', estilo_puntos='o',
@@ -314,7 +264,7 @@ def actualizar_analisis(df, n_replicas, unidad_medida, filas_seleccionadas, deci
314
  palette_linea_ideal='bright', estilo_linea_ideal='--',
315
  palette_barras_error='pastel',
316
  mostrar_linea_ajuste=True,
317
- mostrar_linea_ideal=False, # L铆nea Ideal desmarcada por defecto
318
  mostrar_puntos=True
319
  )
320
  informe, estado = generar_informe_completo(df_valid, n_replicas, unidad_medida)
@@ -331,34 +281,26 @@ def actualizar_graficos(df, n_replicas, unidad_medida,
331
  if df is None or df.empty:
332
  return None
333
 
334
- # Asegurarse de que los c谩lculos est茅n actualizados
335
  df = calcular_promedio_desviacion(df, n_replicas, unidad_medida, decimales)
336
 
337
  col_predicha_num = "Concentraci贸n Predicha Num茅rica"
338
  col_real_promedio = f"Concentraci贸n Real Promedio ({unidad_medida})"
339
 
340
- # Convertir columnas a num茅rico
341
  df[col_predicha_num] = pd.to_numeric(df[col_predicha_num], errors='coerce')
342
  df[col_real_promedio] = pd.to_numeric(df[col_real_promedio], errors='coerce')
343
 
344
  df_valid = df.dropna(subset=[col_predicha_num, col_real_promedio])
345
-
346
- # Resetear el 铆ndice para asegurar que sea secuencial
347
  df_valid.reset_index(drop=True, inplace=True)
348
 
349
- # Convertir filas_seleccionadas a 铆ndices
350
  if not filas_seleccionadas:
351
  return None
352
 
353
  indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas]
354
-
355
- # Filtrar filas seg煤n las seleccionadas
356
  df_valid = df_valid.loc[indices_seleccionados]
357
 
358
  if len(df_valid) < 2:
359
  return None
360
 
361
- # Generar gr谩fico con opciones seleccionadas
362
  fig = generar_graficos(
363
  df_valid, n_replicas, unidad_medida,
364
  palette_puntos, estilo_puntos,
@@ -371,36 +313,28 @@ def actualizar_graficos(df, n_replicas, unidad_medida,
371
  return fig
372
 
373
  def exportar_informe_word(df_valid, informe_md, unidad_medida):
374
- # Crear documento Word
375
  doc = docx.Document()
376
 
377
- # Estilos APA 7
378
  style = doc.styles['Normal']
379
  font = style.font
380
  font.name = 'Times New Roman'
381
  font.size = Pt(12)
382
 
383
- # T铆tulo centrado
384
  titulo = doc.add_heading('Informe de Calibraci贸n', 0)
385
  titulo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
386
 
387
- # Fecha
388
  fecha = doc.add_paragraph(f"Fecha: {datetime.now().strftime('%d/%m/%Y %H:%M')}")
389
  fecha.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
390
 
391
- # Insertar gr谩fico
392
  if os.path.exists('grafico.png'):
393
  doc.add_picture('grafico.png', width=Inches(6))
394
  ultimo_parrafo = doc.paragraphs[-1]
395
  ultimo_parrafo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
396
-
397
- # Leyenda del gr谩fico en estilo APA 7
398
  leyenda = doc.add_paragraph('Figura 1. Gr谩fico de calibraci贸n.')
399
  leyenda_format = leyenda.paragraph_format
400
  leyenda_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
401
  leyenda.style = doc.styles['Caption']
402
 
403
- # Agregar contenido del informe
404
  doc.add_heading('Resumen Estad铆stico', level=1)
405
  for linea in informe_md.split('\n'):
406
  if linea.startswith('##'):
@@ -408,43 +342,34 @@ def exportar_informe_word(df_valid, informe_md, unidad_medida):
408
  else:
409
  doc.add_paragraph(linea)
410
 
411
- # A帽adir tabla de datos
412
  doc.add_heading('Tabla de Datos de Calibraci贸n', level=1)
413
-
414
- # Convertir DataFrame a lista de listas
415
  tabla_datos = df_valid.reset_index(drop=True)
416
- tabla_datos = tabla_datos.round(4) # Redondear a 4 decimales si es necesario
417
  columnas = tabla_datos.columns.tolist()
418
  registros = tabla_datos.values.tolist()
419
 
420
- # Crear tabla en Word
421
  tabla = doc.add_table(rows=1 + len(registros), cols=len(columnas))
422
  tabla.style = 'Table Grid'
423
 
424
- # A帽adir los encabezados
425
  hdr_cells = tabla.rows[0].cells
426
  for idx, col_name in enumerate(columnas):
427
  hdr_cells[idx].text = col_name
428
 
429
- # A帽adir los registros
430
  for i, registro in enumerate(registros):
431
  row_cells = tabla.rows[i + 1].cells
432
  for j, valor in enumerate(registro):
433
  row_cells[j].text = str(valor)
434
 
435
- # Formatear fuente de la tabla
436
  for row in tabla.rows:
437
  for cell in row.cells:
438
  for paragraph in cell.paragraphs:
439
  paragraph.style = doc.styles['Normal']
440
 
441
- # Guardar documento
442
  filename = 'informe_calibracion.docx'
443
  doc.save(filename)
444
  return filename
445
 
446
  def exportar_informe_latex(df_valid, informe_md):
447
- # Generar c贸digo LaTeX
448
  informe_tex = r"""\documentclass{article}
449
  \usepackage[spanish]{babel}
450
  \usepackage{amsmath}
@@ -464,156 +389,142 @@ def exportar_informe_latex(df_valid, informe_md):
464
  def exportar_word(df, informe_md, unidad_medida, filas_seleccionadas):
465
  df_valid = df.copy()
466
  col_predicha_num = "Concentraci贸n Predicha Num茅rica"
467
- col_real_promedio = f"Concentraci贸n Real Promedio ({unidad_medida})"
468
 
469
- # Convertir columnas a num茅rico
470
- df_valid[col_predicha_num] = pd.to_numeric(df_valid[col_predicha_num], errors='coerce')
471
- df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce')
472
-
473
- df_valid = df_valid.dropna(subset=[col_predicha_num, col_real_promedio])
474
-
475
- # Resetear el 铆ndice
476
  df_valid.reset_index(drop=True, inplace=True)
477
 
478
- # Convertir filas_seleccionadas a 铆ndices
479
  if not filas_seleccionadas:
480
  return None
481
 
482
- indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas]
483
-
484
- # Filtrar filas seg煤n las seleccionadas
485
- df_valid = df_valid.loc[indices_seleccionados]
 
486
 
487
  if df_valid.empty:
488
  return None
489
 
490
  filename = exportar_informe_word(df_valid, informe_md, unidad_medida)
491
-
492
- return filename # Retornamos el nombre del archivo
493
 
494
  def exportar_latex(df, informe_md, filas_seleccionadas):
495
  df_valid = df.copy()
496
  col_predicha_num = "Concentraci贸n Predicha Num茅rica"
497
- col_real_promedio = [col for col in df_valid.columns if 'Real Promedio' in col][0]
498
 
499
- # Convertir columnas a num茅rico
500
- df_valid[col_predicha_num] = pd.to_numeric(df_valid[col_predicha_num], errors='coerce')
501
- df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce')
 
502
 
503
- df_valid = df_valid.dropna(subset=[col_predicha_num, col_real_promedio])
504
-
505
- # Resetear el 铆ndice
506
  df_valid.reset_index(drop=True, inplace=True)
507
 
508
- # Convertir filas_seleccionadas a 铆ndices
509
  if not filas_seleccionadas:
510
  return None
511
 
512
- indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas]
513
-
514
- # Filtrar filas seg煤n las seleccionadas
515
- df_valid = df_valid.loc[indices_seleccionados]
 
516
 
517
  if df_valid.empty:
518
  return None
519
 
520
  filename = exportar_informe_latex(df_valid, informe_md)
521
-
522
- return filename # Retornamos el nombre del archivo
523
-
524
- def cargar_ejemplo_ufc(n_replicas):
525
- df = generar_tabla(7, 2000000, "UFC", n_replicas)
526
- # Valores reales de ejemplo
527
- for i in range(1, n_replicas + 1):
528
- valores_reales = [2000000 - (i - 1) * 10000, 1600000 - (i - 1) * 8000, 1200000 - (i - 1) * 6000,
529
- 800000 - (i - 1) * 4000, 400000 - (i - 1) * 2000, 200000 - (i - 1) * 1000,
530
- 100000 - (i - 1) * 500]
531
- df[f"Concentraci贸n Real {i} (UFC)"] = valores_reales
532
- return 2000000, "UFC", 7, df
533
-
534
- def cargar_ejemplo_od(n_replicas):
535
- df = generar_tabla(7, 1.000, "OD", n_replicas)
536
- # Valores reales de ejemplo
537
- for i in range(1, n_replicas + 1):
538
- valores_reales = [1.000 - (i - 1) * 0.050, 0.800 - (i - 1) * 0.040, 0.600 - (i - 1) * 0.030,
539
- 0.400 - (i - 1) * 0.020, 0.200 - (i - 1) * 0.010, 0.100 - (i - 1) * 0.005,
540
- 0.050 - (i - 1) * 0.002]
541
- df[f"Concentraci贸n Real {i} (OD)"] = valores_reales
542
- return 1.000, "OD", 7, df
543
 
544
  def limpiar_datos(n_replicas):
545
- df = generar_tabla(7, 2000000, "UFC", n_replicas)
 
 
 
 
 
 
546
  return (
547
- 2000000, # Concentraci贸n Inicial
548
- "UFC", # Unidad de Medida
549
- 7, # N煤mero de filas
550
- df, # Tabla Output
551
- "", # Estado Output
552
- None, # Gr谩ficos Output
553
- "" # Informe Output
554
  )
555
 
556
  def generar_datos_sinteticos_evento(df, n_replicas, unidad_medida):
557
  df = df.copy()
558
  col_predicha_num = "Concentraci贸n Predicha Num茅rica"
559
-
560
- # Generar datos sint茅ticos para cada r茅plica
561
- for i in range(1, n_replicas + 1):
562
- col_real = f"Concentraci贸n Real {i} ({unidad_medida})"
563
  df[col_predicha_num] = pd.to_numeric(df[col_predicha_num], errors='coerce')
564
- desviacion_std = 0.05 * df[col_predicha_num].mean() # 5% de la media como desviaci贸n est谩ndar
565
- valores_predichos = df[col_predicha_num].astype(float).values
566
- datos_sinteticos = valores_predichos + np.random.normal(0, desviacion_std, size=len(valores_predichos))
567
- datos_sinteticos = np.maximum(0, datos_sinteticos) # Asegurar que no haya valores negativos
568
- datos_sinteticos = np.round(datos_sinteticos, 3)
569
- df[col_real] = datos_sinteticos
 
 
 
 
 
570
 
 
 
 
 
 
 
 
 
571
  return df
572
 
573
- def actualizar_tabla_evento(df, n_filas, concentracion, unidad, n_replicas, decimales):
574
- # Actualizar tabla sin borrar "Concentraci贸n Real"
575
- df_new = generar_tabla(n_filas, concentracion, unidad, n_replicas)
576
 
577
- # Mapear columnas
578
- 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]
579
- 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]
580
 
581
- # Reemplazar valores existentes en "Concentraci贸n Real"
582
- for col_new, col_old in zip(col_real_new, col_real_old):
583
- df_new[col_new] = None
584
- for idx in df_new.index:
585
- if idx in df.index:
586
- df_new.at[idx, col_new] = df.at[idx, col_old]
587
 
588
- # Ajustar decimales
589
- df_new = ajustar_decimales_evento(df_new, decimales)
 
590
 
591
- return df_new
592
 
593
- def cargar_excel(file):
594
- # Leer el archivo Excel
595
- df = pd.read_excel(file.name, sheet_name=None)
 
 
 
 
 
 
596
 
597
- # Verificar que el archivo tenga al menos dos pesta帽as
598
- if len(df) < 2:
599
- return "El archivo debe tener al menos dos pesta帽as.", None, None, None, None, None, None
600
 
601
- # Obtener la primera pesta帽a como referencia
602
- primera_pesta帽a = next(iter(df.values()))
603
- concentracion_inicial = primera_pesta帽a.iloc[0, 0]
604
- unidad_medida = primera_pesta帽a.columns[0].split('(')[-1].split(')')[0]
605
- n_filas = len(primera_pesta帽a)
606
- n_replicas = len(df)
607
 
608
- # Generar la tabla base
609
- df_base = generar_tabla(n_filas, concentracion_inicial, unidad_medida, n_replicas)
610
 
611
- # Llenar la tabla con los datos de cada pesta帽a
612
- for i, (sheet_name, sheet_df) in enumerate(df.items(), start=1):
613
- col_real = f"Concentraci贸n Real {i} ({unidad_medida})"
614
- df_base[col_real] = sheet_df.iloc[:, 1].values
615
 
616
- return concentracion_inicial, unidad_medida, n_filas, n_replicas, df_base, "", None, ""
617
 
618
  def calcular_regresion_tabla_principal(df, unidad_medida, filas_seleccionadas_regresion,
619
  palette_puntos, estilo_puntos,
@@ -627,48 +538,41 @@ def calcular_regresion_tabla_principal(df, unidad_medida, filas_seleccionadas_re
627
  return "Datos insuficientes", None, None, None
628
 
629
  col_concentracion = "Concentraci贸n Predicha Num茅rica"
630
- col_absorbancia = f"Concentraci贸n Real Promedio ({unidad_medida})"
631
  col_desviacion = f"Desviaci贸n Est谩ndar ({unidad_medida})"
632
 
633
- # Calcular promedio y desviaci贸n est谩ndar si es necesario
634
- 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])
635
  df = calcular_promedio_desviacion(df, n_replicas, unidad_medida, decimales)
636
 
637
- # Convertir columnas a num茅rico
638
- df[col_concentracion] = pd.to_numeric(df[col_concentracion], errors='coerce')
639
- df[col_absorbancia] = pd.to_numeric(df[col_absorbancia], errors='coerce')
640
- df[col_desviacion] = pd.to_numeric(df[col_desviacion], errors='coerce')
641
 
642
- df_valid = df.dropna(subset=[col_concentracion, col_absorbancia])
 
 
643
 
644
- # Resetear el 铆ndice para asegurar que sea secuencial
645
  df_valid.reset_index(drop=True, inplace=True)
646
 
647
- # Asegurar que el gr谩fico original tenga todos los puntos
648
  df_original = df_valid.copy()
649
 
650
- # Convertir filas_seleccionadas a 铆ndices
651
  if not filas_seleccionadas_regresion:
652
  return "Se necesitan m谩s datos", None, None, None
653
 
654
- indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas_regresion]
 
 
655
 
656
- # Filtrar filas seg煤n las seleccionadas para el gr谩fico personalizado
657
  df_valid = df_valid.loc[indices_seleccionados]
658
 
659
- if len(df_valid) < 2:
660
- return "Se requieren al menos dos puntos para calcular la regresi贸n", None, None, None
661
-
662
- # Calcular regresi贸n lineal para el gr谩fico personalizado
663
- slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_concentracion], df_valid[col_absorbancia])
664
 
665
- # Generar gr谩fico original (con todos los puntos)
666
  sns.set(style="whitegrid")
667
  fig_original, ax_original = plt.subplots(figsize=(8, 6))
668
 
669
  ax_original.errorbar(
670
  df_original[col_concentracion],
671
- df_original[col_absorbancia],
672
  yerr=df_original[col_desviacion],
673
  fmt='o',
674
  color='blue',
@@ -678,8 +582,7 @@ def calcular_regresion_tabla_principal(df, unidad_medida, filas_seleccionadas_re
678
  label='Datos'
679
  )
680
 
681
- # Calcular regresi贸n para todos los puntos
682
- slope_all, intercept_all, r_value_all, p_value_all, std_err_all = stats.linregress(df_original[col_concentracion], df_original[col_absorbancia])
683
 
684
  ax_original.plot(
685
  df_original[col_concentracion],
@@ -689,15 +592,11 @@ def calcular_regresion_tabla_principal(df, unidad_medida, filas_seleccionadas_re
689
  label='Ajuste Lineal'
690
  )
691
 
692
- # T铆tulo y etiquetas personalizadas para el gr谩fico original
693
  ax_original.set_xlabel(eje_x_original if eje_x_original else 'Concentraci贸n Predicha Num茅rica')
694
  ax_original.set_ylabel(eje_y_original if eje_y_original else f'Concentraci贸n Real Promedio ({unidad_medida})')
695
  ax_original.set_title(titulo_grafico_original if titulo_grafico_original else 'Regresi贸n Lineal: Concentraci贸n Real vs Concentraci贸n Predicha (Original)')
696
-
697
- # Posicionar la leyenda seg煤n la opci贸n seleccionada (por defecto 'lower right')
698
  ax_original.legend(loc=legend_location)
699
 
700
- # A帽adir ecuaci贸n y R虏 en el gr谩fico
701
  ax_original.annotate(
702
  f'y = {intercept_all:.4f} + {slope_all:.4f}x\n$R^2$ = {r_value_all**2:.4f}',
703
  xy=(0.05, 0.95),
@@ -707,11 +606,9 @@ def calcular_regresion_tabla_principal(df, unidad_medida, filas_seleccionadas_re
707
  verticalalignment='top'
708
  )
709
 
710
- # Generar gr谩fico personalizado
711
  sns.set(style="whitegrid")
712
  fig_personalizado, ax_personalizado = plt.subplots(figsize=(8, 6))
713
 
714
- # Obtener colores de las paletas
715
  colors_puntos = sns.color_palette(palette_puntos, as_cmap=False)
716
  colors_linea_ajuste = sns.color_palette(palette_linea_ajuste, as_cmap=False)
717
 
@@ -721,7 +618,7 @@ def calcular_regresion_tabla_principal(df, unidad_medida, filas_seleccionadas_re
721
  if mostrar_puntos:
722
  ax_personalizado.errorbar(
723
  df_valid[col_concentracion],
724
- df_valid[col_absorbancia],
725
  yerr=df_valid[col_desviacion],
726
  fmt=estilo_puntos,
727
  color=color_puntos,
@@ -740,15 +637,11 @@ def calcular_regresion_tabla_principal(df, unidad_medida, filas_seleccionadas_re
740
  label='Ajuste Lineal'
741
  )
742
 
743
- # T铆tulo y etiquetas personalizadas para el gr谩fico personalizado
744
  ax_personalizado.set_xlabel(eje_x_personalizado if eje_x_personalizado else 'Concentraci贸n Predicha Num茅rica')
745
  ax_personalizado.set_ylabel(eje_y_personalizado if eje_y_personalizado else f'Concentraci贸n Real Promedio ({unidad_medida})')
746
  ax_personalizado.set_title(titulo_grafico_personalizado if titulo_grafico_personalizado else 'Regresi贸n Lineal Personalizada')
747
-
748
- # Posicionar la leyenda seg煤n la opci贸n seleccionada
749
  ax_personalizado.legend(loc=legend_location)
750
 
751
- # A帽adir ecuaci贸n y R虏 en el gr谩fico
752
  ax_personalizado.annotate(
753
  f'y = {intercept:.4f} + {slope:.4f}x\n$R^2$ = {r_value**2:.4f}',
754
  xy=(0.05, 0.95),
@@ -758,13 +651,11 @@ def calcular_regresion_tabla_principal(df, unidad_medida, filas_seleccionadas_re
758
  verticalalignment='top'
759
  )
760
 
761
- # Crear tabla resumida
762
- df_resumen = df_valid[[col_concentracion, col_absorbancia, col_desviacion]].copy()
763
  df_resumen.columns = ['Concentraci贸n Predicha', 'Absorbancia Promedio', 'Desviaci贸n Est谩ndar']
764
 
765
  return "Regresi贸n calculada exitosamente", fig_original, fig_personalizado, df_resumen
766
 
767
- # Funci贸n corregida para actualizar las opciones de filas
768
  def actualizar_opciones_filas(df):
769
  if df is None or df.empty:
770
  update = gr.update(choices=[], value=[])
@@ -773,11 +664,10 @@ def actualizar_opciones_filas(df):
773
  update = gr.update(choices=opciones, value=opciones)
774
  return update, update
775
 
776
- # Interfaz Gradio
777
  with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
778
  gr.Markdown("""
779
  # 馃搳 Sistema Avanzado de Calibraci贸n con An谩lisis Estad铆stico
780
- Configure los par谩metros, edite los valores en la tabla y luego presione "Calcular" para obtener el an谩lisis.
781
  """)
782
 
783
  with gr.Tab("馃摑 Datos de Calibraci贸n"):
@@ -836,16 +726,13 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
836
  estado_output = gr.Textbox(label="Estado", interactive=False)
837
  graficos_output = gr.Plot(label="Gr谩ficos de An谩lisis")
838
 
839
- # Reemplazar Multiselect por CheckboxGroup
840
  filas_seleccionadas = gr.CheckboxGroup(
841
  label="Seleccione las filas a incluir en el an谩lisis",
842
  choices=[],
843
  value=[],
844
  )
845
 
846
- # Opciones y botones debajo del gr谩fico
847
  with gr.Row():
848
- # Paletas de colores disponibles en Seaborn
849
  paletas_colores = ["deep", "muted", "pastel", "bright", "dark", "colorblind", "Set1", "Set2", "Set3"]
850
 
851
  palette_puntos_dropdown = gr.Dropdown(
@@ -886,10 +773,13 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
886
  label="Paleta Barras de Error"
887
  )
888
  mostrar_linea_ajuste = gr.Checkbox(value=True, label="Mostrar L铆nea de Ajuste")
889
- mostrar_linea_ideal = gr.Checkbox(value=False, label="Mostrar L铆nea Ideal") # Desmarcado por defecto
890
  mostrar_puntos = gr.Checkbox(value=True, label="Mostrar Puntos")
891
  graficar_btn = gr.Button("馃搳 Graficar", variant="primary")
892
 
 
 
 
893
  with gr.Row():
894
  copiar_btn = gr.Button("馃搵 Copiar Informe", variant="secondary")
895
  exportar_word_btn = gr.Button("馃捑 Exportar Informe Word", variant="primary")
@@ -899,20 +789,17 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
899
  exportar_word_file = gr.File(label="Informe en Word")
900
  exportar_latex_file = gr.File(label="Informe en LaTeX")
901
 
902
- # Informe al final
903
  informe_output = gr.Markdown(elem_id="informe_output")
904
 
905
  with gr.Tab("馃搱 Regresi贸n Absorbancia vs Concentraci贸n"):
906
  gr.Markdown("## Ajuste de Regresi贸n utilizando datos de la Tabla Principal")
907
 
908
- # Casillas para seleccionar filas
909
  filas_seleccionadas_regresion = gr.CheckboxGroup(
910
  label="Seleccione las filas a incluir en el an谩lisis de regresi贸n",
911
  choices=[],
912
  value=[],
913
  )
914
 
915
- # Opciones de personalizaci贸n
916
  with gr.Row():
917
  paletas_colores = ["deep", "muted", "pastel", "bright", "dark", "colorblind", "Set1", "Set2", "Set3"]
918
  palette_puntos_regresion = gr.Dropdown(
@@ -945,11 +832,10 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
945
  'right', 'center left', 'center right', 'lower center',
946
  'upper center', 'center'
947
  ],
948
- value='lower right', # Por defecto 'lower right'
949
  label='Ubicaci贸n de la Leyenda'
950
  )
951
 
952
- # Campos de texto para personalizar t铆tulo y ejes
953
  with gr.Row():
954
  titulo_grafico_original = gr.Textbox(
955
  label="T铆tulo del Gr谩fico Original",
@@ -967,7 +853,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
967
  )
968
  eje_y_original = gr.Textbox(
969
  label="Etiqueta del Eje Y (Gr谩fico Original)",
970
- placeholder=f"Concentraci贸n Real Promedio ({unidad_input.value})"
971
  )
972
 
973
  with gr.Row():
@@ -977,32 +863,28 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
977
  )
978
  eje_y_personalizado = gr.Textbox(
979
  label="Etiqueta del Eje Y (Gr谩fico Personalizado)",
980
- placeholder=f"Concentraci贸n Real Promedio ({unidad_input.value})"
981
  )
982
 
983
  calcular_regresion_btn = gr.Button("Calcular Regresi贸n")
984
 
985
- # Salidas
986
  estado_regresion_output = gr.Textbox(label="Estado de la Regresi贸n", interactive=False)
987
  grafico_original_output = gr.Plot(label="Gr谩fico Original")
988
  grafico_personalizado_output = gr.Plot(label="Gr谩fico Personalizado")
989
  tabla_resumen_output = gr.DataFrame(label="Tabla Resumida")
990
 
991
- # Eventos para actualizar las opciones de filas
992
  tabla_output.change(
993
  fn=actualizar_opciones_filas,
994
  inputs=[tabla_output],
995
  outputs=[filas_seleccionadas, filas_seleccionadas_regresion]
996
  )
997
 
998
- # Evento al presionar el bot贸n Calcular
999
  calcular_btn.click(
1000
  fn=actualizar_analisis,
1001
  inputs=[tabla_output, replicas_slider, unidad_input, filas_seleccionadas, decimales_slider],
1002
  outputs=[estado_output, graficos_output, informe_output, tabla_output]
1003
  )
1004
 
1005
- # Evento para graficar con opciones seleccionadas
1006
  graficar_btn.click(
1007
  fn=actualizar_graficos,
1008
  inputs=[
@@ -1017,37 +899,51 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
1017
  outputs=graficos_output
1018
  )
1019
 
1020
- # Asegurar que la l铆nea ideal est茅 desmarcada por defecto
 
 
 
 
 
 
1021
  def resetear_linea_ideal():
1022
  return gr.update(value=False)
1023
 
1024
- # Desmarcar 'Mostrar L铆nea Ideal' en eventos de botones
1025
- calcular_btn.click(
1026
- fn=resetear_linea_ideal,
1027
- outputs=mostrar_linea_ideal
1028
- )
1029
- limpiar_btn.click(
1030
- fn=resetear_linea_ideal,
1031
- outputs=mostrar_linea_ideal
1032
- )
1033
- ajustar_decimales_btn.click(
1034
- fn=resetear_linea_ideal,
1035
- outputs=mostrar_linea_ideal
1036
- )
1037
- sinteticos_btn.click(
1038
- fn=resetear_linea_ideal,
1039
- outputs=mostrar_linea_ideal
1040
- )
1041
 
1042
- # Eventos de los botones adicionales, como limpiar, cargar ejemplos, ajustar decimales, etc.
1043
- # Evento para limpiar datos
1044
  limpiar_btn.click(
1045
  fn=limpiar_datos,
1046
  inputs=[replicas_slider],
1047
  outputs=[concentracion_input, unidad_input, filas_slider, tabla_output, estado_output, graficos_output, informe_output]
1048
  )
1049
 
1050
- # Eventos de los botones de ejemplo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1051
  ejemplo_ufc_btn.click(
1052
  fn=cargar_ejemplo_ufc,
1053
  inputs=[replicas_slider],
@@ -1060,28 +956,24 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
1060
  outputs=[concentracion_input, unidad_input, filas_slider, tabla_output]
1061
  )
1062
 
1063
- # Evento para generar datos sint茅ticos
1064
  sinteticos_btn.click(
1065
  fn=generar_datos_sinteticos_evento,
1066
  inputs=[tabla_output, replicas_slider, unidad_input],
1067
  outputs=tabla_output
1068
  )
1069
 
1070
- # Evento para cargar archivo Excel
1071
  cargar_excel_btn.upload(
1072
  fn=cargar_excel,
1073
  inputs=[cargar_excel_btn],
1074
  outputs=[concentracion_input, unidad_input, filas_slider, replicas_slider, tabla_output, estado_output, graficos_output, informe_output]
1075
  )
1076
 
1077
- # Evento al presionar el bot贸n Ajustar Decimales
1078
  ajustar_decimales_btn.click(
1079
  fn=ajustar_decimales_evento,
1080
  inputs=[tabla_output, decimales_slider],
1081
  outputs=tabla_output
1082
  )
1083
 
1084
- # Actualizar tabla al cambiar los par谩metros (sin borrar "Concentraci贸n Real")
1085
  def actualizar_tabla_wrapper(df, filas, conc, unidad, replicas, decimales):
1086
  return actualizar_tabla_evento(df, filas, conc, unidad, replicas, decimales)
1087
 
@@ -1115,7 +1007,6 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
1115
  outputs=tabla_output
1116
  )
1117
 
1118
- # Evento de copiar informe utilizando JavaScript
1119
  copiar_btn.click(
1120
  None,
1121
  [],
@@ -1134,7 +1025,6 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
1134
  """
1135
  )
1136
 
1137
- # Eventos de exportar informes
1138
  exportar_word_btn.click(
1139
  fn=exportar_word,
1140
  inputs=[tabla_output, informe_output, unidad_input, filas_seleccionadas],
@@ -1147,16 +1037,17 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
1147
  outputs=exportar_latex_file
1148
  )
1149
 
1150
- # Inicializar la interfaz con el ejemplo base
1151
  def iniciar_con_ejemplo():
 
 
 
 
 
 
 
 
1152
  n_replicas = 1
1153
- df = generar_tabla(7, 2000000, "UFC", n_replicas)
1154
- # Valores reales de ejemplo
1155
- df[f"Concentraci贸n Real 1 (UFC)"] = [2000000, 1600000, 1200000, 800000, 400000, 200000, 100000]
1156
- # Calcular promedio y desviaci贸n est谩ndar
1157
- df = calcular_promedio_desviacion(df, n_replicas, "UFC", 3)
1158
- filas_seleccionadas_inicial = [f"Fila {i+1}" for i in df.index]
1159
- estado, fig, informe, df = actualizar_analisis(df, n_replicas, "UFC", filas_seleccionadas_inicial, 3)
1160
  return (
1161
  2000000,
1162
  "UFC",
@@ -1165,8 +1056,8 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
1165
  estado,
1166
  fig,
1167
  informe,
1168
- filas_seleccionadas_inicial,
1169
- 3 # N煤mero de decimales
1170
  )
1171
 
1172
  interfaz.load(
@@ -1174,7 +1065,6 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
1174
  outputs=[concentracion_input, unidad_input, filas_slider, tabla_output, estado_output, graficos_output, informe_output, filas_seleccionadas, decimales_slider]
1175
  )
1176
 
1177
- # Evento al presionar el bot贸n de calcular regresi贸n
1178
  calcular_regresion_btn.click(
1179
  fn=calcular_regresion_tabla_principal,
1180
  inputs=[
@@ -1190,6 +1080,5 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
1190
  outputs=[estado_regresion_output, grafico_original_output, grafico_personalizado_output, tabla_resumen_output]
1191
  )
1192
 
1193
- # Lanzar la interfaz
1194
  if __name__ == "__main__":
1195
  interfaz.launch()
 
10
  from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
11
  import os
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  def ajustar_decimales_evento(df, decimales):
14
  df = df.copy()
 
15
  for col in df.columns:
16
  try:
17
  df[col] = pd.to_numeric(df[col], errors='ignore')
 
22
 
23
  def calcular_promedio_desviacion(df, n_replicas, unidad_medida, decimales):
24
  df = df.copy()
25
+ col_replicas = [c for c in df.columns if c.startswith("Concentraci贸n Real") and unidad_medida in c and "Promedio" not in c and "Desviaci贸n" not in c]
 
 
26
  for col in col_replicas:
27
  df[col] = pd.to_numeric(df[col], errors='coerce')
28
 
29
+ if len(col_replicas) > 0:
30
+ df[f"Concentraci贸n Real Promedio ({unidad_medida})"] = df[col_replicas].mean(axis=1)
31
+ else:
32
+ df[f"Concentraci贸n Real Promedio ({unidad_medida})"] = np.nan
33
 
34
+ if len(col_replicas) > 1:
35
  df[f"Desviaci贸n Est谩ndar ({unidad_medida})"] = df[col_replicas].std(ddof=1, axis=1)
36
  else:
37
  df[f"Desviaci贸n Est谩ndar ({unidad_medida})"] = 0.0
38
 
 
39
  df[f"Concentraci贸n Real Promedio ({unidad_medida})"] = df[f"Concentraci贸n Real Promedio ({unidad_medida})"].round(decimales)
40
  df[f"Desviaci贸n Est谩ndar ({unidad_medida})"] = df[f"Desviaci贸n Est谩ndar ({unidad_medida})"].round(decimales)
41
 
 
49
  col_real_promedio = f"Concentraci贸n Real Promedio ({unidad_medida})"
50
  col_desviacion = f"Desviaci贸n Est谩ndar ({unidad_medida})"
51
 
52
+ df_valid[col_predicha_num] = pd.to_numeric(df_valid[col_predicha_num], errors='coerce')
53
+ df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce')
54
+ df_valid[col_desviacion] = pd.to_numeric(df_valid[col_desviacion], errors='coerce').fillna(0)
55
+
56
+ if df_valid.empty or df_valid[col_predicha_num].isna().all() or df_valid[col_real_promedio].isna().all():
57
+ fig = plt.figure()
58
+ plt.text(0.5,0.5,"Datos insuficientes para generar el gr谩fico",ha='center',va='center')
59
+ return fig
60
 
 
61
  slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha_num], df_valid[col_real_promedio])
62
  df_valid['Ajuste Lineal'] = intercept + slope * df_valid[col_predicha_num]
63
 
 
64
  sns.set(style="whitegrid")
65
  plt.rcParams.update({'figure.autolayout': True})
66
 
67
  fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
68
 
 
69
  colors_puntos = sns.color_palette(palette_puntos, as_cmap=False)
70
  colors_linea_ajuste = sns.color_palette(palette_linea_ajuste, as_cmap=False)
71
  colors_linea_ideal = sns.color_palette(palette_linea_ideal, as_cmap=False)
72
  colors_barras_error = sns.color_palette(palette_barras_error, as_cmap=False)
73
 
 
74
  color_puntos = colors_puntos[0]
75
  color_linea_ajuste = colors_linea_ajuste[0]
76
  color_linea_ideal = colors_linea_ideal[0]
77
  color_barras_error = colors_barras_error[0]
78
 
 
79
  if mostrar_puntos:
80
  if n_replicas > 1:
 
81
  ax1.errorbar(
82
  df_valid[col_predicha_num],
83
  df_valid[col_real_promedio],
 
99
  marker=estilo_puntos
100
  )
101
 
 
102
  if mostrar_linea_ajuste:
103
  ax1.plot(
104
  df_valid[col_predicha_num],
 
109
  linestyle=estilo_linea_ajuste
110
  )
111
 
 
112
  if mostrar_linea_ideal:
113
  min_predicha = df_valid[col_predicha_num].min()
114
  max_predicha = df_valid[col_predicha_num].max()
 
124
  ax1.set_xlabel('Concentraci贸n Predicha', fontsize=12)
125
  ax1.set_ylabel('Concentraci贸n Real Promedio', fontsize=12)
126
 
 
127
  ax1.annotate(
128
  f'y = {intercept:.3f} + {slope:.3f}x\n$R^2$ = {r_value**2:.4f}',
129
  xy=(0.05, 0.95),
 
133
  verticalalignment='top'
134
  )
135
 
 
136
  ax1.legend(loc='lower right', fontsize=10)
137
 
 
138
  residuos = df_valid[col_real_promedio] - df_valid['Ajuste Lineal']
139
  ax2.scatter(
140
  df_valid[col_predicha_num],
 
152
  ax2.legend(loc='upper right', fontsize=10)
153
 
154
  plt.tight_layout()
155
+ plt.savefig('grafico.png')
156
  return fig
157
 
158
  def evaluar_calidad_calibracion(df_valid, r_squared, rmse, cv_percent):
 
159
  evaluacion = {
160
  "calidad": "",
161
  "recomendaciones": [],
 
177
  if cv_percent > 15:
178
  evaluacion["recomendaciones"].append("- La variabilidad es alta. Revise el procedimiento de diluci贸n")
179
 
180
+ mean_val = df_valid[df_valid.columns[-1]].astype(float).mean() if not df_valid.empty else 1
181
+ if mean_val == 0:
182
+ mean_val = 1
183
+ if rmse > 0.1 * mean_val:
184
  evaluacion["recomendaciones"].append("- El error de predicci贸n es significativo. Verifique la t茅cnica de medici贸n")
185
 
186
  return evaluacion
187
 
188
  def generar_informe_completo(df_valid, n_replicas, unidad_medida):
 
189
  col_predicha_num = "Concentraci贸n Predicha Num茅rica"
190
  col_real_promedio = f"Concentraci贸n Real Promedio ({unidad_medida})"
191
 
192
+ df_valid[col_predicha_num] = pd.to_numeric(df_valid[col_predicha_num], errors='coerce')
193
+ df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce')
194
+
195
+ if len(df_valid) < 2:
196
+ informe = "# Informe de Calibraci贸n 鈿狅笍\nNo hay suficientes datos para calcular la regresi贸n."
197
+ return informe, "鈿狅笍"
198
 
 
199
  slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha_num], df_valid[col_real_promedio])
200
  r_squared = r_value ** 2
201
  rmse = np.sqrt(((df_valid[col_real_promedio] - (intercept + slope * df_valid[col_predicha_num])) ** 2).mean())
202
+ cv = (df_valid[col_real_promedio].std() / df_valid[col_real_promedio].mean()) * 100 if df_valid[col_real_promedio].mean() != 0 else 0
203
 
 
204
  evaluacion = evaluar_calidad_calibracion(df_valid, r_squared, rmse, cv)
205
 
206
  informe = f"""# Informe de Calibraci贸n {evaluacion['estado']}
 
233
  if df is None or df.empty:
234
  return "Error en los datos", None, "No se pueden generar an谩lisis", df
235
 
 
236
  if not filas_seleccionadas:
237
  return "Se necesitan m谩s datos", None, "No se han seleccionado filas para el an谩lisis", df
238
 
239
  indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas]
240
 
 
241
  df = calcular_promedio_desviacion(df, n_replicas, unidad_medida, decimales)
242
 
243
  col_predicha_num = "Concentraci贸n Predicha Num茅rica"
244
  col_real_promedio = f"Concentraci贸n Real Promedio ({unidad_medida})"
245
 
 
246
  df[col_predicha_num] = pd.to_numeric(df[col_predicha_num], errors='coerce')
247
  df[col_real_promedio] = pd.to_numeric(df[col_real_promedio], errors='coerce')
248
 
249
  df_valid = df.dropna(subset=[col_predicha_num, col_real_promedio])
 
 
250
  df_valid.reset_index(drop=True, inplace=True)
251
 
 
252
  df_valid = df_valid.loc[indices_seleccionados]
253
 
254
  if len(df_valid) < 2:
255
  return "Se necesitan m谩s datos", None, "Se requieren al menos dos valores reales para el an谩lisis", df
256
 
 
257
  slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha_num], df_valid[col_real_promedio])
258
  df_valid['Ajuste Lineal'] = intercept + slope * df_valid[col_predicha_num]
259
 
 
260
  fig = generar_graficos(
261
  df_valid, n_replicas, unidad_medida,
262
  palette_puntos='deep', estilo_puntos='o',
 
264
  palette_linea_ideal='bright', estilo_linea_ideal='--',
265
  palette_barras_error='pastel',
266
  mostrar_linea_ajuste=True,
267
+ mostrar_linea_ideal=False,
268
  mostrar_puntos=True
269
  )
270
  informe, estado = generar_informe_completo(df_valid, n_replicas, unidad_medida)
 
281
  if df is None or df.empty:
282
  return None
283
 
 
284
  df = calcular_promedio_desviacion(df, n_replicas, unidad_medida, decimales)
285
 
286
  col_predicha_num = "Concentraci贸n Predicha Num茅rica"
287
  col_real_promedio = f"Concentraci贸n Real Promedio ({unidad_medida})"
288
 
 
289
  df[col_predicha_num] = pd.to_numeric(df[col_predicha_num], errors='coerce')
290
  df[col_real_promedio] = pd.to_numeric(df[col_real_promedio], errors='coerce')
291
 
292
  df_valid = df.dropna(subset=[col_predicha_num, col_real_promedio])
 
 
293
  df_valid.reset_index(drop=True, inplace=True)
294
 
 
295
  if not filas_seleccionadas:
296
  return None
297
 
298
  indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas]
 
 
299
  df_valid = df_valid.loc[indices_seleccionados]
300
 
301
  if len(df_valid) < 2:
302
  return None
303
 
 
304
  fig = generar_graficos(
305
  df_valid, n_replicas, unidad_medida,
306
  palette_puntos, estilo_puntos,
 
313
  return fig
314
 
315
  def exportar_informe_word(df_valid, informe_md, unidad_medida):
 
316
  doc = docx.Document()
317
 
 
318
  style = doc.styles['Normal']
319
  font = style.font
320
  font.name = 'Times New Roman'
321
  font.size = Pt(12)
322
 
 
323
  titulo = doc.add_heading('Informe de Calibraci贸n', 0)
324
  titulo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
325
 
 
326
  fecha = doc.add_paragraph(f"Fecha: {datetime.now().strftime('%d/%m/%Y %H:%M')}")
327
  fecha.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
328
 
 
329
  if os.path.exists('grafico.png'):
330
  doc.add_picture('grafico.png', width=Inches(6))
331
  ultimo_parrafo = doc.paragraphs[-1]
332
  ultimo_parrafo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
 
 
333
  leyenda = doc.add_paragraph('Figura 1. Gr谩fico de calibraci贸n.')
334
  leyenda_format = leyenda.paragraph_format
335
  leyenda_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
336
  leyenda.style = doc.styles['Caption']
337
 
 
338
  doc.add_heading('Resumen Estad铆stico', level=1)
339
  for linea in informe_md.split('\n'):
340
  if linea.startswith('##'):
 
342
  else:
343
  doc.add_paragraph(linea)
344
 
 
345
  doc.add_heading('Tabla de Datos de Calibraci贸n', level=1)
 
 
346
  tabla_datos = df_valid.reset_index(drop=True)
347
+ tabla_datos = tabla_datos.round(4)
348
  columnas = tabla_datos.columns.tolist()
349
  registros = tabla_datos.values.tolist()
350
 
 
351
  tabla = doc.add_table(rows=1 + len(registros), cols=len(columnas))
352
  tabla.style = 'Table Grid'
353
 
 
354
  hdr_cells = tabla.rows[0].cells
355
  for idx, col_name in enumerate(columnas):
356
  hdr_cells[idx].text = col_name
357
 
 
358
  for i, registro in enumerate(registros):
359
  row_cells = tabla.rows[i + 1].cells
360
  for j, valor in enumerate(registro):
361
  row_cells[j].text = str(valor)
362
 
 
363
  for row in tabla.rows:
364
  for cell in row.cells:
365
  for paragraph in cell.paragraphs:
366
  paragraph.style = doc.styles['Normal']
367
 
 
368
  filename = 'informe_calibracion.docx'
369
  doc.save(filename)
370
  return filename
371
 
372
  def exportar_informe_latex(df_valid, informe_md):
 
373
  informe_tex = r"""\documentclass{article}
374
  \usepackage[spanish]{babel}
375
  \usepackage{amsmath}
 
389
  def exportar_word(df, informe_md, unidad_medida, filas_seleccionadas):
390
  df_valid = df.copy()
391
  col_predicha_num = "Concentraci贸n Predicha Num茅rica"
392
+ col_real_promedio = [c for c in df_valid.columns if 'Real Promedio' in c][0] if any('Real Promedio' in c for c in df_valid.columns) else None
393
 
394
+ if col_predicha_num in df_valid.columns:
395
+ df_valid[col_predicha_num] = pd.to_numeric(df_valid[col_predicha_num], errors='coerce')
396
+ if col_real_promedio:
397
+ df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce')
398
+ df_valid = df_valid.dropna(subset=[col_predicha_num] if col_predicha_num else df_valid.columns[0], how='all')
 
 
399
  df_valid.reset_index(drop=True, inplace=True)
400
 
 
401
  if not filas_seleccionadas:
402
  return None
403
 
404
+ indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas if s.split(' ')[1].isdigit() and int(s.split(' ')[1]) - 1 < len(df_valid)]
405
+ if indices_seleccionados:
406
+ df_valid = df_valid.loc[indices_seleccionados]
407
+ else:
408
+ df_valid = df_valid.iloc[:0]
409
 
410
  if df_valid.empty:
411
  return None
412
 
413
  filename = exportar_informe_word(df_valid, informe_md, unidad_medida)
414
+ return filename
 
415
 
416
  def exportar_latex(df, informe_md, filas_seleccionadas):
417
  df_valid = df.copy()
418
  col_predicha_num = "Concentraci贸n Predicha Num茅rica"
419
+ col_real_promedio = [col for col in df_valid.columns if 'Real Promedio' in col][0] if any('Real Promedio' in c for c in df_valid.columns) else None
420
 
421
+ if col_predicha_num in df_valid.columns:
422
+ df_valid[col_predicha_num] = pd.to_numeric(df_valid[col_predicha_num], errors='coerce')
423
+ if col_real_promedio:
424
+ df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce')
425
 
426
+ df_valid = df_valid.dropna(subset=[col_predicha_num] if col_predicha_num else df_valid.columns[0], how='all')
 
 
427
  df_valid.reset_index(drop=True, inplace=True)
428
 
 
429
  if not filas_seleccionadas:
430
  return None
431
 
432
+ indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas if s.split(' ')[1].isdigit() and int(s.split(' ')[1]) - 1 < len(df_valid)]
433
+ if indices_seleccionados:
434
+ df_valid = df_valid.loc[indices_seleccionados]
435
+ else:
436
+ df_valid = df_valid.iloc[:0]
437
 
438
  if df_valid.empty:
439
  return None
440
 
441
  filename = exportar_informe_latex(df_valid, informe_md)
442
+ return filename
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
443
 
444
  def limpiar_datos(n_replicas):
445
+ df = pd.DataFrame({
446
+ "Soluci贸n": [1/(2**i) for i in range(7)],
447
+ "H2O": [1-(1/(2**i)) for i in range(7)],
448
+ "Factor de Diluci贸n": [(1/(1/(2**i))) for i in range(7)],
449
+ "Concentraci贸n Predicha Num茅rica": [2000000/(1/(1/(2**i))) for i in range(7)],
450
+ "Concentraci贸n Predicha (mg/L)": [2000000/(1/(1/(2**i))) for i in range(7)]
451
+ })
452
  return (
453
+ 2000000,
454
+ "UFC",
455
+ 7,
456
+ df,
457
+ "",
458
+ None,
459
+ ""
460
  )
461
 
462
  def generar_datos_sinteticos_evento(df, n_replicas, unidad_medida):
463
  df = df.copy()
464
  col_predicha_num = "Concentraci贸n Predicha Num茅rica"
465
+ if col_predicha_num in df.columns:
 
 
 
466
  df[col_predicha_num] = pd.to_numeric(df[col_predicha_num], errors='coerce')
467
+ for i in range(1, n_replicas + 1):
468
+ col_real = f"Concentraci贸n Real {i} ({unidad_medida})"
469
+ desviacion_std = 0.05 * df[col_predicha_num].mean()
470
+ valores_predichos = df[col_predicha_num].dropna().values
471
+ if len(valores_predichos) == 0:
472
+ continue
473
+ datos_sinteticos = valores_predichos + np.random.normal(0, desviacion_std, size=len(valores_predichos))
474
+ datos_sinteticos = np.maximum(0, datos_sinteticos)
475
+ datos_sinteticos = np.round(datos_sinteticos, 3)
476
+ df.loc[df[col_predicha_num].notna(), col_real] = datos_sinteticos
477
+ return df
478
 
479
+ def actualizar_tabla_evento(df, n_filas, conc, unidad, n_replicas, decimales):
480
+ df = df.copy()
481
+ if len(df) > n_filas:
482
+ df = df.iloc[:n_filas].reset_index(drop=True)
483
+ else:
484
+ for i in range(len(df), n_filas):
485
+ df.loc[i, df.columns] = np.nan
486
+ df = ajustar_decimales_evento(df, decimales)
487
  return df
488
 
489
+ def cargar_excel(file):
490
+ all_sheets = pd.read_excel(file.name, sheet_name=None)
 
491
 
492
+ if len(all_sheets) < 3:
493
+ return "El archivo debe tener al menos tres pesta帽as (Hoja1, Hoja2, Hoja3).", None, None, None, None, None, None, ""
 
494
 
495
+ sheet_names = list(all_sheets.keys())
496
+ sheet1_name = sheet_names[0]
497
+ sheet2_name = sheet_names[1]
498
+ sheet3_name = sheet_names[2]
 
 
499
 
500
+ df_sheet1 = all_sheets[sheet1_name]
501
+ df_sheet2 = all_sheets[sheet2_name]
502
+ df_sheet3 = all_sheets[sheet3_name]
503
 
504
+ df_base = df_sheet1.iloc[:, :5].copy()
505
 
506
+ primera_col = df_base.columns[0]
507
+ try:
508
+ parte_interna = primera_col.split('(')[1].split(')')[0]
509
+ partes = parte_interna.split()
510
+ unidad_medida = partes[-1]
511
+ concentracion_inicial = float("".join(partes[:-1]))
512
+ except:
513
+ concentracion_inicial = 2000000.0
514
+ unidad_medida = "UFC"
515
 
516
+ n_filas = len(df_base)
517
+ n_replicas = 2
 
518
 
519
+ df_sistema = df_base.copy()
 
 
 
 
 
520
 
521
+ col_replica_1 = df_sheet2.iloc[:n_filas, 1].values if df_sheet2.shape[1] > 1 else df_sheet2.iloc[:n_filas,0].values
522
+ col_replica_2 = df_sheet3.iloc[:n_filas, 1].values if df_sheet3.shape[1] > 1 else df_sheet3.iloc[:n_filas,0].values
523
 
524
+ df_sistema[f"Concentraci贸n Real 1 ({unidad_medida})"] = col_replica_1
525
+ df_sistema[f"Concentraci贸n Real 2 ({unidad_medida})"] = col_replica_2
 
 
526
 
527
+ return concentracion_inicial, unidad_medida, n_filas, n_replicas, df_sistema, "", None, ""
528
 
529
  def calcular_regresion_tabla_principal(df, unidad_medida, filas_seleccionadas_regresion,
530
  palette_puntos, estilo_puntos,
 
538
  return "Datos insuficientes", None, None, None
539
 
540
  col_concentracion = "Concentraci贸n Predicha Num茅rica"
541
+ col_real_promedio = f"Concentraci贸n Real Promedio ({unidad_medida})"
542
  col_desviacion = f"Desviaci贸n Est谩ndar ({unidad_medida})"
543
 
544
+ n_replicas = len([c for c in df.columns if 'Concentraci贸n Real' in c and 'Promedio' not in c and 'Desviaci贸n' not in c])
 
545
  df = calcular_promedio_desviacion(df, n_replicas, unidad_medida, decimales)
546
 
547
+ if col_concentracion not in df.columns or col_real_promedio not in df.columns:
548
+ return "Faltan columnas necesarias", None, None, None
 
 
549
 
550
+ df[col_concentracion] = pd.to_numeric(df[col_concentracion], errors='coerce')
551
+ df[col_real_promedio] = pd.to_numeric(df[col_real_promedio], errors='coerce')
552
+ df[col_desviacion] = pd.to_numeric(df[col_desviacion], errors='coerce').fillna(0)
553
 
554
+ df_valid = df.dropna(subset=[col_concentracion, col_real_promedio])
555
  df_valid.reset_index(drop=True, inplace=True)
556
 
 
557
  df_original = df_valid.copy()
558
 
 
559
  if not filas_seleccionadas_regresion:
560
  return "Se necesitan m谩s datos", None, None, None
561
 
562
+ indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas_regresion if s.split(' ')[1].isdigit() and int(s.split(' ')[1]) - 1 < len(df_valid)]
563
+ if len(indices_seleccionados) < 2:
564
+ return "Se requieren al menos dos puntos para calcular la regresi贸n", None, None, None
565
 
 
566
  df_valid = df_valid.loc[indices_seleccionados]
567
 
568
+ slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_concentracion], df_valid[col_real_promedio])
 
 
 
 
569
 
 
570
  sns.set(style="whitegrid")
571
  fig_original, ax_original = plt.subplots(figsize=(8, 6))
572
 
573
  ax_original.errorbar(
574
  df_original[col_concentracion],
575
+ df_original[col_real_promedio],
576
  yerr=df_original[col_desviacion],
577
  fmt='o',
578
  color='blue',
 
582
  label='Datos'
583
  )
584
 
585
+ slope_all, intercept_all, r_value_all, p_value_all, std_err_all = stats.linregress(df_original[col_concentracion], df_original[col_real_promedio])
 
586
 
587
  ax_original.plot(
588
  df_original[col_concentracion],
 
592
  label='Ajuste Lineal'
593
  )
594
 
 
595
  ax_original.set_xlabel(eje_x_original if eje_x_original else 'Concentraci贸n Predicha Num茅rica')
596
  ax_original.set_ylabel(eje_y_original if eje_y_original else f'Concentraci贸n Real Promedio ({unidad_medida})')
597
  ax_original.set_title(titulo_grafico_original if titulo_grafico_original else 'Regresi贸n Lineal: Concentraci贸n Real vs Concentraci贸n Predicha (Original)')
 
 
598
  ax_original.legend(loc=legend_location)
599
 
 
600
  ax_original.annotate(
601
  f'y = {intercept_all:.4f} + {slope_all:.4f}x\n$R^2$ = {r_value_all**2:.4f}',
602
  xy=(0.05, 0.95),
 
606
  verticalalignment='top'
607
  )
608
 
 
609
  sns.set(style="whitegrid")
610
  fig_personalizado, ax_personalizado = plt.subplots(figsize=(8, 6))
611
 
 
612
  colors_puntos = sns.color_palette(palette_puntos, as_cmap=False)
613
  colors_linea_ajuste = sns.color_palette(palette_linea_ajuste, as_cmap=False)
614
 
 
618
  if mostrar_puntos:
619
  ax_personalizado.errorbar(
620
  df_valid[col_concentracion],
621
+ df_valid[col_real_promedio],
622
  yerr=df_valid[col_desviacion],
623
  fmt=estilo_puntos,
624
  color=color_puntos,
 
637
  label='Ajuste Lineal'
638
  )
639
 
 
640
  ax_personalizado.set_xlabel(eje_x_personalizado if eje_x_personalizado else 'Concentraci贸n Predicha Num茅rica')
641
  ax_personalizado.set_ylabel(eje_y_personalizado if eje_y_personalizado else f'Concentraci贸n Real Promedio ({unidad_medida})')
642
  ax_personalizado.set_title(titulo_grafico_personalizado if titulo_grafico_personalizado else 'Regresi贸n Lineal Personalizada')
 
 
643
  ax_personalizado.legend(loc=legend_location)
644
 
 
645
  ax_personalizado.annotate(
646
  f'y = {intercept:.4f} + {slope:.4f}x\n$R^2$ = {r_value**2:.4f}',
647
  xy=(0.05, 0.95),
 
651
  verticalalignment='top'
652
  )
653
 
654
+ df_resumen = df_valid[[col_concentracion, col_real_promedio, col_desviacion]].copy()
 
655
  df_resumen.columns = ['Concentraci贸n Predicha', 'Absorbancia Promedio', 'Desviaci贸n Est谩ndar']
656
 
657
  return "Regresi贸n calculada exitosamente", fig_original, fig_personalizado, df_resumen
658
 
 
659
  def actualizar_opciones_filas(df):
660
  if df is None or df.empty:
661
  update = gr.update(choices=[], value=[])
 
664
  update = gr.update(choices=opciones, value=opciones)
665
  return update, update
666
 
 
667
  with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
668
  gr.Markdown("""
669
  # 馃搳 Sistema Avanzado de Calibraci贸n con An谩lisis Estad铆stico
670
+ Cargue el Excel con las 3 hojas (Hoja1 con 5 primeras columnas, Hoja2 y Hoja3 r茅plicas), edite y calcule.
671
  """)
672
 
673
  with gr.Tab("馃摑 Datos de Calibraci贸n"):
 
726
  estado_output = gr.Textbox(label="Estado", interactive=False)
727
  graficos_output = gr.Plot(label="Gr谩ficos de An谩lisis")
728
 
 
729
  filas_seleccionadas = gr.CheckboxGroup(
730
  label="Seleccione las filas a incluir en el an谩lisis",
731
  choices=[],
732
  value=[],
733
  )
734
 
 
735
  with gr.Row():
 
736
  paletas_colores = ["deep", "muted", "pastel", "bright", "dark", "colorblind", "Set1", "Set2", "Set3"]
737
 
738
  palette_puntos_dropdown = gr.Dropdown(
 
773
  label="Paleta Barras de Error"
774
  )
775
  mostrar_linea_ajuste = gr.Checkbox(value=True, label="Mostrar L铆nea de Ajuste")
776
+ mostrar_linea_ideal = gr.Checkbox(value=False, label="Mostrar L铆nea Ideal")
777
  mostrar_puntos = gr.Checkbox(value=True, label="Mostrar Puntos")
778
  graficar_btn = gr.Button("馃搳 Graficar", variant="primary")
779
 
780
+ # Bot贸n para recalcular informe
781
+ recalcular_btn = gr.Button("馃攧 Calcular Otra Vez", variant="secondary")
782
+
783
  with gr.Row():
784
  copiar_btn = gr.Button("馃搵 Copiar Informe", variant="secondary")
785
  exportar_word_btn = gr.Button("馃捑 Exportar Informe Word", variant="primary")
 
789
  exportar_word_file = gr.File(label="Informe en Word")
790
  exportar_latex_file = gr.File(label="Informe en LaTeX")
791
 
 
792
  informe_output = gr.Markdown(elem_id="informe_output")
793
 
794
  with gr.Tab("馃搱 Regresi贸n Absorbancia vs Concentraci贸n"):
795
  gr.Markdown("## Ajuste de Regresi贸n utilizando datos de la Tabla Principal")
796
 
 
797
  filas_seleccionadas_regresion = gr.CheckboxGroup(
798
  label="Seleccione las filas a incluir en el an谩lisis de regresi贸n",
799
  choices=[],
800
  value=[],
801
  )
802
 
 
803
  with gr.Row():
804
  paletas_colores = ["deep", "muted", "pastel", "bright", "dark", "colorblind", "Set1", "Set2", "Set3"]
805
  palette_puntos_regresion = gr.Dropdown(
 
832
  'right', 'center left', 'center right', 'lower center',
833
  'upper center', 'center'
834
  ],
835
+ value='lower right',
836
  label='Ubicaci贸n de la Leyenda'
837
  )
838
 
 
839
  with gr.Row():
840
  titulo_grafico_original = gr.Textbox(
841
  label="T铆tulo del Gr谩fico Original",
 
853
  )
854
  eje_y_original = gr.Textbox(
855
  label="Etiqueta del Eje Y (Gr谩fico Original)",
856
+ placeholder="Concentraci贸n Real Promedio (UFC)"
857
  )
858
 
859
  with gr.Row():
 
863
  )
864
  eje_y_personalizado = gr.Textbox(
865
  label="Etiqueta del Eje Y (Gr谩fico Personalizado)",
866
+ placeholder="Concentraci贸n Real Promedio (UFC)"
867
  )
868
 
869
  calcular_regresion_btn = gr.Button("Calcular Regresi贸n")
870
 
 
871
  estado_regresion_output = gr.Textbox(label="Estado de la Regresi贸n", interactive=False)
872
  grafico_original_output = gr.Plot(label="Gr谩fico Original")
873
  grafico_personalizado_output = gr.Plot(label="Gr谩fico Personalizado")
874
  tabla_resumen_output = gr.DataFrame(label="Tabla Resumida")
875
 
 
876
  tabla_output.change(
877
  fn=actualizar_opciones_filas,
878
  inputs=[tabla_output],
879
  outputs=[filas_seleccionadas, filas_seleccionadas_regresion]
880
  )
881
 
 
882
  calcular_btn.click(
883
  fn=actualizar_analisis,
884
  inputs=[tabla_output, replicas_slider, unidad_input, filas_seleccionadas, decimales_slider],
885
  outputs=[estado_output, graficos_output, informe_output, tabla_output]
886
  )
887
 
 
888
  graficar_btn.click(
889
  fn=actualizar_graficos,
890
  inputs=[
 
899
  outputs=graficos_output
900
  )
901
 
902
+ # Nuevo bot贸n "Calcular Otra Vez" para recalcular informe y gr谩fico
903
+ recalcular_btn.click(
904
+ fn=actualizar_analisis,
905
+ inputs=[tabla_output, replicas_slider, unidad_input, filas_seleccionadas, decimales_slider],
906
+ outputs=[estado_output, graficos_output, informe_output, tabla_output]
907
+ )
908
+
909
  def resetear_linea_ideal():
910
  return gr.update(value=False)
911
 
912
+ calcular_btn.click(fn=resetear_linea_ideal, outputs=mostrar_linea_ideal)
913
+ limpiar_btn.click(fn=resetear_linea_ideal, outputs=mostrar_linea_ideal)
914
+ ajustar_decimales_btn.click(fn=resetear_linea_ideal, outputs=mostrar_linea_ideal)
915
+ sinteticos_btn.click(fn=resetear_linea_ideal, outputs=mostrar_linea_ideal)
 
 
 
 
 
 
 
 
 
 
 
 
 
916
 
 
 
917
  limpiar_btn.click(
918
  fn=limpiar_datos,
919
  inputs=[replicas_slider],
920
  outputs=[concentracion_input, unidad_input, filas_slider, tabla_output, estado_output, graficos_output, informe_output]
921
  )
922
 
923
+ def cargar_ejemplo_ufc(n_replicas):
924
+ df = pd.DataFrame({
925
+ "Soluci贸n": [1.00,0.80,0.67,0.60,0.53,0.47,0.40],
926
+ "H2O": [0.00,0.20,0.33,0.40,0.47,0.53,0.60],
927
+ "Factor de Diluci贸n": [1.00,1.25,1.50,1.67,1.87,2.14,2.50],
928
+ "Concentraci贸n Predicha Num茅rica": [150,120,100,90,80,70,60],
929
+ "Concentraci贸n Predicha (mg/L)": [150,120,100,90,80,70,60]
930
+ })
931
+ for i in range(1, n_replicas + 1):
932
+ df[f"Concentraci贸n Real {i} (UFC)"] = np.nan
933
+ return 2000000, "UFC", 7, df
934
+
935
+ def cargar_ejemplo_od(n_replicas):
936
+ df = pd.DataFrame({
937
+ "Soluci贸n": [1.00,0.80,0.60,0.40,0.20,0.10,0.05],
938
+ "H2O": [0.00,0.20,0.40,0.60,0.80,0.90,0.95],
939
+ "Factor de Diluci贸n": [1.00,1.25,1.67,2.50,5.00,10.00,20.00],
940
+ "Concentraci贸n Predicha Num茅rica": [1.0,0.8,0.6,0.4,0.2,0.1,0.05],
941
+ "Concentraci贸n Predicha (mg/L)": [1.0,0.8,0.6,0.4,0.2,0.1,0.05]
942
+ })
943
+ for i in range(1, n_replicas + 1):
944
+ df[f"Concentraci贸n Real {i} (OD)"] = np.nan
945
+ return 1.000, "OD", 7, df
946
+
947
  ejemplo_ufc_btn.click(
948
  fn=cargar_ejemplo_ufc,
949
  inputs=[replicas_slider],
 
956
  outputs=[concentracion_input, unidad_input, filas_slider, tabla_output]
957
  )
958
 
 
959
  sinteticos_btn.click(
960
  fn=generar_datos_sinteticos_evento,
961
  inputs=[tabla_output, replicas_slider, unidad_input],
962
  outputs=tabla_output
963
  )
964
 
 
965
  cargar_excel_btn.upload(
966
  fn=cargar_excel,
967
  inputs=[cargar_excel_btn],
968
  outputs=[concentracion_input, unidad_input, filas_slider, replicas_slider, tabla_output, estado_output, graficos_output, informe_output]
969
  )
970
 
 
971
  ajustar_decimales_btn.click(
972
  fn=ajustar_decimales_evento,
973
  inputs=[tabla_output, decimales_slider],
974
  outputs=tabla_output
975
  )
976
 
 
977
  def actualizar_tabla_wrapper(df, filas, conc, unidad, replicas, decimales):
978
  return actualizar_tabla_evento(df, filas, conc, unidad, replicas, decimales)
979
 
 
1007
  outputs=tabla_output
1008
  )
1009
 
 
1010
  copiar_btn.click(
1011
  None,
1012
  [],
 
1025
  """
1026
  )
1027
 
 
1028
  exportar_word_btn.click(
1029
  fn=exportar_word,
1030
  inputs=[tabla_output, informe_output, unidad_input, filas_seleccionadas],
 
1037
  outputs=exportar_latex_file
1038
  )
1039
 
 
1040
  def iniciar_con_ejemplo():
1041
+ df = pd.DataFrame({
1042
+ "Soluci贸n": [1.00,0.80,0.67,0.60,0.53,0.47,0.40],
1043
+ "H2O": [0.00,0.20,0.33,0.40,0.47,0.53,0.60],
1044
+ "Factor de Diluci贸n": [1.00,1.25,1.50,1.67,1.87,2.14,2.50],
1045
+ "Concentraci贸n Predicha Num茅rica": [150,120,100,90,80,70,60],
1046
+ "Concentraci贸n Predicha (mg/L)": [150,120,100,90,80,70,60]
1047
+ })
1048
+ df["Concentraci贸n Real 1 (UFC)"] = [1.715,1.089,0.941,0.552,0.703,0.801,0.516]
1049
  n_replicas = 1
1050
+ estado, fig, informe, df = actualizar_analisis(df, n_replicas, "UFC", [f"Fila {i+1}" for i in df.index], 3)
 
 
 
 
 
 
1051
  return (
1052
  2000000,
1053
  "UFC",
 
1056
  estado,
1057
  fig,
1058
  informe,
1059
+ [f"Fila {i+1}" for i in df.index],
1060
+ 3
1061
  )
1062
 
1063
  interfaz.load(
 
1065
  outputs=[concentracion_input, unidad_input, filas_slider, tabla_output, estado_output, graficos_output, informe_output, filas_seleccionadas, decimales_slider]
1066
  )
1067
 
 
1068
  calcular_regresion_btn.click(
1069
  fn=calcular_regresion_tabla_principal,
1070
  inputs=[
 
1080
  outputs=[estado_regresion_output, grafico_original_output, grafico_personalizado_output, tabla_resumen_output]
1081
  )
1082
 
 
1083
  if __name__ == "__main__":
1084
  interfaz.launch()