C2MV commited on
Commit
0810c82
1 Parent(s): 7edb855

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +764 -99
app.py CHANGED
@@ -9,7 +9,6 @@ import docx
9
  from docx.shared import Inches, Pt
10
  from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
11
  import os
12
- import re
13
 
14
  def generar_tabla(n_filas, concentracion_inicial, unidad_medida, n_replicas):
15
  valores_base = [1.000, 0.800, 0.600, 0.400, 0.200, 0.100, 0.050]
@@ -57,43 +56,87 @@ def ajustar_decimales_evento(df, decimales):
57
  pass
58
  return df
59
 
60
- def generar_graficos(df_valid, unidad_medida, col_absorbancia, palette_puntos, estilo_puntos,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  palette_linea_ajuste, estilo_linea_ajuste,
62
- mostrar_linea_ajuste, mostrar_puntos):
 
63
  col_predicha_num = "Concentración Predicha Numérica"
 
 
64
 
65
  # Convertir a numérico
66
  df_valid[col_predicha_num] = df_valid[col_predicha_num].astype(float)
67
- df_valid[col_absorbancia] = df_valid[col_absorbancia].astype(float)
 
68
 
69
  # Calcular regresión lineal
70
- slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha_num], df_valid[col_absorbancia])
71
  df_valid['Ajuste Lineal'] = intercept + slope * df_valid[col_predicha_num]
72
 
73
  # Configurar estilos
74
  sns.set(style="whitegrid")
75
  plt.rcParams.update({'figure.autolayout': True})
76
 
77
- fig, ax1 = plt.subplots(figsize=(7, 6))
78
 
79
  # Obtener colores de las paletas
80
  colors_puntos = sns.color_palette(palette_puntos, as_cmap=False)
81
  colors_linea_ajuste = sns.color_palette(palette_linea_ajuste, as_cmap=False)
 
 
82
 
83
  # Seleccionar colores
84
  color_puntos = colors_puntos[0]
85
  color_linea_ajuste = colors_linea_ajuste[0]
 
 
86
 
87
  # Gráfico de dispersión con línea de regresión
88
  if mostrar_puntos:
89
- ax1.scatter(
90
- df_valid[col_predicha_num],
91
- df_valid[col_absorbancia],
92
- color=color_puntos,
93
- s=100,
94
- label='Datos',
95
- marker=estilo_puntos
96
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
  # Línea de ajuste
99
  if mostrar_linea_ajuste:
@@ -106,9 +149,21 @@ def generar_graficos(df_valid, unidad_medida, col_absorbancia, palette_puntos, e
106
  linestyle=estilo_linea_ajuste
107
  )
108
 
109
- ax1.set_title('Correlación entre Concentración Predicha y Absorbancia', fontsize=14)
 
 
 
 
 
 
 
 
 
 
 
 
110
  ax1.set_xlabel('Concentración Predicha', fontsize=12)
111
- ax1.set_ylabel('Absorbancia', fontsize=12)
112
 
113
  # Añadir ecuación y R² en el gráfico
114
  ax1.annotate(
@@ -123,6 +178,23 @@ def generar_graficos(df_valid, unidad_medida, col_absorbancia, palette_puntos, e
123
  # Posicionar la leyenda
124
  ax1.legend(loc='lower right', fontsize=10)
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  plt.tight_layout()
127
  plt.savefig('grafico.png') # Guardar el gráfico para incluirlo en el informe
128
  return fig
@@ -155,19 +227,20 @@ def evaluar_calidad_calibracion(df_valid, r_squared, rmse, cv_percent):
155
 
156
  return evaluacion
157
 
158
- def generar_informe_completo(df_valid, unidad_medida, col_absorbancia):
159
  """Generar un informe completo en formato markdown"""
160
  col_predicha_num = "Concentración Predicha Numérica"
 
161
 
162
  # Convertir a numérico
163
  df_valid[col_predicha_num] = df_valid[col_predicha_num].astype(float)
164
- df_valid[col_absorbancia] = df_valid[col_absorbancia].astype(float)
165
 
166
  # Calcular estadísticas
167
- slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha_num], df_valid[col_absorbancia])
168
  r_squared = r_value ** 2
169
- rmse = np.sqrt(((df_valid[col_absorbancia] - (intercept + slope * df_valid[col_predicha_num])) ** 2).mean())
170
- cv = (df_valid[col_absorbancia].std() / df_valid[col_absorbancia].mean()) * 100 # CV de los valores
171
 
172
  # Evaluar calidad
173
  evaluacion = evaluar_calidad_calibracion(df_valid, r_squared, rmse, cv)
@@ -198,7 +271,7 @@ Fecha: {datetime.now().strftime('%d/%m/%Y %H:%M')}
198
  """
199
  return informe, evaluacion['estado']
200
 
201
- def actualizar_analisis(df, unidad_medida, filas_seleccionadas, decimales, absorbancia_seleccionada):
202
  if df is None or df.empty:
203
  return "Error en los datos", None, "No se pueden generar análisis", df
204
 
@@ -208,64 +281,67 @@ def actualizar_analisis(df, unidad_medida, filas_seleccionadas, decimales, absor
208
 
209
  indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas]
210
 
211
- # Usar la columna de absorbancia seleccionada
212
- col_predicha_num = "Concentración Predicha Numérica"
213
- col_absorbancia = absorbancia_seleccionada
214
 
215
- if col_absorbancia not in df.columns:
216
- return "La columna seleccionada no existe en los datos", None, "Error en los datos", df
217
 
218
  # Convertir columnas a numérico
219
  df[col_predicha_num] = pd.to_numeric(df[col_predicha_num], errors='coerce')
220
- df[col_absorbancia] = pd.to_numeric(df[col_absorbancia], errors='coerce')
221
 
222
- df_valid = df.dropna(subset=[col_predicha_num, col_absorbancia])
223
 
224
- # Resetear el índice
225
  df_valid.reset_index(drop=True, inplace=True)
226
 
227
  # Filtrar filas según las seleccionadas
228
  df_valid = df_valid.loc[indices_seleccionados]
229
 
230
  if len(df_valid) < 2:
231
- return "Se necesitan más datos", None, "Se requieren al menos dos valores para el análisis", df
232
 
233
- # Calcular regresión lineal
234
- slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha_num], df_valid[col_absorbancia])
235
  df_valid['Ajuste Lineal'] = intercept + slope * df_valid[col_predicha_num]
236
 
237
  # Generar gráfico con opciones predeterminadas
238
  fig = generar_graficos(
239
- df_valid, unidad_medida, col_absorbancia,
240
  palette_puntos='deep', estilo_puntos='o',
241
  palette_linea_ajuste='muted', estilo_linea_ajuste='-',
 
 
242
  mostrar_linea_ajuste=True,
 
243
  mostrar_puntos=True
244
  )
245
- informe, estado = generar_informe_completo(df_valid, unidad_medida, col_absorbancia)
246
 
247
  return estado, fig, informe, df
248
 
249
- def actualizar_graficos(df, unidad_medida,
250
  palette_puntos, estilo_puntos,
251
  palette_linea_ajuste, estilo_linea_ajuste,
252
- mostrar_linea_ajuste, mostrar_puntos,
253
- filas_seleccionadas, decimales, absorbancia_seleccionada):
 
 
254
  if df is None or df.empty:
255
  return None
256
 
257
  # Asegurarse de que los cálculos estén actualizados
258
- col_predicha_num = "Concentración Predicha Numérica"
259
- col_absorbancia = absorbancia_seleccionada
260
 
261
- if col_absorbancia not in df.columns:
262
- return None
263
 
264
  # Convertir columnas a numérico
265
  df[col_predicha_num] = pd.to_numeric(df[col_predicha_num], errors='coerce')
266
- df[col_absorbancia] = pd.to_numeric(df[col_absorbancia], errors='coerce')
267
 
268
- df_valid = df.dropna(subset=[col_predicha_num, col_absorbancia])
269
 
270
  # Resetear el índice para asegurar que sea secuencial
271
  df_valid.reset_index(drop=True, inplace=True)
@@ -284,56 +360,411 @@ def actualizar_graficos(df, unidad_medida,
284
 
285
  # Generar gráfico con opciones seleccionadas
286
  fig = generar_graficos(
287
- df_valid, unidad_medida, col_absorbancia,
288
  palette_puntos, estilo_puntos,
289
  palette_linea_ajuste, estilo_linea_ajuste,
290
- mostrar_linea_ajuste, mostrar_puntos
 
 
291
  )
292
 
293
  return fig
294
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  def cargar_excel(file):
296
  # Leer el archivo Excel
297
- df = pd.read_excel(file.name, sheet_name=0) # Leer la primera pestaña
298
-
299
- # Verificar que las columnas 'Absorbancia' existan
300
- absorbancia_columns = [col for col in df.columns if 'Absorbancia' in col]
301
- if not absorbancia_columns:
302
- return "El archivo debe tener columnas 'Absorbancia'", None, None, None, None, None, None, None, None
303
-
304
- # Obtener concentración inicial y unidad de medida desde el nombre de la primera columna
305
- first_column_name = df.columns[0]
306
- try:
307
- # Extraer concentración y unidad
308
- match = re.search(r'\(([\d\.]+)\s*(\w+)\)', first_column_name)
309
- if match:
310
- concentracion_inicial = float(match.group(1))
311
- unidad_medida = match.group(2)
312
- else:
313
- concentracion_inicial = 0
314
- unidad_medida = ''
315
- except:
316
- concentracion_inicial = 0
317
- unidad_medida = ''
318
 
319
- n_filas = len(df)
320
- n_replicas = 1 # Solo se selecciona una columna de absorbancia para el análisis
 
 
 
 
 
 
 
 
321
 
322
  # Generar la tabla base
323
  df_base = generar_tabla(n_filas, concentracion_inicial, unidad_medida, n_replicas)
324
 
325
- # Copiar las columnas relevantes al DataFrame base
326
- for col in df.columns:
327
- if col in df_base.columns:
328
- df_base[col] = df[col]
329
- else:
330
- df_base[col] = df[col]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
 
332
- # Obtener las opciones de absorbancia
333
- absorbancia_options = absorbancia_columns # Nombres tal como aparecen en el archivo Excel
 
 
 
 
 
 
 
334
 
335
- return concentracion_inicial, unidad_medida, n_filas, n_replicas, df_base, absorbancia_options, "", None, ""
 
 
 
 
 
336
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  def actualizar_opciones_filas(df):
338
  if df is None or df.empty:
339
  update = gr.update(choices=[], value=[])
@@ -394,12 +825,6 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
394
  sinteticos_btn = gr.Button("🧪 Generar Datos Sintéticos", variant="secondary")
395
  cargar_excel_btn = gr.UploadButton("📂 Cargar Excel", file_types=[".xlsx"], variant="secondary")
396
 
397
- absorbancia_dropdown = gr.Dropdown(
398
- label="Seleccione la Absorbancia para el análisis",
399
- choices=[],
400
- value=None
401
- )
402
-
403
  tabla_output = gr.DataFrame(
404
  wrap=True,
405
  label="Tabla de Datos",
@@ -411,13 +836,16 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
411
  estado_output = gr.Textbox(label="Estado", interactive=False)
412
  graficos_output = gr.Plot(label="Gráficos de Análisis")
413
 
 
414
  filas_seleccionadas = gr.CheckboxGroup(
415
  label="Seleccione las filas a incluir en el análisis",
416
  choices=[],
417
  value=[],
418
  )
419
 
 
420
  with gr.Row():
 
421
  paletas_colores = ["deep", "muted", "pastel", "bright", "dark", "colorblind", "Set1", "Set2", "Set3"]
422
 
423
  palette_puntos_dropdown = gr.Dropdown(
@@ -442,7 +870,23 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
442
  )
443
 
444
  with gr.Row():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
  mostrar_linea_ajuste = gr.Checkbox(value=True, label="Mostrar Línea de Ajuste")
 
446
  mostrar_puntos = gr.Checkbox(value=True, label="Mostrar Puntos")
447
  graficar_btn = gr.Button("📊 Graficar", variant="primary")
448
 
@@ -455,19 +899,106 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
455
  exportar_word_file = gr.File(label="Informe en Word")
456
  exportar_latex_file = gr.File(label="Informe en LaTeX")
457
 
 
458
  informe_output = gr.Markdown(elem_id="informe_output")
459
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  # Eventos para actualizar las opciones de filas
461
  tabla_output.change(
462
  fn=actualizar_opciones_filas,
463
  inputs=[tabla_output],
464
- outputs=[filas_seleccionadas, ]
465
  )
466
 
467
  # Evento al presionar el botón Calcular
468
  calcular_btn.click(
469
  fn=actualizar_analisis,
470
- inputs=[tabla_output, unidad_input, filas_seleccionadas, decimales_slider, absorbancia_dropdown],
471
  outputs=[estado_output, graficos_output, informe_output, tabla_output]
472
  )
473
 
@@ -475,26 +1006,145 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
475
  graficar_btn.click(
476
  fn=actualizar_graficos,
477
  inputs=[
478
- tabla_output, unidad_input,
479
  palette_puntos_dropdown, estilo_puntos_dropdown,
480
  palette_linea_ajuste_dropdown, estilo_linea_ajuste_dropdown,
481
- mostrar_linea_ajuste, mostrar_puntos,
482
- filas_seleccionadas, decimales_slider, absorbancia_dropdown
 
 
483
  ],
484
  outputs=graficos_output
485
  )
486
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487
  # Evento para limpiar datos
488
  limpiar_btn.click(
489
- fn=lambda: (2000000, "UFC", 7, generar_tabla(7, 2000000, "UFC", 1), "", None, "", [], 3, [], None),
490
- outputs=[concentracion_input, unidad_input, filas_slider, tabla_output, estado_output, graficos_output, informe_output, filas_seleccionadas, decimales_slider, absorbancia_dropdown, absorbancia_dropdown]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
491
  )
492
 
493
  # Evento para cargar archivo Excel
494
  cargar_excel_btn.upload(
495
  fn=cargar_excel,
496
  inputs=[cargar_excel_btn],
497
- outputs=[concentracion_input, unidad_input, filas_slider, replicas_slider, tabla_output, absorbancia_dropdown, estado_output, graficos_output, informe_output]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
498
  )
499
 
500
  # Inicializar la interfaz con el ejemplo base
@@ -502,10 +1152,11 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
502
  n_replicas = 1
503
  df = generar_tabla(7, 2000000, "UFC", n_replicas)
504
  # Valores reales de ejemplo
505
- df["Absorbancia 1"] = [0.8, 0.65, 0.5, 0.35, 0.2, 0.1, 0.05]
 
 
506
  filas_seleccionadas_inicial = [f"Fila {i+1}" for i in df.index]
507
- absorbancia_options = ["Absorbancia 1"]
508
- estado, fig, informe, df = actualizar_analisis(df, "UFC", filas_seleccionadas_inicial, 3, "Absorbancia 1")
509
  return (
510
  2000000,
511
  "UFC",
@@ -515,14 +1166,28 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
515
  fig,
516
  informe,
517
  filas_seleccionadas_inicial,
518
- 3, # Número de decimales
519
- absorbancia_options,
520
- "Absorbancia 1"
521
  )
522
 
523
  interfaz.load(
524
  fn=iniciar_con_ejemplo,
525
- outputs=[concentracion_input, unidad_input, filas_slider, tabla_output, estado_output, graficos_output, informe_output, filas_seleccionadas, decimales_slider, absorbancia_dropdown, absorbancia_dropdown]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
526
  )
527
 
528
  # Lanzar la interfaz
 
9
  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]
 
56
  pass
57
  return df
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
+
79
+ return df
80
+
81
+ def generar_graficos(df_valid, n_replicas, unidad_medida, palette_puntos, estilo_puntos,
82
  palette_linea_ajuste, estilo_linea_ajuste,
83
+ palette_linea_ideal, estilo_linea_ideal,
84
+ palette_barras_error, mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos):
85
  col_predicha_num = "Concentración Predicha Numérica"
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],
123
+ yerr=df_valid[col_desviacion],
124
+ fmt=estilo_puntos,
125
+ color=color_puntos,
126
+ ecolor=color_barras_error,
127
+ elinewidth=2,
128
+ capsize=3,
129
+ label='Datos Reales'
130
+ )
131
+ else:
132
+ ax1.scatter(
133
+ df_valid[col_predicha_num],
134
+ df_valid[col_real_promedio],
135
+ color=color_puntos,
136
+ s=100,
137
+ label='Datos Reales',
138
+ marker=estilo_puntos
139
+ )
140
 
141
  # Línea de ajuste
142
  if mostrar_linea_ajuste:
 
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()
156
+ ax1.plot(
157
+ [min_predicha, max_predicha],
158
+ [min_predicha, max_predicha],
159
+ color=color_linea_ideal,
160
+ linestyle=estilo_linea_ideal,
161
+ label='Ideal'
162
+ )
163
+
164
+ ax1.set_title('Correlación entre Concentración Predicha y Real', fontsize=14)
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(
 
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],
185
+ residuos,
186
+ color=color_puntos,
187
+ s=100,
188
+ marker=estilo_puntos,
189
+ label='Residuos'
190
+ )
191
+
192
+ ax2.axhline(y=0, color='black', linestyle='--', linewidth=1)
193
+ ax2.set_title('Gráfico de Residuos', fontsize=14)
194
+ ax2.set_xlabel('Concentración Predicha', fontsize=12)
195
+ ax2.set_ylabel('Residuo', fontsize=12)
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
 
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)
 
271
  """
272
  return informe, evaluacion['estado']
273
 
274
+ def actualizar_analisis(df, n_replicas, unidad_medida, filas_seleccionadas, decimales):
275
  if df is None or df.empty:
276
  return "Error en los datos", None, "No se pueden generar análisis", df
277
 
 
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',
313
  palette_linea_ajuste='muted', estilo_linea_ajuste='-',
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)
321
 
322
  return estado, fig, informe, df
323
 
324
+ def actualizar_graficos(df, n_replicas, unidad_medida,
325
  palette_puntos, estilo_puntos,
326
  palette_linea_ajuste, estilo_linea_ajuste,
327
+ palette_linea_ideal, estilo_linea_ideal,
328
+ palette_barras_error,
329
+ mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos,
330
+ filas_seleccionadas, decimales):
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)
 
360
 
361
  # Generar gráfico con opciones seleccionadas
362
  fig = generar_graficos(
363
+ df_valid, n_replicas, unidad_medida,
364
  palette_puntos, estilo_puntos,
365
  palette_linea_ajuste, estilo_linea_ajuste,
366
+ palette_linea_ideal, estilo_linea_ideal,
367
+ palette_barras_error,
368
+ mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos
369
  )
370
 
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('##'):
407
+ doc.add_heading(linea.replace('##', '').strip(), level=2)
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}
451
+ \usepackage{graphicx}
452
+ \usepackage{booktabs}
453
+ \begin{document}
454
+ """
455
+ informe_tex += informe_md.replace('#', '').replace('**', '\\textbf{').replace('*', '\\textit{')
456
+ informe_tex += r"""
457
+ \end{document}
458
+ """
459
+ filename = 'informe_calibracion.tex'
460
+ with open(filename, 'w') as f:
461
+ f.write(informe_tex)
462
+ return filename
463
+
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,
620
+ palette_linea_ajuste, estilo_linea_ajuste,
621
+ mostrar_linea_ajuste, mostrar_puntos,
622
+ legend_location, decimales,
623
+ titulo_grafico_original, titulo_grafico_personalizado,
624
+ eje_x_original, eje_y_original,
625
+ eje_x_personalizado, eje_y_personalizado):
626
+ if df is None or df.empty:
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',
675
+ ecolor='gray',
676
+ elinewidth=1,
677
+ capsize=3,
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],
686
+ intercept_all + slope_all * df_original[col_concentracion],
687
+ color='red',
688
+ linestyle='-',
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),
704
+ xycoords='axes fraction',
705
+ fontsize=12,
706
+ backgroundcolor='white',
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
+
718
+ color_puntos = colors_puntos[0]
719
+ color_linea_ajuste = colors_linea_ajuste[0]
720
+
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,
728
+ ecolor='gray',
729
+ elinewidth=1,
730
+ capsize=3,
731
+ label='Datos'
732
+ )
733
+
734
+ if mostrar_linea_ajuste:
735
+ ax_personalizado.plot(
736
+ df_valid[col_concentracion],
737
+ intercept + slope * df_valid[col_concentracion],
738
+ color=color_linea_ajuste,
739
+ linestyle=estilo_linea_ajuste,
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),
755
+ xycoords='axes fraction',
756
+ fontsize=12,
757
+ backgroundcolor='white',
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=[])
 
825
  sinteticos_btn = gr.Button("🧪 Generar Datos Sintéticos", variant="secondary")
826
  cargar_excel_btn = gr.UploadButton("📂 Cargar Excel", file_types=[".xlsx"], variant="secondary")
827
 
 
 
 
 
 
 
828
  tabla_output = gr.DataFrame(
829
  wrap=True,
830
  label="Tabla de Datos",
 
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(
 
870
  )
871
 
872
  with gr.Row():
873
+ palette_linea_ideal_dropdown = gr.Dropdown(
874
+ choices=paletas_colores,
875
+ value="bright",
876
+ label="Paleta Línea Ideal"
877
+ )
878
+ estilo_linea_ideal_dropdown = gr.Dropdown(
879
+ choices=["--", "-", "-.", ":"],
880
+ value="--",
881
+ label="Estilo Línea Ideal"
882
+ )
883
+ palette_barras_error_dropdown = gr.Dropdown(
884
+ choices=paletas_colores,
885
+ value="pastel",
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
 
 
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(
919
+ choices=paletas_colores,
920
+ value="deep",
921
+ label="Paleta para Puntos"
922
+ )
923
+ estilo_puntos_regresion = gr.Dropdown(
924
+ choices=["o", "s", "^", "D", "v", "<", ">", "h", "H", "p", "*", "X", "d"],
925
+ value="o",
926
+ label="Estilo de Puntos"
927
+ )
928
+ palette_linea_ajuste_regresion = gr.Dropdown(
929
+ choices=paletas_colores,
930
+ value="muted",
931
+ label="Paleta Línea de Ajuste"
932
+ )
933
+ estilo_linea_ajuste_regresion = gr.Dropdown(
934
+ choices=["-", "--", "-.", ":"],
935
+ value="-",
936
+ label="Estilo Línea de Ajuste"
937
+ )
938
+ mostrar_linea_ajuste_regresion = gr.Checkbox(value=True, label="Mostrar Línea de Ajuste")
939
+ mostrar_puntos_regresion = gr.Checkbox(value=True, label="Mostrar Puntos")
940
+
941
+ with gr.Row():
942
+ legend_location_dropdown = gr.Dropdown(
943
+ choices=[
944
+ 'best', 'upper right', 'upper left', 'lower left', 'lower right',
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",
956
+ placeholder="Regresión Lineal: Concentración Real vs Concentración Predicha (Original)"
957
+ )
958
+ titulo_grafico_personalizado = gr.Textbox(
959
+ label="Título del Gráfico Personalizado",
960
+ placeholder="Regresión Lineal Personalizada"
961
+ )
962
+
963
+ with gr.Row():
964
+ eje_x_original = gr.Textbox(
965
+ label="Etiqueta del Eje X (Gráfico Original)",
966
+ placeholder="Concentración Predicha Numérica"
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():
974
+ eje_x_personalizado = gr.Textbox(
975
+ label="Etiqueta del Eje X (Gráfico Personalizado)",
976
+ placeholder="Concentración Predicha Numérica"
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
 
 
1006
  graficar_btn.click(
1007
  fn=actualizar_graficos,
1008
  inputs=[
1009
+ tabla_output, replicas_slider, unidad_input,
1010
  palette_puntos_dropdown, estilo_puntos_dropdown,
1011
  palette_linea_ajuste_dropdown, estilo_linea_ajuste_dropdown,
1012
+ palette_linea_ideal_dropdown, estilo_linea_ideal_dropdown,
1013
+ palette_barras_error_dropdown,
1014
+ mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos,
1015
+ filas_seleccionadas, decimales_slider
1016
  ],
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],
1054
+ outputs=[concentracion_input, unidad_input, filas_slider, tabla_output]
1055
+ )
1056
+
1057
+ ejemplo_od_btn.click(
1058
+ fn=cargar_ejemplo_od,
1059
+ inputs=[replicas_slider],
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
+
1088
+ concentracion_input.change(
1089
+ fn=actualizar_tabla_wrapper,
1090
+ inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider, decimales_slider],
1091
+ outputs=tabla_output
1092
+ )
1093
+
1094
+ unidad_input.change(
1095
+ fn=actualizar_tabla_wrapper,
1096
+ inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider, decimales_slider],
1097
+ outputs=tabla_output
1098
+ )
1099
+
1100
+ filas_slider.change(
1101
+ fn=actualizar_tabla_wrapper,
1102
+ inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider, decimales_slider],
1103
+ outputs=tabla_output
1104
+ )
1105
+
1106
+ replicas_slider.change(
1107
+ fn=actualizar_tabla_wrapper,
1108
+ inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider, decimales_slider],
1109
+ outputs=tabla_output
1110
+ )
1111
+
1112
+ decimales_slider.change(
1113
+ fn=ajustar_decimales_evento,
1114
+ inputs=[tabla_output, decimales_slider],
1115
+ outputs=tabla_output
1116
+ )
1117
+
1118
+ # Evento de copiar informe utilizando JavaScript
1119
+ copiar_btn.click(
1120
+ None,
1121
+ [],
1122
+ [],
1123
+ js="""
1124
+ function() {
1125
+ const informeElement = document.querySelector('#informe_output');
1126
+ const range = document.createRange();
1127
+ range.selectNode(informeElement);
1128
+ window.getSelection().removeAllRanges();
1129
+ window.getSelection().addRange(range);
1130
+ document.execCommand('copy');
1131
+ window.getSelection().removeAllRanges();
1132
+ alert('Informe copiado al portapapeles');
1133
+ }
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],
1141
+ outputs=exportar_word_file
1142
+ )
1143
+
1144
+ exportar_latex_btn.click(
1145
+ fn=exportar_latex,
1146
+ inputs=[tabla_output, informe_output, filas_seleccionadas],
1147
+ outputs=exportar_latex_file
1148
  )
1149
 
1150
  # Inicializar la interfaz con el ejemplo base
 
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",
 
1166
  fig,
1167
  informe,
1168
  filas_seleccionadas_inicial,
1169
+ 3 # Número de decimales
 
 
1170
  )
1171
 
1172
  interfaz.load(
1173
  fn=iniciar_con_ejemplo,
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=[
1181
+ tabla_output, unidad_input, filas_seleccionadas_regresion,
1182
+ palette_puntos_regresion, estilo_puntos_regresion,
1183
+ palette_linea_ajuste_regresion, estilo_linea_ajuste_regresion,
1184
+ mostrar_linea_ajuste_regresion, mostrar_puntos_regresion,
1185
+ legend_location_dropdown, decimales_slider,
1186
+ titulo_grafico_original, titulo_grafico_personalizado,
1187
+ eje_x_original, eje_y_original,
1188
+ eje_x_personalizado, eje_y_personalizado
1189
+ ],
1190
+ outputs=[estado_regresion_output, grafico_original_output, grafico_personalizado_output, tabla_resumen_output]
1191
  )
1192
 
1193
  # Lanzar la interfaz