C2MV commited on
Commit
e663364
verified
1 Parent(s): e84c450

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +202 -196
app.py CHANGED
@@ -11,26 +11,20 @@ from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
11
  import os
12
  from matplotlib.colors import to_hex
13
 
14
-
15
  def safe_color(c):
16
  if isinstance(c, str):
17
  c = c.strip()
18
  if c.lower().startswith('rgba('):
19
- # Extraer valores num茅ricos del string rgba
20
  vals = c.strip('rgba()').split(',')
21
  vals = [v.strip() for v in vals]
22
  if len(vals) == 4:
23
  r, g, b, a = [float(x) for x in vals]
24
- # Los valores en rgba(...) parecen ser mayores a 1, interpretamos que est谩n en escala 0-255.
25
- # Convertimos a rango [0,1]:
26
- r /= 255.0
27
- g /= 255.0
28
- b /= 255.0
29
- # Convertimos a hex
30
  c = to_hex((r, g, b, a))
31
  return c
32
 
33
-
34
  def ajustar_decimales_evento(df, decimales):
35
  df = df.copy()
36
  for col in df.columns:
@@ -41,66 +35,64 @@ def ajustar_decimales_evento(df, decimales):
41
  pass
42
  return df
43
 
44
- def calcular_promedio_desviacion(df, n_replicas, unidad_medida, decimales):
45
  df = df.copy()
46
- 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]
 
 
47
  for col in col_replicas:
48
  df[col] = pd.to_numeric(df[col], errors='coerce')
49
 
50
  if len(col_replicas) > 0:
51
- df[f"Concentraci贸n Real Promedio ({unidad_medida})"] = df[col_replicas].mean(axis=1)
52
  else:
53
- df[f"Concentraci贸n Real Promedio ({unidad_medida})"] = np.nan
54
 
55
  if len(col_replicas) > 1:
56
- df[f"Desviaci贸n Est谩ndar ({unidad_medida})"] = df[col_replicas].std(ddof=1, axis=1)
57
  else:
58
- df[f"Desviaci贸n Est谩ndar ({unidad_medida})"] = 0.0
59
 
60
- df[f"Concentraci贸n Real Promedio ({unidad_medida})"] = df[f"Concentraci贸n Real Promedio ({unidad_medida})"].round(decimales)
61
- df[f"Desviaci贸n Est谩ndar ({unidad_medida})"] = df[f"Desviaci贸n Est谩ndar ({unidad_medida})"].round(decimales)
62
 
63
  return df
64
 
65
- def generar_graficos(df_valid, n_replicas, unidad_medida,
66
  color_puntos, estilo_puntos,
67
  color_linea_ajuste, estilo_linea_ajuste,
68
- color_linea_ideal, estilo_linea_ideal,
69
  color_barras_error,
70
- mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos):
71
 
72
- # Convertir colores a formato v谩lido para matplotlib
73
  color_puntos = safe_color(color_puntos)
74
  color_linea_ajuste = safe_color(color_linea_ajuste)
75
- color_linea_ideal = safe_color(color_linea_ideal)
76
  color_barras_error = safe_color(color_barras_error)
77
 
78
- col_predicha_num = "Concentraci贸n Predicha Num茅rica"
79
- col_real_promedio = f"Concentraci贸n Real Promedio ({unidad_medida})"
80
- col_desviacion = f"Desviaci贸n Est谩ndar ({unidad_medida})"
81
 
82
- df_valid[col_predicha_num] = pd.to_numeric(df_valid[col_predicha_num], errors='coerce')
83
  df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce')
84
  df_valid[col_desviacion] = pd.to_numeric(df_valid[col_desviacion], errors='coerce').fillna(0)
85
 
86
- if df_valid.empty or df_valid[col_predicha_num].isna().all() or df_valid[col_real_promedio].isna().all():
87
  fig = plt.figure()
88
  plt.text(0.5,0.5,"Datos insuficientes para generar el gr谩fico",ha='center',va='center')
89
  return fig
90
 
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
  sns.set(style="whitegrid")
95
  plt.rcParams.update({'figure.autolayout': True})
96
 
97
  fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
98
 
99
- # Gr谩fico principal
100
  if mostrar_puntos:
101
  if n_replicas > 1:
102
  ax1.errorbar(
103
- df_valid[col_predicha_num],
104
  df_valid[col_real_promedio],
105
  yerr=df_valid[col_desviacion],
106
  fmt=estilo_puntos,
@@ -112,7 +104,7 @@ def generar_graficos(df_valid, n_replicas, unidad_medida,
112
  )
113
  else:
114
  ax1.scatter(
115
- df_valid[col_predicha_num],
116
  df_valid[col_real_promedio],
117
  color=color_puntos,
118
  s=100,
@@ -122,7 +114,7 @@ def generar_graficos(df_valid, n_replicas, unidad_medida,
122
 
123
  if mostrar_linea_ajuste:
124
  ax1.plot(
125
- df_valid[col_predicha_num],
126
  df_valid['Ajuste Lineal'],
127
  color=color_linea_ajuste,
128
  label='Ajuste Lineal',
@@ -130,20 +122,9 @@ def generar_graficos(df_valid, n_replicas, unidad_medida,
130
  linestyle=estilo_linea_ajuste
131
  )
132
 
133
- if mostrar_linea_ideal:
134
- min_predicha = df_valid[col_predicha_num].min()
135
- max_predicha = df_valid[col_predicha_num].max()
136
- ax1.plot(
137
- [min_predicha, max_predicha],
138
- [min_predicha, max_predicha],
139
- color=color_linea_ideal,
140
- linestyle=estilo_linea_ideal,
141
- label='Ideal'
142
- )
143
-
144
- ax1.set_title('Correlaci贸n entre Concentraci贸n Predicha y Real', fontsize=14)
145
- ax1.set_xlabel('Concentraci贸n Predicha', fontsize=12)
146
- ax1.set_ylabel('Concentraci贸n Real Promedio', fontsize=12)
147
 
148
  ax1.annotate(
149
  f'y = {intercept:.3f} + {slope:.3f}x\n$R^2$ = {r_value**2:.4f}',
@@ -156,10 +137,9 @@ def generar_graficos(df_valid, n_replicas, unidad_medida,
156
 
157
  ax1.legend(loc='lower right', fontsize=10)
158
 
159
- # Gr谩fico de residuos
160
  residuos = df_valid[col_real_promedio] - df_valid['Ajuste Lineal']
161
  ax2.scatter(
162
- df_valid[col_predicha_num],
163
  residuos,
164
  color=color_puntos,
165
  s=100,
@@ -169,7 +149,7 @@ def generar_graficos(df_valid, n_replicas, unidad_medida,
169
 
170
  ax2.axhline(y=0, color='black', linestyle='--', linewidth=1)
171
  ax2.set_title('Gr谩fico de Residuos', fontsize=14)
172
- ax2.set_xlabel('Concentraci贸n Predicha', fontsize=12)
173
  ax2.set_ylabel('Residuo', fontsize=12)
174
  ax2.legend(loc='upper right', fontsize=10)
175
 
@@ -207,20 +187,20 @@ def evaluar_calidad_calibracion(df_valid, r_squared, rmse, cv_percent):
207
 
208
  return evaluacion
209
 
210
- def generar_informe_completo(df_valid, n_replicas, unidad_medida):
211
- col_predicha_num = "Concentraci贸n Predicha Num茅rica"
212
- col_real_promedio = f"Concentraci贸n Real Promedio ({unidad_medida})"
213
 
214
- df_valid[col_predicha_num] = pd.to_numeric(df_valid[col_predicha_num], errors='coerce')
215
  df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce')
216
 
217
  if len(df_valid) < 2:
218
  informe = "# Informe de Calibraci贸n 鈿狅笍\nNo hay suficientes datos para calcular la regresi贸n."
219
  return informe, "鈿狅笍"
220
 
221
- slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha_num], df_valid[col_real_promedio])
222
  r_squared = r_value ** 2
223
- rmse = np.sqrt(((df_valid[col_real_promedio] - (intercept + slope * df_valid[col_predicha_num])) ** 2).mean())
224
  cv = (df_valid[col_real_promedio].std() / df_valid[col_real_promedio].mean()) * 100 if df_valid[col_real_promedio].mean() != 0 else 0
225
 
226
  evaluacion = evaluar_calidad_calibracion(df_valid, r_squared, rmse, cv)
@@ -251,7 +231,7 @@ Fecha: {datetime.now().strftime('%d/%m/%Y %H:%M')}
251
  """
252
  return informe, evaluacion['estado']
253
 
254
- def actualizar_analisis(df, n_replicas, unidad_medida, filas_seleccionadas, decimales):
255
  if df is None or df.empty:
256
  return "Error en los datos", None, "No se pueden generar an谩lisis", df
257
 
@@ -260,15 +240,15 @@ def actualizar_analisis(df, n_replicas, unidad_medida, filas_seleccionadas, deci
260
 
261
  indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas]
262
 
263
- df = calcular_promedio_desviacion(df, n_replicas, unidad_medida, decimales)
264
 
265
- col_predicha_num = "Concentraci贸n Predicha Num茅rica"
266
- col_real_promedio = f"Concentraci贸n Real Promedio ({unidad_medida})"
267
 
268
- df[col_predicha_num] = pd.to_numeric(df[col_predicha_num], errors='coerce')
269
  df[col_real_promedio] = pd.to_numeric(df[col_real_promedio], errors='coerce')
270
 
271
- df_valid = df.dropna(subset=[col_predicha_num, col_real_promedio])
272
  df_valid.reset_index(drop=True, inplace=True)
273
 
274
  df_valid = df_valid.loc[indices_seleccionados]
@@ -276,25 +256,22 @@ def actualizar_analisis(df, n_replicas, unidad_medida, filas_seleccionadas, deci
276
  if len(df_valid) < 2:
277
  return "Se necesitan m谩s datos", None, "Se requieren al menos dos valores reales para el an谩lisis", df
278
 
279
- slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha_num], df_valid[col_real_promedio])
280
- df_valid['Ajuste Lineal'] = intercept + slope * df_valid[col_predicha_num]
281
 
282
- # Generar gr谩fico con colores por defecto (podr谩n actualizarse con el bot贸n Graficar)
283
  fig = generar_graficos(
284
- df_valid, n_replicas, unidad_medida,
285
  color_puntos="#0000FF", estilo_puntos='o',
286
  color_linea_ajuste="#00FF00", estilo_linea_ajuste='-',
287
- color_linea_ideal="#FF0000", estilo_linea_ideal='--',
288
  color_barras_error="#FFA500",
289
  mostrar_linea_ajuste=True,
290
- mostrar_linea_ideal=False,
291
  mostrar_puntos=True
292
  )
293
- informe, estado = generar_informe_completo(df_valid, n_replicas, unidad_medida)
294
 
295
  return estado, fig, informe, df
296
 
297
- def exportar_informe_word(df_valid, informe_md, unidad_medida):
298
  doc = docx.Document()
299
 
300
  style = doc.styles['Normal']
@@ -368,16 +345,16 @@ def exportar_informe_latex(df_valid, informe_md):
368
  f.write(informe_tex)
369
  return filename
370
 
371
- def exportar_word(df, informe_md, unidad_medida, filas_seleccionadas):
372
  df_valid = df.copy()
373
- col_predicha_num = "Concentraci贸n Predicha Num茅rica"
374
- 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
375
 
376
- if col_predicha_num in df_valid.columns:
377
- df_valid[col_predicha_num] = pd.to_numeric(df_valid[col_predicha_num], errors='coerce')
378
  if col_real_promedio:
379
  df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce')
380
- df_valid = df_valid.dropna(subset=[col_predicha_num] if col_predicha_num else df_valid.columns[0], how='all')
381
  df_valid.reset_index(drop=True, inplace=True)
382
 
383
  if not filas_seleccionadas:
@@ -392,20 +369,21 @@ def exportar_word(df, informe_md, unidad_medida, filas_seleccionadas):
392
  if df_valid.empty:
393
  return None
394
 
395
- filename = exportar_informe_word(df_valid, informe_md, unidad_medida)
396
  return filename
397
 
398
  def exportar_latex(df, informe_md, filas_seleccionadas):
399
  df_valid = df.copy()
400
- col_predicha_num = "Concentraci贸n Predicha Num茅rica"
401
- 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
 
402
 
403
- if col_predicha_num in df_valid.columns:
404
- df_valid[col_predicha_num] = pd.to_numeric(df_valid[col_predicha_num], errors='coerce')
405
  if col_real_promedio:
406
  df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce')
407
 
408
- df_valid = df_valid.dropna(subset=[col_predicha_num] if col_predicha_num else df_valid.columns[0], how='all')
409
  df_valid.reset_index(drop=True, inplace=True)
410
 
411
  if not filas_seleccionadas:
@@ -424,16 +402,24 @@ def exportar_latex(df, informe_md, filas_seleccionadas):
424
  return filename
425
 
426
  def limpiar_datos(n_replicas):
 
 
 
 
 
427
  df = pd.DataFrame({
428
  "Soluci贸n": [1/(2**i) for i in range(7)],
429
  "H2O": [1-(1/(2**i)) for i in range(7)],
430
  "Factor de Diluci贸n": [(1/(1/(2**i))) for i in range(7)],
431
- "Concentraci贸n Predicha Num茅rica": [2000000/(1/(1/(2**i))) for i in range(7)],
432
- "Concentraci贸n Predicha (mg/L)": [2000000/(1/(1/(2**i))) for i in range(7)]
433
  })
 
 
 
 
434
  return (
435
  2000000,
436
- "UFC",
437
  7,
438
  df,
439
  "",
@@ -441,24 +427,24 @@ def limpiar_datos(n_replicas):
441
  ""
442
  )
443
 
444
- def generar_datos_sinteticos_evento(df, n_replicas, unidad_medida):
445
  df = df.copy()
446
- col_predicha_num = "Concentraci贸n Predicha Num茅rica"
447
- if col_predicha_num in df.columns:
448
- df[col_predicha_num] = pd.to_numeric(df[col_predicha_num], errors='coerce')
449
  for i in range(1, n_replicas + 1):
450
- col_real = f"Concentraci贸n Real {i} ({unidad_medida})"
451
- desviacion_std = 0.05 * df[col_predicha_num].mean()
452
- valores_predichos = df[col_predicha_num].dropna().values
453
  if len(valores_predichos) == 0:
454
  continue
455
  datos_sinteticos = valores_predichos + np.random.normal(0, desviacion_std, size=len(valores_predichos))
456
  datos_sinteticos = np.maximum(0, datos_sinteticos)
457
  datos_sinteticos = np.round(datos_sinteticos, 3)
458
- df.loc[df[col_predicha_num].notna(), col_real] = datos_sinteticos
459
  return df
460
 
461
- def actualizar_tabla_evento(df, n_filas, conc, unidad, n_replicas, decimales):
462
  df = df.copy()
463
  if len(df) > n_filas:
464
  df = df.iloc[:n_filas].reset_index(drop=True)
@@ -483,17 +469,30 @@ def cargar_excel(file):
483
  df_sheet2 = all_sheets[sheet2_name]
484
  df_sheet3 = all_sheets[sheet3_name]
485
 
486
- df_base = df_sheet1.iloc[:, :5].copy()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487
 
488
- primera_col = df_base.columns[0]
 
489
  try:
490
- parte_interna = primera_col.split('(')[1].split(')')[0]
491
- partes = parte_interna.split()
492
- unidad_medida = partes[-1]
493
- concentracion_inicial = float("".join(partes[:-1]))
494
  except:
495
  concentracion_inicial = 2000000.0
496
- unidad_medida = "UFC"
497
 
498
  n_filas = len(df_base)
499
  n_replicas = 2
@@ -503,10 +502,10 @@ def cargar_excel(file):
503
  col_replica_1 = df_sheet2.iloc[:n_filas, 1].values if df_sheet2.shape[1] > 1 else df_sheet2.iloc[:n_filas,0].values
504
  col_replica_2 = df_sheet3.iloc[:n_filas, 1].values if df_sheet3.shape[1] > 1 else df_sheet3.iloc[:n_filas,0].values
505
 
506
- df_sistema[f"Concentraci贸n Real 1 ({unidad_medida})"] = col_replica_1
507
- df_sistema[f"Concentraci贸n Real 2 ({unidad_medida})"] = col_replica_2
508
 
509
- return concentracion_inicial, unidad_medida, n_filas, n_replicas, df_sistema, "", None, ""
510
 
511
  def actualizar_opciones_filas(df):
512
  if df is None or df.empty:
@@ -516,7 +515,7 @@ def actualizar_opciones_filas(df):
516
  update = gr.update(choices=opciones, value=opciones)
517
  return update, update
518
 
519
- def calcular_regresion_tabla_principal(df, unidad_medida, filas_seleccionadas_regresion,
520
  color_puntos, estilo_puntos,
521
  color_linea_ajuste, estilo_linea_ajuste,
522
  mostrar_linea_ajuste, mostrar_puntos,
@@ -528,25 +527,24 @@ def calcular_regresion_tabla_principal(df, unidad_medida, filas_seleccionadas_re
528
  if df is None or df.empty:
529
  return "Datos insuficientes", None, None, None
530
 
531
- # Convertir colores a formato seguro
532
  color_puntos = safe_color(color_puntos)
533
  color_linea_ajuste = safe_color(color_linea_ajuste)
534
 
535
- col_concentracion = "Concentraci贸n Predicha Num茅rica"
536
- col_real_promedio = f"Concentraci贸n Real Promedio ({unidad_medida})"
537
- col_desviacion = f"Desviaci贸n Est谩ndar ({unidad_medida})"
538
 
539
- 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])
540
- df = calcular_promedio_desviacion(df, n_replicas, unidad_medida, decimales)
541
 
542
- if col_concentracion not in df.columns or col_real_promedio not in df.columns:
543
  return "Faltan columnas necesarias", None, None, None
544
 
545
- df[col_concentracion] = pd.to_numeric(df[col_concentracion], errors='coerce')
546
  df[col_real_promedio] = pd.to_numeric(df[col_real_promedio], errors='coerce')
547
  df[col_desviacion] = pd.to_numeric(df[col_desviacion], errors='coerce').fillna(0)
548
 
549
- df_valid = df.dropna(subset=[col_concentracion, col_real_promedio])
550
  df_valid.reset_index(drop=True, inplace=True)
551
 
552
  df_original = df_valid.copy()
@@ -560,14 +558,14 @@ def calcular_regresion_tabla_principal(df, unidad_medida, filas_seleccionadas_re
560
 
561
  df_valid = df_valid.loc[indices_seleccionados]
562
 
563
- slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_concentracion], df_valid[col_real_promedio])
564
 
565
  sns.set(style="whitegrid")
566
  fig_original, ax_original = plt.subplots(figsize=(8, 6))
567
 
568
  # Gr谩fico Original
569
  ax_original.errorbar(
570
- df_original[col_concentracion],
571
  df_original[col_real_promedio],
572
  yerr=df_original[col_desviacion],
573
  fmt=estilo_puntos,
@@ -578,19 +576,19 @@ def calcular_regresion_tabla_principal(df, unidad_medida, filas_seleccionadas_re
578
  label='Datos'
579
  )
580
 
581
- slope_all, intercept_all, r_value_all, p_value_all, std_err_all = stats.linregress(df_original[col_concentracion], df_original[col_real_promedio])
582
 
583
  ax_original.plot(
584
- df_original[col_concentracion],
585
- intercept_all + slope_all * df_original[col_concentracion],
586
  color=color_linea_ajuste,
587
  linestyle='-',
588
  label='Ajuste Lineal'
589
  )
590
 
591
- ax_original.set_xlabel(eje_x_original if eje_x_original else 'Concentraci贸n Predicha Num茅rica')
592
- ax_original.set_ylabel(eje_y_original if eje_y_original else f'Concentraci贸n Real Promedio ({unidad_medida})')
593
- ax_original.set_title(titulo_grafico_original if titulo_grafico_original else 'Regresi贸n Lineal: Concentraci贸n Real vs Concentraci贸n Predicha (Original)')
594
  ax_original.legend(loc=legend_location)
595
 
596
  ax_original.annotate(
@@ -608,7 +606,7 @@ def calcular_regresion_tabla_principal(df, unidad_medida, filas_seleccionadas_re
608
 
609
  if mostrar_puntos:
610
  ax_personalizado.errorbar(
611
- df_valid[col_concentracion],
612
  df_valid[col_real_promedio],
613
  yerr=df_valid[col_desviacion],
614
  fmt=estilo_puntos,
@@ -621,15 +619,15 @@ def calcular_regresion_tabla_principal(df, unidad_medida, filas_seleccionadas_re
621
 
622
  if mostrar_linea_ajuste:
623
  ax_personalizado.plot(
624
- df_valid[col_concentracion],
625
- intercept + slope * df_valid[col_concentracion],
626
  color=color_linea_ajuste,
627
  linestyle=estilo_linea_ajuste,
628
  label='Ajuste Lineal'
629
  )
630
 
631
- ax_personalizado.set_xlabel(eje_x_personalizado if eje_x_personalizado else 'Concentraci贸n Predicha Num茅rica')
632
- ax_personalizado.set_ylabel(eje_y_personalizado if eje_y_personalizado else f'Concentraci贸n Real Promedio ({unidad_medida})')
633
  ax_personalizado.set_title(titulo_grafico_personalizado if titulo_grafico_personalizado else 'Regresi贸n Lineal Personalizada')
634
  ax_personalizado.legend(loc=legend_location)
635
 
@@ -642,25 +640,27 @@ def calcular_regresion_tabla_principal(df, unidad_medida, filas_seleccionadas_re
642
  verticalalignment='top'
643
  )
644
 
645
- df_resumen = df_valid[[col_concentracion, col_real_promedio, col_desviacion]].copy()
646
- df_resumen.columns = ['Concentraci贸n Predicha', 'Absorbancia Promedio', 'Desviaci贸n Est谩ndar']
647
 
648
  return "Regresi贸n calculada exitosamente", fig_original, fig_personalizado, df_resumen
649
 
650
  def iniciar_con_ejemplo():
 
 
651
  df = pd.DataFrame({
652
  "Soluci贸n": [1.00,0.80,0.67,0.60,0.53,0.47,0.40],
653
  "H2O": [0.00,0.20,0.33,0.40,0.47,0.53,0.60],
654
  "Factor de Diluci贸n": [1.00,1.25,1.50,1.67,1.87,2.14,2.50],
655
- "Concentraci贸n Predicha Num茅rica": [150,120,100,90,80,70,60],
656
- "Concentraci贸n Predicha (mg/L)": [150,120,100,90,80,70,60],
657
- "Concentraci贸n Real 1 (UFC)": [1.715,1.089,0.941,0.552,0.703,0.801,0.516]
658
  })
659
  n_replicas = 1
660
- estado, fig, informe, df = actualizar_analisis(df, n_replicas, "UFC", [f"Fila {i+1}" for i in df.index], 3)
661
  return (
662
  2000000,
663
- "UFC",
 
664
  7,
665
  df,
666
  estado,
@@ -670,11 +670,13 @@ def iniciar_con_ejemplo():
670
  3
671
  )
672
 
673
-
674
  with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
675
  gr.Markdown("""
676
  # 馃搳 Sistema Avanzado de Calibraci贸n con An谩lisis Estad铆stico
677
  Configure los par谩metros, edite los valores en la tabla y luego presione "Calcular" para obtener el an谩lisis.
 
 
 
678
  """)
679
 
680
  with gr.Tab("馃摑 Datos de Calibraci贸n"):
@@ -684,10 +686,15 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
684
  label="Concentraci锟斤拷n Inicial",
685
  precision=0
686
  )
687
- unidad_input = gr.Textbox(
 
 
 
 
 
688
  value="UFC",
689
- label="Unidad de Medida",
690
- placeholder="UFC, OD, etc..."
691
  )
692
  filas_slider = gr.Slider(
693
  minimum=1,
@@ -753,16 +760,10 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
753
  label="Estilo L铆nea de Ajuste"
754
  )
755
 
756
- with gr.Row():
757
- color_linea_ideal_picker = gr.ColorPicker(label="Color L铆nea Ideal", value="#FF0000")
758
- estilo_linea_ideal_dropdown = gr.Dropdown(
759
- choices=["--", "-", "-.", ":"],
760
- value="--",
761
- label="Estilo L铆nea Ideal"
762
- )
763
  color_barras_error_picker = gr.ColorPicker(label="Color Barras de Error", value="#FFA500")
 
 
764
  mostrar_linea_ajuste = gr.Checkbox(value=True, label="Mostrar L铆nea de Ajuste")
765
- mostrar_linea_ideal = gr.Checkbox(value=False, label="Mostrar L铆nea Ideal")
766
  mostrar_puntos = gr.Checkbox(value=True, label="Mostrar Puntos")
767
  graficar_btn = gr.Button("馃搳 Graficar", variant="primary")
768
 
@@ -818,7 +819,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
818
  with gr.Row():
819
  titulo_grafico_original = gr.Textbox(
820
  label="T铆tulo del Gr谩fico Original",
821
- placeholder="Regresi贸n Lineal: Concentraci贸n Real vs Concentraci贸n Predicha (Original)"
822
  )
823
  titulo_grafico_personalizado = gr.Textbox(
824
  label="T铆tulo del Gr谩fico Personalizado",
@@ -828,21 +829,21 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
828
  with gr.Row():
829
  eje_x_original = gr.Textbox(
830
  label="Etiqueta del Eje X (Gr谩fico Original)",
831
- placeholder="Concentraci贸n Predicha Num茅rica"
832
  )
833
  eje_y_original = gr.Textbox(
834
  label="Etiqueta del Eje Y (Gr谩fico Original)",
835
- placeholder="Concentraci贸n Real Promedio (UFC)"
836
  )
837
 
838
  with gr.Row():
839
  eje_x_personalizado = gr.Textbox(
840
  label="Etiqueta del Eje X (Gr谩fico Personalizado)",
841
- placeholder="Concentraci贸n Predicha Num茅rica"
842
  )
843
  eje_y_personalizado = gr.Textbox(
844
  label="Etiqueta del Eje Y (Gr谩fico Personalizado)",
845
- placeholder="Concentraci贸n Real Promedio (UFC)"
846
  )
847
 
848
  calcular_regresion_btn = gr.Button("Calcular Regresi贸n")
@@ -860,29 +861,28 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
860
 
861
  calcular_btn.click(
862
  fn=actualizar_analisis,
863
- inputs=[tabla_output, replicas_slider, unidad_input, filas_seleccionadas, decimales_slider],
864
  outputs=[estado_output, graficos_output, informe_output, tabla_output]
865
  )
866
 
867
- def actualizar_graficos_custom(df, n_replicas, unidad_medida,
868
  color_puntos, estilo_puntos,
869
  color_linea_ajuste, estilo_linea_ajuste,
870
- color_linea_ideal, estilo_linea_ideal,
871
  color_barras_error,
872
- mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos,
873
  filas_seleccionadas, decimales):
874
  if df is None or df.empty:
875
  return None
876
 
877
- df = calcular_promedio_desviacion(df, n_replicas, unidad_medida, decimales)
878
 
879
- col_predicha_num = "Concentraci贸n Predicha Num茅rica"
880
- col_real_promedio = f"Concentraci贸n Real Promedio ({unidad_medida})"
881
 
882
- df[col_predicha_num] = pd.to_numeric(df[col_predicha_num], errors='coerce')
883
  df[col_real_promedio] = pd.to_numeric(df[col_real_promedio], errors='coerce')
884
 
885
- df_valid = df.dropna(subset=[col_predicha_num, col_real_promedio])
886
  df_valid.reset_index(drop=True, inplace=True)
887
 
888
  if not filas_seleccionadas:
@@ -895,24 +895,22 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
895
  return None
896
 
897
  fig = generar_graficos(
898
- df_valid, n_replicas, unidad_medida,
899
  color_puntos, estilo_puntos,
900
  color_linea_ajuste, estilo_linea_ajuste,
901
- color_linea_ideal, estilo_linea_ideal,
902
  color_barras_error,
903
- mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos
904
  )
905
  return fig
906
 
907
  graficar_btn.click(
908
  fn=actualizar_graficos_custom,
909
  inputs=[
910
- tabla_output, replicas_slider, unidad_input,
911
  color_puntos_picker, estilo_puntos_dropdown,
912
  color_linea_ajuste_picker, estilo_linea_ajuste_dropdown,
913
- color_linea_ideal_picker, estilo_linea_ideal_dropdown,
914
  color_barras_error_picker,
915
- mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos,
916
  filas_seleccionadas, decimales_slider
917
  ],
918
  outputs=graficos_output
@@ -920,70 +918,72 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
920
 
921
  recalcular_btn.click(
922
  fn=actualizar_analisis,
923
- inputs=[tabla_output, replicas_slider, unidad_input, filas_seleccionadas, decimales_slider],
924
  outputs=[estado_output, graficos_output, informe_output, tabla_output]
925
  )
926
 
927
- def resetear_linea_ideal():
928
- return gr.update(value=False)
929
 
930
- calcular_btn.click(fn=resetear_linea_ideal, outputs=mostrar_linea_ideal)
931
- limpiar_btn.click(fn=resetear_linea_ideal, outputs=mostrar_linea_ideal)
932
- ajustar_decimales_btn.click(fn=resetear_linea_ideal, outputs=mostrar_linea_ideal)
933
- sinteticos_btn.click(fn=resetear_linea_ideal, outputs=mostrar_linea_ideal)
934
 
935
  limpiar_btn.click(
936
  fn=limpiar_datos,
937
  inputs=[replicas_slider],
938
- outputs=[concentracion_input, unidad_input, filas_slider, tabla_output, estado_output, graficos_output, informe_output]
939
  )
940
 
941
  def cargar_ejemplo_ufc(n_replicas):
 
 
942
  df = pd.DataFrame({
943
  "Soluci贸n": [1.00,0.80,0.67,0.60,0.53,0.47,0.40],
944
  "H2O": [0.00,0.20,0.33,0.40,0.47,0.53,0.60],
945
  "Factor de Diluci贸n": [1.00,1.25,1.50,1.67,1.87,2.14,2.50],
946
- "Concentraci贸n Predicha Num茅rica": [150,120,100,90,80,70,60],
947
- "Concentraci贸n Predicha (mg/L)": [150,120,100,90,80,70,60]
948
  })
949
  for i in range(1, n_replicas + 1):
950
- df[f"Concentraci贸n Real {i} (UFC)"] = np.nan
951
- return 2000000, "UFC", 7, df
952
 
953
  def cargar_ejemplo_od(n_replicas):
 
 
954
  df = pd.DataFrame({
955
  "Soluci贸n": [1.00,0.80,0.60,0.40,0.20,0.10,0.05],
956
  "H2O": [0.00,0.20,0.40,0.60,0.80,0.90,0.95],
957
  "Factor de Diluci贸n": [1.00,1.25,1.67,2.50,5.00,10.00,20.00],
958
- "Concentraci贸n Predicha Num茅rica": [1.0,0.8,0.6,0.4,0.2,0.1,0.05],
959
- "Concentraci贸n Predicha (mg/L)": [1.0,0.8,0.6,0.4,0.2,0.1,0.05]
960
  })
961
  for i in range(1, n_replicas + 1):
962
- df[f"Concentraci贸n Real {i} (OD)"] = np.nan
963
- return 1.000, "OD", 7, df
964
 
965
  ejemplo_ufc_btn.click(
966
  fn=cargar_ejemplo_ufc,
967
  inputs=[replicas_slider],
968
- outputs=[concentracion_input, unidad_input, filas_slider, tabla_output]
969
  )
970
 
971
  ejemplo_od_btn.click(
972
  fn=cargar_ejemplo_od,
973
  inputs=[replicas_slider],
974
- outputs=[concentracion_input, unidad_input, filas_slider, tabla_output]
975
  )
976
 
977
  sinteticos_btn.click(
978
  fn=generar_datos_sinteticos_evento,
979
- inputs=[tabla_output, replicas_slider, unidad_input],
980
  outputs=tabla_output
981
  )
982
 
983
  cargar_excel_btn.upload(
984
  fn=cargar_excel,
985
  inputs=[cargar_excel_btn],
986
- outputs=[concentracion_input, unidad_input, filas_slider, replicas_slider, tabla_output, estado_output, graficos_output, informe_output]
987
  )
988
 
989
  ajustar_decimales_btn.click(
@@ -992,30 +992,36 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
992
  outputs=tabla_output
993
  )
994
 
995
- def actualizar_tabla_wrapper(df, filas, conc, unidad, replicas, decimales):
996
- return actualizar_tabla_evento(df, filas, conc, unidad, replicas, decimales)
997
 
998
  concentracion_input.change(
999
  fn=actualizar_tabla_wrapper,
1000
- inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider, decimales_slider],
 
 
 
 
 
 
1001
  outputs=tabla_output
1002
  )
1003
 
1004
- unidad_input.change(
1005
  fn=actualizar_tabla_wrapper,
1006
- inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider, decimales_slider],
1007
  outputs=tabla_output
1008
  )
1009
 
1010
  filas_slider.change(
1011
  fn=actualizar_tabla_wrapper,
1012
- inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider, decimales_slider],
1013
  outputs=tabla_output
1014
  )
1015
 
1016
  replicas_slider.change(
1017
  fn=actualizar_tabla_wrapper,
1018
- inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider, decimales_slider],
1019
  outputs=tabla_output
1020
  )
1021
 
@@ -1045,7 +1051,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
1045
 
1046
  exportar_word_btn.click(
1047
  fn=exportar_word,
1048
- inputs=[tabla_output, informe_output, unidad_input, filas_seleccionadas],
1049
  outputs=exportar_word_file
1050
  )
1051
 
@@ -1057,13 +1063,13 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
1057
 
1058
  interfaz.load(
1059
  fn=iniciar_con_ejemplo,
1060
- outputs=[concentracion_input, unidad_input, filas_slider, tabla_output, estado_output, graficos_output, informe_output, filas_seleccionadas, decimales_slider]
1061
  )
1062
 
1063
  calcular_regresion_btn.click(
1064
  fn=calcular_regresion_tabla_principal,
1065
  inputs=[
1066
- tabla_output, unidad_input, filas_seleccionadas_regresion,
1067
  color_puntos_regresion_picker, estilo_puntos_regresion,
1068
  color_linea_ajuste_regresion_picker, estilo_linea_ajuste_regresion,
1069
  mostrar_linea_ajuste_regresion, mostrar_puntos_regresion,
 
11
  import os
12
  from matplotlib.colors import to_hex
13
 
 
14
  def safe_color(c):
15
  if isinstance(c, str):
16
  c = c.strip()
17
  if c.lower().startswith('rgba('):
 
18
  vals = c.strip('rgba()').split(',')
19
  vals = [v.strip() for v in vals]
20
  if len(vals) == 4:
21
  r, g, b, a = [float(x) for x in vals]
22
+ r = r/255.0 if r > 1 else r
23
+ g = g/255.0 if g > 1 else g
24
+ b = b/255.0 if b > 1 else b
 
 
 
25
  c = to_hex((r, g, b, a))
26
  return c
27
 
 
28
  def ajustar_decimales_evento(df, decimales):
29
  df = df.copy()
30
  for col in df.columns:
 
35
  pass
36
  return df
37
 
38
+ def calcular_promedio_desviacion(df, n_replicas, unidad_predicha, unidad_replicas, decimales):
39
  df = df.copy()
40
+ # Ahora las columnas de las r茅plicas se llaman "Absorbancia Real X (unidad_replicas)"
41
+ col_replicas = [c for c in df.columns if c.startswith("Absorbancia Real") and f"({unidad_replicas})" in c and "Promedio" not in c and "Desviaci贸n" not in c]
42
+
43
  for col in col_replicas:
44
  df[col] = pd.to_numeric(df[col], errors='coerce')
45
 
46
  if len(col_replicas) > 0:
47
+ df[f"Absorbancia Real Promedio ({unidad_replicas})"] = df[col_replicas].mean(axis=1)
48
  else:
49
+ df[f"Absorbancia Real Promedio ({unidad_replicas})"] = np.nan
50
 
51
  if len(col_replicas) > 1:
52
+ df[f"Desviaci贸n Est谩ndar ({unidad_replicas})"] = df[col_replicas].std(ddof=1, axis=1)
53
  else:
54
+ df[f"Desviaci贸n Est谩ndar ({unidad_replicas})"] = 0.0
55
 
56
+ df[f"Absorbancia Real Promedio ({unidad_replicas})"] = df[f"Absorbancia Real Promedio ({unidad_replicas})"].round(decimales)
57
+ df[f"Desviaci贸n Est谩ndar ({unidad_replicas})"] = df[f"Desviaci贸n Est谩ndar ({unidad_replicas})"].round(decimales)
58
 
59
  return df
60
 
61
+ def generar_graficos(df_valid, n_replicas, unidad_predicha, unidad_replicas,
62
  color_puntos, estilo_puntos,
63
  color_linea_ajuste, estilo_linea_ajuste,
 
64
  color_barras_error,
65
+ mostrar_linea_ajuste, mostrar_puntos):
66
 
 
67
  color_puntos = safe_color(color_puntos)
68
  color_linea_ajuste = safe_color(color_linea_ajuste)
 
69
  color_barras_error = safe_color(color_barras_error)
70
 
71
+ col_predicha = f"Concentraci贸n Predicha ({unidad_predicha})"
72
+ col_real_promedio = f"Absorbancia Real Promedio ({unidad_replicas})"
73
+ col_desviacion = f"Desviaci贸n Est谩ndar ({unidad_replicas})"
74
 
75
+ df_valid[col_predicha] = pd.to_numeric(df_valid[col_predicha], errors='coerce')
76
  df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce')
77
  df_valid[col_desviacion] = pd.to_numeric(df_valid[col_desviacion], errors='coerce').fillna(0)
78
 
79
+ if df_valid.empty or df_valid[col_predicha].isna().all() or df_valid[col_real_promedio].isna().all():
80
  fig = plt.figure()
81
  plt.text(0.5,0.5,"Datos insuficientes para generar el gr谩fico",ha='center',va='center')
82
  return fig
83
 
84
+ slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha], df_valid[col_real_promedio])
85
+ df_valid['Ajuste Lineal'] = intercept + slope * df_valid[col_predicha]
86
 
87
  sns.set(style="whitegrid")
88
  plt.rcParams.update({'figure.autolayout': True})
89
 
90
  fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
91
 
 
92
  if mostrar_puntos:
93
  if n_replicas > 1:
94
  ax1.errorbar(
95
+ df_valid[col_predicha],
96
  df_valid[col_real_promedio],
97
  yerr=df_valid[col_desviacion],
98
  fmt=estilo_puntos,
 
104
  )
105
  else:
106
  ax1.scatter(
107
+ df_valid[col_predicha],
108
  df_valid[col_real_promedio],
109
  color=color_puntos,
110
  s=100,
 
114
 
115
  if mostrar_linea_ajuste:
116
  ax1.plot(
117
+ df_valid[col_predicha],
118
  df_valid['Ajuste Lineal'],
119
  color=color_linea_ajuste,
120
  label='Ajuste Lineal',
 
122
  linestyle=estilo_linea_ajuste
123
  )
124
 
125
+ ax1.set_title('Correlaci贸n entre Concentraci贸n Predicha y Absorbancia Real', fontsize=14)
126
+ ax1.set_xlabel(f'Concentraci贸n Predicha ({unidad_predicha})', fontsize=12)
127
+ ax1.set_ylabel(f'Absorbancia Real Promedio ({unidad_replicas})', fontsize=12)
 
 
 
 
 
 
 
 
 
 
 
128
 
129
  ax1.annotate(
130
  f'y = {intercept:.3f} + {slope:.3f}x\n$R^2$ = {r_value**2:.4f}',
 
137
 
138
  ax1.legend(loc='lower right', fontsize=10)
139
 
 
140
  residuos = df_valid[col_real_promedio] - df_valid['Ajuste Lineal']
141
  ax2.scatter(
142
+ df_valid[col_predicha],
143
  residuos,
144
  color=color_puntos,
145
  s=100,
 
149
 
150
  ax2.axhline(y=0, color='black', linestyle='--', linewidth=1)
151
  ax2.set_title('Gr谩fico de Residuos', fontsize=14)
152
+ ax2.set_xlabel(f'Concentraci贸n Predicha ({unidad_predicha})', fontsize=12)
153
  ax2.set_ylabel('Residuo', fontsize=12)
154
  ax2.legend(loc='upper right', fontsize=10)
155
 
 
187
 
188
  return evaluacion
189
 
190
+ def generar_informe_completo(df_valid, n_replicas, unidad_predicha, unidad_replicas):
191
+ col_predicha = f"Concentraci贸n Predicha ({unidad_predicha})"
192
+ col_real_promedio = f"Absorbancia Real Promedio ({unidad_replicas})"
193
 
194
+ df_valid[col_predicha] = pd.to_numeric(df_valid[col_predicha], errors='coerce')
195
  df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce')
196
 
197
  if len(df_valid) < 2:
198
  informe = "# Informe de Calibraci贸n 鈿狅笍\nNo hay suficientes datos para calcular la regresi贸n."
199
  return informe, "鈿狅笍"
200
 
201
+ slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha], df_valid[col_real_promedio])
202
  r_squared = r_value ** 2
203
+ rmse = np.sqrt(((df_valid[col_real_promedio] - (intercept + slope * df_valid[col_predicha])) ** 2).mean())
204
  cv = (df_valid[col_real_promedio].std() / df_valid[col_real_promedio].mean()) * 100 if df_valid[col_real_promedio].mean() != 0 else 0
205
 
206
  evaluacion = evaluar_calidad_calibracion(df_valid, r_squared, rmse, cv)
 
231
  """
232
  return informe, evaluacion['estado']
233
 
234
+ def actualizar_analisis(df, n_replicas, unidad_predicha, unidad_replicas, filas_seleccionadas, decimales):
235
  if df is None or df.empty:
236
  return "Error en los datos", None, "No se pueden generar an谩lisis", df
237
 
 
240
 
241
  indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas]
242
 
243
+ df = calcular_promedio_desviacion(df, n_replicas, unidad_predicha, unidad_replicas, decimales)
244
 
245
+ col_predicha = f"Concentraci贸n Predicha ({unidad_predicha})"
246
+ col_real_promedio = f"Absorbancia Real Promedio ({unidad_replicas})"
247
 
248
+ df[col_predicha] = pd.to_numeric(df[col_predicha], errors='coerce')
249
  df[col_real_promedio] = pd.to_numeric(df[col_real_promedio], errors='coerce')
250
 
251
+ df_valid = df.dropna(subset=[col_predicha, col_real_promedio])
252
  df_valid.reset_index(drop=True, inplace=True)
253
 
254
  df_valid = df_valid.loc[indices_seleccionados]
 
256
  if len(df_valid) < 2:
257
  return "Se necesitan m谩s datos", None, "Se requieren al menos dos valores reales para el an谩lisis", df
258
 
259
+ slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha], df_valid[col_real_promedio])
260
+ df_valid['Ajuste Lineal'] = intercept + slope * df_valid[col_predicha]
261
 
 
262
  fig = generar_graficos(
263
+ df_valid, n_replicas, unidad_predicha, unidad_replicas,
264
  color_puntos="#0000FF", estilo_puntos='o',
265
  color_linea_ajuste="#00FF00", estilo_linea_ajuste='-',
 
266
  color_barras_error="#FFA500",
267
  mostrar_linea_ajuste=True,
 
268
  mostrar_puntos=True
269
  )
270
+ informe, estado = generar_informe_completo(df_valid, n_replicas, unidad_predicha, unidad_replicas)
271
 
272
  return estado, fig, informe, df
273
 
274
+ def exportar_informe_word(df_valid, informe_md, unidad_predicha, unidad_replicas):
275
  doc = docx.Document()
276
 
277
  style = doc.styles['Normal']
 
345
  f.write(informe_tex)
346
  return filename
347
 
348
+ def exportar_word(df, informe_md, unidad_predicha, unidad_replicas, filas_seleccionadas):
349
  df_valid = df.copy()
350
+ col_predicha = f"Concentraci贸n Predicha ({unidad_predicha})"
351
+ col_real_promedio = [c for c in df_valid.columns if 'Absorbancia Real Promedio' in c][0] if any('Absorbancia Real Promedio' in c for c in df_valid.columns) else None
352
 
353
+ if col_predicha in df_valid.columns:
354
+ df_valid[col_predicha] = pd.to_numeric(df_valid[col_predicha], errors='coerce')
355
  if col_real_promedio:
356
  df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce')
357
+ df_valid = df_valid.dropna(subset=[col_predicha] if col_predicha else df_valid.columns[0], how='all')
358
  df_valid.reset_index(drop=True, inplace=True)
359
 
360
  if not filas_seleccionadas:
 
369
  if df_valid.empty:
370
  return None
371
 
372
+ filename = exportar_informe_word(df_valid, informe_md, unidad_predicha, unidad_replicas)
373
  return filename
374
 
375
  def exportar_latex(df, informe_md, filas_seleccionadas):
376
  df_valid = df.copy()
377
+ col_predicha = [c for c in df_valid.columns if 'Concentraci贸n Predicha' in c]
378
+ col_predicha = col_predicha[0] if col_predicha else None
379
+ col_real_promedio = [col for col in df_valid.columns if 'Absorbancia Real Promedio' in col][0] if any('Absorbancia Real Promedio' in c for c in df_valid.columns) else None
380
 
381
+ if col_predicha and col_predicha in df_valid.columns:
382
+ df_valid[col_predicha] = pd.to_numeric(df_valid[col_predicha], errors='coerce')
383
  if col_real_promedio:
384
  df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce')
385
 
386
+ df_valid = df_valid.dropna(subset=[col_predicha] if col_predicha else df_valid.columns[0], how='all')
387
  df_valid.reset_index(drop=True, inplace=True)
388
 
389
  if not filas_seleccionadas:
 
402
  return filename
403
 
404
  def limpiar_datos(n_replicas):
405
+ # Por defecto asignamos una unidad de predicha y replicas.
406
+ # Aqu铆 el ejemplo quedar谩 con mg/L para predicha y Absorbancia para r茅plicas
407
+ unidad_predicha = "mg/L"
408
+ unidad_replicas = "Abs"
409
+
410
  df = pd.DataFrame({
411
  "Soluci贸n": [1/(2**i) for i in range(7)],
412
  "H2O": [1-(1/(2**i)) for i in range(7)],
413
  "Factor de Diluci贸n": [(1/(1/(2**i))) for i in range(7)],
414
+ f"Concentraci贸n Predicha ({unidad_predicha})": [2000000/(1/(1/(2**i))) for i in range(7)]
 
415
  })
416
+ # Creamos columnas de absorbancia real vac铆as
417
+ for i in range(1, n_replicas+1):
418
+ df[f"Absorbancia Real {i} ({unidad_replicas})"] = np.nan
419
+
420
  return (
421
  2000000,
422
+ unidad_predicha,
423
  7,
424
  df,
425
  "",
 
427
  ""
428
  )
429
 
430
+ def generar_datos_sinteticos_evento(df, n_replicas, unidad_predicha, unidad_replicas):
431
  df = df.copy()
432
+ col_predicha = f"Concentraci贸n Predicha ({unidad_predicha})"
433
+ if col_predicha in df.columns:
434
+ df[col_predicha] = pd.to_numeric(df[col_predicha], errors='coerce')
435
  for i in range(1, n_replicas + 1):
436
+ col_real = f"Absorbancia Real {i} ({unidad_replicas})"
437
+ desviacion_std = 0.05 * df[col_predicha].mean()
438
+ valores_predichos = df[col_predicha].dropna().values
439
  if len(valores_predichos) == 0:
440
  continue
441
  datos_sinteticos = valores_predichos + np.random.normal(0, desviacion_std, size=len(valores_predichos))
442
  datos_sinteticos = np.maximum(0, datos_sinteticos)
443
  datos_sinteticos = np.round(datos_sinteticos, 3)
444
+ df.loc[df[col_predicha].notna(), col_real] = datos_sinteticos
445
  return df
446
 
447
+ def actualizar_tabla_evento(df, n_filas, conc, unidad_predicha, unidad_replicas, n_replicas, decimales):
448
  df = df.copy()
449
  if len(df) > n_filas:
450
  df = df.iloc[:n_filas].reset_index(drop=True)
 
469
  df_sheet2 = all_sheets[sheet2_name]
470
  df_sheet3 = all_sheets[sheet3_name]
471
 
472
+ df_base = df_sheet1.iloc[:, :4].copy()
473
+
474
+ # Intentar extraer la unidad de predicha del nombre de la columna de Concentraci贸n Predicha
475
+ # Suponemos que la cuarta columna es "Concentraci贸n Predicha (...)"
476
+ pred_col = df_base.columns[-1]
477
+ unidad_predicha = "mg/L"
478
+ try:
479
+ # intentar extraer unidad de predicha
480
+ if "(" in pred_col and ")" in pred_col:
481
+ unidad_predicha = pred_col.split("(")[1].split(")")[0].strip()
482
+ except:
483
+ unidad_predicha = "mg/L"
484
+
485
+ # Por defecto, la unidad de las r茅plicas (absorbancias)
486
+ unidad_replicas = "Abs"
487
 
488
+ # Intentar extraer la concentraci贸n inicial y unidad
489
+ # Si no se logra, usamos por defecto
490
  try:
491
+ primera_col = df_base.columns[0]
492
+ # Antes se intentaba extraer la unidad de aqu铆, ahora s贸lo la concentraci贸n inicial
493
+ concentracion_inicial = 2000000.0
 
494
  except:
495
  concentracion_inicial = 2000000.0
 
496
 
497
  n_filas = len(df_base)
498
  n_replicas = 2
 
502
  col_replica_1 = df_sheet2.iloc[:n_filas, 1].values if df_sheet2.shape[1] > 1 else df_sheet2.iloc[:n_filas,0].values
503
  col_replica_2 = df_sheet3.iloc[:n_filas, 1].values if df_sheet3.shape[1] > 1 else df_sheet3.iloc[:n_filas,0].values
504
 
505
+ df_sistema[f"Absorbancia Real 1 ({unidad_replicas})"] = col_replica_1
506
+ df_sistema[f"Absorbancia Real 2 ({unidad_replicas})"] = col_replica_2
507
 
508
+ return concentracion_inicial, unidad_predicha, n_filas, n_replicas, df_sistema, "", None, ""
509
 
510
  def actualizar_opciones_filas(df):
511
  if df is None or df.empty:
 
515
  update = gr.update(choices=opciones, value=opciones)
516
  return update, update
517
 
518
+ def calcular_regresion_tabla_principal(df, unidad_predicha, unidad_replicas, filas_seleccionadas_regresion,
519
  color_puntos, estilo_puntos,
520
  color_linea_ajuste, estilo_linea_ajuste,
521
  mostrar_linea_ajuste, mostrar_puntos,
 
527
  if df is None or df.empty:
528
  return "Datos insuficientes", None, None, None
529
 
 
530
  color_puntos = safe_color(color_puntos)
531
  color_linea_ajuste = safe_color(color_linea_ajuste)
532
 
533
+ col_predicha = f"Concentraci贸n Predicha ({unidad_predicha})"
534
+ col_real_promedio = f"Absorbancia Real Promedio ({unidad_replicas})"
535
+ col_desviacion = f"Desviaci贸n Est谩ndar ({unidad_replicas})"
536
 
537
+ n_replicas = len([c for c in df.columns if 'Absorbancia Real ' in c and 'Promedio' not in c and 'Desviaci贸n' not in c])
538
+ df = calcular_promedio_desviacion(df, n_replicas, unidad_predicha, unidad_replicas, decimales)
539
 
540
+ if col_predicha not in df.columns or col_real_promedio not in df.columns:
541
  return "Faltan columnas necesarias", None, None, None
542
 
543
+ df[col_predicha] = pd.to_numeric(df[col_predicha], errors='coerce')
544
  df[col_real_promedio] = pd.to_numeric(df[col_real_promedio], errors='coerce')
545
  df[col_desviacion] = pd.to_numeric(df[col_desviacion], errors='coerce').fillna(0)
546
 
547
+ df_valid = df.dropna(subset=[col_predicha, col_real_promedio])
548
  df_valid.reset_index(drop=True, inplace=True)
549
 
550
  df_original = df_valid.copy()
 
558
 
559
  df_valid = df_valid.loc[indices_seleccionados]
560
 
561
+ slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha], df_valid[col_real_promedio])
562
 
563
  sns.set(style="whitegrid")
564
  fig_original, ax_original = plt.subplots(figsize=(8, 6))
565
 
566
  # Gr谩fico Original
567
  ax_original.errorbar(
568
+ df_original[col_predicha],
569
  df_original[col_real_promedio],
570
  yerr=df_original[col_desviacion],
571
  fmt=estilo_puntos,
 
576
  label='Datos'
577
  )
578
 
579
+ slope_all, intercept_all, r_value_all, p_value_all, std_err_all = stats.linregress(df_original[col_predicha], df_original[col_real_promedio])
580
 
581
  ax_original.plot(
582
+ df_original[col_predicha],
583
+ intercept_all + slope_all * df_original[col_predicha],
584
  color=color_linea_ajuste,
585
  linestyle='-',
586
  label='Ajuste Lineal'
587
  )
588
 
589
+ ax_original.set_xlabel(eje_x_original if eje_x_original else f'Concentraci贸n Predicha ({unidad_predicha})')
590
+ ax_original.set_ylabel(eje_y_original if eje_y_original else f'Absorbancia Real Promedio ({unidad_replicas})')
591
+ ax_original.set_title(titulo_grafico_original if titulo_grafico_original else 'Regresi贸n Lineal: Absorbancia Real vs Concentraci贸n Predicha (Original)')
592
  ax_original.legend(loc=legend_location)
593
 
594
  ax_original.annotate(
 
606
 
607
  if mostrar_puntos:
608
  ax_personalizado.errorbar(
609
+ df_valid[col_predicha],
610
  df_valid[col_real_promedio],
611
  yerr=df_valid[col_desviacion],
612
  fmt=estilo_puntos,
 
619
 
620
  if mostrar_linea_ajuste:
621
  ax_personalizado.plot(
622
+ df_valid[col_predicha],
623
+ intercept + slope * df_valid[col_predicha],
624
  color=color_linea_ajuste,
625
  linestyle=estilo_linea_ajuste,
626
  label='Ajuste Lineal'
627
  )
628
 
629
+ ax_personalizado.set_xlabel(eje_x_personalizado if eje_x_personalizado else f'Concentraci贸n Predicha ({unidad_predicha})')
630
+ ax_personalizado.set_ylabel(eje_y_personalizado if eje_y_personalizado else f'Absorbancia Real Promedio ({unidad_replicas})')
631
  ax_personalizado.set_title(titulo_grafico_personalizado if titulo_grafico_personalizado else 'Regresi贸n Lineal Personalizada')
632
  ax_personalizado.legend(loc=legend_location)
633
 
 
640
  verticalalignment='top'
641
  )
642
 
643
+ df_resumen = df_valid[[col_predicha, col_real_promedio, col_desviacion]].copy()
644
+ df_resumen.columns = [f'Concentraci贸n Predicha ({unidad_predicha})', 'Absorbancia Promedio', 'Desviaci贸n Est谩ndar']
645
 
646
  return "Regresi贸n calculada exitosamente", fig_original, fig_personalizado, df_resumen
647
 
648
  def iniciar_con_ejemplo():
649
+ unidad_predicha = "mg/L"
650
+ unidad_replicas = "UFC"
651
  df = pd.DataFrame({
652
  "Soluci贸n": [1.00,0.80,0.67,0.60,0.53,0.47,0.40],
653
  "H2O": [0.00,0.20,0.33,0.40,0.47,0.53,0.60],
654
  "Factor de Diluci贸n": [1.00,1.25,1.50,1.67,1.87,2.14,2.50],
655
+ f"Concentraci贸n Predicha ({unidad_predicha})": [150,120,100,90,80,70,60],
656
+ f"Absorbancia Real 1 ({unidad_replicas})": [1.715,1.089,0.941,0.552,0.703,0.801,0.516]
 
657
  })
658
  n_replicas = 1
659
+ estado, fig, informe, df = actualizar_analisis(df, n_replicas, unidad_predicha, unidad_replicas, [f"Fila {i+1}" for i in df.index], 3)
660
  return (
661
  2000000,
662
+ unidad_predicha,
663
+ unidad_replicas,
664
  7,
665
  df,
666
  estado,
 
670
  3
671
  )
672
 
 
673
  with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
674
  gr.Markdown("""
675
  # 馃搳 Sistema Avanzado de Calibraci贸n con An谩lisis Estad铆stico
676
  Configure los par谩metros, edite los valores en la tabla y luego presione "Calcular" para obtener el an谩lisis.
677
+
678
+ **Nota:** La unidad en la pesta帽a uno "Unidad de Medida (Predicha)" ser谩 la unidad de la concentraci贸n predicha.
679
+ Debajo se encuentra otra "Unidad de Medida (Absorbancias)" para las r茅plicas (valores reales).
680
  """)
681
 
682
  with gr.Tab("馃摑 Datos de Calibraci贸n"):
 
686
  label="Concentraci锟斤拷n Inicial",
687
  precision=0
688
  )
689
+ unidad_predicha_input = gr.Textbox(
690
+ value="mg/L",
691
+ label="Unidad de Medida (Predicha)",
692
+ placeholder="mg/L, etc."
693
+ )
694
+ unidad_replicas_input = gr.Textbox(
695
  value="UFC",
696
+ label="Unidad de Medida (Absorbancias)",
697
+ placeholder="Abs, OD, UFC, etc."
698
  )
699
  filas_slider = gr.Slider(
700
  minimum=1,
 
760
  label="Estilo L铆nea de Ajuste"
761
  )
762
 
 
 
 
 
 
 
 
763
  color_barras_error_picker = gr.ColorPicker(label="Color Barras de Error", value="#FFA500")
764
+
765
+ with gr.Row():
766
  mostrar_linea_ajuste = gr.Checkbox(value=True, label="Mostrar L铆nea de Ajuste")
 
767
  mostrar_puntos = gr.Checkbox(value=True, label="Mostrar Puntos")
768
  graficar_btn = gr.Button("馃搳 Graficar", variant="primary")
769
 
 
819
  with gr.Row():
820
  titulo_grafico_original = gr.Textbox(
821
  label="T铆tulo del Gr谩fico Original",
822
+ placeholder="Regresi贸n Lineal: Absorbancia Real vs Concentraci贸n Predicha (Original)"
823
  )
824
  titulo_grafico_personalizado = gr.Textbox(
825
  label="T铆tulo del Gr谩fico Personalizado",
 
829
  with gr.Row():
830
  eje_x_original = gr.Textbox(
831
  label="Etiqueta del Eje X (Gr谩fico Original)",
832
+ placeholder="Concentraci贸n Predicha (mg/L)"
833
  )
834
  eje_y_original = gr.Textbox(
835
  label="Etiqueta del Eje Y (Gr谩fico Original)",
836
+ placeholder="Absorbancia Real Promedio (UFC)"
837
  )
838
 
839
  with gr.Row():
840
  eje_x_personalizado = gr.Textbox(
841
  label="Etiqueta del Eje X (Gr谩fico Personalizado)",
842
+ placeholder="Concentraci贸n Predicha (mg/L)"
843
  )
844
  eje_y_personalizado = gr.Textbox(
845
  label="Etiqueta del Eje Y (Gr谩fico Personalizado)",
846
+ placeholder="Absorbancia Real Promedio (UFC)"
847
  )
848
 
849
  calcular_regresion_btn = gr.Button("Calcular Regresi贸n")
 
861
 
862
  calcular_btn.click(
863
  fn=actualizar_analisis,
864
+ inputs=[tabla_output, replicas_slider, unidad_predicha_input, unidad_replicas_input, filas_seleccionadas, decimales_slider],
865
  outputs=[estado_output, graficos_output, informe_output, tabla_output]
866
  )
867
 
868
+ def actualizar_graficos_custom(df, n_replicas, unidad_predicha, unidad_replicas,
869
  color_puntos, estilo_puntos,
870
  color_linea_ajuste, estilo_linea_ajuste,
 
871
  color_barras_error,
872
+ mostrar_linea_ajuste, mostrar_puntos,
873
  filas_seleccionadas, decimales):
874
  if df is None or df.empty:
875
  return None
876
 
877
+ df = calcular_promedio_desviacion(df, n_replicas, unidad_predicha, unidad_replicas, decimales)
878
 
879
+ col_predicha = f"Concentraci贸n Predicha ({unidad_predicha})"
880
+ col_real_promedio = f"Absorbancia Real Promedio ({unidad_replicas})"
881
 
882
+ df[col_predicha] = pd.to_numeric(df[col_predicha], errors='coerce')
883
  df[col_real_promedio] = pd.to_numeric(df[col_real_promedio], errors='coerce')
884
 
885
+ df_valid = df.dropna(subset=[col_predicha, col_real_promedio])
886
  df_valid.reset_index(drop=True, inplace=True)
887
 
888
  if not filas_seleccionadas:
 
895
  return None
896
 
897
  fig = generar_graficos(
898
+ df_valid, n_replicas, unidad_predicha, unidad_replicas,
899
  color_puntos, estilo_puntos,
900
  color_linea_ajuste, estilo_linea_ajuste,
 
901
  color_barras_error,
902
+ mostrar_linea_ajuste, mostrar_puntos
903
  )
904
  return fig
905
 
906
  graficar_btn.click(
907
  fn=actualizar_graficos_custom,
908
  inputs=[
909
+ tabla_output, replicas_slider, unidad_predicha_input, unidad_replicas_input,
910
  color_puntos_picker, estilo_puntos_dropdown,
911
  color_linea_ajuste_picker, estilo_linea_ajuste_dropdown,
 
912
  color_barras_error_picker,
913
+ mostrar_linea_ajuste, mostrar_puntos,
914
  filas_seleccionadas, decimales_slider
915
  ],
916
  outputs=graficos_output
 
918
 
919
  recalcular_btn.click(
920
  fn=actualizar_analisis,
921
+ inputs=[tabla_output, replicas_slider, unidad_predicha_input, unidad_replicas_input, filas_seleccionadas, decimales_slider],
922
  outputs=[estado_output, graficos_output, informe_output, tabla_output]
923
  )
924
 
925
+ def resetear_valores():
926
+ return gr.update(value=True), gr.update(value=True)
927
 
928
+ calcular_btn.click(fn=resetear_valores, outputs=[mostrar_linea_ajuste, mostrar_puntos])
929
+ limpiar_btn.click(fn=resetear_valores, outputs=[mostrar_linea_ajuste, mostrar_puntos])
930
+ ajustar_decimales_btn.click(fn=resetear_valores, outputs=[mostrar_linea_ajuste, mostrar_puntos])
931
+ sinteticos_btn.click(fn=resetear_valores, outputs=[mostrar_linea_ajuste, mostrar_puntos])
932
 
933
  limpiar_btn.click(
934
  fn=limpiar_datos,
935
  inputs=[replicas_slider],
936
+ outputs=[concentracion_input, unidad_predicha_input, filas_slider, tabla_output, estado_output, graficos_output, informe_output]
937
  )
938
 
939
  def cargar_ejemplo_ufc(n_replicas):
940
+ unidad_predicha = "mg/L"
941
+ unidad_replicas = "UFC"
942
  df = pd.DataFrame({
943
  "Soluci贸n": [1.00,0.80,0.67,0.60,0.53,0.47,0.40],
944
  "H2O": [0.00,0.20,0.33,0.40,0.47,0.53,0.60],
945
  "Factor de Diluci贸n": [1.00,1.25,1.50,1.67,1.87,2.14,2.50],
946
+ f"Concentraci贸n Predicha ({unidad_predicha})": [150,120,100,90,80,70,60]
 
947
  })
948
  for i in range(1, n_replicas + 1):
949
+ df[f"Absorbancia Real {i} ({unidad_replicas})"] = np.nan
950
+ return 2000000, unidad_predicha, unidad_replicas, 7, df
951
 
952
  def cargar_ejemplo_od(n_replicas):
953
+ unidad_predicha = "mg/L"
954
+ unidad_replicas = "OD"
955
  df = pd.DataFrame({
956
  "Soluci贸n": [1.00,0.80,0.60,0.40,0.20,0.10,0.05],
957
  "H2O": [0.00,0.20,0.40,0.60,0.80,0.90,0.95],
958
  "Factor de Diluci贸n": [1.00,1.25,1.67,2.50,5.00,10.00,20.00],
959
+ f"Concentraci贸n Predicha ({unidad_predicha})": [1.0,0.8,0.6,0.4,0.2,0.1,0.05]
 
960
  })
961
  for i in range(1, n_replicas + 1):
962
+ df[f"Absorbancia Real {i} ({unidad_replicas})"] = np.nan
963
+ return 1.000, unidad_predicha, unidad_replicas, 7, df
964
 
965
  ejemplo_ufc_btn.click(
966
  fn=cargar_ejemplo_ufc,
967
  inputs=[replicas_slider],
968
+ outputs=[concentracion_input, unidad_predicha_input, unidad_replicas_input, filas_slider, tabla_output]
969
  )
970
 
971
  ejemplo_od_btn.click(
972
  fn=cargar_ejemplo_od,
973
  inputs=[replicas_slider],
974
+ outputs=[concentracion_input, unidad_predicha_input, unidad_replicas_input, filas_slider, tabla_output]
975
  )
976
 
977
  sinteticos_btn.click(
978
  fn=generar_datos_sinteticos_evento,
979
+ inputs=[tabla_output, replicas_slider, unidad_predicha_input, unidad_replicas_input],
980
  outputs=tabla_output
981
  )
982
 
983
  cargar_excel_btn.upload(
984
  fn=cargar_excel,
985
  inputs=[cargar_excel_btn],
986
+ outputs=[concentracion_input, unidad_predicha_input, filas_slider, replicas_slider, tabla_output, estado_output, graficos_output, informe_output]
987
  )
988
 
989
  ajustar_decimales_btn.click(
 
992
  outputs=tabla_output
993
  )
994
 
995
+ def actualizar_tabla_wrapper(df, filas, conc, unidad_predicha, unidad_replicas, replicas, decimales):
996
+ return actualizar_tabla_evento(df, filas, conc, unidad_predicha, unidad_replicas, replicas, decimales)
997
 
998
  concentracion_input.change(
999
  fn=actualizar_tabla_wrapper,
1000
+ inputs=[tabla_output, filas_slider, concentracion_input, unidad_predicha_input, unidad_replicas_input, replicas_slider, decimales_slider],
1001
+ outputs=tabla_output
1002
+ )
1003
+
1004
+ unidad_predicha_input.change(
1005
+ fn=actualizar_tabla_wrapper,
1006
+ inputs=[tabla_output, filas_slider, concentracion_input, unidad_predicha_input, unidad_replicas_input, replicas_slider, decimales_slider],
1007
  outputs=tabla_output
1008
  )
1009
 
1010
+ unidad_replicas_input.change(
1011
  fn=actualizar_tabla_wrapper,
1012
+ inputs=[tabla_output, filas_slider, concentracion_input, unidad_predicha_input, unidad_replicas_input, replicas_slider, decimales_slider],
1013
  outputs=tabla_output
1014
  )
1015
 
1016
  filas_slider.change(
1017
  fn=actualizar_tabla_wrapper,
1018
+ inputs=[tabla_output, filas_slider, concentracion_input, unidad_predicha_input, unidad_replicas_input, replicas_slider, decimales_slider],
1019
  outputs=tabla_output
1020
  )
1021
 
1022
  replicas_slider.change(
1023
  fn=actualizar_tabla_wrapper,
1024
+ inputs=[tabla_output, filas_slider, concentracion_input, unidad_predicha_input, unidad_replicas_input, replicas_slider, decimales_slider],
1025
  outputs=tabla_output
1026
  )
1027
 
 
1051
 
1052
  exportar_word_btn.click(
1053
  fn=exportar_word,
1054
+ inputs=[tabla_output, informe_output, unidad_predicha_input, unidad_replicas_input, filas_seleccionadas],
1055
  outputs=exportar_word_file
1056
  )
1057
 
 
1063
 
1064
  interfaz.load(
1065
  fn=iniciar_con_ejemplo,
1066
+ outputs=[concentracion_input, unidad_predicha_input, unidad_replicas_input, filas_slider, tabla_output, estado_output, graficos_output, informe_output, filas_seleccionadas, decimales_slider]
1067
  )
1068
 
1069
  calcular_regresion_btn.click(
1070
  fn=calcular_regresion_tabla_principal,
1071
  inputs=[
1072
+ tabla_output, unidad_predicha_input, unidad_replicas_input, filas_seleccionadas_regresion,
1073
  color_puntos_regresion_picker, estilo_puntos_regresion,
1074
  color_linea_ajuste_regresion_picker, estilo_linea_ajuste_regresion,
1075
  mostrar_linea_ajuste_regresion, mostrar_puntos_regresion,