Fechas y series temporales
En resumen: Una serie temporal son datos ordenados por fecha; en pandas el texto pasa a fecha con to_datetime, resample los agrupa por día, semana y mes, y rolling da una media móvil.
Problema
«¿Cómo va el año?»
La tienda tiene los pedidos de un año entero: de noviembre de 2025 a finales de octubre de 2026. El gerente pregunta qué mes es el más fuerte, qué día de la semana vende más y cómo cambian los ingresos de un mes a otro.
Hay 11 116 pedidos. Pero la columna ordered_at es str: para pandas, «2025-11-01 07:20» es solo texto. De ahí no puedes sacar el mes:
La idea
El texto pasa a fecha
parse_dates le dice a read_csv qué columnas leer como fechas. pd.to_datetime(orders['ordered_at']) hace lo mismo con una columna existente, como viste en la lección sobre tipos:
El tipo es datetime64: un instante en el tiempo, con fecha y hora. Con él puedes comparar, restar y tomar partes. La diferencia entre el último y el primer pedido es de 364 días, 15 horas y 41 minutos. Es una diferencia de tiempo (Timedelta), no un número.
Una tabla en la que cada fila es un instante o un período, y las filas siguen el tiempo, se llama serie temporal.
La idea
Partes de la fecha: .dt
.dt da las partes de cada fecha de la columna: year, month, day, hour, dayofweek (0 es lunes), day_name(). ¿Cuándo durante el día se pide más?
Hay dos picos: a mediodía (entre las 12 y las 14 h, unos 800 pedidos por hora) y por la noche (entre las 19 y las 21 h, unos 1200 por hora). Por la mañana y muy tarde por la noche hay menos de la mitad.
day_name() da los nombres en inglés («Saturday»). Los nombres en español los pondrás tú mismo con un diccionario, un poco más adelante.
La idea
Filtrar por fecha
Una columna de fechas se puede comparar con texto como «2025-12-01»: pandas lo convierte en fecha. Los pedidos de diciembre:
La respuesta correcta es 1285. Con <= '2025-12-31' obtienes 1250, y el último pedido entre ellos es del 30 de diciembre. El motivo es la hora: «2025-12-31» es el 31 de diciembre a las 00:00, y todos los pedidos del 31 llegan después de la medianoche.
Por eso, para un período escribes «desde el primer día, incluido, hasta el primer día del período siguiente, excluido»: >= '2025-12-01' y < '2026-01-01'.
Pruébalo
La semana antes de Navidad
¿Cuántos pedidos hay del 22 al 28 de diciembre de 2025, ambos incluidos? Guarda esos pedidos en week.
Solución
import pandas as pd
orders = pd.read_csv('year_orders.csv', parse_dates=['ordered_at'])
week = orders[(orders['ordered_at'] >= '2025-12-22') & (orders['ordered_at'] < '2025-12-29')]
print(len(week))orders[(orders["ordered_at"] >= "2025-12-22") & (orders["ordered_at"] < "2025-12-29")] da 309 pedidos. Con <= "2025-12-28" obtienes 279: te pierdes todo el 28 de diciembre, porque una fecha sin hora es la medianoche al principio del día.
La idea
Por mes: resample
Para los ingresos por mes necesitas primero los ingresos de cada pedido. Son la suma de sus líneas: groupby sobre order_id, y después merge con los pedidos. Los pedidos cancelados no son ingresos, así que sold los deja fuera.
Después resample agrupa las filas en períodos de tiempo. Es un groupby cuyos grupos son períodos: días ('D'), semanas ('W'), meses ('MS'). Trabaja sobre un índice de fechas, así que primero set_index('ordered_at'):
Diciembre es el mes más fuerte: 15 915,90 €. Enero (7228,20 €) y febrero (7622,30 €) son flojos, como suele pasar después de las fiestas. El más flojo, sin embargo, es septiembre: 7128,80 €. Por qué, lo descubriremos en el proyecto del final del curso.
Más precisamente, para curiosos
'MS' (month start) etiqueta cada mes con su primer día, y 'ME' (month end) con el último. Las sumas son las mismas.
'W' etiqueta cada semana con el domingo en que termina. La primera semana aquí es solo el 1 y el 2 de noviembre (sábado y domingo), así que es mucho más pequeña que las demás. El primer y el último período suelen estar incompletos: compruébalos antes de compararlos.
Si un día no hay pedidos, resample('D').sum() da igualmente la fila, con 0. En estos datos todos los días tienen pedidos.
Pruébalo
La semana más fuerte
¿Qué semana aportó más ingresos? Guarda los ingresos por semana (sin los pedidos cancelados) en weekly, y la fecha de la semana más fuerte en best_week.
Solución
import pandas as pd
orders = pd.read_csv('year_orders.csv', parse_dates=['ordered_at'])
items = pd.read_csv('year_items.csv')
items['revenue'] = items['quantity'] * items['unit_price']
per_order = items.groupby('order_id', as_index=False)['revenue'].sum()
orders = orders.merge(per_order, left_on='id', right_on='order_id', how='left', validate='one_to_one')
# Los ingresos dejan fuera los pedidos cancelados.
sold = orders[orders['status'] != 'cancelled']
weekly = sold.set_index('ordered_at')['revenue'].resample('W').sum()
best_week = weekly.idxmax()
print(best_week, weekly.max().round(2))sold.set_index("ordered_at")["revenue"].resample("W").sum() da 53 semanas. idxmax() es el 28 de diciembre de 2025: la semana del 22 al 28 de diciembre, con 3865,80 €. Si cuentas también los pedidos cancelados, la misma semana sale con 4039,40 €. max() daría la cantidad, no qué semana es.
La idea
Media móvil: rolling
Los ingresos diarios suben y bajan: altos los viernes, bajos los domingos. Detrás de ese ruido es difícil ver si las cosas van hacia arriba. Una media móvil calcula, para cada día, el promedio de los últimos 7 días, incluido ese día:
rolling(7) es una ventana de 7 filas que se desliza hacia abajo una fila cada vez. Los primeros 6 valores son NaN: antes del 7 de noviembre no hay 7 días hacia atrás. El 7 de noviembre el promedio de los primeros siete días es 374,23 €. Después, cada día entra un día nuevo y sale el más antiguo, así que la línea cambia con suavidad.
La idea
Día de la semana
¿Cuáles son los ingresos medios un lunes, un martes y así sucesivamente? Agrupas los ingresos diarios por el número del día del índice, daily.index.dayofweek, y tomas la media. Agrupas por el número y no por day_name(), porque los nombres se ordenan alfabéticamente y el viernes saldría primero. Después un diccionario (y map) da a los números sus nombres:
El viernes es el más fuerte: 350,79 € de media. El domingo es el más flojo: 250,85 €. Este es el ritmo semanal que rolling(7) suaviza: una ventana de 7 días contiene cada día de la semana exactamente una vez.
Aquí el índice ya está hecho de fechas, así que dayofweek va directamente sobre el índice. Para una columna escribes .dt.dayofweek.
La idea
Mes a mes: pct_change
El gerente no pregunta «cuánto», sino «cuánto más o menos que el mes pasado». pct_change() divide cada valor entre el anterior y resta 1: da el cambio como proporción. Multiplicado por 100, es un porcentaje:
Noviembre es NaN: no hay ningún mes antes. Diciembre está un 71,2 % por encima de noviembre, y enero está un 54,6 % por debajo de diciembre. Septiembre está un 19,1 % por debajo de agosto, y octubre vuelve a subir un 39,2 %.
Si la caída de septiembre es real o solo ruido, y qué la causó, es la pregunta del proyecto. Por ahora sabes que está ahí y cuánto es.
Ejercicio
¿Cuándo se encareció el café?
El café en grano (producto 17) se encareció en algún momento del año. Guarda en price_by_month el precio medio del café de cada mes (en el orden de los meses en el tiempo), y en first_new la fecha y hora de la primera venta al precio nuevo de 14.9.
Solución
import pandas as pd
orders = pd.read_csv('year_orders.csv', parse_dates=['ordered_at'])
items = pd.read_csv('year_items.csv')
coffee = items[items['product_id'] == 17]
coffee = coffee.merge(orders[['id', 'ordered_at']], left_on='order_id', right_on='id', validate='many_to_one')
price_by_month = coffee.set_index('ordered_at')['unit_price'].resample('MS').mean()
first_new = coffee[coffee['unit_price'] == 14.9]['ordered_at'].min()
print(price_by_month)
print(first_new)coffee.set_index("ordered_at")["unit_price"].resample("MS").mean() da 13,90 € de noviembre a febrero y 14,90 € desde marzo. La primera venta a 14,90 € es el 3 de marzo de 2026 a las 18:00, y la última al precio antiguo el 28 de febrero. Con groupby sobre .dt.month los meses pierden su año: noviembre y diciembre de 2025 pasan después de octubre de 2026, y se pierde el orden en el tiempo.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Columnas nuevas · Tipos y valores faltantes · Grupos: groupby · Unir tablas: merge
- Continúa en: Qué gráfico para qué pregunta · Gráficos honestos · Contrastar una hipótesis · Correlación y causa
- Ejercicios sobre el tema: Preparar los datos