La idea
Una serie temporal es una tabla donde el orden de las filas y la distancia entre ellas importan.
pandas lo hace explícito con un DatetimeIndex: pon order_date en el índice y cada fila sabe
dónde está en el calendario. Después puedes hacer tres tipos de pregunta, y cada una tiene una idea
distinta de lo que es un "periodo".
Resample corta el calendario en cubos fijos (días, semanas, meses) y agrega lo que hay en cada
uno. Rolling desliza por la serie una ventana de longitud fija y agrega lo que hay dentro.
Desplazar (shift y pct_change) alinea una fila con otra que está a cierta distancia, y las
compara. Los cubos no se solapan, las ventanas sí, y las comparaciones conectan dos filas.
Figura 1
resample('MS'): los pedidos caen en cubos mensuales
| order_id | order_date |
|---|---|
| 8512 | 2025-01-31 20:11 |
| 8513 | 2025-01-31 21:02 |
| 8514 | 2025-02-01 07:08 |
| 8515 | 2025-02-01 07:17 |
| 8516 | 2025-02-01 08:37 |
| month_start | orders |
|---|---|
| 2025-01-01 | 2 |
| 2025-02-01 | 3 |
- Cinco pedidos alrededor del cambio de mes. Con
order_datecomo índice, pandas sabe dónde está cada uno en el calendario. resample('MS')traza cubos mensuales que empiezan el día 1. Los dos pedidos del 31 de enero pertenecen al cubo de enero, aunque uno se hiciera a las 21:02.- Los tres pedidos del 1 de febrero caen en el cubo de febrero. El corte es a medianoche, no a la última hora laboral del mes.
.size()cuenta lo que hay en cada cubo y da una fila por cubo. Es ungroupbydonde los grupos son periodos de calendario, y un mes vacío seguiría teniendo su fila, con 0.
Por qué existe
Casi toda pregunta de negocio tiene el tiempo en un eje: pedidos por mes, una media de siete días en
el panel, este noviembre frente al noviembre pasado. Todo eso se puede hacer con groupby y una
columna .dt.month, pero tropezarás con lo mismo: una etiqueta de mes que mezcla enero de 2024 con
enero de 2025, un mes sin pedidos que simplemente desaparece, una semana que abarca dos meses.
Un DatetimeIndex se encarga de eso. resample mantiene el año junto al mes, da una fila a los meses
vacíos y te deja pasar de diario a semanal a mensual cambiando una letra.
También hace honestas las preguntas sobre el tiempo. Un informe que dice "los pedidos fueron planos en el primer trimestre" es una afirmación sobre periodos de calendario, y resample hace explícitos los periodos en el código, así que quien lea tu código puede ver exactamente qué días entraron en qué número. Eso importa sobre todo cuando alguien te pide repetirlo el trimestre siguiente.
Cómo funciona por dentro
Resample: cubos de calendario
orders.set_index('order_date').resample('MS').size() hace tres cosas. Reparte las filas en cubos,
agrega cada cubo y rellena todos los cubos entre el primero y el último, incluso los que no tienen
filas. La letra elige el cubo: D para día, W para semana, MS para mes que empieza el día 1,
ME para mes que termina el último día, QS para trimestre, YS para año.
La etiqueta es lo que confunde a la gente. ME etiqueta un mes con su último día (2025-01-31),
MS con su primero (2025-01-01). Ambos agrupan los mismos pedidos, pero si luego alineas la serie
con otra construida del otro modo, nada coincide y obtienes un muro de NaN. Elige una para todo el
análisis.
size() cuenta filas, sum() suma una columna. Para ingresos primero necesitas una columna
line_total, y luego resample('MS')['line_total'].sum(). Elegir entre ambos es la pregunta de
siempre: ¿estoy contando eventos o sumando un importe?
Rolling: una ventana deslizante
rolling(7) significa "las últimas siete filas", y cuenta filas, no días. Solo son siete días si la
serie tiene exactamente una fila por día. Por eso haces primero resample('D'): garantiza una fila
por día, con 0 para los días sin pedidos. Entonces una ventana de 7 es una semana.
Figura 2
Pedidos por día, del 1 al 10 de noviembre de 2025, y la media de 7 días
- Un conteo diario salta de un lado a otro: 12 pedidos el 4 de noviembre, 24 el 6 de noviembre. El ruido de día a día esconde la tendencia.
rolling(7).mean()promedia cada día con los seis anteriores. El 7 de noviembre la ventana abarca del 1 al 7: (16+17+18+12+15+24+22) / 7 = 17.71.- Desliza un día y la ventana suelta el 1 de nov y toma el 8 de nov. La media pasa a 17.86. Las ventanas se solapan, así que cada día tiene su propio valor y la línea es suave.
- Los seis primeros días no tienen una ventana completa, así que son
NaNsalvo que fijesmin_periods. Una ventana móvil mira hacia atrás, así que reacciona un poco después del cambio.
Una media móvil es un suavizador, no un pronóstico. Reacciona después que los datos, y cuanto más ancha la ventana, más tarde. Siete días quitan el patrón de los días de la semana. Treinta días quitan casi todo lo que vale la pena comentar, y eso es un compromiso, no un fallo.
Crecimiento: compara con otra fila
pct_change() calcula (esta - anterior) / anterior. Con filas mensuales es mes contra mes.
pct_change(12) mira doce filas atrás: año contra año. shift(12) te da el valor antiguo como
columna propia, útil cuando quieres mostrar los dos números junto al porcentaje.
Figura 3
Pedidos por mes: dos formas de comparar noviembre de 2025
- Dos otoños de pedidos mensuales. Ambos tienen el salto navideño: noviembre y diciembre quedan muy por encima de octubre.
- Mes contra mes: nov 2025 (715) frente al mes anterior, oct 2025 (371). Es +92.7%, pero sobre todo mide que empieza la temporada navideña.
- Año contra año: nov 2025 (715) frente a nov 2024 (629). Es +13.7%, una comparación de igual a igual que la temporada no puede distorsionar.
La elección importa cuando los datos tienen temporadas. Los pedidos de Harbor & Pine saltan en
noviembre y diciembre cada año, así que el mes contra mes siempre se verá enorme en otoño y dirá
poco. El año contra año compara lo igual con lo igual. Los doce primeros meses de una serie no
tienen con qué compararse, así que su valor año contra año es NaN.
Desfases de fechas y huecos
La aritmética de fechas funciona con marcas de tiempo: order_date + pd.Timedelta(days=30) es un
plazo, y returned_on - order_date da una duración cuyo .dt.days puedes promediar. Para los
huecos, reindex contra pd.date_range muestra qué días faltan: el registro de anuncios de pago
tiene 1,091 filas para 1,096 días. Luego decides qué significa el hueco: fillna(0) si no tener fila
significa que no hubo gasto, o interpolate y ffill si es una medición que falta.
Errores comunes
"rolling(7) significa siete días"
Significa siete filas. En una serie con una fila por día son siete días. En pedidos con un hueco el
domingo es una ventana de siete filas que se extiende por ocho días, y la media cambia de significado
en silencio. Haz resample primero a una frecuencia regular, o dale a rolling una ventana de tiempo
como rolling('7D'), que cuenta días.
"Mes contra mes es la tasa de crecimiento"
Es una tasa de crecimiento, y en un negocio estacional es la menos informativa. El +92.7% de octubre a noviembre te dice que existe la temporada navideña. El +13.7% frente al noviembre pasado te dice que el negocio crece. Nombra la comparación en el título del gráfico, para que nadie lea la primera como si fuera la segunda.
"La etiqueta de resample es el mes de donde vienen los datos"
La etiqueta es un nombre para el cubo y depende de la letra que elegiste: MS da el primer día,
ME el último. Ambas son los pedidos de enero. El peligro no es la etiqueta, sino mezclar las dos
al unir o alinear dos series: fija la misma frecuencia en ambas antes de unirlas.
Ponte a prueba
Una pregunta. Al responderla, la lección queda marcada en el mapa; nunca bloquea un nivel.
No del todo. pct_change() por defecto mira un periodo atrás, así que da mes contra mes. En el diagrama es +92.7%, impulsado por la temporada navideña.Prueba otra respuesta.
Correcto. Con una fila por mes, doce filas antes es el mismo mes del año pasado. 715 frente a 629 es +13.7%.
No del todo. Una media móvil suaviza una serie con una ventana que se desliza. Da una media por mes, no una comparación con el año pasado.Prueba otra respuesta.
No del todo. resample('YS').sum() colapsa los meses en totales anuales, así que desaparece la comparación mes a mes.Prueba otra respuesta.
Lección hecha: ya está marcada en el mapa.