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)?")


Regresión lineal multivariable

import numpy as np # Operaciones numéricas y arrays
import pandas as pd # Manejo de tablas de datos (DataFrames)
import matplotlib.pyplot as plt # Gráficas
from sklearn.model_selection import train_test_split # Dividir datos en tren/test
from sklearn.linear_model import LinearRegression # Nuestro modelo
from sklearn.preprocessing import StandardScaler # Normalizar datos
from sklearn.metrics import mean_squared_error, r2_score

np.random.seed(42)  # Fijamos semilla para reproducibilidad
n = 200         # Número de pisos en nuestro dataset

# Cada columna representa una característica del piso
data = pd.DataFrame({
    'metros2'     : np.random.randint(30, 150, n),   # Entre 30 y 150 m²
    'habitaciones': np.random.randint(1,  6, n),   # Entre 1 y 5 habitaciones
    'planta'      : np.random.randint(0,  10, n),   # Entre planta 0 y 9
    'antiguedad'  : np.random.randint(0,  50, n),   # Años de antigüedad
})

# Fórmula ficticia para el precio (así sabemos cuál es la verdad)
# precio = 50k + 2000×m² + 8000×hab + 500×planta - 300×antigüedad + ruido
ruido = np.random.normal(0, 10000, n)  # Variación aleatoria realista
data['precio'] = (
    50000
    + 2000 * data['metros2']
    + 8000 * data['habitaciones']
    +  500 * data['planta']
    -  300 * data['antiguedad']
    + ruido
)

print(data.head())  # Ver las primeras filas

# Aquí tenemos un dataset de 200 pisos con sus precios aleatorio pero siguiendo
# una fórmula que podemos predecir

# ─── Separar variables de entrada (X) y salida (y) ─────────────
X = data.drop(columns=['precio'])  # Todo excepto el precio
y = data['precio']          # Solo el precio (lo que queremos predecir)

# ─── Dividir en entrenamiento (80%) y test (20%) ─────────────
X_train, X_test, y_train, y_test = train_test_split(
    X, y,                   # Datos de entrada y salida
    test_size=0.2,          # 20% para test
    random_state=42          # Semilla para resultados reproducibles
)

print(f'Datos de entrenamiento: {len(X_train)} pisos')
print(f'Datos de test:          {len(X_test)} pisos')
# → Datos de entrenamiento: 160 pisos
# → Datos de test:          40 pisos

# ─── Normalizar los datos ──────────────────────────────────────
scaler = StandardScaler()  # Creamos el normalizador

# fit_transform: aprende la media y std DE LOS DATOS DE ENTRENAMIENTO
# y luego transforma. NUNCA uses fit en los datos de test.
X_train_scaled = scaler.fit_transform(X_train)

# transform solo: aplica la misma transformación al test
# (sin volver a aprender — eso sería trampa)
X_test_scaled  = scaler.transform(X_test)

modelo = LinearRegression()  # Instanciar el modelo

# .fit() es donde ocurre el 'aprendizaje':
# el modelo encuentra los mejores coeficientes β
modelo.fit(X_train_scaled, y_train)

# Ver los coeficientes aprendidos
print('Intercepto (β₀):', modelo.intercept_)
for nombre, coef in zip(X.columns, modelo.coef_):
    print(f'  {nombre:<15}: {coef:>10.2f}')

y_pred = modelo.predict(X_test_scaled)  # Predicciones

# R² (R-cuadrado): qué porcentaje de la variación explica el modelo
# Va de 0 a 1. Cuanto más cerca de 1, mejor.
r2  = r2_score(y_test, y_pred)

# RMSE: error promedio en las mismas unidades que y (euros)
# Fácil de interpretar: 'me equivoco en promedio X euros'
mse  = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)

print(f'R²   = {r2:.4f}')  # p.ej. R² = 0.9812
print(f'RMSE = {rmse:.2f} €')  # p.ej. RMSE = 9.847,23 €
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# --- Gráfico 1: Predicciones vs Valores reales ---
axes[0].scatter(y_test, y_pred, alpha=0.5, color='steelblue')
# La línea perfecta (y = x) sirve como referencia visual
axes[0].plot([y_test.min(), y_test.max()],
         [y_test.min(), y_test.max()], 'r--', label='Predicción perfecta')
axes[0].set_xlabel('Precio real (€)')
axes[0].set_ylabel('Precio predicho (€)')
axes[0].set_title(f'Predicciones vs. Valores reales\nR² = {r2:.4f}')
axes[0].legend()

# --- Gráfico 2: Residuos (errores del modelo) ---
residuos = y_test - y_pred  # Diferencia entre real y predicho
axes[1].scatter(y_pred, residuos, alpha=0.5, color='coral')
axes[1].axhline(0, color='black', linestyle='--')  # Línea en 0
axes[1].set_xlabel('Precio predicho (€)')
axes[1].set_ylabel('Residuo (real - predicho)')
axes[1].set_title('Análisis de Residuos\n(idealmente dispersos sin patrón)')

plt.tight_layout()  # Ajusta márgenes automáticamente
plt.savefig('resultados_regresion.png', dpi=150, bbox_inches='tight')
plt.show()

# ─── Predecir el precio de un piso nuevo ───────────────────────

# Creamos un DataFrame con las características del piso nuevo
# ¡Importante: usar los mismos nombres de columna que en el entrenamiento!
piso_nuevo = pd.DataFrame([{
    'metros2'     : 90,   # 90 metros cuadrados
    'habitaciones': 3,   # 3 habitaciones
    'planta'      : 5,   # Planta 5
    'antiguedad'  : 10,  # 10 años de antigüedad
}])

# Normalizar usando el MISMO scaler que entrenamos (no fit_transform)
piso_nuevo_scaled = scaler.transform(piso_nuevo)

# Predecir
precio_estimado = modelo.predict(piso_nuevo_scaled)[0]
print(f'Precio estimado: {precio_estimado:,.0f} €')
# → Precio estimado: 250.500 €  (resultado aproximado)

Gráficos

import matplotlib.pyplot as plt

# --- DATOS ---
# Estos son los datos que queremos representar.
# Cada elemento de 'ventas' corresponde a un mes.
meses  = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun']
ventas = [120,   145,   132,   178,   165,   200]
ventas2 = [150,   125,   162,   128,   105,   230]

# plt.subplots() crea dos cosas a la vez:
#   fig  → la figura (el lienzo completo)
#   ax   → los ejes (la zona de dibujo)
fig, ax = plt.subplots()

# --- DIBUJAR LA LÍNEA ---
# ax.plot(eje_x, eje_y) dibuja la línea.
# color     → color de la línea (nombre o código hex)
# linewidth → grosor de la línea
# marker    → símbolo en cada punto ('o' = círculo)
ax.plot(meses, ventas, color='blueviolet', linewidth=2, marker='p',label="Tienda A")
ax.plot(meses, ventas2,mfc='green', color='tomato', linewidth=2, marker='s', linestyle='--', label="Tienda B")
ax.legend(loc='upper left', fontsize=10)
# --- ETIQUETAS Y TÍTULO ---
ax.set_title('Ventas mensuales')   # Título de la gráfica
ax.set_xlabel('Mes')               # Etiqueta del eje horizontal
ax.set_ylabel('Unidades vendidas') # Etiqueta del eje vertical

# --- CUADRÍCULA ---
# La cuadrícula facilita leer los valores.
# linestyle='--' → líneas discontinuas
# alpha=0.4      → 40% de opacidad (líneas sutiles)
ax.grid(True, linestyle='--', alpha=0.4)

# --- MOSTRAR ---
plt.tight_layout()  # Ajusta márgenes automáticamente
plt.savefig('ejemplo.png', dpi=150, bbox_inches='tight')

# plt.show() abre la ventana y muestra el resultado
plt.show()

Ejercicio métricas

# ================================================================
#  EJERCICIO PRÁCTICO: Evaluación de Modelos de Predicción
#  de Ventas con Métricas de Regresión
# ================================================================
#
#  CONTEXTO:
#  Una cafetería registró sus ventas diarias (en €) durante
#  30 días. Dos modelos intentaron predecir esas ventas:
#    - Modelo A: regresión lineal simple
#    - Modelo B: red neuronal experimental
#
#  Tu misión: calcular las métricas, interpretar los resultados
#  y decidir qué modelo recomendarías al dueño de la cafetería.
#
# ================================================================

import numpy as np
import matplotlib.pyplot as plt

# ----------------------------------------------------------------
# DATOS: 30 días de ventas reales y predicciones de cada modelo
# ----------------------------------------------------------------

dias = list(range(1, 31))   # Días del mes (1 al 30)

# Ventas reales registradas en la caja (en €)
ventas_reales = [
    312, 298, 341, 287, 305, 198, 210,   # Semana 1 (fin de semana bajo)
    330, 315, 352, 298, 341, 215, 224,   # Semana 2
    345, 318, 361, 302, 349, 231, 228,   # Semana 3
    358, 334, 372, 315, 360, 244, 237,   # Semana 4
    365, 348                             # Últimos 2 días del mes
]

# Predicciones del Modelo A (regresión lineal, más estable)
predicciones_modelo_A = [
    308, 301, 338, 291, 309, 205, 214,
    325, 318, 347, 303, 338, 221, 220,
    340, 322, 355, 308, 344, 226, 232,
    352, 330, 368, 319, 355, 240, 242,
    361, 344
]

# Predicciones del Modelo B (sobreajustado en semanas centrales)
predicciones_modelo_B = [
    295, 275, 360, 270, 325, 185, 240,
    310, 340, 330, 285, 365, 200, 245,
    325, 340, 340, 290, 370, 210, 255,
    340, 350, 350, 300, 385, 225, 265,
    350, 335
]

# ================================================================
#  SECCIÓN 1 — CÁLCULO DE MÉTRICAS
# ================================================================
#  Completa cada celda marcada con  ??? usando sklearn o numpy.
#  Pista: todas las funciones están en sklearn.metrics
#  Documentación: https://scikit-learn.org/stable/modules/model_evaluation.html
# ================================================================

from sklearn.metrics import mean_squared_error       # MSE
from sklearn.metrics import mean_absolute_error      # MAE
from sklearn.metrics import root_mean_squared_error  # RMSE
from sklearn.metrics import r2_score                 # R²

print("=" * 55)
print("     MÉTRICAS DE EVALUACIÓN — VENTAS 30 DÍAS")
print("=" * 55)

# ---- 1.1  MSE  -----------------------------------------------
# Pista: mean_squared_error(valores_reales, valores_predichos)
# Un MSE menor indica mejor ajuste.
# ¿Qué ocurre si un día el modelo falla por 50 €?
# Ese error pesa 50² = 2500 en el MSE.

mse_A = ???   # Calcula el MSE del Modelo A
mse_B = ???   # Calcula el MSE del Modelo B

print(f"\nMSE  → Modelo A: {mse_A:.2f}  |  Modelo B: {mse_B:.2f}")

# ---- 1.2  MAE  -----------------------------------------------
# Pista: mean_absolute_error(valores_reales, valores_predichos)
# A diferencia del MSE, todos los errores pesan igual.
# Un MAE de 15 significa: "me equivoco ~15 € por día de media".

mae_A = ???   # Calcula el MAE del Modelo A
mae_B = ???   # Calcula el MAE del Modelo B

print(f"MAE  → Modelo A: {mae_A:.2f}  |  Modelo B: {mae_B:.2f}")

# ---- 1.3  RMSE  ----------------------------------------------
# Pista: root_mean_squared_error(valores_reales, valores_predichos)
# Es la raíz cuadrada del MSE → devuelve el error en euros (€),
# igual que los datos originales. Más fácil de interpretar.

rmse_A = ???   # Calcula el RMSE del Modelo A
rmse_B = ???   # Calcula el RMSE del Modelo B

print(f"RMSE → Modelo A: {rmse_A:.2f}  |  Modelo B: {rmse_B:.2f}")

# ---- 1.4  R²  ------------------------------------------------
# Pista: r2_score(valores_reales, valores_predichos)
# Mide qué porcentaje de la variación en ventas explica el modelo.
# R² = 1 → perfecto | R² = 0 → igual que predecir siempre la media.

r2_A = ???   # Calcula el R² del Modelo A
r2_B = ???   # Calcula el R² del Modelo B

print(f"R²   → Modelo A: {r2_A:.4f}  |  Modelo B: {r2_B:.4f}")

print("\n" + "=" * 55)

# ================================================================
#  SECCIÓN 2 — GRÁFICA DE AJUSTE
# ================================================================
#  Aquí tienes la estructura completa de la gráfica.
#  Descomentar el bloque cuando tengas las métricas calculadas.
# ================================================================

# x     = np.array(dias)
# fig, axes = plt.subplots(2, 1, figsize=(14, 9))
# fig.suptitle("Comparativa de Modelos — Ventas Diarias Cafetería", fontsize=14, fontweight="bold")
#
# # --- Subgráfica superior: líneas de ajuste ---
# ax1 = axes[0]
# ax1.plot(x, ventas_reales,        "o-",  color="#2C3E50", lw=2.5, ms=5,  label="Ventas Reales")
# ax1.plot(x, predicciones_modelo_A,"s--", color="#2980B9", lw=1.8, ms=4,  label=f"Modelo A  (RMSE={rmse_A:.1f} €)")
# ax1.plot(x, predicciones_modelo_B,"^--", color="#E74C3C", lw=1.8, ms=4,  label=f"Modelo B  (RMSE={rmse_B:.1f} €)")
# ax1.fill_between(x, ventas_reales, predicciones_modelo_A, alpha=0.12, color="#2980B9")
# ax1.fill_between(x, ventas_reales, predicciones_modelo_B, alpha=0.12, color="#E74C3C")
# ax1.set_ylabel("Ventas (€)")
# ax1.set_xticks(x)
# ax1.set_xticklabels([f"D{d}" for d in dias], fontsize=7, rotation=45)
# ax1.legend(); ax1.grid(True, linestyle="--", alpha=0.4)
#
# # --- Subgráfica inferior: error absoluto por día ---
# error_A = [abs(r - p) for r, p in zip(ventas_reales, predicciones_modelo_A)]
# error_B = [abs(r - p) for r, p in zip(ventas_reales, predicciones_modelo_B)]
# ancho   = 0.35
# ax2 = axes[1]
# ax2.bar(x - ancho/2, error_A, width=ancho, color="#2980B9", alpha=0.8, label="Error diario Modelo A")
# ax2.bar(x + ancho/2, error_B, width=ancho, color="#E74C3C", alpha=0.8, label="Error diario Modelo B")
# ax2.axhline(y=mae_A, color="#1A5276", linestyle="--", lw=1.5, label=f"MAE Modelo A = {mae_A:.1f} €")
# ax2.axhline(y=mae_B, color="#922B21", linestyle="--", lw=1.5, label=f"MAE Modelo B = {mae_B:.1f} €")
# ax2.set_xlabel("Día del mes")
# ax2.set_ylabel("Error absoluto (€)")
# ax2.set_xticks(x)
# ax2.set_xticklabels([f"D{d}" for d in dias], fontsize=7, rotation=45)
# ax2.legend(); ax2.grid(True, axis="y", linestyle="--", alpha=0.4)
#
# plt.tight_layout()
# plt.savefig("ajuste_ventas.png", dpi=150, bbox_inches="tight")
# plt.show()


# ================================================================
#  SECCIÓN 3 — PREGUNTAS DE REFLEXIÓN
# ================================================================
#  Responde a continuación como comentarios de Python (#).
#  No hay una única respuesta correcta; razona tu decisión.
# ================================================================

# PREGUNTA 1 ── ¿Qué modelo tiene menor MSE?
# ¿Por qué crees que el MSE es más sensible a errores grandes
# que el MAE? Piensa en qué ocurre matemáticamente al elevar al
# cuadrado un error de 50 € frente a uno de 5 €.
#
# Tu respuesta:
#

# ─────────────────────────────────────────────────────────────

# PREGUNTA 2 ── Si el MAE de ambos modelos fuese muy similar
# pero el MSE fuese muy distinto, ¿qué nos estaría indicando
# sobre la distribución de los errores de cada modelo?
# Pista: piensa en días concretos donde un modelo falla mucho.
#
# Tu respuesta:
#

# ─────────────────────────────────────────────────────────────

# PREGUNTA 3 ── El dueño de la cafetería puede asumir errores
# pequeños cotidianos, pero le resulta muy perjudicial quedarse
# sin stock un día puntual por una predicción muy mala.
# ¿Qué métrica debería priorizar para elegir el modelo?
# ¿MSE/RMSE o MAE? Justifica tu respuesta.
#
# Tu respuesta:
#

# ─────────────────────────────────────────────────────────────

# PREGUNTA 4 ── Interpreta el valor de R² de cada modelo.
# ¿Un R² de 0.95 significa que el modelo predice bien siempre?
# ¿Podría haber algún día donde falle mucho y aun así tener
# un R² alto globalmente?
#
# Tu respuesta:
#

# ─────────────────────────────────────────────────────────────

# PREGUNTA 5 ── DECISIÓN FINAL
# Con los valores que has calculado, ¿qué modelo recomendarías
# al dueño de la cafetería y por qué?
# Menciona al menos dos métricas en tu argumento.
#
# Tu respuesta:
#

# ================================================================
#  FIN DEL EJERCICIO
# ================================================================

Comparaciones métricas

# =============================================================
# COMPARACIÓN DE MÉTRICAS ENTRE DOS MODELOS
# =============================================================
# Este script compara las predicciones de dos modelos frente
# a los valores reales usando MSE, MAE y RMSE, y genera una
# gráfica para visualizar el ajuste de cada modelo.
# =============================================================

# Valores predichos por cada modelo para 4 observaciones
modelo1 = [20, 22, 23, 24]   # Modelo 1: predicciones más conservadoras
modelo2 = [21, 24, 26, 24]   # Modelo 2: predicciones más variables
real    = [21, 24, 20, 31]   # Valores reales que queremos acertar

# -------------------------------------------------------------
# MSE — Error Cuadrático Medio
# Eleva cada error al cuadrado antes de promediar, por lo que
# penaliza más los errores grandes. Unidades: valor² (ej: €²)
# -------------------------------------------------------------
from sklearn.metrics import mean_squared_error
mse1 = mean_squared_error(real, modelo1)  # MSE del modelo 1
mse2 = mean_squared_error(real, modelo2)  # MSE del modelo 2
print(f"MSE  → Modelo1: {mse1:.2f}  |  Modelo2: {mse2:.2f}")

# -------------------------------------------------------------
# MAE — Error Absoluto Medio
# Promedio de los errores en valor absoluto. No penaliza tanto
# los errores grandes; más robusto ante outliers. Unidades: las
# mismas que los datos originales.
# -------------------------------------------------------------
from sklearn.metrics import mean_absolute_error
mae1 = mean_absolute_error(real, modelo1)
mae2 = mean_absolute_error(real, modelo2)
print(f"MAE  → Modelo1: {mae1:.2f}  |  Modelo2: {mae2:.2f}")

# -------------------------------------------------------------
# RMSE — Raíz del Error Cuadrático Medio
# Es la raíz cuadrada del MSE. Devuelve el error en las mismas
# unidades que los datos originales, siendo más interpretable
# que el MSE pero manteniendo la penalización de errores grandes.
# -------------------------------------------------------------
from sklearn.metrics import root_mean_squared_error
rmse1 = root_mean_squared_error(real, modelo1)
rmse2 = root_mean_squared_error(real, modelo2)
print(f"RMSE → Modelo1: {rmse1:.2f}  |  Modelo2: {rmse2:.2f}")

# =============================================================
# GRÁFICA: Ajuste de los modelos a los datos reales
# =============================================================
import matplotlib.pyplot as plt
import numpy as np

# Eje X: índices de las 4 observaciones (puntos de tiempo o muestras)
x = np.arange(1, len(real) + 1)  # [1, 2, 3, 4]

fig, axes = plt.subplots(1, 2, figsize=(14, 5))
fig.suptitle("Ajuste de Modelos vs. Datos Reales", fontsize=15, fontweight="bold", y=1.01)

# ---- Colores ----
COLOR_REAL    = "#2C3E50"
COLOR_M1      = "#2980B9"
COLOR_M2      = "#E74C3C"
COLOR_ERROR1  = "#AED6F1"
COLOR_ERROR2  = "#F1948A"

# ----------------------------------------------------------
# Subgráfica izquierda: líneas de los tres conjuntos
# ----------------------------------------------------------
ax1 = axes[0]
ax1.plot(x, real,    "o-",  color=COLOR_REAL, linewidth=2.5, markersize=8,  label="Valores Reales",  zorder=3)
ax1.plot(x, modelo1, "s--", color=COLOR_M1,   linewidth=2,   markersize=7,  label=f"Modelo 1  (MSE={mse1:.1f})")
ax1.plot(x, modelo2, "^--", color=COLOR_M2,   linewidth=2,   markersize=7,  label=f"Modelo 2  (MSE={mse2:.1f})")

# Sombrear el área de error de cada modelo respecto a los reales
ax1.fill_between(x, real, modelo1, alpha=0.15, color=COLOR_M1, label="Error Modelo 1")
ax1.fill_between(x, real, modelo2, alpha=0.15, color=COLOR_M2, label="Error Modelo 2")

ax1.set_title("Líneas de ajuste y área de error", fontsize=12)
ax1.set_xlabel("Observación")
ax1.set_ylabel("Valor")
ax1.set_xticks(x)
ax1.set_xticklabels([f"Obs {i}" for i in x])
ax1.legend(fontsize=9)
ax1.grid(True, linestyle="--", alpha=0.5)
ax1.set_ylim(15, 36)

# ----------------------------------------------------------
# Subgráfica derecha: barras comparativas de métricas
# ----------------------------------------------------------
ax2 = axes[1]

metricas      = ["MSE", "MAE", "RMSE"]
valores_m1    = [mse1,  mae1,  rmse1]
valores_m2    = [mse2,  mae2,  rmse2]

ancho = 0.3
pos   = np.arange(len(metricas))

bars1 = ax2.bar(pos - ancho/2, valores_m1, width=ancho, color=COLOR_M1,
                label="Modelo 1", edgecolor="white", linewidth=0.8)
bars2 = ax2.bar(pos + ancho/2, valores_m2, width=ancho, color=COLOR_M2,
                label="Modelo 2", edgecolor="white", linewidth=0.8)

# Etiquetar el valor encima de cada barra
for bar in bars1:
    ax2.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.2,
             f"{bar.get_height():.2f}", ha="center", va="bottom", fontsize=9, color=COLOR_M1, fontweight="bold")
for bar in bars2:
    ax2.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.2,
             f"{bar.get_height():.2f}", ha="center", va="bottom", fontsize=9, color=COLOR_M2, fontweight="bold")

ax2.set_title("Comparativa de métricas de error", fontsize=12)
ax2.set_ylabel("Valor del error")
ax2.set_xticks(pos)
ax2.set_xticklabels(metricas, fontsize=11)
ax2.legend(fontsize=10)
ax2.grid(True, axis="y", linestyle="--", alpha=0.5)

plt.tight_layout()
plt.savefig("ajuste_modelos.png", dpi=150, bbox_inches="tight")
plt.show()
print("Gráfica guardada en ajuste_modelos.png")

MSE




predicho=[20,22,23,24]
real=[21,24,20,31]

total=0
for i in range(4):
    total+=(predicho[i]-real[i])**2
mse=total/4

print(mse)

import numpy as np
pred=np.array(predicho)
obs=np.array(real)

cuadraticos=(pred-real)**2
print(cuadraticos)
mse=np.mean(cuadraticos)
print(mse)

from sklearn.metrics import mean_squared_error
mse=mean_squared_error(real,pred)
print(mse)

Y más regresiones

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score


metros_cuadrados = [
    40, 42, 45, 48, 50, 52, 55, 58, 60, 62,
    65, 68, 70, 72, 75, 78, 80, 82, 85, 88,
    90, 92, 95, 98, 100, 102, 105, 108, 110, 112,
    115, 118, 120, 122, 125, 128, 130, 132, 135, 138,
    140, 142, 145, 148, 150, 152, 155, 158, 160, 165
]

precio_miles_euros = [
    120, 125, 128, 135, 140, 145, 150, 158, 160, 168,
    175, 180, 185, 190, 198, 205, 210, 215, 223, 230,
    235, 240, 248, 255, 260, 265, 273, 280, 285, 290,
    298, 305, 310, 315, 323, 330, 335, 340, 348, 355,
    360, 365, 373, 380, 385, 390, 398, 405, 410, 425
]

x = np.array(metros_cuadrados).reshape(-1, 1)
y = np.array(precio_miles_euros)

# Datos de entrenamiento y pruebas (split)

X_train, X_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=42)

# Crear modelo y entrenar (fit)

model = LinearRegression()
model.fit(X_train, y_train)

print(f"Pendiente {model.coef_} intercepto {model.intercept_}")

# Predecir datos de test

y_pred = model.predict(X_test)
print(y_test)
print(y_pred.round(0))

# Métricas de errores

print(mean_absolute_error(y_test, y_pred))
print(mean_squared_error(y_test, y_pred))
print(r2_score(y_test, y_pred))