Spaces:
Sleeping
Sleeping
proyecto
Browse files- Dockerfile +37 -0
- analisis.py +167 -0
- app.py +76 -0
- graficos_r.py +167 -0
- load_dataframe.py +94 -0
- requirements.txt +7 -0
Dockerfile
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Imagen base con Python
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Instalar R y herramientas necesarias del sistema
|
| 5 |
+
RUN apt-get update && apt-get install -y \
|
| 6 |
+
r-base \
|
| 7 |
+
libcurl4-openssl-dev \
|
| 8 |
+
libssl-dev \
|
| 9 |
+
libxml2-dev \
|
| 10 |
+
libfontconfig1 \
|
| 11 |
+
libfreetype6 \
|
| 12 |
+
libpng-dev \
|
| 13 |
+
libtiff5 \
|
| 14 |
+
libjpeg62-turbo-dev \
|
| 15 |
+
build-essential \
|
| 16 |
+
fonts-dejavu \
|
| 17 |
+
unzip \
|
| 18 |
+
wget \
|
| 19 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 20 |
+
|
| 21 |
+
# Instalar paquetes de R necesarios
|
| 22 |
+
RUN R -e "install.packages(c('ggplot2', 'dplyr', 'RColorBrewer'), repos='http://cran.rstudio.com/')"
|
| 23 |
+
|
| 24 |
+
# Establecer directorio de trabajo
|
| 25 |
+
WORKDIR /app
|
| 26 |
+
|
| 27 |
+
# Copiar archivos del proyecto
|
| 28 |
+
COPY . .
|
| 29 |
+
|
| 30 |
+
# Instalar dependencias de Python
|
| 31 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 32 |
+
|
| 33 |
+
# Exponer el puerto de Gradio (por convenci贸n)
|
| 34 |
+
EXPOSE 7860
|
| 35 |
+
|
| 36 |
+
# Ejecutar la aplicaci贸n
|
| 37 |
+
CMD ["python", "app.py"]
|
analisis.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import tempfile
|
| 3 |
+
import pandas as pd
|
| 4 |
+
from ydata_profiling import ProfileReport
|
| 5 |
+
from load_dataframe import cargar_dataframe, analizar_nulos_por_columna, get_file_metadata
|
| 6 |
+
from graficos_r import generar_histograma_r, generar_barras_r, generar_boxplot_r
|
| 7 |
+
|
| 8 |
+
class AnalizadorDatos:
|
| 9 |
+
def __init__(self, file_path: str):
|
| 10 |
+
self.file_path = file_path
|
| 11 |
+
self.df = None
|
| 12 |
+
self.metadata = None
|
| 13 |
+
self.nulos_por_columna = None
|
| 14 |
+
self.reporte_path = None
|
| 15 |
+
self.graficos = {}
|
| 16 |
+
|
| 17 |
+
def cargar_datos(self) -> bool:
|
| 18 |
+
"""Carga y valida los datos"""
|
| 19 |
+
self.df, error = cargar_dataframe(self.file_path)
|
| 20 |
+
if error:
|
| 21 |
+
print(error)
|
| 22 |
+
return False
|
| 23 |
+
|
| 24 |
+
self.metadata = get_file_metadata(self.file_path)
|
| 25 |
+
self.nulos_por_columna = analizar_nulos_por_columna(self.df)
|
| 26 |
+
return True
|
| 27 |
+
|
| 28 |
+
def generar_reporte_nulos(self) -> str:
|
| 29 |
+
"""Genera un reporte detallado de valores nulos por columna"""
|
| 30 |
+
if not self.nulos_por_columna:
|
| 31 |
+
return "No hay datos de nulos disponibles"
|
| 32 |
+
|
| 33 |
+
reporte = "=== AN脕LISIS DE VALORES NULOS POR COLUMNA ===\n\n"
|
| 34 |
+
reporte += f"Total filas en dataset: {len(self.df)}\n"
|
| 35 |
+
reporte += f"Total columnas: {len(self.df.columns)}\n\n"
|
| 36 |
+
|
| 37 |
+
for col in self.nulos_por_columna:
|
| 38 |
+
reporte += f"Columna: {col['nombre_columna']} ({col['tipo_dato']})\n"
|
| 39 |
+
reporte += f" - Valores nulos: {col['total_nulos']} ({col['porcentaje_nulos']}%)\n"
|
| 40 |
+
reporte += f" - Valores no nulos: {col['no_nulos']} ({col['porcentaje_no_nulos']}%)\n"
|
| 41 |
+
reporte += f" - Valores 煤nicos: {col['valores_unicos']}\n"
|
| 42 |
+
|
| 43 |
+
if col['ejemplo_valores']:
|
| 44 |
+
reporte += f" - Ejemplos: {', '.join(col['ejemplo_valores'])}\n"
|
| 45 |
+
else:
|
| 46 |
+
reporte += " - Ejemplos: TODOS SON NULOS\n"
|
| 47 |
+
|
| 48 |
+
# Recomendaci贸n basada en porcentaje de nulos
|
| 49 |
+
if col['porcentaje_nulos'] > 30:
|
| 50 |
+
reporte += " - RECOMENDACI脫N: Considerar eliminar o imputar esta columna\n"
|
| 51 |
+
elif col['porcentaje_nulos'] > 0:
|
| 52 |
+
reporte += " - RECOMENDACI脫N: Evaluar imputaci贸n de valores\n"
|
| 53 |
+
else:
|
| 54 |
+
reporte += " - RECOMENDACI脫N: Sin problemas de nulos\n"
|
| 55 |
+
|
| 56 |
+
reporte += "\n"
|
| 57 |
+
|
| 58 |
+
return reporte
|
| 59 |
+
|
| 60 |
+
def generar_reporte(self, minimal: bool = True) -> str:
|
| 61 |
+
"""Genera reporte de profiling"""
|
| 62 |
+
try:
|
| 63 |
+
self.reporte_path = tempfile.NamedTemporaryFile(
|
| 64 |
+
suffix='.html',
|
| 65 |
+
delete=False
|
| 66 |
+
).name
|
| 67 |
+
|
| 68 |
+
profile = ProfileReport(
|
| 69 |
+
self.df,
|
| 70 |
+
title=f"Reporte de An谩lisis - {self.metadata['nombre']}",
|
| 71 |
+
minimal=minimal,
|
| 72 |
+
explorative=True
|
| 73 |
+
)
|
| 74 |
+
profile.to_file(self.reporte_path)
|
| 75 |
+
return self.reporte_path
|
| 76 |
+
except Exception as e:
|
| 77 |
+
print(f"Error generando reporte: {e}")
|
| 78 |
+
return None
|
| 79 |
+
|
| 80 |
+
def generar_graficos(self, columnas_analisis: list) -> dict:
|
| 81 |
+
"""Genera gr谩ficos para las columnas especificadas"""
|
| 82 |
+
os.makedirs("graficos", exist_ok=True)
|
| 83 |
+
|
| 84 |
+
for col in columnas_analisis:
|
| 85 |
+
if col not in self.df.columns:
|
| 86 |
+
continue
|
| 87 |
+
|
| 88 |
+
output_path = f"graficos/{col}.png"
|
| 89 |
+
|
| 90 |
+
if pd.api.types.is_numeric_dtype(self.df[col]):
|
| 91 |
+
path = generar_histograma_r(self.df, col, output_path)
|
| 92 |
+
if path:
|
| 93 |
+
self.graficos[col] = {'tipo': 'histograma', 'path': path}
|
| 94 |
+
|
| 95 |
+
# Gr谩fico adicional si hay una columna categ贸rica adecuada
|
| 96 |
+
categoricas = [c for c in self.df.columns
|
| 97 |
+
if pd.api.types.is_categorical_dtype(self.df[c])
|
| 98 |
+
or pd.api.types.is_object_dtype(self.df[c])]
|
| 99 |
+
if categoricas and len(self.df[categoricas[0]].unique()) <= 10:
|
| 100 |
+
path = generar_boxplot_r(self.df, col, categoricas[0], f"graficos/{col}_by_{categoricas[0]}.png")
|
| 101 |
+
if path:
|
| 102 |
+
self.graficos[f"{col}_by_{categoricas[0]}"] = {'tipo': 'boxplot', 'path': path}
|
| 103 |
+
else:
|
| 104 |
+
path = generar_barras_r(self.df, col, output_path)
|
| 105 |
+
if path:
|
| 106 |
+
self.graficos[col] = {'tipo': 'barras', 'path': path}
|
| 107 |
+
|
| 108 |
+
return self.graficos
|
| 109 |
+
|
| 110 |
+
def resumen_analisis(self) -> dict:
|
| 111 |
+
"""Genera un resumen estructurado del an谩lisis"""
|
| 112 |
+
return {
|
| 113 |
+
'metadata': self.metadata,
|
| 114 |
+
'estadisticas': {
|
| 115 |
+
'filas': len(self.df),
|
| 116 |
+
'columnas': len(self.df.columns),
|
| 117 |
+
'columnas_con_nulos': sum(1 for col in self.nulos_por_columna if col['total_nulos'] > 0),
|
| 118 |
+
'columnas_completas': sum(1 for col in self.nulos_por_columna if col['total_nulos'] == 0)
|
| 119 |
+
},
|
| 120 |
+
'nulos_por_columna': self.nulos_por_columna,
|
| 121 |
+
'graficos_generados': list(self.graficos.keys()),
|
| 122 |
+
'rutas': {
|
| 123 |
+
'reporte': self.reporte_path,
|
| 124 |
+
'graficos': {k: v['path'] for k, v in self.graficos.items()}
|
| 125 |
+
}
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def analisis_completo(file_path: str, columnas_analisis: list = None):
|
| 130 |
+
"""Funci贸n principal para ejecutar an谩lisis completo"""
|
| 131 |
+
analizador = AnalizadorDatos(file_path)
|
| 132 |
+
|
| 133 |
+
if not analizador.cargar_datos():
|
| 134 |
+
return None
|
| 135 |
+
|
| 136 |
+
# Si no se especifican columnas, usar las que tienen menos del 50% de nulos
|
| 137 |
+
if not columnas_analisis:
|
| 138 |
+
columnas_validas = [
|
| 139 |
+
col['nombre_columna'] for col in analizador.nulos_por_columna
|
| 140 |
+
if col['porcentaje_nulos'] < 50
|
| 141 |
+
]
|
| 142 |
+
columnas_analisis = columnas_validas # Limitar a 5 columnas por defecto
|
| 143 |
+
|
| 144 |
+
# Generar outputs
|
| 145 |
+
analizador.generar_reporte()
|
| 146 |
+
analizador.generar_graficos(columnas_analisis)
|
| 147 |
+
|
| 148 |
+
# Imprimir reporte de nulos en consola
|
| 149 |
+
print(analizador.generar_reporte_nulos())
|
| 150 |
+
|
| 151 |
+
return analizador.resumen_analisis()
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
if __name__ == "__main__":
|
| 155 |
+
# Ejemplo de uso con an谩lisis mejorado de nulos
|
| 156 |
+
resumen = analisis_completo(
|
| 157 |
+
file_path="Grammy Award Nominees and Winners 1958-2024.csv"
|
| 158 |
+
)
|
| 159 |
+
|
| 160 |
+
if resumen:
|
| 161 |
+
print("\n=== RESUMEN GENERAL ===")
|
| 162 |
+
print(f"Columnas con nulos: {resumen['estadisticas']['columnas_con_nulos']}")
|
| 163 |
+
print(f"Columnas completas: {resumen['estadisticas']['columnas_completas']}")
|
| 164 |
+
|
| 165 |
+
print("\nTop 5 columnas con m谩s nulos:")
|
| 166 |
+
for col in resumen['nulos_por_columna'][:5]:
|
| 167 |
+
print(f"- {col['nombre_columna']}: {col['porcentaje_nulos']}% nulos")
|
app.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
from analisis import AnalizadorDatos
|
| 3 |
+
|
| 4 |
+
analizador_global = None
|
| 5 |
+
|
| 6 |
+
def cargar_y_mostrar(file):
|
| 7 |
+
global analizador_global
|
| 8 |
+
analizador = AnalizadorDatos(file.name)
|
| 9 |
+
|
| 10 |
+
if not analizador.cargar_datos():
|
| 11 |
+
return "Error al cargar archivo", None, None
|
| 12 |
+
|
| 13 |
+
analizador_global = analizador
|
| 14 |
+
|
| 15 |
+
return (
|
| 16 |
+
f"Archivo cargado correctamente: {analizador.metadata['nombre']}\n"
|
| 17 |
+
f"- Tama帽o: {analizador.metadata['tama帽o_mb']} MB\n"
|
| 18 |
+
f"- Filas: {len(analizador.df)}\n"
|
| 19 |
+
f"- Columnas: {len(analizador.df.columns)}\n",
|
| 20 |
+
analizador.df.head(),
|
| 21 |
+
[col['nombre_columna'] for col in analizador.nulos_por_columna]
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
def generar_graficos(columnas):
|
| 25 |
+
global analizador_global
|
| 26 |
+
if not analizador_global:
|
| 27 |
+
return None
|
| 28 |
+
|
| 29 |
+
graficos = analizador_global.generar_graficos(columnas)
|
| 30 |
+
return [gr.Image(value=v['path'], label=k) for k, v in graficos.items()]
|
| 31 |
+
|
| 32 |
+
def generar_reporte():
|
| 33 |
+
global analizador_global
|
| 34 |
+
if not analizador_global:
|
| 35 |
+
return None, None
|
| 36 |
+
|
| 37 |
+
reporte_path = analizador_global.generar_reporte()
|
| 38 |
+
reporte_nulos = analizador_global.generar_reporte_nulos()
|
| 39 |
+
return reporte_path, reporte_nulos
|
| 40 |
+
|
| 41 |
+
with gr.Blocks(title="An谩lisis exploratorio avanzado con R y Python") as demo:
|
| 42 |
+
gr.Markdown("## Exploraci贸n de datos con R, Python y Gradio")
|
| 43 |
+
|
| 44 |
+
with gr.Row():
|
| 45 |
+
archivo_input = gr.File(label="Sube un archivo", file_types=[".csv", ".xlsx", ".json", ".parquet"])
|
| 46 |
+
cargar_btn = gr.Button("Cargar archivo")
|
| 47 |
+
|
| 48 |
+
estado_carga = gr.Textbox(label="Estado de carga", interactive=False)
|
| 49 |
+
df_preview = gr.Dataframe(label="Vista previa")
|
| 50 |
+
columnas_selector = gr.CheckboxGroup(label="Selecciona columnas para an谩lisis", choices=[])
|
| 51 |
+
|
| 52 |
+
cargar_btn.click(
|
| 53 |
+
fn=cargar_y_mostrar,
|
| 54 |
+
inputs=[archivo_input],
|
| 55 |
+
outputs=[estado_carga, df_preview, columnas_selector]
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
graficos_output = gr.Group(label="Gr谩ficos generados")
|
| 59 |
+
|
| 60 |
+
graficar_btn = gr.Button("Generar gr谩ficos")
|
| 61 |
+
graficar_btn.click(fn=generar_graficos, inputs=[columnas_selector], outputs=[graficos_output])
|
| 62 |
+
|
| 63 |
+
with gr.Row():
|
| 64 |
+
generar_reporte_btn = gr.Button("Generar reporte")
|
| 65 |
+
reporte_html = gr.File(label="Descargar reporte")
|
| 66 |
+
reporte_nulos = gr.Textbox(label="An谩lisis de nulos")
|
| 67 |
+
|
| 68 |
+
generar_reporte_btn.click(
|
| 69 |
+
fn=generar_reporte,
|
| 70 |
+
inputs=[],
|
| 71 |
+
outputs=[reporte_html, reporte_nulos]
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
if __name__ == "__main__":
|
| 76 |
+
demo.launch()
|
graficos_r.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import rpy2.robjects as ro
|
| 4 |
+
from rpy2.robjects import pandas2ri
|
| 5 |
+
from rpy2.robjects.conversion import localconverter
|
| 6 |
+
from rpy2.robjects.packages import importr
|
| 7 |
+
import matplotlib.pyplot as plt
|
| 8 |
+
|
| 9 |
+
# Configuraci贸n de temas y estilos en R
|
| 10 |
+
def configurar_estilos_r():
|
| 11 |
+
"""Configura temas y estilos globales para ggplot2"""
|
| 12 |
+
ro.r("""
|
| 13 |
+
library(ggplot2)
|
| 14 |
+
library(RColorBrewer)
|
| 15 |
+
|
| 16 |
+
# Tema personalizado
|
| 17 |
+
tema_personalizado <- theme_minimal() +
|
| 18 |
+
theme(
|
| 19 |
+
text = element_text(family = "sans", size = 12),
|
| 20 |
+
plot.title = element_text(size = 16, face = "bold", hjust = 0.5),
|
| 21 |
+
plot.subtitle = element_text(size = 12, hjust = 0.5),
|
| 22 |
+
axis.title = element_text(face = "bold"),
|
| 23 |
+
legend.position = "top",
|
| 24 |
+
panel.grid.major = element_line(color = "gray90"),
|
| 25 |
+
panel.grid.minor = element_blank(),
|
| 26 |
+
plot.margin = unit(c(1, 1, 1, 1), "cm")
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
# Paleta de colores
|
| 30 |
+
colores <- brewer.pal(8, "Set2")
|
| 31 |
+
""")
|
| 32 |
+
|
| 33 |
+
# Configurar estilos al importar el m贸dulo
|
| 34 |
+
configurar_estilos_r()
|
| 35 |
+
|
| 36 |
+
def generar_histograma_r(df: pd.DataFrame, columna: str, output_path: str) -> str:
|
| 37 |
+
"""
|
| 38 |
+
Genera un histograma estilizado con ggplot2
|
| 39 |
+
"""
|
| 40 |
+
try:
|
| 41 |
+
with localconverter(ro.default_converter + pandas2ri.converter):
|
| 42 |
+
r_df = ro.conversion.py2rpy(df[[columna]])
|
| 43 |
+
|
| 44 |
+
ro.r(f"""
|
| 45 |
+
library(ggplot2)
|
| 46 |
+
p <- ggplot({r_df.r_repr()}, aes(x={columna})) +
|
| 47 |
+
geom_histogram(
|
| 48 |
+
binwidth = diff(range({r_df.r_repr()}${columna}, na.rm=TRUE))/30,
|
| 49 |
+
fill = "#2c7fb8",
|
| 50 |
+
color = "#ffffff",
|
| 51 |
+
alpha = 0.8
|
| 52 |
+
) +
|
| 53 |
+
labs(
|
| 54 |
+
title = "Distribuci贸n de {columna}",
|
| 55 |
+
x = "{columna}",
|
| 56 |
+
y = "Frecuencia"
|
| 57 |
+
) +
|
| 58 |
+
tema_personalizado +
|
| 59 |
+
scale_fill_brewer(palette = "Set2")
|
| 60 |
+
|
| 61 |
+
ggsave(
|
| 62 |
+
filename = "{output_path}",
|
| 63 |
+
plot = p,
|
| 64 |
+
width = 10,
|
| 65 |
+
height = 6,
|
| 66 |
+
dpi = 300
|
| 67 |
+
)
|
| 68 |
+
""")
|
| 69 |
+
|
| 70 |
+
return output_path if os.path.exists(output_path) else None
|
| 71 |
+
|
| 72 |
+
except Exception as e:
|
| 73 |
+
print(f"Error al generar histograma: {e}")
|
| 74 |
+
return None
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def generar_barras_r(df: pd.DataFrame, columna: str, output_path: str) -> str:
|
| 78 |
+
"""
|
| 79 |
+
Genera gr谩fico de barras para variables categ贸ricas
|
| 80 |
+
"""
|
| 81 |
+
try:
|
| 82 |
+
with localconverter(ro.default_converter + pandas2ri.converter):
|
| 83 |
+
r_df = ro.conversion.py2rpy(df[[columna]])
|
| 84 |
+
|
| 85 |
+
ro.r(f"""
|
| 86 |
+
library(ggplot2)
|
| 87 |
+
library(dplyr)
|
| 88 |
+
|
| 89 |
+
top_data <- {r_df.r_repr()} %>%
|
| 90 |
+
group_by({columna}) %>%
|
| 91 |
+
summarise(count = n()) %>%
|
| 92 |
+
arrange(desc(count)) %>%
|
| 93 |
+
head(10)
|
| 94 |
+
|
| 95 |
+
p <- ggplot(top_data, aes(
|
| 96 |
+
x = reorder({columna}, count),
|
| 97 |
+
y = count,
|
| 98 |
+
fill = {columna}
|
| 99 |
+
)) +
|
| 100 |
+
geom_bar(stat = "identity") +
|
| 101 |
+
coord_flip() +
|
| 102 |
+
labs(
|
| 103 |
+
title = "Top 10 categor铆as en {columna}",
|
| 104 |
+
x = "",
|
| 105 |
+
y = "Conteo"
|
| 106 |
+
) +
|
| 107 |
+
tema_personalizado +
|
| 108 |
+
scale_fill_brewer(palette = "Set2") +
|
| 109 |
+
theme(legend.position = "none")
|
| 110 |
+
|
| 111 |
+
ggsave(
|
| 112 |
+
filename = "{output_path}",
|
| 113 |
+
plot = p,
|
| 114 |
+
width = 10,
|
| 115 |
+
height = 6,
|
| 116 |
+
dpi = 300
|
| 117 |
+
)
|
| 118 |
+
""")
|
| 119 |
+
|
| 120 |
+
return output_path if os.path.exists(output_path) else None
|
| 121 |
+
|
| 122 |
+
except Exception as e:
|
| 123 |
+
print(f"Error al generar gr谩fico de barras: {e}")
|
| 124 |
+
return None
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def generar_boxplot_r(df: pd.DataFrame, columna: str, grupo: str, output_path: str) -> str:
|
| 128 |
+
"""
|
| 129 |
+
Genera boxplot comparativo por grupos
|
| 130 |
+
"""
|
| 131 |
+
try:
|
| 132 |
+
with localconverter(ro.default_converter + pandas2ri.converter):
|
| 133 |
+
r_df = ro.conversion.py2rpy(df[[columna, grupo]])
|
| 134 |
+
|
| 135 |
+
ro.r(f"""
|
| 136 |
+
p <- ggplot({r_df.r_repr()}, aes(
|
| 137 |
+
x = {grupo},
|
| 138 |
+
y = {columna},
|
| 139 |
+
fill = {grupo}
|
| 140 |
+
)) +
|
| 141 |
+
geom_boxplot(
|
| 142 |
+
alpha = 0.7,
|
| 143 |
+
outlier.color = "#e34a33"
|
| 144 |
+
) +
|
| 145 |
+
labs(
|
| 146 |
+
title = "Distribuci贸n de {columna} por {grupo}",
|
| 147 |
+
x = "{grupo}",
|
| 148 |
+
y = "{columna}"
|
| 149 |
+
) +
|
| 150 |
+
tema_personalizado +
|
| 151 |
+
scale_fill_brewer(palette = "Set2") +
|
| 152 |
+
theme(axis.text.x = element_text(angle = 45, hjust = 1))
|
| 153 |
+
|
| 154 |
+
ggsave(
|
| 155 |
+
filename = "{output_path}",
|
| 156 |
+
plot = p,
|
| 157 |
+
width = 10,
|
| 158 |
+
height = 6,
|
| 159 |
+
dpi = 300
|
| 160 |
+
)
|
| 161 |
+
""")
|
| 162 |
+
|
| 163 |
+
return output_path if os.path.exists(output_path) else None
|
| 164 |
+
|
| 165 |
+
except Exception as e:
|
| 166 |
+
print(f"Error al generar boxplot: {e}")
|
| 167 |
+
return None
|
load_dataframe.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import pandas as pd
|
| 3 |
+
from typing import Tuple, Optional, Dict, List
|
| 4 |
+
import datetime
|
| 5 |
+
|
| 6 |
+
def cargar_dataframe(file_path: str) -> Tuple[Optional[pd.DataFrame], Optional[str]]:
|
| 7 |
+
"""
|
| 8 |
+
Carga un DataFrame desde m煤ltiples formatos con validaci贸n robusta
|
| 9 |
+
"""
|
| 10 |
+
if not os.path.exists(file_path):
|
| 11 |
+
return None, f"Error: El archivo {file_path} no existe"
|
| 12 |
+
|
| 13 |
+
try:
|
| 14 |
+
ext = os.path.splitext(file_path)[1].lower()
|
| 15 |
+
|
| 16 |
+
if ext == '.csv':
|
| 17 |
+
df = pd.read_csv(file_path, encoding='utf-8', na_values=['', 'NA', 'NULL', 'NaN'])
|
| 18 |
+
elif ext in ('.xls', '.xlsx'):
|
| 19 |
+
df = pd.read_excel(file_path, na_values=['', 'NA', 'NULL', 'NaN'])
|
| 20 |
+
elif ext == '.parquet':
|
| 21 |
+
df = pd.read_parquet(file_path)
|
| 22 |
+
elif ext == '.json':
|
| 23 |
+
df = pd.read_json(file_path)
|
| 24 |
+
else:
|
| 25 |
+
return None, f"Error: Formato {ext} no soportado"
|
| 26 |
+
|
| 27 |
+
# Convertir strings a categor铆as para optimizaci贸n
|
| 28 |
+
for col in df.select_dtypes(include=['object']):
|
| 29 |
+
if len(df[col].unique()) / len(df[col]) < 0.5:
|
| 30 |
+
df[col] = df[col].astype('category')
|
| 31 |
+
|
| 32 |
+
return df, None
|
| 33 |
+
|
| 34 |
+
except Exception as e:
|
| 35 |
+
return None, f"Error al cargar {file_path}: {str(e)}"
|
| 36 |
+
|
| 37 |
+
def analizar_nulos_por_columna(df: pd.DataFrame) -> List[Dict]:
|
| 38 |
+
"""
|
| 39 |
+
Analiza valores nulos por columna con m茅tricas detalladas
|
| 40 |
+
|
| 41 |
+
Returns:
|
| 42 |
+
Lista de diccionarios con:
|
| 43 |
+
- nombre_columna
|
| 44 |
+
- tipo_dato
|
| 45 |
+
- total_nulos
|
| 46 |
+
- porcentaje_nulos
|
| 47 |
+
- no_nulos
|
| 48 |
+
- porcentaje_no_nulos
|
| 49 |
+
- ejemplo_valores (3 primeros no nulos)
|
| 50 |
+
"""
|
| 51 |
+
analisis = []
|
| 52 |
+
total_filas = len(df)
|
| 53 |
+
|
| 54 |
+
for columna in df.columns:
|
| 55 |
+
nulos = df[columna].isnull().sum()
|
| 56 |
+
no_nulos = total_filas - nulos
|
| 57 |
+
porcentaje_nulos = (nulos / total_filas) * 100 if total_filas > 0 else 0
|
| 58 |
+
valores_unicos = df[columna].nunique()
|
| 59 |
+
|
| 60 |
+
# Ejemplos de valores (excluyendo nulos)
|
| 61 |
+
ejemplos = []
|
| 62 |
+
if no_nulos > 0:
|
| 63 |
+
ejemplos = df[columna].dropna().head(3).tolist()
|
| 64 |
+
# Convertir a string si es necesario para serializaci贸n
|
| 65 |
+
ejemplos = [str(x) for x in ejemplos]
|
| 66 |
+
|
| 67 |
+
analisis.append({
|
| 68 |
+
'nombre_columna': columna,
|
| 69 |
+
'tipo_dato': str(df[columna].dtype),
|
| 70 |
+
'total_nulos': nulos,
|
| 71 |
+
'porcentaje_nulos': round(porcentaje_nulos, 2),
|
| 72 |
+
'no_nulos': no_nulos,
|
| 73 |
+
'porcentaje_no_nulos': round(100 - porcentaje_nulos, 2),
|
| 74 |
+
'valores_unicos': valores_unicos,
|
| 75 |
+
'ejemplo_valores': ejemplos
|
| 76 |
+
})
|
| 77 |
+
|
| 78 |
+
# Ordenar por porcentaje de nulos descendente
|
| 79 |
+
return sorted(analisis, key=lambda x: x['porcentaje_nulos'], reverse=True)
|
| 80 |
+
|
| 81 |
+
def get_file_metadata(file_path: str) -> Dict:
|
| 82 |
+
"""
|
| 83 |
+
Obtiene metadatos detallados del archivo
|
| 84 |
+
"""
|
| 85 |
+
stats = os.stat(file_path)
|
| 86 |
+
return {
|
| 87 |
+
'nombre': os.path.basename(file_path),
|
| 88 |
+
'ruta': os.path.abspath(file_path),
|
| 89 |
+
'tama帽o_bytes': stats.st_size,
|
| 90 |
+
'tama帽o_mb': round(stats.st_size / (1024 * 1024), 2),
|
| 91 |
+
'fecha_modificacion': datetime.datetime.fromtimestamp(stats.st_mtime),
|
| 92 |
+
'extension': os.path.splitext(file_path)[1].lower(),
|
| 93 |
+
'encoding': 'utf-8' # Se puede detectar con chardet si es necesario
|
| 94 |
+
}
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio
|
| 2 |
+
numpy
|
| 3 |
+
pandas
|
| 4 |
+
matplotlib
|
| 5 |
+
seaborn
|
| 6 |
+
rpy2
|
| 7 |
+
ydata-profiling
|