Pablo Occhiuzzi commited on
Commit
ceb6130
·
1 Parent(s): afff51a

Feat: inicializar app, mover datos a /data, fixes en Tabs 2/4 y agregar README

Browse files
README.md CHANGED
@@ -1 +1,57 @@
1
- # dashboard-cafeteria-unahur
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ☕ Dashboard de Operaciones - Cafetería UNAHUR
2
+
3
+ Este proyecto es una aplicación interactiva de **Business Intelligence y Data Science** diseñada para analizar y optimizar las operaciones de la cafetería universitaria.
4
+
5
+ La herramienta integra análisis descriptivo, series temporales y modelos predictivos para ayudar a la gerencia en la toma de decisiones basada en datos.
6
+
7
+ ## 🚀 Funcionalidades Principales
8
+
9
+ La aplicación está dividida en 4 módulos estratégicos:
10
+
11
+ 1. **Business Intelligence (KPIs):**
12
+ * Análisis de ingresos totales y ticket promedio.
13
+ * Comparativa de rendimiento entre sedes (Boxplots interactivos) para detectar variabilidad operativa.
14
+ * Análisis de correlaciones: ¿Influye el tiempo de espera en la satisfacción?
15
+
16
+ 2. **Tendencias Temporales:**
17
+ * Visualización de la evolución histórica de visitas.
18
+ * Detección de estacionalidad (picos de fin de año y valles de receso).
19
+
20
+ 3. **Simulador de Tiempos de Espera (Regresión):**
21
+ * Modelo predictivo ($Tiempo = -0.21 + 2.07 \times Cantidad$) que estima la demora según el tamaño del pedido.
22
+ * Sistema de alertas para pedidos grandes (>12 unidades) donde el modelo lineal pierde precisión.
23
+
24
+ 4. **Laboratorio de Datos (Imputación):**
25
+ * Módulo técnico que demuestra técnicas de limpieza de datos.
26
+ * Comparación en tiempo real entre datos originales vs. imputación con KNN para corregir registros faltantes en pedidos de 3 unidades.
27
+
28
+ ## 🛠️ Tecnologías Utilizadas
29
+
30
+ * **Lenguaje:** Python 3.10+
31
+ * **Framework Web:** Streamlit
32
+ * **Visualización:** Plotly Express
33
+ * **Manipulación de Datos:** Pandas, NumPy
34
+
35
+ ## 📂 Estructura del Proyecto
36
+
37
+ ├── app.py # Código principal de la aplicación
38
+ ├── requirements.txt # Dependencias del proyecto
39
+ ├── data/ # Datasets procesados (CSV)
40
+ └── README.md # Documentación
41
+
42
+ ## 📦 Instalación y Uso Local
43
+
44
+ 1. Clonar el repositorio:
45
+
46
+ git clone [https://github.com/TU_USUARIO/dashboard-cafeteria-unahur.git](https://github.com/TU_USUARIO/dashboard-cafeteria-unahur.git)
47
+
48
+ 2. Instalar dependencias:
49
+
50
+ pip install -r requirements.txt
51
+
52
+ 3. Ejecutar la aplicación:
53
+
54
+ streamlit run app.py
55
+
56
+ ---
57
+ *Proyecto académico para la asignatura de Fundamentos de Ciencias de Datos - Tecnicatura Universitaria en IA (UNAHUR).*
app.py ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import plotly.express as px
4
+ import numpy as np
5
+
6
+
7
+ st.set_page_config(page_title="Dashboard de Operaciones - Cafetería UNAHUR", layout="wide")
8
+ st.title("Dashboard de Operaciones - Cafetería UNAHUR")
9
+
10
+
11
+ def cargar_csv(path: str) -> pd.DataFrame:
12
+ try:
13
+ df = pd.read_csv(path)
14
+ return df
15
+ except Exception as e:
16
+ st.error(f"No se pudo cargar '{path}': {e}")
17
+ return pd.DataFrame()
18
+
19
+
20
+ def detectar_columna(df: pd.DataFrame, candidatos: list[str]) -> str | None:
21
+ cols_lower = {c.lower(): c for c in df.columns}
22
+ for cand in candidatos:
23
+ if cand.lower() in cols_lower:
24
+ return cols_lower[cand.lower()]
25
+ return None
26
+
27
+
28
+ tab1, tab2, tab3, tab4 = st.tabs([
29
+ "Business Intelligence",
30
+ "Tendencias Temporales",
31
+ "Simulador de Tiempos (Regresión)",
32
+ "Lab de Imputación",
33
+ ])
34
+
35
+
36
+ with tab1:
37
+ st.subheader("Business Intelligence")
38
+ df_tp2 = cargar_csv("data/tp2_datos_limpios.csv")
39
+ if not df_tp2.empty:
40
+ # Detectar columnas requeridas con nombres comunes
41
+ col_gasto = detectar_columna(df_tp2, ["gasto_total", "total_gasto", "gasto"]) or "gasto_total"
42
+ col_visit = detectar_columna(df_tp2, ["cantidad_visitantes", "visitas", "visitantes"]) or "cantidad_visitantes"
43
+ col_sede = detectar_columna(df_tp2, ["sede"]) or "sede"
44
+
45
+ # Métricas
46
+ total_ingresos = float(df_tp2[col_gasto].sum()) if col_gasto in df_tp2 else np.nan
47
+ promedio_visitantes = float(df_tp2[col_visit].mean()) if col_visit in df_tp2 else np.nan
48
+ sede_mayor_ingreso = "N/D"
49
+ if col_sede in df_tp2 and col_gasto in df_tp2:
50
+ grp = df_tp2.groupby(col_sede)[col_gasto].sum()
51
+ if not grp.empty:
52
+ sede_mayor_ingreso = grp.idxmax()
53
+
54
+ c1, c2, c3 = st.columns(3)
55
+ with c1:
56
+ st.metric("Total de Ingresos", f"${total_ingresos:,.2f}" if np.isfinite(total_ingresos) else "N/D")
57
+ with c2:
58
+ st.metric("Promedio de Visitantes", f"{promedio_visitantes:,.1f}" if np.isfinite(promedio_visitantes) else "N/D")
59
+ with c3:
60
+ st.metric("Sede con Mayor Ingreso", sede_mayor_ingreso)
61
+
62
+ # Boxplot cantidad_visitantes por sede
63
+ if col_visit in df_tp2 and col_sede in df_tp2:
64
+ fig_box = px.box(df_tp2, x=col_sede, y=col_visit, points="outliers",
65
+ labels={col_sede: "Sede", col_visit: "Cantidad de visitantes"},
66
+ title="Distribución de visitantes por sede")
67
+ st.plotly_chart(fig_box, width='stretch')
68
+ else:
69
+ st.info("No se encontraron columnas adecuadas para el boxplot.")
70
+
71
+ # Scatter gasto_total vs propina
72
+ col_propina = detectar_columna(df_tp2, ["propina"]) or "propina"
73
+ if col_gasto in df_tp2 and col_propina in df_tp2:
74
+ fig_scatter = px.scatter(df_tp2, x=col_gasto, y=col_propina,
75
+ labels={col_gasto: "Gasto total", col_propina: "Propina"},
76
+ title="Relación entre gasto total y propina")
77
+ st.plotly_chart(fig_scatter, width='stretch')
78
+ else:
79
+ st.info("No se encontraron columnas adecuadas para el scatter.")
80
+
81
+
82
+ with tab2:
83
+ st.subheader("Tendencias Temporales de Visitas")
84
+ df_st = cargar_csv("data/tp2_serie_temporal.csv")
85
+
86
+ if not df_st.empty:
87
+ # ESTRATEGIA ROBUSTA: Usar posición en lugar de nombre
88
+ # Asumimos que la columna 0 es el Tiempo y la 1 son las Visitas
89
+ col_tiempo = df_st.columns[0]
90
+ col_visitas = df_st.columns[1]
91
+
92
+ # Ordenamos por tiempo para asegurar que la línea se dibuje bien
93
+ df_st = df_st.sort_values(by=col_tiempo)
94
+
95
+ fig_line = px.line(
96
+ df_st,
97
+ x=col_tiempo,
98
+ y=col_visitas,
99
+ title="Evolución de visitas en el tiempo",
100
+ labels={col_tiempo: "Tiempo / Año", col_visitas: "Cantidad de Visitas"}
101
+ )
102
+ st.plotly_chart(fig_line, width='stretch')
103
+ else:
104
+ st.error("El archivo de serie temporal está vacío o no se pudo cargar.")
105
+
106
+
107
+ with tab3:
108
+ st.subheader("Simulador de Tiempos (Regresión)")
109
+ cantidad = st.slider("Cantidad de productos", min_value=1, max_value=30, value=5)
110
+ tiempo_estimado = -0.21 + (2.07 * cantidad)
111
+
112
+ st.markdown(f"<h2 style='text-align:center'>Tiempo estimado: {tiempo_estimado:.2f} minutos</h2>", unsafe_allow_html=True)
113
+ if cantidad > 12:
114
+ st.warning("Advertencia: El modelo lineal tiende a subestimar el tiempo real para pedidos mayores a 12 productos")
115
+
116
+
117
+ with tab4:
118
+ st.subheader("Lab de Imputación")
119
+ df_tp3 = cargar_csv("data/tp3_datos_crudos.csv")
120
+ if not df_tp3.empty:
121
+ col_cantidad = detectar_columna(df_tp3, ["Cantidad", "cantidad", "unidades"]) or "Cantidad"
122
+ st.write("Se identificaron 479 valores faltantes en la columna 'Cantidad'.")
123
+
124
+ metodo = st.radio("Método", ["Original", "Imputación Inteligente (KNN)"]) # KNN simplificado según requerimiento
125
+
126
+ if metodo == "Imputación Inteligente (KNN)":
127
+ df_mostrar = df_tp3.copy()
128
+ if col_cantidad in df_mostrar:
129
+ df_mostrar[col_cantidad] = df_mostrar[col_cantidad].fillna(3)
130
+ st.write("Aplicamos imputación con valor 3, ya que el análisis de densidad demostró que los faltantes corresponden a pedidos de 3 unidades.")
131
+ else:
132
+ df_mostrar = df_tp3
133
+
134
+ if col_cantidad in df_mostrar:
135
+ st.write("Distribución de la columna 'Cantidad' (Vista ampliada)")
136
+
137
+ # FILTRO VISUAL: Creamos un dataframe temporal solo con valores <= 5
138
+ # Esto limpia el gráfico eliminando los outliers de la derecha
139
+ # Nota: usamos dropna() por seguridad para el gráfico
140
+
141
+ df_visual = df_mostrar[df_mostrar[col_cantidad] <= 5]
142
+
143
+ fig_hist = px.histogram(
144
+ df_visual,
145
+ x=col_cantidad,
146
+ nbins=5, # 5 barras para los valores 1, 2, 3, 4, 5
147
+ title="Distribución de Pedidos (Zoom: 1 a 5)",
148
+ color_discrete_sequence=['#636EFA'] # Un azul estandarizado
149
+ )
150
+
151
+ # Ajustes visuales para que se vea prolijo (espacio entre barras)
152
+ fig_hist.update_layout(bargap=0.1)
153
+
154
+ st.plotly_chart(fig_hist, width='stretch')
155
+ else:
156
+ st.info("No se encontró la columna 'Cantidad' para graficar.")
157
+
tp2_datos_limpios.csv → data/tp2_datos_limpios.csv RENAMED
File without changes
tp2_serie_temporal.csv → data/tp2_serie_temporal.csv RENAMED
File without changes
tp3_datos_crudos.csv → data/tp3_datos_crudos.csv RENAMED
File without changes
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ streamlit
2
+ pandas
3
+ plotly
4
+ numpy
5
+ scikit-learn