Análisis serie de tiempo precio de electricidad Colombia#

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.tsa.stattools import adfuller
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf

Funciones:#

plot_serie_tiempo

plot_estacionalidad_aditiva_multiplicativa

plot_estacionalidad_mensual

tabla_descriptiva_serie

histograma_serie

histogramas_transformaciones

graficar_descomposicion_serie

analisis_estacionariedad

def plot_serie_tiempo(
    serie: pd.DataFrame,
    nombre: str,
    unidades: str = None,
    columna: str = None,
    fecha_inicio: str = None,
    fecha_fin: str = None,
    color: str = 'navy',
    linewidth: float = 2,
    num_xticks: int = 12,
    estacionalidad: str = None,         # 'diciembre', 'enero', 'semana', 'semestre', 'custom_month'
    custom_month: int = None,           # Si quieres marcar otro mes (ejemplo: 3 para marzo)
    vline_label: str = None,            # Etiqueta para la(s) línea(s) vertical(es)
    hlines: list = None,                # lista de valores horizontales a marcar
    hlines_labels: list = None,         # lista de etiquetas para líneas horizontales
    color_estacion: str = 'darkgray',     # color de las líneas estacionales
    alpha_estacion: float = 0.3,        # transparencia de líneas estacionales
    color_hline: str = 'gray',          # color de las líneas horizontales
    alpha_hline: float = 0.7            # transparencia de líneas horizontales
):
    """
    Gráfico elegante de serie de tiempo.
    - Eje X alineado con la primera fecha real de la serie.
    - Opcional: marcar estacionalidades (diciembres, semanas, semestres, mes personalizado) con etiqueta.
    - Líneas horizontales con etiqueta opcional (legend).
    """
    df = serie.copy()
    if columna is None:
        columna = df.columns[0]
    if fecha_inicio:
        df = df[df.index >= fecha_inicio]
    if fecha_fin:
        df = df[df.index <= fecha_fin]

    # Asegura que el índice sea datetime y esté ordenado
    df = df.sort_index()
    df.index = pd.to_datetime(df.index)

    plt.style.use('ggplot')
    fig, ax = plt.subplots(figsize=(14, 6))

    # Gráfica principal
    ax.plot(df.index, df[columna], color=color, linewidth=linewidth, label=nombre)
    ax.set_title(f"Serie de tiempo: {nombre}", fontsize=20, weight='bold',
                 color='black')
    ax.set_xlabel("Fecha", fontsize=15, color='black')
    ax.set_ylabel(unidades, fontsize=15, color='black')
    ax.tick_params(axis='both', colors='black', labelsize=13)
    for label in ax.get_xticklabels() + ax.get_yticklabels():
        label.set_color('black')

    # Limita el rango del eje X exactamente al rango de fechas de la serie (no corrido)
    ax.set_xlim(df.index.min(), df.index.max())

    # Ticks equidistantes en eje X, asegurando que empieza en la primera fecha
    idx = df.index
    if len(idx) > num_xticks:
        ticks = np.linspace(0, len(idx)-1, num_xticks, dtype=int)
        ticks[0] = 0  # asegúrate que arranque en la primera fecha
        ticklabels = [idx[i] for i in ticks]
        ax.set_xticks(ticklabels)
        ax.set_xticklabels([pd.to_datetime(t).strftime('%b %Y') for t in ticklabels], rotation=0, color='black')
    else:
        ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))
        fig.autofmt_xdate(rotation=0)

    # ==============================
    # LÍNEAS VERTICALES: Estacionalidad (con etiqueta en leyenda si se desea)
    # ==============================
    vlines_plotted = False
    if estacionalidad is not None:
        if estacionalidad == 'diciembre':
            fechas_mark = df[df.index.month == 12].index
        elif estacionalidad == 'enero':
            fechas_mark = df[df.index.month == 1].index
        elif estacionalidad == 'semana':
            fechas_mark = df[df.index.weekday == 0].index
        elif estacionalidad == 'semestre':
            fechas_mark = df[df.index.month.isin([6, 12])].index
        elif estacionalidad == 'custom_month' and custom_month is not None:
            fechas_mark = df[df.index.month == custom_month].index
        else:
            fechas_mark = []
        for i, f in enumerate(fechas_mark):
            # Solo pone la etiqueta una vez (la primera línea)
            if not vlines_plotted and vline_label is not None:
                ax.axvline(f, color=color_estacion, alpha=alpha_estacion, linewidth=2, linestyle='--', zorder=0, label=vline_label)
                vlines_plotted = True
            else:
                ax.axvline(f, color=color_estacion, alpha=alpha_estacion, linewidth=2, linestyle='--', zorder=0)

    # ==============================
    # LÍNEAS HORIZONTALES OPCIONALES, con leyenda
    # ==============================
    if hlines is not None:
        if hlines_labels is None:
            hlines_labels = [None] * len(hlines)
        for i, h in enumerate(hlines):
            if hlines_labels[i] is not None:
                ax.axhline(h, color=color_hline, alpha=alpha_hline, linewidth=1.5, linestyle='--', zorder=0, label=hlines_labels[i])
            else:
                ax.axhline(h, color=color_hline, alpha=alpha_hline, linewidth=1.5, linestyle='--', zorder=0)

    # Coloca la leyenda solo si hay etiquetas
    handles, labels = ax.get_legend_handles_labels()
    if any(labels):
        ax.legend(loc='best', fontsize=13, frameon=True)

    ax.grid(True, alpha=0.4)
    plt.tight_layout()
    plt.show()

##################################################################################

def plot_estacionalidad_mensual(
    serie: pd.Series,
    tipo: str = "aditiva",  # 'aditiva' o 'multiplicativa'
    window: int = 12,
    nombre: str = None,
    color: str = "navy"
):
    """
    Estima y grafica la estacionalidad mensual (aditiva o multiplicativa) de una serie de tiempo.

    Argumentos:
        serie: Serie de tiempo (pandas.Series) con índice datetime.
        tipo: 'aditiva' o 'multiplicativa'.
        window: ventana de la media móvil para tendencia (por defecto 12).
        nombre: nombre de la serie (para título y eje Y).
        color: color de la línea.
    """
    serie = serie.copy()
    if nombre is None:
        nombre = serie.name if serie.name is not None else "Serie"

    # Tendencia por media móvil centrada
    tendencia = serie.rolling(window=window, center=True).mean()
    # Serie sin tendencia
    if tipo == "aditiva":
        sin_tendencia = serie - tendencia
        # Promedio mensual del componente estacional
        promedio_mensual = sin_tendencia.groupby(sin_tendencia.index.month).mean()
        # Centrar para suma cero
        estacionalidad = promedio_mensual - promedio_mensual.mean()
        ylabel = "Índice estacional aditivo"
        titulo = f"Estacionalidad aditiva: {nombre}"
        ref_line = 0
    elif tipo == "multiplicativa":
        sin_tendencia = serie / tendencia
        promedio_mensual = sin_tendencia.groupby(sin_tendencia.index.month).mean()
        # Centrar para producto 1 (multiplicativa)
        estacionalidad = promedio_mensual / promedio_mensual.mean()
        ylabel = "Índice estacional multiplicativo"
        titulo = f"Estacionalidad multiplicativa: {nombre}"
        ref_line = 1
    else:
        raise ValueError("El tipo debe ser 'aditiva' o 'multiplicativa'.")

    estacionalidad.index.name = "Mes"

    # --- Gráfico ---
    plt.style.use('ggplot')
    fig, ax = plt.subplots(figsize=(5, 4))
    ax.plot(estacionalidad.index, estacionalidad.values, marker='o', linestyle='-', color=color, label="Índice estacional")
    ax.axhline(ref_line, color='gray', linestyle='--', linewidth=1.5, label='Referencia' if tipo == "aditiva" else 'Sin estacionalidad')
    ax.set_title(titulo, fontsize=10, color='black')
    ax.set_xlabel("Mes (1=Ene ... 12=Dic)", fontsize=10, color='black')
    ax.set_ylabel(ylabel, fontsize=10, color='black')
    ax.set_xticks(range(1, 13))
    ax.tick_params(axis='both', labelsize=9, colors='black')
    for label in ax.get_xticklabels() + ax.get_yticklabels():
        label.set_color('black')
    ax.grid(True, alpha=0.4)
    ax.legend(loc='best', fontsize=9, frameon=True)
    plt.tight_layout()
    plt.show()

##################################################################################

def plot_estacionalidad_aditiva_multiplicativa(
    serie,
    columna: str = None,
    nombre: str = None,
    window: int = None,      # Puede ser None; se ajusta por frecuencia si es necesario
    color_add: str = 'navy',
    color_mult: str = 'darkgreen',
    frecuencia: str = 'mensual'
):
    """
    Grafica estacionalidad aditiva (izquierda) y multiplicativa (derecha) para diferentes frecuencias de serie de tiempo.

    Args:
        serie: pd.Series o pd.DataFrame con índice datetime.
        columna: Si serie es DataFrame, nombre de la columna a usar.
        nombre: Nombre de la serie (para títulos).
        window: Ventana de la media móvil centrada (por defecto ajustada por frecuencia).
        color_add: Color de la línea aditiva.
        color_mult: Color de la línea multiplicativa.
        frecuencia: 'horaria', 'diaria', 'semanal', 'mensual', 'trimestral', 'semestral', 'anual'
    """
    # Mapeo de periodos y etiquetas
    frec_dict = {
        'horaria': (24, [str(h) for h in range(0,24)], "Hora del día"),
        'diaria': (7, ['Lun','Mar','Mié','Jue','Vie','Sáb','Dom'], "Día de la semana"),
        'semanal': (52, [str(i) for i in range(1, 53)], "Semana del año"),
        'mensual': (12, ['Ene','Feb','Mar','Abr','May','Jun','Jul','Ago','Sep','Oct','Nov','Dic'], "Mes del año"),
        'trimestral': (4, ['T1','T2','T3','T4'], "Trimestre"),
        'semestral': (2, ['S1','S2'], "Semestre"),
        'anual': (None, [], "Año"),  # No tiene sentido estacionalidad anual
    }

    # Validación de frecuencia
    frecuencia = frecuencia.lower()
    if frecuencia not in frec_dict:
        raise ValueError(f"Frecuencia '{frecuencia}' no soportada.")
    periodos, etiquetas_x, label_x = frec_dict[frecuencia]
    if periodos is None:
        raise ValueError("No tiene sentido calcular estacionalidad anual sobre datos anuales.")

    # Procesamiento de entrada
    if isinstance(serie, pd.DataFrame):
        if columna is None:
            columna = serie.columns[0]
        s = serie[columna].dropna()
        if nombre is None:
            nombre = columna
    else:
        s = serie.dropna()
        if nombre is None:
            nombre = s.name if s.name is not None else "Serie"

    # Definir ventana de suavizamiento si no se especifica
    if window is None:
        window = periodos

    # Tendencia
    tendencia = s.rolling(window=window, center=True).mean()
    # Aditiva
    sin_tendencia_add = s - tendencia
    if frecuencia == 'horaria':
        by_key = sin_tendencia_add.index.hour
    elif frecuencia == 'diaria':
        by_key = sin_tendencia_add.index.dayofweek
    elif frecuencia == 'semanal':
        by_key = sin_tendencia_add.index.isocalendar().week
    elif frecuencia == 'mensual':
        by_key = sin_tendencia_add.index.month
    elif frecuencia == 'trimestral':
        by_key = ((sin_tendencia_add.index.month-1)//3 + 1)
    elif frecuencia == 'semestral':
        by_key = ((sin_tendencia_add.index.month-1)//6 + 1)
    # No se implementa anual porque no hay estacionalidad intrínseca

    promedio_mensual_add = sin_tendencia_add.groupby(by_key).mean()
    estacionalidad_add = promedio_mensual_add - promedio_mensual_add.mean()

    # Multiplicativa
    sin_tendencia_mult = s / tendencia
    promedio_mensual_mult = sin_tendencia_mult.groupby(by_key).mean()
    estacionalidad_mult = promedio_mensual_mult / promedio_mensual_mult.mean()

    # Para graficar: alinear etiquetas en X
    x_axis = np.arange(1, periodos+1)
    if len(etiquetas_x) != periodos:
        etiquetas_x = [str(i) for i in range(1, periodos+1)]

    # Gráfico comparativo
    fig, axes = plt.subplots(1, 2, figsize=(11, 4))
    # Aditiva
    axes[0].plot(x_axis, estacionalidad_add.values, marker='o', linestyle='-', color=color_add, label="Índice estacional aditivo")
    axes[0].axhline(0, color='gray', linestyle='--', linewidth=1.5, label='Referencia')
    axes[0].set_title(f"Estacionalidad aditiva", fontsize=12, color='black')
    axes[0].set_xlabel(label_x, fontsize=10, color='black')
    axes[0].set_ylabel("Índice estacional aditivo", fontsize=10, color='black')
    axes[0].set_xticks(x_axis)
    axes[0].set_xticklabels(etiquetas_x, rotation=0)
    axes[0].tick_params(axis='both', labelsize=9, colors='black')
    for label in axes[0].get_xticklabels() + axes[0].get_yticklabels():
        label.set_color('black')
    axes[0].grid(True, alpha=0.4)
    axes[0].legend(loc='best', fontsize=9, frameon=True)
    # Multiplicativa
    axes[1].plot(x_axis, estacionalidad_mult.values, marker='o', linestyle='-', color=color_mult, label="Índice estacional multiplicativo")
    axes[1].axhline(1, color='gray', linestyle='--', linewidth=1.5, label='Sin estacionalidad')
    axes[1].set_title(f"Estacionalidad multiplicativa", fontsize=12, color='black')
    axes[1].set_xlabel(label_x, fontsize=10, color='black')
    axes[1].set_ylabel("Índice estacional multiplicativo", fontsize=10, color='black')
    axes[1].set_xticks(x_axis)
    axes[1].set_xticklabels(etiquetas_x, rotation=0)
    axes[1].tick_params(axis='both', labelsize=9, colors='black')
    for label in axes[1].get_xticklabels() + axes[1].get_yticklabels():
        label.set_color('black')
    axes[1].grid(True, alpha=0.4)
    axes[1].legend(loc='best', fontsize=9, frameon=True)

    plt.suptitle(f"Estacionalidad {frecuencia} de: {nombre}", fontsize=14, color='black', y=1.05)
    plt.tight_layout()
    plt.show()

##################################################################################

def tabla_descriptiva_serie(
    serie,
    columna: str = None,
    nombre: str = None,
    decimals: int = 4,
    exportar_excel: bool = False,
    nombre_archivo: str = "tabla_descriptiva.xlsx"
):
    """
    Imprime una tabla descriptiva elegante con estadísticas básicas,
    prueba ADF e interpretación de simetría y curtosis.
    Puede exportar la tabla a Excel.

    Argumentos:
        serie: pd.Series o pd.DataFrame con índice de tiempo.
        columna: Si serie es DataFrame, nombre de la columna a usar.
        nombre: Nombre personalizado de la serie.
        decimals: Número de decimales en la tabla.
        exportar_excel: True para guardar la tabla en un archivo Excel.
        nombre_archivo: Nombre del archivo Excel a exportar.
    """
    # Si es DataFrame, selecciona la columna
    if isinstance(serie, pd.DataFrame):
        if columna is None:
            columna = serie.columns[0]
        s = serie[columna].dropna()
        if nombre is None:
            nombre = columna
    else:  # es Serie
        s = serie.dropna()
        if nombre is None:
            nombre = s.name if s.name is not None else "Serie"

    # Estadísticas básicas
    mean = s.mean()
    median = s.median()
    std = s.std()
    min_ = s.min()
    max_ = s.max()
    q25 = s.quantile(0.25)
    q75 = s.quantile(0.75)
    skew = s.skew()
    kurt = s.kurtosis()
    n = len(s)

    # Prueba ADF
    adf_result = adfuller(s, regression='c')
    adf_stat = adf_result[0]
    adf_p = adf_result[1]
    estacionaria = "Sí" if adf_p < 0.05 else "No"

    # Interpretación de curtosis
    if kurt > 0.5:
        curtosis_text = "Leptocúrtica"
    elif kurt < -0.5:
        curtosis_text = "Platicúrtica"
    else:
        curtosis_text = "Mesocúrtica"

    # Interpretación de simetría
    if abs(skew) < 0.1:
        simetria_text = "Simétrica"
    elif skew > 0.1:
        simetria_text = "Asimétrica positiva"
    else:
        simetria_text = "Asimétrica negativa"

    # Construir la tabla como DataFrame para Excel y también como Markdown
    tabla_df = pd.DataFrame({
        "Estadística": [
            "Nombre de la serie", "Número de datos", "Media", "Mediana", "Desviación estándar",
            "Mínimo", "Percentil 25", "Percentil 75", "Máximo",
            "Coeficiente de asimetría", "Tipo de simetría", "Curtosis", "Tipo de curtosis",
            "ADF estadístico", "ADF p-valor", "¿Estacionaria?"
        ],
        "Valor": [
            nombre, n, round(mean, decimals), round(median, decimals), round(std, decimals),
            round(min_, decimals), round(q25, decimals), round(q75, decimals), round(max_, decimals),
            round(skew, decimals), simetria_text, round(kurt, decimals), curtosis_text,
            round(adf_stat, decimals), round(adf_p, decimals), estacionaria
        ]
    })

    # Tabla Markdown (para imprimir en consola o Jupyter)
    tabla_md = "| Estadística              | Valor         |\n"
    tabla_md += "|--------------------------|-------------:|\n"
    for idx, row in tabla_df.iterrows():
        tabla_md += f"| {row['Estadística']:<25} | {row['Valor']} |\n"


    if exportar_excel:
        tabla_df.to_excel(nombre_archivo, index=False)

    return tabla_df

##################################################################################

def histograma_serie(
    serie,
    columna: str = None,
    nombre: str = None,
    bins: int = 30,
    color: str = 'navy',
    alpha: float = 0.82
):
    """
    Grafica el histograma de la serie original con formato elegante.

    Args:
        serie: pd.Series o pd.DataFrame con índice de tiempo.
        columna: Si serie es DataFrame, nombre de la columna a usar.
        nombre: Nombre de la serie (para título).
        bins: Número de bins en el histograma.
        color: Color de las barras.
        alpha: Transparencia de las barras.
    """
    # Selección de serie
    if isinstance(serie, pd.DataFrame):
        if columna is None:
            columna = serie.columns[0]
        s = serie[columna].dropna()
        if nombre is None:
            nombre = columna
    else:
        s = serie.dropna()
        if nombre is None:
            nombre = s.name if s.name is not None else "Serie"

    plt.figure(figsize=(7, 5))
    plt.hist(s, bins=bins, color=color, alpha=alpha, edgecolor='black')
    plt.title(f"Histograma de {nombre}", color='black', fontsize=9)
    plt.xlabel("Valor", color='black', fontsize=9)
    plt.ylabel("Frecuencia", color='black', fontsize=9)
    plt.tick_params(axis='both', labelsize=9, colors='black')
    plt.grid(True, alpha=0.18)
    plt.tight_layout()
    plt.show()

##################################################################################

def graficar_descomposicion_serie(
    serie,
    columna: str = None,
    nombre: str = None,
    periodos: int = 12
):
    """
    Grafica la descomposición aditiva (izquierda) y multiplicativa (derecha)
    (Tendencia, Estacionalidad, Residuo) en dos columnas.

    Args:
        serie: pd.Series o pd.DataFrame con índice de tiempo.
        columna: Si serie es DataFrame, nombre de la columna a usar.
        nombre: Nombre de la serie (para títulos).
        periodos: Periodo de la estacionalidad (por defecto 12, meses).
    """
    # Selección de serie
    if isinstance(serie, pd.DataFrame):
        if columna is None:
            columna = serie.columns[0]
        s = serie[columna].dropna()
        if nombre is None:
            nombre = columna
    else:
        s = serie.dropna()
        if nombre is None:
            nombre = s.name if s.name is not None else "Serie"

    # Descomposición
    result_add = seasonal_decompose(s, model="additive", period=periodos)
    result_mult = seasonal_decompose(s, model="multiplicative", period=periodos)

    componentes = ["Serie observada", "Tendencia", "Estacionalidad", "Residuo"]
    datos_add = [result_add.observed, result_add.trend, result_add.seasonal, result_add.resid]
    datos_mult = [result_mult.observed, result_mult.trend, result_mult.seasonal, result_mult.resid]

    fig, axes = plt.subplots(4, 2, figsize=(12, 10), sharex=True)
    for i in range(4):
        # Aditiva (izquierda)
        axes[i, 0].plot(datos_add[i], color='black')
        axes[i, 0].set_ylabel(componentes[i], color='black', fontsize=11)
        axes[i, 0].set_title("Aditiva" if i == 0 else "", color='black', fontsize=13)
        axes[i, 0].tick_params(axis='both', labelsize=10, colors='black')
        axes[i, 0].grid(True, alpha=0.18)
        # Multiplicativa (derecha)
        axes[i, 1].plot(datos_mult[i], color='black')
        axes[i, 1].set_title("Multiplicativa" if i == 0 else "", color='black', fontsize=13)
        axes[i, 1].tick_params(axis='both', labelsize=10, colors='black')
        axes[i, 1].grid(True, alpha=0.18)
        # Solo la fila inferior lleva etiqueta x
        if i == 3:
            axes[i, 0].set_xlabel("Fecha", color='black')
            axes[i, 1].set_xlabel("Fecha", color='black')

    fig.suptitle(f"Descomposición estacional de la serie: {nombre}", fontsize=15, color='black', y=1.02)
    plt.tight_layout()
    plt.show()

##################################################################################

def histogramas_transformaciones(
    serie,
    columna: str = None,
    nombre: str = None,
    bins: int = 30,
    color: str = 'navy',
    alpha: float = 0.82
):
    """
    Grafica histogramas de:
    - Serie original
    - Primera diferencia
    - Logaritmo
    - Primera diferencia del logaritmo

    Args:
        serie: pd.Series o pd.DataFrame con índice de tiempo.
        columna: Si serie es DataFrame, nombre de la columna a usar.
        nombre: Nombre de la serie (para títulos).
        bins: Número de bins en el histograma.
        color: Color de las barras.
        alpha: Transparencia de las barras.
    """
    # Procesamiento de entrada
    if isinstance(serie, pd.DataFrame):
        if columna is None:
            columna = serie.columns[0]
        s = serie[columna].dropna()
        if nombre is None:
            nombre = columna
    else:  # es Serie
        s = serie.dropna()
        if nombre is None:
            nombre = s.name if s.name is not None else "Serie"

    # Transformaciones
    serie_1 = s
    serie_2 = s.diff().dropna()
    serie_3 = np.log(s).dropna()
    serie_4 = serie_3.diff().dropna()

    series = [
        (serie_1, f"{nombre}"),
        (serie_2, "Primera diferencia"),
        (serie_3, "Logaritmo"),
        (serie_4, "Diferencia del logaritmo")
    ]

    fig, axes = plt.subplots(2, 2, figsize=(13, 8))
    axes = axes.flatten()

    for i, (data, title) in enumerate(series):
        axes[i].hist(data, bins=bins, color=color, alpha=alpha, edgecolor='black')
        axes[i].set_title(title, color='black', fontsize=13)
        axes[i].set_xlabel("Valor", color='black')
        axes[i].set_ylabel("Frecuencia", color='black')
        axes[i].tick_params(axis='both', labelsize=11, colors='black')
        axes[i].grid(True, alpha=0.2)

    plt.tight_layout()
    plt.show()

##################################################################################

def analisis_estacionariedad(
    serie: pd.Series,
    nombre: str = None,
    lags: int = 24,
    xtick_interval: int = 3
):
    """
    Gráfica y análisis de estacionariedad para una serie de tiempo:
    - Serie original, diferencia, logaritmo y diferencia del logaritmo.
    - Muestra la ACF, PACF y resultado ADF en subplots.

    Args:
        serie: Serie de tiempo (índice datetime, pandas.Series)
        nombre: Nombre de la serie (para títulos)
        lags: Número de rezagos para ACF/PACF
        xtick_interval: Mostrar ticks en X cada este número de lags, incluyendo siempre el lag 1
    """
    if nombre is None:
        nombre = serie.name if serie.name is not None else "Serie"

    # Transformaciones
    serie_1 = serie.copy()
    serie_2 = serie_1.diff().dropna()
    serie_3 = np.log(serie_1)
    serie_4 = serie_3.diff().dropna()

    titulos = [
        f"Serie original: {nombre}",
        "Diferenciación",
        "Logaritmo",
        "Diferenciación del Logaritmo"
    ]
    series = [serie_1, serie_2, serie_3, serie_4]

    resultados_adf = []
    interpretaciones = []

    for i, serie_i in enumerate(series):
        serie_ = serie_i.dropna()
        # Selección de regresión en ADF
        if i in [0, 2]:
            adf = adfuller(serie_, regression='ct')
        else:
            adf = adfuller(serie_, regression='c')
        estadistico = adf[0]
        pvalue = adf[1]
        resultados_adf.append((estadistico, pvalue))
        interpretaciones.append("Estacionaria" if pvalue < 0.05 else "No estacionaria")

    fig, axes = plt.subplots(4, 3, figsize=(18, 16))
    colores = ['black', 'black', 'black', 'black']

    for fila in range(4):
        # Serie y etiquetas
        axes[fila, 0].plot(series[fila], color=colores[fila])
        axes[fila, 0].set_title(titulos[fila], color='black')
        axes[fila, 0].set_xlabel("Fecha", color='black')
        if fila == 0:
            axes[fila, 0].set_ylabel("Valor", color='black')
        elif fila == 1:
            axes[fila, 0].set_ylabel("Δ Valor", color='black')
        elif fila == 2:
            axes[fila, 0].set_ylabel("Log(Valor)", color='black')
        else:
            axes[fila, 0].set_ylabel("Δ Log(Valor)", color='black')
        axes[fila, 0].grid(True, alpha=0.3)
        axes[fila, 0].tick_params(axis='both', labelsize=11, colors='black')

        # ACF
        plot_acf(
            series[fila].dropna(),
            lags=lags,
            ax=axes[fila, 1],
            zero=False,
            color=colores[fila]
        )
        axes[fila, 1].set_title("ACF", color='black')
        # xticks: incluir lag 1 y luego cada xtick_interval (ej: 1, 3, 6, ...)
        xticks = [1] + list(range(xtick_interval, lags + 1, xtick_interval))
        xticks = sorted(set(xticks))  # asegura que no haya duplicados
        axes[fila, 1].set_xticks(xticks)
        axes[fila, 1].tick_params(axis='both', labelsize=11, colors='black')
        axes[fila, 1].set_xlabel("Lag", color='black')
        axes[fila, 1].set_ylabel("Autocorrelación", color='black')

        # PACF
        plot_pacf(
            series[fila].dropna(),
            lags=lags,
            ax=axes[fila, 2],
            zero=False,
            color=colores[fila]
        )
        axes[fila, 2].set_title("PACF", color='black')
        axes[fila, 2].set_xticks(xticks)
        axes[fila, 2].tick_params(axis='both', labelsize=11, colors='black')
        axes[fila, 2].set_xlabel("Lag", color='black')
        axes[fila, 2].set_ylabel("Autocorrelación parcial", color='black')

        # Indicador estacionariedad (más abajo)
        axes[fila, 0].text(
            0.02, 0.85,
            f"ADF: {resultados_adf[fila][0]:.2f}\np-valor: {resultados_adf[fila][1]:.4f}\n{interpretaciones[fila]}",
            transform=axes[fila, 0].transAxes,
            fontsize=11, bbox=dict(facecolor='white', alpha=0.85), color='black'
        )

    plt.tight_layout()
    plt.show()

    # Devuelve los resultados en un dict (opcional)
    adf_dict = {
        titulos[i]: {
            "estadístico ADF": resultados_adf[i][0],
            "p-valor": resultados_adf[i][1],
            "interpretación": interpretaciones[i]
        }
        for i in range(4)
    }
    return adf_dict

Precio de electricidad:#

# Cargar el archivo
precio_electricidad = pd.read_csv("Precio_electricidad.csv")

# Corregir nombres de columnas si tienen espacios
precio_electricidad.columns = precio_electricidad.columns.str.strip()

# Convertir 'Fecha' a datetime y usar como índice
precio_electricidad['Fecha'] = pd.to_datetime(precio_electricidad['Fecha'])
precio_electricidad.set_index('Fecha', inplace=True)

# Ordenar por fecha por si acaso
precio_electricidad = precio_electricidad.sort_index()

precio_electricidad.head()
Precio
Fecha
2000-01-01 36.539729
2000-02-01 39.885205
2000-03-01 35.568126
2000-04-01 44.957443
2000-05-01 33.848903
plot_serie_tiempo(
    precio_electricidad,
    nombre="Precio de electricidad",
    columna='Precio',
    unidades='COP/kWh',
    estacionalidad='diciembre',
    vline_label="Diciembre",
    num_xticks = 14
)
../../../_images/output_7_014.png

Estadística descriptiva:#

# Si tienes un DataFrame:
tabla_descriptiva_serie(precio_electricidad, columna='Precio',
                         nombre="Precio de electricidad",
                        decimals=2,
                        exportar_excel=True,
                        nombre_archivo="tabla_descriptiva_precio_electricidad.xlsx"
)
Estadística Valor
0 Nombre de la serie Precio de electricidad
1 Número de datos 291
2 Media 167.58
3 Mediana 108.52
4 Desviación estándar 166.63
5 Mínimo 33.85
6 Percentil 25 71.62
7 Percentil 75 191.54
8 Máximo 1145.23
9 Coeficiente de asimetría 2.87
10 Tipo de simetría Asimétrica positiva
11 Curtosis 10.21
12 Tipo de curtosis Leptocúrtica
13 ADF estadístico -2.09
14 ADF p-valor 0.25
15 ¿Estacionaria? No

Histograma de la serie de tiempo:#

histograma_serie(precio_electricidad,
                columna='Precio',
                nombre="Precio de electricidad",
                color='navy',
                alpha=0.82)
../../../_images/output_11_016.png

Descomposición:#

graficar_descomposicion_serie(precio_electricidad,
                              columna='Precio',
                              nombre="Precio de electricidad",
                              periodos=12)
../../../_images/output_13_09.png

Estacionalidad:#

plot_estacionalidad_aditiva_multiplicativa(precio_electricidad,
                                           columna='Precio',
                                           nombre="Precio de electricidad",
                                           frecuencia='mensual')
../../../_images/output_15_012.png
# Estacionalidad aditiva
plot_estacionalidad_mensual(precio_electricidad, tipo="aditiva",
nombre="Precio de electricidad")

# Estacionalidad multiplicativa
plot_estacionalidad_mensual(precio_electricidad, tipo="multiplicativa",
nombre="Precio de electricidad")
../../../_images/output_16_013.png ../../../_images/output_16_13.png

Transformaciones y prueba ADF:#

adf_resultados = analisis_estacionariedad(
    precio_electricidad['Precio'],
    nombre="Precio de electricidad",
    lags=24,
    xtick_interval=3
)
../../../_images/output_18_010.png

Histograma de las transformaciones:#

histogramas_transformaciones(precio_electricidad,
                            columna='Precio',
                            nombre="Precio de electricidad")
../../../_images/output_20_09.png