Proyecto1-ML / analisis.py
guille03's picture
cambios
316a44d
Raw
History Blame Contribute Delete
9.81 kB
import os
import tempfile
import pandas as pd
from ydata_profiling import ProfileReport
from load_dataframe import cargar_dataframe, analizar_nulos_por_columna, get_file_metadata
from graficos_r import generar_histograma_r, generar_barras_r, generar_boxplot_r
class AnalizadorDatos:
def __init__(self, file_path: str):
self.file_path = file_path
self.df = None
self.metadata = None
self.nulos_por_columna = None
self.reporte_path = None
self.graficos = {}
def cargar_datos(self) -> bool:
"""Carga y valida los datos"""
self.df, error = cargar_dataframe(self.file_path)
if error:
print(error)
return False
self.metadata = get_file_metadata(self.file_path)
self.nulos_por_columna = analizar_nulos_por_columna(self.df)
return True
def generar_reporte_nulos(self) -> str:
"""Genera un reporte detallado de valores nulos por columna"""
if not self.nulos_por_columna:
return "No hay datos de nulos disponibles"
reporte = "=== ANÁLISIS DE VALORES NULOS POR COLUMNA ===\n\n"
reporte += f"Total filas en dataset: {len(self.df)}\n"
reporte += f"Total columnas: {len(self.df.columns)}\n\n"
for col in self.nulos_por_columna:
reporte += f"Columna: {col['nombre_columna']} ({col['tipo_dato']})\n"
reporte += f" - Valores nulos: {col['total_nulos']} ({col['porcentaje_nulos']}%)\n"
reporte += f" - Valores no nulos: {col['no_nulos']} ({col['porcentaje_no_nulos']}%)\n"
reporte += f" - Valores únicos: {col['valores_unicos']}\n"
if col['ejemplo_valores']:
reporte += f" - Ejemplos: {', '.join(col['ejemplo_valores'])}\n"
else:
reporte += " - Ejemplos: TODOS SON NULOS\n"
# Recomendación basada en porcentaje de nulos
if col['porcentaje_nulos'] > 30:
reporte += " - RECOMENDACIÓN: Considerar eliminar o imputar esta columna\n"
elif col['porcentaje_nulos'] > 0:
reporte += " - RECOMENDACIÓN: Evaluar imputación de valores\n"
else:
reporte += " - RECOMENDACIÓN: Sin problemas de nulos\n"
reporte += "\n"
return reporte
def generar_reporte(self, minimal: bool = True) -> str:
"""Genera reporte de profiling"""
try:
self.reporte_path = tempfile.NamedTemporaryFile(
suffix='.html',
delete=False
).name
profile = ProfileReport(
self.df,
title=f"Reporte de Análisis - {self.metadata['nombre']}",
minimal=minimal,
explorative=True
)
profile.to_file(self.reporte_path)
return self.reporte_path
except Exception as e:
print(f"Error generando reporte: {e}")
return None
def generar_graficos(self, columnas_analisis: list) -> dict:
"""Genera gráficos para las columnas especificadas"""
os.makedirs("graficos", exist_ok=True)
for col in columnas_analisis:
if col not in self.df.columns:
continue
output_path = f"graficos/{col}.png"
if pd.api.types.is_numeric_dtype(self.df[col]):
path = generar_histograma_r(self.df, col, output_path)
if path:
self.graficos[col] = {'tipo': 'histograma', 'path': path}
# Gráfico adicional si hay una columna categórica adecuada
categoricas = [c for c in self.df.columns
if pd.api.types.is_categorical_dtype(self.df[c])
or pd.api.types.is_object_dtype(self.df[c])]
if categoricas and len(self.df[categoricas[0]].unique()) <= 10:
path = generar_boxplot_r(self.df, col, categoricas[0], f"graficos/{col}_by_{categoricas[0]}.png")
if path:
self.graficos[f"{col}_by_{categoricas[0]}"] = {'tipo': 'boxplot', 'path': path}
else:
path = generar_barras_r(self.df, col, output_path)
if path:
self.graficos[col] = {'tipo': 'barras', 'path': path}
return self.graficos
def resumen_analisis(self) -> dict:
"""Genera un resumen estructurado del análisis"""
return {
'metadata': self.metadata,
'estadisticas': {
'filas': len(self.df),
'columnas': len(self.df.columns),
'columnas_con_nulos': sum(1 for col in self.nulos_por_columna if col['total_nulos'] > 0),
'columnas_completas': sum(1 for col in self.nulos_por_columna if col['total_nulos'] == 0)
},
'nulos_por_columna': self.nulos_por_columna,
'graficos_generados': list(self.graficos.keys()),
'rutas': {
'reporte': self.reporte_path,
'graficos': {k: v['path'] for k, v in self.graficos.items()}
}
}
def procesar_valores_nulos(self, metodo: str) -> str:
"""Procesa los valores nulos del DataFrame según el método especificado"""
if self.df is None:
return "Error: No hay datos cargados"
df_original = self.df.copy()
columnas_numericas = self.df.select_dtypes(include=['number']).columns
columnas_categoricas = self.df.select_dtypes(include=['object', 'category']).columns
try:
if metodo == "media":
# Para columnas numéricas: llenar con la media
for col in columnas_numericas:
if self.df[col].isnull().any():
media = self.df[col].mean()
self.df[col].fillna(media, inplace=True)
# Para columnas categóricas: llenar con la moda
for col in columnas_categoricas:
if self.df[col].isnull().any():
moda = self.df[col].mode()
if not moda.empty:
self.df[col].fillna(moda[0], inplace=True)
elif metodo == "mediana":
# Para columnas numéricas: llenar con la mediana
for col in columnas_numericas:
if self.df[col].isnull().any():
mediana = self.df[col].median()
self.df[col].fillna(mediana, inplace=True)
# Para columnas categóricas: llenar con la moda
for col in columnas_categoricas:
if self.df[col].isnull().any():
moda = self.df[col].mode()
if not moda.empty:
self.df[col].fillna(moda[0], inplace=True)
elif metodo == "moda":
# Para todas las columnas: llenar con la moda
for col in self.df.columns:
if self.df[col].isnull().any():
moda = self.df[col].mode()
if not moda.empty:
self.df[col].fillna(moda[0], inplace=True)
# Actualizar análisis de nulos
self.nulos_por_columna = analizar_nulos_por_columna(self.df)
nulos_antes = df_original.isnull().sum().sum()
nulos_despues = self.df.isnull().sum().sum()
return (f"Procesamiento completado con método: {metodo}\n"
f"Valores nulos antes: {nulos_antes}\n"
f"Valores nulos después: {nulos_despues}\n"
f"Valores procesados: {nulos_antes - nulos_despues}")
except Exception as e:
# Restaurar DataFrame original en caso de error
self.df = df_original
return f"Error al procesar valores nulos: {str(e)}"
def analisis_completo(file_path: str, columnas_analisis: list = None):
"""Función principal para ejecutar análisis completo"""
analizador = AnalizadorDatos(file_path)
if not analizador.cargar_datos():
return None
# Si no se especifican columnas, usar las que tienen menos del 50% de nulos
if not columnas_analisis:
columnas_validas = [
col['nombre_columna'] for col in analizador.nulos_por_columna
if col['porcentaje_nulos'] < 50
]
columnas_analisis = columnas_validas # Limitar a 5 columnas por defecto
# Generar outputs
analizador.generar_reporte()
analizador.generar_graficos(columnas_analisis)
# Imprimir reporte de nulos en consola
print(analizador.generar_reporte_nulos())
return analizador.resumen_analisis()
if __name__ == "__main__":
# Ejemplo de uso con análisis mejorado de nulos
resumen = analisis_completo(
file_path="Grammy Award Nominees and Winners 1958-2024.csv"
)
if resumen:
print("\n=== RESUMEN GENERAL ===")
print(f"Columnas con nulos: {resumen['estadisticas']['columnas_con_nulos']}")
print(f"Columnas completas: {resumen['estadisticas']['columnas_completas']}")
print("\nTop 5 columnas con más nulos:")
for col in resumen['nulos_por_columna'][:5]:
print(f"- {col['nombre_columna']}: {col['porcentaje_nulos']}% nulos")