# =============================================================================
# EJERCICIO GUIADO: Regresión Lineal Multivariable
# Dataset: Propinas en un restaurante (tips)
# Nivel: Principiante / Intermedio
# =============================================================================
#
# CONTEXTO DEL PROBLEMA
# ─────────────────────
# En el ejercicio anterior usamos solo el total de la cuenta para predecir
# la propina. ¡Pero hay más información disponible!
#
# PREGUNTA: ¿Podemos predecir mejor la propina usando VARIAS variables?
#
# Esto es lo que hace la REGRESIÓN LINEAL MULTIVARIABLE:
# aprender cómo influye cada variable de entrada en la propina.
#
# La fórmula pasa de tener una sola X a varias:
#
# tip = b₀ + b₁×total_bill + b₂×size
#
# donde cada bᵢ (coeficiente) indica cuánto influye esa variable.
#
# Al final del ejercicio sabrás:
# · Seleccionar múltiples variables numéricas
# · Normalizar los datos para que las variables estén en la misma escala
# · Entrenar un modelo de regresión multivariable
# · Comparar su rendimiento con el modelo simple anterior
# · Interpretar los coeficientes de cada variable
# · Predecir la propina para una comanda nueva con múltiples datos
#
# VARIABLES DEL DATASET
# ─────────────────────
# total_bill → importe total de la cuenta en dólares [numérica]
# tip → propina dejada por el cliente [numérica, objetivo]
# sex → sexo del cliente que paga [categórica]
# smoker → si la mesa es de fumadores [categórica]
# day → día de la semana [categórica]
# time → Lunch / Dinner [categórica]
# size → número de personas en la mesa [numérica]
#
# En este ejercicio usaremos: total_bill y size.
#
# =============================================================================
# ─────────────────────────────────────────────────────────────────────────────
# PASO 0 — IMPORTACIONES
# ─────────────────────────────────────────────────────────────────────────────
#
# Ejecuta este bloque primero. Si alguna librería no está instalada:
# pip install seaborn scikit-learn matplotlib
import seaborn as sns
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler # ← nueva importación
from sklearn.metrics import (
r2_score,
mean_absolute_error,
mean_squared_error,
root_mean_squared_error
)
# ─────────────────────────────────────────────────────────────────────────────
# PASO 1 — CARGAR EL DATASET
# ─────────────────────────────────────────────────────────────────────────────
#
# Cargamos el mismo dataset de propinas que en el ejercicio anterior.
print("=" * 60)
print(" PASO 1: Carga del dataset")
print("=" * 60)
df = sns.load_dataset("tips")
# ─────────────────────────────────────────────────────────────────────────────
# PASO 3 — SELECCIONAR X e y
# ─────────────────────────────────────────────────────────────────────────────
#
# Ahora X tiene DOS columnas (de ahí el nombre "multivariable"):
# X = [total_bill, size]
# y = tip (lo mismo que antes)
#
# 💡 PISTA: Usa doble corchete para seleccionar varias columnas:
# X = df[["total_bill", "size"]]
#
# 🛠️ TU TURNO:
# 1. Define X con las dos variables indicadas.
# 2. Define y como la columna "tip".
# 3. Imprime X.shape e y.shape.
# ¿Qué diferencia ves en X respecto al ejercicio de regresión simple?
print("\n" + "=" * 60)
print(" PASO 3: Seleccionar X e y")
print("=" * 60)
# --- ESCRIBE TU CÓDIGO AQUÍ ---
X = df[["total_bill", "size"]] # ¿Qué variables? total_bill y size
y = df["tip"]
# ─────────────────────────────────────────────────────────────────────────────
# PASO 4 — DIVIDIR EN ENTRENAMIENTO Y TEST
# ─────────────────────────────────────────────────────────────────────────────
#
# Dividimos ANTES de normalizar para evitar "data leakage":
# si normalizamos con todos los datos, el modelo vería información
# del conjunto de test durante el entrenamiento (hace trampa).
#
# Orden correcto: dividir → normalizar con train → aplicar a test.
#
# 💡 PISTA: train_test_split(X, y, test_size=0.2, random_state=42)
#
# 🛠️ TU TURNO:
# Aplica train_test_split y guarda los cuatro conjuntos:
# X_train, X_test, y_train, y_test
# Imprime cuántas filas tiene cada parte.
print("\n" + "=" * 60)
print(" PASO 4: División train / test (80% / 20%)")
print("=" * 60)
# --- ESCRIBE TU CÓDIGO AQUÍ -
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# ─────────────────────────────────────────────────────────────────────────────
# PASO 5 — NORMALIZACIÓN (StandardScaler)
# ─────────────────────────────────────────────────────────────────────────────
#
# PROBLEMA: total_bill va de 3 a 51 $, pero size va de 1 a 6 personas.
# Son escalas muy distintas. Si no normalizamos, el modelo puede dar
# más "peso" a total_bill simplemente porque sus valores son más grandes,
# no porque sea más importante.
#
# StandardScaler convierte cada variable para que tenga:
# · media = 0
# · desviación típica = 1
#
# Fórmula: z = (x − media) / desviación_típica
#
# ⚠️ REGLA IMPORTANTE: el scaler se "aprende" (fit) SOLO con X_train.
# Luego se aplica (transform) tanto a X_train como a X_test.
# Nunca hagas fit con X_test: estarías mirando datos que no deberías ver.
#
# 💡 PISTA:
# scaler = StandardScaler()
# X_train_sc = scaler.fit_transform(X_train) # aprende Y transforma
# X_test_sc = scaler.transform(X_test) # solo transforma
#
# 🛠️ TU TURNO:
# 1. Crea el scaler y aplícalo según el ejemplo de arriba.
# 2. Imprime la media y desviación típica que aprendió el scaler.
# Pista: scaler.mean_ y scaler.scale_
# 3. Comprueba que X_train_sc tiene media ≈ 0 y std ≈ 1 por columna.
# Pista: pd.DataFrame(X_train_sc, columns=X.columns).describe().round(2)
print("\n" + "=" * 60)
print(" PASO 5: Normalización con StandardScaler")
print("=" * 60)
# --- ESCRIBE TU CÓDIGO AQUÍ ---
scaler = StandardScaler()
X_train_sc = scaler.fit_transform(X_train)
X_test_sc = scaler.transform(X_test)
# ─────────────────────────────────────────────────────────────────────────────
# PASO 6 — ENTRENAR EL MODELO
# ─────────────────────────────────────────────────────────────────────────────
#
# Entrenamos con X_train_sc (los datos normalizados), no con X_train.
#
# Con dos variables normalizadas, la fórmula aprendida será:
# tip = b₀ + b₁×total_bill_sc + b₂×size_sc
#
# Ahora los coeficientes b₁ y b₂ SÍ son directamente comparables:
# el mayor indica la variable que más influye en la propina.
#
# 💡 PISTA:
# modelo = LinearRegression()
# modelo.fit(X_train_sc, y_train)
#
# 🛠️ TU TURNO:
# 1. Crea el modelo y entrénalo con los datos normalizados.
# 2. Imprime los coeficientes junto al nombre de cada variable.
# Pista: zip(X.columns, modelo.coef_)
# 3. ¿Qué variable tiene el coeficiente más alto?
# Después de normalizar, eso sí que indica cuál importa más.
print("\n" + "=" * 60)
print(" PASO 6: Entrenamiento del modelo")
print("=" * 60)
# --- ESCRIBE TU CÓDIGO AQUÍ ---
modelo = LinearRegression()
modelo.fit(X_train_sc, y_train)
# ─────────────────────────────────────────────────────────────────────────────
# PASO 7 — EVALUAR EL MODELO
# ─────────────────────────────────────────────────────────────────────────────
#
# Calcula las predicciones sobre X_test_sc y luego las tres métricas:
# · R² → ¿qué porcentaje de la variación explica el modelo?
# · MAE → error medio en dólares
# · RMSE → igual que MAE pero penaliza más los errores grandes
#
# 💡 PISTA: El modelo simple (solo total_bill, sin normalizar) obtenía R² ≈ 0.46.
# ¿Mejora al usar dos variables normalizadas?
#
# 🛠️ TU TURNO:
# 1. Obtén y_pred con modelo.predict(X_test_sc).
# 2. Calcula r2, mae y rmse.
# 3. Imprime los resultados y compáralos con el modelo simple.
print("\n" + "=" * 60)
print(" PASO 7: Evaluación del modelo")
print("=" * 60)
# --- ESCRIBE TU CÓDIGO AQUÍ ---
y_pred = modelo.predict(X_test_sc)
r2 = r2_score(y_test, y_pred)
mae = mean_absolute_error(y_test, y_pred)
rmse = root_mean_squared_error(y_test, y_pred)
print(f" R² = {r2:.4f}")
print(f" MAE = {mae:.4f} $")
print(f" RMSE = {rmse:.4f} $")
print("\n ¿Mejoró respecto al modelo simple (R² ≈ 0.46)?")