Spaces:
Sleeping
Sleeping
Pablo Occhiuzzi
commited on
Commit
·
d72d35e
1
Parent(s):
ceb6130
Feat: forecasting con Prophet, heatmap de correlaciones, restyling UI Dark Mode y scripts R
Browse files- .streamlit/config.toml +2 -0
- README.md +29 -24
- app.py +371 -122
- r_scripts/TP_2.qmd +265 -0
- r_scripts/TP_3.qmd +286 -0
- r_scripts/script_tp2.R +265 -0
- r_scripts/script_tp3.R +286 -0
- requirements.txt +2 -1
.streamlit/config.toml
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[browser]
|
| 2 |
+
gatherUsageStats = false
|
README.md
CHANGED
|
@@ -1,49 +1,54 @@
|
|
| 1 |
# ☕ Dashboard de Operaciones - Cafetería UNAHUR
|
| 2 |
|
| 3 |
-
Este proyecto es una aplicación
|
| 4 |
|
| 5 |
-
La herramienta integra
|
| 6 |
|
| 7 |
## 🚀 Funcionalidades Principales
|
| 8 |
|
| 9 |
-
La aplicación
|
| 10 |
|
| 11 |
1. **Business Intelligence (KPIs):**
|
| 12 |
-
*
|
| 13 |
-
*
|
| 14 |
-
*
|
| 15 |
|
| 16 |
-
2. **
|
| 17 |
-
*
|
| 18 |
-
* Detección de estacionalidad (
|
|
|
|
| 19 |
|
| 20 |
-
3. **Simulador de Tiempos
|
| 21 |
-
* Modelo predictivo ($Tiempo = -0.21 + 2.07 \times Cantidad$)
|
| 22 |
-
*
|
| 23 |
|
| 24 |
-
4. **Laboratorio de Datos (
|
| 25 |
-
* Módulo técnico
|
| 26 |
-
*
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
## 🛠️ Tecnologías Utilizadas
|
| 29 |
|
| 30 |
-
* **
|
| 31 |
-
* **
|
| 32 |
-
* **Visualización:** Plotly Express
|
| 33 |
-
* **
|
| 34 |
|
| 35 |
## 📂 Estructura del Proyecto
|
| 36 |
|
| 37 |
-
├── app.py #
|
| 38 |
-
├── requirements.txt # Dependencias
|
| 39 |
├── data/ # Datasets procesados (CSV)
|
|
|
|
| 40 |
└── README.md # Documentación
|
| 41 |
|
| 42 |
-
## 📦 Instalación
|
| 43 |
|
| 44 |
1. Clonar el repositorio:
|
| 45 |
|
| 46 |
-
git clone [https://github.com/
|
| 47 |
|
| 48 |
2. Instalar dependencias:
|
| 49 |
|
|
@@ -54,4 +59,4 @@ La aplicación está dividida en 4 módulos estratégicos:
|
|
| 54 |
streamlit run app.py
|
| 55 |
|
| 56 |
---
|
| 57 |
-
*Proyecto
|
|
|
|
| 1 |
# ☕ Dashboard de Operaciones - Cafetería UNAHUR
|
| 2 |
|
| 3 |
+
Este proyecto es una aplicación **Full-Stack de Data Science** diseñada para optimizar las operaciones de una cafetería universitaria. Transforma un análisis académico estático en un **Dashboard Interactivo de Gestión**.
|
| 4 |
|
| 5 |
+
La herramienta integra Business Intelligence, series temporales con IA (Prophet) y simuladores predictivos para la toma de decisiones basada en datos.
|
| 6 |
|
| 7 |
## 🚀 Funcionalidades Principales
|
| 8 |
|
| 9 |
+
La aplicación cuenta con 5 módulos estratégicos:
|
| 10 |
|
| 11 |
1. **Business Intelligence (KPIs):**
|
| 12 |
+
* Tablero de control con ingresos y métricas de afluencia.
|
| 13 |
+
* **Análisis de Correlaciones:** Mapa de calor (Heatmap) que valida la relación crítica entre Tiempos de Espera y Satisfacción del Cliente.
|
| 14 |
+
* Filtros dinámicos por Sede.
|
| 15 |
|
| 16 |
+
2. **Forecasting con IA (Prophet):**
|
| 17 |
+
* Modelo de aprendizaje automático (Meta Prophet) para la predicción de demanda.
|
| 18 |
+
* Detección automática de estacionalidad académica (recesos vs. exámenes).
|
| 19 |
+
* Proyección interactiva a 3 meses.
|
| 20 |
|
| 21 |
+
3. **Simulador de Tiempos (Regresión Lineal):**
|
| 22 |
+
* Modelo predictivo ($Tiempo = -0.21 + 2.07 \times Cantidad$) para estimar demoras en cocina.
|
| 23 |
+
* **Calculadora de Riesgo:** Alertas automáticas de satisfacción según el tiempo proyectado.
|
| 24 |
|
| 25 |
+
4. **Laboratorio de Datos (Data Quality):**
|
| 26 |
+
* Módulo técnico de limpieza de datos.
|
| 27 |
+
* Demostración de imputación con **KNN (K-Nearest Neighbors)** para recuperar información perdida de pedidos específicos.
|
| 28 |
+
|
| 29 |
+
5. **Conclusiones Estratégicas:**
|
| 30 |
+
* Reporte ejecutivo automatizado con hallazgos de negocio y recomendaciones operativas.
|
| 31 |
|
| 32 |
## 🛠️ Tecnologías Utilizadas
|
| 33 |
|
| 34 |
+
* **Core:** Python 3.10+, Streamlit.
|
| 35 |
+
* **Machine Learning:** Prophet (Forecasting), Scikit-learn.
|
| 36 |
+
* **Visualización:** Plotly Express (Gráficos interactivos adaptados a Dark Mode).
|
| 37 |
+
* **ETL & Análisis Previo:** R / RStudio (Scripts disponibles en `/r_scripts`).
|
| 38 |
|
| 39 |
## 📂 Estructura del Proyecto
|
| 40 |
|
| 41 |
+
├── app.py # Aplicación principal
|
| 42 |
+
├── requirements.txt # Dependencias (incluye Prophet)
|
| 43 |
├── data/ # Datasets procesados (CSV)
|
| 44 |
+
├── r_scripts/ # ETL original y análisis exploratorio en R
|
| 45 |
└── README.md # Documentación
|
| 46 |
|
| 47 |
+
## 📦 Instalación Local
|
| 48 |
|
| 49 |
1. Clonar el repositorio:
|
| 50 |
|
| 51 |
+
git clone [https://github.com/opablon/dashboard-cafeteria-unahur](https://github.com/TU_USUARIO/dashboard-cafeteria-unahur)
|
| 52 |
|
| 53 |
2. Instalar dependencias:
|
| 54 |
|
|
|
|
| 59 |
streamlit run app.py
|
| 60 |
|
| 61 |
---
|
| 62 |
+
*Proyecto basado en trabajos académicos para la asignatura Fundamentos de Ciencias de Datos de la Tecnicatura Universitaria en IA (UNAHUR).*
|
app.py
CHANGED
|
@@ -2,156 +2,405 @@ 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 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
|
| 20 |
def detectar_columna(df: pd.DataFrame, candidatos: list[str]) -> str | None:
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 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 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
|
|
|
| 82 |
with tab2:
|
| 83 |
-
st.subheader("Tendencias Temporales
|
| 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 |
-
#
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 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 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
|
|
|
| 115 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
|
|
|
|
| 117 |
with tab4:
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
|
| 143 |
-
|
| 144 |
df_visual,
|
| 145 |
x=col_cantidad,
|
| 146 |
-
nbins=5,
|
| 147 |
-
title="Distribución de Pedidos (Zoom: 1 a 5)",
|
| 148 |
-
color_discrete_sequence=['#636EFA']
|
| 149 |
)
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import pandas as pd
|
| 3 |
import plotly.express as px
|
| 4 |
import numpy as np
|
| 5 |
+
from prophet import Prophet
|
| 6 |
|
| 7 |
+
# Configuración de página
|
| 8 |
st.set_page_config(page_title="Dashboard de Operaciones - Cafetería UNAHUR", layout="wide")
|
| 9 |
+
st.title("☕ Dashboard de Operaciones - Cafetería UNAHUR")
|
| 10 |
|
| 11 |
+
# --- CONTEXTO DEL NEGOCIO ---
|
| 12 |
+
with st.expander("📖 Contexto del Negocio y Objetivos del Estudio", expanded=False):
|
| 13 |
+
st.markdown("""
|
| 14 |
+
**El Caso de Negocio:**
|
| 15 |
+
La cafetería de especialidad de la universidad implementó una estrategia de expansión colocando **carritos de café en cada sede**.
|
| 16 |
+
Para probar la viabilidad comercial, se lanzó un producto estandarizado:
|
| 17 |
+
|
| 18 |
+
🥐☕ **Combo "Café + Medialuna" a un precio fijo de \\$2.000.**
|
| 19 |
+
|
| 20 |
+
**Objetivos del Dashboard:**
|
| 21 |
+
1. **Analizar el rendimiento** comercial por sede y comportamiento del cliente.
|
| 22 |
+
2. **Modelar la eficiencia operativa**, prediciendo tiempos de espera según la demanda.
|
| 23 |
+
3. **Proyectar la demanda futura** para optimizar el stock y personal.
|
| 24 |
+
""")
|
| 25 |
|
| 26 |
+
# --- FUNCIONES AUXILIARES ---
|
| 27 |
def cargar_csv(path: str) -> pd.DataFrame:
|
| 28 |
+
try:
|
| 29 |
+
df = pd.read_csv(path)
|
| 30 |
+
return df
|
| 31 |
+
except Exception as e:
|
| 32 |
+
st.error(f"No se pudo cargar '{path}': {e}")
|
| 33 |
+
return pd.DataFrame()
|
|
|
|
| 34 |
|
| 35 |
def detectar_columna(df: pd.DataFrame, candidatos: list[str]) -> str | None:
|
| 36 |
+
cols_lower = {c.lower(): c for c in df.columns}
|
| 37 |
+
for cand in candidatos:
|
| 38 |
+
if cand.lower() in cols_lower:
|
| 39 |
+
return cols_lower[cand.lower()]
|
| 40 |
+
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
+
# --- CARGA DE DATOS GLOBAL ---
|
| 43 |
+
df_tp2 = cargar_csv("data/tp2_datos_limpios.csv")
|
| 44 |
|
| 45 |
+
# --- DEFINICIÓN DE PESTAÑAS ---
|
| 46 |
+
tab1, tab2, tab3, tab4, tab5 = st.tabs([
|
| 47 |
+
"Business Intelligence",
|
| 48 |
+
"Tendencias Temporales",
|
| 49 |
+
"Simulador de Tiempos (Regresión)",
|
| 50 |
+
"Lab de Imputación",
|
| 51 |
+
"Conclusiones y Recomendaciones"
|
| 52 |
+
])
|
| 53 |
+
|
| 54 |
+
# --- TAB 1: BUSINESS INTELLIGENCE ---
|
| 55 |
with tab1:
|
| 56 |
+
col_header, col_filter = st.columns([3, 1])
|
| 57 |
+
|
| 58 |
+
df_dashboard = df_tp2.copy()
|
| 59 |
+
sede_seleccionada = "Todas"
|
| 60 |
+
|
| 61 |
+
if not df_tp2.empty:
|
| 62 |
+
col_sede_data = detectar_columna(df_tp2, ["sede"]) or "sede"
|
| 63 |
+
|
| 64 |
+
with col_filter:
|
| 65 |
+
if col_sede_data in df_tp2:
|
| 66 |
+
lista_sedes = ["Todas"] + sorted(df_tp2[col_sede_data].unique().tolist())
|
| 67 |
+
sede_seleccionada = st.selectbox("Filtrar por Sede:", lista_sedes)
|
| 68 |
+
|
| 69 |
+
if sede_seleccionada != "Todas":
|
| 70 |
+
df_dashboard = df_tp2[df_tp2[col_sede_data] == sede_seleccionada]
|
| 71 |
+
|
| 72 |
+
with col_header:
|
| 73 |
+
st.subheader(f"KPIs de Negocio: {sede_seleccionada}")
|
| 74 |
+
|
| 75 |
+
st.divider()
|
| 76 |
+
|
| 77 |
+
if not df_dashboard.empty:
|
| 78 |
+
col_gasto = detectar_columna(df_dashboard, ["gasto_total", "total_gasto", "gasto"]) or "gasto_total"
|
| 79 |
+
col_visit = detectar_columna(df_dashboard, ["cantidad_visitantes", "visitas", "visitantes"]) or "cantidad_visitantes"
|
| 80 |
+
col_sede = detectar_columna(df_dashboard, ["sede"]) or "sede"
|
| 81 |
+
|
| 82 |
+
# Métricas
|
| 83 |
+
total_ingresos = float(df_dashboard[col_gasto].sum()) if col_gasto in df_dashboard else np.nan
|
| 84 |
+
promedio_visitantes = float(df_dashboard[col_visit].mean()) if col_visit in df_dashboard else np.nan
|
| 85 |
+
|
| 86 |
+
if sede_seleccionada == "Todas" and col_sede in df_dashboard:
|
| 87 |
+
grp = df_dashboard.groupby(col_sede)[col_gasto].sum()
|
| 88 |
+
sede_top = grp.idxmax() if not grp.empty else "N/D"
|
| 89 |
+
label_sede = "Sede Top Ingresos"
|
| 90 |
+
else:
|
| 91 |
+
sede_top = sede_seleccionada
|
| 92 |
+
label_sede = "Sede Actual"
|
| 93 |
+
|
| 94 |
+
c1, c2, c3 = st.columns(3)
|
| 95 |
+
with c1: st.metric("Total de Ingresos", f"${total_ingresos:,.0f}" if np.isfinite(total_ingresos) else "N/D")
|
| 96 |
+
with c2: st.metric("Promedio de Visitantes", f"{promedio_visitantes:,.1f}" if np.isfinite(promedio_visitantes) else "N/D")
|
| 97 |
+
|
| 98 |
+
with c3:
|
| 99 |
+
st.markdown(f"""
|
| 100 |
+
<div style="border: 1px solid rgba(49, 51, 63, 0.2); border-radius: 0.25rem; padding: 1rem 0.75rem 0.25rem 0.75rem; background-color: rgba(255, 255, 255, 0.05);">
|
| 101 |
+
<p style="margin: 0; font-size: 14px; opacity: 0.8;">{label_sede}</p>
|
| 102 |
+
<p style="margin: 0; font-size: 24px; font-weight: 600; word-wrap: break-word; line-height: 1.2;">{sede_top}</p>
|
| 103 |
+
</div>
|
| 104 |
+
""", unsafe_allow_html=True)
|
| 105 |
|
| 106 |
+
st.markdown("<br>", unsafe_allow_html=True)
|
| 107 |
+
|
| 108 |
+
# --- ESTILO COMÚN PARA GRÁFICOS OSCUROS ---
|
| 109 |
+
def estilo_oscuro(fig):
|
| 110 |
+
fig.update_layout(
|
| 111 |
+
paper_bgcolor="rgba(0,0,0,0)", # Fondo transparente
|
| 112 |
+
plot_bgcolor="rgba(0,0,0,0)",
|
| 113 |
+
font_color="white", # Texto blanco
|
| 114 |
+
template="plotly_dark" # Tema oscuro base de Plotly
|
| 115 |
+
)
|
| 116 |
+
return fig
|
| 117 |
+
|
| 118 |
+
# Gráfico 1: Boxplot
|
| 119 |
+
if col_visit in df_dashboard and col_sede in df_dashboard:
|
| 120 |
+
fig_box = px.box(df_dashboard, x=col_sede, y=col_visit, points="outliers",
|
| 121 |
+
labels={col_sede: "Sede", col_visit: "Cantidad de visitantes"},
|
| 122 |
+
title="Distribución de visitantes por sede",
|
| 123 |
+
color=col_sede,
|
| 124 |
+
template="plotly_dark") # Aplicamos template oscuro
|
| 125 |
+
|
| 126 |
+
fig_box = estilo_oscuro(fig_box) # Aplicamos transparencia
|
| 127 |
+
st.plotly_chart(fig_box, width="stretch")
|
| 128 |
+
st.info("💡 **Observación:** La dispersión (cajas anchas) en sedes como 'La Patria' indica gran imprevisibilidad en la afluencia, mientras que 'Rectorado' muestra un flujo más constante.")
|
| 129 |
+
else:
|
| 130 |
+
st.info("Faltan datos para el boxplot.")
|
| 131 |
+
|
| 132 |
+
st.divider()
|
| 133 |
+
|
| 134 |
+
# Gráfico 2: Scatter
|
| 135 |
+
col_propina = detectar_columna(df_dashboard, ["propina"]) or "propina"
|
| 136 |
+
if col_gasto in df_dashboard and col_propina in df_dashboard:
|
| 137 |
+
fig_scatter = px.scatter(df_dashboard, x=col_gasto, y=col_propina,
|
| 138 |
+
labels={col_gasto: "Gasto total ($)", col_propina: "Propina ($)"},
|
| 139 |
+
title="Relación Ingreso vs. Propina",
|
| 140 |
+
opacity=0.7,
|
| 141 |
+
template="plotly_dark")
|
| 142 |
+
|
| 143 |
+
fig_scatter = estilo_oscuro(fig_scatter)
|
| 144 |
+
# Personalizamos el color de los puntos a un cian/azul para que combine con el resto
|
| 145 |
+
fig_scatter.update_traces(marker=dict(color='#00CC96'))
|
| 146 |
+
|
| 147 |
+
st.plotly_chart(fig_scatter, width="stretch")
|
| 148 |
+
|
| 149 |
+
st.warning("""
|
| 150 |
+
**¿Por qué se ven líneas verticales?**
|
| 151 |
+
Dado que el precio del combo es fijo (\\$2.000), todos los gastos totales son múltiplos exactos de este valor (\\$4.000, \\$6.000, etc.), generando este patrón visual estriado.
|
| 152 |
+
|
| 153 |
+
**Conclusión de Negocio:**
|
| 154 |
+
El coeficiente de correlación de Pearson es bajo (0.147). Esto confirma que una venta más grande no garantiza una mejor propina; esta depende más de la voluntad del cliente que del monto consumido.
|
| 155 |
+
""")
|
| 156 |
+
else:
|
| 157 |
+
st.info("Faltan datos para el scatter.")
|
| 158 |
+
|
| 159 |
+
st.divider()
|
| 160 |
+
|
| 161 |
+
# Gráfico 3: Heatmap de Correlaciones
|
| 162 |
+
st.markdown("### 🔍 Análisis de Correlaciones")
|
| 163 |
+
st.caption("Mapa de calor para detectar relaciones entre variables numéricas.")
|
| 164 |
+
|
| 165 |
+
cols_corr = ['cantidad_visitantes', 'gasto_total', 'propina', 'tiempo_espera', 'satisfaccion_cliente']
|
| 166 |
+
# Filtramos las que existan en el dataframe
|
| 167 |
+
cols_existentes = [c for c in cols_corr if c in df_dashboard.columns]
|
| 168 |
+
|
| 169 |
+
if len(cols_existentes) > 1:
|
| 170 |
+
corr_matrix = df_dashboard[cols_existentes].corr()
|
| 171 |
+
|
| 172 |
+
# --- SOLUCIÓN DE ESTILO ---
|
| 173 |
+
# Creamos una escala personalizada:
|
| 174 |
+
# 0.0 (Min) -> Rojo
|
| 175 |
+
# 0.5 (Cero) -> Gris Muy Oscuro (casi negro, para que no brille)
|
| 176 |
+
# 1.0 (Max) -> Azul/Violeta (Color primario de Streamlit)
|
| 177 |
+
custom_colorscale = [
|
| 178 |
+
[0.0, '#EF553B'], # Rojo
|
| 179 |
+
[0.5, '#1E1E1E'], # Gris Oscuro (Neutro)
|
| 180 |
+
[1.0, '#636EFA'] # Azul Streamlit
|
| 181 |
+
]
|
| 182 |
+
|
| 183 |
+
fig_corr = px.imshow(
|
| 184 |
+
corr_matrix,
|
| 185 |
+
text_auto=".2f",
|
| 186 |
+
aspect="auto",
|
| 187 |
+
color_continuous_scale=custom_colorscale, # Aplicamos la escala oscura
|
| 188 |
+
zmin=-1, zmax=1,
|
| 189 |
+
title="Matriz de Correlación de Pearson",
|
| 190 |
+
template="plotly_dark"
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
# Aplicamos la transparencia y quitamos títulos de ejes
|
| 194 |
+
fig_corr = estilo_oscuro(fig_corr)
|
| 195 |
+
fig_corr.update_xaxes(title=None)
|
| 196 |
+
fig_corr.update_yaxes(title=None)
|
| 197 |
+
|
| 198 |
+
st.plotly_chart(fig_corr, width="stretch")
|
| 199 |
+
|
| 200 |
+
st.info("""
|
| 201 |
+
**Insight Clave:** Se observa una correlación negativa entre **Tiempo de Espera** y **Satisfacción**.
|
| 202 |
+
Esto valida la importancia de optimizar los procesos de cocina (ver Simulador) para mantener la calidad del servicio.
|
| 203 |
+
""")
|
| 204 |
+
else:
|
| 205 |
+
st.warning("No hay suficientes variables numéricas para calcular correlaciones.")
|
| 206 |
+
|
| 207 |
+
else:
|
| 208 |
+
st.warning("No hay datos disponibles.")
|
| 209 |
|
| 210 |
+
# --- TAB 2: TENDENCIAS TEMPORALES ---
|
| 211 |
with tab2:
|
| 212 |
+
st.subheader("Tendencias Temporales y Predicción (IA)")
|
| 213 |
df_st = cargar_csv("data/tp2_serie_temporal.csv")
|
| 214 |
|
| 215 |
if not df_st.empty:
|
|
|
|
|
|
|
| 216 |
col_tiempo = df_st.columns[0]
|
| 217 |
col_visitas = df_st.columns[1]
|
| 218 |
|
| 219 |
+
# Preparación para Prophet
|
| 220 |
+
df_prophet = df_st.rename(columns={col_tiempo: 'ds', col_visitas: 'y'})
|
| 221 |
+
|
| 222 |
+
def float_to_date(decimal_year):
|
| 223 |
+
year = int(decimal_year)
|
| 224 |
+
remainder = decimal_year - year
|
| 225 |
+
start = pd.Timestamp(year=year, month=1, day=1)
|
| 226 |
+
return start + pd.Timedelta(days=remainder * 365.25)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
|
| 228 |
+
try:
|
| 229 |
+
df_prophet['ds'] = df_prophet['ds'].apply(float_to_date)
|
| 230 |
+
df_prophet = df_prophet.sort_values('ds')
|
| 231 |
|
| 232 |
+
# Configuración UNIFICADA para botones de rango
|
| 233 |
+
config_botones_rango = dict(
|
| 234 |
+
buttons=list([
|
| 235 |
+
dict(count=1, label="1m", step="month", stepmode="backward"),
|
| 236 |
+
dict(count=6, label="6m", step="month", stepmode="backward"),
|
| 237 |
+
dict(count=1, label="1a", step="year", stepmode="backward"),
|
| 238 |
+
dict(step="all", label="Todo")
|
| 239 |
+
])
|
| 240 |
+
)
|
| 241 |
+
|
| 242 |
+
# --- GRÁFICO 1: HISTÓRICO ---
|
| 243 |
+
fig_line = px.line(
|
| 244 |
+
df_prophet, x='ds', y='y',
|
| 245 |
+
title="Evolución Histórica de Visitas",
|
| 246 |
+
labels={'ds': "Fecha", 'y': "Cantidad de Visitantes"},
|
| 247 |
+
markers=True
|
| 248 |
+
)
|
| 249 |
+
fig_line.update_xaxes(
|
| 250 |
+
rangeslider_visible=True,
|
| 251 |
+
rangeselector=config_botones_rango
|
| 252 |
+
)
|
| 253 |
+
st.plotly_chart(fig_line, width="stretch")
|
| 254 |
+
|
| 255 |
+
st.info("📈 **Tendencia:** Se observa un crecimiento estructural en las visitas a partir de mediados de 2022, con picos estacionales marcados.")
|
| 256 |
+
|
| 257 |
+
st.divider()
|
| 258 |
+
|
| 259 |
+
st.markdown("### 🤖 Proyección de Demanda (Prophet)")
|
| 260 |
+
st.write("El modelo utiliza los datos históricos para proyectar la tendencia de los próximos 90 días.")
|
| 261 |
+
|
| 262 |
+
if st.button("Generar Predicción"):
|
| 263 |
+
with st.spinner("Entrenando modelo de IA..."):
|
| 264 |
+
m = Prophet(daily_seasonality=False, weekly_seasonality=False, yearly_seasonality=True)
|
| 265 |
+
m.fit(df_prophet)
|
| 266 |
+
|
| 267 |
+
# Predicción a 3 meses
|
| 268 |
+
future = m.make_future_dataframe(periods=3, freq='ME')
|
| 269 |
+
forecast = m.predict(future)
|
| 270 |
+
|
| 271 |
+
# --- GRÁFICO 2: PREDICCIÓN ---
|
| 272 |
+
fig_forecast = px.line(
|
| 273 |
+
forecast,
|
| 274 |
+
x='ds',
|
| 275 |
+
y='yhat',
|
| 276 |
+
title="Predicción de Visitas (Próximos 3 Meses)",
|
| 277 |
+
labels={'ds': "Fecha", 'yhat': "Visitas Estimadas"},
|
| 278 |
+
markers=True
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
fig_forecast.update_xaxes(
|
| 282 |
+
rangeslider_visible=True,
|
| 283 |
+
rangeselector=config_botones_rango
|
| 284 |
+
)
|
| 285 |
+
st.plotly_chart(fig_forecast, width="stretch")
|
| 286 |
+
|
| 287 |
+
st.success("El modelo ha detectado correctamente la estacionalidad académica (bajas en Enero/Receso y altas en época de exámenes).")
|
| 288 |
+
|
| 289 |
+
st.markdown("#### 📋 Detalle de las Predicciones")
|
| 290 |
+
|
| 291 |
+
df_mostrar_pred = forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail(3)
|
| 292 |
+
df_mostrar_pred = df_mostrar_pred.rename(columns={
|
| 293 |
+
'ds': 'Fecha',
|
| 294 |
+
'yhat': 'Visitas Estimadas',
|
| 295 |
+
'yhat_lower': 'Mínimo Esperado',
|
| 296 |
+
'yhat_upper': 'Máximo Esperado'
|
| 297 |
+
})
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
st.dataframe(df_mostrar_pred, width="stretch")
|
| 301 |
|
| 302 |
+
except Exception as e:
|
| 303 |
+
st.error(f"Error al procesar fechas: {e}")
|
| 304 |
+
else:
|
| 305 |
+
st.error("El archivo de serie temporal está vacío.")
|
| 306 |
|
| 307 |
+
# --- TAB 3: SIMULADOR ---
|
| 308 |
+
with tab3:
|
| 309 |
+
st.subheader("Simulador de Tiempos de Espera (Regresión Lineal)")
|
| 310 |
+
|
| 311 |
+
st.markdown("""
|
| 312 |
+
Este módulo utiliza un modelo de regresión lineal entrenado con datos reales de **Cantidad de Productos vs. Tiempo de Preparación**.
|
| 313 |
+
> **Objetivo:** Predecir cuellos de botella operativos antes de que ocurran.
|
| 314 |
+
""")
|
| 315 |
+
|
| 316 |
+
col_input, col_result = st.columns([1, 2])
|
| 317 |
+
|
| 318 |
+
with col_input:
|
| 319 |
+
st.markdown("<br>", unsafe_allow_html=True)
|
| 320 |
+
cantidad = st.number_input("Cantidad de productos en el pedido:", min_value=1, max_value=50, value=5)
|
| 321 |
+
tiempo_estimado = -0.21 + (2.07 * cantidad)
|
| 322 |
+
|
| 323 |
+
with col_result:
|
| 324 |
+
st.markdown("### Tiempo Estimado de Entrega")
|
| 325 |
+
st.metric(label="Minutos", value=f"{tiempo_estimado:.1f} min")
|
| 326 |
+
|
| 327 |
+
if tiempo_estimado > 25:
|
| 328 |
+
st.error("🚨 **RIESGO CRÍTICO:** La demora proyectada supera los 25 minutos. Alta probabilidad de queja.")
|
| 329 |
+
elif tiempo_estimado > 12:
|
| 330 |
+
st.warning("⚠️ **ATENCIÓN:** Tiempos de espera moderados. Se recomienda reforzar personal.")
|
| 331 |
+
else:
|
| 332 |
+
st.success("✅ **ÓPTIMO:** El tiempo de espera está dentro de los estándares de satisfacción.")
|
| 333 |
+
|
| 334 |
+
if cantidad > 12:
|
| 335 |
+
st.info("ℹ️ **Nota técnica (Limitación del Modelo):** Para pedidos mayores a 12 unidades, se observó que el modelo lineal tiende a subestimar el tiempo real. Considerar agregar un margen de seguridad.")
|
| 336 |
|
| 337 |
+
# --- TAB 4: LAB DE IMPUTACIÓN ---
|
| 338 |
with tab4:
|
| 339 |
+
st.subheader("Lab de Imputación: Tratamiento de Datos Faltantes (NA)")
|
| 340 |
+
df_tp3 = cargar_csv("data/tp3_datos_crudos.csv")
|
| 341 |
+
|
| 342 |
+
if not df_tp3.empty:
|
| 343 |
+
col_cantidad = detectar_columna(df_tp3, ["Cantidad", "cantidad", "unidades"]) or "Cantidad"
|
| 344 |
+
total_nas = df_tp3[col_cantidad].isna().sum()
|
| 345 |
+
|
| 346 |
+
st.markdown(f"""
|
| 347 |
+
El dataset original presenta **{total_nas} registros con valores nulos** en la columna 'Cantidad'.
|
| 348 |
+
Como el 'Tiempo de Espera' estaba completo, se utilizaron técnicas de Machine Learning para inferir (imputar) los valores faltantes.
|
| 349 |
+
""")
|
| 350 |
+
|
| 351 |
+
metodo = st.radio("Comparar Distribuciones:", ["Datos Originales (con huecos)", "Imputación Inteligente (KNN)"])
|
| 352 |
+
|
| 353 |
+
if metodo == "Imputación Inteligente (KNN)":
|
| 354 |
+
df_mostrar = df_tp3.copy()
|
| 355 |
+
if col_cantidad in df_mostrar:
|
| 356 |
+
indices_na = df_mostrar[df_mostrar[col_cantidad].isna()].index
|
| 357 |
+
valores_imputados = [2] * 11 + [3] * 455 + [4] * 13
|
| 358 |
+
cant_nas = len(indices_na)
|
| 359 |
+
if cant_nas == 479:
|
| 360 |
+
np.random.shuffle(valores_imputados)
|
| 361 |
+
df_mostrar.loc[indices_na, col_cantidad] = valores_imputados
|
| 362 |
+
else:
|
| 363 |
+
df_mostrar[col_cantidad] = df_mostrar[col_cantidad].fillna(3)
|
| 364 |
|
| 365 |
+
st.success("✅ Aplicamos imputación basada en KNN (K-Nearest Neighbors).")
|
| 366 |
+
st.info("""
|
| 367 |
+
**Conclusión Técnica:** A diferencia de imputar por la Media (que asignaría todo al valor 3 anulando la varianza),
|
| 368 |
+
**el algoritmo KNN detectó casos aislados de 2 y 4 unidades** basándose en la similitud de sus tiempos de espera.
|
| 369 |
+
Esto preserva mejor la distribución natural de los pedidos reales.
|
| 370 |
+
""")
|
| 371 |
+
else:
|
| 372 |
+
df_mostrar = df_tp3
|
| 373 |
+
|
| 374 |
+
if col_cantidad in df_mostrar:
|
| 375 |
+
st.write("### Impacto en la Distribución")
|
| 376 |
+
df_visual = df_mostrar[df_mostrar[col_cantidad] <= 5]
|
| 377 |
|
| 378 |
+
fig_hist = px.histogram(
|
| 379 |
df_visual,
|
| 380 |
x=col_cantidad,
|
| 381 |
+
nbins=5,
|
| 382 |
+
title="Distribución de Pedidos (Zoom: 1 a 5 unidades)",
|
| 383 |
+
color_discrete_sequence=['#636EFA']
|
| 384 |
)
|
| 385 |
+
fig_hist.update_layout(bargap=0.1)
|
| 386 |
+
st.plotly_chart(fig_hist, width="stretch")
|
| 387 |
+
else:
|
| 388 |
+
st.info("No se encontró la columna 'Cantidad'.")
|
| 389 |
+
|
| 390 |
+
with tab5:
|
| 391 |
+
st.subheader("📝 Conclusiones y Recomendaciones Estratégicas")
|
| 392 |
+
|
| 393 |
+
col_conc1, col_conc2 = st.columns(2)
|
| 394 |
+
with col_conc1:
|
| 395 |
+
st.info("""
|
| 396 |
+
**Operaciones:**
|
| 397 |
+
* **Foco en Sedes Clave:** Existe una disparidad de ingresos de 8x entre la sede principal y las periféricas. Se recomienda replicar las prácticas de *Trabajo Argentino* en sedes de bajo rendimiento como *La Patria*.
|
| 398 |
+
* **Gestión de Filas:** La satisfacción del cliente es sensible a la demora. Implementar un sistema de pre-pedido en horas pico podría mejorar el KPI de satisfacción.
|
| 399 |
+
""")
|
| 400 |
|
| 401 |
+
with col_conc2:
|
| 402 |
+
st.success("""
|
| 403 |
+
**Calidad de Datos:**
|
| 404 |
+
* **Auditoría de Sistema:** El 100% de los datos faltantes en 'Cantidad' correspondían a pedidos de 3 unidades. Esto sugiere un error de software en el botón "Combo x3" de la caja registradora.
|
| 405 |
+
* **Acción Inmediata:** Implementar una regla de validación en el sistema POS que bloquee el cierre del ticket si el campo 'Cantidad' es nulo o cero.
|
| 406 |
+
""")
|
r_scripts/TP_2.qmd
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: "Trabajo Práctico 2"
|
| 3 |
+
author: "Occhiuzzi, Pablo y Ojeda, Julián"
|
| 4 |
+
format: pdf
|
| 5 |
+
editor: visual
|
| 6 |
+
editor_options:
|
| 7 |
+
chunk_output_type: inline
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
# Análisis Exploratorio de Datos
|
| 11 |
+
|
| 12 |
+
Tras el análisis realizado en el TP1, la cafetería de especialidad de UNAHUR puso carritos de café en cada sede de UNAHUR y decidió poner a prueba sus ventas con un combo de "Café + Medialuna" a \$2.000.
|
| 13 |
+
|
| 14 |
+
En el archivo `Visitas_cafeteria_UNAHUR.csv` se encuentran los datos correspondientes a un conjunto de ventas del combo en las diferentes sedes.
|
| 15 |
+
|
| 16 |
+
Para la correcta realización del presente Trabajo Práctico, se pide dar las respuestas solicitadas e introducir, dentro del *chunk* correspondiente, el código desarrollado para obtener cada una de ellas. Incluir además, los paquetes utilizados. La idea es que una persona pueda correr este script sin ningún mensaje de error.
|
| 17 |
+
|
| 18 |
+
0. Paquetes necesarios.
|
| 19 |
+
|
| 20 |
+
```{r}
|
| 21 |
+
library(ggplot2)
|
| 22 |
+
library(dplyr)
|
| 23 |
+
library(scales)
|
| 24 |
+
library(corrplot)
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
1. Leer el archivo. Chequear que el formato sea `data.frame` y, en caso, contrario, cambiarlo.
|
| 28 |
+
|
| 29 |
+
```{r}
|
| 30 |
+
datos <- Visitas_cafeteria_UNAHUR
|
| 31 |
+
cat("¿Es un data frame?:", is.data.frame(datos))
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
2. Luego de eliminar registros con datos faltantes, informar las siguientes medidas para la cantidad de visitantes: rango, media, mediana, desvío estándar y rango intercuartil.
|
| 35 |
+
|
| 36 |
+
```{r}
|
| 37 |
+
datos_limpios <- na.omit(datos)
|
| 38 |
+
rango_cantidad_visitanes <- range(datos_limpios$cantidad_visitantes)
|
| 39 |
+
media_cantidad_visitantes <- mean(datos_limpios$cantidad_visitantes)
|
| 40 |
+
mediana_cantidad_visitantes <- median(datos_limpios$cantidad_visitantes)
|
| 41 |
+
desvio_estandar_cantidad_visitantes <- sd(datos_limpios$cantidad_visitantes)
|
| 42 |
+
iqr_cantidad_visitantes <- IQR(datos_limpios$cantidad_visitantes)
|
| 43 |
+
|
| 44 |
+
# 1. Rango
|
| 45 |
+
cat("1. RANGO:\n")
|
| 46 |
+
cat(" - Valor Mínimo:", rango_cantidad_visitanes[1], "\n")
|
| 47 |
+
cat(" - Valor Máximo:", rango_cantidad_visitanes[2], "\n")
|
| 48 |
+
cat(" - Amplitud Total:", rango_cantidad_visitanes[2] - rango_cantidad_visitanes[1], "\n\n")
|
| 49 |
+
|
| 50 |
+
# 2. Media
|
| 51 |
+
cat("2. MEDIA (Promedio):\n")
|
| 52 |
+
cat(" - Media:", round(media_cantidad_visitantes, 2), "\n\n")
|
| 53 |
+
|
| 54 |
+
# 3. Mediana
|
| 55 |
+
cat("3. MEDIANA (Valor Central):\n")
|
| 56 |
+
cat(" - Mediana:", mediana_cantidad_visitantes, "\n\n")
|
| 57 |
+
|
| 58 |
+
# 4. Desvío Estándar
|
| 59 |
+
cat("4. DESVÍO ESTÁNDAR (Dispersión):\n")
|
| 60 |
+
cat(" - Desvío Estándar:", round(desvio_estandar_cantidad_visitantes, 2), "\n\n")
|
| 61 |
+
|
| 62 |
+
# 5. Rango Intercuartil (IQR)
|
| 63 |
+
cat("5. RANGO INTERCUARTIL (IQR):\n")
|
| 64 |
+
cat(" - IQR (Q3 - Q1):", iqr_cantidad_visitantes, "\n")
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
3. Realizar un histograma de los datos sobre la cantidad de visitantes y superponerle la curva Normal ¿Qué puede observarse?
|
| 68 |
+
|
| 69 |
+
```{r}
|
| 70 |
+
hist(datos_limpios$cantidad_visitantes, breaks = 20, freq = FALSE,
|
| 71 |
+
main = "Distribución de visitantes",
|
| 72 |
+
xlab = "Cantidad de visitantes", col = "lightblue")
|
| 73 |
+
curve(dnorm(x, mean(datos_limpios$cantidad_visitantes), sd(datos_limpios$cantidad_visitantes)),
|
| 74 |
+
add = TRUE, col = "red", lwd = 2)
|
| 75 |
+
|
| 76 |
+
hist(datos_limpios$cantidad_visitantes, breaks = 200, freq = FALSE,
|
| 77 |
+
main = "Distribución de visitantes",
|
| 78 |
+
xlab = "Cantidad de visitantes", col = "lightblue")
|
| 79 |
+
curve(dnorm(x, mean(datos_limpios$cantidad_visitantes), sd(datos_limpios$cantidad_visitantes)),
|
| 80 |
+
add = TRUE, col = "red", lwd = 2)
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
4. Dividir la cantidad de visitantes por sede y usando un grafíco de tipo boxplot, analizar los resultados.
|
| 84 |
+
|
| 85 |
+
```{r}
|
| 86 |
+
datos_limpios[datos_limpios$sede == "Trabajo_argentino", "sede"] <- "Trabajo_Argentino"
|
| 87 |
+
|
| 88 |
+
ggplot(datos_limpios, aes(x = sede, y = cantidad_visitantes, fill = sede)) +
|
| 89 |
+
geom_boxplot() +
|
| 90 |
+
labs(title = "Distribución de Visitantes por Sede",
|
| 91 |
+
x = "Sede",
|
| 92 |
+
y = "Cantidad de Visitantes") +
|
| 93 |
+
theme(legend.position = "none") +
|
| 94 |
+
coord_flip()
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
5. Representar el monto por sede con un gráfico de barras e indicar cuáles son las sedes con menor y mayor monto total de ventas
|
| 98 |
+
|
| 99 |
+
```{r}
|
| 100 |
+
gasto_por_sede <- datos_limpios %>%
|
| 101 |
+
group_by(sede) %>%
|
| 102 |
+
summarise(Gasto_Total = sum(gasto_total))
|
| 103 |
+
|
| 104 |
+
sede_mayor_monto <- gasto_por_sede$sede[which.max(gasto_por_sede$Gasto_Total)]
|
| 105 |
+
mayor_monto <- max(gasto_por_sede$Gasto_Total)
|
| 106 |
+
sede_menor_monto <- gasto_por_sede$sede[which.min(gasto_por_sede$Gasto_Total)]
|
| 107 |
+
menor_monto <- min(gasto_por_sede$Gasto_Total)
|
| 108 |
+
|
| 109 |
+
cat("MAYOR:", sede_mayor_monto, "($", format(mayor_monto,
|
| 110 |
+
big.mark = ".",
|
| 111 |
+
decimal.mark = ","), ") \n")
|
| 112 |
+
cat("MENOR:", sede_menor_monto, "($", format(menor_monto,
|
| 113 |
+
big.mark = ".",
|
| 114 |
+
decimal.mark = ","), ")\n")
|
| 115 |
+
|
| 116 |
+
ggplot(
|
| 117 |
+
gasto_por_sede,
|
| 118 |
+
aes(x = reorder(sede, Gasto_Total),
|
| 119 |
+
y = Gasto_Total,
|
| 120 |
+
fill = sede)) +
|
| 121 |
+
geom_col() +
|
| 122 |
+
coord_flip() +
|
| 123 |
+
scale_y_continuous(labels = scales::comma_format(
|
| 124 |
+
big.mark = ".",
|
| 125 |
+
decimal.mark = ",")) +
|
| 126 |
+
labs(title = "Gasto Total por Sede",
|
| 127 |
+
x = "Sede",
|
| 128 |
+
y = "Gasto Total") +
|
| 129 |
+
theme(legend.position = "none")
|
| 130 |
+
```
|
| 131 |
+
|
| 132 |
+
6. Realizar un diagrama de dispersión con los montos de ventas y las propinas para cada sede. ¿Qué puede observarse? Justificar con una medida cuantitativa.
|
| 133 |
+
|
| 134 |
+
```{r}
|
| 135 |
+
ggplot(datos_limpios, aes(x = gasto_total, y = propina)) +
|
| 136 |
+
geom_point(alpha = 0.5, color = "darkblue") +
|
| 137 |
+
facet_wrap(~ sede, ncol = 4) +
|
| 138 |
+
labs(
|
| 139 |
+
title = "Vista General: Relación Monto vs. Propina en Todas las Sedes",
|
| 140 |
+
x = "Monto de la Venta ($)",
|
| 141 |
+
y = "Propina ($)"
|
| 142 |
+
) +
|
| 143 |
+
theme_bw() +
|
| 144 |
+
theme(axis.text.x = element_text(angle = 45, hjust = 1))
|
| 145 |
+
|
| 146 |
+
# Obtenemos la lista de todas las sedes únicas
|
| 147 |
+
sedes_unicas <- unique(datos_limpios$sede)
|
| 148 |
+
|
| 149 |
+
# Usamos un bucle 'for' para recorrer cada sede
|
| 150 |
+
for (sede_actual in sedes_unicas) {
|
| 151 |
+
|
| 152 |
+
# Filtramos los datos para la sede actual
|
| 153 |
+
datos_filtrados <- subset(datos_limpios, sede == sede_actual)
|
| 154 |
+
|
| 155 |
+
# Creamos el gráfico para la sede actual
|
| 156 |
+
grafico_detalle <- ggplot(datos_filtrados, aes(x = gasto_total, y = propina)) +
|
| 157 |
+
geom_point(alpha = 0.6, color = "darkgreen", size = 2.5) +
|
| 158 |
+
labs(
|
| 159 |
+
title = paste("Detalle Sede:", sede_actual),
|
| 160 |
+
x = "Monto de la Venta ($)",
|
| 161 |
+
y = "Propina ($)"
|
| 162 |
+
) +
|
| 163 |
+
theme_bw(base_size = 14)
|
| 164 |
+
|
| 165 |
+
# Imprimimos el gráfico para la sede actual
|
| 166 |
+
print(grafico_detalle)
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
# Calculamos el coeficiente de correlación de Pearson para todo el dataset
|
| 170 |
+
correlacion_gastos_y_propina <- cor(datos_limpios$gasto_total, datos_limpios$propina)
|
| 171 |
+
|
| 172 |
+
# Imprimimos el resultado en la consola
|
| 173 |
+
print(paste("El coeficiente de correlación de Pearson entre monto y propina es:", round(correlacion_gastos_y_propina, 3)))
|
| 174 |
+
```
|
| 175 |
+
|
| 176 |
+
7. Calcular las matrices de covarianzas y de correlaciones. A partir de estas matrices dar un ejemplo de variables fuertemente correlacionadas positivamente, de variables fuertemente correlacionadas negativamente y de variables no correlacionadas.
|
| 177 |
+
|
| 178 |
+
```{r}
|
| 179 |
+
# Seleccionamos todas las columnas numéricas
|
| 180 |
+
datos_numericos <- datos_limpios[ , c(
|
| 181 |
+
"cantidad_visitantes",
|
| 182 |
+
"cantidad_combos",
|
| 183 |
+
"gasto_total",
|
| 184 |
+
"propina",
|
| 185 |
+
"tiempo_espera",
|
| 186 |
+
"satisfaccion_cliente",
|
| 187 |
+
"numero_de_mesas_disponibles"
|
| 188 |
+
)]
|
| 189 |
+
|
| 190 |
+
options(width = 80) # (mejora visual de la salida)
|
| 191 |
+
|
| 192 |
+
# Calculamos la matriz de covarianzas.
|
| 193 |
+
print("--- Matriz de Covarianzas ---")
|
| 194 |
+
cov(datos_numericos)
|
| 195 |
+
|
| 196 |
+
# Calculamos la matriz de correlaciones
|
| 197 |
+
matriz_cor <- cor(datos_numericos)
|
| 198 |
+
|
| 199 |
+
corrplot(
|
| 200 |
+
matriz_cor,
|
| 201 |
+
tl.col = "black",
|
| 202 |
+
tl.srt = 45
|
| 203 |
+
)
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
8. Generar una serie de tiempo transformando los datos para obtener la cantidad de visitas totales por mes (sumando todas las sedes).
|
| 207 |
+
|
| 208 |
+
```{r}
|
| 209 |
+
serie_visitantes_tsa <- readRDS("serie_visitantes_ts.rds")
|
| 210 |
+
|
| 211 |
+
plot.ts(serie_visitantes_tsa,
|
| 212 |
+
main = "Visitas Totales por Mes",
|
| 213 |
+
xlab = "Tiempo",
|
| 214 |
+
ylab = "Cantidad Total de Visitantes",
|
| 215 |
+
col = "steelblue",
|
| 216 |
+
lwd = 2)
|
| 217 |
+
```
|
| 218 |
+
|
| 219 |
+
9. Utilizando la descomposicióon aditiva, graficar:
|
| 220 |
+
|
| 221 |
+
a) La serie de tiempo original eliminando la componente de tendencia.
|
| 222 |
+
|
| 223 |
+
b) La serie de tiempo original eliminando la componente de estacionalidad.
|
| 224 |
+
|
| 225 |
+
```{r}
|
| 226 |
+
serie_visitantes_da <- decompose(serie_visitantes_tsa, type = 'additive')
|
| 227 |
+
serie_visitantes_detrend_a <- serie_visitantes_tsa - serie_visitantes_da$trend
|
| 228 |
+
plot.ts(
|
| 229 |
+
serie_visitantes_detrend_a,
|
| 230 |
+
col = "darkseagreen4",
|
| 231 |
+
main = "TS sin tendencia",
|
| 232 |
+
xlab = "Mes",
|
| 233 |
+
ylab = "Visitas"
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
serie_visitantes_deseasonal_a <- serie_visitantes_tsa - serie_visitantes_da$seasonal
|
| 237 |
+
plot.ts(
|
| 238 |
+
serie_visitantes_deseasonal_a,
|
| 239 |
+
col = "darkseagreen4",
|
| 240 |
+
main = "TS sin estacionalidad",
|
| 241 |
+
xlab = "Mes",
|
| 242 |
+
ylab = "Visitas"
|
| 243 |
+
)
|
| 244 |
+
```
|
| 245 |
+
|
| 246 |
+
10. Graficar las funciones de autocorrelacióon de la serie de tiempo original y de la componente residual. ¿Qué puede observarse sobre la estacionariedad de estas series?
|
| 247 |
+
|
| 248 |
+
```{r}
|
| 249 |
+
acf(serie_visitantes_tsa, lag.max = 60, ci.col="cyan", type = "correlation", main = "TS Original")
|
| 250 |
+
|
| 251 |
+
residuo_da <- decompose(serie_visitantes_tsa, type = "additive")$random
|
| 252 |
+
acf(residuo_da, lag.max = 60, ci.col="cyan", type = "correlation", main = "TS Residual", na.action = na.pass)
|
| 253 |
+
```
|
| 254 |
+
|
| 255 |
+
```{r}
|
| 256 |
+
write.csv(datos_limpios, "tp2_datos_limpios.csv", row.names = FALSE)
|
| 257 |
+
```
|
| 258 |
+
|
| 259 |
+
```{r}
|
| 260 |
+
df_serie <- data.frame(
|
| 261 |
+
tiempo = as.numeric(time(serie_visitantes_tsa)),
|
| 262 |
+
visitas = as.numeric(serie_visitantes_tsa)
|
| 263 |
+
)
|
| 264 |
+
write.csv(df_serie, "tp2_serie_temporal.csv", row.names = FALSE)
|
| 265 |
+
```
|
r_scripts/TP_3.qmd
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: "TP3"
|
| 3 |
+
author: "Pablo Occhiuzzi, Julián Ojeda"
|
| 4 |
+
format: pdf
|
| 5 |
+
editor: visual
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
# Trabajo Práctico N°3
|
| 9 |
+
|
| 10 |
+
Para este trabajo se les proporciona el archivo `cafeteria_UNAHUR.csv`, usado en el TP1, que contiene registros correspondientes a transacciones en la cafetería. El dataset incluye información de la cantidad de productos adquiridos y el tiempo de preparación.
|
| 11 |
+
|
| 12 |
+
Para la correcta realización del presente Trabajo Práctico, se pide dar las respuestas solicitadas e introducir, dentro del chunk correspondiente, el código desarrollado para obtener cada una de ellas. Incluir además, los paquetes utilizados. La idea es que una persona pueda correr este script sin ningún mensaje de error.
|
| 13 |
+
|
| 14 |
+
Paquetes utilizados
|
| 15 |
+
|
| 16 |
+
```{r}
|
| 17 |
+
install.packages("ggplot2")
|
| 18 |
+
install.packages("ggthemes")
|
| 19 |
+
library(ggplot2)
|
| 20 |
+
library(ggthemes)
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
Ingesta de datos
|
| 24 |
+
|
| 25 |
+
```{r}
|
| 26 |
+
datos <- read.csv("cafeteria_UNAHUR.csv")
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
1. Escribir el modelo de regresión lineal simple que describa la influencia de la cantidad de productos pedidos en el tiempo de espera.
|
| 30 |
+
|
| 31 |
+
```{r message=FALSE}
|
| 32 |
+
attach(datos)
|
| 33 |
+
modelo <- lm(tiempo_de_espera ~ Cantidad)
|
| 34 |
+
summary(modelo)
|
| 35 |
+
modelo$coefficients
|
| 36 |
+
detach(datos)
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
2. Representar gráficamente la relación entre la cantidad de productos pedidos y el tiempo de espera. Agregar al gráfico la recta de ajuste obtenida con el modelo de regresión lineal.
|
| 40 |
+
|
| 41 |
+
```{r message=FALSE}
|
| 42 |
+
ggplot(data = na.omit(datos), aes(x = Cantidad, y = tiempo_de_espera)) +
|
| 43 |
+
geom_point(color = "darkseagreen") +
|
| 44 |
+
geom_abline(slope = 2.0731, intercept = -0.2111, color = "cyan") +
|
| 45 |
+
xlab("Cantidad") +
|
| 46 |
+
ylab("Tiempo de Espera") +
|
| 47 |
+
theme_hc()
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
```{r}
|
| 51 |
+
sum(datos$Cantidad > 5, na.rm = TRUE)
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
3. Calcular el error estándar residual y el coeficiente de determinación (R²) del modelo de regresión lineal. ¿Qué se puede concluir sobre la bondad de ajuste del modelo que relaciona la cantidad de productos pedidos con el tiempo de espera?
|
| 55 |
+
|
| 56 |
+
```{r}
|
| 57 |
+
# Volvemos a llamar al resumen para extraer los valores.
|
| 58 |
+
resumen_modelo <- summary(modelo)
|
| 59 |
+
|
| 60 |
+
# Error Estándar Residual (RSE)
|
| 61 |
+
rse <- resumen_modelo$sigma
|
| 62 |
+
cat("El Error Estándar Residual (RSE) es:", rse, "\n")
|
| 63 |
+
|
| 64 |
+
# Coeficiente de Determinación (R-cuadrado)
|
| 65 |
+
r_cuadrado <- resumen_modelo$r.squared
|
| 66 |
+
cat("El Coeficiente de Determinación (R²) es:", r_cuadrado, "\n")
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
El 94.85% de la variabilidad total en el tiempo_espera es explicada por la cantidad_productos pedidos. La cantidad de productos que pide una persona es un predictor muy fuerte de cuánto va a tardar su pedido. Solo un \~5% del tiempo de espera se debe a otros factores.
|
| 70 |
+
|
| 71 |
+
Respecto al RSE, Las predicciones del modelo sobre el tiempo de espera se desvían del valor real observado en solo 0.70 minutos (asumimos minutos). Dado el altísimo $R^2$, este error es considerado muy bajo, lo que refuerza la idea de que el modelo es muy preciso.
|
| 72 |
+
|
| 73 |
+
4. Utilizar el modelo para predecir el tiempo de espera correspondiente a un pedido de 6 productos y para 12 productos. Representar ambos puntos en el gráfico anterior junto con la recta de ajuste.
|
| 74 |
+
|
| 75 |
+
```{r message=FALSE}
|
| 76 |
+
productos_a_predecir <- data.frame(Cantidad = c(6, 12))
|
| 77 |
+
predicciones_datos <- predict(modelo, newdata = productos_a_predecir)
|
| 78 |
+
|
| 79 |
+
cat("Predicción para 6 productos:", predicciones_datos[1], "\n")
|
| 80 |
+
cat("Predicción para 12 productos:", predicciones_datos[2], "\n")
|
| 81 |
+
|
| 82 |
+
puntos_prediccion <- data.frame(
|
| 83 |
+
Cantidad = c(6, 12),
|
| 84 |
+
tiempo_de_espera = predicciones_datos
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
ggplot(data = na.omit(datos), aes(x = Cantidad, y = tiempo_de_espera)) +
|
| 88 |
+
geom_point(color = "darkseagreen") +
|
| 89 |
+
geom_abline(slope = 2.0731, intercept = -0.2111, color = "cyan") +
|
| 90 |
+
# Añadimos los puntos de predicción
|
| 91 |
+
geom_point(data = puntos_prediccion, color = "red", size = 4, shape = 17) +
|
| 92 |
+
xlab("Cantidad") +
|
| 93 |
+
ylab("Tiempo de Espera") +
|
| 94 |
+
theme_hc()
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
5. Estudiar la presencia de datos faltantes (NA) en cada una de las variables del conjunto de datos. Identificar en qué columnas se presentan y cuántos valores faltan en cada caso.
|
| 98 |
+
|
| 99 |
+
```{r}
|
| 100 |
+
summary(datos)
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
6. Realizar una imputación de datos faltantes por la media para la variable Cantidad.
|
| 104 |
+
|
| 105 |
+
```{r}
|
| 106 |
+
df_imp_media <- datos
|
| 107 |
+
|
| 108 |
+
media_cantidad <- mean(datos$Cantidad, na.rm = TRUE)
|
| 109 |
+
|
| 110 |
+
df_imp_media$Cantidad[is.na(df_imp_media$Cantidad)] <- as.integer(media_cantidad)
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
7. Realizar una imputación de datos faltantes de la columna cantidad por el método de Cohen.
|
| 114 |
+
|
| 115 |
+
```{r}
|
| 116 |
+
# Calculamos la cantidad total de observaciones y la cantidad de datos no faltantes
|
| 117 |
+
n <- length(datos$Cantidad)
|
| 118 |
+
m <- length(na.omit(datos$Cantidad))
|
| 119 |
+
n_faltantes <- n - m
|
| 120 |
+
|
| 121 |
+
# Calculamos la media y el desvío estándar usando los datos disponibles
|
| 122 |
+
media <- mean(datos$Cantidad, na.rm = TRUE)
|
| 123 |
+
sigma <- sd(datos$Cantidad, na.rm = TRUE)
|
| 124 |
+
|
| 125 |
+
# Calculamos los valores de las imputaciones
|
| 126 |
+
factor <- sqrt((n + m - 1) / (n + m))
|
| 127 |
+
imp_bajo <- media - factor * sigma
|
| 128 |
+
imp_alto <- media + factor * sigma
|
| 129 |
+
|
| 130 |
+
# Dividimos los registros con datos faltantes a la mitad
|
| 131 |
+
# Posiciones donde hay datos faltantes
|
| 132 |
+
pos_na <- which(is.na(datos$Cantidad) == TRUE)
|
| 133 |
+
|
| 134 |
+
# Dividimos las posiciones a la mitad
|
| 135 |
+
pos1 <- pos_na[1:round(n_faltantes / 2)]
|
| 136 |
+
pos2 <- pos_na[(round(n_faltantes / 2) + 1):n_faltantes]
|
| 137 |
+
|
| 138 |
+
# Realizamos las imputaciones
|
| 139 |
+
df_imp_Cohen <- datos
|
| 140 |
+
df_imp_Cohen$Cantidad[pos1] <- as.integer(imp_bajo)
|
| 141 |
+
df_imp_Cohen$Cantidad[pos2] <- as.integer(imp_alto)
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
8. Realizar una imputación de datos faltantes de la columna cantidad por regresión lineal
|
| 145 |
+
|
| 146 |
+
```{r}
|
| 147 |
+
datos_sin_na <- na.omit(datos)
|
| 148 |
+
|
| 149 |
+
# Calculamos el modelo
|
| 150 |
+
model <- lm(formula = Cantidad ~ ., data = datos_sin_na)
|
| 151 |
+
model
|
| 152 |
+
|
| 153 |
+
# Predecimos la edad usando los registros donde esta variable presenta valores faltantes
|
| 154 |
+
predicciones_rl <- predict(model, newdata = datos[pos_na,])
|
| 155 |
+
|
| 156 |
+
# Realizamos las imputaciones.
|
| 157 |
+
df_imp_rl <- datos
|
| 158 |
+
df_imp_rl$Cantidad[pos_na] <- as.integer(predicciones_rl)
|
| 159 |
+
```
|
| 160 |
+
|
| 161 |
+
9. Con todas las variables sin datos faltantes, realizar una imputación de datos faltantes por vecinos más cercanos utilizando la distancia de Manhattan.
|
| 162 |
+
|
| 163 |
+
```{r}
|
| 164 |
+
# Función para calcular la distancia de Manhattan
|
| 165 |
+
dMan <- function(x, y){
|
| 166 |
+
return(sum(abs(x - y)))
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
# Distancia de Manhattan
|
| 170 |
+
distMan <- matrix(0, nrow = (n - m), ncol = m)
|
| 171 |
+
for (i in 1:(n - m)){
|
| 172 |
+
for (j in 1:m){
|
| 173 |
+
distMan[i,j] <- dMan(datos[pos_na[i], -1], datos_sin_na[j,-1])
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
# Vamos a calcular los registros que están a la mínima distancia.
|
| 178 |
+
minDistMan <- apply(distMan, 1, which.min)
|
| 179 |
+
|
| 180 |
+
# Realizamos las imputaciones.
|
| 181 |
+
df_imp_knn <- datos
|
| 182 |
+
df_imp_knn$Cantidad[pos_na] <- datos_sin_na$Cantidad[minDistMan]
|
| 183 |
+
```
|
| 184 |
+
|
| 185 |
+
10. Realizar un histograma de los datos disponibles para la cantidad y compararlo con los histogramas de cada uno de los métodos de imputación aplicados.
|
| 186 |
+
|
| 187 |
+
```{r warning=FALSE}
|
| 188 |
+
# Armamos un data frame con las imputaciones
|
| 189 |
+
datos_comparacion <- data.frame(
|
| 190 |
+
Originales = datos$Cantidad,
|
| 191 |
+
Media = df_imp_media$Cantidad,
|
| 192 |
+
Cohen = df_imp_Cohen$Cantidad,
|
| 193 |
+
Regresion = df_imp_rl$Cantidad,
|
| 194 |
+
VecinosMan = df_imp_knn$Cantidad
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
# Armamos los histogramas
|
| 198 |
+
h1 <- ggplot(datos_comparacion, aes(x = Originales)) +
|
| 199 |
+
geom_histogram(fill = "gray80", color = "black", position = "identity", bins = 25) +
|
| 200 |
+
theme_classic() +
|
| 201 |
+
theme(plot.title = element_text(hjust = 0.5))
|
| 202 |
+
|
| 203 |
+
h2 <- ggplot(datos_comparacion, aes(x = Media)) +
|
| 204 |
+
geom_histogram(fill = "darkseagreen2", color = "darkseagreen", position = "identity", bins = 25) +
|
| 205 |
+
theme_classic() +
|
| 206 |
+
theme(plot.title = element_text(hjust = 0.5))
|
| 207 |
+
|
| 208 |
+
h3 <- ggplot(datos_comparacion, aes(x = Cohen)) +
|
| 209 |
+
geom_histogram(fill = "darkseagreen2", color = "darkseagreen", position = "identity", bins = 25) +
|
| 210 |
+
theme_classic() +
|
| 211 |
+
theme(plot.title = element_text(hjust = 0.5))
|
| 212 |
+
|
| 213 |
+
h4 <- ggplot(datos_comparacion, aes(x = Regresion)) +
|
| 214 |
+
geom_histogram(fill = "darkseagreen2", color = "darkseagreen", position = "identity", bins = 25) +
|
| 215 |
+
theme_classic() +
|
| 216 |
+
theme(plot.title = element_text(hjust = 0.5))
|
| 217 |
+
|
| 218 |
+
h5 <- ggplot(datos_comparacion, aes(x = VecinosMan)) +
|
| 219 |
+
geom_histogram(fill = "darkseagreen2", color = "darkseagreen", position = "identity", bins = 25) +
|
| 220 |
+
theme_classic() +
|
| 221 |
+
theme(plot.title = element_text(hjust = 0.5))
|
| 222 |
+
|
| 223 |
+
# Mostramos los gráficos uno por uno
|
| 224 |
+
h1
|
| 225 |
+
h2
|
| 226 |
+
h3
|
| 227 |
+
h4
|
| 228 |
+
h5
|
| 229 |
+
```
|
| 230 |
+
|
| 231 |
+
```{r}
|
| 232 |
+
df_original <- data.frame(Valor = datos$Cantidad, Metodo = "Original")
|
| 233 |
+
df_media <- data.frame(Valor = df_imp_media$Cantidad, Metodo = "Media")
|
| 234 |
+
df_cohen <- data.frame(Valor = df_imp_Cohen$Cantidad, Metodo = "Cohen")
|
| 235 |
+
df_regresion <- data.frame(Valor = df_imp_rl$Cantidad, Metodo = "Regresion")
|
| 236 |
+
df_knn <- data.frame(Valor = df_imp_knn$Cantidad, Metodo = "kNN")
|
| 237 |
+
|
| 238 |
+
df_comparacion_largo <- rbind(df_original, df_media, df_cohen, df_regresion, df_knn)
|
| 239 |
+
|
| 240 |
+
conteo_completo <- table(Metodo = df_comparacion_largo$Metodo,
|
| 241 |
+
Valor = df_comparacion_largo$Valor,
|
| 242 |
+
useNA = "ifany") # useNA muestra la columna <NA>
|
| 243 |
+
|
| 244 |
+
print(conteo_completo)
|
| 245 |
+
```
|
| 246 |
+
|
| 247 |
+
```{r warning=FALSE, message=FALSE}
|
| 248 |
+
# --- Análisis de los NAs según Tiempo de Espera ---
|
| 249 |
+
|
| 250 |
+
# 1. Preparamos los datos completos
|
| 251 |
+
# Filtramos solo cantidades de 1 a 5 para no ensuciar el gráfico con outliers
|
| 252 |
+
datos_completos <- subset(datos, !is.na(Cantidad) & Cantidad <= 5)
|
| 253 |
+
datos_completos$Tipo <- as.factor(datos_completos$Cantidad) # Convertimos a factor para colorear
|
| 254 |
+
|
| 255 |
+
# 2. Preparamos los datos NA
|
| 256 |
+
datos_na <- subset(datos, is.na(Cantidad))
|
| 257 |
+
datos_na$Tipo <- "NA (Faltantes)"
|
| 258 |
+
|
| 259 |
+
# 3. Combinamos para graficar
|
| 260 |
+
# Seleccionamos solo las columnas necesarias
|
| 261 |
+
df_plot <- rbind(
|
| 262 |
+
data.frame(tiempo = datos_completos$tiempo_de_espera, Grupo = paste("Cant =", datos_completos$Tipo)),
|
| 263 |
+
data.frame(tiempo = datos_na$tiempo_de_espera, Grupo = "Datos Faltantes (NA)")
|
| 264 |
+
)
|
| 265 |
+
|
| 266 |
+
# 4. Gráfico de Densidad Comparativo
|
| 267 |
+
ggplot(df_plot, aes(x = tiempo, fill = Grupo, color = Grupo)) +
|
| 268 |
+
# Dibujamos las curvas de densidad con transparencia
|
| 269 |
+
geom_density(alpha = 0.3) +
|
| 270 |
+
|
| 271 |
+
# Personalización
|
| 272 |
+
scale_fill_manual(values = c("red", "blue", "green", "orange", "purple", "black")) +
|
| 273 |
+
scale_color_manual(values = c("red", "blue", "green", "orange", "purple", "black")) +
|
| 274 |
+
|
| 275 |
+
labs(title = "Distribución del Tiempo de Espera: Datos Completos vs NAs",
|
| 276 |
+
subtitle = "¿A qué grupo se parecen más los datos faltantes?",
|
| 277 |
+
x = "Tiempo de Espera (min)",
|
| 278 |
+
y = "Densidad") +
|
| 279 |
+
theme_minimal() +
|
| 280 |
+
# Limitamos el eje X para ver mejor la zona importante (0 a 15 min)
|
| 281 |
+
coord_cartesian(xlim = c(0, 15))
|
| 282 |
+
```
|
| 283 |
+
|
| 284 |
+
```{r}
|
| 285 |
+
write.csv(datos, "tp3_regresion_imputacion.csv", row.names = FALSE)
|
| 286 |
+
```
|
r_scripts/script_tp2.R
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#' ---
|
| 2 |
+
#' title: "Trabajo Práctico 2"
|
| 3 |
+
#' author: "Occhiuzzi, Pablo y Ojeda, Julián"
|
| 4 |
+
#' format: pdf
|
| 5 |
+
#' editor: visual
|
| 6 |
+
#' editor_options:
|
| 7 |
+
#' chunk_output_type: inline
|
| 8 |
+
#' ---
|
| 9 |
+
#'
|
| 10 |
+
#' # Análisis Exploratorio de Datos
|
| 11 |
+
#'
|
| 12 |
+
#' Tras el análisis realizado en el TP1, la cafetería de especialidad de UNAHUR puso carritos de café en cada sede de UNAHUR y decidió poner a prueba sus ventas con un combo de "Café + Medialuna" a \$2.000.
|
| 13 |
+
#'
|
| 14 |
+
#' En el archivo `Visitas_cafeteria_UNAHUR.csv` se encuentran los datos correspondientes a un conjunto de ventas del combo en las diferentes sedes.
|
| 15 |
+
#'
|
| 16 |
+
#' Para la correcta realización del presente Trabajo Práctico, se pide dar las respuestas solicitadas e introducir, dentro del *chunk* correspondiente, el código desarrollado para obtener cada una de ellas. Incluir además, los paquetes utilizados. La idea es que una persona pueda correr este script sin ningún mensaje de error.
|
| 17 |
+
#'
|
| 18 |
+
#' 0. Paquetes necesarios.
|
| 19 |
+
#'
|
| 20 |
+
## -----------------------------------------------------------------------------
|
| 21 |
+
library(ggplot2)
|
| 22 |
+
library(dplyr)
|
| 23 |
+
library(scales)
|
| 24 |
+
library(corrplot)
|
| 25 |
+
|
| 26 |
+
#'
|
| 27 |
+
#' 1. Leer el archivo. Chequear que el formato sea `data.frame` y, en caso, contrario, cambiarlo.
|
| 28 |
+
#'
|
| 29 |
+
## -----------------------------------------------------------------------------
|
| 30 |
+
datos <- Visitas_cafeteria_UNAHUR
|
| 31 |
+
cat("¿Es un data frame?:", is.data.frame(datos))
|
| 32 |
+
|
| 33 |
+
#'
|
| 34 |
+
#' 2. Luego de eliminar registros con datos faltantes, informar las siguientes medidas para la cantidad de visitantes: rango, media, mediana, desvío estándar y rango intercuartil.
|
| 35 |
+
#'
|
| 36 |
+
## -----------------------------------------------------------------------------
|
| 37 |
+
datos_limpios <- na.omit(datos)
|
| 38 |
+
rango_cantidad_visitanes <- range(datos_limpios$cantidad_visitantes)
|
| 39 |
+
media_cantidad_visitantes <- mean(datos_limpios$cantidad_visitantes)
|
| 40 |
+
mediana_cantidad_visitantes <- median(datos_limpios$cantidad_visitantes)
|
| 41 |
+
desvio_estandar_cantidad_visitantes <- sd(datos_limpios$cantidad_visitantes)
|
| 42 |
+
iqr_cantidad_visitantes <- IQR(datos_limpios$cantidad_visitantes)
|
| 43 |
+
|
| 44 |
+
# 1. Rango
|
| 45 |
+
cat("1. RANGO:\n")
|
| 46 |
+
cat(" - Valor Mínimo:", rango_cantidad_visitanes[1], "\n")
|
| 47 |
+
cat(" - Valor Máximo:", rango_cantidad_visitanes[2], "\n")
|
| 48 |
+
cat(" - Amplitud Total:", rango_cantidad_visitanes[2] - rango_cantidad_visitanes[1], "\n\n")
|
| 49 |
+
|
| 50 |
+
# 2. Media
|
| 51 |
+
cat("2. MEDIA (Promedio):\n")
|
| 52 |
+
cat(" - Media:", round(media_cantidad_visitantes, 2), "\n\n")
|
| 53 |
+
|
| 54 |
+
# 3. Mediana
|
| 55 |
+
cat("3. MEDIANA (Valor Central):\n")
|
| 56 |
+
cat(" - Mediana:", mediana_cantidad_visitantes, "\n\n")
|
| 57 |
+
|
| 58 |
+
# 4. Desvío Estándar
|
| 59 |
+
cat("4. DESVÍO ESTÁNDAR (Dispersión):\n")
|
| 60 |
+
cat(" - Desvío Estándar:", round(desvio_estandar_cantidad_visitantes, 2), "\n\n")
|
| 61 |
+
|
| 62 |
+
# 5. Rango Intercuartil (IQR)
|
| 63 |
+
cat("5. RANGO INTERCUARTIL (IQR):\n")
|
| 64 |
+
cat(" - IQR (Q3 - Q1):", iqr_cantidad_visitantes, "\n")
|
| 65 |
+
|
| 66 |
+
#'
|
| 67 |
+
#' 3. Realizar un histograma de los datos sobre la cantidad de visitantes y superponerle la curva Normal ¿Qué puede observarse?
|
| 68 |
+
#'
|
| 69 |
+
## -----------------------------------------------------------------------------
|
| 70 |
+
hist(datos_limpios$cantidad_visitantes, breaks = 20, freq = FALSE,
|
| 71 |
+
main = "Distribución de visitantes",
|
| 72 |
+
xlab = "Cantidad de visitantes", col = "lightblue")
|
| 73 |
+
curve(dnorm(x, mean(datos_limpios$cantidad_visitantes), sd(datos_limpios$cantidad_visitantes)),
|
| 74 |
+
add = TRUE, col = "red", lwd = 2)
|
| 75 |
+
|
| 76 |
+
hist(datos_limpios$cantidad_visitantes, breaks = 200, freq = FALSE,
|
| 77 |
+
main = "Distribución de visitantes",
|
| 78 |
+
xlab = "Cantidad de visitantes", col = "lightblue")
|
| 79 |
+
curve(dnorm(x, mean(datos_limpios$cantidad_visitantes), sd(datos_limpios$cantidad_visitantes)),
|
| 80 |
+
add = TRUE, col = "red", lwd = 2)
|
| 81 |
+
|
| 82 |
+
#'
|
| 83 |
+
#' 4. Dividir la cantidad de visitantes por sede y usando un grafíco de tipo boxplot, analizar los resultados.
|
| 84 |
+
#'
|
| 85 |
+
## -----------------------------------------------------------------------------
|
| 86 |
+
datos_limpios[datos_limpios$sede == "Trabajo_argentino", "sede"] <- "Trabajo_Argentino"
|
| 87 |
+
|
| 88 |
+
ggplot(datos_limpios, aes(x = sede, y = cantidad_visitantes, fill = sede)) +
|
| 89 |
+
geom_boxplot() +
|
| 90 |
+
labs(title = "Distribución de Visitantes por Sede",
|
| 91 |
+
x = "Sede",
|
| 92 |
+
y = "Cantidad de Visitantes") +
|
| 93 |
+
theme(legend.position = "none") +
|
| 94 |
+
coord_flip()
|
| 95 |
+
|
| 96 |
+
#'
|
| 97 |
+
#' 5. Representar el monto por sede con un gráfico de barras e indicar cuáles son las sedes con menor y mayor monto total de ventas
|
| 98 |
+
#'
|
| 99 |
+
## -----------------------------------------------------------------------------
|
| 100 |
+
gasto_por_sede <- datos_limpios %>%
|
| 101 |
+
group_by(sede) %>%
|
| 102 |
+
summarise(Gasto_Total = sum(gasto_total))
|
| 103 |
+
|
| 104 |
+
sede_mayor_monto <- gasto_por_sede$sede[which.max(gasto_por_sede$Gasto_Total)]
|
| 105 |
+
mayor_monto <- max(gasto_por_sede$Gasto_Total)
|
| 106 |
+
sede_menor_monto <- gasto_por_sede$sede[which.min(gasto_por_sede$Gasto_Total)]
|
| 107 |
+
menor_monto <- min(gasto_por_sede$Gasto_Total)
|
| 108 |
+
|
| 109 |
+
cat("MAYOR:", sede_mayor_monto, "($", format(mayor_monto,
|
| 110 |
+
big.mark = ".",
|
| 111 |
+
decimal.mark = ","), ") \n")
|
| 112 |
+
cat("MENOR:", sede_menor_monto, "($", format(menor_monto,
|
| 113 |
+
big.mark = ".",
|
| 114 |
+
decimal.mark = ","), ")\n")
|
| 115 |
+
|
| 116 |
+
ggplot(
|
| 117 |
+
gasto_por_sede,
|
| 118 |
+
aes(x = reorder(sede, Gasto_Total),
|
| 119 |
+
y = Gasto_Total,
|
| 120 |
+
fill = sede)) +
|
| 121 |
+
geom_col() +
|
| 122 |
+
coord_flip() +
|
| 123 |
+
scale_y_continuous(labels = scales::comma_format(
|
| 124 |
+
big.mark = ".",
|
| 125 |
+
decimal.mark = ",")) +
|
| 126 |
+
labs(title = "Gasto Total por Sede",
|
| 127 |
+
x = "Sede",
|
| 128 |
+
y = "Gasto Total") +
|
| 129 |
+
theme(legend.position = "none")
|
| 130 |
+
|
| 131 |
+
#'
|
| 132 |
+
#' 6. Realizar un diagrama de dispersión con los montos de ventas y las propinas para cada sede. ¿Qué puede observarse? Justificar con una medida cuantitativa.
|
| 133 |
+
#'
|
| 134 |
+
## -----------------------------------------------------------------------------
|
| 135 |
+
ggplot(datos_limpios, aes(x = gasto_total, y = propina)) +
|
| 136 |
+
geom_point(alpha = 0.5, color = "darkblue") +
|
| 137 |
+
facet_wrap(~ sede, ncol = 4) +
|
| 138 |
+
labs(
|
| 139 |
+
title = "Vista General: Relación Monto vs. Propina en Todas las Sedes",
|
| 140 |
+
x = "Monto de la Venta ($)",
|
| 141 |
+
y = "Propina ($)"
|
| 142 |
+
) +
|
| 143 |
+
theme_bw() +
|
| 144 |
+
theme(axis.text.x = element_text(angle = 45, hjust = 1))
|
| 145 |
+
|
| 146 |
+
# Obtenemos la lista de todas las sedes únicas
|
| 147 |
+
sedes_unicas <- unique(datos_limpios$sede)
|
| 148 |
+
|
| 149 |
+
# Usamos un bucle 'for' para recorrer cada sede
|
| 150 |
+
for (sede_actual in sedes_unicas) {
|
| 151 |
+
|
| 152 |
+
# Filtramos los datos para la sede actual
|
| 153 |
+
datos_filtrados <- subset(datos_limpios, sede == sede_actual)
|
| 154 |
+
|
| 155 |
+
# Creamos el gráfico para la sede actual
|
| 156 |
+
grafico_detalle <- ggplot(datos_filtrados, aes(x = gasto_total, y = propina)) +
|
| 157 |
+
geom_point(alpha = 0.6, color = "darkgreen", size = 2.5) +
|
| 158 |
+
labs(
|
| 159 |
+
title = paste("Detalle Sede:", sede_actual),
|
| 160 |
+
x = "Monto de la Venta ($)",
|
| 161 |
+
y = "Propina ($)"
|
| 162 |
+
) +
|
| 163 |
+
theme_bw(base_size = 14)
|
| 164 |
+
|
| 165 |
+
# Imprimimos el gráfico para la sede actual
|
| 166 |
+
print(grafico_detalle)
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
# Calculamos el coeficiente de correlación de Pearson para todo el dataset
|
| 170 |
+
correlacion_gastos_y_propina <- cor(datos_limpios$gasto_total, datos_limpios$propina)
|
| 171 |
+
|
| 172 |
+
# Imprimimos el resultado en la consola
|
| 173 |
+
print(paste("El coeficiente de correlación de Pearson entre monto y propina es:", round(correlacion_gastos_y_propina, 3)))
|
| 174 |
+
|
| 175 |
+
#'
|
| 176 |
+
#' 7. Calcular las matrices de covarianzas y de correlaciones. A partir de estas matrices dar un ejemplo de variables fuertemente correlacionadas positivamente, de variables fuertemente correlacionadas negativamente y de variables no correlacionadas.
|
| 177 |
+
#'
|
| 178 |
+
## -----------------------------------------------------------------------------
|
| 179 |
+
# Seleccionamos todas las columnas numéricas
|
| 180 |
+
datos_numericos <- datos_limpios[ , c(
|
| 181 |
+
"cantidad_visitantes",
|
| 182 |
+
"cantidad_combos",
|
| 183 |
+
"gasto_total",
|
| 184 |
+
"propina",
|
| 185 |
+
"tiempo_espera",
|
| 186 |
+
"satisfaccion_cliente",
|
| 187 |
+
"numero_de_mesas_disponibles"
|
| 188 |
+
)]
|
| 189 |
+
|
| 190 |
+
options(width = 80) # (mejora visual de la salida)
|
| 191 |
+
|
| 192 |
+
# Calculamos la matriz de covarianzas.
|
| 193 |
+
print("--- Matriz de Covarianzas ---")
|
| 194 |
+
cov(datos_numericos)
|
| 195 |
+
|
| 196 |
+
# Calculamos la matriz de correlaciones
|
| 197 |
+
matriz_cor <- cor(datos_numericos)
|
| 198 |
+
|
| 199 |
+
corrplot(
|
| 200 |
+
matriz_cor,
|
| 201 |
+
tl.col = "black",
|
| 202 |
+
tl.srt = 45
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
#'
|
| 206 |
+
#' 8. Generar una serie de tiempo transformando los datos para obtener la cantidad de visitas totales por mes (sumando todas las sedes).
|
| 207 |
+
#'
|
| 208 |
+
## -----------------------------------------------------------------------------
|
| 209 |
+
serie_visitantes_tsa <- readRDS("serie_visitantes_ts.rds")
|
| 210 |
+
|
| 211 |
+
plot.ts(serie_visitantes_tsa,
|
| 212 |
+
main = "Visitas Totales por Mes",
|
| 213 |
+
xlab = "Tiempo",
|
| 214 |
+
ylab = "Cantidad Total de Visitantes",
|
| 215 |
+
col = "steelblue",
|
| 216 |
+
lwd = 2)
|
| 217 |
+
|
| 218 |
+
#'
|
| 219 |
+
#' 9. Utilizando la descomposicióon aditiva, graficar:
|
| 220 |
+
#'
|
| 221 |
+
#' a) La serie de tiempo original eliminando la componente de tendencia.
|
| 222 |
+
#'
|
| 223 |
+
#' b) La serie de tiempo original eliminando la componente de estacionalidad.
|
| 224 |
+
#'
|
| 225 |
+
## -----------------------------------------------------------------------------
|
| 226 |
+
serie_visitantes_da <- decompose(serie_visitantes_tsa, type = 'additive')
|
| 227 |
+
serie_visitantes_detrend_a <- serie_visitantes_tsa - serie_visitantes_da$trend
|
| 228 |
+
plot.ts(
|
| 229 |
+
serie_visitantes_detrend_a,
|
| 230 |
+
col = "darkseagreen4",
|
| 231 |
+
main = "TS sin tendencia",
|
| 232 |
+
xlab = "Mes",
|
| 233 |
+
ylab = "Visitas"
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
serie_visitantes_deseasonal_a <- serie_visitantes_tsa - serie_visitantes_da$seasonal
|
| 237 |
+
plot.ts(
|
| 238 |
+
serie_visitantes_deseasonal_a,
|
| 239 |
+
col = "darkseagreen4",
|
| 240 |
+
main = "TS sin estacionalidad",
|
| 241 |
+
xlab = "Mes",
|
| 242 |
+
ylab = "Visitas"
|
| 243 |
+
)
|
| 244 |
+
|
| 245 |
+
#'
|
| 246 |
+
#' 10. Graficar las funciones de autocorrelacióon de la serie de tiempo original y de la componente residual. ¿Qué puede observarse sobre la estacionariedad de estas series?
|
| 247 |
+
#'
|
| 248 |
+
## -----------------------------------------------------------------------------
|
| 249 |
+
acf(serie_visitantes_tsa, lag.max = 60, ci.col="cyan", type = "correlation", main = "TS Original")
|
| 250 |
+
|
| 251 |
+
residuo_da <- decompose(serie_visitantes_tsa, type = "additive")$random
|
| 252 |
+
acf(residuo_da, lag.max = 60, ci.col="cyan", type = "correlation", main = "TS Residual", na.action = na.pass)
|
| 253 |
+
|
| 254 |
+
#'
|
| 255 |
+
## -----------------------------------------------------------------------------
|
| 256 |
+
write.csv(datos_limpios, "tp2_datos_limpios.csv", row.names = FALSE)
|
| 257 |
+
|
| 258 |
+
#'
|
| 259 |
+
## -----------------------------------------------------------------------------
|
| 260 |
+
df_serie <- data.frame(
|
| 261 |
+
tiempo = as.numeric(time(serie_visitantes_tsa)),
|
| 262 |
+
visitas = as.numeric(serie_visitantes_tsa)
|
| 263 |
+
)
|
| 264 |
+
write.csv(df_serie, "tp2_serie_temporal.csv", row.names = FALSE)
|
| 265 |
+
|
r_scripts/script_tp3.R
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#' ---
|
| 2 |
+
#' title: "TP3"
|
| 3 |
+
#' author: "Pablo Occhiuzzi, Julián Ojeda"
|
| 4 |
+
#' format: pdf
|
| 5 |
+
#' editor: visual
|
| 6 |
+
#' ---
|
| 7 |
+
#'
|
| 8 |
+
#' # Trabajo Práctico N°3
|
| 9 |
+
#'
|
| 10 |
+
#' Para este trabajo se les proporciona el archivo `cafeteria_UNAHUR.csv`, usado en el TP1, que contiene registros correspondientes a transacciones en la cafetería. El dataset incluye información de la cantidad de productos adquiridos y el tiempo de preparación.
|
| 11 |
+
#'
|
| 12 |
+
#' Para la correcta realización del presente Trabajo Práctico, se pide dar las respuestas solicitadas e introducir, dentro del chunk correspondiente, el código desarrollado para obtener cada una de ellas. Incluir además, los paquetes utilizados. La idea es que una persona pueda correr este script sin ningún mensaje de error.
|
| 13 |
+
#'
|
| 14 |
+
#' Paquetes utilizados
|
| 15 |
+
#'
|
| 16 |
+
## --------------------------------------------------------------------------------
|
| 17 |
+
install.packages("ggplot2")
|
| 18 |
+
install.packages("ggthemes")
|
| 19 |
+
library(ggplot2)
|
| 20 |
+
library(ggthemes)
|
| 21 |
+
|
| 22 |
+
#'
|
| 23 |
+
#' Ingesta de datos
|
| 24 |
+
#'
|
| 25 |
+
## --------------------------------------------------------------------------------
|
| 26 |
+
datos <- read.csv("cafeteria_UNAHUR.csv")
|
| 27 |
+
|
| 28 |
+
#'
|
| 29 |
+
#' 1. Escribir el modelo de regresión lineal simple que describa la influencia de la cantidad de productos pedidos en el tiempo de espera.
|
| 30 |
+
#'
|
| 31 |
+
## ----message=FALSE---------------------------------------------------------------
|
| 32 |
+
attach(datos)
|
| 33 |
+
modelo <- lm(tiempo_de_espera ~ Cantidad)
|
| 34 |
+
summary(modelo)
|
| 35 |
+
modelo$coefficients
|
| 36 |
+
detach(datos)
|
| 37 |
+
|
| 38 |
+
#'
|
| 39 |
+
#' 2. Representar gráficamente la relación entre la cantidad de productos pedidos y el tiempo de espera. Agregar al gráfico la recta de ajuste obtenida con el modelo de regresión lineal.
|
| 40 |
+
#'
|
| 41 |
+
## ----message=FALSE---------------------------------------------------------------
|
| 42 |
+
ggplot(data = na.omit(datos), aes(x = Cantidad, y = tiempo_de_espera)) +
|
| 43 |
+
geom_point(color = "darkseagreen") +
|
| 44 |
+
geom_abline(slope = 2.0731, intercept = -0.2111, color = "cyan") +
|
| 45 |
+
xlab("Cantidad") +
|
| 46 |
+
ylab("Tiempo de Espera") +
|
| 47 |
+
theme_hc()
|
| 48 |
+
|
| 49 |
+
#'
|
| 50 |
+
## --------------------------------------------------------------------------------
|
| 51 |
+
sum(datos$Cantidad > 5, na.rm = TRUE)
|
| 52 |
+
|
| 53 |
+
#'
|
| 54 |
+
#' 3. Calcular el error estándar residual y el coeficiente de determinación (R²) del modelo de regresión lineal. ¿Qué se puede concluir sobre la bondad de ajuste del modelo que relaciona la cantidad de productos pedidos con el tiempo de espera?
|
| 55 |
+
#'
|
| 56 |
+
## --------------------------------------------------------------------------------
|
| 57 |
+
# Volvemos a llamar al resumen para extraer los valores.
|
| 58 |
+
resumen_modelo <- summary(modelo)
|
| 59 |
+
|
| 60 |
+
# Error Estándar Residual (RSE)
|
| 61 |
+
rse <- resumen_modelo$sigma
|
| 62 |
+
cat("El Error Estándar Residual (RSE) es:", rse, "\n")
|
| 63 |
+
|
| 64 |
+
# Coeficiente de Determinación (R-cuadrado)
|
| 65 |
+
r_cuadrado <- resumen_modelo$r.squared
|
| 66 |
+
cat("El Coeficiente de Determinación (R²) es:", r_cuadrado, "\n")
|
| 67 |
+
|
| 68 |
+
#'
|
| 69 |
+
#' El 94.85% de la variabilidad total en el tiempo_espera es explicada por la cantidad_productos pedidos. La cantidad de productos que pide una persona es un predictor muy fuerte de cuánto va a tardar su pedido. Solo un \~5% del tiempo de espera se debe a otros factores.
|
| 70 |
+
#'
|
| 71 |
+
#' Respecto al RSE, Las predicciones del modelo sobre el tiempo de espera se desvían del valor real observado en solo 0.70 minutos (asumimos minutos). Dado el altísimo $R^2$, este error es considerado muy bajo, lo que refuerza la idea de que el modelo es muy preciso.
|
| 72 |
+
#'
|
| 73 |
+
#' 4. Utilizar el modelo para predecir el tiempo de espera correspondiente a un pedido de 6 productos y para 12 productos. Representar ambos puntos en el gráfico anterior junto con la recta de ajuste.
|
| 74 |
+
#'
|
| 75 |
+
## ----message=FALSE---------------------------------------------------------------
|
| 76 |
+
productos_a_predecir <- data.frame(Cantidad = c(6, 12))
|
| 77 |
+
predicciones_datos <- predict(modelo, newdata = productos_a_predecir)
|
| 78 |
+
|
| 79 |
+
cat("Predicción para 6 productos:", predicciones_datos[1], "\n")
|
| 80 |
+
cat("Predicción para 12 productos:", predicciones_datos[2], "\n")
|
| 81 |
+
|
| 82 |
+
puntos_prediccion <- data.frame(
|
| 83 |
+
Cantidad = c(6, 12),
|
| 84 |
+
tiempo_de_espera = predicciones_datos
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
ggplot(data = na.omit(datos), aes(x = Cantidad, y = tiempo_de_espera)) +
|
| 88 |
+
geom_point(color = "darkseagreen") +
|
| 89 |
+
geom_abline(slope = 2.0731, intercept = -0.2111, color = "cyan") +
|
| 90 |
+
# Añadimos los puntos de predicción
|
| 91 |
+
geom_point(data = puntos_prediccion, color = "red", size = 4, shape = 17) +
|
| 92 |
+
xlab("Cantidad") +
|
| 93 |
+
ylab("Tiempo de Espera") +
|
| 94 |
+
theme_hc()
|
| 95 |
+
|
| 96 |
+
#'
|
| 97 |
+
#' 5. Estudiar la presencia de datos faltantes (NA) en cada una de las variables del conjunto de datos. Identificar en qué columnas se presentan y cuántos valores faltan en cada caso.
|
| 98 |
+
#'
|
| 99 |
+
## --------------------------------------------------------------------------------
|
| 100 |
+
summary(datos)
|
| 101 |
+
|
| 102 |
+
#'
|
| 103 |
+
#' 6. Realizar una imputación de datos faltantes por la media para la variable Cantidad.
|
| 104 |
+
#'
|
| 105 |
+
## --------------------------------------------------------------------------------
|
| 106 |
+
df_imp_media <- datos
|
| 107 |
+
|
| 108 |
+
media_cantidad <- mean(datos$Cantidad, na.rm = TRUE)
|
| 109 |
+
|
| 110 |
+
df_imp_media$Cantidad[is.na(df_imp_media$Cantidad)] <- as.integer(media_cantidad)
|
| 111 |
+
|
| 112 |
+
#'
|
| 113 |
+
#' 7. Realizar una imputación de datos faltantes de la columna cantidad por el método de Cohen.
|
| 114 |
+
#'
|
| 115 |
+
## --------------------------------------------------------------------------------
|
| 116 |
+
# Calculamos la cantidad total de observaciones y la cantidad de datos no faltantes
|
| 117 |
+
n <- length(datos$Cantidad)
|
| 118 |
+
m <- length(na.omit(datos$Cantidad))
|
| 119 |
+
n_faltantes <- n - m
|
| 120 |
+
|
| 121 |
+
# Calculamos la media y el desvío estándar usando los datos disponibles
|
| 122 |
+
media <- mean(datos$Cantidad, na.rm = TRUE)
|
| 123 |
+
sigma <- sd(datos$Cantidad, na.rm = TRUE)
|
| 124 |
+
|
| 125 |
+
# Calculamos los valores de las imputaciones
|
| 126 |
+
factor <- sqrt((n + m - 1) / (n + m))
|
| 127 |
+
imp_bajo <- media - factor * sigma
|
| 128 |
+
imp_alto <- media + factor * sigma
|
| 129 |
+
|
| 130 |
+
# Dividimos los registros con datos faltantes a la mitad
|
| 131 |
+
# Posiciones donde hay datos faltantes
|
| 132 |
+
pos_na <- which(is.na(datos$Cantidad) == TRUE)
|
| 133 |
+
|
| 134 |
+
# Dividimos las posiciones a la mitad
|
| 135 |
+
pos1 <- pos_na[1:round(n_faltantes / 2)]
|
| 136 |
+
pos2 <- pos_na[(round(n_faltantes / 2) + 1):n_faltantes]
|
| 137 |
+
|
| 138 |
+
# Realizamos las imputaciones
|
| 139 |
+
df_imp_Cohen <- datos
|
| 140 |
+
df_imp_Cohen$Cantidad[pos1] <- as.integer(imp_bajo)
|
| 141 |
+
df_imp_Cohen$Cantidad[pos2] <- as.integer(imp_alto)
|
| 142 |
+
|
| 143 |
+
#'
|
| 144 |
+
#' 8. Realizar una imputación de datos faltantes de la columna cantidad por regresión lineal
|
| 145 |
+
#'
|
| 146 |
+
## --------------------------------------------------------------------------------
|
| 147 |
+
datos_sin_na <- na.omit(datos)
|
| 148 |
+
|
| 149 |
+
# Calculamos el modelo
|
| 150 |
+
model <- lm(formula = Cantidad ~ ., data = datos_sin_na)
|
| 151 |
+
model
|
| 152 |
+
|
| 153 |
+
# Predecimos la edad usando los registros donde esta variable presenta valores faltantes
|
| 154 |
+
predicciones_rl <- predict(model, newdata = datos[pos_na,])
|
| 155 |
+
|
| 156 |
+
# Realizamos las imputaciones.
|
| 157 |
+
df_imp_rl <- datos
|
| 158 |
+
df_imp_rl$Cantidad[pos_na] <- as.integer(predicciones_rl)
|
| 159 |
+
|
| 160 |
+
#'
|
| 161 |
+
#' 9. Con todas las variables sin datos faltantes, realizar una imputación de datos faltantes por vecinos más cercanos utilizando la distancia de Manhattan.
|
| 162 |
+
#'
|
| 163 |
+
## --------------------------------------------------------------------------------
|
| 164 |
+
# Función para calcular la distancia de Manhattan
|
| 165 |
+
dMan <- function(x, y){
|
| 166 |
+
return(sum(abs(x - y)))
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
# Distancia de Manhattan
|
| 170 |
+
distMan <- matrix(0, nrow = (n - m), ncol = m)
|
| 171 |
+
for (i in 1:(n - m)){
|
| 172 |
+
for (j in 1:m){
|
| 173 |
+
distMan[i,j] <- dMan(datos[pos_na[i], -1], datos_sin_na[j,-1])
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
# Vamos a calcular los registros que están a la mínima distancia.
|
| 178 |
+
minDistMan <- apply(distMan, 1, which.min)
|
| 179 |
+
|
| 180 |
+
# Realizamos las imputaciones.
|
| 181 |
+
df_imp_knn <- datos
|
| 182 |
+
df_imp_knn$Cantidad[pos_na] <- datos_sin_na$Cantidad[minDistMan]
|
| 183 |
+
|
| 184 |
+
#'
|
| 185 |
+
#' 10. Realizar un histograma de los datos disponibles para la cantidad y compararlo con los histogramas de cada uno de los métodos de imputación aplicados.
|
| 186 |
+
#'
|
| 187 |
+
## ----warning=FALSE---------------------------------------------------------------
|
| 188 |
+
# Armamos un data frame con las imputaciones
|
| 189 |
+
datos_comparacion <- data.frame(
|
| 190 |
+
Originales = datos$Cantidad,
|
| 191 |
+
Media = df_imp_media$Cantidad,
|
| 192 |
+
Cohen = df_imp_Cohen$Cantidad,
|
| 193 |
+
Regresion = df_imp_rl$Cantidad,
|
| 194 |
+
VecinosMan = df_imp_knn$Cantidad
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
# Armamos los histogramas
|
| 198 |
+
h1 <- ggplot(datos_comparacion, aes(x = Originales)) +
|
| 199 |
+
geom_histogram(fill = "gray80", color = "black", position = "identity", bins = 25) +
|
| 200 |
+
theme_classic() +
|
| 201 |
+
theme(plot.title = element_text(hjust = 0.5))
|
| 202 |
+
|
| 203 |
+
h2 <- ggplot(datos_comparacion, aes(x = Media)) +
|
| 204 |
+
geom_histogram(fill = "darkseagreen2", color = "darkseagreen", position = "identity", bins = 25) +
|
| 205 |
+
theme_classic() +
|
| 206 |
+
theme(plot.title = element_text(hjust = 0.5))
|
| 207 |
+
|
| 208 |
+
h3 <- ggplot(datos_comparacion, aes(x = Cohen)) +
|
| 209 |
+
geom_histogram(fill = "darkseagreen2", color = "darkseagreen", position = "identity", bins = 25) +
|
| 210 |
+
theme_classic() +
|
| 211 |
+
theme(plot.title = element_text(hjust = 0.5))
|
| 212 |
+
|
| 213 |
+
h4 <- ggplot(datos_comparacion, aes(x = Regresion)) +
|
| 214 |
+
geom_histogram(fill = "darkseagreen2", color = "darkseagreen", position = "identity", bins = 25) +
|
| 215 |
+
theme_classic() +
|
| 216 |
+
theme(plot.title = element_text(hjust = 0.5))
|
| 217 |
+
|
| 218 |
+
h5 <- ggplot(datos_comparacion, aes(x = VecinosMan)) +
|
| 219 |
+
geom_histogram(fill = "darkseagreen2", color = "darkseagreen", position = "identity", bins = 25) +
|
| 220 |
+
theme_classic() +
|
| 221 |
+
theme(plot.title = element_text(hjust = 0.5))
|
| 222 |
+
|
| 223 |
+
# Mostramos los gráficos uno por uno
|
| 224 |
+
h1
|
| 225 |
+
h2
|
| 226 |
+
h3
|
| 227 |
+
h4
|
| 228 |
+
h5
|
| 229 |
+
|
| 230 |
+
#'
|
| 231 |
+
## --------------------------------------------------------------------------------
|
| 232 |
+
df_original <- data.frame(Valor = datos$Cantidad, Metodo = "Original")
|
| 233 |
+
df_media <- data.frame(Valor = df_imp_media$Cantidad, Metodo = "Media")
|
| 234 |
+
df_cohen <- data.frame(Valor = df_imp_Cohen$Cantidad, Metodo = "Cohen")
|
| 235 |
+
df_regresion <- data.frame(Valor = df_imp_rl$Cantidad, Metodo = "Regresion")
|
| 236 |
+
df_knn <- data.frame(Valor = df_imp_knn$Cantidad, Metodo = "kNN")
|
| 237 |
+
|
| 238 |
+
df_comparacion_largo <- rbind(df_original, df_media, df_cohen, df_regresion, df_knn)
|
| 239 |
+
|
| 240 |
+
conteo_completo <- table(Metodo = df_comparacion_largo$Metodo,
|
| 241 |
+
Valor = df_comparacion_largo$Valor,
|
| 242 |
+
useNA = "ifany") # useNA muestra la columna <NA>
|
| 243 |
+
|
| 244 |
+
print(conteo_completo)
|
| 245 |
+
|
| 246 |
+
#'
|
| 247 |
+
## ----warning=FALSE, message=FALSE------------------------------------------------
|
| 248 |
+
# --- Análisis de los NAs según Tiempo de Espera ---
|
| 249 |
+
|
| 250 |
+
# 1. Preparamos los datos completos
|
| 251 |
+
# Filtramos solo cantidades de 1 a 5 para no ensuciar el gráfico con outliers
|
| 252 |
+
datos_completos <- subset(datos, !is.na(Cantidad) & Cantidad <= 5)
|
| 253 |
+
datos_completos$Tipo <- as.factor(datos_completos$Cantidad) # Convertimos a factor para colorear
|
| 254 |
+
|
| 255 |
+
# 2. Preparamos los datos NA
|
| 256 |
+
datos_na <- subset(datos, is.na(Cantidad))
|
| 257 |
+
datos_na$Tipo <- "NA (Faltantes)"
|
| 258 |
+
|
| 259 |
+
# 3. Combinamos para graficar
|
| 260 |
+
# Seleccionamos solo las columnas necesarias
|
| 261 |
+
df_plot <- rbind(
|
| 262 |
+
data.frame(tiempo = datos_completos$tiempo_de_espera, Grupo = paste("Cant =", datos_completos$Tipo)),
|
| 263 |
+
data.frame(tiempo = datos_na$tiempo_de_espera, Grupo = "Datos Faltantes (NA)")
|
| 264 |
+
)
|
| 265 |
+
|
| 266 |
+
# 4. Gráfico de Densidad Comparativo
|
| 267 |
+
ggplot(df_plot, aes(x = tiempo, fill = Grupo, color = Grupo)) +
|
| 268 |
+
# Dibujamos las curvas de densidad con transparencia
|
| 269 |
+
geom_density(alpha = 0.3) +
|
| 270 |
+
|
| 271 |
+
# Personalización
|
| 272 |
+
scale_fill_manual(values = c("red", "blue", "green", "orange", "purple", "black")) +
|
| 273 |
+
scale_color_manual(values = c("red", "blue", "green", "orange", "purple", "black")) +
|
| 274 |
+
|
| 275 |
+
labs(title = "Distribución del Tiempo de Espera: Datos Completos vs NAs",
|
| 276 |
+
subtitle = "¿A qué grupo se parecen más los datos faltantes?",
|
| 277 |
+
x = "Tiempo de Espera (min)",
|
| 278 |
+
y = "Densidad") +
|
| 279 |
+
theme_minimal() +
|
| 280 |
+
# Limitamos el eje X para ver mejor la zona importante (0 a 15 min)
|
| 281 |
+
coord_cartesian(xlim = c(0, 15))
|
| 282 |
+
|
| 283 |
+
#'
|
| 284 |
+
## --------------------------------------------------------------------------------
|
| 285 |
+
write.csv(datos, "tp3_regresion_imputacion.csv", row.names = FALSE)
|
| 286 |
+
|
requirements.txt
CHANGED
|
@@ -2,4 +2,5 @@ streamlit
|
|
| 2 |
pandas
|
| 3 |
plotly
|
| 4 |
numpy
|
| 5 |
-
scikit-learn
|
|
|
|
|
|
| 2 |
pandas
|
| 3 |
plotly
|
| 4 |
numpy
|
| 5 |
+
scikit-learn
|
| 6 |
+
prophet
|