Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -25,7 +25,7 @@ client = anthropic.Anthropic()
|
|
25 |
class AnalysisType(Enum):
|
26 |
MATHEMATICAL_MODEL = "mathematical_model"
|
27 |
DATA_FITTING = "data_fitting"
|
28 |
-
FITTING_RESULTS = "fitting_results"
|
29 |
UNKNOWN = "unknown"
|
30 |
|
31 |
# Estructura modular para modelos
|
@@ -194,10 +194,8 @@ class AIAnalyzer:
|
|
194 |
def detect_analysis_type(self, content: Union[str, pd.DataFrame]) -> AnalysisType:
|
195 |
"""Detecta el tipo de análisis necesario"""
|
196 |
if isinstance(content, pd.DataFrame):
|
197 |
-
# Analizar si son datos experimentales o resultados de ajuste
|
198 |
columns = [col.lower() for col in content.columns]
|
199 |
|
200 |
-
# Indicadores de resultados de ajuste
|
201 |
fitting_indicators = [
|
202 |
'r2', 'r_squared', 'rmse', 'mse', 'aic', 'bic',
|
203 |
'parameter', 'param', 'coefficient', 'fit',
|
@@ -205,7 +203,6 @@ class AIAnalyzer:
|
|
205 |
'p_value', 'confidence', 'standard_error', 'se'
|
206 |
]
|
207 |
|
208 |
-
# Verificar si hay indicadores de resultados de ajuste
|
209 |
has_fitting_results = any(indicator in ' '.join(columns) for indicator in fitting_indicators)
|
210 |
|
211 |
if has_fitting_results:
|
@@ -213,7 +210,6 @@ class AIAnalyzer:
|
|
213 |
else:
|
214 |
return AnalysisType.DATA_FITTING
|
215 |
|
216 |
-
# Analizar texto para determinar tipo
|
217 |
prompt = """
|
218 |
Analiza este contenido y determina si es:
|
219 |
1. Un artículo científico que describe modelos matemáticos biotecnológicos
|
@@ -274,7 +270,6 @@ class AIAnalyzer:
|
|
274 |
}
|
275 |
|
276 |
try:
|
277 |
-
# Identificar modelos
|
278 |
response = self.client.messages.create(
|
279 |
model=claude_model,
|
280 |
max_tokens=2000,
|
@@ -286,7 +281,6 @@ class AIAnalyzer:
|
|
286 |
|
287 |
models_info = response.content[0].text
|
288 |
|
289 |
-
# Recomendaciones
|
290 |
response2 = self.client.messages.create(
|
291 |
model=claude_model,
|
292 |
max_tokens=2000,
|
@@ -307,7 +301,6 @@ class AIAnalyzer:
|
|
307 |
|
308 |
def analyze_fitting_data(self, data: pd.DataFrame, claude_model: str) -> Dict:
|
309 |
"""Analiza datos experimentales para ajuste de parámetros"""
|
310 |
-
# Preparar resumen de datos
|
311 |
data_summary = f"""
|
312 |
Columnas: {list(data.columns)}
|
313 |
Forma: {data.shape}
|
@@ -348,14 +341,16 @@ class AIAnalyzer:
|
|
348 |
except Exception as e:
|
349 |
return {"error": str(e)}
|
350 |
|
351 |
-
def analyze_fitting_results(self, data: pd.DataFrame, claude_model: str) -> Dict:
|
352 |
-
"""Analiza resultados de ajuste de modelos
|
353 |
-
|
|
|
354 |
data_summary = f"""
|
355 |
-
|
356 |
|
357 |
-
|
358 |
-
|
|
|
359 |
|
360 |
Datos completos:
|
361 |
{data.to_string()}
|
@@ -364,39 +359,86 @@ class AIAnalyzer:
|
|
364 |
{data.describe().to_string()}
|
365 |
"""
|
366 |
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
|
375 |
-
|
376 |
-
-
|
377 |
-
-
|
378 |
-
|
379 |
-
|
380 |
-
|
381 |
-
-
|
382 |
-
-
|
383 |
-
|
384 |
-
|
385 |
-
|
386 |
-
|
387 |
-
-
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
400 |
"""
|
401 |
|
402 |
try:
|
@@ -405,27 +447,62 @@ class AIAnalyzer:
|
|
405 |
max_tokens=4000,
|
406 |
messages=[{
|
407 |
"role": "user",
|
408 |
-
"content": f"{prompt}\n\
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
409 |
}]
|
410 |
)
|
411 |
|
412 |
return {
|
413 |
-
"tipo": "Análisis
|
414 |
"analisis_completo": response.content[0].text,
|
415 |
-
"
|
416 |
-
"
|
417 |
-
|
418 |
-
|
|
|
|
|
|
|
|
|
|
|
419 |
}
|
420 |
|
421 |
except Exception as e:
|
422 |
return {"error": str(e)}
|
423 |
|
424 |
-
def process_files(files, claude_model: str) -> str:
|
425 |
"""Procesa múltiples archivos"""
|
426 |
processor = FileProcessor()
|
427 |
analyzer = AIAnalyzer(client, model_registry)
|
428 |
results = []
|
|
|
429 |
|
430 |
for file in files:
|
431 |
if file is None:
|
@@ -434,13 +511,10 @@ def process_files(files, claude_model: str) -> str:
|
|
434 |
file_name = file.name if hasattr(file, 'name') else "archivo"
|
435 |
file_ext = Path(file_name).suffix.lower()
|
436 |
|
437 |
-
# Leer contenido del archivo
|
438 |
with open(file.name, 'rb') as f:
|
439 |
file_content = f.read()
|
440 |
|
441 |
-
# Procesar según tipo
|
442 |
if file_ext == '.zip':
|
443 |
-
# Extraer y procesar archivos del ZIP
|
444 |
extracted_files = processor.extract_from_zip(file_content)
|
445 |
results.append(f"## 📦 Archivo ZIP: {file_name}")
|
446 |
results.append(f"Contiene {len(extracted_files)} archivos\n")
|
@@ -458,7 +532,7 @@ def process_files(files, claude_model: str) -> str:
|
|
458 |
else:
|
459 |
result = {"tipo": "PDF no reconocido", "contenido": text[:500]}
|
460 |
|
461 |
-
results.append(json.dumps(result, indent=2, ensure_ascii=False))
|
462 |
|
463 |
elif sub_ext in ['.csv', '.xlsx', '.xls']:
|
464 |
if sub_ext == '.csv':
|
@@ -470,11 +544,13 @@ def process_files(files, claude_model: str) -> str:
|
|
470 |
analysis_type = analyzer.detect_analysis_type(df)
|
471 |
|
472 |
if analysis_type == AnalysisType.FITTING_RESULTS:
|
473 |
-
result = analyzer.analyze_fitting_results(df, claude_model)
|
|
|
|
|
|
|
474 |
else:
|
475 |
result = analyzer.analyze_fitting_data(df, claude_model)
|
476 |
-
|
477 |
-
results.append(json.dumps(result, indent=2, ensure_ascii=False))
|
478 |
|
479 |
results.append("\n---\n")
|
480 |
|
@@ -486,13 +562,13 @@ def process_files(files, claude_model: str) -> str:
|
|
486 |
|
487 |
if analysis_type == AnalysisType.MATHEMATICAL_MODEL:
|
488 |
result = analyzer.analyze_mathematical_article(text, claude_model)
|
|
|
489 |
else:
|
490 |
result = {"tipo": "PDF - Contenido no identificado", "texto": text[:1000]}
|
491 |
-
|
492 |
-
results.append(json.dumps(result, indent=2, ensure_ascii=False))
|
493 |
|
494 |
elif file_ext in ['.csv', '.xlsx', '.xls']:
|
495 |
-
results.append(f"## 📊
|
496 |
|
497 |
if file_ext == '.csv':
|
498 |
df = processor.read_csv(file_content)
|
@@ -503,17 +579,22 @@ def process_files(files, claude_model: str) -> str:
|
|
503 |
analysis_type = analyzer.detect_analysis_type(df)
|
504 |
|
505 |
if analysis_type == AnalysisType.FITTING_RESULTS:
|
506 |
-
result = analyzer.analyze_fitting_results(df, claude_model)
|
507 |
-
results.append("### 🎯 ANÁLISIS DE
|
|
|
|
|
|
|
508 |
else:
|
509 |
result = analyzer.analyze_fitting_data(df, claude_model)
|
510 |
results.append("### 📈 ANÁLISIS DE DATOS EXPERIMENTALES")
|
511 |
-
|
512 |
-
results.append(json.dumps(result, indent=2, ensure_ascii=False))
|
513 |
|
514 |
results.append("\n---\n")
|
515 |
|
516 |
-
|
|
|
|
|
|
|
517 |
|
518 |
def generate_implementation_code(analysis_results: str) -> str:
|
519 |
"""Genera código de implementación basado en el análisis"""
|
@@ -525,183 +606,448 @@ from scipy.integrate import odeint
|
|
525 |
from scipy.optimize import curve_fit, differential_evolution
|
526 |
from sklearn.metrics import r2_score, mean_squared_error
|
527 |
import seaborn as sns
|
|
|
528 |
|
529 |
# Configuración de visualización
|
530 |
plt.style.use('seaborn-v0_8-darkgrid')
|
531 |
sns.set_palette("husl")
|
532 |
|
533 |
-
class
|
534 |
-
\"\"\"
|
|
|
|
|
|
|
535 |
|
536 |
def __init__(self):
|
537 |
-
self.
|
538 |
-
self.
|
539 |
-
self.
|
540 |
-
|
541 |
-
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
self.results_df = pd.read_csv(data_path)
|
546 |
else:
|
547 |
-
self.results_df = pd.read_excel(
|
|
|
|
|
|
|
548 |
|
549 |
return self.results_df
|
550 |
|
551 |
-
def
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
|
556 |
-
|
557 |
-
|
558 |
-
|
559 |
-
|
560 |
-
|
561 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
562 |
|
563 |
-
|
564 |
-
|
565 |
-
|
566 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
567 |
|
568 |
-
print(f"{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
569 |
|
570 |
-
|
571 |
-
|
572 |
-
|
573 |
-
\"\"\"Interpreta el significado biológico de parámetros\"\"\"
|
574 |
-
interpretations = {
|
575 |
-
'monod': {
|
576 |
-
'mu_max': 'Velocidad máxima específica de crecimiento',
|
577 |
-
'Ks': 'Constante de saturación (afinidad por sustrato)',
|
578 |
-
'biological_meaning': 'Crecimiento limitado por sustrato único'
|
579 |
-
},
|
580 |
-
'logistic': {
|
581 |
-
'K': 'Capacidad de carga del sistema',
|
582 |
-
'r': 'Tasa intrínseca de crecimiento',
|
583 |
-
'biological_meaning': 'Crecimiento limitado por densidad poblacional'
|
584 |
-
},
|
585 |
-
'gompertz': {
|
586 |
-
'A': 'Asíntota superior (biomasa máxima)',
|
587 |
-
'mu': 'Velocidad máxima de crecimiento',
|
588 |
-
'lambda': 'Tiempo de fase lag',
|
589 |
-
'biological_meaning': 'Crecimiento con adaptación inicial'
|
590 |
-
}
|
591 |
-
}
|
592 |
|
593 |
-
|
594 |
-
|
595 |
-
return interpretations[model_key]
|
596 |
-
else:
|
597 |
-
return {'biological_meaning': 'Modelo no reconocido en base de datos'}
|
598 |
|
599 |
-
def
|
600 |
-
\"\"\"
|
601 |
-
|
|
|
|
|
|
|
602 |
raise ValueError("Primero carga los datos")
|
603 |
|
604 |
-
|
605 |
-
report.append("# 🧬 REPORTE DE ANÁLISIS BIOTECNOLÓGICO")
|
606 |
-
report.append("=" * 50)
|
607 |
-
report.append("")
|
608 |
-
|
609 |
-
# Mejor modelo
|
610 |
-
best_model = self.results_df.loc[self.results_df['R2'].idxmax()]
|
611 |
-
report.append(f"## 🏆 MEJOR MODELO IDENTIFICADO")
|
612 |
-
report.append(f"**Modelo:** {best_model.get('Model', 'No especificado')}")
|
613 |
-
report.append(f"**Calidad del ajuste:** R² = {best_model.get('R2', 'N/A'):.4f}")
|
614 |
-
report.append(f"**Error:** RMSE = {best_model.get('RMSE', 'N/A'):.4f}")
|
615 |
-
report.append("")
|
616 |
-
|
617 |
-
# Interpretación biológica
|
618 |
-
report.append("## 🔬 SIGNIFICADO BIOLÓGICO")
|
619 |
-
report.append("Los parámetros ajustados nos indican:")
|
620 |
-
|
621 |
-
# Aquí puedes expandir según los modelos específicos encontrados
|
622 |
-
report.append("- El sistema estudiado muestra un comportamiento predecible")
|
623 |
-
report.append("- Los parámetros están dentro de rangos biológicamente plausibles")
|
624 |
-
report.append("")
|
625 |
|
626 |
-
|
627 |
-
|
628 |
-
|
629 |
-
|
630 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
631 |
|
632 |
-
return
|
633 |
|
634 |
-
def
|
635 |
-
\"\"\"
|
636 |
-
|
637 |
-
|
|
|
|
|
|
|
|
|
638 |
|
639 |
-
|
|
|
640 |
|
641 |
-
# Gráfico de R²
|
642 |
-
|
643 |
-
|
|
|
644 |
|
645 |
-
ax1.bar(models, r2_values, color='skyblue', edgecolor='navy', alpha=0.7)
|
646 |
ax1.set_title('Comparación de R² por Modelo', fontsize=14, fontweight='bold')
|
647 |
ax1.set_ylabel('R² (Coeficiente de Determinación)', fontsize=12)
|
648 |
-
ax1.set_ylim(0, 1)
|
|
|
|
|
649 |
ax1.grid(True, alpha=0.3)
|
650 |
|
651 |
-
#
|
652 |
-
|
|
|
|
|
|
|
653 |
|
654 |
-
# Gráfico de RMSE
|
655 |
-
|
656 |
-
|
|
|
|
|
657 |
ax2.set_title('Comparación de RMSE por Modelo', fontsize=14, fontweight='bold')
|
658 |
ax2.set_ylabel('RMSE (Error Cuadrático Medio)', fontsize=12)
|
|
|
|
|
659 |
ax2.grid(True, alpha=0.3)
|
660 |
|
661 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
662 |
|
663 |
-
plt.tight_layout()
|
664 |
return fig
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
665 |
|
666 |
-
#
|
667 |
-
|
668 |
-
|
669 |
-
|
670 |
-
|
671 |
-
|
672 |
-
|
673 |
-
|
674 |
-
|
675 |
-
|
676 |
-
def
|
677 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
678 |
|
679 |
-
# Ejemplo de uso
|
680 |
if __name__ == "__main__":
|
681 |
-
|
682 |
-
|
683 |
-
|
684 |
-
# Ejemplo de carga de datos
|
685 |
-
# analyzer.load_fitting_results('resultados_ajuste.csv')
|
686 |
|
687 |
-
#
|
688 |
-
|
689 |
|
690 |
-
#
|
691 |
-
|
692 |
-
|
|
|
|
|
|
|
|
|
693 |
|
694 |
-
print("
|
695 |
-
print("📊 Carga tus resultados CSV y utiliza analyzer.load_fitting_results()")
|
696 |
-
print("📈 Luego usa analyzer.compare_models() para comparar")
|
697 |
"""
|
698 |
|
699 |
return code
|
700 |
|
701 |
-
# Interfaz Gradio optimizada
|
702 |
def create_interface():
|
703 |
with gr.Blocks(
|
704 |
-
title="Analizador
|
705 |
theme=gr.themes.Soft(),
|
706 |
css="""
|
707 |
.gradio-container {
|
@@ -713,30 +1059,31 @@ def create_interface():
|
|
713 |
border-radius: 10px;
|
714 |
border-left: 5px solid #4CAF50;
|
715 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
716 |
"""
|
717 |
) as demo:
|
718 |
|
719 |
gr.Markdown("""
|
720 |
-
# 🧬 Analizador
|
721 |
-
|
722 |
-
### 🎯 Especializado en
|
723 |
-
- **Análisis
|
724 |
-
- **
|
725 |
-
- **
|
726 |
-
- **
|
727 |
-
- **
|
728 |
-
|
729 |
-
|
730 |
-
|
731 |
-
-
|
732 |
-
-
|
733 |
-
-
|
734 |
-
|
735 |
-
### 🔍 ¿Qué analiza específicamente?
|
736 |
-
- Calidad del ajuste y comparación entre modelos
|
737 |
-
- Significado biológico de parámetros estimados
|
738 |
-
- Detección de sobreajuste o problemas en el ajuste
|
739 |
-
- Interpretación de resultados en contexto biotecnológico
|
740 |
""")
|
741 |
|
742 |
with gr.Row():
|
@@ -755,8 +1102,15 @@ def create_interface():
|
|
755 |
info="Selecciona el modelo de IA"
|
756 |
)
|
757 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
758 |
analyze_btn = gr.Button(
|
759 |
-
"🚀 Analizar
|
760 |
variant="primary",
|
761 |
size="lg"
|
762 |
)
|
@@ -782,81 +1136,83 @@ def create_interface():
|
|
782 |
|
783 |
with gr.Column(scale=2):
|
784 |
analysis_output = gr.Markdown(
|
785 |
-
label="📊 Análisis
|
786 |
elem_classes=["highlight-results"]
|
787 |
)
|
788 |
|
789 |
code_output = gr.Code(
|
790 |
-
label="💻 Código de
|
791 |
language="python",
|
792 |
-
interactive=True
|
|
|
793 |
)
|
794 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
795 |
# Ejemplos
|
796 |
gr.Examples(
|
797 |
examples=[
|
798 |
-
[["examples/
|
799 |
-
[["examples/
|
800 |
-
[["examples/
|
801 |
],
|
802 |
-
inputs=[files_input],
|
803 |
-
label="📚 Ejemplos de
|
804 |
)
|
805 |
|
806 |
-
# Guía de uso
|
807 |
-
gr.Markdown("""
|
808 |
-
---
|
809 |
-
### 📋 Guía de uso para resultados de ajuste:
|
810 |
-
|
811 |
-
**Para obtener el mejor análisis, asegúrate que tu CSV/Excel contenga:**
|
812 |
-
|
813 |
-
1. **Columna de modelos**: Nombres de los modelos ajustados (Monod, Logístico, Gompertz, etc.)
|
814 |
-
2. **Métricas de ajuste**: R², RMSE, AIC, BIC, MSE, etc.
|
815 |
-
3. **Parámetros**: Valores de parámetros estimados (μmax, Ks, K, etc.)
|
816 |
-
4. **Errores estándar**: Si están disponibles
|
817 |
-
|
818 |
-
**Ejemplo de estructura ideal:**
|
819 |
-
```
|
820 |
-
Model | R2 | RMSE | mu_max | Ks | AIC
|
821 |
-
Monod | 0.985 | 0.023 | 0.45 | 2.1 | -45.2
|
822 |
-
Logistic | 0.976 | 0.031 | 0.42 | 15.3 | -42.1
|
823 |
-
Gompertz | 0.992 | 0.018 | 0.48 | 1.8 | -48.5
|
824 |
-
```
|
825 |
-
|
826 |
-
### 🔬 Lo que obtendrás:
|
827 |
-
- **Ranking de modelos** basado en calidad de ajuste
|
828 |
-
- **Interpretación biológica** de cada parámetro
|
829 |
-
- **Análisis del diseño experimental** inferido
|
830 |
-
- **Recomendaciones** sobre cuál modelo usar
|
831 |
-
- **Explicación en lenguaje simple** de los resultados
|
832 |
-
""")
|
833 |
-
|
834 |
-
# Footer
|
835 |
-
gr.Markdown("""
|
836 |
-
---
|
837 |
-
### 🔧 Características técnicas:
|
838 |
-
- **Detección automática** de tipo de análisis (datos vs resultados)
|
839 |
-
- **Interpretación contextual** de parámetros biotecnológicos
|
840 |
-
- **Análisis comparativo** inteligente entre modelos
|
841 |
-
- **Traducción técnica** a lenguaje comprensible
|
842 |
-
|
843 |
-
### 💡 Casos de uso:
|
844 |
-
- Análisis de resultados de ajustes de crecimiento microbiano
|
845 |
-
- Comparación de modelos de consumo de sustrato
|
846 |
-
- Evaluación de modelos de formación de producto
|
847 |
-
- Interpretación de parámetros cinéticos
|
848 |
-
""")
|
849 |
-
|
850 |
# Eventos
|
851 |
analyze_btn.click(
|
852 |
-
fn=lambda files, model: (
|
853 |
-
|
854 |
-
|
855 |
),
|
856 |
-
inputs=[files_input, model_selector],
|
857 |
outputs=[analysis_output, code_output]
|
858 |
)
|
859 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
860 |
# Cargar info inicial del modelo
|
861 |
demo.load(
|
862 |
fn=lambda: update_model_info("claude-3-5-sonnet-20241022"),
|
|
|
25 |
class AnalysisType(Enum):
|
26 |
MATHEMATICAL_MODEL = "mathematical_model"
|
27 |
DATA_FITTING = "data_fitting"
|
28 |
+
FITTING_RESULTS = "fitting_results"
|
29 |
UNKNOWN = "unknown"
|
30 |
|
31 |
# Estructura modular para modelos
|
|
|
194 |
def detect_analysis_type(self, content: Union[str, pd.DataFrame]) -> AnalysisType:
|
195 |
"""Detecta el tipo de análisis necesario"""
|
196 |
if isinstance(content, pd.DataFrame):
|
|
|
197 |
columns = [col.lower() for col in content.columns]
|
198 |
|
|
|
199 |
fitting_indicators = [
|
200 |
'r2', 'r_squared', 'rmse', 'mse', 'aic', 'bic',
|
201 |
'parameter', 'param', 'coefficient', 'fit',
|
|
|
203 |
'p_value', 'confidence', 'standard_error', 'se'
|
204 |
]
|
205 |
|
|
|
206 |
has_fitting_results = any(indicator in ' '.join(columns) for indicator in fitting_indicators)
|
207 |
|
208 |
if has_fitting_results:
|
|
|
210 |
else:
|
211 |
return AnalysisType.DATA_FITTING
|
212 |
|
|
|
213 |
prompt = """
|
214 |
Analiza este contenido y determina si es:
|
215 |
1. Un artículo científico que describe modelos matemáticos biotecnológicos
|
|
|
270 |
}
|
271 |
|
272 |
try:
|
|
|
273 |
response = self.client.messages.create(
|
274 |
model=claude_model,
|
275 |
max_tokens=2000,
|
|
|
281 |
|
282 |
models_info = response.content[0].text
|
283 |
|
|
|
284 |
response2 = self.client.messages.create(
|
285 |
model=claude_model,
|
286 |
max_tokens=2000,
|
|
|
301 |
|
302 |
def analyze_fitting_data(self, data: pd.DataFrame, claude_model: str) -> Dict:
|
303 |
"""Analiza datos experimentales para ajuste de parámetros"""
|
|
|
304 |
data_summary = f"""
|
305 |
Columnas: {list(data.columns)}
|
306 |
Forma: {data.shape}
|
|
|
341 |
except Exception as e:
|
342 |
return {"error": str(e)}
|
343 |
|
344 |
+
def analyze_fitting_results(self, data: pd.DataFrame, claude_model: str, detail_level: str = "detallado") -> Dict:
|
345 |
+
"""Analiza resultados de ajuste de modelos con enfoque mejorado"""
|
346 |
+
|
347 |
+
# Preparar resumen completo de los datos
|
348 |
data_summary = f"""
|
349 |
+
RESULTADOS DE AJUSTE DE MODELOS MATEMÁTICOS:
|
350 |
|
351 |
+
Estructura de datos:
|
352 |
+
- Columnas: {list(data.columns)}
|
353 |
+
- Número de modelos evaluados: {len(data)}
|
354 |
|
355 |
Datos completos:
|
356 |
{data.to_string()}
|
|
|
359 |
{data.describe().to_string()}
|
360 |
"""
|
361 |
|
362 |
+
# Prompt mejorado y especializado
|
363 |
+
prompt = f"""
|
364 |
+
Eres un experto en biotecnología y modelado matemático. Analiza estos resultados de ajuste de modelos cinéticos/biotecnológicos.
|
365 |
+
|
366 |
+
NIVEL DE DETALLE SOLICITADO: {detail_level}
|
367 |
+
|
368 |
+
REALIZA UN ANÁLISIS COMPARATIVO EXHAUSTIVO:
|
369 |
+
|
370 |
+
1. **IDENTIFICACIÓN Y CLASIFICACIÓN DE MODELOS**
|
371 |
+
- Identifica TODOS los modelos matemáticos ajustados
|
372 |
+
- Clasifícalos por tipo: biomasa, sustrato, producto
|
373 |
+
- Indica la ecuación matemática de cada modelo si es posible inferirla
|
374 |
+
|
375 |
+
2. **ANÁLISIS COMPARATIVO DE CALIDAD DE AJUSTE**
|
376 |
+
- Compara TODOS los indicadores disponibles: R², RMSE, AIC, BIC, etc.
|
377 |
+
- Crea un ranking ordenado de mejor a peor modelo
|
378 |
+
- Identifica diferencias significativas entre modelos
|
379 |
+
- Detecta posible sobreajuste o subajuste
|
380 |
+
|
381 |
+
3. **DETERMINACIÓN DEL MEJOR MODELO**
|
382 |
+
- Selecciona el MEJOR modelo basándote en MÚLTIPLES criterios:
|
383 |
+
* Mayor R² (más cercano a 1)
|
384 |
+
* Menor RMSE/MSE
|
385 |
+
* Menor AIC/BIC (si están disponibles)
|
386 |
+
* Parsimonia (menos parámetros si el ajuste es similar)
|
387 |
+
- Justifica NUMÉRICAMENTE por qué es el mejor
|
388 |
+
- Si hay empate técnico, explica las ventajas de cada uno
|
389 |
+
|
390 |
+
4. **ANÁLISIS ESPECÍFICO POR TIPO DE VARIABLE**
|
391 |
+
a) **BIOMASA (si aplica)**:
|
392 |
+
- Parámetros de crecimiento (μmax, Xmax, etc.)
|
393 |
+
- Tiempo de duplicación
|
394 |
+
- Productividad de biomasa
|
395 |
+
- Comparación numérica entre modelos
|
396 |
+
|
397 |
+
b) **SUSTRATO (si aplica)**:
|
398 |
+
- Constantes de afinidad (Ks, Km)
|
399 |
+
- Velocidades de consumo
|
400 |
+
- Rendimiento Yx/s
|
401 |
+
- Eficiencia de utilización
|
402 |
+
|
403 |
+
c) **PRODUCTO (si aplica)**:
|
404 |
+
- Parámetros de producción (α, β)
|
405 |
+
- Productividad específica
|
406 |
+
- Rendimiento Yp/x
|
407 |
+
- Tipo de producción (asociada/no asociada)
|
408 |
+
|
409 |
+
5. **INTERPRETACIÓN BIOLÓGICA DE PARÁMETROS**
|
410 |
+
- Explica qué significa CADA parámetro biológicamente
|
411 |
+
- Compara valores entre modelos
|
412 |
+
- Evalúa si son realistas para el sistema
|
413 |
+
- Identifica parámetros críticos del proceso
|
414 |
+
|
415 |
+
6. **CONCLUSIONES CON CONTENIDO NUMÉRICO**
|
416 |
+
- Resume los hallazgos clave con NÚMEROS específicos
|
417 |
+
- Proporciona rangos de confianza si están disponibles
|
418 |
+
- Indica condiciones óptimas de operación
|
419 |
+
- Sugiere valores de diseño para escalamiento
|
420 |
+
|
421 |
+
7. **RECOMENDACIONES PRÁCTICAS**
|
422 |
+
- Qué modelo(s) usar para predicción
|
423 |
+
- Limitaciones del modelo seleccionado
|
424 |
+
- Experimentos adicionales recomendados
|
425 |
+
- Consideraciones para implementación industrial
|
426 |
+
|
427 |
+
8. **TABLA COMPARATIVA FINAL**
|
428 |
+
Crea una tabla resumen con:
|
429 |
+
- Modelo | R² | RMSE | AIC/BIC | Parámetros clave | Ranking
|
430 |
+
|
431 |
+
FORMATO DE RESPUESTA:
|
432 |
+
- Si el nivel es "detallado": incluye TODOS los puntos con explicaciones completas
|
433 |
+
- Si el nivel es "resumido": enfócate en puntos 3, 6 y 8 con valores numéricos clave
|
434 |
+
|
435 |
+
Usa formato Markdown con:
|
436 |
+
- Títulos y subtítulos claros
|
437 |
+
- **Negritas** para valores importantes
|
438 |
+
- Tablas cuando sea apropiado
|
439 |
+
- Listas numeradas y con viñetas
|
440 |
+
|
441 |
+
IMPORTANTE: Basa TODAS las conclusiones en los NÚMEROS específicos de los datos proporcionados.
|
442 |
"""
|
443 |
|
444 |
try:
|
|
|
447 |
max_tokens=4000,
|
448 |
messages=[{
|
449 |
"role": "user",
|
450 |
+
"content": f"{prompt}\n\n{data_summary}"
|
451 |
+
}]
|
452 |
+
)
|
453 |
+
|
454 |
+
# Análisis adicional para generar código si es necesario
|
455 |
+
code_prompt = """
|
456 |
+
Basándote en el análisis anterior, genera código Python para:
|
457 |
+
|
458 |
+
1. Cargar y visualizar estos resultados de ajuste
|
459 |
+
2. Crear gráficos comparativos de modelos (barras para R², RMSE)
|
460 |
+
3. Implementar el mejor modelo identificado
|
461 |
+
4. Generar predicciones con el modelo seleccionado
|
462 |
+
5. Análisis de sensibilidad de parámetros
|
463 |
+
|
464 |
+
Incluye:
|
465 |
+
- Imports necesarios
|
466 |
+
- Funciones bien documentadas
|
467 |
+
- Visualizaciones profesionales
|
468 |
+
- Manejo de errores
|
469 |
+
- Ejemplo de uso
|
470 |
+
|
471 |
+
El código debe ser ejecutable y modular.
|
472 |
+
"""
|
473 |
+
|
474 |
+
code_response = self.client.messages.create(
|
475 |
+
model=claude_model,
|
476 |
+
max_tokens=3000,
|
477 |
+
messages=[{
|
478 |
+
"role": "user",
|
479 |
+
"content": f"{code_prompt}\n\nBasado en estos modelos:\n{response.content[0].text[:1000]}"
|
480 |
}]
|
481 |
)
|
482 |
|
483 |
return {
|
484 |
+
"tipo": "Análisis Comparativo de Modelos Matemáticos",
|
485 |
"analisis_completo": response.content[0].text,
|
486 |
+
"codigo_implementacion": code_response.content[0].text,
|
487 |
+
"resumen_datos": {
|
488 |
+
"n_modelos": len(data),
|
489 |
+
"columnas": list(data.columns),
|
490 |
+
"metricas_disponibles": [col for col in data.columns if any(metric in col.lower()
|
491 |
+
for metric in ['r2', 'rmse', 'aic', 'bic', 'mse'])],
|
492 |
+
"mejor_r2": data['R2'].max() if 'R2' in data.columns else None,
|
493 |
+
"mejor_modelo_r2": data.loc[data['R2'].idxmax()]['Model'] if 'R2' in data.columns and 'Model' in data.columns else None
|
494 |
+
}
|
495 |
}
|
496 |
|
497 |
except Exception as e:
|
498 |
return {"error": str(e)}
|
499 |
|
500 |
+
def process_files(files, claude_model: str, detail_level: str = "detallado") -> Tuple[str, str]:
|
501 |
"""Procesa múltiples archivos"""
|
502 |
processor = FileProcessor()
|
503 |
analyzer = AIAnalyzer(client, model_registry)
|
504 |
results = []
|
505 |
+
all_code = []
|
506 |
|
507 |
for file in files:
|
508 |
if file is None:
|
|
|
511 |
file_name = file.name if hasattr(file, 'name') else "archivo"
|
512 |
file_ext = Path(file_name).suffix.lower()
|
513 |
|
|
|
514 |
with open(file.name, 'rb') as f:
|
515 |
file_content = f.read()
|
516 |
|
|
|
517 |
if file_ext == '.zip':
|
|
|
518 |
extracted_files = processor.extract_from_zip(file_content)
|
519 |
results.append(f"## 📦 Archivo ZIP: {file_name}")
|
520 |
results.append(f"Contiene {len(extracted_files)} archivos\n")
|
|
|
532 |
else:
|
533 |
result = {"tipo": "PDF no reconocido", "contenido": text[:500]}
|
534 |
|
535 |
+
results.append(result.get("analisis_completo", json.dumps(result, indent=2, ensure_ascii=False)))
|
536 |
|
537 |
elif sub_ext in ['.csv', '.xlsx', '.xls']:
|
538 |
if sub_ext == '.csv':
|
|
|
544 |
analysis_type = analyzer.detect_analysis_type(df)
|
545 |
|
546 |
if analysis_type == AnalysisType.FITTING_RESULTS:
|
547 |
+
result = analyzer.analyze_fitting_results(df, claude_model, detail_level)
|
548 |
+
results.append(result.get("analisis_completo", ""))
|
549 |
+
if "codigo_implementacion" in result:
|
550 |
+
all_code.append(result["codigo_implementacion"])
|
551 |
else:
|
552 |
result = analyzer.analyze_fitting_data(df, claude_model)
|
553 |
+
results.append(result.get("analisis", ""))
|
|
|
554 |
|
555 |
results.append("\n---\n")
|
556 |
|
|
|
562 |
|
563 |
if analysis_type == AnalysisType.MATHEMATICAL_MODEL:
|
564 |
result = analyzer.analyze_mathematical_article(text, claude_model)
|
565 |
+
results.append(result.get("modelos", "") + "\n" + result.get("recomendaciones", ""))
|
566 |
else:
|
567 |
result = {"tipo": "PDF - Contenido no identificado", "texto": text[:1000]}
|
568 |
+
results.append(json.dumps(result, indent=2, ensure_ascii=False))
|
|
|
569 |
|
570 |
elif file_ext in ['.csv', '.xlsx', '.xls']:
|
571 |
+
results.append(f"## 📊 Análisis de Resultados: {file_name}")
|
572 |
|
573 |
if file_ext == '.csv':
|
574 |
df = processor.read_csv(file_content)
|
|
|
579 |
analysis_type = analyzer.detect_analysis_type(df)
|
580 |
|
581 |
if analysis_type == AnalysisType.FITTING_RESULTS:
|
582 |
+
result = analyzer.analyze_fitting_results(df, claude_model, detail_level)
|
583 |
+
results.append("### 🎯 ANÁLISIS COMPARATIVO DE MODELOS MATEMÁTICOS")
|
584 |
+
results.append(result.get("analisis_completo", ""))
|
585 |
+
if "codigo_implementacion" in result:
|
586 |
+
all_code.append(result["codigo_implementacion"])
|
587 |
else:
|
588 |
result = analyzer.analyze_fitting_data(df, claude_model)
|
589 |
results.append("### 📈 ANÁLISIS DE DATOS EXPERIMENTALES")
|
590 |
+
results.append(result.get("analisis", ""))
|
|
|
591 |
|
592 |
results.append("\n---\n")
|
593 |
|
594 |
+
analysis_text = "\n".join(results)
|
595 |
+
code_text = "\n\n# " + "="*50 + "\n\n".join(all_code) if all_code else generate_implementation_code(analysis_text)
|
596 |
+
|
597 |
+
return analysis_text, code_text
|
598 |
|
599 |
def generate_implementation_code(analysis_results: str) -> str:
|
600 |
"""Genera código de implementación basado en el análisis"""
|
|
|
606 |
from scipy.optimize import curve_fit, differential_evolution
|
607 |
from sklearn.metrics import r2_score, mean_squared_error
|
608 |
import seaborn as sns
|
609 |
+
from typing import Dict, List, Tuple, Optional
|
610 |
|
611 |
# Configuración de visualización
|
612 |
plt.style.use('seaborn-v0_8-darkgrid')
|
613 |
sns.set_palette("husl")
|
614 |
|
615 |
+
class ComparativeModelAnalyzer:
|
616 |
+
\"\"\"
|
617 |
+
Clase para análisis comparativo de resultados de ajuste de modelos biotecnológicos.
|
618 |
+
Especializada en comparar modelos de biomasa, sustrato y producto.
|
619 |
+
\"\"\"
|
620 |
|
621 |
def __init__(self):
|
622 |
+
self.results_df = None
|
623 |
+
self.best_models = {}
|
624 |
+
self.model_rankings = {}
|
625 |
+
|
626 |
+
def load_results(self, file_path: str) -> pd.DataFrame:
|
627 |
+
\"\"\"Carga resultados de ajuste desde archivo CSV o Excel\"\"\"
|
628 |
+
if file_path.endswith('.csv'):
|
629 |
+
self.results_df = pd.read_csv(file_path)
|
|
|
630 |
else:
|
631 |
+
self.results_df = pd.read_excel(file_path)
|
632 |
+
|
633 |
+
print(f"✅ Datos cargados: {len(self.results_df)} modelos")
|
634 |
+
print(f"📊 Columnas disponibles: {list(self.results_df.columns)}")
|
635 |
|
636 |
return self.results_df
|
637 |
|
638 |
+
def analyze_model_quality(self,
|
639 |
+
r2_col: str = 'R2',
|
640 |
+
rmse_col: str = 'RMSE',
|
641 |
+
aic_col: Optional[str] = 'AIC',
|
642 |
+
bic_col: Optional[str] = 'BIC',
|
643 |
+
model_col: str = 'Model') -> pd.DataFrame:
|
644 |
+
\"\"\"
|
645 |
+
Analiza y compara la calidad de ajuste de todos los modelos.
|
646 |
+
Crea un ranking basado en múltiples métricas.
|
647 |
+
\"\"\"
|
648 |
+
if self.results_df is None:
|
649 |
+
raise ValueError("Primero carga los datos con load_results()")
|
650 |
+
|
651 |
+
# Crear DataFrame de comparación
|
652 |
+
comparison = self.results_df.copy()
|
653 |
+
|
654 |
+
# Calcular puntuación compuesta
|
655 |
+
scores = pd.DataFrame(index=comparison.index)
|
656 |
+
|
657 |
+
# Normalizar métricas (0-1)
|
658 |
+
if r2_col in comparison.columns:
|
659 |
+
scores['r2_score'] = comparison[r2_col] # Ya está entre 0-1
|
660 |
+
|
661 |
+
if rmse_col in comparison.columns:
|
662 |
+
# Invertir y normalizar RMSE (menor es mejor)
|
663 |
+
max_rmse = comparison[rmse_col].max()
|
664 |
+
scores['rmse_score'] = 1 - (comparison[rmse_col] / max_rmse)
|
665 |
+
|
666 |
+
if aic_col and aic_col in comparison.columns:
|
667 |
+
# Invertir y normalizar AIC (menor es mejor)
|
668 |
+
min_aic = comparison[aic_col].min()
|
669 |
+
max_aic = comparison[aic_col].max()
|
670 |
+
scores['aic_score'] = 1 - ((comparison[aic_col] - min_aic) / (max_aic - min_aic))
|
671 |
+
|
672 |
+
if bic_col and bic_col in comparison.columns:
|
673 |
+
# Invertir y normalizar BIC (menor es mejor)
|
674 |
+
min_bic = comparison[bic_col].min()
|
675 |
+
max_bic = comparison[bic_col].max()
|
676 |
+
scores['bic_score'] = 1 - ((comparison[bic_col] - min_bic) / (max_bic - min_bic))
|
677 |
+
|
678 |
+
# Calcular puntuación total (promedio ponderado)
|
679 |
+
weights = {
|
680 |
+
'r2_score': 0.4,
|
681 |
+
'rmse_score': 0.3,
|
682 |
+
'aic_score': 0.15,
|
683 |
+
'bic_score': 0.15
|
684 |
+
}
|
685 |
|
686 |
+
scores['total_score'] = 0
|
687 |
+
for metric, weight in weights.items():
|
688 |
+
if metric in scores.columns:
|
689 |
+
scores['total_score'] += scores[metric] * weight
|
690 |
+
|
691 |
+
# Añadir puntuación al DataFrame de comparación
|
692 |
+
comparison['Score'] = scores['total_score']
|
693 |
+
comparison['Ranking'] = comparison['Score'].rank(ascending=False).astype(int)
|
694 |
+
|
695 |
+
# Ordenar por ranking
|
696 |
+
comparison = comparison.sort_values('Ranking')
|
697 |
+
|
698 |
+
# Identificar mejor modelo
|
699 |
+
best_idx = comparison['Score'].idxmax()
|
700 |
+
self.best_models['overall'] = comparison.loc[best_idx]
|
701 |
+
|
702 |
+
# Imprimir tabla de comparación
|
703 |
+
print("\\n" + "="*80)
|
704 |
+
print("📊 TABLA COMPARATIVA DE MODELOS")
|
705 |
+
print("="*80)
|
706 |
+
|
707 |
+
print(f"\\n{'Rank':<6} {'Modelo':<20} {'R²':<8} {'RMSE':<10} {'AIC':<10} {'BIC':<10} {'Score':<8}")
|
708 |
+
print("-"*80)
|
709 |
+
|
710 |
+
for idx, row in comparison.iterrows():
|
711 |
+
rank = row['Ranking']
|
712 |
+
model = row.get(model_col, f'Model_{idx}')[:20]
|
713 |
+
r2 = row.get(r2_col, 0)
|
714 |
+
rmse = row.get(rmse_col, 0)
|
715 |
+
aic = row.get(aic_col, 'N/A')
|
716 |
+
bic = row.get(bic_col, 'N/A')
|
717 |
+
score = row['Score']
|
718 |
|
719 |
+
print(f"{rank:<6} {model:<20} {r2:<8.4f} {rmse:<10.4f} ", end="")
|
720 |
+
if isinstance(aic, (int, float)):
|
721 |
+
print(f"{aic:<10.2f} ", end="")
|
722 |
+
else:
|
723 |
+
print(f"{'N/A':<10} ", end="")
|
724 |
+
if isinstance(bic, (int, float)):
|
725 |
+
print(f"{bic:<10.2f} ", end="")
|
726 |
+
else:
|
727 |
+
print(f"{'N/A':<10} ", end="")
|
728 |
+
print(f"{score:<8.4f}")
|
729 |
|
730 |
+
print("\\n🏆 MEJOR MODELO: " + comparison.iloc[0].get(model_col, 'No especificado'))
|
731 |
+
print(f" - R² = {comparison.iloc[0].get(r2_col, 0):.4f}")
|
732 |
+
print(f" - RMSE = {comparison.iloc[0].get(rmse_col, 0):.4f}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
733 |
|
734 |
+
self.model_rankings = comparison
|
735 |
+
return comparison
|
|
|
|
|
|
|
736 |
|
737 |
+
def analyze_by_category(self, category_col: Optional[str] = None) -> Dict:
|
738 |
+
\"\"\"
|
739 |
+
Analiza modelos por categoría (biomasa, sustrato, producto).
|
740 |
+
Si no hay columna de categoría, intenta inferir del nombre del modelo.
|
741 |
+
\"\"\"
|
742 |
+
if self.results_df is None:
|
743 |
raise ValueError("Primero carga los datos")
|
744 |
|
745 |
+
categories = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
746 |
|
747 |
+
if category_col and category_col in self.results_df.columns:
|
748 |
+
# Usar columna de categoría existente
|
749 |
+
for cat in self.results_df[category_col].unique():
|
750 |
+
cat_data = self.results_df[self.results_df[category_col] == cat]
|
751 |
+
categories[cat] = cat_data
|
752 |
+
else:
|
753 |
+
# Inferir categorías del nombre del modelo
|
754 |
+
biomass_keywords = ['monod', 'logistic', 'gompertz', 'baranyi', 'growth']
|
755 |
+
substrate_keywords = ['michaelis', 'menten', 'substrate', 'consumption']
|
756 |
+
product_keywords = ['luedeking', 'piret', 'product', 'formation']
|
757 |
+
|
758 |
+
for idx, row in self.results_df.iterrows():
|
759 |
+
model_name = str(row.get('Model', '')).lower()
|
760 |
+
|
761 |
+
if any(kw in model_name for kw in biomass_keywords):
|
762 |
+
if 'biomasa' not in categories:
|
763 |
+
categories['biomasa'] = []
|
764 |
+
categories['biomasa'].append(row)
|
765 |
+
elif any(kw in model_name for kw in substrate_keywords):
|
766 |
+
if 'sustrato' not in categories:
|
767 |
+
categories['sustrato'] = []
|
768 |
+
categories['sustrato'].append(row)
|
769 |
+
elif any(kw in model_name for kw in product_keywords):
|
770 |
+
if 'producto' not in categories:
|
771 |
+
categories['producto'] = []
|
772 |
+
categories['producto'].append(row)
|
773 |
+
else:
|
774 |
+
if 'otros' not in categories:
|
775 |
+
categories['otros'] = []
|
776 |
+
categories['otros'].append(row)
|
777 |
+
|
778 |
+
# Convertir listas a DataFrames
|
779 |
+
for cat in categories:
|
780 |
+
if isinstance(categories[cat], list):
|
781 |
+
categories[cat] = pd.DataFrame(categories[cat])
|
782 |
+
|
783 |
+
# Analizar cada categoría
|
784 |
+
print("\\n" + "="*80)
|
785 |
+
print("📈 ANÁLISIS POR CATEGORÍA")
|
786 |
+
print("="*80)
|
787 |
+
|
788 |
+
for cat, data in categories.items():
|
789 |
+
if len(data) > 0:
|
790 |
+
print(f"\\n### {cat.upper()}")
|
791 |
+
print(f"Modelos analizados: {len(data)}")
|
792 |
+
|
793 |
+
if 'R2' in data.columns:
|
794 |
+
best_idx = data['R2'].idxmax()
|
795 |
+
best_model = data.loc[best_idx]
|
796 |
+
|
797 |
+
print(f"Mejor modelo: {best_model.get('Model', 'N/A')}")
|
798 |
+
print(f" - R² = {best_model.get('R2', 0):.4f}")
|
799 |
+
print(f" - RMSE = {best_model.get('RMSE', 0):.4f}")
|
800 |
+
|
801 |
+
self.best_models[cat] = best_model
|
802 |
|
803 |
+
return categories
|
804 |
|
805 |
+
def plot_comparison(self, save_path: Optional[str] = None) -> plt.Figure:
|
806 |
+
\"\"\"
|
807 |
+
Genera visualizaciones comparativas de los modelos.
|
808 |
+
\"\"\"
|
809 |
+
if self.model_rankings is None:
|
810 |
+
raise ValueError("Primero ejecuta analyze_model_quality()")
|
811 |
+
|
812 |
+
fig = plt.figure(figsize=(16, 10))
|
813 |
|
814 |
+
# Configurar grid de subplots
|
815 |
+
gs = fig.add_gridspec(3, 2, height_ratios=[1, 1, 1], hspace=0.3, wspace=0.3)
|
816 |
|
817 |
+
# 1. Gráfico de barras R²
|
818 |
+
ax1 = fig.add_subplot(gs[0, 0])
|
819 |
+
models = self.model_rankings.get('Model', range(len(self.model_rankings)))
|
820 |
+
r2_values = self.model_rankings.get('R2', [])
|
821 |
|
822 |
+
bars1 = ax1.bar(range(len(models)), r2_values, color='skyblue', edgecolor='navy', alpha=0.7)
|
823 |
ax1.set_title('Comparación de R² por Modelo', fontsize=14, fontweight='bold')
|
824 |
ax1.set_ylabel('R² (Coeficiente de Determinación)', fontsize=12)
|
825 |
+
ax1.set_ylim(0, 1.05)
|
826 |
+
ax1.set_xticks(range(len(models)))
|
827 |
+
ax1.set_xticklabels(models, rotation=45, ha='right')
|
828 |
ax1.grid(True, alpha=0.3)
|
829 |
|
830 |
+
# Añadir valores en las barras
|
831 |
+
for bar, val in zip(bars1, r2_values):
|
832 |
+
height = bar.get_height()
|
833 |
+
ax1.text(bar.get_x() + bar.get_width()/2., height + 0.01,
|
834 |
+
f'{val:.3f}', ha='center', va='bottom', fontsize=9)
|
835 |
|
836 |
+
# 2. Gráfico de barras RMSE
|
837 |
+
ax2 = fig.add_subplot(gs[0, 1])
|
838 |
+
rmse_values = self.model_rankings.get('RMSE', [])
|
839 |
+
|
840 |
+
bars2 = ax2.bar(range(len(models)), rmse_values, color='lightcoral', edgecolor='darkred', alpha=0.7)
|
841 |
ax2.set_title('Comparación de RMSE por Modelo', fontsize=14, fontweight='bold')
|
842 |
ax2.set_ylabel('RMSE (Error Cuadrático Medio)', fontsize=12)
|
843 |
+
ax2.set_xticks(range(len(models)))
|
844 |
+
ax2.set_xticklabels(models, rotation=45, ha='right')
|
845 |
ax2.grid(True, alpha=0.3)
|
846 |
|
847 |
+
# 3. Gráfico de puntuación total
|
848 |
+
ax3 = fig.add_subplot(gs[1, :])
|
849 |
+
scores = self.model_rankings.get('Score', [])
|
850 |
+
rankings = self.model_rankings.get('Ranking', [])
|
851 |
+
|
852 |
+
# Crear gradiente de colores basado en ranking
|
853 |
+
colors = plt.cm.RdYlGn(1 - (rankings - 1) / (len(rankings) - 1))
|
854 |
+
|
855 |
+
bars3 = ax3.bar(range(len(models)), scores, color=colors, edgecolor='black', alpha=0.8)
|
856 |
+
ax3.set_title('Puntuación Total Compuesta (Mayor es Mejor)', fontsize=16, fontweight='bold')
|
857 |
+
ax3.set_ylabel('Puntuación Total', fontsize=12)
|
858 |
+
ax3.set_xticks(range(len(models)))
|
859 |
+
ax3.set_xticklabels(models, rotation=45, ha='right')
|
860 |
+
ax3.grid(True, alpha=0.3)
|
861 |
+
|
862 |
+
# Marcar el mejor modelo
|
863 |
+
best_idx = scores.argmax()
|
864 |
+
bars3[best_idx].set_linewidth(3)
|
865 |
+
bars3[best_idx].set_edgecolor('gold')
|
866 |
+
|
867 |
+
# 4. Tabla de métricas
|
868 |
+
ax4 = fig.add_subplot(gs[2, :])
|
869 |
+
ax4.axis('tight')
|
870 |
+
ax4.axis('off')
|
871 |
+
|
872 |
+
# Preparar datos para la tabla
|
873 |
+
table_data = []
|
874 |
+
for idx, row in self.model_rankings.head(5).iterrows():
|
875 |
+
table_data.append([
|
876 |
+
row.get('Ranking', ''),
|
877 |
+
row.get('Model', '')[:20],
|
878 |
+
f"{row.get('R2', 0):.4f}",
|
879 |
+
f"{row.get('RMSE', 0):.4f}",
|
880 |
+
f"{row.get('AIC', 'N/A'):.2f}" if isinstance(row.get('AIC'), (int, float)) else 'N/A',
|
881 |
+
f"{row.get('BIC', 'N/A'):.2f}" if isinstance(row.get('BIC'), (int, float)) else 'N/A',
|
882 |
+
f"{row.get('Score', 0):.4f}"
|
883 |
+
])
|
884 |
+
|
885 |
+
table = ax4.table(cellText=table_data,
|
886 |
+
colLabels=['Rank', 'Modelo', 'R²', 'RMSE', 'AIC', 'BIC', 'Score'],
|
887 |
+
cellLoc='center',
|
888 |
+
loc='center',
|
889 |
+
colWidths=[0.08, 0.25, 0.12, 0.12, 0.12, 0.12, 0.12])
|
890 |
+
|
891 |
+
table.auto_set_font_size(False)
|
892 |
+
table.set_fontsize(10)
|
893 |
+
table.scale(1.2, 1.5)
|
894 |
+
|
895 |
+
# Colorear la primera fila (mejor modelo)
|
896 |
+
for j in range(len(table_data[0])):
|
897 |
+
table[(1, j)].set_facecolor('#90EE90')
|
898 |
+
|
899 |
+
ax4.set_title('Top 5 Modelos - Tabla Resumen', fontsize=14, fontweight='bold', pad=20)
|
900 |
+
|
901 |
+
plt.suptitle('Análisis Comparativo de Modelos Biotecnológicos', fontsize=18, fontweight='bold')
|
902 |
+
|
903 |
+
if save_path:
|
904 |
+
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
905 |
+
print(f"\\n💾 Gráfico guardado en: {save_path}")
|
906 |
|
|
|
907 |
return fig
|
908 |
+
|
909 |
+
def generate_report(self, output_file: str = 'informe_comparativo.txt'):
|
910 |
+
\"\"\"
|
911 |
+
Genera un informe detallado con todas las conclusiones numéricas.
|
912 |
+
\"\"\"
|
913 |
+
if self.model_rankings is None:
|
914 |
+
raise ValueError("Primero ejecuta analyze_model_quality()")
|
915 |
+
|
916 |
+
report = []
|
917 |
+
report.append("="*80)
|
918 |
+
report.append("INFORME DE ANÁLISIS COMPARATIVO DE MODELOS MATEMÁTICOS")
|
919 |
+
report.append("="*80)
|
920 |
+
report.append(f"\\nFecha: {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
921 |
+
report.append(f"Número de modelos analizados: {len(self.results_df)}")
|
922 |
+
|
923 |
+
# Resumen ejecutivo
|
924 |
+
report.append("\\n" + "-"*40)
|
925 |
+
report.append("RESUMEN EJECUTIVO")
|
926 |
+
report.append("-"*40)
|
927 |
+
|
928 |
+
best_model = self.model_rankings.iloc[0]
|
929 |
+
report.append(f"\\nMEJOR MODELO GLOBAL: {best_model.get('Model', 'N/A')}")
|
930 |
+
report.append(f" - R² = {best_model.get('R2', 0):.4f} (explica {best_model.get('R2', 0)*100:.1f}% de la variabilidad)")
|
931 |
+
report.append(f" - RMSE = {best_model.get('RMSE', 0):.4f}")
|
932 |
+
|
933 |
+
if 'AIC' in best_model:
|
934 |
+
report.append(f" - AIC = {best_model.get('AIC'):.2f}")
|
935 |
+
if 'BIC' in best_model:
|
936 |
+
report.append(f" - BIC = {best_model.get('BIC'):.2f}")
|
937 |
+
|
938 |
+
# Análisis detallado
|
939 |
+
report.append("\\n" + "-"*40)
|
940 |
+
report.append("ANÁLISIS DETALLADO POR MODELO")
|
941 |
+
report.append("-"*40)
|
942 |
+
|
943 |
+
for idx, row in self.model_rankings.iterrows():
|
944 |
+
report.append(f"\\n{row.get('Ranking')}. {row.get('Model', 'N/A')}")
|
945 |
+
report.append(f" Métricas de ajuste:")
|
946 |
+
report.append(f" - R² = {row.get('R2', 0):.4f}")
|
947 |
+
report.append(f" - RMSE = {row.get('RMSE', 0):.4f}")
|
948 |
+
|
949 |
+
# Interpretación
|
950 |
+
r2_val = row.get('R2', 0)
|
951 |
+
if r2_val > 0.95:
|
952 |
+
quality = "EXCELENTE"
|
953 |
+
elif r2_val > 0.90:
|
954 |
+
quality = "MUY BUENO"
|
955 |
+
elif r2_val > 0.80:
|
956 |
+
quality = "BUENO"
|
957 |
+
elif r2_val > 0.70:
|
958 |
+
quality = "ACEPTABLE"
|
959 |
+
else:
|
960 |
+
quality = "POBRE"
|
961 |
+
|
962 |
+
report.append(f" - Calidad del ajuste: {quality}")
|
963 |
+
|
964 |
+
# Análisis por categorías si está disponible
|
965 |
+
if hasattr(self, 'best_models') and len(self.best_models) > 1:
|
966 |
+
report.append("\\n" + "-"*40)
|
967 |
+
report.append("MEJORES MODELOS POR CATEGORÍA")
|
968 |
+
report.append("-"*40)
|
969 |
+
|
970 |
+
for cat, model in self.best_models.items():
|
971 |
+
if cat != 'overall':
|
972 |
+
report.append(f"\\n{cat.upper()}:")
|
973 |
+
report.append(f" Mejor modelo: {model.get('Model', 'N/A')}")
|
974 |
+
report.append(f" - R² = {model.get('R2', 0):.4f}")
|
975 |
+
report.append(f" - RMSE = {model.get('RMSE', 0):.4f}")
|
976 |
+
|
977 |
+
# Recomendaciones
|
978 |
+
report.append("\\n" + "-"*40)
|
979 |
+
report.append("RECOMENDACIONES")
|
980 |
+
report.append("-"*40)
|
981 |
+
|
982 |
+
report.append(f"\\n1. Para predicciones generales, usar: {best_model.get('Model', 'N/A')}")
|
983 |
+
report.append("2. Validar con conjunto de datos independiente")
|
984 |
+
report.append("3. Considerar análisis de residuos")
|
985 |
+
report.append("4. Evaluar estabilidad de parámetros")
|
986 |
+
|
987 |
+
# Guardar informe
|
988 |
+
with open(output_file, 'w', encoding='utf-8') as f:
|
989 |
+
f.write("\\n".join(report))
|
990 |
+
|
991 |
+
print(f"\\n📄 Informe guardado en: {output_file}")
|
992 |
+
|
993 |
+
return "\\n".join(report)
|
994 |
|
995 |
+
# Implementación de modelos específicos
|
996 |
+
class BiotechModels:
|
997 |
+
\"\"\"Biblioteca de modelos biotecnológicos comunes\"\"\"
|
998 |
+
|
999 |
+
@staticmethod
|
1000 |
+
def monod(S, mu_max, Ks):
|
1001 |
+
\"\"\"Modelo de Monod para crecimiento\"\"\"
|
1002 |
+
return mu_max * S / (Ks + S)
|
1003 |
+
|
1004 |
+
@staticmethod
|
1005 |
+
def logistic(t, K, r, t0):
|
1006 |
+
\"\"\"Modelo logístico\"\"\"
|
1007 |
+
return K / (1 + np.exp(-r * (t - t0)))
|
1008 |
+
|
1009 |
+
@staticmethod
|
1010 |
+
def gompertz(t, A, mu, lambda_param):
|
1011 |
+
\"\"\"Modelo de Gompertz\"\"\"
|
1012 |
+
return A * np.exp(-np.exp(mu * np.e / A * (lambda_param - t) + 1))
|
1013 |
+
|
1014 |
+
@staticmethod
|
1015 |
+
def michaelis_menten(S, Vmax, Km):
|
1016 |
+
\"\"\"Modelo de Michaelis-Menten\"\"\"
|
1017 |
+
return Vmax * S / (Km + S)
|
1018 |
+
|
1019 |
+
@staticmethod
|
1020 |
+
def luedeking_piret_integrated(t, X0, mu_max, alpha, beta):
|
1021 |
+
\"\"\"Modelo de Luedeking-Piret integrado\"\"\"
|
1022 |
+
X = X0 * np.exp(mu_max * t)
|
1023 |
+
P = alpha * (X - X0) + beta * X0 * (np.exp(mu_max * t) - 1) / mu_max
|
1024 |
+
return P
|
1025 |
|
1026 |
+
# Ejemplo de uso
|
1027 |
if __name__ == "__main__":
|
1028 |
+
print("🧬 Sistema de Análisis Comparativo de Modelos Biotecnológicos")
|
1029 |
+
print("="*60)
|
|
|
|
|
|
|
1030 |
|
1031 |
+
# Crear analizador
|
1032 |
+
analyzer = ComparativeModelAnalyzer()
|
1033 |
|
1034 |
+
# Instrucciones
|
1035 |
+
print("\\n📋 INSTRUCCIONES DE USO:")
|
1036 |
+
print("1. analyzer.load_results('tu_archivo.csv')")
|
1037 |
+
print("2. analyzer.analyze_model_quality()")
|
1038 |
+
print("3. analyzer.analyze_by_category()")
|
1039 |
+
print("4. analyzer.plot_comparison()")
|
1040 |
+
print("5. analyzer.generate_report()")
|
1041 |
|
1042 |
+
print("\\n✨ ¡Sistema listo para análisis!")
|
|
|
|
|
1043 |
"""
|
1044 |
|
1045 |
return code
|
1046 |
|
1047 |
+
# Interfaz Gradio optimizada
|
1048 |
def create_interface():
|
1049 |
with gr.Blocks(
|
1050 |
+
title="Analizador Comparativo de Modelos Biotecnológicos",
|
1051 |
theme=gr.themes.Soft(),
|
1052 |
css="""
|
1053 |
.gradio-container {
|
|
|
1059 |
border-radius: 10px;
|
1060 |
border-left: 5px solid #4CAF50;
|
1061 |
}
|
1062 |
+
.comparison-table {
|
1063 |
+
background-color: #f9f9f9;
|
1064 |
+
padding: 10px;
|
1065 |
+
border-radius: 8px;
|
1066 |
+
font-family: monospace;
|
1067 |
+
}
|
1068 |
"""
|
1069 |
) as demo:
|
1070 |
|
1071 |
gr.Markdown("""
|
1072 |
+
# 🧬 Analizador Comparativo de Modelos Biotecnológicos
|
1073 |
+
|
1074 |
+
### 🎯 Especializado en:
|
1075 |
+
- **Análisis comparativo** de resultados de ajuste de modelos matemáticos
|
1076 |
+
- **Determinación del mejor modelo** con justificación numérica
|
1077 |
+
- **Análisis específico** para biomasa, sustrato y producto
|
1078 |
+
- **Conclusiones numéricas** detalladas y ordenadas
|
1079 |
+
- **Generación de código** para implementación y análisis
|
1080 |
+
|
1081 |
+
### 📊 Métricas analizadas:
|
1082 |
+
- R² (Coeficiente de determinación)
|
1083 |
+
- RMSE (Error cuadrático medio)
|
1084 |
+
- AIC/BIC (Criterios de información)
|
1085 |
+
- Parámetros específicos del modelo
|
1086 |
+
- Intervalos de confianza
|
|
|
|
|
|
|
|
|
|
|
1087 |
""")
|
1088 |
|
1089 |
with gr.Row():
|
|
|
1102 |
info="Selecciona el modelo de IA"
|
1103 |
)
|
1104 |
|
1105 |
+
detail_level = gr.Radio(
|
1106 |
+
choices=["detallado", "resumido"],
|
1107 |
+
value="detallado",
|
1108 |
+
label="📋 Nivel de detalle del análisis",
|
1109 |
+
info="Detallado: análisis completo | Resumido: puntos clave"
|
1110 |
+
)
|
1111 |
+
|
1112 |
analyze_btn = gr.Button(
|
1113 |
+
"🚀 Analizar y Comparar Modelos",
|
1114 |
variant="primary",
|
1115 |
size="lg"
|
1116 |
)
|
|
|
1136 |
|
1137 |
with gr.Column(scale=2):
|
1138 |
analysis_output = gr.Markdown(
|
1139 |
+
label="📊 Análisis Comparativo",
|
1140 |
elem_classes=["highlight-results"]
|
1141 |
)
|
1142 |
|
1143 |
code_output = gr.Code(
|
1144 |
+
label="💻 Código de Implementación",
|
1145 |
language="python",
|
1146 |
+
interactive=True,
|
1147 |
+
lines=20
|
1148 |
)
|
1149 |
|
1150 |
+
# Guía de formato de datos
|
1151 |
+
with gr.Accordion("📋 Formato de datos esperado", open=False):
|
1152 |
+
gr.Markdown("""
|
1153 |
+
### Estructura ideal del CSV/Excel:
|
1154 |
+
|
1155 |
+
| Model | R2 | RMSE | AIC | BIC | mu_max | Ks | Parameters |
|
1156 |
+
|-------|-----|------|-----|-----|--------|-------|------------|
|
1157 |
+
| Monod | 0.985 | 0.023 | -45.2 | -42.1 | 0.45 | 2.1 | {...} |
|
1158 |
+
| Logistic | 0.976 | 0.031 | -42.1 | -39.5 | 0.42 | - | {...} |
|
1159 |
+
| Gompertz | 0.992 | 0.018 | -48.5 | -45.2 | 0.48 | - | {...} |
|
1160 |
+
|
1161 |
+
**Columnas mínimas requeridas:**
|
1162 |
+
- `Model` o `Modelo`: Nombre del modelo
|
1163 |
+
- `R2` o `R_squared`: Coeficiente de determinación
|
1164 |
+
- `RMSE` o `MSE`: Error de ajuste
|
1165 |
+
|
1166 |
+
**Columnas opcionales pero recomendadas:**
|
1167 |
+
- `AIC`, `BIC`: Criterios de información
|
1168 |
+
- Parámetros específicos del modelo
|
1169 |
+
- `SE` o `Standard_Error`: Errores estándar
|
1170 |
+
- `CI_lower`, `CI_upper`: Intervalos de confianza
|
1171 |
+
""")
|
1172 |
+
|
1173 |
# Ejemplos
|
1174 |
gr.Examples(
|
1175 |
examples=[
|
1176 |
+
[["examples/biomass_models_comparison.csv"], "claude-3-5-sonnet-20241022", "detallado"],
|
1177 |
+
[["examples/substrate_kinetics_results.xlsx"], "claude-3-5-sonnet-20241022", "resumido"],
|
1178 |
+
[["examples/product_formation_fits.csv"], "claude-3-opus-20240229", "detallado"]
|
1179 |
],
|
1180 |
+
inputs=[files_input, model_selector, detail_level],
|
1181 |
+
label="📚 Ejemplos de análisis"
|
1182 |
)
|
1183 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1184 |
# Eventos
|
1185 |
analyze_btn.click(
|
1186 |
+
fn=lambda files, model, detail: process_files(files, model, detail) if files else (
|
1187 |
+
"Por favor sube archivos con resultados de ajuste para analizar",
|
1188 |
+
""
|
1189 |
),
|
1190 |
+
inputs=[files_input, model_selector, detail_level],
|
1191 |
outputs=[analysis_output, code_output]
|
1192 |
)
|
1193 |
|
1194 |
+
# Footer con información adicional
|
1195 |
+
gr.Markdown("""
|
1196 |
+
---
|
1197 |
+
### 🔍 Qué analiza específicamente:
|
1198 |
+
|
1199 |
+
1. **Comparación de modelos**: Ranking basado en múltiples métricas
|
1200 |
+
2. **Mejor modelo**: Identificación y justificación numérica
|
1201 |
+
3. **Análisis por tipo**:
|
1202 |
+
- **Biomasa**: μmax, Xmax, tiempo de duplicación
|
1203 |
+
- **Sustrato**: Ks, Km, velocidades de consumo, Yx/s
|
1204 |
+
- **Producto**: α, β, productividad, Yp/x
|
1205 |
+
4. **Significado biológico**: Interpretación de parámetros
|
1206 |
+
5. **Conclusiones numéricas**: Valores óptimos, rangos de operación
|
1207 |
+
6. **Código Python**: Implementación lista para usar
|
1208 |
+
|
1209 |
+
### 💡 Tips para mejores resultados:
|
1210 |
+
- Incluye todas las métricas de ajuste disponibles
|
1211 |
+
- Usa nombres descriptivos para los modelos
|
1212 |
+
- Incluye errores estándar si están disponibles
|
1213 |
+
- Añade información de condiciones experimentales si es relevante
|
1214 |
+
""")
|
1215 |
+
|
1216 |
# Cargar info inicial del modelo
|
1217 |
demo.load(
|
1218 |
fn=lambda: update_model_info("claude-3-5-sonnet-20241022"),
|