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

Ejercicio coches

"""
╔══════════════════════════════════════════════════════════════════╗
║   EJERCICIO: "NOS HAN DICHO QUE LOS COCHES MÁS POTENTES        ║
║               GASTAN MÁS COMBUSTIBLE... ¿ES CIERTO?"            ║
╚══════════════════════════════════════════════════════════════════╝

LA SITUACIÓN
────────────
  Un amigo mecánico te dice:
  "Oye, está clarísimo: cuantos más caballos tiene un coche,
   más combustible gasta. Siempre ha sido así."

  ¿Tiene razón? ¿Podemos demostrarlo (o refutarlo) con datos?

  En este ejercicio vas a:
    1. Cargar un dataset real de coches
    2. Entender qué contiene
    3. Limpiarlo correctamente
    4. Calcular si realmente existe esa relación
    5. Visualizarla
    6. Responder preguntas para interpretar los resultados

EL DATASET: MPG (Miles Per Gallon)
────────────────────────────────────
  Contiene información de 398 coches fabricados entre 1970 y 1982.
  Fue recopilado originalmente por la revista Consumer Reports y
  es uno de los datasets clásicos del aprendizaje automático.

  Columnas principales:
    mpg          → millas por galón (eficiencia: más = gasta MENOS)
    cylinders    → número de cilindros del motor
    displacement → cilindrada (pulgadas cúbicas)
    horsepower   → caballos de potencia (HP)
    weight       → peso del coche (libras)
    acceleration → aceleración (segundos de 0 a 60 mph)
    model_year   → año del modelo (70 = 1970, 82 = 1982)
    origin       → origen (1=EEUU, 2=Europa, 3=Japón)
    name         → nombre del modelo

  PREGUNTA A INVESTIGAR:
    ¿Existe relación entre los caballos de potencia (horsepower)
    y el consumo de combustible (mpg)?

Librerías necesarias:
  pip install pandas matplotlib seaborn
"""

import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import seaborn as sns
import numpy as np
from scipy import stats


# ═══════════════════════════════════════════════════════════════════
#  PASO 1 — CARGA DE DATOS
# ═══════════════════════════════════════════════════════════════════
# Cargamos el dataset "mpg" directamente desde seaborn.
# seaborn incluye varios datasets de ejemplo listos para usar;
# no hace falta descargar ningún archivo manualmente.

print("\n" + "═"*60)
print("  PASO 1 — CARGA DE DATOS")
print("═"*60)

coches = sns.load_dataset("mpg")

# Comprobamos que se ha cargado correctamente: cuántas filas y columnas
print(f"\n  Filas    : {coches.shape[0]}")
print(f"  Columnas : {coches.shape[1]}")
print(f"  Nombres  : {coches.columns.tolist()}")


# ═══════════════════════════════════════════════════════════════════
#  PASO 2 — PRIMER VISTAZO
# ═══════════════════════════════════════════════════════════════════
# Antes de analizar nada, siempre hay que MIRAR los datos.
# Es como abrir una caja antes de saber qué hay dentro.

print("\n" + "═"*60)
print("  PASO 2 — PRIMER VISTAZO")
print("═"*60)

print("\n── .head(): primeras 5 filas ──────────────────────────────")
print(coches.head())

print("\n── .tail(): últimas 5 filas ───────────────────────────────")
print(coches.tail())

print("\n── .dtypes: tipo de dato de cada columna ──────────────────")
print(coches.dtypes)
# ATENCIÓN: fíjate en el tipo de 'horsepower'. ¿Es numérico?
# Si aparece como 'object' en lugar de 'float64', significa que
# pandas lo ha interpretado como texto.

print("\n── .describe(): estadísticas rápidas ──────────────────────")
print(coches.describe().round(2))
# describe() solo muestra columnas numéricas por defecto.
# Si 'horsepower' no aparece aquí, confirma que es texto.


# ═══════════════════════════════════════════════════════════════════
#  PASO 3 — ANÁLISIS DE CALIDAD: ¿ESTÁN BIEN LOS DATOS?
# ═══════════════════════════════════════════════════════════════════
# Los datos reales casi nunca son perfectos. Hay que buscar:
#   a) Valores nulos (celdas vacías)
#   b) Tipos de datos incorrectos
#   c) Valores extraños o imposibles

print("\n" + "═"*60)
print("  PASO 3 — ANÁLISIS DE CALIDAD DE LOS DATOS")
print("═"*60)

# ── a) Valores nulos ────────────────────────────────────────────
print("\n── a) ¿Cuántos valores nulos hay por columna? ─────────────")
nulos = coches.isnull().sum()
print(nulos)
print(f"\n  Total de celdas nulas: {nulos.sum()}")

print("\n── c) Rango de 'mpg' ──────────────────────────────────────")
print(f"  Mínimo: {coches['mpg'].min():.1f}  |  Máximo: {coches['mpg'].max():.1f}")


# ═══════════════════════════════════════════════════════════════════
#  PASO 4 — LIMPIEZA DE DATOS
# ═══════════════════════════════════════════════════════════════════
# Ahora que sabemos los problemas, los corregimos uno a uno.
# Es importante hacerlo en un paso separado y documentado.

print("\n" + "═"*60)
print("  PASO 4 — LIMPIEZA DE DATOS")
print("═"*60)

# Hacemos una copia para no modificar el dataset original
# (buena práctica: siempre trabajar sobre una copia)
df = coches.copy()

# ── Corrección 2: eliminar filas con NaN en las columnas clave ───
# Solo nos importan 'mpg' y 'horsepower' para este análisis.
# dropna(subset=[...]) elimina solo las filas con NaN en esas columnas.
filas_antes = len(df)
df = df.dropna(subset=["mpg", "horsepower"])
filas_despues = len(df)

print(f"  ✓ Filas eliminadas con NaN: {filas_antes - filas_despues}")
print(f"  ✓ Filas disponibles para el análisis: {filas_despues}")

# ── Verificación final ───────────────────────────────────────────
print(f"\n  Nulos restantes en 'mpg'       : {df['mpg'].isnull().sum()}")
print(f"  Nulos restantes en 'horsepower': {df['horsepower'].isnull().sum()}")
print("\n  Dataset limpio y listo para analizar.")


# ═══════════════════════════════════════════════════════════════════
#  PASO 5 — ESTADÍSTICAS DESCRIPTIVAS DE LAS VARIABLES CLAVE
# ═══════════════════════════════════════════════════════════════════
# Antes de calcular la correlación, conoce bien cada variable
# por separado. ¿En qué rango están? ¿Hay mucha variación?

print("\n" + "═"*60)
print("  PASO 5 — ESTADÍSTICAS DE LAS VARIABLES CLAVE")
print("═"*60)

for col, nombre in [("mpg", "Eficiencia (mpg)"),
                    ("horsepower", "Potencia (HP)")]:
    s = df[col]
    print(f"\n  {nombre}:")
    print(f"    Media         : {s.mean():.1f}")
    print(f"    Mediana       : {s.median():.1f}")
    print(f"    Desv. típica  : {s.std():.1f}")
    print(f"    Mínimo        : {s.min():.1f}")
    print(f"    Máximo        : {s.max():.1f}")


# ═══════════════════════════════════════════════════════════════════
#  PASO 6 — CALCULAR LA CORRELACIÓN
# ═══════════════════════════════════════════════════════════════════
# Ahora sí: calculamos el coeficiente de Pearson entre
# 'horsepower' y 'mpg'.
#
# RECORDATORIO:
#   r cercano a +1 → relación positiva fuerte
#   r cercano a -1 → relación negativa fuerte
#   r cercano a  0 → sin relación lineal clara
#
# OJO: mpg mide eficiencia (más mpg = MENOS consumo).
# Si el amigo tiene razón, esperamos r NEGATIVO:
# más HP → menos mpg (peor eficiencia = más consumo).

print("\n" + "═"*60)
print("  PASO 6 — CORRELACIÓN DE PEARSON")
print("═"*60)

r,p=stats.pearsonr(df["mpg"], df["horsepower"])
print(f"La correlación entre potencia y eficiencia es {r:.2f} con una significación de {p:.2f}.")

# ═══════════════════════════════════════════════════════════════════
#  PASO 7 — PREGUNTAS INTERPRETATIVAS
# ═══════════════════════════════════════════════════════════════════
# Lee los resultados con atención antes de responder.

print("\n" + "═"*60)
print("  PASO 8 — PREGUNTAS PARA REFLEXIONAR")
print("═"*60)
print(f"""
  Antes de responder, fíjate en:
    • El valor de r y su signo

  ──────────────────────────────────────────────────────────
  BLOQUE A — Sobre los datos

  1. ¿Cuántos coches tenía el dataset original?
     ¿Cuántos se han eliminado en la limpieza y por qué?
     ¿Crees que esa pérdida afecta al análisis?



  ──────────────────────────────────────────────────────────
  BLOQUE B — Sobre la correlación

  4. El coeficiente r 
     ¿Es positivo o negativo? ¿Qué significa eso en este contexto?
     (Recuerda: mpg alto = gasta POCO; mpg bajo = gasta MUCHO)

  5. ¿Tenía razón el amigo mecánico?
     Formula la respuesta con datos concretos.

  6. R² 
     Eso significa que la potencia (HP) explica el ___% de las
     diferencias de consumo entre coches.
     ¿Qué otros factores podrían explicar el resto?

  7. El p-valor es 
     ¿Podemos confiar en este resultado o podría ser coincidencia?

  

  ──────────────────────────────────────────────────────────
  BLOQUE D — Reflexión final

  11. "Correlación no implica causalidad."
      ¿Puedes pensar en una variable que esté relacionada tanto
      con la potencia como con el consumo y que pudiera estar
      "confundiendo" la relación? (Pista: mira las columnas del
      dataset)

  12. Si quisieras PREDECIR el consumo de un coche nuevo del que
      solo sabes los HP, ¿usarías este modelo?
      ¿Qué columnas añadirías para mejorar la predicción?
""")