Funciones de ventana: rolling, expanding y ewm

Avanzado
Pandas
Pandas
Actualizado: 05/05/2026

¿Qué son las funciones de ventana?

Las funciones de ventana calculan un valor para cada fila de una Serie o DataFrame teniendo en cuenta un subconjunto de filas vecinas (la "ventana"), en lugar de toda la columna. Son el equivalente de OVER (ROWS BETWEEN ... AND ...) en SQL.

graph TB
    SER[Serie temporal] -->|rolling window=n| ROL[Ventana deslizante fija]
    SER -->|expanding| EXP[Ventana creciente desde inicio]
    SER -->|ewm alpha=...| EWM[Pesos exponenciales]
    ROL -->|.mean / .sum / .std| AGG[Agregación]
    EXP --> AGG
    EWM --> AGG
    ROL -.->|min_periods| BORD[Control bordes]
    SER -->|groupby + rolling| GBR[Ventanas por grupo]
    AGG --> RES[Serie con valores deslizantes]
    RES --> APP["Indicadores SMA, EMA, Bollinger Bands"]

En Pandas existen tres tipos principales:

| Tipo | Método | Descripción | |------|--------|-------------| | Deslizante fija | rolling(n) | Ventana de las últimas n observaciones | | Acumulada creciente | expanding() | Ventana desde el inicio hasta la fila actual | | Exponencialmente ponderada | ewm() | Pesos decrecientes con el tiempo |

import pandas as pd
import numpy as np

# Serie de ventas diarias para los ejemplos
np.random.seed(42)
fechas = pd.date_range("2024-01-01", periods=20, freq="D")
ventas = pd.Series(
    [145, 160, 138, 172, 155, 189, 142, 165, 178, 150,
     190, 168, 145, 175, 188, 162, 155, 170, 185, 195],
    index=fechas,
    name="ventas"
)
print(ventas.head())

rolling(): ventana deslizante fija

rolling(window) crea un objeto Rolling que aplica una función de agregación sobre las últimas window filas. El resultado tiene el mismo índice que la serie original; las primeras window-1 posiciones son NaN porque no hay suficientes datos.

# Media móvil de 3 días
media_3 = ventas.rolling(window=3).mean()
print(media_3)
# 2024-01-01         NaN
# 2024-01-02         NaN
# 2024-01-03    147.667  <- (145+160+138)/3
# ...

Funciones disponibles sobre rolling

# Suma acumulada en ventana de 5 días
suma_5 = ventas.rolling(5).sum()

# Desviación estándar móvil (volatilidad)
std_7 = ventas.rolling(7).std()

# Mínimo y máximo en ventana de 3 días
min_3 = ventas.rolling(3).min()
max_3 = ventas.rolling(3).max()

# Mediana móvil
mediana_5 = ventas.rolling(5).median()

min_periods: controlar el mínimo de observaciones requerido

Por defecto, rolling(n) exige exactamente n observaciones. Con min_periods se puede reducir ese mínimo para obtener valores parciales al principio de la serie:

# Ventana de 7 días pero con al menos 1 observación
media_parcial = ventas.rolling(7, min_periods=1).mean()
print(media_parcial.head(7))
# Los primeros valores no serán NaN: usan las observaciones disponibles

Esto es útil cuando los datos tienen valores faltantes o cuando se quiere un resultado desde el primer elemento.

center=True: ventana centrada

Por defecto la ventana incluye la observación actual y las n-1 anteriores (ventana "hacia atrás"). Con center=True la ventana se centra en la observación actual:

media_centrada = ventas.rolling(window=5, center=True).mean()

Las primeras n//2 y las últimas n//2 filas siguen siendo NaN con la ventana centrada.

rolling con ventana temporal en Series con DatetimeIndex

Cuando la Serie tiene un DatetimeIndex, window puede ser una cadena de duración en lugar de un entero. Esto permite definir la ventana en términos de tiempo real (útil con datos irregulares):

# Ventana de 7 días naturales
media_7d = ventas.rolling("7D").mean()
print(media_7d.head(10))

Con ventanas temporales Pandas incluye todas las observaciones dentro del rango de tiempo, aunque haya huecos. min_periods sigue aplicando.

expanding(): ventana acumulada creciente

expanding() calcula estadísticas usando todas las observaciones desde el inicio de la serie hasta la fila actual. Es equivalente a rolling(window=len(serie), min_periods=1), pero más eficiente.

# Media acumulada (promedio histórico)
media_acumulada = ventas.expanding().mean()
print(media_acumulada)
# 2024-01-01    145.000  <- solo 1 obs
# 2024-01-02    152.500  <- (145+160)/2
# 2024-01-03    147.667  <- (145+160+138)/3
# ...

# Máximo histórico
maximo_hist = ventas.expanding().max()

# Suma acumulada total
suma_acumulada = ventas.expanding().sum()

Uso de min_periods en expanding

# Requiere al menos 5 observaciones para calcular la desviación estándar
std_acumulada = ventas.expanding(min_periods=5).std()
print(std_acumulada.head(7))

ewm(): media ponderada exponencialmente

ewm() (Exponentially Weighted Moving) asigna pesos decrecientes a las observaciones más antiguas. Las observaciones recientes tienen más influencia en el resultado.

El parámetro de decaimiento puede especificarse de varias formas:

| Parámetro | Significado | |-----------|-------------| | span=n | La ventana efectiva equivale aproximadamente a n periodos | | com=c | Centro de masa: alpha = 1/(1+c) | | alpha=a | Factor de suavizado directo (0 < alpha <= 1) | | halflife=h | Semivida: tiempo para que el peso caiga a la mitad |

# EMA con span de 7 (aproxima una media de 7 periodos)
ema_7 = ventas.ewm(span=7).mean()

# EMA con alpha directo (0.3 = 30% de peso en la observación actual)
ema_alpha = ventas.ewm(alpha=0.3).mean()

# Comparar SMA, EMA y expanding
df_comparativa = pd.DataFrame({
    "ventas": ventas,
    "SMA_7": ventas.rolling(7).mean(),
    "EMA_7": ventas.ewm(span=7).mean(),
    "media_acumulada": ventas.expanding().mean()
})
print(df_comparativa)

La EMA reacciona más rápido a cambios recientes que la SMA, lo que la hace preferida en análisis financiero y detección de tendencias.

rolling con funciones personalizadas: apply()

Se puede pasar cualquier función Python a rolling().apply() para calcular estadísticas no contempladas de forma nativa:

# Rango intercuartílico (IQR) deslizante
def iqr(x):
    return np.percentile(x, 75) - np.percentile(x, 25)

iqr_movil = ventas.rolling(7).apply(iqr, raw=True)
print(iqr_movil)

raw=True pasa el array NumPy subyacente en lugar de una Serie, lo que es más eficiente para funciones numéricas puras.

rolling en DataFrames

Cuando se aplica sobre un DataFrame, rolling() opera sobre cada columna de forma independiente:

df_ventas = pd.DataFrame({
    "laptop": [120, 145, 132, 158, 140, 175, 162, 148, 170, 155],
    "tablet": [80, 95, 88, 102, 91, 115, 108, 98, 110, 105],
    "movil":  [200, 220, 215, 240, 205, 255, 238, 225, 245, 230]
}, index=pd.date_range("2024-01-01", periods=10, freq="D"))

# Media móvil de 3 días en todas las columnas
print(df_ventas.rolling(3).mean())

# Suma acumulada por columna
print(df_ventas.expanding().sum())

groupby() + rolling(): ventanas deslizantes por grupo

Combinar groupby() con rolling() permite calcular estadísticas deslizantes dentro de cada grupo, sin que las filas de un grupo afecten al cálculo del otro:

df_tiendas = pd.DataFrame({
    "tienda":  ["A", "A", "A", "A", "A", "B", "B", "B", "B", "B"],
    "semana":  [1, 2, 3, 4, 5, 1, 2, 3, 4, 5],
    "ventas":  [100, 110, 105, 120, 115, 90, 95, 88, 102, 98]
})

# Media móvil de 3 semanas por tienda
df_tiendas["media_movil_3"] = (
    df_tiendas.groupby("tienda")["ventas"]
    .transform(lambda s: s.rolling(3, min_periods=1).mean())
)
print(df_tiendas)

transform() es necesario para que el resultado tenga el mismo índice y longitud que el DataFrame original.

Indicadores financieros con funciones de ventana

Las funciones de ventana son la herramienta estándar para calcular indicadores técnicos en análisis financiero:

import pandas as pd
import numpy as np

# Precio de cierre simulado
np.random.seed(0)
precios = pd.Series(
    100 + np.cumsum(np.random.randn(100) * 2),
    index=pd.date_range("2024-01-02", periods=100, freq="B"),  # días hábiles
    name="cierre"
)

# SMA (Simple Moving Average) de 20 días
sma_20 = precios.rolling(20).mean()

# EMA (Exponential Moving Average) de 12 días
ema_12 = precios.ewm(span=12).mean()
ema_26 = precios.ewm(span=26).mean()

# MACD (diferencia de EMAs)
macd = ema_12 - ema_26
señal_macd = macd.ewm(span=9).mean()

# Bollinger Bands (±2 desviaciones estándar sobre SMA-20)
sma_20_bb = precios.rolling(20).mean()
std_20 = precios.rolling(20).std()
banda_superior = sma_20_bb + 2 * std_20
banda_inferior = sma_20_bb - 2 * std_20

indicadores = pd.DataFrame({
    "precio": precios,
    "SMA_20": sma_20,
    "EMA_12": ema_12,
    "MACD": macd,
    "BB_sup": banda_superior,
    "BB_inf": banda_inferior
})

print(indicadores.tail(10))
print(f"\nAncho de bandas (último día): {banda_superior.iloc[-1] - banda_inferior.iloc[-1]:.2f}")

Caso práctico: análisis de rendimiento mensual

import pandas as pd
import numpy as np

# Datos diarios de visitas a una tienda online durante 6 meses
np.random.seed(7)
fechas = pd.date_range("2024-01-01", "2024-06-30", freq="D")
visitas = pd.Series(
    500 + np.cumsum(np.random.randn(len(fechas)) * 20),
    index=fechas,
    name="visitas"
).clip(lower=100)

analisis = pd.DataFrame({
    "visitas": visitas,
    "media_7d": visitas.rolling("7D").mean().round(1),
    "media_30d": visitas.rolling("30D").mean().round(1),
    "max_historico": visitas.expanding().max().round(1),
    "zscore_30d": (
        (visitas - visitas.rolling("30D").mean()) /
        visitas.rolling("30D").std()
    ).round(2)
})

print(analisis.tail(15))
print(f"\nDía con mayor volumen: {visitas.idxmax().strftime('%Y-%m-%d')} ({visitas.max():.0f} visitas)")
print(f"Media global: {visitas.mean():.1f} visitas/día")

Las funciones de ventana son imprescindibles en análisis de series temporales, finanzas, meteorología y cualquier dominio donde sea necesario capturar tendencias, suavizar ruido o detectar anomalías en datos ordenados cronológicamente.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Pandas es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Pandas

Explora más contenido relacionado con Pandas y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

  • Calcular estadísticas deslizantes con rolling(window) aplicando funciones como mean(), sum(), std() y min()/max().
  • Controlar el comportamiento del borde con el parámetro min_periods en ventanas deslizantes.
  • Usar rolling con ventanas de tiempo en Series temporales indexadas con DatetimeIndex.
  • Calcular estadísticas acumuladas con expanding() para obtener valores desde el inicio de la serie.
  • Aplicar medias móviles ponderadas exponencialmente con ewm() controlando el decaimiento con span, com y alpha.
  • Combinar funciones de ventana con groupby() para calcular estadísticas deslizantes por grupo.
  • Crear indicadores financieros comunes (SMA, EMA, Bollinger Bands, RSI simplificado) con funciones de ventana.

Cursos que incluyen esta lección

Esta lección forma parte de los siguientes cursos estructurados con rutas de aprendizaje