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

Ejercicio regresión

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)

# Crear modelo y entrenar (fit)

# Predecir datos de test

# Métricas de errores
 

Ejemplo regresión

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

anuncios=[1,2,3,4,5]
# Datos perfectos
ventas=[5,8,11,14,17]
# Datos más reales
ventas=[5,7,12,14,16]


x = np.array(anuncios).reshape(-1, 1)
y = np.array(ventas)

modelo = LinearRegression()
modelo.fit(x, y)

print(f"Coeficiente {modelo.coef_} intercepto {modelo.intercept_}")

fig, axes = plt.subplots(1, 1, figsize=(10, 8))

axes.scatter(anuncios, ventas, color="steelblue", label="Datos")
axes.plot(x, modelo.predict(x), "r-", label="Modelo")
axes.set_title("Relación entre anuncios y ventas")
axes.set_xlabel("Anuncios"); axes.set_ylabel("Ventas")
axes.legend()

plt.tight_layout()
plt.show()

Probar regresión

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

# DATOS

x = [
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
    11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
    21, 22, 23, 24, 25, 26, 27, 28, 29, 30
]

y = [
    96, 92, 91, 87, 86, 82, 80, 78, 74, 72,
    69, 68, 64, 61, 60, 56, 54, 51, 49, 45,
    43, 40, 38, 35, 33, 29, 28, 24, 23, 19
]
x = np.array(x).reshape(-1, 1)
y = np.array(y)

# SPLIT

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

# ENTRENAMIENTO

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


# PREDICCION

y_pred = modelo.predict(X_test)

mae  = mean_absolute_error(y_test, y_pred)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2   = r2_score(y_test, y_pred)

print(f"\nResultados en el conjunto de TEST:")
print(f"  MAE  = {mae:.3f}   (error medio absoluto)")
print(f"  RMSE = {rmse:.3f}  (raiz error cuadratico)")
print(f"  R2   = {r2:.3f}   (coef. determinacion)")

Ejemplo regresión lineal

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

# 1. DATOS: horas de estudio y notas de 20 estudiantes

horas = np.array([[ 1.        ],  [ 1.47368421],  [ 1.94736842],  [ 2.42105263],  [ 2.89473684],  [ 3.36842105],  [ 3.84210526],  [ 4.31578947],  [ 4.78947368],  [ 5.26315789],  [ 5.73684211],  [ 6.21052632]
,  [ 6.68421053],  [ 7.15789474],  [ 7.63157895],  [ 8.10526316],  [ 8.57894737],  [ 9.05263158],  [ 9.52631579],  [10.        ]])
notas = np.array([ 2.84835708, 2.95718364, 3.77647585, 4.6404623, 4.18818647, 4.61451047
, 5.94750114, 5.96792789, 5.77578912, 6.70812212, 6.63144905, 7.05660881
, 7.83677061, 7.18546514, 7.70596214, 8.71359308, 8.91463707, 10.00449209
, 9.81967217, 9.99384815])

# 2. DIVISION TRAIN / TEST (70% entrenamiento, 30% test)
X_train, X_test, y_train, y_test = train_test_split(
    horas, notas, test_size=0.3,random_state=42
)

print(f"Datos de entrenamiento: {len(X_train)} muestras")
print(f"Datos de test:          {len(X_test)} muestras")

# 3. ENTRENAMIENTO: el modelo aprende con X_train
modelo = LinearRegression()
modelo.fit(X_train, y_train)          # Aqui ocurre el aprendizaje

print(f"\nEcuacion aprendida:")
print(f"  nota = {modelo.coef_[0]:.3f} * horas + {modelo.intercept_:.3f}")

# 4. PREDICCION en datos de test (datos nunca vistos)
y_pred = modelo.predict(X_test)

# 5. EVALUACION con metricas
mae  = mean_absolute_error(y_test, y_pred)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2   = r2_score(y_test, y_pred)

print(f"\nResultados en el conjunto de TEST:")
print(f"  MAE  = {mae:.3f}   (error medio absoluto)")
print(f"  RMSE = {rmse:.3f}  (raiz error cuadratico)")
print(f"  R2   = {r2:.3f}   (coef. determinacion)")

# 6. VISUALIZACION: entrenamiento vs test
fig, axes = plt.subplots(1, 2, figsize=(12, 5))

axes[0].scatter(X_train, y_train, color="steelblue", label="Train")
axes[0].plot(horas, modelo.predict(horas), "r-", label="Modelo")
axes[0].set_title("Fase de Entrenamiento")
axes[0].set_xlabel("Horas"); axes[0].set_ylabel("Nota")
axes[0].legend()

axes[1].scatter(X_test, y_test, color="orange", label="Test")
axes[1].plot(horas, modelo.predict(horas), "r-", label="Modelo")
axes[1].set_title(f"Fase de Test  (R2={r2:.2f})")
axes[1].set_xlabel("Horas"); axes[1].set_ylabel("Nota")
axes[1].legend()

plt.tight_layout()
plt.show()

Ejemplos pandas

ventas_alimentacion

import pandas as pd

df = pd.read_csv("ventas_alimentacion.csv", index_col=0)
print(df)

# Estadísticas por ciudad
print(df.sum())    # total ventas por ciudad
print(df.mean())   # media por ciudad
print(df.max())    # máximo por ciudad


datos = {
    "Madrid":    [12450, 8760, 11230, 9340, 14560, 10870, 6540, 7890],
    "Barcelona": [9870,  7540, 8650,  7120, 11230, 12340, 5120, 6540],
    "Valencia":  [7340,  5980, 6780,  8450, 8760,  7650,  4870, 5230],
    "Sevilla":   [8920,  6120, 7450,  9210, 9870,  8430,  5340, 4980],
    "Zaragoza":  [4120,  3870, 4560,  3980, 5120,  3760,  2980, 3240],
    "Bilbao":    [5630,  4920, 6120,  4760, 7340,  9120,  3870, 4560],
    "Málaga":    [6780,  4560, 5340,  7890, 6540,  8760,  4120, 3870],
    "Murcia":    [3940,  3210, 4120,  5340, 4870,  4230,  2760, 2980],
}

indice = [
    "Aceite de oliva",
    "Pan y bollería",
    "Leche y lácteos",
    "Frutas y verduras",
    "Carne y charcutería",
    "Pescado y marisco",
    "Pasta, arroz y legumbres",
    "Conservas y enlatados",
]

df = pd.DataFrame(datos, index=indice)
print(df)

import pandas as pd

# ─────────────────────────────────────────────
#  DATAFRAME DE EJEMPLO
# ─────────────────────────────────────────────
data = {
    "Madrid":    [12450, 8760, 11230,  9340, 14560, 10870],
    "Barcelona": [ 9870, 7540,  8650,  7120, 11230, 12340],
    "Valencia":  [ 7340, 5980,  6780,  8450,  8760,  7650],
    "Sevilla":   [ 8920, 6120,  7450,  9210,  9870,  8430],
    "Bilbao":    [ 5630, 4920,  6120,  4760,  7340,  9120],
    "Murcia":    [ 3940, 3210,  4120,  5340,  4870,  4230],
}

indice = [
    "Aceite de oliva",
    "Pan y bollería",
    "Leche y lácteos",
    "Frutas y verduras",
    "Carne y charcutería",
    "Pescado y marisco",
]

df = pd.DataFrame(data, index=indice)

print(df)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 1 — .loc[]  →  celda exacta por nombre
# ══════════════════════════════════════════════════════════════════
# .loc usa ETIQUETAS (nombres reales de filas y columnas).
# Sintaxis: df.loc["nombre_fila", "nombre_columna"]
resultado = df.loc["Leche y lácteos", "Madrid"]
print("1) .loc celda exacta:")
print(resultado)          # 11230
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 2 — .iloc[]  →  celda exacta por posición numérica
# ══════════════════════════════════════════════════════════════════
# .iloc usa POSICIONES enteras (0-based, igual que las listas).
# Fila 1 = "Pan y bollería", columna 2 = "Valencia"
resultado = df.iloc[1, 2]
print("2) .iloc celda exacta:")
print(resultado)          # 5980
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 3 — df["columna"]  →  columna completa (una Serie)
# ══════════════════════════════════════════════════════════════════
# Con un solo corchete y el nombre de columna obtienes una Serie.
# La Serie conserva el índice original del DataFrame.
resultado = df["Barcelona"]
print("3) Columna completa con df['col']:")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 4 — .loc["fila"]  →  fila completa por nombre
# ══════════════════════════════════════════════════════════════════
# Al pasar solo un nombre de fila, .loc devuelve una Serie
# donde el índice son los nombres de las columnas.
resultado = df.loc["Frutas y verduras"]
print("4) Fila completa con .loc:")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 5 — .iloc[:, col]  →  columna completa por posición
# ══════════════════════════════════════════════════════════════════
# El ":" significa "todas las filas".
# La columna 3 corresponde a "Sevilla" (0=Madrid, 1=Barcelona, ...)
resultado = df.iloc[:, 3]
print("5) Columna por posición con .iloc[:, 3]:")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 6 — .loc[inicio:fin]  →  rango de filas por nombre
# ══════════════════════════════════════════════════════════════════
# Con .loc los rangos son INCLUSIVOS en ambos extremos,
# es decir, se incluyen tanto "Pan y bollería" como "Frutas y verduras".
resultado = df.loc["Pan y bollería":"Frutas y verduras"]
print("6) Rango de filas con .loc (inclusivo):")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 7 — .iloc[inicio:fin]  →  rango de filas por posición
# ══════════════════════════════════════════════════════════════════
# Con .iloc los rangos son EXCLUSIVOS en el extremo derecho,
# igual que los slices de Python. iloc[2:5] devuelve filas 2, 3 y 4.
resultado = df.iloc[2:5]
print("7) Rango de filas con .iloc (exclusivo en el extremo derecho):")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 8 — .iloc[f1:f2, c1:c2]  →  submatriz por posición
# ══════════════════════════════════════════════════════════════════
# Selecciona filas 0, 1, 2  (0:3 → exclusivo en 3)
# y columnas 1, 2, 3        (1:4 → exclusivo en 4)
resultado = df.iloc[0:3, 1:4]
print("8) Submatriz con .iloc[0:3, 1:4]:")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 9 — .loc[rango_filas, lista_columnas]
# ══════════════════════════════════════════════════════════════════
# Combina un rango de filas (por nombre) con una lista de columnas
# específicas. Muy útil para extraer subconjuntos concretos.
resultado = df.loc["Aceite de oliva":"Leche y lácteos", ["Madrid", "Bilbao"]]
print("9) Rango de filas + columnas específicas con .loc:")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 10 — df[df["col"] > valor]  →  filtrado booleano
# ══════════════════════════════════════════════════════════════════
# La expresión df["Madrid"] > 10000 genera una Serie de True/False.
# Al pasarla al DataFrame, devuelve solo las filas donde es True.
resultado = df[df["Madrid"] > 10000]
print("10) Máscara booleana — ventas en Madrid > 10.000 €:")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 11 — .loc[condición, columnas]  →  filtro + selección
# ══════════════════════════════════════════════════════════════════
# Combina un filtro booleano con selección de columnas concretas.
# Primero filtra las filas y luego escoge qué columnas mostrar.
resultado = df.loc[df["Barcelona"] > 9000, ["Barcelona", "Madrid"]]
print("11) .loc con condición y selección de columnas:")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 12 — df[["c1", "c2", ...]]  →  varias columnas a la vez
# ══════════════════════════════════════════════════════════════════
# Con DOBLE corchete se pasa una lista de nombres.
# El resultado es un DataFrame (no una Serie), aunque elijas 1 columna.
resultado = df[["Madrid", "Sevilla", "Murcia"]]
print("12) Varias columnas con doble corchete:")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 13 — .iat[]  →  celda por posición (versión rápida)
# ══════════════════════════════════════════════════════════════════
# .iat es equivalente a .iloc para UN ÚNICO valor escalar.
# Es más eficiente que .iloc en DataFrames grandes porque
# no construye objetos intermedios.
resultado = df.iat[4, 0]
print("13) .iat — celda por posición (rápido):")
print(resultado)          # 14560  (fila 4 = "Carne", col 0 = "Madrid")
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 14 — .at[]  →  celda por etiqueta (versión rápida)
# ══════════════════════════════════════════════════════════════════
# .at es equivalente a .loc para UN ÚNICO valor escalar.
# Más rápido que .loc cuando solo necesitas un dato concreto.
resultado = df.at["Pescado y marisco", "Bilbao"]
print("14) .at — celda por etiqueta (rápido):")
print(resultado)          # 9120

import pandas as pd

# ─────────────────────────────────────────────
#  DATAFRAME DE EJEMPLO
# ─────────────────────────────────────────────
data = {
    "Madrid":    [12450, 8760, 11230,  9340, 14560, 10870],
    "Barcelona": [ 9870, 7540,  8650,  7120, 11230, 12340],
    "Valencia":  [ 7340, 5980,  6780,  8450,  8760,  7650],
    "Sevilla":   [ 8920, 6120,  7450,  9210,  9870,  8430],
    "Bilbao":    [ 5630, 4920,  6120,  4760,  7340,  9120],
    "Murcia":    [ 3940, 3210,  4120,  5340,  4870,  4230],
}

indice = [
    "Aceite de oliva",
    "Pan y bollería",
    "Leche y lácteos",
    "Frutas y verduras",
    "Carne y charcutería",
    "Pescado y marisco",
]

df = pd.DataFrame(data, index=indice)

print(df)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 1 — df["col"] > valor  →  máscara booleana simple
# ══════════════════════════════════════════════════════════════════
# Una comparación sobre una columna devuelve una Serie de True/False.
# Al pasarla al DataFrame, actúa como "filtro de filas".
# Solo se muestran las filas donde la condición es verdadera.
mascara = df["Madrid"] > 10000
resultado = df[mascara]
print("1) Máscara booleana simple — Madrid > 10 000 €:")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 2 — & (AND)  →  dos condiciones simultáneas
# ══════════════════════════════════════════════════════════════════
# Para combinar condiciones usa & (AND) o | (OR).
# IMPORTANTE: cada condición debe ir entre paréntesis porque
# & tiene mayor precedencia que > y <.
resultado = df[(df["Madrid"] > 9000) & (df["Barcelona"] > 9000)]
print("2) Dos condiciones con & (AND) — Madrid > 9 000 y Barcelona > 9 000:")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 3 — | (OR)  →  al menos una condición verdadera
# ══════════════════════════════════════════════════════════════════
# Con | basta con que UNA de las condiciones sea True.
# Aquí: filas donde Bilbao supera 8 000 O Murcia supera 5 000.
resultado = df[(df["Bilbao"] > 8000) | (df["Murcia"] > 5000)]
print("3) Dos condiciones con | (OR) — Bilbao > 8 000 o Murcia > 5 000:")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 4 — ~  →  negación de una máscara
# ══════════════════════════════════════════════════════════════════
# El operador ~ invierte una máscara booleana (True → False y viceversa).
# Equivale al NOT lógico: devuelve las filas que NO cumplen la condición.
resultado = df[~(df["Valencia"] > 7000)]
print("4) Negación con ~ — filas donde Valencia NO supera 7 000 €:")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 5 — .isin()  →  filtrar por lista de valores
# ══════════════════════════════════════════════════════════════════
# .isin(lista) devuelve True en las filas cuyo valor está en la lista.
# Muy útil cuando tienes un conjunto de etiquetas permitidas.
categorias_interes = ["Aceite de oliva", "Carne y charcutería", "Pescado y marisco"]
resultado = df[df.index.isin(categorias_interes)]
print("5) .isin() — solo categorías de interés:")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 6 — .query()  →  filtrar con expresión de texto
# ══════════════════════════════════════════════════════════════════
# .query() acepta una cadena de texto con la condición en lenguaje
# casi natural. Más legible para condiciones complejas.
# Nota: si el nombre de columna tiene espacios, usa backticks (`col`).
resultado = df.query("Madrid > 10000 and Sevilla > 8000")
print("6) .query() — Madrid > 10 000 y Sevilla > 8 000:")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 7 — .filter(items=)  →  seleccionar columnas por nombre
# ══════════════════════════════════════════════════════════════════
# .filter() selecciona columnas (o filas con axis=0) por nombre exacto.
# No filtra por valores, sino por NOMBRE de columna.
resultado = df.filter(items=["Madrid", "Barcelona", "Murcia"])
print("7) .filter(items=) — columnas Madrid, Barcelona y Murcia:")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 8 — .filter(like=)  →  columnas cuyo nombre contiene texto
# ══════════════════════════════════════════════════════════════════
# like= selecciona columnas cuyo nombre CONTIENE la subcadena dada.
# axis=1 indica que buscamos en columnas; axis=0 buscaría en el índice.
resultado = df.filter(like="a", axis=1)   # columnas que contienen "a"
print("8) .filter(like='a') — columnas que contienen la letra 'a':")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 9 — .filter(regex=)  →  columnas por expresión regular
# ══════════════════════════════════════════════════════════════════
# regex= permite filtrar columnas cuyo nombre cumple un patrón regex.
# Aquí seleccionamos columnas que empiezan por 'M', 'B' o 'S'.
resultado = df.filter(regex="^(M|B|S)", axis=1)
print("9) .filter(regex=) — columnas que empiezan por M, B o S:")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 10 — .loc[condición, columnas]  →  filtro fila + columnas
# ══════════════════════════════════════════════════════════════════
# .loc admite una máscara booleana como selector de filas Y una lista
# de columnas como segundo argumento. El resultado es un sub-DataFrame.
resultado = df.loc[df["Bilbao"] > 6000, ["Bilbao", "Madrid", "Murcia"]]
print("10) .loc con condición + columnas — Bilbao > 6 000, ver Bilbao/Madrid/Murcia:")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 11 — .where()  →  mantiene forma, enmascara con NaN
# ══════════════════════════════════════════════════════════════════
# A diferencia de los filtros anteriores, .where() conserva el shape
# original del DataFrame. Las celdas que NO cumplen la condición
# se sustituyen por NaN (o por el valor que indiques en `other=`).
resultado = df.where(df > 8000)
print("11) .where(df > 8000) — valores < 8 000 sustituidos por NaN:")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 12 — .mask()  →  inverso de .where()
# ══════════════════════════════════════════════════════════════════
# .mask() es el opuesto de .where(): enmascara con NaN las celdas
# que SÍ cumplen la condición, manteniendo las que no la cumplen.
resultado = df.mask(df > 8000)
print("12) .mask(df > 8000) — valores > 8 000 sustituidos por NaN:")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 13 — filtro por índice con .str  →  texto en el índice
# ══════════════════════════════════════════════════════════════════
# Si el índice es de tipo cadena, puedes usar .str para filtrarlo.
# .str.contains() busca una subcadena; .str.startswith() un prefijo.
resultado = df[df.index.str.contains("y")]
print("13) Filtro de texto en el índice — categorías que contienen 'y':")
print(resultado)
print()


# ══════════════════════════════════════════════════════════════════
#  MÉTODO 14 — .nlargest() / .nsmallest()  →  top N por columna
# ══════════════════════════════════════════════════════════════════
# .nlargest(n, columna) devuelve las n filas con mayor valor en
# la columna indicada, ordenadas de mayor a menor.
# .nsmallest(n, columna) hace lo mismo para los menores valores.
resultado = df.nlargest(3, "Madrid")
print("14) .nlargest(3, 'Madrid') — top 3 categorías en Madrid:")
print(resultado)
print()

resultado = df.nsmallest(3, "Murcia")
print("    .nsmallest(3, 'Murcia') — bottom 3 categorías en Murcia:")
print(resultado)
print()