# ── Importaciones ─────────────────────────────────────────────────────
import numpy as np # para operaciones numéricas
import matplotlib.pyplot as plt # para hacer gráficos
from sklearn.cluster import KMeans # el algoritmo K-Means
from sklearn.datasets import make_blobs # generador de datos de ejemplo
# ── PASO 1: Crear datos de ejemplo ────────────────────────────────────
#
# make_blobs() genera puntos artificiales agrupados en 'manchas' (blobs).
# Es como inventarse datos ya organizados para practicar.
#
# n_samples = cuántos puntos queremos generar en total
# centers = cuántos grupos/manchas queremos que tenga
# cluster_std = qué tan dispersos están los puntos dentro de cada grupo
# (número pequeño = puntos muy juntos, número grande = dispersos)
# random_state = semilla aleatoria (garantiza que siempre salga igual)
#
X, y_real = make_blobs(
n_samples=300, # 300 puntos en total
centers=4, # distribuidos en 4 grupos
cluster_std=0.8, # puntos bastante juntos en cada grupo
random_state=42 # para reproducibilidad
)
#
# X tiene forma (300, 2): 300 filas (puntos) y 2 columnas (coordenadas x, y)
# y_real contiene el grupo real de cada punto (solo para comparar después)
# ── PASO 2: Crear el modelo K-Means ───────────────────────────────────
#
# Aquí solo DEFINIMOS el modelo, todavía no lo entrenamos.
# Parámetros importantes:
#
# n_clusters = el K: cuántos grupos queremos encontrar
# Elegimos 4 porque sabemos que los datos tienen 4 grupos.
# En la vida real no sabremos esto y usaremos el Método del Codo.
#
# n_init = cuántas veces reinicia el algoritmo con centroides distintos.
# Por defecto prueba 10 inicializaciones y elige la mejor.
# Más intentos = más fiable, pero más lento.
#
# random_state = semilla para que el resultado sea siempre el mismo.
#
kmeans = KMeans(
n_clusters=4,
n_init=10,
random_state=42
)
# ── PASO 3: Entrenar el modelo ─────────────────────────────────────────
#
# .fit(X) ejecuta el algoritmo K-Means sobre nuestros datos X.
# Internamente hace todos los pasos descritos antes:
# coloca centroides → asigna puntos → recalcula centroides → repite.
#
kmeans.fit(X)
# ── PASO 4: Obtener los resultados ────────────────────────────────────
#
# Después del entrenamiento, el modelo guarda sus resultados en atributos:
#
# .labels_ → array con el número de cluster asignado a cada punto
# Ejemplo: [0, 2, 1, 0, 3, 1, 2, ...] (un número por punto)
#
# .cluster_centers_ → array con las coordenadas de los K centroides finales
# Tiene forma (K, n_características)
#
# .inertia_ → el valor WCSS final (qué tan compactos son los clusters)
#
etiquetas = kmeans.labels_
centroides = kmeans.cluster_centers_
inercia = kmeans.inertia_
print(f'Inercia (WCSS): {inercia:.2f}')
print(f'Centroides encontrados:')
print(centroides)
# ── PASO 5: Visualizar los resultados ─────────────────────────────────
#
# Hacemos un scatter plot (nube de puntos).
# Cada punto se colorea según el cluster al que fue asignado.
# Los centroides se marcan con una X grande en rojo.
#
plt.figure(figsize=(8, 6))
# scatter() dibuja los puntos:
# X[:, 0] = primera columna (coordenada x de todos los puntos)
# X[:, 1] = segunda columna (coordenada y de todos los puntos)
# c=etiquetas → el color de cada punto depende de su cluster
# cmap='viridis' → la paleta de colores a usar
# alpha=0.7 → transparencia (0=invisible, 1=sólido)
plt.scatter(X[:, 0], X[:, 1], c=etiquetas, cmap='viridis', s=50, alpha=0.7)
# Dibujamos los centroides con una X roja más grande:
# centroides[:, 0] = coordenadas x de todos los centroides
# centroides[:, 1] = coordenadas y de todos los centroides
# marker='X' → símbolo de X
# zorder=5 → se dibuja por encima de los puntos
plt.scatter(centroides[:, 0], centroides[:, 1],
c='red', marker='X', s=200, zorder=5, label='Centroides')
plt.title('K-Means: Resultado del clustering')
plt.xlabel('Característica 1')
plt.ylabel('Característica 2')
plt.legend()
plt.colorbar(label='Número de cluster')
plt.tight_layout()
plt.show()
Regresión ( de nuevo)
# ==============================================================================
# 1. IMPORTACIÓN DE LIBRERÍAS
# ==============================================================================
# Pandas es la librería estrella para manejar datos en formato de tabla (como un Excel).
import pandas as pd
# De la librería 'sklearn' (scikit-learn), importamos herramientas específicas:
# 'train_test_split' nos servirá para dividir nuestros datos en dos grupos (entrenamiento y examen).
from sklearn.model_selection import train_test_split
# 'StandardScaler' se usa para "normalizar" o "escalar" los datos (ponerlos todos en una misma escala).
from sklearn.preprocessing import StandardScaler
# 'LogisticRegression' es el algoritmo que creará nuestro modelo de clasificación.
from sklearn.linear_model import LogisticRegression
# Importamos las métricas para evaluar qué tan bueno es nuestro modelo al final:
from sklearn.metrics import (
accuracy_score, # Exactitud: Porcentaje total de aciertos sobre el total.
precision_score, # Precisión: De los que el modelo dijo que eran positivos, ¿cuántos lo eran de verdad?
recall_score, # Exhaustividad: De los positivos reales que había, ¿cuántos fue capaz de detectar?
f1_score, # Puntuación F1: Un equilibrio o "media" entre precisión y recall.
confusion_matrix, # Matriz de confusión: Una tabla que muestra dónde acertó y dónde se equivocó.
classification_report # Un resumen de texto que junta todas las métricas anteriores.
)
# ==============================================================================
# 2. PREPARACIÓN DE LOS DATOS INICIALES
# ==============================================================================
# Creamos dos listas de Python con nuestros datos brutos.
# 'horas_entrenamiento' es lo que queremos usar para predecir (la causa).
horas_entrenamiento = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
# 'clasificado' es el resultado (el efecto): 0 significa "No clasificado" y 1 significa "Clasificado".
clasificado = [0,0,0,0,0,0,0,1,0,0,1,1,0,1,1,1,1,1,0,1]
# Verificamos que ambas listas tengan la misma cantidad de elementos (en este caso, 20).
# Esto es vital: cada alumno/caso debe tener sus horas y su resultado correspondiente.
print("Cantidad de datos de horas:", len(horas_entrenamiento))
print("Cantidad de datos de clasificación:", len(clasificado))
# ==============================================================================
# 3. CREACIÓN DEL DATAFRAME (LA TABLA DE DATOS)
# ==============================================================================
# Juntamos las dos listas en una estructura de tabla organizada gracias a Pandas.
df = pd.DataFrame({
"horas": horas_entrenamiento,
"clasificado": clasificado
})
# Imprimimos la tabla en consola para ver visualmente cómo quedan las filas y columnas.
print("\n--- Nuestra tabla de datos (DataFrame) ---")
print(df)
# Separamos la tabla en dos partes fundamentales para el Machine Learning:
# X: Las variables "predictoras" o características (en mayúscula por convención matemática).
# Usamos doble corchete [[ ]] porque los modelos de sklearn siempre esperan una matriz de dos dimensiones para las X.
X = df[["horas"]]
# y: La variable "objetivo" o lo que queremos predecir (en minúscula por ser un solo vector).
y = df["clasificado"]
# ==============================================================================
# 4. DIVISIÓN EN ENTRENAMIENTO (TRAIN) Y PRUEBA (TEST)
# ==============================================================================
# Imagina que preparas a un alumno para un examen: no le das las preguntas del examen para estudiar.
# Por eso dividimos los datos: un grupo para estudiar (Train) y otro oculto para evaluar (Test).
# 'test_size=0.2' significa que dejamos el 20% de los datos para el examen (4 filas) y el 80% para estudiar (16 filas).
# 'random_state=0' es una "semilla" para que el reparto aleatorio sea siempre el mismo si volvemos a ejecutar el código.
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
# ==============================================================================
# 5. NORMALIZACIÓN / ESCALADO DE DATOS
# ==============================================================================
# A los algoritmos les cuesta más aprender si los números son gigantescos o están en escalas muy diferentes.
# El escalador ajusta los datos para que tengan una media de 0 y una desviación de 1 (los "centra").
escalador = StandardScaler()
# ¡OJO aquí! Calculamos la escala con los datos de entrenamiento y los transformamos.
# Es fundamental guardar el resultado de vuelta en las variables 'X_train' y 'X_test'.
X_train = escalador.fit_transform(X_train)
# Con los datos de test (el examen) SOLO transformamos, no recalculamos la escala,
# para no hacer "trampa" y mantener las reglas del grupo de entrenamiento.
X_test = escalador.transform(X_test)
# ==============================================================================
# 6. CREACIÓN Y ENTRENAMIENTO DEL MODELO
# ==============================================================================
# Llamamos al algoritmo de Regresión Logística (pese a llamarse regresión, sirve para clasificar 0s y 1s).
modelo = LogisticRegression()
# Entrenamos el modelo con la función '.fit()'.
# Aquí es donde el modelo "estudia" la relación entre las horas (X_train) y si clasificó o no (y_train).
modelo.fit(X_train, y_train)
print("\n¡El modelo ha sido entrenado con éxito!")
# ==============================================================================
# 7. PREDICCIÓN Y EVALUACIÓN (EL EXAMEN)
# ==============================================================================
# Le pedimos al modelo que intente adivinar el resultado de las horas del grupo de examen (X_test).
# El modelo NO conoce las respuestas reales (y_test), solo ve las horas.
y_pred = modelo.predict(X_test)
# Ahora comparamos las respuestas reales (y_test) con las que el modelo predijo (y_pred).
exactitud = accuracy_score(y_test, y_pred)
# Mostramos el resultado final por pantalla.
# Si da 1.0 significa 100% de aciertos en el examen; si da 0.5 significa un 50%, etc.
print("\n--- Evaluación del Modelo ---")
print(f"La exactitud (Accuracy) del modelo en el examen es de: {exactitud}")
Los 5 pasos de la regresión
# Los 5 pasos de la regresión
En un mundo obsesionado con avanzar cada vez más rápido, *Los 5 pasos de la regresión* propone una idea aparentemente contradictoria: para progresar de verdad, primero debemos aprender a regresar. Regresar a los fundamentos, a los patrones que gobiernan nuestra mente y a las preguntas esenciales que dan sentido a nuestra existencia. Este libro combina programación en Python, desarrollo personal y reflexión espiritual en una propuesta única destinada a quienes desean comprender tanto las máquinas como a sí mismos.
El autor parte de un concepto tomado de la ciencia de datos: la regresión. En programación, una regresión busca descubrir relaciones ocultas entre variables para poder comprender y predecir el comportamiento de un sistema. En la vida ocurre algo similar. Nuestros pensamientos, emociones y decisiones no aparecen de manera aislada; responden a patrones que pueden ser observados, analizados y transformados.
El primer paso, **Observar los datos**, enseña a contemplar la realidad sin prejuicios. Igual que un programador examina un conjunto de datos antes de construir un modelo, el lector aprende a identificar hábitos, creencias y circunstancias que influyen en su vida cotidiana. La observación rigurosa se convierte en una forma de autoconocimiento.
El segundo paso, **Limpiar el ruido**, muestra cómo distinguir la información relevante de las distracciones. En Python, los datos incompletos o erróneos pueden arruinar cualquier análisis. Del mismo modo, los miedos heredados, las expectativas ajenas y las narrativas falsas distorsionan nuestra percepción del mundo. El proceso de depuración se transforma aquí en una práctica espiritual.
El tercer paso, **Encontrar las variables ocultas**, invita a explorar aquello que no siempre es visible. Muchas veces buscamos respuestas en el exterior cuando las causas reales se encuentran en dimensiones más profundas de nuestra personalidad. El libro conecta conceptos estadísticos con enseñanzas filosóficas y tradiciones espirituales para mostrar cómo descubrir esos factores invisibles.
El cuarto paso, **Entrenar el modelo**, explica que ningún cambio ocurre de forma instantánea. Tanto en programación como en la vida, el aprendizaje requiere repetición, paciencia y corrección constante. Los ejercicios prácticos en Python sirven como metáfora de la disciplina personal necesaria para construir una identidad más consciente y resiliente.
El quinto paso, **Predecir con humildad**, aborda la gran lección de toda regresión: ningún modelo es perfecto. Podemos mejorar nuestras estimaciones, comprender tendencias y tomar mejores decisiones, pero siempre existirá incertidumbre. Esta aceptación de los límites se convierte en una fuente de serenidad y sabiduría.
A lo largo de la obra, el lector encontrará ejemplos accesibles de programación en Python, explicados para principiantes, junto con ejercicios de reflexión y prácticas de atención consciente. El objetivo no es formar únicamente programadores ni únicamente buscadores espirituales, sino personas capaces de pensar con claridad y actuar con propósito.
Lejos de presentar la tecnología y la espiritualidad como mundos opuestos, el libro demuestra que ambos comparten una misma aspiración: comprender patrones ocultos. Allí donde el programador busca relaciones entre variables, el ser humano busca significado. Ambas búsquedas pueden enriquecerse mutuamente.
*Los 5 pasos de la regresión* es una invitación a mirar la propia vida como un conjunto de datos en constante evolución. No promete fórmulas mágicas ni respuestas definitivas, sino una metodología para aprender, corregir errores, adaptarse al cambio y encontrar sentido en medio de la complejidad. Un manual para quienes desean programar mejor, vivir mejor y comprender mejor el misterio de ser humanos.
Normalización test train
import pandas as pd
from sklearn.preprocessing import StandardScaler
datos = {
'superficie': [75, 95, 60, 120, 85, 50, 110, 70, 140, 65, 90, 100],
'habitaciones': [3, 3, 2, 4, 3, 1, 4, 2, 5, 2, 3, 4],
'banios': [1, 2, 1, 2, 2, 1, 3, 1, 3, 1, 2, 2],
'antiguedad': [20, 10, 35, 5, 15, 40, 8, 25, 2, 30, 12, 7],
'dist_centro': [5.2, 3.8, 8.1, 2.1, 4.5, 9.0, 3.2, 6.7, 1.5, 7.4, 4.0, 2.8],
}
df = pd.DataFrame(datos)
print('─── ANTES ───')
print(df)
print(f'Medias: {df.mean().round(2).to_dict()}')
print(f'Desv.tip.: {df.std().round(2).to_dict()}')
# ── Aplicar Standard Scaler ──────────────────────────────────────
scaler = StandardScaler()
X_std = scaler.fit_transform(df)
df_std = pd.DataFrame(X_std, columns=df.columns)
print('─── DESPUÉS (Standard Scaler) ───')
print(df_std.round(3))
print(f'Medias tras escalar: {df_std.mean().round(6).to_dict()}')
print(f'Desv.tip. tras escalar: {df_std.std().round(3).to_dict()}')
# Medias ≈ 0.0 | Desviaciones típicas ≈ 1.0
# ── Ver los parámetros aprendidos ────────────────────────────────
print('Medias aprendidas (mean_):', scaler.mean_.round(3))
print('Desv. típ. aprendidas (scale_):', scaler.scale_.round(3))
# ── Uso CORRECTO con train/test ──────────────────────────────────
from sklearn.model_selection import train_test_split
X_train, X_test = train_test_split(df, test_size=0.2, random_state=42)
scaler2 = StandardScaler()
X_train_std = scaler2.fit_transform(X_train) # aprende media/std de TRAIN
X_test_std = scaler2.transform(X_test) # aplica misma media/std a TEST
Normalización
import pandas as pd
import numpy as np
from sklearn.preprocessing import MinMaxScaler, StandardScaler
# ── Dataset ─────────────────────────────────────────────────────
datos = {
'superficie': [75, 95, 60, 120, 85, 50, 110, 70, 140, 65, 90, 100],
'habitaciones': [3, 3, 2, 4, 3, 1, 4, 2, 5, 2, 3, 4],
'banios': [1, 2, 1, 2, 2, 1, 3, 1, 3, 1, 2, 2],
'antiguedad': [20, 10, 35, 5, 15, 40, 8, 25, 2, 30, 12, 7],
'dist_centro': [5.2, 3.8, 8.1, 2.1, 4.5, 9.0, 3.2, 6.7, 1.5, 7.4, 4.0, 2.8],
}
df = pd.DataFrame(datos)
cols = df.columns.tolist()
# ── Aplicar ambos métodos ────────────────────────────────────────
mm_scaler = MinMaxScaler()
std_scaler = StandardScaler()
df_mm = pd.DataFrame(mm_scaler.fit_transform(df), columns=cols)
df_std = pd.DataFrame(std_scaler.fit_transform(df), columns=cols)
# ── Comparativa visual en consola ────────────────────────────────
separador = '─' * 65
print(separador)
print('ORIGINAL (valores sin normalizar)')
print(separador)
print(df.to_string(index=False))
print(f' Min: {df.min().to_dict()}')
print(f' Max: {df.max().to_dict()}')
print()
print(separador)
print('MIN-MAX SCALER (rango [0, 1])')
print(separador)
print(df_mm.round(3).to_string(index=False))
print(f' Min: {df_mm.min().round(3).to_dict()}')
print(f' Max: {df_mm.max().round(3).to_dict()}')
print()
print(separador)
print('STANDARD SCALER (media≈0, desv.típ.≈1)')
print(separador)
print(df_std.round(3).to_string(index=False))
print(f' Medias: {df_std.mean().round(4).to_dict()}')
print(f' Desv.tip.: {df_std.std().round(3).to_dict()}')
# ── Tabla resumen por variable ───────────────────────────────────
print()
print(separador)
print('RESUMEN POR VARIABLE')
print(separador)
for col in cols:
print(f'{col:>14} | original: [{df[col].min():.1f}, {df[col].max():.1f}] | min-max: [{df_mm[col].min():.3f}, {df_mm[col].max():.3f}] | z-score: [{df_std[col].min():.3f}, {df_std[col].max():.3f}]')
Regresiones con train y test
# ─────────────────────────────────────────────────────────────────────────────
# REGRESIÓN LOGÍSTICA — Predicción de aprobados/suspensos
# ─────────────────────────────────────────────────────────────────────────────
# La regresión LOGÍSTICA se usa cuando queremos predecir una CATEGORÍA (0 o 1),
# no un número continuo. Aquí queremos saber: ¿aprobará o suspenderá?
# ─────────────────────────────────────────────────────────────────────────────
import pandas as pd
from sklearn.linear_model import LogisticRegression # el modelo de clasificación
from sklearn.model_selection import train_test_split # para dividir los datos
# ── 1. DATASET ────────────────────────────────────────────────────────────────
# Cada lista representa una columna con los datos de 15 estudiantes.
# La columna 'aprobo' es la que queremos predecir: 1 = aprobó, 0 = suspendió.
datos = {
'horas_estudio': [1.5, 3.0, 2.0, 4.5, 1.0, 3.5, 2.5, 0.5, 4.0, 2.0, 3.0, 1.5, 5.0, 2.5, 1.0],
'asistencia': [72, 90, 65, 95, 60, 88, 78, 55, 92, 70, 85, 68, 98, 75, 62],
'nota_parcial': [4.5, 7.2, 5.0, 8.8, 3.9, 7.5, 6.1, 3.0, 8.2, 5.5, 7.0, 4.8, 9.5, 6.3, 4.0],
'horas_suenio': [6, 8, 7, 8, 5, 7, 6, 5, 8, 7, 8, 6, 9, 7, 6],
'aprobo': [0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0]
}
# Convertimos el diccionario en un DataFrame (tabla) de pandas.
# Es la estructura más cómoda para trabajar con datos en Python.
df = pd.DataFrame(datos)
# ── 2. SEPARAR X e y ──────────────────────────────────────────────────────────
# X = variables predictoras (las "pistas" que el modelo usará para aprender)
# y = variable objetivo (lo que queremos predecir)
#
# Nota: X lleva VARIAS columnas → usamos dobles corchetes df[[ ]]
# y lleva UNA sola columna → usamos un corchete df[ ]
X = df[['horas_estudio', 'asistencia', 'nota_parcial', 'horas_suenio']]
y = df['aprobo']
# ── 3. DIVIDIR EN ENTRENAMIENTO Y TEST ────────────────────────────────────────
# No podemos evaluar el modelo con los mismos datos con los que aprendió
# (sería como darle las respuestas del examen antes del examen).
# Guardamos un 20% de los datos para comprobar después si el modelo acierta.
#
# X_train, y_train → datos con los que el modelo va a aprender (80%)
# X_test, y_test → datos que guardamos para evaluar al final (20%)
# random_state=42 → fija el azar para que la división sea siempre la misma
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# ── 4. CREAR Y ENTRENAR EL MODELO ─────────────────────────────────────────────
# LogisticRegression() crea el modelo (aún no sabe nada).
modelo = LogisticRegression()
# .fit() es donde ocurre el aprendizaje: el modelo analiza X_train e y_train
# y ajusta sus parámetros internos para predecir lo mejor posible.
# IMPORTANTE: solo usamos los datos de ENTRENAMIENTO aquí, nunca X_test.
modelo.fit(X_train, y_train)
# ── 5. PREDECIR ESTUDIANTES NUEVOS ────────────────────────────────────────────
# Aquí probamos el modelo con estudiantes que NO estaban en el dataset.
# Cada fila es un estudiante: [horas_estudio, asistencia, nota_parcial, horas_suenio]
#
# ⚠ OJO: el tercer estudiante tiene horas_suenio=1 (parece un error en los datos,
# debería ser algo como 6, 7 u 8). El modelo igualmente hace una predicción,
# pero el resultado puede no ser realista.
estudiante_nuevo = [
[3.5, 82, 6.8, 7], # estudiante 1: estudia bastante, buena nota parcial
[2.0, 60, 6.0, 6], # estudiante 2: perfil intermedio
[4.0, 50, 5.0, 8], # estudiante 3: estudia mucho pero baja asistencia
]
# predict_proba() devuelve la PROBABILIDAD de pertenecer a cada clase.
# Para cada estudiante devuelve dos números: [P(suspende), P(aprueba)]
# Ambos suman siempre 1.0 (100%).
# Ejemplo: [0.30, 0.70] → 30% de suspender, 70% de aprobar
prob = modelo.predict_proba(estudiante_nuevo)
# predict() devuelve directamente la CLASE predicha: 0 (suspenso) o 1 (aprobado).
# Internamente aplica un umbral de 0.5: si P(aprueba) > 0.5 → predice 1.
clase = modelo.predict(estudiante_nuevo)
# Accedemos a la probabilidad de suspender del SEGUNDO estudiante (índice 1).
# prob tiene forma [estudiante][clase]: prob[1][0] = P(suspende) del estudiante 2.
print(prob[1][0])
# Imprime la clase predicha de los tres estudiantes: algo como [1 0 1]
print(clase)
# Recorremos todos los estudiantes e imprimimos sus probabilidades de forma legible.
# round(...*100) convierte 0.73 en 73 para mostrarlo como porcentaje entero.
for probabilidad in prob:
print(
f"Probabilidad de suspender {round(probabilidad[0] * 100)}% "
f"y de aprobar {round(probabilidad[1] * 100)}%"
)
# ── 6. EVALUAR EL MODELO SOBRE LOS DATOS DE TEST ──────────────────────────────
# Ahora usamos X_test (las características que el modelo NO vio al entrenar)
# para ver si predice correctamente. Comparamos con y_test (los valores reales).
# y_pred contiene la clase predicha (0 o 1) para cada fila de X_test
y_pred = modelo.predict(X_test)
# y_pred_proba contiene las probabilidades [P(0), P(1)] para cada fila de X_test
y_pred_proba = modelo.predict_proba(X_test)
# Clases predichas: [1 0 1 ...] → lo que el modelo cree que va a pasar
print(y_pred)
# Probabilidades: [[0.3, 0.7], [0.8, 0.2], ...] → con qué seguridad lo predice
print(y_pred_proba)
# Clases reales: los valores verdaderos de y_test que guardamos al principio.
# Comparando y_pred con y_test podemos calcular si el modelo acierta o falla.
print(y_test)
El tuétano de las regresiones
# =============================================================================
# COMPARATIVA: REGRESIÓN LOGÍSTICA vs REGRESIÓN LINEAL
# ¿En qué se diferencian? ¿Cuándo usar cada una?
# =============================================================================
#
# IDEA GENERAL DEL EJERCICIO:
# Tenemos 10 alumnos con sus horas de estudio.
# Vamos a entrenar DOS modelos distintos con los mismos datos:
#
# Modelo 1 — Regresión LOGÍSTICA:
# Pregunta: ¿Aprueba o suspende? → Responde con 0 o 1 (categoría)
#
# Modelo 2 — Regresión LINEAL:
# Pregunta: ¿Qué nota sacará? → Responde con un número decimal (valor continuo)
#
# Esto ilustra la diferencia clave entre CLASIFICACIÓN y REGRESIÓN en ML.
# =============================================================================
# ─────────────────────────────────────────────────────────────────────────────
# IMPORTACIONES
# ─────────────────────────────────────────────────────────────────────────────
import numpy as np
# numpy nos permite trabajar con arrays numéricos de forma eficiente.
# Lo usamos aquí para crear los datos de entrada y darles el formato correcto.
from sklearn.linear_model import LogisticRegression, LinearRegression
# De scikit-learn importamos los dos modelos que vamos a comparar:
# - LogisticRegression: clasifica (aprueba / suspende)
# - LinearRegression: predice un número continuo (la nota)
from sklearn.model_selection import train_test_split
# Herramienta para dividir los datos en entrenamiento y prueba.
# En este ejercicio no la usamos porque los datos son muy pocos (solo 10),
# pero se importa para recordar que en proyectos reales SIEMPRE hay que usarla.
from sklearn.metrics import accuracy_score
# Función para medir el porcentaje de aciertos de un clasificador.
# Igual que train_test_split, se importa como recordatorio de buenas prácticas,
# aunque en este ejercicio simplificado no la aplicamos.
# =============================================================================
# PASO 1 — PREPARAR LOS DATOS
# =============================================================================
# Horas de estudio de 10 alumnos (la variable de entrada, llamada X)
# Cada número representa cuántas horas estudió ese alumno.
X = np.array([1, 2, 2, 3, 4, 5, 6, 7, 8, 9]).reshape(-1, 1)
#
# ¿Qué hace .reshape(-1, 1)?
# ─────────────────────────
# scikit-learn espera que X tenga forma de TABLA (filas × columnas),
# aunque tengamos una sola columna.
#
# Sin reshape, X sería una lista plana: [1, 2, 2, 3, ...] → forma (10,)
# Con reshape(-1, 1), X se convierte en una columna vertical:
#
# [[1],
# [2],
# [2],
# [3], ← cada alumno ocupa una fila
# ...]
#
# El -1 le dice a numpy: "calcula tú cuántas filas hacen falta".
# El 1 indica que queremos 1 columna.
# Resultado: shape (10, 1) → 10 filas, 1 columna.
# Etiquetas para el Modelo 1 — Regresión LOGÍSTICA
# 0 = suspende, 1 = aprueba
y = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1])
#
# Alumno 1 estudió 1 hora → suspende (0)
# Alumno 2 estudió 2 horas → suspende (0)
# Alumno 3 estudió 2 horas → suspende (0)
# Alumno 4 estudió 3 horas → suspende (0)
# Alumno 5 estudió 4 horas → suspende (0)
# Alumno 6 estudió 5 horas → aprueba (1)
# Alumno 7 estudió 6 horas → aprueba (1)
# Alumno 8 estudió 7 horas → aprueba (1)
# Alumno 9 estudió 8 horas → aprueba (1)
# Alumno 10 estudió 9 horas → aprueba (1)
#
# El patrón es claro: a partir de 5 horas, los alumnos aprueban.
# El modelo logístico tiene que aprender ese umbral.
# =============================================================================
# PASO 2 — MODELO 1: REGRESIÓN LOGÍSTICA (clasificación)
# =============================================================================
#
# La regresión logística responde preguntas de tipo SÍ/NO, o en este caso
# APRUEBA/SUSPENDE. Internamente calcula la probabilidad de que el resultado
# sea 1 (aprueba), y si esa probabilidad supera el 50%, predice 1.
#
# Ejemplo: para un alumno con 3 horas podría calcular:
# probabilidad de aprobar = 15% → predice 0 (suspende)
#
# Para un alumno con 7 horas:
# probabilidad de aprobar = 92% → predice 1 (aprueba)
modelo = LogisticRegression()
# Creamos el modelo. Por ahora es una "caja vacía", aún no ha aprendido nada.
modelo.fit(X, y)
# .fit() es el entrenamiento.
# El modelo analiza todos los pares (horas_estudio, resultado)
# y ajusta sus parámetros internos para aprender el patrón:
# "a partir de X horas, la probabilidad de aprobar supera el 50%".
# ── Predicción con el Modelo 1 ────────────────────────────────────────────
print("=== MODELO 1: Regresión Logística (¿aprueba o suspende?) ===")
print(modelo.predict([[3]]))
# Le preguntamos al modelo: ¿un alumno que estudia 3 horas aprueba o suspende?
#
# [[3]] → los corchetes dobles son necesarios porque el modelo espera
# una tabla, no un número suelto. Es la misma lógica que el reshape anterior.
#
# Resultado esperado: [0] → el modelo predice que SUSPENDE.
# Esto tiene sentido: con solo 3 horas, los datos de entrenamiento
# mostraban que ese alumno suspendería.
print(modelo.predict_proba([[3]]))
# predict_proba nos da la probabilidad de cada clase, no solo la decisión.
# Devuelve dos números: [P(suspende), P(aprueba)]
# Por ejemplo: [0.82, 0.18] → 82% de probabilidad de suspender, 18% de aprobar.
# Útil cuando no queremos solo un sí/no, sino el grado de certeza del modelo.
# =============================================================================
# PASO 3 — DATOS PARA EL MODELO 2
# =============================================================================
# Notas numéricas de los mismos 10 alumnos (la nueva variable objetivo)
notas = np.array([3, 3, 4, 4, 5, 5, 6, 6, 8, 9])
#
# Alumno 1 (1 hora) → nota 3
# Alumno 2 (2 horas) → nota 3
# Alumno 3 (2 horas) → nota 4
# Alumno 4 (3 horas) → nota 4
# Alumno 5 (4 horas) → nota 5
# Alumno 6 (5 horas) → nota 5
# Alumno 7 (6 horas) → nota 6
# Alumno 8 (7 horas) → nota 6
# Alumno 9 (8 horas) → nota 8
# Alumno 10 (9 horas) → nota 9
#
# Aquí ya no es aprueba/suspende, sino la nota EXACTA.
# El modelo lineal tiene que aprender cuánto sube la nota por cada hora más.
# =============================================================================
# PASO 4 — MODELO 2: REGRESIÓN LINEAL (predicción de un valor continuo)
# =============================================================================
#
# La regresión lineal ajusta una LÍNEA RECTA a los datos.
# Busca la ecuación: nota = a × horas + b
#
# Donde:
# a (pendiente) = cuántos puntos sube la nota por cada hora extra
# b (intercepto) = nota base si el alumno estudiara 0 horas
#
# A diferencia del modelo logístico, la salida puede ser CUALQUIER número
# decimal: 4.2, 5.87, 7.3... No está limitada a 0 y 1.
modelo_2 = LinearRegression()
# Creamos el segundo modelo, también vacío de momento.
modelo_2.fit(X, notas)
# Entrenamos el modelo con las mismas horas de estudio (X)
# pero ahora la variable objetivo son las notas numéricas.
#
# Internamente calcula la recta que mejor se ajusta a los datos,
# minimizando el error total entre las notas reales y las predichas.
# ── Predicción con el Modelo 2 ────────────────────────────────────────────
print("\n=== MODELO 2: Regresión Lineal (¿qué nota sacará?) ===")
print(modelo_2.predict([[3]]))
# Le preguntamos: ¿qué nota obtendrá un alumno que estudia 3 horas?
#
# Resultado esperado: algo cercano a 4.0 o 4.2 (un número decimal).
# No devuelve 0 o 1, sino un valor numérico que representa la nota estimada.
# =============================================================================
# RESUMEN: ¿EN QUÉ SE DIFERENCIAN LOS DOS MODELOS?
# =============================================================================
#
# ┌──────────────────────┬─────────────────────┬──────────────────────────┐
# │ │ Regresión LOGÍSTICA │ Regresión LINEAL │
# ├──────────────────────┼─────────────────────┼──────────────────────────┤
# │ Tipo de problema │ Clasificación │ Regresión │
# │ Pregunta que responde│ ¿Categoría A o B? │ ¿Qué valor numérico? │
# │ Salida del modelo │ 0 o 1 (clase) │ Número decimal (nota) │
# │ En este ejercicio │ Suspende / Aprueba │ Nota entre 0 y 10 │
# │ Función interna │ Sigmoide (0 a 1) │ Recta y = ax + b │
# │ Métrica habitual │ Accuracy │ MSE, RMSE, R² │
# └──────────────────────┴─────────────────────┴──────────────────────────┘
#
# REGLA PRÁCTICA:
# → Si la respuesta es una CATEGORÍA (sí/no, color, especie...) → Logística
# → Si la respuesta es un NÚMERO CONTINUO (precio, temperatura...) → Lineal
# =============================================================================
Regresión logística spam
"""
Ejemplo didáctico de Regresión Logística
========================================
Objetivo:
Predecir si un correo es SPAM (1) o NO SPAM (0).
Este ejemplo está pensado para enseñar:
1. Qué es un dataset.
2. Por qué se divide en entrenamiento (train) y prueba (test).
3. Cómo aprende un modelo de regresión logística.
4. Qué significa predecir.
5. Cómo interpretar las métricas.
Dataset:
- 30 correos ficticios.
- Variables:
* num_palabras_promocionales
* num_enlaces
* porcentaje_mayusculas
* spam (objetivo)
La variable spam vale:
1 = spam
0 = no spam
"""
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (
accuracy_score,
confusion_matrix,
classification_report
)
# ------------------------------------------------------------------
# 1. DATASET
# ------------------------------------------------------------------
datos = [
[0,0,5,0],
[1,0,4,0],
[0,1,8,0],
[2,0,6,0],
[1,1,7,0],
[0,0,3,0],
[2,1,10,0],
[1,0,12,0],
[0,1,6,0],
[2,0,8,0],
[5,3,45,1],
[6,4,55,1],
[4,2,40,1],
[7,5,60,1],
[5,4,50,1],
[6,3,48,1],
[8,5,70,1],
[4,3,42,1],
[7,4,65,1],
[5,2,46,1],
[1,1,15,0],
[2,1,18,0],
[3,1,22,0],
[3,2,25,0],
[4,1,28,0],
[4,2,30,1],
[3,3,35,1],
[2,2,20,0],
[5,1,32,1],
[1,2,18,0]
]
df = pd.DataFrame(
datos,
columns=[
"num_palabras_promocionales",
"num_enlaces",
"porcentaje_mayusculas",
"spam"
]
)
print("="*70)
print("DATASET COMPLETO")
print("="*70)
print(df)
# ------------------------------------------------------------------
# 2. SEPARAR VARIABLES DE ENTRADA Y SALIDA
# ------------------------------------------------------------------
X = df[
[
"num_palabras_promocionales",
"num_enlaces",
"porcentaje_mayusculas"
]
]
y = df["spam"]
# ------------------------------------------------------------------
# 3. DIVISIÓN TRAIN / TEST
# ------------------------------------------------------------------
"""
¿Por qué dividimos los datos?
Imagina que un profesor enseña las respuestas exactas de un examen
a un alumno y luego le pone exactamente el mismo examen.
Sacar un 10 no demostraría que ha aprendido.
Con Machine Learning ocurre lo mismo.
TRAIN:
Datos que el modelo utiliza para aprender.
TEST:
Datos que el modelo NO ha visto nunca.
Si funciona bien en TEST, tenemos más confianza en que
generaliza correctamente.
"""
X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.30,
random_state=42
)
print("\n")
print("="*70)
print("DIVISIÓN TRAIN / TEST")
print("="*70)
print(f"Filas para entrenamiento: {len(X_train)}")
print(f"Filas para prueba: {len(X_test)}")
print("Estos datos los hemos reservado para test")
print(X_test)
print("Y estas son los datos reales")
print(y_test)
# ------------------------------------------------------------------
# 4. ENTRENAMIENTO
# ------------------------------------------------------------------
modelo = LogisticRegression()
modelo.fit(X_train, y_train)
print("\nModelo entrenado.")
# ------------------------------------------------------------------
# 5. PREDICCIÓN
# ------------------------------------------------------------------
"""
¿Qué significa predecir?
El modelo observa las características de un correo que nunca
ha visto y estima:
P(spam)
Es decir, la probabilidad de que sea spam.
Después convierte esa probabilidad en una clase:
>= 0.5 -> spam
< 0.5 -> no spam
"""
probabilidades = modelo.predict_proba(X_test)
predicciones = modelo.predict(X_test)
print("\n")
print("="*70)
print("PREDICCIONES")
print("="*70)
for i in range(len(X_test)):
prob_no_spam = probabilidades[i][0]
prob_spam = probabilidades[i][1]
print(
f"Correo {i+1:2d} | "
f"P(No Spam)={prob_no_spam:.3f} | "
f"P(Spam)={prob_spam:.3f} | "
f"Predicción={predicciones[i]} | "
f"Real={list(y_test)[i]}"
)
print("Los datos reales vs. predichos:")
print(y_test.tolist())
print(predicciones)
# ------------------------------------------------------------------
# 6. MÉTRICAS
# ------------------------------------------------------------------
accuracy = accuracy_score(y_test, predicciones)
print("\n")
print("="*70)
print("ACCURACY")
print("="*70)
print(f"Accuracy = {accuracy:.3f}")
"""
Accuracy:
(Número de aciertos) / (Número total de casos)
Ejemplo:
Si el modelo acierta 8 de 10 correos:
Accuracy = 8/10 = 0.80 = 80%
"""
print("\n")
print("="*70)
print("MATRIZ DE CONFUSIÓN")
print("="*70)
cm = confusion_matrix(y_test, predicciones)
print(cm)
"""
Matriz de confusión:
Predijo No Spam Predijo Spam
Real No Spam VN FP
Real Spam FN VP
VN = Verdadero Negativo
FP = Falso Positivo
FN = Falso Negativo
VP = Verdadero Positivo
FP:
Correo normal marcado como spam.
FN:
Correo spam que el modelo dejó pasar.
"""
print("\n")
print("="*70)
print("CLASSIFICATION REPORT")
print("="*70)
print(classification_report(y_test, predicciones))
"""
Precision:
De todos los correos marcados como spam,
¿cuántos eran realmente spam?
Recall:
De todos los spam reales,
¿cuántos encontró el modelo?
F1:
Media armónica entre Precision y Recall.
Support:
Número de ejemplos de cada clase.
"""
# ------------------------------------------------------------------
# 7. EJEMPLO NUEVO
# ------------------------------------------------------------------
nuevo_correo = pd.DataFrame(
[[6, 4, 58]],
columns=[
"num_palabras_promocionales",
"num_enlaces",
"porcentaje_mayusculas"
]
)
prob = modelo.predict_proba(nuevo_correo)[0][1]
print("\n")
print("="*70)
print("EJEMPLO DE CORREO NUEVO")
print("="*70)
print(nuevo_correo)
print(f"Probabilidad de spam: {prob:.3f}")
print(f"Clasificación final: {modelo.predict(nuevo_correo)[0]}")
"""
Conclusión:
Entrenamiento:
El modelo aprende patrones.
Prueba:
Verificamos si esos patrones funcionan en datos nuevos.
Predicción:
Aplicamos lo aprendido a correos nunca vistos.
Métricas:
Cuantifican si el modelo está funcionando bien.
"""
Regresión logística
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
#1. DATOS
# Horas de estudio de 10 alumnos
X = np.array([1, 2, 2, 3, 4, 5, 6, 7, 8, 9]).reshape(-1, 1)
# 0 = suspenso, 1 = aprobado
y = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1])
print(X)
# Dividir train y test
X_train, X_test, y_train, y_test = train_test_split(X, y,test_size=0.2, random_state=42)
# Entreno el modelo
# Creamos el modelo
modelo = LogisticRegression()
# Entrenamos el modelo con los datos de entrenamiento
modelo.fit(X_train, y_train)
# Medimos que tal funciona nuestro modelo
# Predecimos con los datos de prueba
predicciones = modelo.predict(X_test)
print('Predicciones:', predicciones)
# Calculamos la precisión
precision = accuracy_score(y_test, predicciones)
print(f'Precisión del modelo: {precision * 100:.1f}%')
# Predecir un alumno nuevo: ¿aprobará alguien que estudió 5 horas?
nuevo_alumno = np.array([[5]])
print('¿Aprueba con 5 horas?', modelo.predict(nuevo_alumno))
print('Probabilidad:', modelo.predict_proba(nuevo_alumno))
Ejemplo completo
# =============================================================================
# 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)?")