ScikitLearn
Tutorial ScikitLearn: Transformación y escalado de series temporales
Aprende a aplicar normalización y estandarización a series temporales con Python y Scikit-Learn para optimizar tus modelos predictivos con las mejores técnicas disponibles.
Aprende ScikitLearn GRATIS y certifícateNormalización y estandarización de datos temporales
En el análisis de series temporales, la normalización y la estandarización son técnicas fundamentales para preparar los datos antes de aplicar modelos predictivos. Estas transformaciones permiten que las características numéricas estén en escalas comparables, lo que puede mejorar significativamente el rendimiento de los algoritmos de aprendizaje automático.
La normalización consiste en escalar los datos para que sus valores se encuentren en un rango específico, generalmente entre 0 y 1. Esto es especialmente útil cuando los datos no siguen una distribución normal y se desea mantener la forma original de la distribución.
Por otro lado, la estandarización transforma los datos para que tengan una media de 0 y una desviación estándar de 1. Esta técnica es apropiada cuando los datos siguen una distribución aproximadamente normal y es importante para algoritmos que asumen esta propiedad en los datos de entrada.
A continuación, se muestra cómo aplicar estas técnicas a una serie temporal utilizando Scikit-Learn y Python:
import numpy as np
import pandas as pd
from sklearn.preprocessing import MinMaxScaler, StandardScaler
import matplotlib.pyplot as plt
# Crear un generador de números aleatorios
rng = np.random.default_rng(42)
# Generar datos de ejemplo
fechas = pd.date_range('2024-01-01', periods=100, freq='D')
valores = rng.random(100) * 100 + 50 # Datos entre 50 y 150
serie_temporal = pd.Series(valores, index=fechas)
# Visualizar la serie temporal original
plt.figure(figsize=(12, 6))
plt.plot(serie_temporal, label='Datos originales')
plt.title('Serie temporal original')
plt.xlabel('Fecha')
plt.ylabel('Valor')
plt.legend()
plt.show()
En este ejemplo, se crea una serie temporal sintética con valores aleatorios entre 50 y 150. Al visualizar la serie, observamos la variación de los datos en el tiempo. Ahora, aplicaremos la normalización y la estandarización a estos datos.
Aplicación de la normalización:
# Convertir los datos a una matriz 2D
datos = serie_temporal.values.reshape(-1, 1)
# Crear el escalador MinMaxScaler
normalizador = MinMaxScaler()
# Ajustar y transformar los datos
datos_normalizados = normalizador.fit_transform(datos)
# Crear una serie temporal con los datos normalizados
serie_normalizada = pd.Series(datos_normalizados.flatten(), index=fechas)
Aplicación de la estandarización:
# Crear el escalador StandardScaler
estandarizador = StandardScaler()
# Ajustar y transformar los datos
datos_estandarizados = estandarizador.fit_transform(datos)
# Crear una serie temporal con los datos estandarizados
serie_estandarizada = pd.Series(datos_estandarizados.flatten(), index=fechas)
Ahora, compararemos las tres series:
# Visualizar las series transformadas
plt.figure(figsize=(12, 6))
plt.plot(serie_temporal, label='Original')
plt.plot(serie_normalizada, label='Normalizada')
plt.plot(serie_estandarizada, label='Estandarizada')
plt.title('Comparación de series temporales')
plt.xlabel('Fecha')
plt.ylabel('Valor')
plt.legend()
plt.show()
Al observar el gráfico, notamos que:
- La serie normalizada tiene todos sus valores entre 0 y 1.
- La serie estandarizada tiene una media de 0 y una desviación estándar de 1.
- La forma general de las series se mantiene, pero la escala cambia según la transformación aplicada.
La elección entre normalización y estandarización depende del algoritmo de aprendizaje que se vaya a utilizar y de las características de los datos. Por ejemplo, los algoritmos basados en distancias, como los vecinos más cercanos, pueden beneficiarse de la normalización, mientras que los que asumen una distribución normal en los datos podrían requerir estandarización.
Generación de características para un modelo predictivo
Las características adicionales son esenciales para que los modelos predictivos puedan capturar patrones temporales en los datos. En este caso, generaremos características basadas en valores rezagados (lags) que proporcionan información sobre el comportamiento pasado de la serie:
# Crear características de rezagos
serie_temporal_df = pd.DataFrame({
'valor': serie_temporal,
'lag_1': serie_temporal.shift(1),
'lag_2': serie_temporal.shift(2)
}).dropna()
# Dividir en entrenamiento y prueba
porcentaje_entrenamiento = 0.8
tamaño_entrenamiento = int(len(serie_temporal_df) * porcentaje_entrenamiento)
entrenamiento = serie_temporal_df.iloc[:tamaño_entrenamiento]
prueba = serie_temporal_df.iloc[tamaño_entrenamiento:]
X_entrenamiento = entrenamiento[['lag_1', 'lag_2']]
y_entrenamiento = entrenamiento['valor']
X_prueba = prueba[['lag_1', 'lag_2']]
y_prueba = prueba['valor']
Entrenamiento del modelo
Entrenaremos un modelo de regresión lineal utilizando las características de rezagos:
from sklearn.linear_model import LinearRegression
modelo = LinearRegression()
modelo.fit(X_entrenamiento, y_entrenamiento)
# Realizar predicciones
predicciones = modelo.predict(X_prueba)
# Visualizar las predicciones
plt.figure(figsize=(12, 6))
plt.plot(prueba.index, y_prueba, label='Prueba')
plt.plot(prueba.index, predicciones, label='Predicciones')
plt.title('Modelo de regresión lineal con características de rezago')
plt.xlabel('Fecha')
plt.ylabel('Valor')
plt.legend()
plt.show()
Integración de las transformaciones en un Pipeline
Para asegurar que las transformaciones se aplican de forma consistente, encapsulamos el proceso en un Pipeline:
from sklearn.linear_model import LinearRegression
modelo = LinearRegression()
modelo.fit(X_entrenamiento, y_entrenamiento)
# Realizar predicciones
predicciones = modelo.predict(X_prueba)
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
# Crear un transformador para normalizar los datos
transformador = ColumnTransformer([
('normalizador', MinMaxScaler(), ['lag_1', 'lag_2'])
])
# Crear un pipeline que incluya el escalador y el modelo
pipeline = Pipeline([
('preprocesamiento', transformador),
('regresor', LinearRegression())
], memory = None)
# Entrenar el pipeline
pipeline.fit(X_entrenamiento, y_entrenamiento)
# Realizar predicciones
predicciones_pipeline = pipeline.predict(X_prueba)
# Visualizar las predicciones
plt.figure(figsize=(12, 6))
plt.plot(prueba.index, y_prueba, label='Prueba')
plt.plot(prueba.index, predicciones_pipeline, label='Predicciones')
plt.title('Modelo con Pipeline y características de rezago')
plt.xlabel('Fecha')
plt.ylabel('Valor original')
plt.legend()
plt.show()
En este ejemplo, hemos construido un modelo de predicción basado en características derivadas de los datos históricos de la serie temporal. Incorporar valores rezagados como características permite capturar patrones temporales relevantes, mejorando la capacidad del modelo para realizar predicciones precisas. Además, encapsular las transformaciones y el entrenamiento en un Pipeline asegura consistencia y simplicidad en el flujo de trabajo.
Transformaciones logarítmicas y de Box-Cox
En el análisis de series temporales, es común encontrarse con datos que no cumplen los supuestos de normalidad o que presentan heterocedasticidad (varianza no constante). Para abordar estos problemas, se utilizan transformaciones como la logarítmica y la de Box-Cox, que ayudan a estabilizar la varianza y a aproximar la distribución a una normal.
La transformación logarítmica es una de las más utilizadas cuando los datos exhiben una relación exponencial o una asimetría positiva. Al aplicar el logaritmo natural a los valores de la serie, se reducen las diferencias entre los grandes y pequeños valores, lo que puede mejorar el rendimiento de los modelos de aprendizaje automático.
A continuación, se muestra cómo aplicar la transformación logarítmica a una serie temporal utilizando Python y Scikit-Learn:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Crear un generador de números aleatorios
rng = np.random.default_rng(42)
# Generar datos de ejemplo con distribución exponencial
fechas = pd.date_range('2024-01-01', periods=100, freq='D')
valores = rng.exponential(scale=2, size=100) # Distribución exponencial
serie_temporal = pd.Series(valores, index=fechas)
# Aplicar la transformación logarítmica
serie_log = np.log(serie_temporal + 1e-6)
# Visualizar la serie transformada
plt.figure(figsize=(12, 6))
plt.plot(serie_temporal, label='Original')
plt.plot(serie_log, label='Logarítmica', color='orange')
plt.title('Transformación Logarítmica')
plt.legend()
plt.show()
Al visualizar la serie transformada, notamos que los valores se han estabilizado, y la serie es menos asimétrica. Esto puede ser beneficioso al entrenar modelos que asumen una distribución normal de los residuos.
Sin embargo, no siempre la transformación logarítmica es suficiente. En algunos casos, es necesario utilizar transformaciones más flexibles, como la de Box-Cox. La transformación de Box-Cox busca encontrar la mejor potencia lambda para transformar los datos y lograr una distribución lo más cercana posible a la normal.
Para aplicar la transformación de Box-Cox, utilizamos la función boxcox
de SciPy:
from scipy.stats import boxcox
# Verificar si la serie tiene valores negativos o cero
serie_temporal_ajustada = serie_temporal + abs(serie_temporal.min()) + 1
# Aplicar la transformación de Box-Cox
serie_boxcox, lambda_optima = boxcox(serie_temporal_ajustada)
# Visualizar la serie transformada
plt.figure(figsize=(12, 6))
plt.plot(serie_boxcox, label='Box-Cox', color='green')
plt.title('Transformación de Box-Cox')
plt.legend()
plt.show()
print(f'Lambda óptima encontrada: {lambda_optima}')
La transformación de Box-Cox ajusta los datos utilizando la lambda óptima que maximiza la normalidad de la serie. Al visualizar la serie transformada, notamos una mejora en la simetría y en la estabilización de la varianza.
Es importante destacar que, al aplicar estas transformaciones, debemos considerar cómo afectan a la interpretación de los resultados. Por ejemplo, al revertir la transformación para interpretar las predicciones, debemos aplicar la transformación inversa correspondiente.
Para mejorar las predicciones, se pueden incorporar transformaciones y características adicionales al modelo. Aquí integramos tanto las transformaciones de la serie temporal como características derivadas, como componentes estacionales.
from sklearn.preprocessing import PowerTransformer, StandardScaler
from sklearn.compose import TransformedTargetRegressor
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
# Crear variables independientes
X = np.arange(len(serie_temporal)).reshape(-1, 1)
# Dividir los datos en entrenamiento y prueba
porcentaje_entrenamiento = 0.8
tamaño_entrenamiento = int(len(X) * porcentaje_entrenamiento)
X_entrenamiento = X[:tamaño_entrenamiento]
X_prueba = X[tamaño_entrenamiento:]
y_entrenamiento = serie_temporal_ajustada[:tamaño_entrenamiento]
y_prueba = serie_temporal_ajustada[tamaño_entrenamiento:]
# Añadir características adicionales
X_extendido = np.hstack([
X,
np.sin(2 * np.pi * X / 365), # Estacionalidad anual
np.cos(2 * np.pi * X / 365), # Estacionalidad anual
np.log1p(X) # Transformación logarítmica
])
X_entrenamiento_ext = X_extendido[:tamaño_entrenamiento]
X_prueba_ext = X_extendido[tamaño_entrenamiento:]
# Crear el pipeline
transformador_y = PowerTransformer(method='box-cox')
pipeline = Pipeline([
('estandarizador', StandardScaler()),
('regressor', TransformedTargetRegressor(
regressor=LinearRegression(),
transformer=transformador_y
))
], memory = None)
# Entrenar y predecir
pipeline.fit(X_entrenamiento_ext, y_entrenamiento)
predicciones_ext = pipeline.predict(X_prueba_ext)
# Visualizar las predicciones
plt.figure(figsize=(12, 6))
plt.plot(fechas[tamaño_entrenamiento:], y_prueba, label='Valores reales', marker='o')
plt.plot(fechas[tamaño_entrenamiento:], predicciones_ext, label='Predicciones', color='green', marker='x')
plt.title('Predicción con Transformaciones y Características Adicionales')
plt.legend()
plt.show()
En este gráfico, las predicciones reflejan la dinámica de la serie temporal, integrando componentes estacionales y características derivadas del tiempo para un ajuste más preciso.
Es esencial tener en cuenta que algunas transformaciones, como la logarítmica, requieren que los datos sean estrictamente positivos. Si la serie contiene valores cero o negativos, debemos ajustar los datos sumando una constante positiva.
Además, es recomendable verificar si la transformación ha mejorado la normalidad y la homocedasticidad de los datos. Podemos utilizar gráficos de probabilidad normal (Q-Q plots) o pruebas estadísticas como la de Shapiro-Wilk para evaluar la normalidad.
Por ejemplo, para comparar la distribución de los datos antes y después de la transformación:
from scipy.stats import probplot
# Gráfico Q-Q de los datos originales
plt.figure(figsize=(12, 6))
probplot(serie_temporal_ajustada, dist="norm", plot=plt)
plt.title('Q-Q plot de datos originales')
plt.show()
# Gráfico Q-Q de los datos transformados con Box-Cox
plt.figure(figsize=(12, 6))
probplot(serie_boxcox, dist="norm", plot=plt)
plt.title('Q-Q plot de datos transformados (Box-Cox)')
plt.show()
Al analizar los gráficos Q-Q, podemos observar si los puntos se ajustan a la línea diagonal, lo que indicaría una distribución aproximadamente normal tras la transformación.
Detección y tratamiento de valores atípicos en series temporales
En el análisis de series temporales, la presencia de valores atípicos puede distorsionar los resultados de los modelos predictivos. Los valores atípicos son observaciones que se alejan significativamente del patrón general de la serie y pueden deberse a errores de medición, eventos puntuales o cambios estructurales en los datos.
Detectar y tratar adecuadamente los valores atípicos es esencial para mejorar la calidad del modelo y obtener predicciones más precisas. A continuación, se presentan técnicas para identificar y manejar valores atípicos en series temporales utilizando Scikit-Learn y Python.
Identificación de valores atípicos con métodos estadísticos
Una forma común de detectar valores atípicos es mediante métodos estadísticos basados en la distribución de los datos. Por ejemplo, se pueden considerar como atípicos aquellos valores que se encuentran más allá de tres desviaciones estándar de la media.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Crear un generador de números aleatorios
rng = np.random.default_rng(42)
# Generar una serie temporal sintética
fechas = pd.date_range('2024-01-01', periods=200, freq='D')
# Generar valores con una componente sinusoidal y ruido normal
valores = rng.normal(loc=0, scale=0.5, size=200) + np.sin(np.linspace(0, 20, 200))
# Introducir valores atípicos
valores[50] += 6
valores[150] -= 5
# Crear la serie temporal
serie_temporal = pd.Series(valores, index=fechas)
# Visualizar la serie temporal
plt.figure(figsize=(12, 6))
plt.plot(serie_temporal, label='Datos originales')
plt.title('Serie temporal con valores atípicos')
plt.xlabel('Fecha')
plt.ylabel('Valor')
plt.legend()
plt.show()
En este ejemplo, se crea una serie temporal con dos valores atípicos intencionales. Los picos anómalos pueden afectar el análisis y el rendimiento de los algoritmos de aprendizaje automático.
Para detectar los valores atípicos basados en la desviación estándar:
# Calcular la media y la desviación estándar
media = serie_temporal.mean()
desviacion_estandar = serie_temporal.std()
# Definir umbrales para detectar valores atípicos
umbral_superior = media + 3 * desviacion_estandar
umbral_inferior = media - 3 * desviacion_estandar
# Identificar los valores atípicos
valores_atipicos = serie_temporal[(serie_temporal > umbral_superior) | (serie_temporal < umbral_inferior)]
Ahora, visualizamos los valores atípicos detectados:
# Visualizar los valores atípicos
plt.figure(figsize=(12, 6))
plt.plot(serie_temporal, label='Datos originales')
plt.scatter(valores_atipicos.index, valores_atipicos.values, color='red', label='Valores atípicos')
plt.title('Detección de valores atípicos')
plt.xlabel('Fecha')
plt.ylabel('Valor')
plt.legend()
plt.show()
Los puntos rojos indican los valores atípicos identificados. Este método es sencillo pero puede no ser eficaz si la serie tiene estacionalidad o tendencia.
Detección de valores atípicos con el rango intercuartílico (IQR)
El uso del rango intercuartílico es otra técnica estadística robusta para detectar valores atípicos:
# Calcular el primer y tercer cuartil
Q1 = serie_temporal.quantile(0.25)
Q3 = serie_temporal.quantile(0.75)
IQR = Q3 - Q1
# Definir umbrales usando el IQR
umbral_inferior = Q1 - 1.5 * IQR
umbral_superior = Q3 + 1.5 * IQR
# Identificar valores atípicos
valores_atipicos_IQR = serie_temporal[(serie_temporal < umbral_inferior) | (serie_temporal > umbral_superior)]
Visualizamos los valores atípicos detectados con el método IQR:
# Visualizar los valores atípicos (IQR)
plt.figure(figsize=(12, 6))
plt.plot(serie_temporal, label='Datos originales')
plt.scatter(valores_atipicos_IQR.index, valores_atipicos_IQR.values, color='orange', label='Valores atípicos (IQR)')
plt.title('Detección de valores atípicos con IQR')
plt.xlabel('Fecha')
plt.ylabel('Valor')
plt.legend()
plt.show()
El método IQR es menos sensible a las distribuciones no normales y es más robusto frente a valores extremos.
Detección de valores atípicos con algoritmos de aprendizaje automático
Scikit-Learn proporciona algoritmos diseñados para la detección de anomalías, como Isolation Forest y Local Outlier Factor (LOF).
Isolation Forest:
from sklearn.ensemble import IsolationForest
# Preparar los datos para el modelo
X = serie_temporal.values.reshape(-1, 1)
# Crear y ajustar el modelo Isolation Forest
modelo_iso = IsolationForest(contamination=0.01, random_state=42)
modelo_iso.fit(X)
# Predecir las anomalías (-1: anómalo, 1: normal)
predicciones = modelo_iso.predict(X)
# Extraer los valores atípicos
valores_atipicos_iso = serie_temporal[predicciones == -1]
Visualizamos los valores atípicos detectados por Isolation Forest:
# Visualizar los valores atípicos (Isolation Forest)
plt.figure(figsize=(12, 6))
plt.plot(serie_temporal, label='Datos originales')
plt.scatter(valores_atipicos_iso.index, valores_atipicos_iso.values, color='green', label='Anomalías (Isolation Forest)')
plt.title('Detección con Isolation Forest')
plt.xlabel('Fecha')
plt.ylabel('Valor')
plt.legend()
plt.show()
Local Outlier Factor (LOF):
from sklearn.neighbors import LocalOutlierFactor
# Aplicar LOF para detectar valores atípicos
modelo_lof = LocalOutlierFactor(n_neighbors=20, contamination=0.01)
predicciones_lof = modelo_lof.fit_predict(X)
# Extraer los valores atípicos
valores_atipicos_lof = serie_temporal[predicciones_lof == -1]
Visualizamos los valores atípicos detectados por LOF:
# Visualizar los valores atípicos (LOF)
plt.figure(figsize=(12, 6))
plt.plot(serie_temporal, label='Datos originales')
plt.scatter(valores_atipicos_lof.index, valores_atipicos_lof.values, color='purple', label='Anomalías (LOF)')
plt.title('Detección con Local Outlier Factor')
plt.xlabel('Fecha')
plt.ylabel('Valor')
plt.legend()
plt.show()
Estos algoritmos son útiles para detectar valores atípicos en datos multidimensionales y pueden capturar patrones más complejos.
Tratamiento de valores atípicos
Una vez identificados los valores atípicos, es importante decidir cómo tratarlos para mejorar la calidad del análisis. Algunas técnicas comunes son:
- Eliminación de los valores atípicos.
- Imputación utilizando valores estadísticos como la media o la mediana.
- Interpolación basada en los valores vecinos.
- Transformación para reducir el impacto de los atípicos.
Imputación con la mediana:
# Reemplazar los valores atípicos por la mediana
mediana = serie_temporal.median()
serie_imputada = serie_temporal.copy()
serie_imputada[valores_atipicos.index] = mediana
Visualizamos la serie con imputación:
# Visualizar serie con imputación
plt.figure(figsize=(12, 6))
plt.plot(serie_temporal, label='Original')
plt.plot(serie_imputada, label='Imputada (mediana)', color='red')
plt.title('Tratamiento de valores atípicos con imputación')
plt.xlabel('Fecha')
plt.ylabel('Valor')
plt.legend()
plt.show()
Interpolación lineal de valores atípicos:
# Reemplazar valores atípicos por NaN y aplicar interpolación
serie_interpolada = serie_temporal.copy()
serie_interpolada[valores_atipicos.index] = np.nan
serie_interpolada = serie_interpolada.interpolate(method='linear')
Visualizamos la serie interpolada:
# Visualizar serie interpolada
plt.figure(figsize=(12, 6))
plt.plot(serie_temporal, label='Original')
plt.plot(serie_interpolada, label='Interpolada', color='orange')
plt.title('Tratamiento de valores atípicos con interpolación')
plt.xlabel('Fecha')
plt.ylabel('Valor')
plt.legend()
plt.show()
La interpolación es adecuada cuando se espera que el valor atípico sea un error y se desea estimar un valor más probable basado en los datos circundantes.
Aplicación de técnicas de reducción de dimensionalidad (PCA)
La reducción de dimensionalidad es una técnica esencial al trabajar con series temporales multivariadas. En muchos casos, los datos temporales contienen múltiples variables que pueden estar altamente correlacionadas, lo que dificulta el análisis y el modelado. El Análisis de Componentes Principales (PCA) permite transformar las variables originales en un conjunto reducido de componentes principales que capturan la mayor parte de la variabilidad presente en los datos.
Aplicar PCA a series temporales ayuda a simplificar el conjunto de datos, reducir el ruido y mejorar el rendimiento de los algoritmos de aprendizaje automático. A continuación, exploraremos cómo implementar PCA utilizando Scikit-Learn y Python.
Preparación de los datos de series temporales
Suponemos que disponemos de una serie temporal multivariada con varias variables numéricas registradas a lo largo del tiempo. Para ilustrar el proceso, generaremos un conjunto de datos sintéticos:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Crear un generador de números aleatorios
rng = np.random.default_rng(42)
# Generar fechas
fechas = pd.date_range(start='2024-01-01', periods=200, freq='D')
# Crear variables simuladas utilizando el generador
variable_1 = np.sin(np.linspace(0, 20, 200)) + rng.normal(loc=0, scale=0.5, size=200)
variable_2 = np.cos(np.linspace(0, 20, 200)) + rng.normal(loc=0, scale=0.5, size=200)
variable_3 = variable_1 + variable_2 + rng.normal(loc=0, scale=0.2, size=200)
variable_4 = rng.random(size=200) * 2
# Construir DataFrame
datos = pd.DataFrame({
'Var1': variable_1,
'Var2': variable_2,
'Var3': variable_3,
'Var4': variable_4
}, index=fechas)
En este ejemplo, hemos creado cuatro variables, algunas de las cuales están correlacionadas. Antes de aplicar PCA, es fundamental estandarizar las variables, ya que PCA es sensible a la escala de los datos.
Estandarización de las variables
Utilizamos StandardScaler
para estandarizar las variables:
from sklearn.preprocessing import StandardScaler
# Seleccionar las variables para PCA
X = datos.values
# Estandarizar los datos
escalador = StandardScaler()
X_escalado = escalador.fit_transform(X)
Aplicación del Análisis de Componentes Principales
Aplicamos PCA para reducir la dimensionalidad de los datos:
from sklearn.decomposition import PCA
# Definir el número de componentes principales
n_componentes = 2
# Crear el modelo PCA
pca = PCA(n_components=n_componentes)
# Ajustar el modelo y transformar los datos
X_pca = pca.fit_transform(X_escalado)
El objeto pca
contiene información sobre los componentes principales, incluyendo la varianza explicada por cada componente:
# Porcentaje de varianza explicada por cada componente
varianza_explicada = pca.explained_variance_ratio_
print('Varianza explicada por componente:', varianza_explicada)
# Varianza acumulada
varianza_acumulada = np.cumsum(varianza_explicada)
print('Varianza explicada acumulada:', varianza_acumulada)
Por ejemplo, podríamos obtener una salida similar a:
Varianza explicada por componente: [0.49647566 0.28235176]
Varianza explicada acumulada: [0.49647566 0.77882742]
Esto indica que los dos primeros componentes principales explican aproximadamente el 87.5% de la variabilidad total de los datos.
Visualización de los componentes principales
Podemos visualizar los datos proyectados en los componentes principales:
# Crear un DataFrame con los componentes principales
df_pca = pd.DataFrame(X_pca, columns=['PC1', 'PC2'], index=datos.index)
# Graficar los componentes principales
plt.figure(figsize=(10, 6))
plt.scatter(df_pca['PC1'], df_pca['PC2'], c=np.arange(len(df_pca)), cmap='viridis')
plt.colorbar(label='Índice temporal')
plt.xlabel('Componente Principal 1')
plt.ylabel('Componente Principal 2')
plt.title('Datos proyectados en los componentes principales')
plt.show()
La gráfica muestra cómo los datos se distribuyen en el espacio reducido, lo que puede ayudar a identificar patrones o clusters.
Reconstrucción de los datos originales
Es posible aproximar los datos originales utilizando los componentes principales seleccionados:
# Reconstruir los datos aproximados
X_aproximado = pca.inverse_transform(X_pca)
# Invertir la estandarización
X_recuperado = escalador.inverse_transform(X_aproximado)
# Crear un DataFrame con los datos recuperados
datos_recuperados = pd.DataFrame(X_recuperado, columns=datos.columns, index=datos.index)
Comparar los datos originales con los datos recuperados permite evaluar la pérdida de información debido a la reducción de dimensionalidad.
Evaluación del error de reconstrucción
Calculamos el error cuadrático medio entre los datos originales y los recuperados:
from sklearn.metrics import mean_squared_error
# Calcular el error para cada variable
errores = {}
for i, columna in enumerate(datos.columns):
errores[columna] = mean_squared_error(datos[columna], datos_recuperados[columna])
print('Errores de reconstrucción por variable:', errores)
Un error bajo indica que los componentes principales seleccionados capturan adecuadamente la información relevante.
Integración con modelos predictivos
La reducción de dimensionalidad puede mejorar el rendimiento de los modelos de aprendizaje automático. A continuación, entrenamos un modelo de regresión lineal utilizando los componentes principales:
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
# Supongamos que Var4 es nuestra variable objetivo
y = datos['Var4'].values
# Dividir los datos en entrenamiento y prueba
X_entrenamiento, X_prueba, y_entrenamiento, y_prueba = train_test_split(
X_pca, y, test_size=0.2, shuffle=False)
# Entrenar el modelo
modelo = LinearRegression()
modelo.fit(X_entrenamiento, y_entrenamiento)
# Realizar predicciones
predicciones = modelo.predict(X_prueba)
# Calcular el error cuadrático medio
mse = mean_squared_error(y_prueba, predicciones)
print('Error cuadrático medio en el conjunto de prueba:', mse)
Al utilizar los componentes principales, el modelo puede ser más eficiente y menos propenso al sobreajuste, especialmente cuando el número de variables originales es grande.
Creación de un Pipeline con PCA
Integrar PCA en un Pipeline de Scikit-Learn facilita la gestión de las etapas de preprocesamiento y modelado:
from sklearn.pipeline import Pipeline
# Crear el pipeline
pipeline = Pipeline([
('escalado', StandardScaler()),
('pca', PCA(n_components=2)),
('regresion', LinearRegression())
], memory = None)
# Entrenar el pipeline
pipeline.fit(X_entrenamiento, y_entrenamiento)
# Realizar predicciones
predicciones_pipeline = pipeline.predict(X_prueba)
# Calcular el error cuadrático medio
mse_pipeline = mean_squared_error(y_prueba, predicciones_pipeline)
print('Error cuadrático medio con Pipeline:', mse_pipeline)
El uso de un Pipeline asegura que las transformaciones se apliquen de manera consistente y permite un flujo de trabajo más organizado.
Selección del número óptimo de componentes
Para determinar el número adecuado de componentes principales, analizamos la varianza explicada acumulada:
# Ajustar PCA sin especificar el número de componentes
pca_completa = PCA()
pca_completa.fit(X_escalado)
# Calcular la varianza explicada acumulada
varianza_acumulada = np.cumsum(pca_completa.explained_variance_ratio_)
# Graficar la varianza explicada acumulada
plt.figure(figsize=(8, 5))
plt.plot(range(1, len(varianza_acumulada) + 1), varianza_acumulada, marker='o')
plt.xlabel('Número de componentes')
plt.ylabel('Varianza explicada acumulada')
plt.title('Determinación del número óptimo de componentes')
plt.grid(True)
plt.show()
La gráfica ayuda a identificar el punto en el que agregar más componentes aporta beneficios marginales en términos de varianza explicada.
Consideraciones al aplicar PCA en series temporales
Al aplicar PCA en series temporales, es importante tener en cuenta:
- Preservación de la estructura temporal: Asegurarse de que las transformaciones no alteren el orden cronológico de los datos.
- Efecto de la autocorrelación: Las variables temporales pueden estar autocorrelacionadas, lo que puede influir en la efectividad de PCA.
- Interpretabilidad: Los componentes principales pueden ser combinaciones complejas de las variables originales, lo que dificulta su interpretación directa.
Aplicación práctica en series temporales financieras
En análisis financiero, PCA es útil para reducir la dimensionalidad de un conjunto de acciones o índices que están correlacionados. Esto permite identificar factores subyacentes que influyen en el mercado y construir indicadores más robustos.
Uso de PCA para detección de anomalías
PCA también puede emplearse para detectar anomalías en series temporales. Al reconstruir los datos a partir de un número reducido de componentes, las observaciones que presentan grandes errores de reconstrucción pueden considerarse anómalas.
# Calcular el error de reconstrucción por observación
errores_reconstruccion = np.mean((X_escalado - X_aproximado) ** 2, axis=1)
# Umbral para considerar una observación como anómala
umbral = np.percentile(errores_reconstruccion, 95)
# Identificar las observaciones anómalas
anomalías = errores_reconstruccion > umbral
# Visualizar las anomalías
plt.figure(figsize=(10, 6))
plt.plot(datos.index, errores_reconstruccion, label='Error de reconstrucción')
plt.axhline(y=umbral, color='red', linestyle='--', label='Umbral de anomalía')
plt.xlabel('Fecha')
plt.ylabel('Error')
plt.title('Detección de anomalías con PCA')
plt.legend()
plt.show()
Esta técnica puede ser útil en el monitoreo de sistemas industriales o en la detección de fraudes.
Resumen
La aplicación de PCA en series temporales es una herramienta poderosa para simplificar el análisis y mejorar el rendimiento de los modelos. Al reducir la dimensionalidad, se facilita la interpretación de los datos y se optimiza el procesamiento computacional, sin perder información significativa.
Selección y extracción de características relevantes
En el análisis de series temporales, la selección y extracción de características relevantes es fundamental para mejorar el rendimiento y la interpretabilidad de los modelos. Al identificar las variables más significativas, podemos reducir la complejidad del modelo, evitar el sobreajuste y aumentar la eficiencia computacional.
Sin embargo, la selección de características en datos temporales presenta desafíos únicos debido a las dependencias temporales presentes en las observaciones. Por ello, es importante utilizar técnicas que tengan en cuenta la naturaleza secuencial de las series temporales.
A continuación, exploraremos métodos para seleccionar y extraer características relevantes en series temporales utilizando Scikit-Learn y Python. Incluiremos ejemplos de código completos que demuestran cómo aplicar estas técnicas en la práctica.
Selección univariante de características
La selección univariante evalúa cada característica individualmente en relación con la variable objetivo. En contextos de regresión con series temporales, podemos utilizar pruebas estadísticas como la información mutua para medir la dependencia entre las características y el objetivo.
Scikit-Learn proporciona la clase SelectKBest
combinada con funciones de puntuación como mutual_info_regression
. A continuación, mostramos cómo aplicar esta técnica:
import numpy as np
import pandas as pd
from sklearn.feature_selection import SelectKBest, mutual_info_regression
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import TimeSeriesSplit
from sklearn.metrics import mean_squared_error
# Crear un generador de números aleatorios usando la nueva API
rng = np.random.default_rng(42)
# Generar fechas
fechas = pd.date_range(start='2024-01-01', periods=200, freq='D')
# Crear variables simuladas utilizando el generador
X = pd.DataFrame({
'feature_1': rng.random(200),
'feature_2': rng.random(200),
'feature_3': rng.random(200),
'feature_4': rng.random(200)
}, index=fechas)
# Variable objetivo (depende de feature_1 y feature_3)
y = 2 * X['feature_1'] - 3 * X['feature_3'] + rng.normal(loc=0, scale=0.1, size=200)
# Convertir a arreglos numpy
X_values = X.values
y_values = y.values
# División de datos respetando el orden temporal
tscv = TimeSeriesSplit(n_splits=5)
for train_index, test_index in tscv.split(X_values):
X_train, X_test = X_values[train_index], X_values[test_index]
y_train, y_test = y_values[train_index], y_values[test_index]
# Aplicar SelectKBest con mutual_info_regression
selector = SelectKBest(score_func=mutual_info_regression, k=2)
X_train_selected = selector.fit_transform(X_train, y_train)
X_test_selected = selector.transform(X_test)
# Obtener los nombres de las características seleccionadas
mascaras = selector.get_support()
caracteristicas_seleccionadas = X.columns[mascaras]
print("Características seleccionadas:", caracteristicas_seleccionadas)
# Entrenar un modelo de regresión lineal
modelo = LinearRegression()
modelo.fit(X_train_selected, y_train)
# Realizar predicciones y evaluar el modelo
y_pred = modelo.predict(X_test_selected)
error = mean_squared_error(y_test, y_pred)
print("Error cuadrático medio:", error)
En este ejemplo, utilizamos SelectKBest
para seleccionar las dos características más relevantes según la información mutua con la variable objetivo. Al imprimir las características seleccionadas, observamos que el método identifica correctamente las variables más importantes.
Selección basada en modelos
Otra técnica efectiva es la selección de características basada en modelos. Los modelos lineales con regularización L1, como la Regresión Lasso, pueden reducir los coeficientes de características irrelevantes a cero, realizando una forma de selección automática.
A continuación, mostramos cómo aplicar la Regresión Lasso para la selección de características:
from sklearn.linear_model import Lasso
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
# Crear un pipeline que incluya el escalado y el modelo Lasso
pipeline = Pipeline([
('escalado', StandardScaler()),
('lasso', Lasso(alpha=0.1))
], memory = None)
# Entrenar el modelo
pipeline.fit(X_train, y_train)
# Obtener los coeficientes del modelo
coeficientes = pipeline.named_steps['lasso'].coef_
# Identificar las características relevantes
caracteristicas_relevantes = X.columns[coeficientes != 0]
print("Características relevantes según Lasso:", caracteristicas_relevantes)
# Evaluar el modelo
y_pred = pipeline.predict(X_test)
error = mean_squared_error(y_test, y_pred)
print("Error cuadrático medio:", error)
En este caso, la Regresión Lasso selecciona automáticamente las características con mayor peso en el modelo, eliminando aquellas menos significativas al reducir sus coeficientes a cero.
Eliminación recursiva de características (RFE)
La Eliminación Recursiva de Características (RFE) es un método que selecciona características de forma iterativa, entrenando un modelo y eliminando las menos importantes en cada paso hasta alcanzar el número deseado de características.
Ejemplo de aplicación de RFE:
from sklearn.feature_selection import RFE
# Utilizar un modelo de regresión lineal como estimador
modelo_base = LinearRegression()
# Configurar RFE para seleccionar las 2 mejores características
rfe = RFE(estimator=modelo_base, n_features_to_select=2)
# Ajustar RFE al conjunto de entrenamiento
rfe.fit(X_train, y_train)
# Obtener las características seleccionadas
caracteristicas_rfe = X.columns[rfe.support_]
print("Características seleccionadas por RFE:", caracteristicas_rfe)
# Realizar predicciones y evaluar el modelo
y_pred = rfe.predict(X_test)
error = mean_squared_error(y_test, y_pred)
print("Error cuadrático medio:", error)
RFE utiliza el estimador especificado para determinar la importancia de cada característica y selecciona aquellas que contribuyen más al rendimiento del modelo.
Importancia de características con modelos basados en árboles
Los modelos basados en árboles, como los Random Forest, proporcionan estimaciones de importancia de características basadas en la reducción de impureza o en la disminución del error al utilizar una característica en particular.
Ejemplo con Random Forest:
from sklearn.ensemble import RandomForestRegressor
# Entrenar un modelo Random Forest
modelo_rf = RandomForestRegressor(n_estimators=100, random_state=42)
modelo_rf.fit(X_train, y_train)
# Obtener importancias de las características
importancias = modelo_rf.feature_importances_
for nombre, importancia in zip(X.columns, importancias):
print(f"Importancia de {nombre}: {importancia:.4f}")
# Seleccionar características con importancia por encima de un umbral
umbral = 0.25
caracteristicas_importantes = X.columns[importancias > umbral]
print("Características importantes según Random Forest:", caracteristicas_importantes)
# Entrenar de nuevo el modelo con características seleccionadas
X_train_imp = X_train[:, importancias > umbral]
X_test_imp = X_test[:, importancias > umbral]
modelo_rf.fit(X_train_imp, y_train)
# Realizar predicciones y evaluar el modelo
y_pred = modelo_rf.predict(X_test_imp)
error = mean_squared_error(y_test, y_pred)
print("Error cuadrático medio:", error)
Los modelos de Random Forest pueden capturar relaciones no lineales y consideraciones de interacción entre características, proporcionando una perspectiva diferente sobre su relevancia.
Integración de la selección de características en un Pipeline
Es recomendable integrar la selección de características dentro de un Pipeline de Scikit-Learn para asegurar un flujo de trabajo coherente y evitar filtraciones de información. Esto es particularmente importante en series temporales, donde el orden de los datos es crítico.
Ejemplo de Pipeline con selección de características:
from sklearn.pipeline import Pipeline
# Crear el Pipeline
pipeline = Pipeline([
('seleccion', SelectKBest(score_func=mutual_info_regression, k=2)),
('modelo', LinearRegression())
], memory = None)
# Entrenar el Pipeline
pipeline.fit(X_train, y_train)
# Evaluar el modelo
y_pred = pipeline.predict(X_test)
error = mean_squared_error(y_test, y_pred)
print("Error cuadrático medio con Pipeline:", error)
Al incluir la selección de características en el Pipeline, garantizamos que este paso se aplique de forma consistente durante el entrenamiento y la predicción, manteniendo la integridad del modelo.
Consideraciones al seleccionar características en series temporales
Al aplicar técnicas de selección de características en series temporales, es fundamental tener en cuenta:
- Dependencias temporales: Algunas características pueden ser relevantes en ciertos periodos y no en otros. Es útil analizar la relevancia de las características en diferentes ventanas temporales.
- Estacionariedad: Verificar si las características y la variable objetivo son estacionarias. Si no lo son, pueden ser necesarias transformaciones adicionales.
- Colinealidad: Las características altamente correlacionadas pueden redundar en la información aportada al modelo. Técnicas como la reducción de dimensionalidad o la regresión penalizada pueden ayudar a abordar este problema.
Extracción de características relevantes
Además de la selección, la extracción de características implica transformar las características originales para obtener nuevas representaciones que capturen la esencia de los datos.
Ejemplo de extracción de características usando análisis de componentes independientes (ICA):
from sklearn.decomposition import FastICA
# Aplicar ICA para extraer componentes independientes
ica = FastICA(n_components=2, random_state=42)
X_train_ica = ica.fit_transform(X_train)
X_test_ica = ica.transform(X_test)
# Entrenar un modelo con las nuevas características
modelo = LinearRegression()
modelo.fit(X_train_ica, y_train)
# Evaluar el modelo
y_pred = modelo.predict(X_test_ica)
error = mean_squared_error(y_test, y_pred)
print("Error cuadrático medio utilizando ICA:", error)
El uso de técnicas de extracción de características puede revelar estructuras subyacentes en los datos que no son evidentes en las variables originales.
Métricas para evaluar la relevancia de las características
Es importante utilizar métricas adecuadas para evaluar la relevancia de las características seleccionadas. La importancia de características y las puntuaciones de selección proporcionan información útil para tomar decisiones informadas.
Por ejemplo, con SelectKBest
, podemos obtener las puntuaciones:
# Obtener las puntuaciones de cada característica
puntuaciones = selector.scores_
for nombre, puntuacion in zip(X.columns, puntuaciones):
print(f"Puntuación de {nombre}: {puntuacion:.4f}")
Esta información nos ayuda a entender qué tan influyente es cada característica en relación con la variable objetivo.
Consideraciones finales
La selección y extracción de características relevantes en series temporales es un paso clave para construir modelos fiables y eficientes. Al aplicar técnicas apropiadas y considerar las peculiaridades de los datos temporales, podemos mejorar significativamente el rendimiento de nuestros modelos de aprendizaje automático.
Ejercicios de esta lección Transformación y escalado de series temporales
Evalúa tus conocimientos de esta lección Transformación y escalado de series temporales con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Todas las lecciones de ScikitLearn
Accede a todas las lecciones de ScikitLearn y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Aprendizaje Automático
Introducción Y Entorno
Introducción E Instalación
Introducción Y Entorno
Introducción Al Preprocesamiento De Datos
Preprocesamiento De Datos
Identificación Y Tratamiento De Valores Faltantes
Preprocesamiento De Datos
Escalado De Datos
Preprocesamiento De Datos
Normalización De Datos
Preprocesamiento De Datos
Codificación De Variables Categóricas
Preprocesamiento De Datos
Ingeniería De Características
Preprocesamiento De Datos
Selección De Características
Preprocesamiento De Datos
Extracción De Características
Preprocesamiento De Datos
Particionamiento De Datos
Preprocesamiento De Datos
Preprocesamiento De Datos Desbalanceados
Preprocesamiento De Datos
Introducción A La Regresión
Regresión
Regresión Lineal
Regresión
Regresión Knn Kneighborsregressor
Regresión
Regresión Svm Con Svr
Regresión
Regresión Con Árboles Decisiontreeregressor
Regresión
Regresión Con Algoritmos De Conjunto
Regresión
Introducción A La Clasificación
Clasificación
Clasificación Con Regresión Logística
Clasificación
Clasificación Knn Kneighborsclassifier
Clasificación
Clasificación Svm Con Svc
Clasificación
Clasificación Con Árboles Decisiontreeclassifier
Clasificación
Clasificación Con Algoritmos De Conjunto
Clasificación
Reducción De La Dimensionalidad Con Pca
Aprendizaje No Supervisado
Clustering Con Kmeans
Aprendizaje No Supervisado
Clustering Jerárquico
Aprendizaje No Supervisado
Clustering De Densidad Con Dbscan
Aprendizaje No Supervisado
Preprocesamiento De Textos Para Nlp
Nlp
Representación De Texto Y Extracción De Características
Nlp
Clasificación De Texto Con Scikit Learn
Nlp
Análisis De Sentimiento
Nlp
Técnicas Avanzadas De Extracción De Características
Nlp
Introducción Al Análisis De Series Temporales
Series Temporales
Preprocesamiento De Datos De Series Temporales
Series Temporales
Ingeniería De Características Para Series Temporales
Series Temporales
Transformación Y Escalado De Series Temporales
Series Temporales
Validación Y Evaluación De Modelos En Series Temporales
Series Temporales
Validación Y Evaluación De Modelos
Validación De Modelos
Técnicas De Validación Cruzada
Validación De Modelos
Métricas De Regresión
Validación De Modelos
Métricas De Clasificación
Validación De Modelos
Ajuste De Hiperparámetros
Validación De Modelos
Introducción A Pipelines
Pipelines Y Despliegue
Creación De Pipelines Básicos
Pipelines Y Despliegue
Preprocesamiento De Datos Con Pipelines
Pipelines Y Despliegue
Pipelines Y Validación Cruzada
Pipelines Y Despliegue
Pipelines Con Columntransformer
Pipelines Y Despliegue
Exportar E Importar Pipelines
Pipelines Y Despliegue
Objetivos de aprendizaje de esta lección
- Comprender la importancia de la normalización y la estandarización en la preparación de series temporales.
- Aprender a aplicar normalización con MinMaxScaler y estandarización con StandardScaler.
- Saber incorporar correctamente estas transformaciones en flujos de datos temporales, evitando la fuga de información.
- Evaluar el impacto de las transformaciones en el rendimiento de los algoritmos de aprendizaje automático mediante gráficos comparativos.