Fechas: pandas DatetimeIndex
Por qué las fechas merecen un tutorial
Las fechas son el tipo de dato más traicionero en análisis. Cinco fuentes de bugs:
- Parsing ambiguo:
01/03/2024es 1 de marzo o 3 de enero según el locale. - Strings que parecen fechas pero no lo son.
df.dtypesdiceobject, nodatetime. - Zonas horarias silenciosas, comparar fechas con y sin tz da error o resultado raro.
- Agregaciones por período (semana, mes) que requieren conocer
resample. - Operaciones con
Timedeltaque no son intuitivas en Python.
pandas tiene buena infraestructura para todo esto si conoces los conceptos. Aquí están.
El tipo: datetime64[ns]
pandas representa fechas con datetime64[ns], un tipo nativo con resolución de nanosegundos:
import pandas as pd
df = pd.DataFrame({
"fecha": ["2024-01-15", "2024-02-20", "2024-03-10"],
"ventas": [1500, 2100, 1800],
})
df.dtypes
# fecha object
# ventas int64
df["fecha"] = pd.to_datetime(df["fecha"])
df.dtypes
# fecha datetime64[ns]
# ventas int64Hasta que la columna no sea datetime64, no es una fecha real para pandas, solo un string que se parece a una fecha. Las operaciones temporales (filtrar por mes, agregar por semana) requieren el tipo correcto.
pd.to_datetime(): el conversor universal
pd.to_datetime("2024-03-15") # Timestamp único
pd.to_datetime(["2024-01-01", "2024-06-01"]) # DatetimeIndex
pd.to_datetime(df["fecha"]) # Series datetimeFormato explícito
Si las fechas no están en formato ISO (YYYY-MM-DD), especifica:
pd.to_datetime(df["fecha"], format="%d/%m/%Y")
# para fechas como "15/03/2024"Códigos de formato (los mismos de strftime en cualquier lenguaje):
| Código | Significado |
|---|---|
%Y |
año 4 dígitos (2024) |
%y |
año 2 dígitos (24) |
%m |
mes numérico (03) |
%d |
día (15) |
%H |
hora 24h (14) |
%M |
minuto (30) |
%S |
segundo (45) |
Manejo de errores
pd.to_datetime(df["fecha"], errors="coerce")
# las fechas que no se pueden parsear quedan como NaT (Not a Time)errors="coerce" es el patrón seguro, en lugar de fallar, mete NaT (el NaN de fechas) en los strings inválidos. Después puedes diagnosticar con df[df["fecha"].isna()].
Construir fechas desde columnas
df = pd.DataFrame({"year": [2024, 2024], "month": [3, 4], "day": [15, 20]})
df["fecha"] = pd.to_datetime(df[["year", "month", "day"]])pandas reconoce las columnas year, month, day (y opcionalmente hour, minute, second) y las combina. Útil cuando los datos vienen en columnas separadas.
DatetimeIndex: cuando la fecha es el índice
El máximo poder de pandas con fechas aparece cuando la fecha está como índice:
df = df.set_index("fecha")Ahora puedes filtrar con slicing temporal:
df.loc["2024"] # todo 2024
df.loc["2024-02"] # todo febrero 2024
df.loc["2024-02-15":"2024-03-15"] # rango de mes y medio
df.loc["2024-Q1"] # primer trimestreEsta sintaxis no funciona si la fecha es una columna normal. Convertir a índice es el paso clave para análisis temporal cómodo.
Para crear un índice de fechas regular:
fechas = pd.date_range("2024-01-01", "2024-12-31", freq="D")
df = pd.DataFrame({"valor": range(len(fechas))}, index=fechas)freq admite muchos valores: "D" (diario), "W" (semanal), "ME" (fin de mes), "YE" (fin de año), "h" (cada hora), "15min", etc.
resample(): agregación temporal
resample() es como groupby() pero para fechas. Agrupa filas por período y aplica una agregación:
df.resample("ME").sum() # suma mensual (fin de mes)
df.resample("W").mean() # media semanal
df.resample("QE").sum() # suma trimestral
df.resample("YE").mean() # media anualLas frecuencias más útiles:
| Código | Período |
|---|---|
"D" |
Diario |
"W" |
Semanal (acaba en domingo) |
"ME" |
Mensual (fin de mes) |
"MS" |
Mensual (inicio de mes) |
"QE" |
Trimestral (fin de trimestre) |
"YE" |
Anual (fin de año) |
"h" |
Por hora |
resample() necesita un DatetimeIndex. Si la fecha está en una columna, primero .set_index("fecha"), o usa df.resample("ME", on="fecha").
Agregaciones múltiples con named aggregations:
df.resample("ME").agg(
total=("ventas", "sum"),
media=("ventas", "mean"),
n=("ventas", "count"),
)Mismo patrón que groupby().agg(), los conceptos se transfieren.
El accesor .dt: extraer componentes
Para una Series datetime, .dt te da acceso a sus componentes:
df["año"] = df["fecha"].dt.year
df["mes"] = df["fecha"].dt.month
df["día"] = df["fecha"].dt.day
df["dia_semana"] = df["fecha"].dt.day_name() # "Monday", "Tuesday"...
df["es_finde"] = df["fecha"].dt.dayofweek >= 5 # 5=sábado, 6=domingo
df["semana"] = df["fecha"].dt.isocalendar().week
df["trimestre"] = df["fecha"].dt.quarter
df["inicio_mes"] = df["fecha"].dt.is_month_start.dt es el equivalente al .str para strings: solo funciona en Series del tipo correcto. Si la columna no es datetime, .dt falla.
Aritmética con fechas: Timedelta
Restar dos fechas da un Timedelta:
df["dias_desde_inicio"] = df["fecha"] - pd.Timestamp("2024-01-01")
# columna de timedelta64
df["dias"] = (df["fecha"] - pd.Timestamp("2024-01-01")).dt.days
# columna de int (número de días)Sumar tiempo a una fecha:
df["fecha_envio"] = df["fecha"] + pd.Timedelta(days=7)
df["fecha_fin_mes"] = df["fecha"] + pd.offsets.MonthEnd(0)Timedelta para duraciones simples. pd.offsets para algo más sofisticado (último día del mes, primer lunes…).
Zonas horarias
Por defecto las fechas son naive (sin tz). Para añadir zona:
df["fecha"] = df["fecha"].dt.tz_localize("Europe/Madrid")Convertir a otra zona:
df["fecha_utc"] = df["fecha"].dt.tz_convert("UTC")Regla: decide pronto si trabajas con timestamps naive o con tz. Mezclarlos da errores. Para análisis interno suele bastar naive. Para datos de API o producción, conviene tz-aware.
Patrones idiomáticos
Filtrar últimos N días
hoy = pd.Timestamp.now().normalize()
df.loc[df["fecha"] >= hoy - pd.Timedelta(days=30)].normalize() pone la hora a 00:00, útil para comparar fechas sin que la hora actual contamine.
Calendario laboral
df["es_laborable"] = df["fecha"].dt.dayofweek < 5Para festivos, el paquete pandas.tseries.holiday ofrece calendarios construibles. Para España, suele usarse workalendar (externo).
Agrupar por año y mes
df.groupby([df["fecha"].dt.year, df["fecha"].dt.month])["ventas"].sum()Cuando no quieres meter la fecha al índice pero sí agrupar por año-mes. Más conciso que resample cuando solo necesitas una vez.
Trampas habituales
- Olvidar
pd.to_datetime. Una columna de strings parecidos a fechas no es una fecha.df.dtypesdebe decirdatetime64[ns]. Si diceobject, las operaciones.dty.resampleno funcionan. parse_datessilencioso enread_csv. Si pandas no puede parsear todas las fechas, la columna queda comoobjectsin error fuerte. Siempredf.dtypesdespués de leer.- Formato europeo mal parseado.
01/03/2024por defecto se parsea como 3 de enero (formato US). Usaformat="%d/%m/%Y"explícito. - Comparar tz-aware con tz-naive. pandas da error o resultado raro. Mantén toda la columna en un solo modo.
resamplesin DatetimeIndex. Si la fecha está como columna, usadf.resample("ME", on="fecha")o pon la fecha como índice antes.Timedelta(days=30)para “un mes”. Un mes no son 30 días siempre. Para sumar un mes calendar, usapd.DateOffset(months=1), sabe que enero tiene 31 y febrero 28.
En la siguiente entrega
Tienes el motor analítico completo. La última pieza antes del caso real: visualización. matplotlib en el nivel justo, seaborn para gráficos rápidos y elegantes, y los patrones idiomáticos de plotting desde pandas. Lo siguiente.