W3docs

Regresión Polinomial

Aprende cómo funciona la regresión polinomial, cuándo usarla y cómo construir, evaluar y ajustar un modelo polinomial en Python con scikit-learn.

La regresión lineal traza una línea recta a través de los datos. Cuando la relación entre la entrada y la salida es curvilínea — piensa en la trayectoria de una pelota, el crecimiento de una colonia bacteriana o una curva de dosis-respuesta — una línea recta es un ajuste deficiente sin importar cuántas características añadas. La regresión polinomial resuelve esto añadiendo versiones elevadas a potencias de las características existentes (, , …) para que el modelo pueda curvarse y adaptarse a la curvatura de los datos, mientras sigue utilizando el mecanismo de mínimos cuadrados ordinarios internamente.

Esta página cubre:

  • Cómo la regresión polinomial extiende la regresión lineal matemáticamente
  • Cuándo usar la regresión polinomial y qué grado elegir
  • Un pipeline completo con scikit-learn: transformación de características, entrenamiento, evaluación
  • La trampa del sobreajuste y cómo detectarla con curvas de entrenamiento/prueba
  • Comparación de la calidad del modelo con RMSE y R²

Cómo Funciona la Regresión Polinomial

La Ecuación

La regresión lineal ajusta:

y = β₀ + β₁x

La regresión polinomial de grado n ajusta:

y = β₀ + β₁x + β₂x² + β₃x³ + … + βₙxⁿ

La clave está en que , , y así sucesivamente se tratan como características adicionales. El modelo sigue siendo lineal en sus coeficientes (valores β); solo las características son no lineales. Esto significa que los mínimos cuadrados ordinarios siguen funcionando — simplemente hay que preprocesar primero la matriz de entrada.

Grados y Su Significado

GradoNombreForma
1LinealLínea recta
2CuadráticoCurva simple (parábola)
3CúbicoUn punto de inflexión
4+Orden superiorCurvas más complejas

Elegir el grado correcto es la habilidad central. Un grado demasiado bajo hace que el modelo no ajuste bien (pierde la curvatura real). Un grado demasiado alto hace que el modelo sobreajuste (memoriza el ruido y falla con datos nuevos).

Cuándo Usar la Regresión Polinomial

Usa la regresión polinomial cuando:

  • Un diagrama de dispersión muestra una curva clara en los datos que una línea recta no puede capturar
  • Quieres una alternativa rápida e interpretable a los modelos basados en árboles para curvatura moderada
  • Ya tienes una regresión lineal base que no ajusta bien

Prefiere otros enfoques cuando:

  • La relación es muy compleja o tiene muchas características → prueba árboles de decisión o gradient boosting
  • Necesitas predecir muy fuera del rango de entrenamiento (extrapolación) → los polinomios de grado alto divergen drásticamente fuera del rango de los datos
  • Tienes muchas características de entrada → cada característica obtiene n términos polinomiales, por lo que la matriz de características crece rápidamente

Construyendo un Modelo de Regresión Polinomial

Paso 1: Importar Bibliotecas

import numpy as np
import matplotlib
matplotlib.use('Agg')  # non-interactive backend for scripts
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score

El transformador PolynomialFeatures de scikit-learn maneja automáticamente la expansión x → [1, x, x², …, xⁿ]. Envolverlo junto con LinearRegression en un Pipeline mantiene el código limpio y evita la fuga de datos durante la validación cruzada.

Paso 2: Crear Datos de Muestra

rng = np.random.default_rng(42)
X = rng.uniform(-3, 3, 80).reshape(-1, 1)   # 80 points from -3 to 3
y = 0.5 * X.ravel()**2 - X.ravel() + 2 + rng.normal(0, 0.5, 80)

# True relationship: y ≈ 0.5x² - x + 2  (a parabola with noise)

La relación subyacente es cuadrática, por lo que un polinomio de grado 2 debería recuperarla bien. Este es el tipo de situación donde la regresión lineal no ajusta sistemáticamente.

Paso 3: Dividir en Conjuntos de Entrenamiento y Prueba

Siempre divide antes de ajustar para tener datos separados con los que evaluar. Consulta Train/Test Split para una explicación completa.

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

print(f"Training samples: {len(X_train)}")  # 64
print(f"Test samples:     {len(X_test)}")   # 16

Paso 4: Construir y Ajustar el Pipeline

degree = 2
model = make_pipeline(
    PolynomialFeatures(degree=degree, include_bias=False),
    LinearRegression()
)
model.fit(X_train, y_train)

make_pipeline encadena los dos pasos: PolynomialFeatures transforma la entrada, luego LinearRegression ajusta sobre las características expandidas. Llamas a .fit(), .predict() y .score() sobre el pipeline exactamente como lo harías con un estimador simple.

Paso 5: Evaluar el Modelo

y_pred_train = model.predict(X_train)
y_pred_test  = model.predict(X_test)

rmse_train = np.sqrt(mean_squared_error(y_train, y_pred_train))
rmse_test  = np.sqrt(mean_squared_error(y_test,  y_pred_test))
r2_train   = r2_score(y_train, y_pred_train)
r2_test    = r2_score(y_test,  y_pred_test)

print(f"Train RMSE: {rmse_train:.4f}   Test RMSE: {rmse_test:.4f}")
print(f"Train R²:   {r2_train:.4f}   Test R²:   {r2_test:.4f}")

Salida esperada:

Train RMSE: 0.4605   Test RMSE: 0.4538
Train R²:   0.9480   Test R²:   0.9514

Un R² cercano a 0.95 en datos no vistos significa que el modelo explica aproximadamente el 95% de la varianza — un ajuste excelente para datos con este nivel de ruido. Las puntuaciones de entrenamiento y prueba son similares, lo que indica que el modelo generaliza en lugar de sobreajustar.

Interpretación de las métricas:

  • RMSE (Root Mean Squared Error) — el error promedio en las mismas unidades que y. Cuanto más bajo, mejor.
  • R² (coeficiente de determinación) — fracción de varianza explicada. Los valores más cercanos a 1.0 son mejores; 0 significa que el modelo no es mejor que predecir la media.

Paso 6: Visualizar el Ajuste

X_line = np.linspace(-3.5, 3.5, 200).reshape(-1, 1)
y_line = model.predict(X_line)

plt.figure(figsize=(7, 5))
plt.scatter(X_train, y_train, alpha=0.6, label='Train', color='steelblue', s=25)
plt.scatter(X_test,  y_test,  alpha=0.8, label='Test',  color='orange',    s=40, zorder=5)
plt.plot(X_line, y_line, color='red', linewidth=2, label=f'Degree-{degree} fit')
plt.xlabel('x')
plt.ylabel('y')
plt.title('Polynomial Regression (degree 2)')
plt.legend()
plt.tight_layout()
plt.savefig('poly_reg_fit.png', dpi=120)
print("Plot saved.")

La curva roja debería atravesar suavemente la dispersión — lo suficientemente cerca como para capturar la forma parabólica sin zigzaguear a través de los puntos de ruido individuales.

La Trampa del Sobreajuste

El mayor riesgo de la regresión polinomial es elegir un grado demasiado alto. Un polinomio de grado 15 puede memorizar perfectamente los 80 puntos de entrenamiento (RMSE ≈ 0) pero oscilará drásticamente entre ellos y fallará con datos nuevos.

El diagnóstico estándar es una curva de validación: traza el error de entrenamiento y prueba en función del grado.

degrees = range(1, 12)
train_rmses, test_rmses = [], []

for d in degrees:
    m = make_pipeline(PolynomialFeatures(d, include_bias=False), LinearRegression())
    m.fit(X_train, y_train)
    train_rmses.append(np.sqrt(mean_squared_error(y_train, m.predict(X_train))))
    test_rmses.append(np.sqrt(mean_squared_error(y_test,  m.predict(X_test))))

plt.figure(figsize=(7, 4))
plt.plot(degrees, train_rmses, 'o-', label='Train RMSE', color='steelblue')
plt.plot(degrees, test_rmses,  's-', label='Test RMSE',  color='orange')
plt.xlabel('Polynomial Degree')
plt.ylabel('RMSE')
plt.title('Validation Curve: Choosing the Best Degree')
plt.legend()
plt.tight_layout()
plt.savefig('poly_reg_validation_curve.png', dpi=120)
print("Plot saved.")

Lo que verás:

  • Grado 1 (lineal): tanto el RMSE de entrenamiento como el de prueba son altos — subajuste.
  • Grado 2: ambos caen bruscamente — el modelo captura la forma real.
  • Grado 5+: el RMSE de entrenamiento sigue bajando, pero el RMSE de prueba sube — la brecha entre entrenamiento y prueba indica sobreajuste.

El mejor grado es donde el RMSE de prueba es más bajo antes de que comience a subir de nuevo. En este ejemplo es el grado 2, que coincide con el verdadero proceso generador de datos.

Para una selección de grado más robusta, usa validación cruzada en lugar de una única división entrenamiento/prueba.

Usando numpy.polyfit (Alternativa Rápida)

Para problemas univariados simples, la función polyfit de NumPy ofrece un ajuste en una sola línea sin scikit-learn. Es útil para análisis exploratorio pero no se integra con pipelines ni con validación cruzada.

import numpy as np

x = np.array([1, 2, 3, 4, 5], dtype=float)
y = np.array([2.1, 4.0, 9.2, 16.1, 25.0])

# Fit a degree-2 polynomial
# Returns coefficients from highest to lowest degree: [a2, a1, a0]
coefficients = np.polyfit(x, y, 2)
poly = np.poly1d(coefficients)

print("Coefficients (highest to lowest degree):", np.round(coefficients, 3))
print("Prediction at x=6:", round(poly(6), 2))

Salida esperada:

Coefficients (highest to lowest degree): [ 1.121 -0.939  1.76 ]
Prediction at x=6: 36.5

La curva ajustada es aproximadamente y ≈ 1.12x² - 0.94x + 1.76, que se acerca a la verdadera relación y = x² en estos datos. Los coeficientes no son exactamente [1, 0, 0] porque solo hay cinco puntos de datos ruidosos para ajustar.

np.poly1d envuelve los coeficientes para que puedas llamar al polinomio como una función: poly(6) evalúa 1.121(36) - 0.939(6) + 1.76 ≈ 36.5.

Pipeline Completo (Todos los Pasos Juntos)

import numpy as np
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score

# 1. Generate data (true relationship: y = 0.5x² - x + 2 + noise)
rng = np.random.default_rng(42)
X = rng.uniform(-3, 3, 80).reshape(-1, 1)
y = 0.5 * X.ravel()**2 - X.ravel() + 2 + rng.normal(0, 0.5, 80)

# 2. Split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 3. Build and fit
model = make_pipeline(PolynomialFeatures(degree=2, include_bias=False), LinearRegression())
model.fit(X_train, y_train)

# 4. Evaluate
rmse = np.sqrt(mean_squared_error(y_test, model.predict(X_test)))
r2   = r2_score(y_test, model.predict(X_test))
print(f"Test RMSE: {rmse:.4f}")
print(f"Test R²:   {r2:.4f}")

# 5. Inspect learned coefficients
lr = model.named_steps['linearregression']
pf = model.named_steps['polynomialfeatures']
feature_names = pf.get_feature_names_out(['x'])
for name, coef in zip(feature_names, lr.coef_):
    print(f"  {name}: {coef:.4f}")
print(f"  intercept: {lr.intercept_:.4f}")

Salida esperada:

Test RMSE: 0.4538
Test R²:   0.9514
  x: -0.9625
  x^2: 0.4909
  intercept: 1.9654

Los coeficientes recuperados (x^2 ≈ 0.49, x ≈ -0.96, intercepto ≈ 1.97) coinciden estrechamente con los valores reales (0.5, -1, 2), lo que confirma que el modelo ha aprendido la forma correcta.

Escalado de Características y Regresión Polinomial

Al trabajar con polinomios de grado alto, los valores de las características crecen rápidamente — x = 100 produce x² = 10,000 y x³ = 1,000,000. Esto puede hacer que el problema de mínimos cuadrados sea numéricamente inestable.

La solución estándar es escalar las características con StandardScaler dentro del pipeline, antes de la expansión polinomial:

from sklearn.preprocessing import StandardScaler

model = make_pipeline(
    StandardScaler(),
    PolynomialFeatures(degree=3, include_bias=False),
    LinearRegression()
)

Colocar StandardScaler primero significa que se ajusta solo con los datos de entrenamiento y se aplica de forma consistente a los datos de prueba — sin fuga de datos. Consulta Feature Scaling para más detalles sobre por qué el escalado importa.

Errores Comunes

Omitir la división entrenamiento/prueba. Si ajustas y evalúas con los mismos datos, los polinomios de grado alto parecerán funcionar perfectamente mientras fallan completamente con nuevas entradas. Siempre evalúa con datos separados.

Extrapolar fuera del rango de entrenamiento. Las curvas polinomiales oscilan y divergen fuera del rango de los datos de entrenamiento. Un modelo entrenado con x ∈ [0, 5] puede dar predicciones absurdas en x = 10. La regresión lineal extrapola de forma más conservadora.

No escalar antes de los términos de grado alto. Los valores grandes de características combinados con una expansión de grado alto pueden producir desbordamiento numérico o matrices mal condicionadas. Usa StandardScaler en el pipeline.

Elegir el grado por el error de entrenamiento únicamente. El RMSE de entrenamiento siempre disminuye al aumentar el grado. Usa el RMSE de prueba o la validación cruzada para encontrar el grado que generaliza mejor.

Olvidar include_bias=False. PolynomialFeatures añade una columna constante (término de intercepto) por defecto. LinearRegression también añade su propio intercepto. Pasar include_bias=False a PolynomialFeatures evita la columna redundante.

Próximos Pasos

  • Regresión Lineal — la base de línea recta sobre la que se construye la regresión polinomial
  • Regresión Múltiple — combina muchas características (la regresión polinomial aplicada a múltiples entradas también produce términos de interacción)
  • Train/Test Split — la forma correcta de evaluar cualquier modelo de regresión
  • Validación Cruzada — más robusta que una única división para ajustar el grado polinomial
  • Feature Scaling — por qué y cómo estandarizar las entradas antes de ajustar
Was this page helpful?