Skip to content

Regresión lineal con Sklearn: guía completa con ejemplos en Python

Updated on

Tienes un dataset con features y una variable objetivo continua. Quieres predecir resultados —precios de vivienda, ingresos por ventas, tendencias de temperatura— pero no estás seguro de qué enfoque usar o cómo configurarlo correctamente en Python. Elegir el modelo incorrecto o saltarte pasos de preprocessing lleva a malas predicciones y a perder tiempo depurando.

La regresión lineal es el algoritmo más usado para tareas de predicción continua, pero hacerlo bien implica más que llamar a .fit() y .predict(). Necesitas entender cómo funciona el modelo internamente, cuándo falla, cómo evaluarlo de forma correcta y cuándo cambiar a variantes regularizadas como Ridge o Lasso. Omitir estos pasos significa desplegar modelos que rinden bien en entrenamiento pero se desmoronan con nuevas observaciones.

Scikit-learn ofrece LinearRegression junto con un ecosistema completo de herramientas para preprocessing, evaluación y regularización. Esta guía cubre todo, desde el uso básico hasta pipelines de regresión listos para producción.

📚

¿Qué es la regresión lineal?

La regresión lineal modela la relación entre una o más features de entrada y una salida continua ajustando una recta (o hiperplano) que minimiza la suma de los residuos al cuadrado. La ecuación para un modelo con n features es:

y = b0 + b1*x1 + b2*x2 + ... + bn*xn

Donde b0 es el intercepto (término de sesgo), b1...bn son los coeficientes (pesos) de cada feature, y y es el valor predicho.

El modelo encuentra los coeficientes que minimizan la función de coste de Ordinary Least Squares (OLS):

Cost = Sum of (y_actual - y_predicted)^2

Esto tiene una solución en forma cerrada, por lo que el entrenamiento es rápido incluso en datasets grandes.

Regresión lineal simple con Sklearn

La regresión lineal simple usa una única feature para predecir el target. Aquí tienes un ejemplo completo:

from sklearn.linear_model import LinearRegression
import numpy as np
 
# Sample data: years of experience vs salary (in thousands)
X = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).reshape(-1, 1)
y = np.array([35, 40, 45, 55, 60, 62, 70, 75, 82, 90])
 
# Create and train the model
model = LinearRegression()
model.fit(X, y)
 
# Model parameters
print(f"Coefficient (slope): {model.coef_[0]:.4f}")
print(f"Intercept: {model.intercept_:.4f}")
 
# Predict salary for 12 years of experience
prediction = model.predict([[12]])
print(f"Predicted salary for 12 years: ${prediction[0]:.2f}k")
# Coefficient (slope): 5.9394
# Intercept: 28.3333
# Predicted salary for 12 years: $99.61k

Entendiendo la salida

AttributeMeaningExample Value
model.coef_Peso para cada feature[5.94] -- el salario aumenta ~$5,940 por año
model.intercept_y predicha cuando todas las features valen 028.33 -- salario base de $28,330
model.score(X, y)R-squared en los datos dados0.98

Regresión lineal múltiple

Cuando tienes más de una feature, el modelo ajusta un hiperplano en lugar de una recta:

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_california_housing
import numpy as np
 
# Load California housing dataset
housing = fetch_california_housing()
X, y = housing.data, housing.target
feature_names = housing.feature_names
 
print(f"Features: {feature_names}")
print(f"Dataset shape: {X.shape}")  # (20640, 8)
 
# Split into train and test
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)
 
# Train model
model = LinearRegression()
model.fit(X_train, y_train)
 
# Print coefficients for each feature
print("\nFeature Coefficients:")
for name, coef in zip(feature_names, model.coef_):
    print(f"  {name:12s}: {coef:+.6f}")
print(f"  {'Intercept':12s}: {model.intercept_:+.6f}")
 
# Evaluate
train_score = model.score(X_train, y_train)
test_score = model.score(X_test, y_test)
print(f"\nR² (train): {train_score:.4f}")
print(f"R² (test):  {test_score:.4f}")

Evaluación del modelo: R-squared, MSE y RMSE

R-squared por sí solo no cuenta toda la historia. Usa múltiples métricas para evaluar modelos de regresión:

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from sklearn.datasets import fetch_california_housing
import numpy as np
 
housing = fetch_california_housing()
X_train, X_test, y_train, y_test = train_test_split(
    housing.data, housing.target, test_size=0.2, random_state=42
)
 
model = LinearRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
 
# Calculate metrics
r2 = r2_score(y_test, y_pred)
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
mae = mean_absolute_error(y_test, y_pred)
 
print(f"R² Score:  {r2:.4f}")
print(f"MSE:       {mse:.4f}")
print(f"RMSE:      {rmse:.4f}")
print(f"MAE:       {mae:.4f}")
# R² Score:  0.5758
# MSE:       0.5559
# RMSE:      0.7456
# MAE:       0.5332

Métricas de evaluación explicadas

MetricFormulaRangeInterpretation
R-squared (R²)1 - (SS_res / SS_tot)(-inf, 1]Proporción de varianza explicada. 1.0 = perfecto, 0 = no mejor que la media
MSEmean((y - y_pred)²)[0, inf)Error cuadrático medio. Penaliza más los errores grandes
RMSEsqrt(MSE)[0, inf)Misma unidad que el target. Más fácil de interpretar que MSE
MAEmean(|y - y_pred|)[0, inf)Error absoluto medio. Más robusto ante outliers

Un R-squared bajo no siempre significa un mal modelo. Para datos reales ruidosos (como precios de vivienda), R² = 0.6 puede ser razonable. Siempre compara RMSE con la escala de tu variable objetivo.

Escalado de features para regresión lineal

El LinearRegression estándar no requiere escalado de features porque usa OLS con una solución en forma cerrada. Sin embargo, el escalado se vuelve esencial al usar regularización:

from sklearn.linear_model import LinearRegression, Ridge
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.datasets import fetch_california_housing
 
housing = fetch_california_housing()
X_train, X_test, y_train, y_test = train_test_split(
    housing.data, housing.target, test_size=0.2, random_state=42
)
 
# Without scaling (fine for basic LinearRegression)
model_no_scale = LinearRegression()
model_no_scale.fit(X_train, y_train)
print(f"LinearRegression R² (no scaling): {model_no_scale.score(X_test, y_test):.4f}")
 
# With scaling via Pipeline (required for regularized models)
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('ridge', Ridge(alpha=1.0))
])
pipeline.fit(X_train, y_train)
print(f"Ridge R² (with scaling):          {pipeline.score(X_test, y_test):.4f}")

Por qué el escalado importa para la regularización: Ridge y Lasso penalizan coeficientes grandes por igual. Si una feature está en el rango 0–1 y otra en 0–100,000, la penalización encoge de manera desproporcionada el coeficiente de la feature de rango pequeño. El escalado pone todas las features en la misma escala para que la penalización se aplique de forma justa.

Features polinómicas: modelar relaciones no lineales

Cuando la relación entre features y target no es lineal, las features polinómicas pueden capturar curvas e interacciones:

from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score
import numpy as np
 
# Generate non-linear data
np.random.seed(42)
X = np.linspace(0, 10, 200).reshape(-1, 1)
y = 3 * X.ravel()**2 - 5 * X.ravel() + 10 + np.random.randn(200) * 15
 
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)
 
# Linear model
linear = LinearRegression()
linear.fit(X_train, y_train)
print(f"Linear R²: {r2_score(y_test, linear.predict(X_test)):.4f}")
 
# Polynomial (degree 2) model
poly_pipeline = Pipeline([
    ('poly', PolynomialFeatures(degree=2, include_bias=False)),
    ('linear', LinearRegression())
])
poly_pipeline.fit(X_train, y_train)
print(f"Poly (d=2) R²: {r2_score(y_test, poly_pipeline.predict(X_test)):.4f}")
 
# Polynomial (degree 3) model
poly3_pipeline = Pipeline([
    ('poly', PolynomialFeatures(degree=3, include_bias=False)),
    ('linear', LinearRegression())
])
poly3_pipeline.fit(X_train, y_train)
print(f"Poly (d=3) R²: {r2_score(y_test, poly3_pipeline.predict(X_test)):.4f}")

Advertencia: Los polinomios de alto grado sobreajustan rápidamente. Usa cross-validation para seleccionar el grado adecuado y prefiere regularización para modelos polinómicos.

Regularización: Ridge, Lasso y ElasticNet

Cuando tu modelo tiene muchas features o términos polinómicos, la regularización evita el sobreajuste añadiendo una penalización a coeficientes grandes.

Ridge Regression (penalización L2)

Ridge añade la suma de los coeficientes al cuadrado a la función de coste. Reduce los coeficientes hacia cero, pero nunca los hace exactamente cero.

from sklearn.linear_model import Ridge
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.datasets import fetch_california_housing
import numpy as np
 
housing = fetch_california_housing()
X_train, X_test, y_train, y_test = train_test_split(
    housing.data, housing.target, test_size=0.2, random_state=42
)
 
# Find best alpha with cross-validation
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('ridge', Ridge())
])
 
param_grid = {'ridge__alpha': [0.01, 0.1, 1.0, 10.0, 100.0]}
grid = GridSearchCV(pipeline, param_grid, cv=5, scoring='r2')
grid.fit(X_train, y_train)
 
print(f"Best alpha: {grid.best_params_['ridge__alpha']}")
print(f"Best CV R²: {grid.best_score_:.4f}")
print(f"Test R²:    {grid.score(X_test, y_test):.4f}")

Lasso Regression (penalización L1)

Lasso añade la suma de los valores absolutos de los coeficientes. Puede llevar coeficientes exactamente a cero, realizando selección automática de features:

from sklearn.linear_model import Lasso
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.datasets import fetch_california_housing
import numpy as np
 
housing = fetch_california_housing()
X_train, X_test, y_train, y_test = train_test_split(
    housing.data, housing.target, test_size=0.2, random_state=42
)
 
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('lasso', Lasso(max_iter=10000))
])
 
param_grid = {'lasso__alpha': [0.001, 0.01, 0.1, 1.0, 10.0]}
grid = GridSearchCV(pipeline, param_grid, cv=5, scoring='r2')
grid.fit(X_train, y_train)
 
print(f"Best alpha: {grid.best_params_['lasso__alpha']}")
print(f"Test R²:    {grid.score(X_test, y_test):.4f}")
 
# Show which features were selected (non-zero coefficients)
lasso_model = grid.best_estimator_.named_steps['lasso']
feature_names = housing.feature_names
for name, coef in zip(feature_names, lasso_model.coef_):
    status = "KEPT" if abs(coef) > 1e-6 else "DROPPED"
    print(f"  {name:12s}: {coef:+.6f}  [{status}]")

ElasticNet (penalización L1 + L2)

ElasticNet combina las penalizaciones de Ridge y Lasso. El parámetro l1_ratio controla la mezcla: 0 = Ridge puro, 1 = Lasso puro.

from sklearn.linear_model import ElasticNet
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.datasets import fetch_california_housing
 
housing = fetch_california_housing()
X_train, X_test, y_train, y_test = train_test_split(
    housing.data, housing.target, test_size=0.2, random_state=42
)
 
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('elasticnet', ElasticNet(max_iter=10000))
])
 
param_grid = {
    'elasticnet__alpha': [0.01, 0.1, 1.0],
    'elasticnet__l1_ratio': [0.1, 0.3, 0.5, 0.7, 0.9]
}
 
grid = GridSearchCV(pipeline, param_grid, cv=5, scoring='r2')
grid.fit(X_train, y_train)
 
print(f"Best alpha:    {grid.best_params_['elasticnet__alpha']}")
print(f"Best l1_ratio: {grid.best_params_['elasticnet__l1_ratio']}")
print(f"Test R²:       {grid.score(X_test, y_test):.4f}")

Comparación: LinearRegression vs Ridge vs Lasso vs ElasticNet

ModelPenaltyFeature SelectionWhen to UseScaling Required
LinearRegressionNoneNoPocas features, sin multicolinealidad, buena relación señal/ruidoNo
RidgeL2 (squared)No (encoge hacia cero)Muchas features correlacionadas, quieres mantener todas las featuresYes
LassoL1 (absolute)Yes (pone coeficientes en cero)Muchas features, quieres selección automática de featuresYes
ElasticNetL1 + L2Yes (partial)Muchas features correlacionadas, quieres algo de selecciónYes

Cómo elegir el modelo adecuado

Usa LinearRegression como baseline. Si el modelo sobreajusta (gran diferencia entre R-squared de train y test), prueba primero Ridge. Si sospechas que hay muchas features irrelevantes, prueba Lasso. Si las features están correlacionadas y quieres selección, prueba ElasticNet. Siempre usa cross-validation para comparar.

Supuestos de la regresión lineal

La regresión lineal produce resultados fiables cuando se cumplen estos supuestos:

  1. Linealidad -- La relación entre features y target es lineal (o se puede linearizar con transformaciones).
  2. Independencia -- Las observaciones son independientes entre sí. Se viola en series temporales si no se tiene en cuenta la autocorrelación.
  3. Homoscedasticidad -- La varianza de los residuos es constante en todos los niveles de los valores predichos.
  4. Normalidad de los residuos -- Los residuos siguen una distribución normal. Importa más para intervalos de confianza y tests de hipótesis, menos para precisión de predicción.
  5. Sin multicolinealidad -- Las features no están altamente correlacionadas entre sí. La multicolinealidad infla la varianza de los coeficientes y vuelve poco fiables los coeficientes individuales.

Comprobando supuestos con código

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_california_housing
import numpy as np
 
housing = fetch_california_housing()
X_train, X_test, y_train, y_test = train_test_split(
    housing.data, housing.target, test_size=0.2, random_state=42
)
 
model = LinearRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
residuals = y_test - y_pred
 
# Check residual statistics
print(f"Residual mean:     {residuals.mean():.6f}")   # Should be near 0
print(f"Residual std:      {residuals.std():.4f}")
print(f"Residual skewness: {float(np.mean((residuals - residuals.mean())**3) / residuals.std()**3):.4f}")
 
# Check for multicollinearity (correlation matrix)
corr_matrix = np.corrcoef(X_train, rowvar=False)
print(f"\nMax feature correlation: {np.max(np.abs(corr_matrix - np.eye(corr_matrix.shape[0]))):.4f}")

Pipeline completo: regresión en el mundo real

Aquí tienes un pipeline de estilo producción que combina preprocessing, feature engineering y selección de modelos:

from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.preprocessing import StandardScaler, PolynomialFeatures
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.datasets import fetch_california_housing
import numpy as np
 
# Load data
housing = fetch_california_housing()
X_train, X_test, y_train, y_test = train_test_split(
    housing.data, housing.target, test_size=0.2, random_state=42
)
 
# Define models to compare
models = {
    'LinearRegression': Pipeline([
        ('scaler', StandardScaler()),
        ('model', LinearRegression())
    ]),
    'Ridge (alpha=1)': Pipeline([
        ('scaler', StandardScaler()),
        ('model', Ridge(alpha=1.0))
    ]),
    'Lasso (alpha=0.01)': Pipeline([
        ('scaler', StandardScaler()),
        ('model', Lasso(alpha=0.01, max_iter=10000))
    ]),
    'Poly(2) + Ridge': Pipeline([
        ('poly', PolynomialFeatures(degree=2, include_bias=False)),
        ('scaler', StandardScaler()),
        ('model', Ridge(alpha=10.0))
    ])
}
 
# Evaluate all models
print(f"{'Model':<25} {'CV R² (mean)':>12} {'CV R² (std)':>12} {'Test R²':>10}")
print("-" * 62)
 
for name, pipeline in models.items():
    cv_scores = cross_val_score(pipeline, X_train, y_train, cv=5, scoring='r2')
    pipeline.fit(X_train, y_train)
    test_r2 = pipeline.score(X_test, y_test)
    print(f"{name:<25} {cv_scores.mean():>12.4f} {cv_scores.std():>12.4f} {test_r2:>10.4f}")

Explorando resultados de regresión con PyGWalker

Después de entrenar tu modelo, entender patrones de predicción es crítico. PyGWalker (opens in a new tab) te permite explorar visualmente residuos, importancias de features y relaciones predicho-vs-real mediante una interfaz interactiva de drag-and-drop en Jupyter:

import pandas as pd
import pygwalker as pyg
 
# Build a results DataFrame
results = pd.DataFrame(housing.data[len(X_train):], columns=housing.feature_names)
results['actual'] = y_test
results['predicted'] = y_pred
results['residual'] = y_test - y_pred
results['abs_error'] = np.abs(y_test - y_pred)
 
# Launch interactive exploration
walker = pyg.walk(results)

Puedes arrastrar features a los ejes, colorear por magnitud del residual e identificar en qué segmentos de tus datos el modelo tiene más problemas, todo sin escribir código de plotting.

Para ejecutar experimentos de forma iterativa en Jupyter, RunCell (opens in a new tab) ofrece un agente de IA que te ayuda a probar diferentes combinaciones de features, hyperparameters y pasos de preprocessing sin reescribir celdas manualmente.

FAQ

¿Qué es LinearRegression en sklearn?

sklearn.linear_model.LinearRegression es un modelo de regresión Ordinary Least Squares (OLS). Ajusta una ecuación lineal a los datos minimizando la suma de las diferencias al cuadrado entre valores reales y predichos. Es el modelo de regresión más simple y más interpretable en scikit-learn.

¿Cómo interpreto el score R-squared?

R-squared mide la proporción de varianza de la variable objetivo explicada por el modelo. Un R-squared de 0.80 significa que se explica el 80% de la varianza. Un valor de 1.0 es un ajuste perfecto, 0.0 significa que el modelo no mejora a predecir la media, y valores negativos significan que el modelo es peor que usar la media.

¿Cuándo debería usar Ridge vs Lasso vs ElasticNet?

Usa Ridge cuando quieras mantener todas las features pero reducir el sobreajuste (features multicolineales). Usa Lasso cuando quieras selección automática de features (pone en cero coeficientes irrelevantes). Usa ElasticNet cuando las features estén correlacionadas y quieras un equilibrio entre la estabilidad de Ridge y la sparsity de Lasso.

¿LinearRegression necesita escalado de features?

LinearRegression básico no requiere escalado porque la solución OLS es invariante a la escala. Sin embargo, Ridge, Lasso y ElasticNet sí requieren escalado porque sus penalizaciones tratan todas las magnitudes de coeficientes por igual. Escala siempre las features antes de una regresión regularizada.

¿Cómo manejo features categóricas en regresión lineal?

Convierte las features categóricas a numéricas usando OneHotEncoder o pd.get_dummies() antes de entrenar. LinearRegression de sklearn solo acepta entrada numérica. En pipelines, usa ColumnTransformer para aplicar transformaciones diferentes a columnas numéricas y categóricas.

¿Cuál es la diferencia entre MSE y RMSE?

MSE (Mean Squared Error) es el promedio de las diferencias al cuadrado entre valores reales y predichos. RMSE (Root Mean Squared Error) es la raíz cuadrada del MSE. RMSE tiene las mismas unidades que la variable objetivo, lo que lo hace más fácil de interpretar. Por ejemplo, si predices precios de casas en dólares, un RMSE de 50,000 significa un error de predicción promedio de aproximadamente $50,000.

Conclusión

LinearRegression de Sklearn es el punto de partida para cualquier tarea de regresión en Python. Es rápido, interpretable y efectivo cuando la relación subyacente es aproximadamente lineal. Para datasets del mundo real con ruido, multicolinealidad o muchas features, Ridge, Lasso y ElasticNet aportan regularización que mejora la generalización. Evalúa siempre con múltiples métricas (R-squared, RMSE, MAE), usa train-test splits para evitar sobreajuste y revisa patrones de residuos para verificar que se cumplen los supuestos del modelo. Construye pipelines con StandardScaler y PolynomialFeatures para mantener tu flujo de trabajo limpio y reproducible.

📚