tutoriales.com

Series Temporales con Pandas: Desentrañando Patrones y Tendencias 🕰️

Este tutorial te guiará a través del potente mundo de las series temporales utilizando las bibliotecas Pandas y NumPy en Python. Aprenderás a cargar, limpiar, manipular y analizar datos con marca de tiempo, desentrañando patrones y tendencias ocultas. Esencial para cualquier analista de datos que trabaje con información dependiente del tiempo.

Intermedio18 min de lectura8 views
Reportar error

Los datos de series temporales son omnipresentes en una multitud de campos, desde las finanzas y la economía hasta la meteorología y la medicina. Analizar estos datos de manera efectiva es crucial para la toma de decisiones informadas, la predicción de eventos futuros y la comprensión de patrones subyacentes. Python, con sus bibliotecas Pandas y NumPy, ofrece un conjunto de herramientas extremadamente potente y flexible para este propósito.

En este tutorial, exploraremos los fundamentos del manejo de series temporales en Pandas, cubriendo desde la creación y manipulación de índices de tiempo hasta técnicas avanzadas de remuestreo y visualización. Prepárate para dominar una habilidad esencial en el arsenal de cualquier científico o analista de datos.

🚀 ¿Qué son las Series Temporales y por qué son Importantes?

Una serie temporal es una secuencia de puntos de datos indexados (o listados) en orden de tiempo. La diferencia clave con otros tipos de datos es la dependencia temporal, lo que significa que el valor actual puede estar influenciado por valores pasados.

Características Clave de los Datos de Series Temporales:

  • Orden Cronológico: Los datos tienen un orden natural definido por el tiempo.
  • Dependencia: Las observaciones no son independientes entre sí.
  • Tendencia: Movimiento a largo plazo hacia arriba o hacia abajo.
  • Estacionalidad: Patrones regulares que se repiten en períodos de tiempo fijos (ej., diaria, semanal, anual).
  • Ciclicidad: Patrones que no tienen un período fijo, sino que suben y bajan con el tiempo.
  • Irregularidad/Ruido: Variaciones aleatorias que no se pueden explicar por los componentes anteriores.
🔥 Importante: La indexación basada en el tiempo es fundamental para la mayoría de las operaciones de series temporales en Pandas.

🛠️ Preparando el Entorno: Instalación y Carga de Datos

Antes de sumergirnos en el código, asegúrate de tener las bibliotecas necesarias instaladas. Si no las tienes, puedes instalarlas fácilmente usando pip:

pip install pandas numpy matplotlib seaborn

Una vez instaladas, podemos importarlas y comenzar a trabajar.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# Configuración para una mejor visualización de gráficos
%matplotlib inline
sns.set_style("whitegrid")
plt.rcParams['figure.figsize'] = (12, 6)
plt.rcParams['figure.dpi'] = 100

Para nuestros ejemplos, utilizaremos un conjunto de datos ficticio de ventas diarias o un conjunto de datos público simple. Crearemos uno para demostrar los conceptos.

# Generar datos de ejemplo de series temporales
np.random.seed(42)
fechas = pd.date_range(start='2023-01-01', end='2023-12-31', freq='D')
ventas = np.random.randint(100, 500, size=len(fechas)) + \
         np.sin(np.linspace(0, 30, len(fechas))) * 80 + \
         np.random.normal(0, 20, size=len(fechas))

ventas[ventas < 0] = 0 # Asegurarse de que las ventas no sean negativas

df = pd.DataFrame({'Fecha': fechas, 'Ventas': ventas.round(2)})
print(df.head())
print(df.info())

Salida esperada:

        Fecha  Ventas
0  2023-01-01  258.91
1  2023-01-02  381.16
2  2023-01-03  338.24
3  2023-01-04  221.75
4  2023-01-05  221.60
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 365 entries, 0 to 364
Data columns (total 2 columns):
 #   Column  Non-Null Count  Dtype         
---  ------  --------------  -----         
 0   Fecha   365 non-null    datetime64[ns]
 1   Ventas  365 non-null    float64       
dtypes: datetime64[ns](1), float64(1)
memory usage: 5.8 KB

Observa que la columna Fecha ya se ha interpretado como datetime64[ns] gracias a pd.date_range. Si estuvieras cargando desde un CSV, necesitarías convertirla:

# Si se carga desde CSV, esto sería necesario
# df['Fecha'] = pd.to_datetime(df['Fecha'])

🗓️ Índices de Tiempo: El Corazón de las Series Temporales en Pandas

El paso más crucial para trabajar con series temporales en Pandas es establecer una columna de tipo fecha/hora como el índice del DataFrame. Esto permite que Pandas habilite una serie de funcionalidades poderosas.

df = df.set_index('Fecha')
print(df.head())
print(df.index)

Salida esperada:

            Ventas
Fecha             
2023-01-01  258.91
2023-01-02  381.16
2023-01-03  338.24
2023-01-04  221.75
2023-01-05  221.60
DatetimeIndex(['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04',
               '2023-01-05', '2023-01-06', '2023-01-07', '2023-01-08',
               '2023-01-09', '2023-01-10',
               ...
               '2023-12-22', '2023-12-23', '2023-12-24', '2023-12-25',
               '2023-12-26', '2023-12-27', '2023-12-28', '2023-12-29',
               '2023-12-30', '2023-12-31'],
              dtype='datetime64[ns]', name='Fecha', length=365, freq='D')

El índice ahora es un DatetimeIndex, lo que nos abre un abanico de posibilidades.

🔍 Acceso y Selección de Datos Basado en Tiempo

Con un DatetimeIndex, la selección de datos se vuelve increíblemente intuitiva:

# Seleccionar un día específico
print("Ventas del 15 de marzo de 2023:")
print(df.loc['2023-03-15'])

# Seleccionar un mes específico
print("\nVentas de enero de 2023 (primeros 5 días):")
print(df.loc['2023-01'].head())

# Seleccionar un rango de fechas
print("\nVentas de la primera semana de febrero de 2023:")
print(df.loc['2023-02-01':'2023-02-07'])

# Filtrar por año y mes
print("\nVentas de diciembre de 2023 (últimos 5 días):")
print(df.loc['2023-12'].tail())

🧩 Atributos del Índice de Tiempo

El DatetimeIndex ofrece una gran cantidad de atributos para extraer información detallada del tiempo:

# Extraer año, mes, día, día de la semana, etc.
df['Año'] = df.index.year
df['Mes'] = df.index.month
df['Día'] = df.index.day
df['Día_Semana'] = df.index.dayofweek # Lunes=0, Domingo=6
df['Nombre_Mes'] = df.index.month_name()
df['Semana_Año'] = df.index.isocalendar().week

print(df.head())

# Podemos filtrar fácilmente por estos atributos
print("\nVentas de todos los lunes:")
print(df[df['Día_Semana'] == 0].head())
💡 Consejo: Utiliza `df.index.day_name()` para obtener el nombre del día de la semana en lugar del número.

📈 Remuestreo (Resampling): Agregando Datos en Diferentes Frecuencias

El remuestreo es una de las operaciones más potentes en el análisis de series temporales. Permite cambiar la frecuencia de nuestros datos (por ejemplo, de diario a semanal, mensual o anual) y agregar los valores. Esto es fundamental para suavizar el ruido y revelar tendencias a diferentes escalas.

Pandas proporciona el método .resample() que funciona de manera similar a .groupby() pero con un enfoque en las frecuencias de tiempo.

Frecuencias Comunes:

AliasDescripción
DDía natural
------
WSemana natural
MFin de mes
------
QFin de trimestre
AFin de año
------
HHora
TMinuto
------
SSegundo
📌 Nota: Para más alias, consulta la documentación de Pandas sobre `Offset Aliases`.

Ejemplos de Remuestreo:

# Remuestrear a frecuencia semanal y calcular la suma de ventas
ventas_semanales = df['Ventas'].resample('W').sum()
print("\nVentas Semanales (primeras 5):")
print(ventas_semanales.head())

# Remuestrear a frecuencia mensual y calcular el promedio de ventas
ventas_mensuales_promedio = df['Ventas'].resample('M').mean()
print("\nVentas Mensuales Promedio (primeras 5):")
print(ventas_mensuales_promedio.head())

# Remuestrear a frecuencia trimestral y obtener el máximo
ventas_trimestrales_max = df['Ventas'].resample('Q').max()
print("\nVentas Trimestrales Máximas:")
print(ventas_trimestrales_max)

# Remuestrear a frecuencia anual y calcular el total de ventas
ventas_anuales_total = df['Ventas'].resample('A').sum()
print("\nVentas Anuales Totales:")
print(ventas_anuales_total)

Salida esperada (ejemplo parcial):

Ventas Semanales (primeras 5):
Fecha
2023-01-01     258.91
2023-01-08    2018.42
2023-01-15    2021.05
2023-01-22    2378.11
2023-01-29    2208.68
Freq: W-SUN, Name: Ventas, dtype: float64

Ventas Mensuales Promedio (primeras 5):
Fecha
2023-01-31    321.051290
2023-02-28    332.181429
2023-03-31    358.552258
2023-04-30    392.684000
2023-05-31    386.136452
Freq: M, Name: Ventas, dtype: float64

Remuestreo para Rellenar Gaps (Upsampling)

El remuestreo no solo sirve para reducir la frecuencia (downsampling), sino también para aumentarla (upsampling). Si tienes datos con gaps o en una frecuencia menor y quieres analizarlos en una frecuencia mayor, puedes hacerlo, pero necesitarás una estrategia para rellenar los nuevos valores NaN generados.

# Crear un DataFrame con menos frecuencia para demostrar upsampling
df_semanal_ficticio = df['Ventas'].resample('W').mean().to_frame()
print("\nDataFrame semanal ficticio (primeros 5):")
print(df_semanal_ficticio.head())

# Upsampling a diario y rellenar con forward fill (ffill)
ventas_diarias_upsampled_ffill = df_semanal_ficticio.resample('D').ffill()
print("\nUpsampling diario con ffill (primeros 10):")
print(ventas_diarias_upsampled_ffill.head(10))

# Upsampling diario y rellenar con interpolación lineal
ventas_diarias_upsampled_interp = df_semanal_ficticio.resample('D').interpolate(method='linear')
print("\nUpsampling diario con interpolación (primeros 10):")
print(ventas_diarias_upsampled_interp.head(10))
⚠️ Advertencia: El `upsampling` y el relleno de datos pueden introducir artefactos o sesgos si no se utilizan con cuidado. Elige el método de relleno adecuado para tu contexto de datos.

📊 Visualización de Series Temporales

La visualización es clave para entender las series temporales. Pandas, junto con Matplotlib y Seaborn, facilita la creación de gráficos informativos.

# Gráfico de la serie temporal original
plt.figure(figsize=(14, 7))
plt.plot(df.index, df['Ventas'], label='Ventas Diarias', alpha=0.7)
plt.title('Ventas Diarias a lo largo del Tiempo (2023)')
plt.xlabel('Fecha')
plt.ylabel('Ventas')
plt.legend()
plt.tight_layout()
plt.show()

# Gráfico de las ventas mensuales promedio
plt.figure(figsize=(14, 7))
plt.plot(ventas_mensuales_promedio.index, ventas_mensuales_promedio.values, marker='o', linestyle='-', color='orange', label='Ventas Mensuales Promedio')
plt.title('Ventas Mensuales Promedio (2023)')
plt.xlabel('Fecha')
plt.ylabel('Ventas Promedio')
plt.legend()
plt.tight_layout()
plt.show()

# Comparación de ventas diarias y mensuales suavizadas
plt.figure(figsize=(14, 7))
plt.plot(df.index, df['Ventas'], label='Ventas Diarias', alpha=0.5, color='lightgray')
plt.plot(ventas_mensuales_promedio.index, ventas_mensuales_promedio.values, marker='o', linestyle='-', color='blue', label='Ventas Mensuales Promedio')
plt.title('Ventas Diarias vs. Ventas Mensuales Promedio (2023)')
plt.xlabel('Fecha')
plt.ylabel('Ventas')
plt.legend()
plt.tight_layout()
plt.show()
Ventas Diarias a lo largo del Tiempo (2023) 0 25 50 75 100 Ventas Ene Mar May Jul Sep Nov

📈 Ventanas Deslizantes (Rolling Windows): Suavizado y Cálculo de Tendencias

Las ventanas deslizantes (o rolling windows) son esenciales para calcular estadísticas sobre un subconjunto de datos que se mueve a lo largo de la serie temporal. Esto es útil para suavizar el ruido, identificar tendencias locales y calcular promedios móviles.

El método .rolling() de Pandas permite definir una ventana y aplicar una función de agregación (como mean(), sum(), std(), min(), max()) sobre esa ventana.

# Calcular un promedio móvil de 7 días
df['Ventas_MA_7d'] = df['Ventas'].rolling(window=7).mean()

# Calcular una desviación estándar móvil de 30 días
df['Ventas_Std_30d'] = df['Ventas'].rolling(window=30).std()

print("\nDataFrame con promedios móviles (primeros 10 filas con MA):")
print(df[['Ventas', 'Ventas_MA_7d']].head(10))
📌 Nota: Los primeros `window - 1` valores de una ventana móvil serán `NaN` porque no hay suficientes datos previos para calcular el promedio o la estadística.

Visualizando Promedios Móviles

plt.figure(figsize=(14, 7))
plt.plot(df.index, df['Ventas'], label='Ventas Diarias', alpha=0.7)
plt.plot(df.index, df['Ventas_MA_7d'], label='Promedio Móvil 7 Días', color='red')
plt.title('Ventas Diarias y Promedio Móvil de 7 Días')
plt.xlabel('Fecha')
plt.ylabel('Ventas')
plt.legend()
plt.tight_layout()
plt.show()
0 500 1000 Día 1 Día 15 Día 30 Ventas Diarias Promedio Móvil 7 Días Análisis de Tendencia de Ventas

Expansión de Ventanas (Expanding Windows)

Las ventanas de expansión son similares a las ventanas deslizantes, pero la ventana crece con cada punto de datos, acumulando todos los datos pasados. Son útiles para calcular estadísticas acumuladas.

# Calcular la suma acumulada de ventas
df['Ventas_CumSum'] = df['Ventas'].expanding().sum()

# Calcular el promedio acumulado de ventas
df['Ventas_CumMean'] = df['Ventas'].expanding().mean()

print("\nDataFrame con sumas y promedios acumulados (primeros 10 filas):")
print(df[['Ventas', 'Ventas_CumSum', 'Ventas_CumMean']].head(10))

plt.figure(figsize=(14, 7))
plt.plot(df.index, df['Ventas_CumSum'], label='Suma Acumulada de Ventas', color='purple')
plt.title('Suma Acumulada de Ventas a lo largo del Tiempo')
plt.xlabel('Fecha')
plt.ylabel('Suma Acumulada')
plt.legend()
plt.tight_layout()
plt.show()

🔄 Desplazamiento de Datos (Shifting): Comparaciones Temporales

El desplazamiento (shifting) de datos permite mover los valores de una serie temporal hacia adelante o hacia atrás en el tiempo. Esto es increíblemente útil para calcular la diferencia de valores entre períodos o para crear características de lag para modelos predictivos.

El método .shift() de Pandas realiza esta operación. Un valor positivo desplaza los datos hacia abajo (futuro, introduciendo NaN al principio), mientras que un valor negativo los desplaza hacia arriba (pasado, introduciendo NaN al final).

# Calcular la diferencia de ventas día a día
df['Ventas_Dia_Anterior'] = df['Ventas'].shift(1)
df['Cambio_Diario'] = df['Ventas'] - df['Ventas_Dia_Anterior']

# Calcular ventas de la semana anterior (7 días atrás)
df['Ventas_Semana_Anterior'] = df['Ventas'].shift(7)

print("\nDataFrame con datos desplazados y cambios diarios (primeros 10):")
print(df[['Ventas', 'Ventas_Dia_Anterior', 'Cambio_Diario', 'Ventas_Semana_Anterior']].head(10))

plt.figure(figsize=(14, 7))
plt.plot(df.index, df['Cambio_Diario'], label='Cambio Diario de Ventas', alpha=0.7, color='green')
plt.axhline(0, color='gray', linestyle='--', linewidth=0.8)
plt.title('Cambio Diario en Ventas')
plt.xlabel('Fecha')
plt.ylabel('Cambio de Ventas')
plt.legend()
plt.tight_layout()
plt.show()

🧩 Descomposición de Series Temporales (Componentes)

Muchas series temporales pueden descomponerse en sus componentes principales: tendencia, estacionalidad y residual (ruido). Esto ayuda a entender mejor la estructura subyacente de los datos.

Aunque Pandas por sí solo no tiene una función directa para la descomposición, se integra perfectamente con statsmodels.

from statsmodels.tsa.seasonal import seasonal_decompose

# Para la descomposición, necesitamos una serie temporal sin NaNs y con una frecuencia clara.
# Usaremos las ventas diarias para este ejemplo.

# Primero, asegurémonos de que el índice tenga una frecuencia si no la tiene.
# En nuestro caso, ya la tiene ('D'), pero si no, podríamos usar:
# df_ts_clean = df['Ventas'].asfreq('D').fillna(method='ffill')
# o simplemente usar la serie original si no tiene NaNs
df_ts_clean = df['Ventas']

# Realizar la descomposición aditiva (útil cuando la magnitud de la estacionalidad 
# y los cambios residuales no aumenta con la tendencia)
# Para datos con tendencia creciente, la descomposición multiplicativa puede ser mejor.
# model = seasonal_decompose(df_ts_clean, model='multiplicative', period=7) # Para estacionalidad semanal
model = seasonal_decompose(df_ts_clean, model='additive', period=7) # Para estacionalidad semanal

# Plotear los componentes
plt.figure(figsize=(14, 10))
model.plot()
plt.suptitle('Descomposición de Series Temporales de Ventas (Estacionalidad Semanal)', y=1.02) # Ajustar el título superior
plt.tight_layout(rect=[0, 0.03, 1, 0.98]) # Ajustar el layout para el suptitle
plt.show()
Ene Mar May Jul Sep Nov Serie Original Tendencia Estacionalidad Residual (Ruido)
¿Por qué period=7? En nuestro ejemplo de ventas diarias, un `period=7` es apropiado porque esperamos que los patrones de ventas se repitan semanalmente (por ejemplo, mayores ventas los fines de semana o días específicos de la semana). Si fueran datos mensuales con estacionalidad anual, el `period` sería `12`.

✨ Conclusión y Próximos Pasos

Has llegado al final de este tutorial sobre el manejo de series temporales con Pandas y NumPy. Hemos cubierto los fundamentos esenciales, desde la creación de índices de tiempo hasta el remuestreo, las ventanas deslizantes y la visualización. Estas herramientas son la base para cualquier análisis de series temporales, permitiéndote limpiar, transformar y obtener información valiosa de tus datos dependientes del tiempo.

Dominar estas técnicas te permitirá:

  • Entender mejor tus datos: Identificar tendencias, estacionalidad y anomalías.
  • Preparar datos para modelado: Crear características de lag, promedios móviles y otras transformaciones cruciales para modelos predictivos.
  • Comunicar hallazgos: Generar visualizaciones claras que revelen patrones importantes.

🎯 Siguientes Pasos:

  1. Explora más datasets: Aplica lo aprendido a diferentes conjuntos de datos reales (ej., datos de bolsa, clima, tráfico).
  2. Modelado de series temporales: Investiga modelos como ARIMA, Prophet, o redes neuronales recurrentes (RNN) para la predicción.
  3. Manejo de valores perdidos: Profundiza en estrategias avanzadas para imputar datos faltantes en series temporales.
  4. Análisis de estacionalidad avanzada: Utiliza herramientas como seasonal_decompose con diferentes períodos y modelos.

El análisis de series temporales es un campo vasto y fascinante. Con Pandas como tu aliado, tienes una base sólida para explorar sus complejidades y extraer conocimientos poderosos.

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!