# ==============================================================================
# CONVERSIÓN FAHRENHEIT → CELSIUS CON REDES NEURONALES
#
# FÓRMULA REAL: C = (F - 32) × 5/9 ≈ C = F × 0.5556 - 17.778
#
# OBJETIVO: que la red aprenda sola esa fórmula, SIN que nosotros se la digamos.
# Solo le damos pares de datos (F, C) y dejamos que ajuste sus pesos.
#
# MODELOS:
# Modelo 1 — UNA sola neurona (1 peso + 1 bias = 2 parámetros)
# Modelo 2 — Dos capas de 4 neuronas (más potente, más parámetros)
#
# INSTALACIÓN (una sola vez):
# pip install tensorflow numpy matplotlib
# ==============================================================================
# ==============================================================================
# PASO 1: Importar librerías
# ==============================================================================
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
tf.random.set_seed(42)
np.random.seed(42)
print("=" * 60)
print(" REDES NEURONALES: Fahrenheit → Celsius")
print("=" * 60)
print(f" TensorFlow: {tf.__version__}")
# ==============================================================================
# PASO 2: Crear el dataset — 20 pares (Fahrenheit, Celsius)
# ==============================================================================
#
# Generamos 20 temperaturas en Fahrenheit distribuidas entre -40 °F y 212 °F
# y calculamos su equivalente exacto en Celsius con la fórmula real.
#
# -40 °F es un punto especial: coincide exactamente con -40 °C.
# 212 °F = punto de ebullición del agua = 100 °C.
fahrenheit = np.array([
-40, -22, -4, 14, 32, 50, 68, 77, 86, 95,
104, 113, 122, 140, 158, 176, 185, 194, 203, 212
], dtype=float)
# Fórmula exacta: C = (F - 32) × 5 / 9
celsius = (fahrenheit - 32) * 5 / 9
print("\n" + "=" * 60)
print("PASO 2: Dataset — 20 pares Fahrenheit / Celsius")
print("=" * 60)
print(f"{'Fahrenheit':>12} │ {'Celsius':>10} │ {'Nota'}")
print("-" * 45)
notas = {-40: "F=C", 32: "Congela", 98.6: "Cuerpo", 212: "Hierve"}
for f, c in zip(fahrenheit, celsius):
nota = notas.get(f, "")
print(f"{f:>12.1f} │ {c:>10.4f} │ {nota}")
# ==============================================================================
# PASO 3: Normalizar los datos
# ==============================================================================
#
# Las redes neuronales aprenden mejor cuando los datos están en rangos pequeños
# (cerca de 0). Guardamos la media y desviación para desnormalizar al final.
f_media = fahrenheit.mean()
f_std = fahrenheit.std()
c_media = celsius.mean()
c_std = celsius.std()
F_norm = (fahrenheit - f_media) / f_std # Fahrenheit normalizado
C_norm = (celsius - c_media) / c_std # Celsius normalizado
print("\n" + "=" * 60)
print("PASO 3: Normalización (media=0, desv=1)")
print("=" * 60)
print(f" Fahrenheit → media: {f_media:.2f}, desv: {f_std:.2f}")
print(f" Celsius → media: {c_media:.2f}, desv: {c_std:.2f}")
# ==============================================================================
# PASO 4A: MODELO 1 — Una sola neurona
# ==============================================================================
#
# Arquitectura: [1 entrada F] → [1 neurona] → [1 salida C]
#
# Esta neurona tiene exactamente DOS parámetros:
# w (peso / weight): multiplica a F
# b (bias / sesgo): suma un valor fijo
#
# La neurona calcula: salida = w × F + b
#
# ¿Se puede aprender C = F × 0.5556 - 17.778 con una sola neurona?
# SÍ, porque la conversión es una función LINEAL (línea recta).
# Una neurona sin activación no lineal es exactamente eso: una línea.
#
# Sin activation= → la neurona es lineal (el valor por defecto es 'linear')
modelo1 = keras.Sequential([
layers.Dense(units=1, input_shape=(1,)) # 1 neurona, sin activación no lineal
], name="Una_Neurona")
modelo1.compile(
optimizer=keras.optimizers.Adam(learning_rate=0.05),
loss="mean_squared_error" # MSE: error cuadrático medio entre C_pred y C_real
)
print("\n" + "=" * 60)
print("PASO 4A: Modelo 1 — Una sola neurona")
print("=" * 60)
modelo1.summary()
hist1 = modelo1.fit(
F_norm, C_norm,
epochs=500,
verbose=0 # silencioso para no llenar la pantalla
)
loss_final_m1 = hist1.history["loss"][-1]
print(f"\n ✅ Entrenamiento completado — Loss final: {loss_final_m1:.8f}")
# ==============================================================================
# PASO 4B: MODELO 2 — Dos capas de 4 neuronas
# ==============================================================================
#
# Arquitectura:
# [1 entrada] → [4 neuronas ReLU] → [4 neuronas ReLU] → [1 salida]
#
# Parámetros:
# Capa oculta 1: (1 entrada × 4 neuronas) + 4 bias = 8
# Capa oculta 2: (4 entradas × 4 neuronas) + 4 bias = 20
# Capa salida: (4 entradas × 1 neurona) + 1 bias = 5
# TOTAL: 33 parámetros
#
# Para un problema LINEAL como este, esta arquitectura es "excesiva",
# pero sirve perfectamente para comparar comportamiento y mostrar los pesos.
#
# ReLU: si el valor es negativo → 0; si es positivo → lo deja pasar.
# La combinación de varias ReLU puede aproximar cualquier función.
modelo2 = keras.Sequential([
layers.Dense(units=4, activation="relu", input_shape=(1,)), # capa oculta 1
layers.Dense(units=4, activation="relu"), # capa oculta 2
layers.Dense(units=1) # capa de salida (lineal)
], name="Dos_Capas_4_Neuronas")
modelo2.compile(
optimizer=keras.optimizers.Adam(learning_rate=0.05),
loss="mean_squared_error"
)
print("\n" + "=" * 60)
print("PASO 4B: Modelo 2 — Dos capas de 4 neuronas")
print("=" * 60)
modelo2.summary()
hist2 = modelo2.fit(
F_norm, C_norm,
epochs=500,
verbose=0
)
loss_final_m2 = hist2.history["loss"][-1]
print(f"\n ✅ Entrenamiento completado — Loss final: {loss_final_m2:.8f}")
# ==============================================================================
# PASO 5: Evaluar el rendimiento de ambos modelos
# ==============================================================================
def evaluar_modelo(modelo, F_norm, celsius_real, f_media, f_std, c_media, c_std):
"""
Evalúa un modelo comparando sus predicciones con los valores reales.
Desnormaliza tanto la predicción como el valor real para mostrar °C verdaderos.
"""
C_pred_norm = modelo.predict(F_norm, verbose=0).flatten()
# Desnormalizamos para obtener grados Celsius reales
C_pred_real = C_pred_norm * c_std + c_media
errores_abs = np.abs(C_pred_real - celsius_real)
mae = errores_abs.mean() # Error Absoluto Medio (MAE) en °C
rmse = np.sqrt(((C_pred_real - celsius_real) ** 2).mean()) # Raíz del Error Cuadrático Medio
return C_pred_real, mae, rmse
C_pred1, mae1, rmse1 = evaluar_modelo(modelo1, F_norm, celsius, f_media, f_std, c_media, c_std)
C_pred2, mae2, rmse2 = evaluar_modelo(modelo2, F_norm, celsius, f_media, f_std, c_media, c_std)
print("\n" + "=" * 60)
print("PASO 5: Rendimiento comparado")
print("=" * 60)
print(f"\n {'Modelo':<28} {'MAE (°C)':>10} {'RMSE (°C)':>12}")
print(" " + "-" * 52)
print(f" {'Modelo 1: Una neurona':<28} {mae1:>10.6f} {rmse1:>12.6f}")
print(f" {'Modelo 2: Dos capas × 4':<28} {mae2:>10.6f} {rmse2:>12.6f}")
print("\n MAE = error promedio en grados Celsius")
print(" RMSE = error promedio (penaliza más los errores grandes)")
print("\n Un MAE < 0.5 °C es excelente para uso práctico.")
# Tabla predicciones vs real
print("\n Tabla de predicciones (primeras 10 muestras):")
print(f" {'F (°F)':>8} │ {'C real':>8} │ {'M1 pred':>8} │ {'M1 err':>8} │ "
f"{'M2 pred':>8} │ {'M2 err':>8}")
print(" " + "-" * 65)
for i in range(10):
print(f" {fahrenheit[i]:>8.1f} │ {celsius[i]:>8.4f} │ {C_pred1[i]:>8.4f} │ "
f"{abs(C_pred1[i]-celsius[i]):>8.4f} │ {C_pred2[i]:>8.4f} │ "
f"{abs(C_pred2[i]-celsius[i]):>8.4f}")
# ==============================================================================
# PASO 6: Mostrar los pesos de cada neurona
# ==============================================================================
#
# Los pesos son los NÚMEROS que la red aprendió ajustando sus parámetros.
# Para Modelo 1 (una neurona lineal), deberían aproximarse a:
# w ≈ 1.0 (en escala normalizada)
# b ≈ 0.0 (la fórmula C = F × 0.5556 - 17.78 se transforma en la escala norm.)
#
# Los pesos reales en escala original se pueden calcular así:
# pendiente = w × (c_std / f_std)
# intercepto = b × c_std + c_media - w × (c_std / f_std) × f_media
print("\n" + "=" * 60)
print("PASO 6: Pesos aprendidos por cada neurona")
print("=" * 60)
# ---- MODELO 1: UNA NEURONA ----
print("\n ── MODELO 1: Una sola neurona ──")
pesos_m1, bias_m1 = modelo1.layers[0].get_weights()
# get_weights() devuelve una lista: [matriz_de_pesos, vector_de_bias]
# Para 1 neurona con 1 entrada: pesos shape=(1,1), bias shape=(1,)
w = pesos_m1[0][0] # el único peso
b = bias_m1[0] # el único bias
# Convertir de escala normalizada a escala real (°C / °F)
pendiente = w * (c_std / f_std)
intercepto = b * c_std + c_media - w * (c_std / f_std) * f_media
print(f"\n Neurona 1:")
print(f" Peso (w) en escala norm.: {w:>10.6f}")
print(f" Bias (b) en escala norm.: {b:>10.6f}")
print(f"\n Traducido a escala real °C = w×°F + b:")
print(f" Pendiente aprendida: {pendiente:>10.6f} (real: 0.555556)")
print(f" Intercepto aprendido: {intercepto:>10.6f} (real: -17.777778)")
print(f"\n Error de la pendiente: {abs(pendiente - 0.555556):>10.8f} °C/°F")
print(f" Error del intercepto: {abs(intercepto - (-17.777778)):>10.8f} °C")
# ---- MODELO 2: DOS CAPAS DE 4 NEURONAS ----
print("\n\n ── MODELO 2: Dos capas de 4 neuronas ──")
nombres_capas = ["Capa oculta 1 (ReLU)", "Capa oculta 2 (ReLU)", "Capa de salida (lineal)"]
for idx, (capa, nombre) in enumerate(zip(modelo2.layers, nombres_capas)):
pesos, bias = capa.get_weights()
# pesos shape: (n_entradas, n_neuronas)
# bias shape: (n_neuronas,)
n_entradas, n_neuronas = pesos.shape
print(f"\n ┌─ {nombre}")
print(f" │ Entradas: {n_entradas} Neuronas: {n_neuronas}")
print(f" │")
for j in range(n_neuronas):
print(f" │ Neurona {j+1}:")
for i in range(n_entradas):
print(f" │ w[entrada_{i+1}→neurona_{j+1}] = {pesos[i][j]:>10.6f}")
print(f" │ bias[neurona_{j+1}] = {bias[j]:>10.6f}")
if j < n_neuronas - 1:
print(f" │")
print(f" └{'─' * 50}")
# ==============================================================================
# PASO 7: Gráficos
# ==============================================================================
fig, axes = plt.subplots(2, 2, figsize=(15, 11))
fig.suptitle("Redes Neuronales: Aprender Fahrenheit → Celsius", fontsize=15, fontweight="bold")
VERDE = "#27AE60"
NARANJA = "#E67E22"
AZUL = "#2980B9"
ROJO = "#E74C3C"
# ---- Gráfico 1: Curvas de pérdida ----
ax = axes[0, 0]
ax.plot(hist1.history["loss"], color=NARANJA, linewidth=2, label="Modelo 1: 1 neurona")
ax.plot(hist2.history["loss"], color=AZUL, linewidth=2, label="Modelo 2: 2 capas × 4")
ax.set_title("Curva de pérdida (MSE) durante el entrenamiento")
ax.set_xlabel("Época")
ax.set_ylabel("MSE (escala normalizada)")
ax.legend()
ax.grid(True, alpha=0.3)
ax.set_yscale("log") # escala logarítmica para ver mejor la convergencia
# ---- Gráfico 2: Predicciones vs real ----
ax = axes[0, 1]
ax.plot(fahrenheit, celsius, color=ROJO, linewidth=2.5, linestyle="--",
label="Fórmula real", zorder=5)
ax.scatter(fahrenheit, C_pred1, color=NARANJA, s=80, marker="o",
label="Modelo 1: 1 neurona", zorder=4)
ax.scatter(fahrenheit, C_pred2, color=AZUL, s=60, marker="^",
label="Modelo 2: 2 capas × 4", zorder=3)
ax.set_title("Predicciones vs valores reales")
ax.set_xlabel("Temperatura (°F)")
ax.set_ylabel("Temperatura (°C)")
ax.legend()
ax.grid(True, alpha=0.3)
# ---- Gráfico 3: Error absoluto por muestra ----
ax = axes[1, 0]
x_idx = np.arange(len(fahrenheit))
ancho = 0.35
bars1 = ax.bar(x_idx - ancho/2, np.abs(C_pred1 - celsius), ancho,
color=NARANJA, alpha=0.85, label="Modelo 1: 1 neurona")
bars2 = ax.bar(x_idx + ancho/2, np.abs(C_pred2 - celsius), ancho,
color=AZUL, alpha=0.85, label="Modelo 2: 2 capas × 4")
ax.set_title("Error absoluto por muestra (°C)")
ax.set_xlabel("Índice de muestra")
ax.set_ylabel("|Predicción − Real| (°C)")
ax.set_xticks(x_idx)
ax.set_xticklabels([f"{f:.0f}" for f in fahrenheit], rotation=45, ha="right", fontsize=8)
ax.legend()
ax.grid(True, alpha=0.3, axis="y")
# ---- Gráfico 4: Visualización de pesos del Modelo 1 ----
ax = axes[1, 1]
categorias = ["Peso (w)\n(escala norm.)", "Bias (b)\n(escala norm.)",
"Pendiente real\n(°C/°F)", "Intercepto real\n(°C)"]
valores_aprendidos = [w, b, pendiente, intercepto]
valores_reales = [None, None, 0.555556, -17.777778]
colores = [NARANJA, NARANJA, VERDE, VERDE]
bars = ax.bar(categorias, valores_aprendidos, color=colores, alpha=0.8, edgecolor="white")
# Líneas horizontales con los valores teóricos (solo donde aplica)
ax.axhline(0.555556, xmin=0.49, xmax=0.74, color=ROJO, linewidth=2.5,
linestyle="--", label="Valor teórico")
ax.axhline(-17.777778, xmin=0.74, xmax=1.0, color=ROJO, linewidth=2.5,
linestyle="--")
for bar, val in zip(bars, valores_aprendidos):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.15 * np.sign(val + 0.001),
f"{val:.4f}", ha="center", va="bottom" if val >= 0 else "top", fontsize=9, fontweight="bold")
ax.set_title("Modelo 1: Pesos aprendidos vs teóricos")
ax.set_ylabel("Valor del parámetro")
ax.axhline(0, color="black", linewidth=0.8)
ax.legend()
ax.grid(True, alpha=0.3, axis="y")
plt.tight_layout()
plt.savefig("fahrenheit_celsius_redes.png", dpi=150, bbox_inches="tight")
plt.show()
# ==============================================================================
# RESUMEN FINAL
# ==============================================================================
print("\n" + "=" * 60)
print("RESUMEN FINAL")
print("=" * 60)
print(f"\n Fórmula real: C = F × 0.555556 − 17.777778")
print(f" Fórmula aprendida: C = F × {pendiente:.6f} − {abs(intercepto):.6f}")
print(f"\n {'Modelo':<28} {'Parámetros':>12} {'MAE (°C)':>10}")
print(" " + "-" * 52)
print(f" {'Modelo 1: Una neurona':<28} {'2':>12} {mae1:>10.6f}")
print(f" {'Modelo 2: Dos capas × 4':<28} {'33':>12} {mae2:>10.6f}")
print(f"\n ✅ Código completado. Gráfico guardado: fahrenheit_celsius_redes.png")
Categoría: Python
Ejemplo K-means
# ── 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}")
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)?")