Regresión lineal
En resumen: La regresión lineal predice un número como suma de las características con coeficientes; el MAE dice cuánto se equivoca de media, y el RMSE pesa más los errores grandes, ambos en la misma unidad.
Problema
«¿Cuánto dinero espero el viernes?»
Contabilidad planifica el dinero de las próximas semanas. Pregunta cuántos ingresos esperar el viernes 6 de noviembre y el domingo 8 de noviembre. Aquí están los ingresos por día del año, sin los pedidos cancelados, porque no traen dinero:
Un día trae de media 304,62 €. El viernes trae 350,79 € y el domingo 250,85 €. Los días de diciembre traen de media 513,42 €. La media de cada día de la semana es un comienzo, pero en ella están mezclados diciembre y el crecimiento lento a lo largo del año. Necesitas un modelo que los junte.
La idea
Una suma de características
La regresión lineal predice como una suma: un número inicial más un número por cada característica, multiplicado por su valor. Esos números son los coeficientes. Las características aquí son de tres tipos:
- el día de la semana, como columnas de 0 y 1. Con
drop_first=Truese quita la columna del lunes: el lunes es la base, y cada otro día es «cuánto más que el lunes»; day, el número correlativo del día: 0 el 1 de noviembre de 2025. Es la tendencia;december: 1 para los días de diciembre.
El modelo aprende de noviembre a junio, 242 días. De julio a octubre, 123 días, son para la prueba. Por qué no mezclados está al final de la lección:
El número inicial es 273,98 €: un lunes en el día 0, fuera de diciembre. El viernes suma 63,58 € y el domingo quita 45,44 €. Diciembre suma 233,05 € al día. Cada día suma 0,06 €, es decir, unos 21,33 € al año. Así que la predicción para un viernes de diciembre es 273,98 + 63,58 + 233,05 más la tendencia.
Más precisamente, para curiosos
Los coeficientes son estimaciones y llevan ruido, como la media en la lección sobre muestras. Entrenado con los 365 días, el modelo le da al viernes +44,70 €, no +63,58 €. Cómo encuentra la regresión los números (la menor suma de errores al cuadrado) está, sin biblioteca, en la lección Regresión lineal del curso de IA.
Pruébalo
Cuál es la base
La idea
Cuánto se equivoca: MAE y RMSE
El error se mide en la prueba. Hay dos medidas habituales:
- MAE (mean absolute error, error absoluto medio): el error medio sin signo. En euros, y la jefa lo entiende: «nos equivocamos de media tanto al día».
- RMSE (root mean squared error, raíz del error cuadrático medio): los errores se elevan al cuadrado, se hace la media y luego la raíz. También está en euros, pero un error grande pesa más.
sklearn.metrics tiene los dos:
El modelo se equivoca en 59,88 € al día de media (MAE), y el RMSE es 75,91 €. Las dos cuentas pequeñas muestran la diferencia. Errores de 10 y 10 dan un MAE de 10 y un RMSE de 10. Errores de 0 y 20 dan el mismo MAE de 10, pero un RMSE de 14,14. Si un error grande te cuesta más que unos cuantos pequeños, por ejemplo una caja vacía el viernes, miras el RMSE. Si no, el MAE es más claro para la jefa.
Pruébalo
¿Y la línea base?
¿El modelo supera a «siempre el día promedio»? Pon en guess los ingresos medios de los días de entrenamiento, y en mae_base y rmse_base los errores de esta predicción en los días de prueba.
Solución
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
orders = pd.read_csv('year_orders.csv')
items = pd.read_csv('year_items.csv')
orders = orders[orders['status'] != 'cancelled']
orders['ordered_at'] = pd.to_datetime(orders['ordered_at'])
items['revenue'] = items['quantity'] * items['unit_price']
lines = items.merge(orders, left_on='order_id', right_on='id')
daily = lines.set_index('ordered_at').resample('D')['revenue'].sum().to_frame()
daily['weekday'] = daily.index.dayofweek
daily['day'] = np.arange(len(daily))
daily['december'] = (daily.index.month == 12).astype(int)
X = pd.get_dummies(daily[['weekday', 'day', 'december']], columns=['weekday'], drop_first=True, dtype=int)
y = daily['revenue']
train = daily.index < '2026-07-01'
X_train, X_test = X[train], X[~train]
y_train, y_test = y[train], y[~train]
model = LinearRegression().fit(X_train, y_train)
guess = y_train.mean()
mae_base = np.abs(y_test - guess).mean()
rmse_base = np.sqrt(((y_test - guess) ** 2).mean())
print(round(guess, 2), round(mae_base, 2), round(rmse_base, 2))La línea base dice 314,34 € para cada día. Se equivoca con un MAE de 66,24 € y un RMSE de 82,67 €. El modelo tiene 59,88 € y 75,91 €: de 6 a 7 € menos al día en ambas medidas. No es mucho: los ingresos de un día tienen ruido, y la mayor parte del error viene de qué clientes vinieron justo ese día. Sin la raíz obtienes 6834,32: euros al cuadrado, que no se comparan con nada. Con la media de la prueba (285,49 €) el MAE es 61,02 € y la línea base parece casi tan buena como el modelo, pero usa la respuesta.
La idea
Los residuos
Un residuo es el valor real menos la predicción. Más significa que la tienda vendió más de lo que dijo el modelo. Con un buen modelo los residuos se reparten alrededor de 0, sin patrón. Un patrón es algo que el modelo no sabe:
El mayor error está el 30 de julio de 2026: 239,28 € por encima de la predicción. Es un jueves inusualmente fuerte con 535,40 € de ingresos. Por meses, julio, agosto y octubre están cerca de 0 (3,99 €, −8,03 € y 20,42 €). Septiembre es −58,85 € al día: durante un mes entero la tienda vendió menos de lo que explican el día y la tendencia. No es ruido, pasó algo. Qué, lo descubriremos en el proyecto del curso.
La idea
Lo que el modelo no puede hacer
El modelo solo sabe lo que le dicen sus características. Sin la columna december:
La tendencia pasa a −0,40 € al día, −147,35 € al año. Diciembre está al comienzo de los datos de entrenamiento y «levanta» el comienzo de la línea, y el modelo no tiene otra forma de explicarlo. El error de la prueba pasa a 69,43 €, peor que la línea base (66,24 €). El salto de las tarjetas regalo en diciembre solo se ve si una característica lo dice.
Dos límites más:
- Sin extrapolación. La línea sigue para siempre. Con 8 meses el crecimiento es de 21,33 € al año, pero si continúa el año que viene no lo dicen los datos. Hemos visto un solo diciembre.
- Un coeficiente no es una causa. «+63,58 € el viernes» no significa que una promoción movida al viernes traiga 63,58 €. El coeficiente describe los datos. Cuánto aporta una acción lo dice un experimento, como en la lección Correlación y causa.
Pruébalo
Dónde el modelo es ciego
Entrena el modelo sin la columna december con los días de entrenamiento. Calcula sus residuos en esos mismos días y el residuo medio por mes en by_month (agrupa por res.index.strftime('%Y-%m')). Dibuja by_month como barras, con título y etiqueta en el eje vertical.
Solución
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
orders = pd.read_csv('year_orders.csv')
items = pd.read_csv('year_items.csv')
orders = orders[orders['status'] != 'cancelled']
orders['ordered_at'] = pd.to_datetime(orders['ordered_at'])
items['revenue'] = items['quantity'] * items['unit_price']
lines = items.merge(orders, left_on='order_id', right_on='id')
daily = lines.set_index('ordered_at').resample('D')['revenue'].sum().to_frame()
daily['weekday'] = daily.index.dayofweek
daily['day'] = np.arange(len(daily))
daily['december'] = (daily.index.month == 12).astype(int)
X = pd.get_dummies(daily[['weekday', 'day', 'december']], columns=['weekday'], drop_first=True, dtype=int)
y = daily['revenue']
train = daily.index < '2026-07-01'
X_train, X_test = X[train], X[~train]
y_train, y_test = y[train], y[~train]
import matplotlib.pyplot as plt
no_dec = X.drop(columns='december')
blind = LinearRegression().fit(no_dec[train], y_train)
res = y_train - blind.predict(no_dec[train])
by_month = res.groupby(res.index.strftime('%Y-%m')).mean()
fig, ax = plt.subplots(figsize=(8, 3.5))
ax.bar(by_month.index, by_month.values)
ax.axhline(0, color='black', linewidth=1)
ax.set_title('Modelo sin diciembre: residuo medio por mes')
ax.set_ylabel('€ por día')Diciembre está 167,92 € al día por encima de la predicción: el modelo no ve las tarjetas regalo ni las fiestas. Enero es −100,63 €: la línea, inclinada hacia abajo por culpa de diciembre, queda demasiado alta al comienzo del año. Con la columna december la barra de diciembre es casi 0. El residuo es real menos predicción. Si lo inviertes, diciembre sale por debajo de 0 y el gráfico dice lo contrario.
La idea
Dividir por tiempo, no mezclar
El modelo de esta lección aprende de noviembre a junio y se comprueba con julio–octubre. ¿Por qué no con train_test_split? Aquí hay un modelo con el día y el mes, comprobado de las dos maneras. El segundo número de cada línea es la línea base:
Mezclado, el modelo se equivoca en 59,81 € y la línea base en 81,48 €: un 27 % mejor. Pero 87 de los 92 días de prueba tienen un día vecino, el anterior o el siguiente, en los datos de entrenamiento. El modelo ha aprendido cómo fueron los días alrededor del día de prueba, también los posteriores.
Comprobado por tiempo, el mismo modelo se equivoca en 71,98 €, más que la línea base (66,24 €). Los meses de julio a octubre no están en los datos de entrenamiento. Sus columnas son 0 en todas las filas de entrenamiento, y el modelo les da el nivel de enero, el mes más débil.
Una predicción real es siempre para días posteriores a todos los ejemplos. Así que la prueba también tiene que venir después del entrenamiento. Mezclar los días deja entrar el futuro en el entrenamiento. Eso es una fuga de datos, y una lección entera más adelante trata de ella.
Ejercicio
Viernes y domingo
Ahora la respuesta para contabilidad. Entrena el modelo otra vez con los 365 días. Crea X_new para el viernes 6 de noviembre de 2026 y el domingo 8 de noviembre de 2026 (day sigue la cuenta desde el 1 de noviembre de 2025, december es 0) y pon en forecast la predicción para los dos días (una Series con las fechas como índice).
Solución
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
orders = pd.read_csv('year_orders.csv')
items = pd.read_csv('year_items.csv')
orders = orders[orders['status'] != 'cancelled']
orders['ordered_at'] = pd.to_datetime(orders['ordered_at'])
items['revenue'] = items['quantity'] * items['unit_price']
lines = items.merge(orders, left_on='order_id', right_on='id')
daily = lines.set_index('ordered_at').resample('D')['revenue'].sum().to_frame()
daily['weekday'] = daily.index.dayofweek
daily['day'] = np.arange(len(daily))
daily['december'] = (daily.index.month == 12).astype(int)
X = pd.get_dummies(daily[['weekday', 'day', 'december']], columns=['weekday'], drop_first=True, dtype=int)
y = daily['revenue']
train = daily.index < '2026-07-01'
X_train, X_test = X[train], X[~train]
y_train, y_test = y[train], y[~train]
final = LinearRegression().fit(X, y)
dates = pd.to_datetime(['2026-11-06', '2026-11-08'])
new = pd.DataFrame({'weekday': dates.dayofweek, 'day': (dates - daily.index[0]).days, 'december': 0}, index=dates)
X_new = pd.get_dummies(new, columns=['weekday'], dtype=int).reindex(columns=X.columns, fill_value=0)
forecast = pd.Series(final.predict(X_new), index=dates).round(2)
print(forecast)El viernes son 336,38 € y el domingo 236,58 €. Con un error de unos 60 € al día, la respuesta honesta es «unos 340 € el viernes y unos 240 € el domingo, con 60 € de margen». La prueba fue la comprobación. Para la predicción entrenas con todos los días, porque los últimos meses son los más cercanos a noviembre. Por eso el viernes es +44,70 € respecto al lunes, no +63,58 €. day es 370 y 372, contados exactamente como en el entrenamiento. Con day 0 la predicción es 329,70 € y 229,86 €, y con el modelo viejo entrenado hasta junio, 359,17 € y 250,27 €.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: El primer gráfico · Distribuciones · Correlación y causa · Modelos: aprender de ejemplos
- Continúa en: Clasificación · Fuga de datos · Validación cruzada
- Ejercicios sobre el tema: Modelos con scikit-learn