Menu iconMenu icon
Machine Learning Hero

Chapter 3: Data Preprocessing and Feature Engineering

3.4 Técnicas de escalado, normalización y transformación de datos

La escala y distribución de tu conjunto de datos puede influir profundamente en la efectividad de muchos modelos, especialmente aquellos que dependen en gran medida de cálculos de distancia o que emplean técnicas de optimización basadas en gradientes.

Muchos algoritmos de aprendizaje automático operan bajo la suposición de que todas las características tienen una escala uniforme. Si no se aborda esta suposición, las características con rangos más amplios pueden dominar el proceso de entrenamiento, mientras que las características con rangos más estrechos pueden perderse. Para mitigar estos desafíos y garantizar un rendimiento óptimo del modelo, se emplean diversas técnicas de preprocesamiento de datos, como el escalado, la normalización y otros métodos transformativos.

3.4.1 Por qué el escalado y la normalización de datos son importantes

Los modelos de aprendizaje automático, especialmente aquellos que dependen de cálculos de distancia o de optimización basada en gradientes, son muy sensibles a la escala y el rango de las características de entrada. Esta sensibilidad puede conducir a problemas significativos en el rendimiento del modelo si no se abordan correctamente.

1. K-Nearest Neighbors (KNN)

KNN es un algoritmo de aprendizaje automático que se basa en gran medida en cálculos de distancia entre puntos de datos para hacer predicciones o clasificaciones. Sin embargo, la efectividad de KNN puede verse significativamente afectada por la escala de las diferentes características en el conjunto de datos.

Cuando las características en un conjunto de datos tienen escalas muy diferentes, esto puede conducir a resultados sesgados e inexactos en los algoritmos KNN. Las características con rangos numéricos más grandes influirán de manera desproporcionada en los cálculos de distancia, eclipsando el impacto de las características con rangos más pequeños.

Ejemplo concreto:

Considera un conjunto de datos con dos características: ingresos anuales y edad. Los ingresos anuales pueden variar de miles a millones (por ejemplo, $30,000 a $1,000,000), mientras que la edad típicamente varía de 0 a 100. En este escenario:

  • La característica de ingresos, debido a su escala mucho más grande, dominará los cálculos de distancia. Incluso una pequeña diferencia en ingresos (por ejemplo, $10,000) crearía una distancia mucho mayor que una diferencia significativa en la edad (por ejemplo, 20 años).
  • Esto significa que el algoritmo ignoraría prácticamente la característica de la edad, basando sus decisiones casi exclusivamente en las diferencias de ingresos.
  • Como resultado, dos personas con ingresos similares pero edades muy diferentes podrían ser consideradas "vecinos cercanos" por el algoritmo, incluso si la diferencia de edad es crucial para el análisis.

Esto puede conducir a problemas como:

  • Clasificación incorrecta: El algoritmo puede clasificar incorrectamente los puntos de datos basándose en la característica que domina.
  • Pérdida de información: Se pierden perspectivas valiosas de características con escalas más pequeñas, como la edad.
  • Reducción del rendimiento del modelo: La precisión y fiabilidad del modelo KNN pueden verse comprometidas.

Para mitigar estos problemas, es crucial aplicar técnicas adecuadas de escalado (como la estandarización o normalización) para garantizar que todas las características contribuyan proporcionalmente a los cálculos de distancia. Este paso de preprocesamiento ayuda a crear un campo de juego equilibrado para todas las características, lo que permite que el algoritmo KNN haga predicciones más precisas basadas en similitudes relevantes entre puntos de datos.

2. Máquinas de Vectores de Soporte (SVM)

Las Máquinas de Vectores de Soporte (SVM) son algoritmos poderosos utilizados para tareas de clasificación y regresión. Funcionan encontrando el hiperplano óptimo que mejor separa las diferentes clases en el espacio de características. Sin embargo, cuando las características están en diferentes escalas, las SVM pueden enfrentar desafíos significativos:

  • Determinación del hiperplano: El principio central de las SVM es maximizar el margen entre las clases. Cuando las características tienen escalas muy diferentes, el algoritmo puede tener dificultades para encontrar este hiperplano óptimo de manera eficiente. Esto se debe a que la característica con la escala más grande dominará los cálculos de distancia utilizados para determinar el margen.
  • Sesgo en la importancia de las características: Las características con magnitudes mayores podrían recibir una importancia indebida al determinar la frontera de decisión. Por ejemplo, si una característica varía de 0 a 1 y otra de 0 a 1000, la segunda tendrá una influencia mucho mayor en el proceso de toma de decisiones de la SVM, incluso si no es inherentemente más importante para la tarea de clasificación.
  • Impacto en la función kernel: Muchas SVM utilizan funciones kernel (como el kernel RBF) para mapear los datos a espacios de mayor dimensión. Estas funciones suelen depender de los cálculos de distancia entre puntos de datos. Cuando las características están en diferentes escalas, estos cálculos de distancia pueden distorsionarse, lo que lleva a un rendimiento subóptimo de la función kernel.
  • Problemas de convergencia: El proceso de optimización en las SVM puede volverse más lento y menos estable cuando las características no están escaladas de manera uniforme. Esto se debe a que el paisaje de optimización se vuelve más complejo y potencialmente más difícil de navegar cuando las características tienen rangos muy diferentes.
  • Dificultades de interpretación: En las SVM lineales, los coeficientes de la función de decisión pueden interpretarse como la importancia de las características. Sin embargo, cuando las características están en diferentes escalas, estos coeficientes se vuelven difíciles de comparar e interpretar con precisión.

Para mitigar estos problemas, es crucial aplicar técnicas de escalado adecuadas (como la estandarización o normalización) antes de entrenar una SVM. Esto asegura que todas las características contribuyan proporcionalmente al proceso de toma de decisiones del modelo, lo que lleva a resultados más precisos y confiables.

3. Algoritmos basados en gradiente

Las redes neuronales y otros métodos basados en gradiente emplean con frecuencia técnicas de optimización como el descenso de gradiente. Estos algoritmos son particularmente sensibles a la escala de las características de entrada, y cuando las características tienen escalas muy diferentes, pueden surgir varios problemas:

  • Paisaje de optimización alargado: Cuando las características tienen escalas diferentes, el paisaje de optimización se alarga y distorsiona. Esto significa que los contornos de la función de pérdida se estiran en la dirección de la característica con la mayor escala. Como resultado, el algoritmo de descenso de gradiente puede zigzaguear de un lado a otro a lo largo del valle estrecho de la superficie de error alargada, lo que dificulta la convergencia eficiente hacia la solución óptima.
  • Sensibilidad a la tasa de aprendizaje: La tasa de aprendizaje, un hiperparámetro crucial en el descenso de gradiente, se vuelve más difícil de ajustar correctamente cuando las características tienen escalas diferentes. Una tasa de aprendizaje que funcione bien para una característica podría ser demasiado grande o pequeña para otra, lo que provocaría que se sobrepase el mínimo o que la convergencia sea lenta.
  • Dominancia de características: Las características con escalas más grandes pueden dominar el proceso de aprendizaje, lo que hace que el modelo sea demasiado sensible a los cambios en estas características mientras subvalora el impacto de las características con escalas más pequeñas. Esto puede llevar a un modelo sesgado que no capture con precisión las relaciones verdaderas en los datos.
  • Convergencia más lenta: Debido a los desafíos mencionados, el proceso de optimización a menudo requiere más iteraciones para converger. Esto resulta en tiempos de entrenamiento más largos, lo que puede ser problemático al trabajar con conjuntos de datos grandes o modelos complejos.
  • Soluciones subóptimas: En algunos casos, las dificultades para navegar por el paisaje de optimización pueden causar que el algoritmo se atasque en mínimos locales o puntos de silla, lo que lleva a soluciones subóptimas. Esto significa que el modelo final podría no rendir tan bien como lo haría si las características estuvieran adecuadamente escaladas.
  • Inestabilidad numérica: Las grandes diferencias en las escalas de las características a veces pueden llevar a inestabilidad numérica durante el cálculo de gradientes, especialmente cuando se utiliza aritmética de punto flotante. Esto puede resultar en problemas como gradientes que explotan o desaparecen, lo que es particularmente problemático en redes neuronales profundas.

Para mitigar estos problemas, es crucial aplicar técnicas de escalado adecuadas, como la estandarización o normalización, antes de entrenar modelos basados en gradiente. Esto asegura que todas las características contribuyan proporcionalmente al proceso de optimización, lo que lleva a una convergencia más rápida, un entrenamiento más estable y un rendimiento potencialmente mejor del modelo.

4. Modelos lineales

En la regresión lineal o regresión logística, los coeficientes del modelo representan directamente el impacto o la importancia de cada característica sobre el resultado predicho. Esta interpretabilidad es una de las ventajas clave de los modelos lineales. Sin embargo, cuando las características están en escalas muy diferentes, comparar estos coeficientes se vuelve problemático y puede llevar a una mala interpretación de la importancia de las características.

Por ejemplo, considera un modelo de regresión lineal que predice precios de casas basado en dos características: el número de habitaciones (que típicamente varía de 1 a 10) y el área en pies cuadrados (que podría variar de 500 a 5000). Sin un escalado adecuado:

  • El coeficiente para el área en pies cuadrados probablemente sería mucho menor que el coeficiente para el número de habitaciones, simplemente debido a la diferencia en la escala de los datos.
  • Esto podría sugerir erróneamente que el número de habitaciones tiene un impacto más significativo en el precio de la casa que el área en pies cuadrados, cuando en realidad ambas características podrían ser igualmente importantes o el área en pies cuadrados podría ser más influyente.

Además, cuando las características están en diferentes escalas:

  • El proceso de optimización durante el entrenamiento del modelo puede verse afectado negativamente, lo que lleva a una convergencia más lenta o soluciones subóptimas.
  • Algunas características podrían dominar a otras únicamente debido a su escala más grande, en lugar de su verdadero poder predictivo.
  • El modelo se vuelve más sensible a pequeños cambios en las características con escalas más grandes, lo que puede provocar inestabilidad en las predicciones.

Al aplicar técnicas de escalado adecuadas, nos aseguramos de que todas las características contribuyan proporcionalmente al modelo, en función de su verdadera importancia y no de su escala numérica. Esto no solo mejora el rendimiento del modelo, sino que también mejora su interpretabilidad, permitiendo comparaciones más precisas y significativas de la importancia de las características a través de sus respectivos coeficientes.

Para ilustrar, considera un conjunto de datos donde una característica representa ingresos (que varía de miles a millones) y otra representa la edad (que varía de 0 a 100). Sin un escalado adecuado:

  • La característica de ingresos dominaría los cálculos de distancia en KNN.
  • Las SVM podrían tener dificultades para encontrar una frontera de decisión óptima.
  • Las redes neuronales podrían enfrentar dificultades en la optimización de los pesos.
  • Los modelos lineales producirían coeficientes que no son directamente comparables.

Para abordar estos problemas, empleamos técnicas de escalado y normalización. Estos métodos transforman todas las características a una escala común, asegurando que cada característica contribuya de manera proporcional al proceso de toma de decisiones del modelo. Las técnicas comunes incluyen:

  • Escalado Min-Max: Escala las características a un rango fijo, típicamente [0, 1].
  • Estandarización: Transforma las características para que tengan una media de cero y una varianza unitaria.
  • Escalado robusto: Utiliza estadísticas que son robustas a valores atípicos, como la mediana y el rango intercuartílico.

Al aplicar estas técnicas, creamos un campo de juego equilibrado para todas las características, permitiendo que los modelos aprendan de cada característica de manera equitativa. Esto no solo mejora el rendimiento del modelo, sino que también mejora la interpretabilidad y la capacidad de generalización a nuevos datos no vistos.

3.4.2 Escalado Min-Max

El escalado min-max, también conocido como normalización, es una técnica fundamental de preprocesamiento de datos que transforma las características a un rango específico, típicamente entre 0 y 1. Este método es esencial en el aprendizaje automático por varias razones:

  1. Escalado de características: Esta técnica asegura que todas las características estén en una escala comparable, evitando que aquellas con magnitudes mayores eclipsen a las que tienen magnitudes menores. Por ejemplo, si una característica varía entre 0 y 100 y otra entre 0 y 1, el escalado min-max normalizaría ambas al rango 0-1, permitiendo que contribuyan de manera equitativa al proceso de toma de decisiones del modelo.
  2. Mejora de la eficiencia de los algoritmos: Muchos algoritmos de aprendizaje automático, especialmente aquellos que dependen de cálculos de distancia o de optimización mediante descenso de gradiente, muestran un mejor rendimiento cuando las características están escaladas de manera similar. Esto incluye algoritmos populares como K-Nearest Neighbors (KNN), Máquinas de Vectores de Soporte (SVM) y varias arquitecturas de redes neuronales. Al igualar las escalas de las características, creamos un espacio de características más equilibrado para que estos algoritmos funcionen de manera óptima.
  3. Retención de valores cero: A diferencia de otros métodos de escalado como la estandarización, el escalado min-max mantiene los valores cero en conjuntos de datos dispersos. Esta característica es particularmente crucial para ciertos tipos de datos o algoritmos donde los valores cero tienen un significado importante, como en el análisis de texto o los sistemas de recomendación.
  4. Manejo de valores atípicos: Aunque el escalado min-max es sensible a los valores atípicos, puede ser ventajoso en escenarios donde se desea preservar la distribución relativa de los valores de las características, al tiempo que se comprime el rango general. Este enfoque puede ayudar a mitigar el impacto de los valores extremos sin eliminar por completo su influencia en el modelo.
  5. Facilidad de interpretación: Los valores escalados resultantes de la normalización min-max son fáciles de interpretar, ya que representan la posición relativa del valor original dentro de su rango. Esta propiedad facilita la comprensión de la importancia de las características y las comparaciones relativas entre diferentes puntos de datos.

Sin embargo, es importante tener en cuenta que el escalado min-max tiene limitaciones. No centra los datos alrededor de cero, lo que puede ser problemático para algunos algoritmos. Además, no maneja bien los valores atípicos, ya que los valores extremos pueden comprimir el rango escalado de la mayoría de los puntos de datos. Por lo tanto, la decisión de utilizar el escalado min-max debe tomarse en función de los requisitos específicos de los datos y los algoritmos que se planea utilizar.

La fórmula para el escalado min-max es:

Donde:

  • X es el valor original de la característica,
  • X' es el valor escalado,
  • X_{min} y X_{max} son los valores mínimos y máximos de la característica, respectivamente.

Aplicación del Escalado Min-Max con Scikit-learn

Scikit-learn ofrece una clase poderosa y fácil de usar llamada MinMaxScaler para implementar el escalado min-max. Esta herramienta versátil simplifica el proceso de transformar las características a un rango específico, típicamente entre 0 y 1, asegurando que todas las variables contribuyan de manera equitativa al proceso de toma de decisiones del modelo.

Al aprovechar este escalador, los científicos de datos pueden normalizar eficientemente sus conjuntos de datos, lo que facilita la creación de modelos de aprendizaje automático más precisos y robustos.

Ejemplo: Escalado Min-Max con Scikit-learn

from sklearn.preprocessing import MinMaxScaler
import pandas as pd

# Sample data
data = {'Age': [25, 30, 35, 40],
        'Income': [50000, 60000, 70000, 80000]}
df = pd.DataFrame(data)

# Initialize the MinMaxScaler
scaler = MinMaxScaler()

# Fit and transform the data
scaled_data = scaler.fit_transform(df)

# Convert the scaled data back to a DataFrame
df_scaled = pd.DataFrame(scaled_data, columns=['Age', 'Income'])
print(df_scaled)

3.4.3 Estandarización (Normalización Z-Score)

La estandarización (también conocida como normalización Z-score) transforma los datos para que tengan una media de 0 y una desviación estándar de 1. Esta técnica es particularmente útil para modelos que asumen que los datos están distribuidos normalmente, como la regresión lineal y la regresión logística. La estandarización es menos afectada por valores atípicos que el escalado min-max porque se centra en la distribución de los datos en lugar de su rango.

La fórmula para la estandarización es:


Z = \frac {X - \mu}{\sigma}


Donde:

  • X es el valor original de la característica,
  • \mu es la media de la característica,
  • \sigma es la desviación estándar de la característica.

Aplicación de la estandarización con Scikit-learn

Scikit-learn proporciona un StandardScaler para estandarizar características.

Ejemplo: Estandarización con Scikit-learn

from sklearn.preprocessing import StandardScaler

# Initialize the StandardScaler
scaler = StandardScaler()

# Fit and transform the data
standardized_data = scaler.fit_transform(df)

# Convert the standardized data back to a DataFrame
df_standardized = pd.DataFrame(standardized_data, columns=['Age', 'Income'])
print(df_standardized)

Aquí, "Edad" e "Ingreso" se transforman para tener una media de 0 y una desviación estándar de 1. Esto asegura que las características contribuyan equitativamente al modelo, especialmente en algoritmos como la regresión logística o redes neuronales.

3.4.4 Escalado Robusto

El escalado robusto es otra técnica de escalado que es particularmente efectiva cuando se trata con datos que contienen valores atípicos. A diferencia de la estandarización y el escalado min-max, que pueden verse fuertemente influenciados por valores extremos, el escalado robusto utiliza la mediana y el rango intercuartílico (IQR) para escalar los datos, haciéndolo más resistente a los valores atípicos.

La fórmula para el escalado robusto es:


X' = \frac{X - Q_2}{IQR}

Donde:

  • Q_2 es la mediana de los datos,
  • IQR es el rango intercuartílico, es decir, la diferencia entre los percentiles 75 y 25.

Aplicación del Escalado Robusto con Scikit-learn

Scikit-learn ofrece una clase potente y versátil llamada RobustScaler, que aplica eficientemente el escalado robusto a las características. Este escalador es particularmente útil cuando se trabaja con conjuntos de datos que contienen valores atípicos o cuando se desea que el método de escalado sea menos sensible a valores extremos.

Al aprovechar la mediana y el rango intercuartílico (IQR) en lugar de la media y la desviación estándar, el RobustScaler ofrece un enfoque más robusto para el escalado de características, manteniendo la integridad de la distribución de los datos incluso en presencia de valores atípicos.

Ejemplo: Escalado Robusto con Scikit-learn

import numpy as np
import pandas as pd
from sklearn.preprocessing import RobustScaler
from sklearn.datasets import make_regression

# Generate sample data
X, y = make_regression(n_samples=100, n_features=2, noise=0.1, random_state=42)
df = pd.DataFrame(X, columns=['Feature1', 'Feature2'])

# Add some outliers
df.loc[0, 'Feature1'] = 1000
df.loc[1, 'Feature2'] = -1000

print("Original data:")
print(df.describe())

# Initialize the RobustScaler
scaler = RobustScaler()

# Fit and transform the data
robust_scaled_data = scaler.fit_transform(df)

# Convert the robust scaled data back to a DataFrame
df_robust_scaled = pd.DataFrame(robust_scaled_data, columns=['Feature1', 'Feature2'])

print("\nRobust scaled data:")
print(df_robust_scaled.describe())

# Compare original and scaled data for a few samples
print("\nComparison of original and scaled data:")
print(pd.concat([df.head(), df_robust_scaled.head()], axis=1))

# Inverse transform to get back original scale
df_inverse = pd.DataFrame(scaler.inverse_transform(robust_scaled_data), columns=['Feature1', 'Feature2'])

print("\nInverse transformed data:")
print(df_inverse.head())

Desglose del Código:

  1. Generación de datos:
    • Usamos make_regression de Scikit-learn para crear un conjunto de datos de muestra con 100 muestras y 2 características.
    • Se agregan valores atípicos artificiales para demostrar la robustez del escalado.
  2. Inicialización de RobustScaler:
    • Creamos una instancia de RobustScaler de Scikit-learn.
    • Por defecto, utiliza el rango intercuartílico (IQR) y la mediana para el escalado.
  3. Ajuste y Transformación:
    • Usamos el método fit_transform() para ajustar el escalador a los datos y transformarlos.
    • Este método calcula la mediana y el IQR para cada característica y luego aplica la transformación.
  4. Creación de un DataFrame:
    • Los datos escalados se convierten de nuevo en un DataFrame de pandas para facilitar la visualización y comparación.
  5. Análisis de Resultados:
    • Imprimimos estadísticas descriptivas tanto de los datos originales como de los escalados.
    • Los datos escalados deberían tener una mediana cercana a 0 y un IQR cercano a 1 para cada característica.
  6. Comparación:
    • Mostramos algunas muestras de los datos originales y escalados lado a lado.
    • Esto ayuda a visualizar cómo afecta el escalado a puntos de datos individuales.
  7. Transformación Inversa:
    • Demostramos cómo revertir el escalado usando inverse_transform().
    • Esto es útil cuando se necesita convertir las predicciones o los datos transformados de nuevo a la escala original.

Este ejemplo de código muestra el flujo completo de uso de RobustScaler, desde la preparación de los datos hasta el escalado y la retrotransformación. Resalta la capacidad del escalador para manejar valores atípicos y proporciona una clara comparación entre los datos originales y los escalados.

En este ejemplo, el escalado robusto garantiza que los valores extremos (valores atípicos) tengan una influencia menor en el proceso de escalado. Esto es particularmente útil en conjuntos de datos donde los valores atípicos están presentes, pero no deben dominar el entrenamiento del modelo.

3.4.5. Transformaciones Logarítmicas

En casos donde las características exhiben una distribución muy sesgada, una transformación logarítmica puede ser una herramienta invaluable para comprimir el rango de valores y reducir la asimetría. Esta técnica es particularmente útil para características como ingresospoblación o precios de acciones, donde los valores pueden abarcar varios órdenes de magnitud.

La transformación logarítmica funciona aplicando la función logaritmo a cada valor en el conjunto de datos. Esto tiene varios efectos beneficiosos:

  • Compresión de valores grandes: Los valores extremadamente grandes se acercan más al resto de los datos, reduciendo el impacto de los valores atípicos.
  • Expansión de valores pequeños: Los valores pequeños se expanden, permitiendo una mejor diferenciación entre ellos.
  • Normalización de la distribución: La transformación a menudo da como resultado una distribución más parecida a la normal, lo que es beneficioso para muchos métodos estadísticos y algoritmos de aprendizaje automático.

Por ejemplo, considera una distribución de ingresos donde los valores varían entre $10,000 y $1,000,000. Después de aplicar una transformación logarítmica:

  • $10,000 se convierte en log(10,000) ≈ 9.21
  • $100,000 se convierte en log(100,000) ≈ 11.51
  • $1,000,000 se convierte en log(1,000,000) ≈ 13.82

Como puedes ver, la gran diferencia entre los valores más altos y más bajos se ha reducido significativamente, haciendo que los datos sean más fáciles de interpretar y procesar para los modelos. Esto puede mejorar el rendimiento del modelo, especialmente en algoritmos que son sensibles a la escala de las características de entrada.

Sin embargo, es importante tener en cuenta que las transformaciones logarítmicas deben usarse con precaución. Son más efectivas cuando los datos están sesgados positivamente y abarcan varios órdenes de magnitud. Además, las transformaciones logarítmicas solo pueden aplicarse a valores positivos, ya que el logaritmo de cero o números negativos no está definido en los sistemas de números reales.

Aplicación de Transformaciones Logarítmicas

Las transformaciones logarítmicas se usan comúnmente para características con una distribución sesgada a la derecha, como ingresos o precios de propiedades.

Ejemplo: Transformación Logarítmica con NumPy

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

# Create a sample dataset
np.random.seed(42)
income = np.random.lognormal(mean=10, sigma=1, size=1000)
df = pd.DataFrame({'Income': income})

# Apply log transformation
df['Log_Income'] = np.log(df['Income'])

# Print summary statistics
print("Original Income:")
print(df['Income'].describe())
print("\nLog-transformed Income:")
print(df['Log_Income'].describe())

# Visualize the distributions
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))

ax1.hist(df['Income'], bins=50, edgecolor='black')
ax1.set_title('Original Income Distribution')
ax1.set_xlabel('Income')
ax1.set_ylabel('Frequency')

ax2.hist(df['Log_Income'], bins=50, edgecolor='black')
ax2.set_title('Log-transformed Income Distribution')
ax2.set_xlabel('Log(Income)')
ax2.set_ylabel('Frequency')

plt.tight_layout()
plt.show()

# Calculate skewness
original_skewness = np.mean(((df['Income'] - df['Income'].mean()) / df['Income'].std())**3)
log_skewness = np.mean(((df['Log_Income'] - df['Log_Income'].mean()) / df['Log_Income'].std())**3)

print(f"\nOriginal Income Skewness: {original_skewness:.2f}")
print(f"Log-transformed Income Skewness: {log_skewness:.2f}")

# Demonstrate inverse transformation
inverse_income = np.exp(df['Log_Income'])
print("\nInverse Transformation (first 5 rows):")
print(pd.DataFrame({'Original': df['Income'][:5], 'Log': df['Log_Income'][:5], 'Inverse': inverse_income[:5]}))

Desglose del Código:

  1. Generación de Datos:
    • Usamos random.lognormal() de NumPy para generar un conjunto de datos de muestra de 1000 valores de ingresos.
    • La distribución lognormal se utiliza a menudo para modelar ingresos, ya que naturalmente produce una distribución sesgada a la derecha.
    • Establecemos una semilla aleatoria para garantizar la reproducibilidad.
  2. Transformación Logarítmica:
    • Aplicamos el logaritmo natural (base e) a la columna 'Income' utilizando la función log() de NumPy.
    • Esto crea una nueva columna 'Log_Income' en nuestro DataFrame.
  3. Estadísticas Resumidas:
    • Imprimimos estadísticas descriptivas tanto de los ingresos originales como de los transformados mediante logaritmo utilizando el método describe() de Pandas.
    • Esto nos permite comparar las características de la distribución antes y después de la transformación.
  4. Visualización:
    • Creamos histogramas de las distribuciones de ingresos originales y transformados logarítmicamente.
    • Esta representación visual ayuda a ver claramente el efecto de la transformación logarítmica en la distribución de los datos.
  5. Cálculo de la Asimetría:
    • Calculamos la asimetría de ambas distribuciones utilizando operaciones de NumPy.
    • La asimetría cuantifica la falta de simetría en la distribución. Un valor cercano a 0 indica una distribución más simétrica.
  6. Transformación Inversa:
    • Demostramos cómo revertir la transformación logarítmica utilizando la función exp() de NumPy.
    • Esto es crucial cuando se necesita interpretar los resultados en la escala original después de realizar análisis sobre datos transformados.

Este ejemplo muestra todo el proceso de transformación logarítmica, desde la generación de datos hasta el análisis y la visualización, utilizando principalmente operaciones de NumPy. Demuestra cómo la transformación logarítmica puede hacer que una distribución sesgada a la derecha sea más simétrica, lo que a menudo es beneficioso para el análisis estadístico y los algoritmos de aprendizaje automático.

En este ejemplo, la transformación logarítmica reduce el amplio rango de valores de ingresos, haciendo que la distribución sea más manejable para los algoritmos de aprendizaje automático. Es importante destacar que las transformaciones logarítmicas solo deben aplicarse a valores positivos, ya que el logaritmo de un número negativo no está definido.

3.4.6 Transformaciones de Potencia

Las transformaciones de potencia son técnicas estadísticas avanzadas utilizadas para modificar la distribución de los datos. Dos ejemplos prominentes son las transformaciones Box-Cox y Yeo-Johnson. Estos métodos tienen dos propósitos principales:

  1. Estabilización de la varianza: Estas transformaciones ayudan a asegurar que la variabilidad de los datos se mantenga constante en todo su rango, lo cual es una suposición crucial en muchos análisis estadísticos. Al aplicar transformaciones de potencia, los investigadores pueden mitigar problemas relacionados con la heterocedasticidad, donde la dispersión de los residuos varía a lo largo del rango de una variable predictora. Esta estabilización de la varianza puede conducir a inferencias estadísticas más confiables y un mejor rendimiento del modelo.
  2. Normalización de distribuciones: Las transformaciones de potencia buscan hacer que los datos se asemejen más a una distribución normal (gaussiana), lo que es beneficioso para muchas pruebas estadísticas y algoritmos de aprendizaje automático. Al cambiar la forma de la distribución de los datos, estas transformaciones pueden ayudar a cumplir con la suposición de normalidad requerida por muchos métodos estadísticos paramétricos. Este proceso de normalización puede revelar patrones ocultos en los datos, mejorar la interpretabilidad de los resultados y potencialmente mejorar el poder predictivo de varios modelos de aprendizaje automático, especialmente aquellos que asumen entradas distribuidas normalmente.

Las transformaciones de potencia son especialmente valiosas cuando se trabaja con características que exhiben distribuciones no normales, como aquellas con una asimetría o curtosis significativa. Al aplicar estas transformaciones, los científicos de datos pueden mejorar el rendimiento y la confiabilidad de sus modelos, especialmente aquellos que asumen entradas distribuidas normalmente.

La transformación de Box-Cox, introducida por los estadísticos George Box y David Cox en 1964, es aplicable solo a datos positivos. Involucra encontrar un parámetro óptimo λ (lambda) que determina la transformación de potencia específica a aplicar. Por otro lado, la transformación de Yeo-Johnson, desarrollada por In-Kwon Yeo y Richard Johnson en el año 2000, extiende el concepto para manejar tanto valores positivos como negativos, haciéndola más versátil en la práctica.

Al emplear estas transformaciones, los analistas pueden descubrir relaciones en los datos que de otro modo podrían estar ocultas, lo que conduce a predicciones más precisas y mejores conocimientos en diversos campos como finanzas, biología y ciencias sociales.

a. Transformación de Box-Cox

La transformación de Box-Cox es una poderosa técnica estadística que solo se puede aplicar a datos positivos. Este método es particularmente útil para abordar la no normalidad en las distribuciones de datos y estabilizar la varianza. Aquí tienes una explicación más detallada:

  1. Selección del parámetro óptimo: La transformación de Box-Cox encuentra un parámetro de transformación óptimo, denotado como λ (lambda). Este parámetro determina la transformación de potencia específica que se aplicará a los datos.
  2. Estabilización de la varianza: Uno de los principales objetivos de la transformación de Box-Cox es estabilizar la varianza a lo largo del rango de los datos. Esto es crucial para muchos análisis estadísticos que asumen homocedasticidad (varianza constante).
  3. Normalización: La transformación busca hacer que los datos se asemejen más a una distribución normal. Esto es beneficioso para muchas pruebas estadísticas y algoritmos de aprendizaje automático que asumen normalidad.
  4. Fórmula matemática: La transformación de Box-Cox se define como:
    • y(λ) = (x^λ - 1) / λ,  if λ ≠ 0
    • y(λ) = log(x),         if λ = 0
      Donde x son los datos originales y y(\lambda) son los datos transformados.
  5. Interpretación: Diferentes valores de λ resultan en diferentes transformaciones. Por ejemplo, λ = 1 significa que no hay transformación, λ = 0 es equivalente a una transformación logarítmica, y λ = 0.5 es equivalente a una transformación por raíz cuadrada.

Al aplicar esta transformación, los analistas pueden descubrir relaciones en los datos que de otro modo podrían estar ocultas, lo que lleva a predicciones más precisas y mejores conocimientos en campos como finanzas, biología y ciencias sociales.

Ejemplo: Transformación de Box-Cox con Scikit-learn

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import PowerTransformer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

# Create a sample dataset
np.random.seed(42)
income = np.random.lognormal(mean=10, sigma=1, size=1000)
age = np.random.normal(loc=40, scale=10, size=1000)
df = pd.DataFrame({'Income': income, 'Age': age})

# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(
    df[['Income', 'Age']], df['Income'], test_size=0.2, random_state=42)

# Initialize the PowerTransformer for Box-Cox (only for positive data)
boxcox_transformer = PowerTransformer(method='box-cox', standardize=True)

# Fit and transform the training data
X_train_transformed = boxcox_transformer.fit_transform(X_train)

# Transform the test data
X_test_transformed = boxcox_transformer.transform(X_test)

# Train a linear regression model on the original data
model_original = LinearRegression()
model_original.fit(X_train, y_train)

# Train a linear regression model on the transformed data
model_transformed = LinearRegression()
model_transformed.fit(X_train_transformed, y_train)

# Make predictions
y_pred_original = model_original.predict(X_test)
y_pred_transformed = model_transformed.predict(X_test_transformed)

# Calculate performance metrics
mse_original = mean_squared_error(y_test, y_pred_original)
r2_original = r2_score(y_test, y_pred_original)
mse_transformed = mean_squared_error(y_test, y_pred_transformed)
r2_transformed = r2_score(y_test, y_pred_transformed)

# Print results
print("Original Data Performance:")
print(f"Mean Squared Error: {mse_original:.2f}")
print(f"R-squared Score: {r2_original:.2f}")
print("\nTransformed Data Performance:")
print(f"Mean Squared Error: {mse_transformed:.2f}")
print(f"R-squared Score: {r2_transformed:.2f}")

# Visualize the distributions
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))

ax1.hist(X_train['Income'], bins=50, edgecolor='black')
ax1.set_title('Original Income Distribution')
ax1.set_xlabel('Income')
ax1.set_ylabel('Frequency')

ax2.hist(X_train_transformed[:, 0], bins=50, edgecolor='black')
ax2.set_title('Box-Cox Transformed Income Distribution')
ax2.set_xlabel('Transformed Income')
ax2.set_ylabel('Frequency')

plt.tight_layout()
plt.show()

Desglose del Código:

  1. Importar bibliotecas necesarias: Importamos NumPy, Pandas, Matplotlib y varios módulos de Scikit-learn para la manipulación de datos, visualización y tareas de aprendizaje automático.
  2. Crear un conjunto de datos de muestra: Generamos un conjunto de datos sintético con características de 'Ingresos' (distribuidos lognormalmente) y 'Edad' (distribuida normalmente).
  3. Dividir los datos: Usamos train_test_split de Scikit-learn para dividir los datos en conjuntos de entrenamiento y prueba.
  4. Inicializar PowerTransformer: Creamos un objeto PowerTransformer para la transformación de Box-Cox, estableciendo standardize=True para asegurar que la salida tenga una media de cero y una varianza unitaria.
  5. Aplicar la transformación de Box-Cox: Ajustamos el transformador con los datos de entrenamiento y transformamos tanto los datos de entrenamiento como los de prueba.
  6. Entrenar modelos de regresión lineal: Creamos dos modelos de LinearRegression: uno para los datos originales y otro para los datos transformados.
  7. Hacer predicciones y evaluar: Usamos ambos modelos para hacer predicciones en el conjunto de prueba y calculamos el error cuadrático medio (MSE) y los puntajes de R-cuadrado utilizando las métricas de Scikit-learn.
  8. Visualizar distribuciones: Creamos histogramas para comparar las distribuciones de ingresos originales y transformados.

Este ejemplo integral muestra todo el proceso de aplicar una transformación de Box-Cox usando Scikit-learn, desde la preparación de los datos hasta la evaluación del modelo. Demuestra cómo la transformación puede afectar el rendimiento del modelo y la distribución de los datos, proporcionando un contexto práctico para entender el impacto de las transformaciones de potencia en los flujos de trabajo de aprendizaje automático.

b. Transformación de Yeo-Johnson

La transformación de Yeo-Johnson es una extensión de la transformación de Box-Cox que ofrece una mayor flexibilidad en el preprocesamiento de datos. Mientras que Box-Cox está limitado a datos estrictamente positivos, Yeo-Johnson puede manejar tanto valores positivos como negativos, lo que la hace más versátil para conjuntos de datos del mundo real. Esta transformación fue desarrollada por In-Kwon Yeo y Richard A. Johnson en el año 2000 para abordar las limitaciones de Box-Cox.

Las características clave de la transformación de Yeo-Johnson incluyen:

  • Aplicabilidad a todos los números reales: A diferencia de Box-Cox, Yeo-Johnson puede aplicarse a valores cero y negativos, eliminando la necesidad de ajustar los datos.
  • Continuidad en cero: La transformación es continua en λ = 0, lo que asegura transiciones suaves entre diferentes transformaciones de potencia.
  • Efecto de normalización: Similar a Box-Cox, ayuda a normalizar datos sesgados, lo que potencialmente mejora el rendimiento de los algoritmos de aprendizaje automático que asumen entradas distribuidas normalmente.
  • Estabilización de la varianza: Puede ayudar a estabilizar la varianza en todo el rango de los datos, abordando problemas de heterocedasticidad en los análisis estadísticos.

La formulación matemática de la transformación de Yeo-Johnson es un poco más compleja que la de Box-Cox, ya que acomoda tanto valores positivos como negativos a través de diferentes ecuaciones basadas en el signo del valor de entrada. Esta complejidad adicional permite una mayor adaptabilidad a diversos conjuntos de datos, lo que la convierte en una herramienta poderosa en el conjunto de herramientas de preprocesamiento de un científico de datos.

Ejemplo: Transformación de Yeo-Johnson con Scikit-learn

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import PowerTransformer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

# Create a sample dataset with both positive and negative values
np.random.seed(42)
income = np.random.lognormal(mean=10, sigma=1, size=1000)
expenses = np.random.normal(loc=50000, scale=10000, size=1000)
net_income = income - expenses
df = pd.DataFrame({'Income': income, 'Expenses': expenses, 'NetIncome': net_income})

# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(
    df[['Income', 'Expenses']], df['NetIncome'], test_size=0.2, random_state=42)

# Initialize the PowerTransformer for Yeo-Johnson
yeojohnson_transformer = PowerTransformer(method='yeo-johnson', standardize=True)

# Fit and transform the training data
X_train_transformed = yeojohnson_transformer.fit_transform(X_train)

# Transform the test data
X_test_transformed = yeojohnson_transformer.transform(X_test)

# Train linear regression models on original and transformed data
model_original = LinearRegression().fit(X_train, y_train)
model_transformed = LinearRegression().fit(X_train_transformed, y_train)

# Make predictions
y_pred_original = model_original.predict(X_test)
y_pred_transformed = model_transformed.predict(X_test_transformed)

# Calculate performance metrics
mse_original = mean_squared_error(y_test, y_pred_original)
r2_original = r2_score(y_test, y_pred_original)
mse_transformed = mean_squared_error(y_test, y_pred_transformed)
r2_transformed = r2_score(y_test, y_pred_transformed)

# Print results
print("Original Data Performance:")
print(f"Mean Squared Error: {mse_original:.2f}")
print(f"R-squared Score: {r2_original:.2f}")
print("\nTransformed Data Performance:")
print(f"Mean Squared Error: {mse_transformed:.2f}")
print(f"R-squared Score: {r2_transformed:.2f}")

# Visualize the distributions
fig, axs = plt.subplots(2, 2, figsize=(15, 15))

axs[0, 0].hist(X_train['Income'], bins=50, edgecolor='black')
axs[0, 0].set_title('Original Income Distribution')
axs[0, 0].set_xlabel('Income')
axs[0, 0].set_ylabel('Frequency')

axs[0, 1].hist(X_train_transformed[:, 0], bins=50, edgecolor='black')
axs[0, 1].set_title('Yeo-Johnson Transformed Income Distribution')
axs[0, 1].set_xlabel('Transformed Income')
axs[0, 1].set_ylabel('Frequency')

axs[1, 0].hist(X_train['Expenses'], bins=50, edgecolor='black')
axs[1, 0].set_title('Original Expenses Distribution')
axs[1, 0].set_xlabel('Expenses')
axs[1, 0].set_ylabel('Frequency')

axs[1, 1].hist(X_train_transformed[:, 1], bins=50, edgecolor='black')
axs[1, 1].set_title('Yeo-Johnson Transformed Expenses Distribution')
axs[1, 1].set_xlabel('Transformed Expenses')
axs[1, 1].set_ylabel('Frequency')

plt.tight_layout()
plt.show()

# Print the lambda values used for transformation
print("\nLambda values used for Yeo-Johnson transformation:")
print(yeojohnson_transformer.lambdas_)

Desglose del Código:

  1. Generación de datos: Creamos un conjunto de datos sintético con 'Ingresos' (distribuidos lognormalmente), 'Gastos' (distribuidos normalmente) y 'IngresoNeto' (diferencia entre Ingresos y Gastos). Este conjunto de datos incluye valores tanto positivos como negativos, lo que muestra la capacidad de Yeo-Johnson para manejar tales datos.
  2. División de datos: Usamos train_test_split de Scikit-learn para dividir nuestros datos en conjuntos de entrenamiento y prueba. Esto es crucial para evaluar el rendimiento del modelo con datos no vistos.
  3. Transformación de Yeo-Johnson: Inicializamos un PowerTransformer con method='yeo-johnson'. El parámetro standardize=True asegura que la salida transformada tenga media cero y varianza unitaria.
  4. Entrenamiento del modelo: Entrenamos dos modelos de regresión lineal (LinearRegression): uno con los datos originales y otro con los datos transformados por Yeo-Johnson. Esto nos permite comparar el rendimiento de los modelos con y sin la transformación.
  5. Predicción y evaluación: Usamos ambos modelos para hacer predicciones en el conjunto de prueba y calculamos el error cuadrático medio (MSE) y los puntajes de R-cuadrado utilizando las métricas de Scikit-learn. Esto nos ayuda a cuantificar el impacto de la transformación Yeo-Johnson en el rendimiento del modelo.
  6. Visualización: Creamos histogramas para comparar las distribuciones originales y transformadas tanto de Ingresos como de Gastos. Esta representación visual ayuda a entender cómo la transformación Yeo-Johnson afecta la distribución de los datos.
  7. Valores de Lambda: Imprimimos los valores de lambda utilizados para la transformación de Yeo-Johnson. Estos valores indican la transformación de potencia específica aplicada a cada característica.

Este ejemplo demuestra todo el proceso de aplicar una transformación Yeo-Johnson usando Scikit-learn, desde la preparación de los datos hasta la evaluación y visualización del modelo. Muestra cómo la transformación puede afectar el rendimiento del modelo y la distribución de los datos, proporcionando un contexto práctico para entender el impacto de las transformaciones de potencia en los flujos de trabajo de aprendizaje automático, especialmente cuando se trabaja con conjuntos de datos que incluyen tanto valores positivos como negativos.

3.4.7 Normalización (L1 y L2)

La normalización es una técnica crucial en el preprocesamiento de datos que se utiliza para volver a escalar las características de modo que la norma del vector de características sea 1. Este proceso asegura que todas las características contribuyan de manera equitativa al análisis, evitando que aquellas con magnitudes mayores dominen el modelo. La normalización es particularmente valiosa en algoritmos de aprendizaje automático que dependen de cálculos de distancia, como K-Nearest Neighbors (KNN) o la agrupación K-means.

En KNN, por ejemplo, la normalización ayuda a evitar que las características con escalas más grandes tengan una influencia desproporcionada en los cálculos de distancia. De manera similar, en K-means, las características normalizadas aseguran que la agrupación se base en la importancia relativa de las características en lugar de sus escalas absolutas.

Hay dos tipos principales de normalización:

a. Normalización L1 (norma Manhattan)

La normalización L1, también conocida como norma Manhattan, es un método que asegura que la suma de los valores absolutos de un vector de características sea igual a 1. Esta técnica es particularmente útil en el preprocesamiento de datos para algoritmos de aprendizaje automático. Para entender la normalización L1, desglosémosla matemáticamente:

Para un vector de características x = (x₁, ..., xₙ), la norma L1 se calcula como:

||x||₁ = |x₁| + |x₂| + ... + |xₙ|

donde |xᵢ| representa el valor absoluto de cada característica.

Para lograr la normalización L1, dividimos cada característica por la norma L1:

x_normalizado = x / ||x||₁

Este proceso resulta en un vector de características normalizado donde la suma de los valores absolutos es igual a 1.

Una ventaja notable de la normalización L1 es su menor sensibilidad a los valores atípicos en comparación con la normalización L2. Esta característica la hace particularmente útil en escenarios donde los valores extremos podrían influir desproporcionadamente en el rendimiento del modelo. Además, la normalización L1 puede llevar a vectores de características dispersos, lo que puede ser beneficioso en ciertas aplicaciones de aprendizaje automático, como la selección de características o técnicas de regularización como la regresión Lasso.

Ejemplo de código de normalización L1:

import numpy as np
import pandas as pd
from sklearn.preprocessing import Normalizer
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score

# Create a sample dataset
np.random.seed(42)
X = np.random.rand(100, 3) * 100  # 100 samples, 3 features
y = np.random.randint(0, 2, 100)  # Binary classification

# Split the data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Initialize L1 normalizer
l1_normalizer = Normalizer(norm='l1')

# Fit and transform the training data
X_train_normalized = l1_normalizer.fit_transform(X_train)

# Transform the test data
X_test_normalized = l1_normalizer.transform(X_test)

# Train KNN classifier on original data
knn_original = KNeighborsClassifier(n_neighbors=3)
knn_original.fit(X_train, y_train)
y_pred_original = knn_original.predict(X_test)

# Train KNN classifier on normalized data
knn_normalized = KNeighborsClassifier(n_neighbors=3)
knn_normalized.fit(X_train_normalized, y_train)
y_pred_normalized = knn_normalized.predict(X_test_normalized)

# Calculate accuracies
accuracy_original = accuracy_score(y_test, y_pred_original)
accuracy_normalized = accuracy_score(y_test, y_pred_normalized)

print("Original Data Accuracy:", accuracy_original)
print("L1 Normalized Data Accuracy:", accuracy_normalized)

# Display a sample of original and normalized data
sample_original = X_train[:5]
sample_normalized = X_train_normalized[:5]

print("\nOriginal Data Sample:")
print(pd.DataFrame(sample_original, columns=['Feature 1', 'Feature 2', 'Feature 3']))

print("\nL1 Normalized Data Sample:")
print(pd.DataFrame(sample_normalized, columns=['Feature 1', 'Feature 2', 'Feature 3']))

# Verify L1 norm
print("\nL1 Norm of normalized samples:")
print(np.sum(np.abs(sample_normalized), axis=1))

Desglose del Código:

  1. Generación de datos: Creamos un conjunto de datos sintético con 100 muestras y 3 características, junto con etiquetas de clasificación binaria. Esto simula un escenario del mundo real donde las características pueden tener diferentes escalas.
  2. División de datos: Usamos train_test_split para dividir nuestros datos en conjuntos de entrenamiento y prueba. Esto es crucial para evaluar el rendimiento del modelo con datos no vistos.
  3. Normalización L1: Inicializamos un Normalizer con norm='l1'. Este normalizador se ajusta a los datos de entrenamiento y luego se usa para transformar tanto los datos de entrenamiento como los de prueba.
  4. Entrenamiento del modelo: Entrenamos dos clasificadores KNN: uno con los datos originales y otro con los datos normalizados con L1. Esto nos permite comparar el rendimiento de los modelos con y sin normalización.
  5. Predicción y evaluación: Ambos modelos hacen predicciones en sus respectivos conjuntos de prueba (original y normalizado). Luego, calculamos y comparamos las puntuaciones de precisión para ver el impacto de la normalización L1.
  6. Visualización de datos: Mostramos muestras de los datos originales y normalizados para ilustrar cómo la normalización L1 afecta los valores de las características.
  7. Verificación de la norma L1: Calculamos la suma de los valores absolutos de cada muestra normalizada para verificar que la norma L1 sea igual a 1 después de la normalización.

Este ejemplo muestra todo el proceso de aplicar la normalización L1 utilizando Scikit-learn, desde la preparación de los datos hasta la evaluación del modelo. Demuestra cómo la normalización puede afectar el rendimiento del modelo y la representación de los datos, proporcionando un contexto práctico para comprender el impacto de la normalización L1 en los flujos de trabajo de aprendizaje automático.

b. Normalización L2 (norma euclidiana):

La normalización L2, también conocida como norma euclidiana, es una técnica poderosa que asegura que la suma de los valores cuadrados dentro de un vector de características sea igual a 1. Este método es particularmente efectivo para estandarizar datos en diferentes escalas y dimensiones. Para ilustrarlo, consideremos un vector de características x = (x₁, ..., xₙ). La norma L2 para este vector se calcula utilizando la siguiente fórmula:

||x||₂ = √(x₁² + x₂² + ... + xₙ²)

Una vez que hemos calculado la norma L2, podemos proceder con el proceso de normalización. Esto se logra dividiendo cada característica individual por la norma L2 calculada:

x_normalizado = x / ||x||₂

El vector normalizado resultante mantiene las mismas propiedades direccionales que el original, pero con una longitud unitaria. Esta transformación tiene varias ventajas en las aplicaciones de aprendizaje automático. Por ejemplo, ayuda a mitigar el impacto de los valores atípicos y asegura que todas las características contribuyan de manera equitativa al modelo, independientemente de su escala original.

La normalización L2 se adopta ampliamente en varios algoritmos de aprendizaje automático y es especialmente beneficiosa cuando se trabaja con vectores dispersos. Su popularidad se debe a su capacidad para preservar la importancia relativa de las características mientras se estandarizan sus magnitudes. Esta característica la hace particularmente útil en escenarios como la clasificación de texto, el reconocimiento de imágenes y los sistemas de recomendación, donde el escalado de características puede afectar significativamente el rendimiento del modelo.

Ejemplo de código de normalización L2:

import numpy as np
import pandas as pd
from sklearn.preprocessing import Normalizer
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score

# Create a sample dataset
np.random.seed(42)
X = np.random.rand(100, 3) * 100  # 100 samples, 3 features
y = np.random.randint(0, 2, 100)  # Binary classification

# Split the data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Initialize L2 normalizer
l2_normalizer = Normalizer(norm='l2')

# Fit and transform the training data
X_train_normalized = l2_normalizer.fit_transform(X_train)

# Transform the test data
X_test_normalized = l2_normalizer.transform(X_test)

# Train KNN classifier on original data
knn_original = KNeighborsClassifier(n_neighbors=3)
knn_original.fit(X_train, y_train)
y_pred_original = knn_original.predict(X_test)

# Train KNN classifier on normalized data
knn_normalized = KNeighborsClassifier(n_neighbors=3)
knn_normalized.fit(X_train_normalized, y_train)
y_pred_normalized = knn_normalized.predict(X_test_normalized)

# Calculate accuracies
accuracy_original = accuracy_score(y_test, y_pred_original)
accuracy_normalized = accuracy_score(y_test, y_pred_normalized)

print("Original Data Accuracy:", accuracy_original)
print("L2 Normalized Data Accuracy:", accuracy_normalized)

# Display a sample of original and normalized data
sample_original = X_train[:5]
sample_normalized = X_train_normalized[:5]

print("\nOriginal Data Sample:")
print(pd.DataFrame(sample_original, columns=['Feature 1', 'Feature 2', 'Feature 3']))

print("\nL2 Normalized Data Sample:")
print(pd.DataFrame(sample_normalized, columns=['Feature 1', 'Feature 2', 'Feature 3']))

# Verify L2 norm
print("\nL2 Norm of normalized samples:")
print(np.sqrt(np.sum(np.square(sample_normalized), axis=1)))

Desglose del Código:

  1. Generación de datos: Creamos un conjunto de datos sintético con 100 muestras y 3 características, junto con etiquetas de clasificación binaria. Esto simula un escenario del mundo real donde las características pueden tener diferentes escalas.
  2. División de datos: Usamos train_test_split para dividir nuestros datos en conjuntos de entrenamiento y prueba. Esto es crucial para evaluar el rendimiento del modelo con datos no vistos.
  3. Normalización L2: Inicializamos un Normalizer con norm='l2'. Este normalizador se ajusta a los datos de entrenamiento y luego se usa para transformar tanto los datos de entrenamiento como los de prueba.
  4. Entrenamiento del modelo: Entrenamos dos clasificadores KNN: uno con los datos originales y otro con los datos normalizados con L2. Esto nos permite comparar el rendimiento de los modelos con y sin normalización.
  5. Predicción y evaluación: Ambos modelos hacen predicciones en sus respectivos conjuntos de prueba (original y normalizado). Luego calculamos y comparamos las puntuaciones de precisión para ver el impacto de la normalización L2.
  6. Visualización de datos: Mostramos muestras de los datos originales y normalizados para ilustrar cómo la normalización L2 afecta los valores de las características.
  7. Verificación de la norma L2: Calculamos la norma L2 para cada muestra normalizada para verificar que sea igual a 1 después de la normalización.

Este ejemplo demuestra todo el proceso de aplicar la normalización L2 utilizando Scikit-learn, desde la preparación de los datos hasta la evaluación del modelo. Demuestra cómo la normalización puede afectar el rendimiento del modelo y la representación de los datos, proporcionando un contexto práctico para entender el impacto de la normalización L2 en los flujos de trabajo de aprendizaje automático. La comparación entre las precisiones de los datos originales y normalizados ayuda a ilustrar los posibles beneficios de la normalización L2 para mejorar el rendimiento del modelo, especialmente para algoritmos basados en distancia como KNN.

La elección entre la normalización L1 y L2 depende de los requisitos específicos de tu tarea de aprendizaje automático y la naturaleza de tus datos. Ambos métodos tienen sus fortalezas y son herramientas valiosas en el conjunto de herramientas del científico de datos para preparar características para análisis y entrenamiento de modelos.

3.4 Técnicas de escalado, normalización y transformación de datos

La escala y distribución de tu conjunto de datos puede influir profundamente en la efectividad de muchos modelos, especialmente aquellos que dependen en gran medida de cálculos de distancia o que emplean técnicas de optimización basadas en gradientes.

Muchos algoritmos de aprendizaje automático operan bajo la suposición de que todas las características tienen una escala uniforme. Si no se aborda esta suposición, las características con rangos más amplios pueden dominar el proceso de entrenamiento, mientras que las características con rangos más estrechos pueden perderse. Para mitigar estos desafíos y garantizar un rendimiento óptimo del modelo, se emplean diversas técnicas de preprocesamiento de datos, como el escalado, la normalización y otros métodos transformativos.

3.4.1 Por qué el escalado y la normalización de datos son importantes

Los modelos de aprendizaje automático, especialmente aquellos que dependen de cálculos de distancia o de optimización basada en gradientes, son muy sensibles a la escala y el rango de las características de entrada. Esta sensibilidad puede conducir a problemas significativos en el rendimiento del modelo si no se abordan correctamente.

1. K-Nearest Neighbors (KNN)

KNN es un algoritmo de aprendizaje automático que se basa en gran medida en cálculos de distancia entre puntos de datos para hacer predicciones o clasificaciones. Sin embargo, la efectividad de KNN puede verse significativamente afectada por la escala de las diferentes características en el conjunto de datos.

Cuando las características en un conjunto de datos tienen escalas muy diferentes, esto puede conducir a resultados sesgados e inexactos en los algoritmos KNN. Las características con rangos numéricos más grandes influirán de manera desproporcionada en los cálculos de distancia, eclipsando el impacto de las características con rangos más pequeños.

Ejemplo concreto:

Considera un conjunto de datos con dos características: ingresos anuales y edad. Los ingresos anuales pueden variar de miles a millones (por ejemplo, $30,000 a $1,000,000), mientras que la edad típicamente varía de 0 a 100. En este escenario:

  • La característica de ingresos, debido a su escala mucho más grande, dominará los cálculos de distancia. Incluso una pequeña diferencia en ingresos (por ejemplo, $10,000) crearía una distancia mucho mayor que una diferencia significativa en la edad (por ejemplo, 20 años).
  • Esto significa que el algoritmo ignoraría prácticamente la característica de la edad, basando sus decisiones casi exclusivamente en las diferencias de ingresos.
  • Como resultado, dos personas con ingresos similares pero edades muy diferentes podrían ser consideradas "vecinos cercanos" por el algoritmo, incluso si la diferencia de edad es crucial para el análisis.

Esto puede conducir a problemas como:

  • Clasificación incorrecta: El algoritmo puede clasificar incorrectamente los puntos de datos basándose en la característica que domina.
  • Pérdida de información: Se pierden perspectivas valiosas de características con escalas más pequeñas, como la edad.
  • Reducción del rendimiento del modelo: La precisión y fiabilidad del modelo KNN pueden verse comprometidas.

Para mitigar estos problemas, es crucial aplicar técnicas adecuadas de escalado (como la estandarización o normalización) para garantizar que todas las características contribuyan proporcionalmente a los cálculos de distancia. Este paso de preprocesamiento ayuda a crear un campo de juego equilibrado para todas las características, lo que permite que el algoritmo KNN haga predicciones más precisas basadas en similitudes relevantes entre puntos de datos.

2. Máquinas de Vectores de Soporte (SVM)

Las Máquinas de Vectores de Soporte (SVM) son algoritmos poderosos utilizados para tareas de clasificación y regresión. Funcionan encontrando el hiperplano óptimo que mejor separa las diferentes clases en el espacio de características. Sin embargo, cuando las características están en diferentes escalas, las SVM pueden enfrentar desafíos significativos:

  • Determinación del hiperplano: El principio central de las SVM es maximizar el margen entre las clases. Cuando las características tienen escalas muy diferentes, el algoritmo puede tener dificultades para encontrar este hiperplano óptimo de manera eficiente. Esto se debe a que la característica con la escala más grande dominará los cálculos de distancia utilizados para determinar el margen.
  • Sesgo en la importancia de las características: Las características con magnitudes mayores podrían recibir una importancia indebida al determinar la frontera de decisión. Por ejemplo, si una característica varía de 0 a 1 y otra de 0 a 1000, la segunda tendrá una influencia mucho mayor en el proceso de toma de decisiones de la SVM, incluso si no es inherentemente más importante para la tarea de clasificación.
  • Impacto en la función kernel: Muchas SVM utilizan funciones kernel (como el kernel RBF) para mapear los datos a espacios de mayor dimensión. Estas funciones suelen depender de los cálculos de distancia entre puntos de datos. Cuando las características están en diferentes escalas, estos cálculos de distancia pueden distorsionarse, lo que lleva a un rendimiento subóptimo de la función kernel.
  • Problemas de convergencia: El proceso de optimización en las SVM puede volverse más lento y menos estable cuando las características no están escaladas de manera uniforme. Esto se debe a que el paisaje de optimización se vuelve más complejo y potencialmente más difícil de navegar cuando las características tienen rangos muy diferentes.
  • Dificultades de interpretación: En las SVM lineales, los coeficientes de la función de decisión pueden interpretarse como la importancia de las características. Sin embargo, cuando las características están en diferentes escalas, estos coeficientes se vuelven difíciles de comparar e interpretar con precisión.

Para mitigar estos problemas, es crucial aplicar técnicas de escalado adecuadas (como la estandarización o normalización) antes de entrenar una SVM. Esto asegura que todas las características contribuyan proporcionalmente al proceso de toma de decisiones del modelo, lo que lleva a resultados más precisos y confiables.

3. Algoritmos basados en gradiente

Las redes neuronales y otros métodos basados en gradiente emplean con frecuencia técnicas de optimización como el descenso de gradiente. Estos algoritmos son particularmente sensibles a la escala de las características de entrada, y cuando las características tienen escalas muy diferentes, pueden surgir varios problemas:

  • Paisaje de optimización alargado: Cuando las características tienen escalas diferentes, el paisaje de optimización se alarga y distorsiona. Esto significa que los contornos de la función de pérdida se estiran en la dirección de la característica con la mayor escala. Como resultado, el algoritmo de descenso de gradiente puede zigzaguear de un lado a otro a lo largo del valle estrecho de la superficie de error alargada, lo que dificulta la convergencia eficiente hacia la solución óptima.
  • Sensibilidad a la tasa de aprendizaje: La tasa de aprendizaje, un hiperparámetro crucial en el descenso de gradiente, se vuelve más difícil de ajustar correctamente cuando las características tienen escalas diferentes. Una tasa de aprendizaje que funcione bien para una característica podría ser demasiado grande o pequeña para otra, lo que provocaría que se sobrepase el mínimo o que la convergencia sea lenta.
  • Dominancia de características: Las características con escalas más grandes pueden dominar el proceso de aprendizaje, lo que hace que el modelo sea demasiado sensible a los cambios en estas características mientras subvalora el impacto de las características con escalas más pequeñas. Esto puede llevar a un modelo sesgado que no capture con precisión las relaciones verdaderas en los datos.
  • Convergencia más lenta: Debido a los desafíos mencionados, el proceso de optimización a menudo requiere más iteraciones para converger. Esto resulta en tiempos de entrenamiento más largos, lo que puede ser problemático al trabajar con conjuntos de datos grandes o modelos complejos.
  • Soluciones subóptimas: En algunos casos, las dificultades para navegar por el paisaje de optimización pueden causar que el algoritmo se atasque en mínimos locales o puntos de silla, lo que lleva a soluciones subóptimas. Esto significa que el modelo final podría no rendir tan bien como lo haría si las características estuvieran adecuadamente escaladas.
  • Inestabilidad numérica: Las grandes diferencias en las escalas de las características a veces pueden llevar a inestabilidad numérica durante el cálculo de gradientes, especialmente cuando se utiliza aritmética de punto flotante. Esto puede resultar en problemas como gradientes que explotan o desaparecen, lo que es particularmente problemático en redes neuronales profundas.

Para mitigar estos problemas, es crucial aplicar técnicas de escalado adecuadas, como la estandarización o normalización, antes de entrenar modelos basados en gradiente. Esto asegura que todas las características contribuyan proporcionalmente al proceso de optimización, lo que lleva a una convergencia más rápida, un entrenamiento más estable y un rendimiento potencialmente mejor del modelo.

4. Modelos lineales

En la regresión lineal o regresión logística, los coeficientes del modelo representan directamente el impacto o la importancia de cada característica sobre el resultado predicho. Esta interpretabilidad es una de las ventajas clave de los modelos lineales. Sin embargo, cuando las características están en escalas muy diferentes, comparar estos coeficientes se vuelve problemático y puede llevar a una mala interpretación de la importancia de las características.

Por ejemplo, considera un modelo de regresión lineal que predice precios de casas basado en dos características: el número de habitaciones (que típicamente varía de 1 a 10) y el área en pies cuadrados (que podría variar de 500 a 5000). Sin un escalado adecuado:

  • El coeficiente para el área en pies cuadrados probablemente sería mucho menor que el coeficiente para el número de habitaciones, simplemente debido a la diferencia en la escala de los datos.
  • Esto podría sugerir erróneamente que el número de habitaciones tiene un impacto más significativo en el precio de la casa que el área en pies cuadrados, cuando en realidad ambas características podrían ser igualmente importantes o el área en pies cuadrados podría ser más influyente.

Además, cuando las características están en diferentes escalas:

  • El proceso de optimización durante el entrenamiento del modelo puede verse afectado negativamente, lo que lleva a una convergencia más lenta o soluciones subóptimas.
  • Algunas características podrían dominar a otras únicamente debido a su escala más grande, en lugar de su verdadero poder predictivo.
  • El modelo se vuelve más sensible a pequeños cambios en las características con escalas más grandes, lo que puede provocar inestabilidad en las predicciones.

Al aplicar técnicas de escalado adecuadas, nos aseguramos de que todas las características contribuyan proporcionalmente al modelo, en función de su verdadera importancia y no de su escala numérica. Esto no solo mejora el rendimiento del modelo, sino que también mejora su interpretabilidad, permitiendo comparaciones más precisas y significativas de la importancia de las características a través de sus respectivos coeficientes.

Para ilustrar, considera un conjunto de datos donde una característica representa ingresos (que varía de miles a millones) y otra representa la edad (que varía de 0 a 100). Sin un escalado adecuado:

  • La característica de ingresos dominaría los cálculos de distancia en KNN.
  • Las SVM podrían tener dificultades para encontrar una frontera de decisión óptima.
  • Las redes neuronales podrían enfrentar dificultades en la optimización de los pesos.
  • Los modelos lineales producirían coeficientes que no son directamente comparables.

Para abordar estos problemas, empleamos técnicas de escalado y normalización. Estos métodos transforman todas las características a una escala común, asegurando que cada característica contribuya de manera proporcional al proceso de toma de decisiones del modelo. Las técnicas comunes incluyen:

  • Escalado Min-Max: Escala las características a un rango fijo, típicamente [0, 1].
  • Estandarización: Transforma las características para que tengan una media de cero y una varianza unitaria.
  • Escalado robusto: Utiliza estadísticas que son robustas a valores atípicos, como la mediana y el rango intercuartílico.

Al aplicar estas técnicas, creamos un campo de juego equilibrado para todas las características, permitiendo que los modelos aprendan de cada característica de manera equitativa. Esto no solo mejora el rendimiento del modelo, sino que también mejora la interpretabilidad y la capacidad de generalización a nuevos datos no vistos.

3.4.2 Escalado Min-Max

El escalado min-max, también conocido como normalización, es una técnica fundamental de preprocesamiento de datos que transforma las características a un rango específico, típicamente entre 0 y 1. Este método es esencial en el aprendizaje automático por varias razones:

  1. Escalado de características: Esta técnica asegura que todas las características estén en una escala comparable, evitando que aquellas con magnitudes mayores eclipsen a las que tienen magnitudes menores. Por ejemplo, si una característica varía entre 0 y 100 y otra entre 0 y 1, el escalado min-max normalizaría ambas al rango 0-1, permitiendo que contribuyan de manera equitativa al proceso de toma de decisiones del modelo.
  2. Mejora de la eficiencia de los algoritmos: Muchos algoritmos de aprendizaje automático, especialmente aquellos que dependen de cálculos de distancia o de optimización mediante descenso de gradiente, muestran un mejor rendimiento cuando las características están escaladas de manera similar. Esto incluye algoritmos populares como K-Nearest Neighbors (KNN), Máquinas de Vectores de Soporte (SVM) y varias arquitecturas de redes neuronales. Al igualar las escalas de las características, creamos un espacio de características más equilibrado para que estos algoritmos funcionen de manera óptima.
  3. Retención de valores cero: A diferencia de otros métodos de escalado como la estandarización, el escalado min-max mantiene los valores cero en conjuntos de datos dispersos. Esta característica es particularmente crucial para ciertos tipos de datos o algoritmos donde los valores cero tienen un significado importante, como en el análisis de texto o los sistemas de recomendación.
  4. Manejo de valores atípicos: Aunque el escalado min-max es sensible a los valores atípicos, puede ser ventajoso en escenarios donde se desea preservar la distribución relativa de los valores de las características, al tiempo que se comprime el rango general. Este enfoque puede ayudar a mitigar el impacto de los valores extremos sin eliminar por completo su influencia en el modelo.
  5. Facilidad de interpretación: Los valores escalados resultantes de la normalización min-max son fáciles de interpretar, ya que representan la posición relativa del valor original dentro de su rango. Esta propiedad facilita la comprensión de la importancia de las características y las comparaciones relativas entre diferentes puntos de datos.

Sin embargo, es importante tener en cuenta que el escalado min-max tiene limitaciones. No centra los datos alrededor de cero, lo que puede ser problemático para algunos algoritmos. Además, no maneja bien los valores atípicos, ya que los valores extremos pueden comprimir el rango escalado de la mayoría de los puntos de datos. Por lo tanto, la decisión de utilizar el escalado min-max debe tomarse en función de los requisitos específicos de los datos y los algoritmos que se planea utilizar.

La fórmula para el escalado min-max es:

Donde:

  • X es el valor original de la característica,
  • X' es el valor escalado,
  • X_{min} y X_{max} son los valores mínimos y máximos de la característica, respectivamente.

Aplicación del Escalado Min-Max con Scikit-learn

Scikit-learn ofrece una clase poderosa y fácil de usar llamada MinMaxScaler para implementar el escalado min-max. Esta herramienta versátil simplifica el proceso de transformar las características a un rango específico, típicamente entre 0 y 1, asegurando que todas las variables contribuyan de manera equitativa al proceso de toma de decisiones del modelo.

Al aprovechar este escalador, los científicos de datos pueden normalizar eficientemente sus conjuntos de datos, lo que facilita la creación de modelos de aprendizaje automático más precisos y robustos.

Ejemplo: Escalado Min-Max con Scikit-learn

from sklearn.preprocessing import MinMaxScaler
import pandas as pd

# Sample data
data = {'Age': [25, 30, 35, 40],
        'Income': [50000, 60000, 70000, 80000]}
df = pd.DataFrame(data)

# Initialize the MinMaxScaler
scaler = MinMaxScaler()

# Fit and transform the data
scaled_data = scaler.fit_transform(df)

# Convert the scaled data back to a DataFrame
df_scaled = pd.DataFrame(scaled_data, columns=['Age', 'Income'])
print(df_scaled)

3.4.3 Estandarización (Normalización Z-Score)

La estandarización (también conocida como normalización Z-score) transforma los datos para que tengan una media de 0 y una desviación estándar de 1. Esta técnica es particularmente útil para modelos que asumen que los datos están distribuidos normalmente, como la regresión lineal y la regresión logística. La estandarización es menos afectada por valores atípicos que el escalado min-max porque se centra en la distribución de los datos en lugar de su rango.

La fórmula para la estandarización es:


Z = \frac {X - \mu}{\sigma}


Donde:

  • X es el valor original de la característica,
  • \mu es la media de la característica,
  • \sigma es la desviación estándar de la característica.

Aplicación de la estandarización con Scikit-learn

Scikit-learn proporciona un StandardScaler para estandarizar características.

Ejemplo: Estandarización con Scikit-learn

from sklearn.preprocessing import StandardScaler

# Initialize the StandardScaler
scaler = StandardScaler()

# Fit and transform the data
standardized_data = scaler.fit_transform(df)

# Convert the standardized data back to a DataFrame
df_standardized = pd.DataFrame(standardized_data, columns=['Age', 'Income'])
print(df_standardized)

Aquí, "Edad" e "Ingreso" se transforman para tener una media de 0 y una desviación estándar de 1. Esto asegura que las características contribuyan equitativamente al modelo, especialmente en algoritmos como la regresión logística o redes neuronales.

3.4.4 Escalado Robusto

El escalado robusto es otra técnica de escalado que es particularmente efectiva cuando se trata con datos que contienen valores atípicos. A diferencia de la estandarización y el escalado min-max, que pueden verse fuertemente influenciados por valores extremos, el escalado robusto utiliza la mediana y el rango intercuartílico (IQR) para escalar los datos, haciéndolo más resistente a los valores atípicos.

La fórmula para el escalado robusto es:


X' = \frac{X - Q_2}{IQR}

Donde:

  • Q_2 es la mediana de los datos,
  • IQR es el rango intercuartílico, es decir, la diferencia entre los percentiles 75 y 25.

Aplicación del Escalado Robusto con Scikit-learn

Scikit-learn ofrece una clase potente y versátil llamada RobustScaler, que aplica eficientemente el escalado robusto a las características. Este escalador es particularmente útil cuando se trabaja con conjuntos de datos que contienen valores atípicos o cuando se desea que el método de escalado sea menos sensible a valores extremos.

Al aprovechar la mediana y el rango intercuartílico (IQR) en lugar de la media y la desviación estándar, el RobustScaler ofrece un enfoque más robusto para el escalado de características, manteniendo la integridad de la distribución de los datos incluso en presencia de valores atípicos.

Ejemplo: Escalado Robusto con Scikit-learn

import numpy as np
import pandas as pd
from sklearn.preprocessing import RobustScaler
from sklearn.datasets import make_regression

# Generate sample data
X, y = make_regression(n_samples=100, n_features=2, noise=0.1, random_state=42)
df = pd.DataFrame(X, columns=['Feature1', 'Feature2'])

# Add some outliers
df.loc[0, 'Feature1'] = 1000
df.loc[1, 'Feature2'] = -1000

print("Original data:")
print(df.describe())

# Initialize the RobustScaler
scaler = RobustScaler()

# Fit and transform the data
robust_scaled_data = scaler.fit_transform(df)

# Convert the robust scaled data back to a DataFrame
df_robust_scaled = pd.DataFrame(robust_scaled_data, columns=['Feature1', 'Feature2'])

print("\nRobust scaled data:")
print(df_robust_scaled.describe())

# Compare original and scaled data for a few samples
print("\nComparison of original and scaled data:")
print(pd.concat([df.head(), df_robust_scaled.head()], axis=1))

# Inverse transform to get back original scale
df_inverse = pd.DataFrame(scaler.inverse_transform(robust_scaled_data), columns=['Feature1', 'Feature2'])

print("\nInverse transformed data:")
print(df_inverse.head())

Desglose del Código:

  1. Generación de datos:
    • Usamos make_regression de Scikit-learn para crear un conjunto de datos de muestra con 100 muestras y 2 características.
    • Se agregan valores atípicos artificiales para demostrar la robustez del escalado.
  2. Inicialización de RobustScaler:
    • Creamos una instancia de RobustScaler de Scikit-learn.
    • Por defecto, utiliza el rango intercuartílico (IQR) y la mediana para el escalado.
  3. Ajuste y Transformación:
    • Usamos el método fit_transform() para ajustar el escalador a los datos y transformarlos.
    • Este método calcula la mediana y el IQR para cada característica y luego aplica la transformación.
  4. Creación de un DataFrame:
    • Los datos escalados se convierten de nuevo en un DataFrame de pandas para facilitar la visualización y comparación.
  5. Análisis de Resultados:
    • Imprimimos estadísticas descriptivas tanto de los datos originales como de los escalados.
    • Los datos escalados deberían tener una mediana cercana a 0 y un IQR cercano a 1 para cada característica.
  6. Comparación:
    • Mostramos algunas muestras de los datos originales y escalados lado a lado.
    • Esto ayuda a visualizar cómo afecta el escalado a puntos de datos individuales.
  7. Transformación Inversa:
    • Demostramos cómo revertir el escalado usando inverse_transform().
    • Esto es útil cuando se necesita convertir las predicciones o los datos transformados de nuevo a la escala original.

Este ejemplo de código muestra el flujo completo de uso de RobustScaler, desde la preparación de los datos hasta el escalado y la retrotransformación. Resalta la capacidad del escalador para manejar valores atípicos y proporciona una clara comparación entre los datos originales y los escalados.

En este ejemplo, el escalado robusto garantiza que los valores extremos (valores atípicos) tengan una influencia menor en el proceso de escalado. Esto es particularmente útil en conjuntos de datos donde los valores atípicos están presentes, pero no deben dominar el entrenamiento del modelo.

3.4.5. Transformaciones Logarítmicas

En casos donde las características exhiben una distribución muy sesgada, una transformación logarítmica puede ser una herramienta invaluable para comprimir el rango de valores y reducir la asimetría. Esta técnica es particularmente útil para características como ingresospoblación o precios de acciones, donde los valores pueden abarcar varios órdenes de magnitud.

La transformación logarítmica funciona aplicando la función logaritmo a cada valor en el conjunto de datos. Esto tiene varios efectos beneficiosos:

  • Compresión de valores grandes: Los valores extremadamente grandes se acercan más al resto de los datos, reduciendo el impacto de los valores atípicos.
  • Expansión de valores pequeños: Los valores pequeños se expanden, permitiendo una mejor diferenciación entre ellos.
  • Normalización de la distribución: La transformación a menudo da como resultado una distribución más parecida a la normal, lo que es beneficioso para muchos métodos estadísticos y algoritmos de aprendizaje automático.

Por ejemplo, considera una distribución de ingresos donde los valores varían entre $10,000 y $1,000,000. Después de aplicar una transformación logarítmica:

  • $10,000 se convierte en log(10,000) ≈ 9.21
  • $100,000 se convierte en log(100,000) ≈ 11.51
  • $1,000,000 se convierte en log(1,000,000) ≈ 13.82

Como puedes ver, la gran diferencia entre los valores más altos y más bajos se ha reducido significativamente, haciendo que los datos sean más fáciles de interpretar y procesar para los modelos. Esto puede mejorar el rendimiento del modelo, especialmente en algoritmos que son sensibles a la escala de las características de entrada.

Sin embargo, es importante tener en cuenta que las transformaciones logarítmicas deben usarse con precaución. Son más efectivas cuando los datos están sesgados positivamente y abarcan varios órdenes de magnitud. Además, las transformaciones logarítmicas solo pueden aplicarse a valores positivos, ya que el logaritmo de cero o números negativos no está definido en los sistemas de números reales.

Aplicación de Transformaciones Logarítmicas

Las transformaciones logarítmicas se usan comúnmente para características con una distribución sesgada a la derecha, como ingresos o precios de propiedades.

Ejemplo: Transformación Logarítmica con NumPy

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

# Create a sample dataset
np.random.seed(42)
income = np.random.lognormal(mean=10, sigma=1, size=1000)
df = pd.DataFrame({'Income': income})

# Apply log transformation
df['Log_Income'] = np.log(df['Income'])

# Print summary statistics
print("Original Income:")
print(df['Income'].describe())
print("\nLog-transformed Income:")
print(df['Log_Income'].describe())

# Visualize the distributions
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))

ax1.hist(df['Income'], bins=50, edgecolor='black')
ax1.set_title('Original Income Distribution')
ax1.set_xlabel('Income')
ax1.set_ylabel('Frequency')

ax2.hist(df['Log_Income'], bins=50, edgecolor='black')
ax2.set_title('Log-transformed Income Distribution')
ax2.set_xlabel('Log(Income)')
ax2.set_ylabel('Frequency')

plt.tight_layout()
plt.show()

# Calculate skewness
original_skewness = np.mean(((df['Income'] - df['Income'].mean()) / df['Income'].std())**3)
log_skewness = np.mean(((df['Log_Income'] - df['Log_Income'].mean()) / df['Log_Income'].std())**3)

print(f"\nOriginal Income Skewness: {original_skewness:.2f}")
print(f"Log-transformed Income Skewness: {log_skewness:.2f}")

# Demonstrate inverse transformation
inverse_income = np.exp(df['Log_Income'])
print("\nInverse Transformation (first 5 rows):")
print(pd.DataFrame({'Original': df['Income'][:5], 'Log': df['Log_Income'][:5], 'Inverse': inverse_income[:5]}))

Desglose del Código:

  1. Generación de Datos:
    • Usamos random.lognormal() de NumPy para generar un conjunto de datos de muestra de 1000 valores de ingresos.
    • La distribución lognormal se utiliza a menudo para modelar ingresos, ya que naturalmente produce una distribución sesgada a la derecha.
    • Establecemos una semilla aleatoria para garantizar la reproducibilidad.
  2. Transformación Logarítmica:
    • Aplicamos el logaritmo natural (base e) a la columna 'Income' utilizando la función log() de NumPy.
    • Esto crea una nueva columna 'Log_Income' en nuestro DataFrame.
  3. Estadísticas Resumidas:
    • Imprimimos estadísticas descriptivas tanto de los ingresos originales como de los transformados mediante logaritmo utilizando el método describe() de Pandas.
    • Esto nos permite comparar las características de la distribución antes y después de la transformación.
  4. Visualización:
    • Creamos histogramas de las distribuciones de ingresos originales y transformados logarítmicamente.
    • Esta representación visual ayuda a ver claramente el efecto de la transformación logarítmica en la distribución de los datos.
  5. Cálculo de la Asimetría:
    • Calculamos la asimetría de ambas distribuciones utilizando operaciones de NumPy.
    • La asimetría cuantifica la falta de simetría en la distribución. Un valor cercano a 0 indica una distribución más simétrica.
  6. Transformación Inversa:
    • Demostramos cómo revertir la transformación logarítmica utilizando la función exp() de NumPy.
    • Esto es crucial cuando se necesita interpretar los resultados en la escala original después de realizar análisis sobre datos transformados.

Este ejemplo muestra todo el proceso de transformación logarítmica, desde la generación de datos hasta el análisis y la visualización, utilizando principalmente operaciones de NumPy. Demuestra cómo la transformación logarítmica puede hacer que una distribución sesgada a la derecha sea más simétrica, lo que a menudo es beneficioso para el análisis estadístico y los algoritmos de aprendizaje automático.

En este ejemplo, la transformación logarítmica reduce el amplio rango de valores de ingresos, haciendo que la distribución sea más manejable para los algoritmos de aprendizaje automático. Es importante destacar que las transformaciones logarítmicas solo deben aplicarse a valores positivos, ya que el logaritmo de un número negativo no está definido.

3.4.6 Transformaciones de Potencia

Las transformaciones de potencia son técnicas estadísticas avanzadas utilizadas para modificar la distribución de los datos. Dos ejemplos prominentes son las transformaciones Box-Cox y Yeo-Johnson. Estos métodos tienen dos propósitos principales:

  1. Estabilización de la varianza: Estas transformaciones ayudan a asegurar que la variabilidad de los datos se mantenga constante en todo su rango, lo cual es una suposición crucial en muchos análisis estadísticos. Al aplicar transformaciones de potencia, los investigadores pueden mitigar problemas relacionados con la heterocedasticidad, donde la dispersión de los residuos varía a lo largo del rango de una variable predictora. Esta estabilización de la varianza puede conducir a inferencias estadísticas más confiables y un mejor rendimiento del modelo.
  2. Normalización de distribuciones: Las transformaciones de potencia buscan hacer que los datos se asemejen más a una distribución normal (gaussiana), lo que es beneficioso para muchas pruebas estadísticas y algoritmos de aprendizaje automático. Al cambiar la forma de la distribución de los datos, estas transformaciones pueden ayudar a cumplir con la suposición de normalidad requerida por muchos métodos estadísticos paramétricos. Este proceso de normalización puede revelar patrones ocultos en los datos, mejorar la interpretabilidad de los resultados y potencialmente mejorar el poder predictivo de varios modelos de aprendizaje automático, especialmente aquellos que asumen entradas distribuidas normalmente.

Las transformaciones de potencia son especialmente valiosas cuando se trabaja con características que exhiben distribuciones no normales, como aquellas con una asimetría o curtosis significativa. Al aplicar estas transformaciones, los científicos de datos pueden mejorar el rendimiento y la confiabilidad de sus modelos, especialmente aquellos que asumen entradas distribuidas normalmente.

La transformación de Box-Cox, introducida por los estadísticos George Box y David Cox en 1964, es aplicable solo a datos positivos. Involucra encontrar un parámetro óptimo λ (lambda) que determina la transformación de potencia específica a aplicar. Por otro lado, la transformación de Yeo-Johnson, desarrollada por In-Kwon Yeo y Richard Johnson en el año 2000, extiende el concepto para manejar tanto valores positivos como negativos, haciéndola más versátil en la práctica.

Al emplear estas transformaciones, los analistas pueden descubrir relaciones en los datos que de otro modo podrían estar ocultas, lo que conduce a predicciones más precisas y mejores conocimientos en diversos campos como finanzas, biología y ciencias sociales.

a. Transformación de Box-Cox

La transformación de Box-Cox es una poderosa técnica estadística que solo se puede aplicar a datos positivos. Este método es particularmente útil para abordar la no normalidad en las distribuciones de datos y estabilizar la varianza. Aquí tienes una explicación más detallada:

  1. Selección del parámetro óptimo: La transformación de Box-Cox encuentra un parámetro de transformación óptimo, denotado como λ (lambda). Este parámetro determina la transformación de potencia específica que se aplicará a los datos.
  2. Estabilización de la varianza: Uno de los principales objetivos de la transformación de Box-Cox es estabilizar la varianza a lo largo del rango de los datos. Esto es crucial para muchos análisis estadísticos que asumen homocedasticidad (varianza constante).
  3. Normalización: La transformación busca hacer que los datos se asemejen más a una distribución normal. Esto es beneficioso para muchas pruebas estadísticas y algoritmos de aprendizaje automático que asumen normalidad.
  4. Fórmula matemática: La transformación de Box-Cox se define como:
    • y(λ) = (x^λ - 1) / λ,  if λ ≠ 0
    • y(λ) = log(x),         if λ = 0
      Donde x son los datos originales y y(\lambda) son los datos transformados.
  5. Interpretación: Diferentes valores de λ resultan en diferentes transformaciones. Por ejemplo, λ = 1 significa que no hay transformación, λ = 0 es equivalente a una transformación logarítmica, y λ = 0.5 es equivalente a una transformación por raíz cuadrada.

Al aplicar esta transformación, los analistas pueden descubrir relaciones en los datos que de otro modo podrían estar ocultas, lo que lleva a predicciones más precisas y mejores conocimientos en campos como finanzas, biología y ciencias sociales.

Ejemplo: Transformación de Box-Cox con Scikit-learn

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import PowerTransformer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

# Create a sample dataset
np.random.seed(42)
income = np.random.lognormal(mean=10, sigma=1, size=1000)
age = np.random.normal(loc=40, scale=10, size=1000)
df = pd.DataFrame({'Income': income, 'Age': age})

# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(
    df[['Income', 'Age']], df['Income'], test_size=0.2, random_state=42)

# Initialize the PowerTransformer for Box-Cox (only for positive data)
boxcox_transformer = PowerTransformer(method='box-cox', standardize=True)

# Fit and transform the training data
X_train_transformed = boxcox_transformer.fit_transform(X_train)

# Transform the test data
X_test_transformed = boxcox_transformer.transform(X_test)

# Train a linear regression model on the original data
model_original = LinearRegression()
model_original.fit(X_train, y_train)

# Train a linear regression model on the transformed data
model_transformed = LinearRegression()
model_transformed.fit(X_train_transformed, y_train)

# Make predictions
y_pred_original = model_original.predict(X_test)
y_pred_transformed = model_transformed.predict(X_test_transformed)

# Calculate performance metrics
mse_original = mean_squared_error(y_test, y_pred_original)
r2_original = r2_score(y_test, y_pred_original)
mse_transformed = mean_squared_error(y_test, y_pred_transformed)
r2_transformed = r2_score(y_test, y_pred_transformed)

# Print results
print("Original Data Performance:")
print(f"Mean Squared Error: {mse_original:.2f}")
print(f"R-squared Score: {r2_original:.2f}")
print("\nTransformed Data Performance:")
print(f"Mean Squared Error: {mse_transformed:.2f}")
print(f"R-squared Score: {r2_transformed:.2f}")

# Visualize the distributions
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))

ax1.hist(X_train['Income'], bins=50, edgecolor='black')
ax1.set_title('Original Income Distribution')
ax1.set_xlabel('Income')
ax1.set_ylabel('Frequency')

ax2.hist(X_train_transformed[:, 0], bins=50, edgecolor='black')
ax2.set_title('Box-Cox Transformed Income Distribution')
ax2.set_xlabel('Transformed Income')
ax2.set_ylabel('Frequency')

plt.tight_layout()
plt.show()

Desglose del Código:

  1. Importar bibliotecas necesarias: Importamos NumPy, Pandas, Matplotlib y varios módulos de Scikit-learn para la manipulación de datos, visualización y tareas de aprendizaje automático.
  2. Crear un conjunto de datos de muestra: Generamos un conjunto de datos sintético con características de 'Ingresos' (distribuidos lognormalmente) y 'Edad' (distribuida normalmente).
  3. Dividir los datos: Usamos train_test_split de Scikit-learn para dividir los datos en conjuntos de entrenamiento y prueba.
  4. Inicializar PowerTransformer: Creamos un objeto PowerTransformer para la transformación de Box-Cox, estableciendo standardize=True para asegurar que la salida tenga una media de cero y una varianza unitaria.
  5. Aplicar la transformación de Box-Cox: Ajustamos el transformador con los datos de entrenamiento y transformamos tanto los datos de entrenamiento como los de prueba.
  6. Entrenar modelos de regresión lineal: Creamos dos modelos de LinearRegression: uno para los datos originales y otro para los datos transformados.
  7. Hacer predicciones y evaluar: Usamos ambos modelos para hacer predicciones en el conjunto de prueba y calculamos el error cuadrático medio (MSE) y los puntajes de R-cuadrado utilizando las métricas de Scikit-learn.
  8. Visualizar distribuciones: Creamos histogramas para comparar las distribuciones de ingresos originales y transformados.

Este ejemplo integral muestra todo el proceso de aplicar una transformación de Box-Cox usando Scikit-learn, desde la preparación de los datos hasta la evaluación del modelo. Demuestra cómo la transformación puede afectar el rendimiento del modelo y la distribución de los datos, proporcionando un contexto práctico para entender el impacto de las transformaciones de potencia en los flujos de trabajo de aprendizaje automático.

b. Transformación de Yeo-Johnson

La transformación de Yeo-Johnson es una extensión de la transformación de Box-Cox que ofrece una mayor flexibilidad en el preprocesamiento de datos. Mientras que Box-Cox está limitado a datos estrictamente positivos, Yeo-Johnson puede manejar tanto valores positivos como negativos, lo que la hace más versátil para conjuntos de datos del mundo real. Esta transformación fue desarrollada por In-Kwon Yeo y Richard A. Johnson en el año 2000 para abordar las limitaciones de Box-Cox.

Las características clave de la transformación de Yeo-Johnson incluyen:

  • Aplicabilidad a todos los números reales: A diferencia de Box-Cox, Yeo-Johnson puede aplicarse a valores cero y negativos, eliminando la necesidad de ajustar los datos.
  • Continuidad en cero: La transformación es continua en λ = 0, lo que asegura transiciones suaves entre diferentes transformaciones de potencia.
  • Efecto de normalización: Similar a Box-Cox, ayuda a normalizar datos sesgados, lo que potencialmente mejora el rendimiento de los algoritmos de aprendizaje automático que asumen entradas distribuidas normalmente.
  • Estabilización de la varianza: Puede ayudar a estabilizar la varianza en todo el rango de los datos, abordando problemas de heterocedasticidad en los análisis estadísticos.

La formulación matemática de la transformación de Yeo-Johnson es un poco más compleja que la de Box-Cox, ya que acomoda tanto valores positivos como negativos a través de diferentes ecuaciones basadas en el signo del valor de entrada. Esta complejidad adicional permite una mayor adaptabilidad a diversos conjuntos de datos, lo que la convierte en una herramienta poderosa en el conjunto de herramientas de preprocesamiento de un científico de datos.

Ejemplo: Transformación de Yeo-Johnson con Scikit-learn

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import PowerTransformer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

# Create a sample dataset with both positive and negative values
np.random.seed(42)
income = np.random.lognormal(mean=10, sigma=1, size=1000)
expenses = np.random.normal(loc=50000, scale=10000, size=1000)
net_income = income - expenses
df = pd.DataFrame({'Income': income, 'Expenses': expenses, 'NetIncome': net_income})

# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(
    df[['Income', 'Expenses']], df['NetIncome'], test_size=0.2, random_state=42)

# Initialize the PowerTransformer for Yeo-Johnson
yeojohnson_transformer = PowerTransformer(method='yeo-johnson', standardize=True)

# Fit and transform the training data
X_train_transformed = yeojohnson_transformer.fit_transform(X_train)

# Transform the test data
X_test_transformed = yeojohnson_transformer.transform(X_test)

# Train linear regression models on original and transformed data
model_original = LinearRegression().fit(X_train, y_train)
model_transformed = LinearRegression().fit(X_train_transformed, y_train)

# Make predictions
y_pred_original = model_original.predict(X_test)
y_pred_transformed = model_transformed.predict(X_test_transformed)

# Calculate performance metrics
mse_original = mean_squared_error(y_test, y_pred_original)
r2_original = r2_score(y_test, y_pred_original)
mse_transformed = mean_squared_error(y_test, y_pred_transformed)
r2_transformed = r2_score(y_test, y_pred_transformed)

# Print results
print("Original Data Performance:")
print(f"Mean Squared Error: {mse_original:.2f}")
print(f"R-squared Score: {r2_original:.2f}")
print("\nTransformed Data Performance:")
print(f"Mean Squared Error: {mse_transformed:.2f}")
print(f"R-squared Score: {r2_transformed:.2f}")

# Visualize the distributions
fig, axs = plt.subplots(2, 2, figsize=(15, 15))

axs[0, 0].hist(X_train['Income'], bins=50, edgecolor='black')
axs[0, 0].set_title('Original Income Distribution')
axs[0, 0].set_xlabel('Income')
axs[0, 0].set_ylabel('Frequency')

axs[0, 1].hist(X_train_transformed[:, 0], bins=50, edgecolor='black')
axs[0, 1].set_title('Yeo-Johnson Transformed Income Distribution')
axs[0, 1].set_xlabel('Transformed Income')
axs[0, 1].set_ylabel('Frequency')

axs[1, 0].hist(X_train['Expenses'], bins=50, edgecolor='black')
axs[1, 0].set_title('Original Expenses Distribution')
axs[1, 0].set_xlabel('Expenses')
axs[1, 0].set_ylabel('Frequency')

axs[1, 1].hist(X_train_transformed[:, 1], bins=50, edgecolor='black')
axs[1, 1].set_title('Yeo-Johnson Transformed Expenses Distribution')
axs[1, 1].set_xlabel('Transformed Expenses')
axs[1, 1].set_ylabel('Frequency')

plt.tight_layout()
plt.show()

# Print the lambda values used for transformation
print("\nLambda values used for Yeo-Johnson transformation:")
print(yeojohnson_transformer.lambdas_)

Desglose del Código:

  1. Generación de datos: Creamos un conjunto de datos sintético con 'Ingresos' (distribuidos lognormalmente), 'Gastos' (distribuidos normalmente) y 'IngresoNeto' (diferencia entre Ingresos y Gastos). Este conjunto de datos incluye valores tanto positivos como negativos, lo que muestra la capacidad de Yeo-Johnson para manejar tales datos.
  2. División de datos: Usamos train_test_split de Scikit-learn para dividir nuestros datos en conjuntos de entrenamiento y prueba. Esto es crucial para evaluar el rendimiento del modelo con datos no vistos.
  3. Transformación de Yeo-Johnson: Inicializamos un PowerTransformer con method='yeo-johnson'. El parámetro standardize=True asegura que la salida transformada tenga media cero y varianza unitaria.
  4. Entrenamiento del modelo: Entrenamos dos modelos de regresión lineal (LinearRegression): uno con los datos originales y otro con los datos transformados por Yeo-Johnson. Esto nos permite comparar el rendimiento de los modelos con y sin la transformación.
  5. Predicción y evaluación: Usamos ambos modelos para hacer predicciones en el conjunto de prueba y calculamos el error cuadrático medio (MSE) y los puntajes de R-cuadrado utilizando las métricas de Scikit-learn. Esto nos ayuda a cuantificar el impacto de la transformación Yeo-Johnson en el rendimiento del modelo.
  6. Visualización: Creamos histogramas para comparar las distribuciones originales y transformadas tanto de Ingresos como de Gastos. Esta representación visual ayuda a entender cómo la transformación Yeo-Johnson afecta la distribución de los datos.
  7. Valores de Lambda: Imprimimos los valores de lambda utilizados para la transformación de Yeo-Johnson. Estos valores indican la transformación de potencia específica aplicada a cada característica.

Este ejemplo demuestra todo el proceso de aplicar una transformación Yeo-Johnson usando Scikit-learn, desde la preparación de los datos hasta la evaluación y visualización del modelo. Muestra cómo la transformación puede afectar el rendimiento del modelo y la distribución de los datos, proporcionando un contexto práctico para entender el impacto de las transformaciones de potencia en los flujos de trabajo de aprendizaje automático, especialmente cuando se trabaja con conjuntos de datos que incluyen tanto valores positivos como negativos.

3.4.7 Normalización (L1 y L2)

La normalización es una técnica crucial en el preprocesamiento de datos que se utiliza para volver a escalar las características de modo que la norma del vector de características sea 1. Este proceso asegura que todas las características contribuyan de manera equitativa al análisis, evitando que aquellas con magnitudes mayores dominen el modelo. La normalización es particularmente valiosa en algoritmos de aprendizaje automático que dependen de cálculos de distancia, como K-Nearest Neighbors (KNN) o la agrupación K-means.

En KNN, por ejemplo, la normalización ayuda a evitar que las características con escalas más grandes tengan una influencia desproporcionada en los cálculos de distancia. De manera similar, en K-means, las características normalizadas aseguran que la agrupación se base en la importancia relativa de las características en lugar de sus escalas absolutas.

Hay dos tipos principales de normalización:

a. Normalización L1 (norma Manhattan)

La normalización L1, también conocida como norma Manhattan, es un método que asegura que la suma de los valores absolutos de un vector de características sea igual a 1. Esta técnica es particularmente útil en el preprocesamiento de datos para algoritmos de aprendizaje automático. Para entender la normalización L1, desglosémosla matemáticamente:

Para un vector de características x = (x₁, ..., xₙ), la norma L1 se calcula como:

||x||₁ = |x₁| + |x₂| + ... + |xₙ|

donde |xᵢ| representa el valor absoluto de cada característica.

Para lograr la normalización L1, dividimos cada característica por la norma L1:

x_normalizado = x / ||x||₁

Este proceso resulta en un vector de características normalizado donde la suma de los valores absolutos es igual a 1.

Una ventaja notable de la normalización L1 es su menor sensibilidad a los valores atípicos en comparación con la normalización L2. Esta característica la hace particularmente útil en escenarios donde los valores extremos podrían influir desproporcionadamente en el rendimiento del modelo. Además, la normalización L1 puede llevar a vectores de características dispersos, lo que puede ser beneficioso en ciertas aplicaciones de aprendizaje automático, como la selección de características o técnicas de regularización como la regresión Lasso.

Ejemplo de código de normalización L1:

import numpy as np
import pandas as pd
from sklearn.preprocessing import Normalizer
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score

# Create a sample dataset
np.random.seed(42)
X = np.random.rand(100, 3) * 100  # 100 samples, 3 features
y = np.random.randint(0, 2, 100)  # Binary classification

# Split the data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Initialize L1 normalizer
l1_normalizer = Normalizer(norm='l1')

# Fit and transform the training data
X_train_normalized = l1_normalizer.fit_transform(X_train)

# Transform the test data
X_test_normalized = l1_normalizer.transform(X_test)

# Train KNN classifier on original data
knn_original = KNeighborsClassifier(n_neighbors=3)
knn_original.fit(X_train, y_train)
y_pred_original = knn_original.predict(X_test)

# Train KNN classifier on normalized data
knn_normalized = KNeighborsClassifier(n_neighbors=3)
knn_normalized.fit(X_train_normalized, y_train)
y_pred_normalized = knn_normalized.predict(X_test_normalized)

# Calculate accuracies
accuracy_original = accuracy_score(y_test, y_pred_original)
accuracy_normalized = accuracy_score(y_test, y_pred_normalized)

print("Original Data Accuracy:", accuracy_original)
print("L1 Normalized Data Accuracy:", accuracy_normalized)

# Display a sample of original and normalized data
sample_original = X_train[:5]
sample_normalized = X_train_normalized[:5]

print("\nOriginal Data Sample:")
print(pd.DataFrame(sample_original, columns=['Feature 1', 'Feature 2', 'Feature 3']))

print("\nL1 Normalized Data Sample:")
print(pd.DataFrame(sample_normalized, columns=['Feature 1', 'Feature 2', 'Feature 3']))

# Verify L1 norm
print("\nL1 Norm of normalized samples:")
print(np.sum(np.abs(sample_normalized), axis=1))

Desglose del Código:

  1. Generación de datos: Creamos un conjunto de datos sintético con 100 muestras y 3 características, junto con etiquetas de clasificación binaria. Esto simula un escenario del mundo real donde las características pueden tener diferentes escalas.
  2. División de datos: Usamos train_test_split para dividir nuestros datos en conjuntos de entrenamiento y prueba. Esto es crucial para evaluar el rendimiento del modelo con datos no vistos.
  3. Normalización L1: Inicializamos un Normalizer con norm='l1'. Este normalizador se ajusta a los datos de entrenamiento y luego se usa para transformar tanto los datos de entrenamiento como los de prueba.
  4. Entrenamiento del modelo: Entrenamos dos clasificadores KNN: uno con los datos originales y otro con los datos normalizados con L1. Esto nos permite comparar el rendimiento de los modelos con y sin normalización.
  5. Predicción y evaluación: Ambos modelos hacen predicciones en sus respectivos conjuntos de prueba (original y normalizado). Luego, calculamos y comparamos las puntuaciones de precisión para ver el impacto de la normalización L1.
  6. Visualización de datos: Mostramos muestras de los datos originales y normalizados para ilustrar cómo la normalización L1 afecta los valores de las características.
  7. Verificación de la norma L1: Calculamos la suma de los valores absolutos de cada muestra normalizada para verificar que la norma L1 sea igual a 1 después de la normalización.

Este ejemplo muestra todo el proceso de aplicar la normalización L1 utilizando Scikit-learn, desde la preparación de los datos hasta la evaluación del modelo. Demuestra cómo la normalización puede afectar el rendimiento del modelo y la representación de los datos, proporcionando un contexto práctico para comprender el impacto de la normalización L1 en los flujos de trabajo de aprendizaje automático.

b. Normalización L2 (norma euclidiana):

La normalización L2, también conocida como norma euclidiana, es una técnica poderosa que asegura que la suma de los valores cuadrados dentro de un vector de características sea igual a 1. Este método es particularmente efectivo para estandarizar datos en diferentes escalas y dimensiones. Para ilustrarlo, consideremos un vector de características x = (x₁, ..., xₙ). La norma L2 para este vector se calcula utilizando la siguiente fórmula:

||x||₂ = √(x₁² + x₂² + ... + xₙ²)

Una vez que hemos calculado la norma L2, podemos proceder con el proceso de normalización. Esto se logra dividiendo cada característica individual por la norma L2 calculada:

x_normalizado = x / ||x||₂

El vector normalizado resultante mantiene las mismas propiedades direccionales que el original, pero con una longitud unitaria. Esta transformación tiene varias ventajas en las aplicaciones de aprendizaje automático. Por ejemplo, ayuda a mitigar el impacto de los valores atípicos y asegura que todas las características contribuyan de manera equitativa al modelo, independientemente de su escala original.

La normalización L2 se adopta ampliamente en varios algoritmos de aprendizaje automático y es especialmente beneficiosa cuando se trabaja con vectores dispersos. Su popularidad se debe a su capacidad para preservar la importancia relativa de las características mientras se estandarizan sus magnitudes. Esta característica la hace particularmente útil en escenarios como la clasificación de texto, el reconocimiento de imágenes y los sistemas de recomendación, donde el escalado de características puede afectar significativamente el rendimiento del modelo.

Ejemplo de código de normalización L2:

import numpy as np
import pandas as pd
from sklearn.preprocessing import Normalizer
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score

# Create a sample dataset
np.random.seed(42)
X = np.random.rand(100, 3) * 100  # 100 samples, 3 features
y = np.random.randint(0, 2, 100)  # Binary classification

# Split the data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Initialize L2 normalizer
l2_normalizer = Normalizer(norm='l2')

# Fit and transform the training data
X_train_normalized = l2_normalizer.fit_transform(X_train)

# Transform the test data
X_test_normalized = l2_normalizer.transform(X_test)

# Train KNN classifier on original data
knn_original = KNeighborsClassifier(n_neighbors=3)
knn_original.fit(X_train, y_train)
y_pred_original = knn_original.predict(X_test)

# Train KNN classifier on normalized data
knn_normalized = KNeighborsClassifier(n_neighbors=3)
knn_normalized.fit(X_train_normalized, y_train)
y_pred_normalized = knn_normalized.predict(X_test_normalized)

# Calculate accuracies
accuracy_original = accuracy_score(y_test, y_pred_original)
accuracy_normalized = accuracy_score(y_test, y_pred_normalized)

print("Original Data Accuracy:", accuracy_original)
print("L2 Normalized Data Accuracy:", accuracy_normalized)

# Display a sample of original and normalized data
sample_original = X_train[:5]
sample_normalized = X_train_normalized[:5]

print("\nOriginal Data Sample:")
print(pd.DataFrame(sample_original, columns=['Feature 1', 'Feature 2', 'Feature 3']))

print("\nL2 Normalized Data Sample:")
print(pd.DataFrame(sample_normalized, columns=['Feature 1', 'Feature 2', 'Feature 3']))

# Verify L2 norm
print("\nL2 Norm of normalized samples:")
print(np.sqrt(np.sum(np.square(sample_normalized), axis=1)))

Desglose del Código:

  1. Generación de datos: Creamos un conjunto de datos sintético con 100 muestras y 3 características, junto con etiquetas de clasificación binaria. Esto simula un escenario del mundo real donde las características pueden tener diferentes escalas.
  2. División de datos: Usamos train_test_split para dividir nuestros datos en conjuntos de entrenamiento y prueba. Esto es crucial para evaluar el rendimiento del modelo con datos no vistos.
  3. Normalización L2: Inicializamos un Normalizer con norm='l2'. Este normalizador se ajusta a los datos de entrenamiento y luego se usa para transformar tanto los datos de entrenamiento como los de prueba.
  4. Entrenamiento del modelo: Entrenamos dos clasificadores KNN: uno con los datos originales y otro con los datos normalizados con L2. Esto nos permite comparar el rendimiento de los modelos con y sin normalización.
  5. Predicción y evaluación: Ambos modelos hacen predicciones en sus respectivos conjuntos de prueba (original y normalizado). Luego calculamos y comparamos las puntuaciones de precisión para ver el impacto de la normalización L2.
  6. Visualización de datos: Mostramos muestras de los datos originales y normalizados para ilustrar cómo la normalización L2 afecta los valores de las características.
  7. Verificación de la norma L2: Calculamos la norma L2 para cada muestra normalizada para verificar que sea igual a 1 después de la normalización.

Este ejemplo demuestra todo el proceso de aplicar la normalización L2 utilizando Scikit-learn, desde la preparación de los datos hasta la evaluación del modelo. Demuestra cómo la normalización puede afectar el rendimiento del modelo y la representación de los datos, proporcionando un contexto práctico para entender el impacto de la normalización L2 en los flujos de trabajo de aprendizaje automático. La comparación entre las precisiones de los datos originales y normalizados ayuda a ilustrar los posibles beneficios de la normalización L2 para mejorar el rendimiento del modelo, especialmente para algoritmos basados en distancia como KNN.

La elección entre la normalización L1 y L2 depende de los requisitos específicos de tu tarea de aprendizaje automático y la naturaleza de tus datos. Ambos métodos tienen sus fortalezas y son herramientas valiosas en el conjunto de herramientas del científico de datos para preparar características para análisis y entrenamiento de modelos.

3.4 Técnicas de escalado, normalización y transformación de datos

La escala y distribución de tu conjunto de datos puede influir profundamente en la efectividad de muchos modelos, especialmente aquellos que dependen en gran medida de cálculos de distancia o que emplean técnicas de optimización basadas en gradientes.

Muchos algoritmos de aprendizaje automático operan bajo la suposición de que todas las características tienen una escala uniforme. Si no se aborda esta suposición, las características con rangos más amplios pueden dominar el proceso de entrenamiento, mientras que las características con rangos más estrechos pueden perderse. Para mitigar estos desafíos y garantizar un rendimiento óptimo del modelo, se emplean diversas técnicas de preprocesamiento de datos, como el escalado, la normalización y otros métodos transformativos.

3.4.1 Por qué el escalado y la normalización de datos son importantes

Los modelos de aprendizaje automático, especialmente aquellos que dependen de cálculos de distancia o de optimización basada en gradientes, son muy sensibles a la escala y el rango de las características de entrada. Esta sensibilidad puede conducir a problemas significativos en el rendimiento del modelo si no se abordan correctamente.

1. K-Nearest Neighbors (KNN)

KNN es un algoritmo de aprendizaje automático que se basa en gran medida en cálculos de distancia entre puntos de datos para hacer predicciones o clasificaciones. Sin embargo, la efectividad de KNN puede verse significativamente afectada por la escala de las diferentes características en el conjunto de datos.

Cuando las características en un conjunto de datos tienen escalas muy diferentes, esto puede conducir a resultados sesgados e inexactos en los algoritmos KNN. Las características con rangos numéricos más grandes influirán de manera desproporcionada en los cálculos de distancia, eclipsando el impacto de las características con rangos más pequeños.

Ejemplo concreto:

Considera un conjunto de datos con dos características: ingresos anuales y edad. Los ingresos anuales pueden variar de miles a millones (por ejemplo, $30,000 a $1,000,000), mientras que la edad típicamente varía de 0 a 100. En este escenario:

  • La característica de ingresos, debido a su escala mucho más grande, dominará los cálculos de distancia. Incluso una pequeña diferencia en ingresos (por ejemplo, $10,000) crearía una distancia mucho mayor que una diferencia significativa en la edad (por ejemplo, 20 años).
  • Esto significa que el algoritmo ignoraría prácticamente la característica de la edad, basando sus decisiones casi exclusivamente en las diferencias de ingresos.
  • Como resultado, dos personas con ingresos similares pero edades muy diferentes podrían ser consideradas "vecinos cercanos" por el algoritmo, incluso si la diferencia de edad es crucial para el análisis.

Esto puede conducir a problemas como:

  • Clasificación incorrecta: El algoritmo puede clasificar incorrectamente los puntos de datos basándose en la característica que domina.
  • Pérdida de información: Se pierden perspectivas valiosas de características con escalas más pequeñas, como la edad.
  • Reducción del rendimiento del modelo: La precisión y fiabilidad del modelo KNN pueden verse comprometidas.

Para mitigar estos problemas, es crucial aplicar técnicas adecuadas de escalado (como la estandarización o normalización) para garantizar que todas las características contribuyan proporcionalmente a los cálculos de distancia. Este paso de preprocesamiento ayuda a crear un campo de juego equilibrado para todas las características, lo que permite que el algoritmo KNN haga predicciones más precisas basadas en similitudes relevantes entre puntos de datos.

2. Máquinas de Vectores de Soporte (SVM)

Las Máquinas de Vectores de Soporte (SVM) son algoritmos poderosos utilizados para tareas de clasificación y regresión. Funcionan encontrando el hiperplano óptimo que mejor separa las diferentes clases en el espacio de características. Sin embargo, cuando las características están en diferentes escalas, las SVM pueden enfrentar desafíos significativos:

  • Determinación del hiperplano: El principio central de las SVM es maximizar el margen entre las clases. Cuando las características tienen escalas muy diferentes, el algoritmo puede tener dificultades para encontrar este hiperplano óptimo de manera eficiente. Esto se debe a que la característica con la escala más grande dominará los cálculos de distancia utilizados para determinar el margen.
  • Sesgo en la importancia de las características: Las características con magnitudes mayores podrían recibir una importancia indebida al determinar la frontera de decisión. Por ejemplo, si una característica varía de 0 a 1 y otra de 0 a 1000, la segunda tendrá una influencia mucho mayor en el proceso de toma de decisiones de la SVM, incluso si no es inherentemente más importante para la tarea de clasificación.
  • Impacto en la función kernel: Muchas SVM utilizan funciones kernel (como el kernel RBF) para mapear los datos a espacios de mayor dimensión. Estas funciones suelen depender de los cálculos de distancia entre puntos de datos. Cuando las características están en diferentes escalas, estos cálculos de distancia pueden distorsionarse, lo que lleva a un rendimiento subóptimo de la función kernel.
  • Problemas de convergencia: El proceso de optimización en las SVM puede volverse más lento y menos estable cuando las características no están escaladas de manera uniforme. Esto se debe a que el paisaje de optimización se vuelve más complejo y potencialmente más difícil de navegar cuando las características tienen rangos muy diferentes.
  • Dificultades de interpretación: En las SVM lineales, los coeficientes de la función de decisión pueden interpretarse como la importancia de las características. Sin embargo, cuando las características están en diferentes escalas, estos coeficientes se vuelven difíciles de comparar e interpretar con precisión.

Para mitigar estos problemas, es crucial aplicar técnicas de escalado adecuadas (como la estandarización o normalización) antes de entrenar una SVM. Esto asegura que todas las características contribuyan proporcionalmente al proceso de toma de decisiones del modelo, lo que lleva a resultados más precisos y confiables.

3. Algoritmos basados en gradiente

Las redes neuronales y otros métodos basados en gradiente emplean con frecuencia técnicas de optimización como el descenso de gradiente. Estos algoritmos son particularmente sensibles a la escala de las características de entrada, y cuando las características tienen escalas muy diferentes, pueden surgir varios problemas:

  • Paisaje de optimización alargado: Cuando las características tienen escalas diferentes, el paisaje de optimización se alarga y distorsiona. Esto significa que los contornos de la función de pérdida se estiran en la dirección de la característica con la mayor escala. Como resultado, el algoritmo de descenso de gradiente puede zigzaguear de un lado a otro a lo largo del valle estrecho de la superficie de error alargada, lo que dificulta la convergencia eficiente hacia la solución óptima.
  • Sensibilidad a la tasa de aprendizaje: La tasa de aprendizaje, un hiperparámetro crucial en el descenso de gradiente, se vuelve más difícil de ajustar correctamente cuando las características tienen escalas diferentes. Una tasa de aprendizaje que funcione bien para una característica podría ser demasiado grande o pequeña para otra, lo que provocaría que se sobrepase el mínimo o que la convergencia sea lenta.
  • Dominancia de características: Las características con escalas más grandes pueden dominar el proceso de aprendizaje, lo que hace que el modelo sea demasiado sensible a los cambios en estas características mientras subvalora el impacto de las características con escalas más pequeñas. Esto puede llevar a un modelo sesgado que no capture con precisión las relaciones verdaderas en los datos.
  • Convergencia más lenta: Debido a los desafíos mencionados, el proceso de optimización a menudo requiere más iteraciones para converger. Esto resulta en tiempos de entrenamiento más largos, lo que puede ser problemático al trabajar con conjuntos de datos grandes o modelos complejos.
  • Soluciones subóptimas: En algunos casos, las dificultades para navegar por el paisaje de optimización pueden causar que el algoritmo se atasque en mínimos locales o puntos de silla, lo que lleva a soluciones subóptimas. Esto significa que el modelo final podría no rendir tan bien como lo haría si las características estuvieran adecuadamente escaladas.
  • Inestabilidad numérica: Las grandes diferencias en las escalas de las características a veces pueden llevar a inestabilidad numérica durante el cálculo de gradientes, especialmente cuando se utiliza aritmética de punto flotante. Esto puede resultar en problemas como gradientes que explotan o desaparecen, lo que es particularmente problemático en redes neuronales profundas.

Para mitigar estos problemas, es crucial aplicar técnicas de escalado adecuadas, como la estandarización o normalización, antes de entrenar modelos basados en gradiente. Esto asegura que todas las características contribuyan proporcionalmente al proceso de optimización, lo que lleva a una convergencia más rápida, un entrenamiento más estable y un rendimiento potencialmente mejor del modelo.

4. Modelos lineales

En la regresión lineal o regresión logística, los coeficientes del modelo representan directamente el impacto o la importancia de cada característica sobre el resultado predicho. Esta interpretabilidad es una de las ventajas clave de los modelos lineales. Sin embargo, cuando las características están en escalas muy diferentes, comparar estos coeficientes se vuelve problemático y puede llevar a una mala interpretación de la importancia de las características.

Por ejemplo, considera un modelo de regresión lineal que predice precios de casas basado en dos características: el número de habitaciones (que típicamente varía de 1 a 10) y el área en pies cuadrados (que podría variar de 500 a 5000). Sin un escalado adecuado:

  • El coeficiente para el área en pies cuadrados probablemente sería mucho menor que el coeficiente para el número de habitaciones, simplemente debido a la diferencia en la escala de los datos.
  • Esto podría sugerir erróneamente que el número de habitaciones tiene un impacto más significativo en el precio de la casa que el área en pies cuadrados, cuando en realidad ambas características podrían ser igualmente importantes o el área en pies cuadrados podría ser más influyente.

Además, cuando las características están en diferentes escalas:

  • El proceso de optimización durante el entrenamiento del modelo puede verse afectado negativamente, lo que lleva a una convergencia más lenta o soluciones subóptimas.
  • Algunas características podrían dominar a otras únicamente debido a su escala más grande, en lugar de su verdadero poder predictivo.
  • El modelo se vuelve más sensible a pequeños cambios en las características con escalas más grandes, lo que puede provocar inestabilidad en las predicciones.

Al aplicar técnicas de escalado adecuadas, nos aseguramos de que todas las características contribuyan proporcionalmente al modelo, en función de su verdadera importancia y no de su escala numérica. Esto no solo mejora el rendimiento del modelo, sino que también mejora su interpretabilidad, permitiendo comparaciones más precisas y significativas de la importancia de las características a través de sus respectivos coeficientes.

Para ilustrar, considera un conjunto de datos donde una característica representa ingresos (que varía de miles a millones) y otra representa la edad (que varía de 0 a 100). Sin un escalado adecuado:

  • La característica de ingresos dominaría los cálculos de distancia en KNN.
  • Las SVM podrían tener dificultades para encontrar una frontera de decisión óptima.
  • Las redes neuronales podrían enfrentar dificultades en la optimización de los pesos.
  • Los modelos lineales producirían coeficientes que no son directamente comparables.

Para abordar estos problemas, empleamos técnicas de escalado y normalización. Estos métodos transforman todas las características a una escala común, asegurando que cada característica contribuya de manera proporcional al proceso de toma de decisiones del modelo. Las técnicas comunes incluyen:

  • Escalado Min-Max: Escala las características a un rango fijo, típicamente [0, 1].
  • Estandarización: Transforma las características para que tengan una media de cero y una varianza unitaria.
  • Escalado robusto: Utiliza estadísticas que son robustas a valores atípicos, como la mediana y el rango intercuartílico.

Al aplicar estas técnicas, creamos un campo de juego equilibrado para todas las características, permitiendo que los modelos aprendan de cada característica de manera equitativa. Esto no solo mejora el rendimiento del modelo, sino que también mejora la interpretabilidad y la capacidad de generalización a nuevos datos no vistos.

3.4.2 Escalado Min-Max

El escalado min-max, también conocido como normalización, es una técnica fundamental de preprocesamiento de datos que transforma las características a un rango específico, típicamente entre 0 y 1. Este método es esencial en el aprendizaje automático por varias razones:

  1. Escalado de características: Esta técnica asegura que todas las características estén en una escala comparable, evitando que aquellas con magnitudes mayores eclipsen a las que tienen magnitudes menores. Por ejemplo, si una característica varía entre 0 y 100 y otra entre 0 y 1, el escalado min-max normalizaría ambas al rango 0-1, permitiendo que contribuyan de manera equitativa al proceso de toma de decisiones del modelo.
  2. Mejora de la eficiencia de los algoritmos: Muchos algoritmos de aprendizaje automático, especialmente aquellos que dependen de cálculos de distancia o de optimización mediante descenso de gradiente, muestran un mejor rendimiento cuando las características están escaladas de manera similar. Esto incluye algoritmos populares como K-Nearest Neighbors (KNN), Máquinas de Vectores de Soporte (SVM) y varias arquitecturas de redes neuronales. Al igualar las escalas de las características, creamos un espacio de características más equilibrado para que estos algoritmos funcionen de manera óptima.
  3. Retención de valores cero: A diferencia de otros métodos de escalado como la estandarización, el escalado min-max mantiene los valores cero en conjuntos de datos dispersos. Esta característica es particularmente crucial para ciertos tipos de datos o algoritmos donde los valores cero tienen un significado importante, como en el análisis de texto o los sistemas de recomendación.
  4. Manejo de valores atípicos: Aunque el escalado min-max es sensible a los valores atípicos, puede ser ventajoso en escenarios donde se desea preservar la distribución relativa de los valores de las características, al tiempo que se comprime el rango general. Este enfoque puede ayudar a mitigar el impacto de los valores extremos sin eliminar por completo su influencia en el modelo.
  5. Facilidad de interpretación: Los valores escalados resultantes de la normalización min-max son fáciles de interpretar, ya que representan la posición relativa del valor original dentro de su rango. Esta propiedad facilita la comprensión de la importancia de las características y las comparaciones relativas entre diferentes puntos de datos.

Sin embargo, es importante tener en cuenta que el escalado min-max tiene limitaciones. No centra los datos alrededor de cero, lo que puede ser problemático para algunos algoritmos. Además, no maneja bien los valores atípicos, ya que los valores extremos pueden comprimir el rango escalado de la mayoría de los puntos de datos. Por lo tanto, la decisión de utilizar el escalado min-max debe tomarse en función de los requisitos específicos de los datos y los algoritmos que se planea utilizar.

La fórmula para el escalado min-max es:

Donde:

  • X es el valor original de la característica,
  • X' es el valor escalado,
  • X_{min} y X_{max} son los valores mínimos y máximos de la característica, respectivamente.

Aplicación del Escalado Min-Max con Scikit-learn

Scikit-learn ofrece una clase poderosa y fácil de usar llamada MinMaxScaler para implementar el escalado min-max. Esta herramienta versátil simplifica el proceso de transformar las características a un rango específico, típicamente entre 0 y 1, asegurando que todas las variables contribuyan de manera equitativa al proceso de toma de decisiones del modelo.

Al aprovechar este escalador, los científicos de datos pueden normalizar eficientemente sus conjuntos de datos, lo que facilita la creación de modelos de aprendizaje automático más precisos y robustos.

Ejemplo: Escalado Min-Max con Scikit-learn

from sklearn.preprocessing import MinMaxScaler
import pandas as pd

# Sample data
data = {'Age': [25, 30, 35, 40],
        'Income': [50000, 60000, 70000, 80000]}
df = pd.DataFrame(data)

# Initialize the MinMaxScaler
scaler = MinMaxScaler()

# Fit and transform the data
scaled_data = scaler.fit_transform(df)

# Convert the scaled data back to a DataFrame
df_scaled = pd.DataFrame(scaled_data, columns=['Age', 'Income'])
print(df_scaled)

3.4.3 Estandarización (Normalización Z-Score)

La estandarización (también conocida como normalización Z-score) transforma los datos para que tengan una media de 0 y una desviación estándar de 1. Esta técnica es particularmente útil para modelos que asumen que los datos están distribuidos normalmente, como la regresión lineal y la regresión logística. La estandarización es menos afectada por valores atípicos que el escalado min-max porque se centra en la distribución de los datos en lugar de su rango.

La fórmula para la estandarización es:


Z = \frac {X - \mu}{\sigma}


Donde:

  • X es el valor original de la característica,
  • \mu es la media de la característica,
  • \sigma es la desviación estándar de la característica.

Aplicación de la estandarización con Scikit-learn

Scikit-learn proporciona un StandardScaler para estandarizar características.

Ejemplo: Estandarización con Scikit-learn

from sklearn.preprocessing import StandardScaler

# Initialize the StandardScaler
scaler = StandardScaler()

# Fit and transform the data
standardized_data = scaler.fit_transform(df)

# Convert the standardized data back to a DataFrame
df_standardized = pd.DataFrame(standardized_data, columns=['Age', 'Income'])
print(df_standardized)

Aquí, "Edad" e "Ingreso" se transforman para tener una media de 0 y una desviación estándar de 1. Esto asegura que las características contribuyan equitativamente al modelo, especialmente en algoritmos como la regresión logística o redes neuronales.

3.4.4 Escalado Robusto

El escalado robusto es otra técnica de escalado que es particularmente efectiva cuando se trata con datos que contienen valores atípicos. A diferencia de la estandarización y el escalado min-max, que pueden verse fuertemente influenciados por valores extremos, el escalado robusto utiliza la mediana y el rango intercuartílico (IQR) para escalar los datos, haciéndolo más resistente a los valores atípicos.

La fórmula para el escalado robusto es:


X' = \frac{X - Q_2}{IQR}

Donde:

  • Q_2 es la mediana de los datos,
  • IQR es el rango intercuartílico, es decir, la diferencia entre los percentiles 75 y 25.

Aplicación del Escalado Robusto con Scikit-learn

Scikit-learn ofrece una clase potente y versátil llamada RobustScaler, que aplica eficientemente el escalado robusto a las características. Este escalador es particularmente útil cuando se trabaja con conjuntos de datos que contienen valores atípicos o cuando se desea que el método de escalado sea menos sensible a valores extremos.

Al aprovechar la mediana y el rango intercuartílico (IQR) en lugar de la media y la desviación estándar, el RobustScaler ofrece un enfoque más robusto para el escalado de características, manteniendo la integridad de la distribución de los datos incluso en presencia de valores atípicos.

Ejemplo: Escalado Robusto con Scikit-learn

import numpy as np
import pandas as pd
from sklearn.preprocessing import RobustScaler
from sklearn.datasets import make_regression

# Generate sample data
X, y = make_regression(n_samples=100, n_features=2, noise=0.1, random_state=42)
df = pd.DataFrame(X, columns=['Feature1', 'Feature2'])

# Add some outliers
df.loc[0, 'Feature1'] = 1000
df.loc[1, 'Feature2'] = -1000

print("Original data:")
print(df.describe())

# Initialize the RobustScaler
scaler = RobustScaler()

# Fit and transform the data
robust_scaled_data = scaler.fit_transform(df)

# Convert the robust scaled data back to a DataFrame
df_robust_scaled = pd.DataFrame(robust_scaled_data, columns=['Feature1', 'Feature2'])

print("\nRobust scaled data:")
print(df_robust_scaled.describe())

# Compare original and scaled data for a few samples
print("\nComparison of original and scaled data:")
print(pd.concat([df.head(), df_robust_scaled.head()], axis=1))

# Inverse transform to get back original scale
df_inverse = pd.DataFrame(scaler.inverse_transform(robust_scaled_data), columns=['Feature1', 'Feature2'])

print("\nInverse transformed data:")
print(df_inverse.head())

Desglose del Código:

  1. Generación de datos:
    • Usamos make_regression de Scikit-learn para crear un conjunto de datos de muestra con 100 muestras y 2 características.
    • Se agregan valores atípicos artificiales para demostrar la robustez del escalado.
  2. Inicialización de RobustScaler:
    • Creamos una instancia de RobustScaler de Scikit-learn.
    • Por defecto, utiliza el rango intercuartílico (IQR) y la mediana para el escalado.
  3. Ajuste y Transformación:
    • Usamos el método fit_transform() para ajustar el escalador a los datos y transformarlos.
    • Este método calcula la mediana y el IQR para cada característica y luego aplica la transformación.
  4. Creación de un DataFrame:
    • Los datos escalados se convierten de nuevo en un DataFrame de pandas para facilitar la visualización y comparación.
  5. Análisis de Resultados:
    • Imprimimos estadísticas descriptivas tanto de los datos originales como de los escalados.
    • Los datos escalados deberían tener una mediana cercana a 0 y un IQR cercano a 1 para cada característica.
  6. Comparación:
    • Mostramos algunas muestras de los datos originales y escalados lado a lado.
    • Esto ayuda a visualizar cómo afecta el escalado a puntos de datos individuales.
  7. Transformación Inversa:
    • Demostramos cómo revertir el escalado usando inverse_transform().
    • Esto es útil cuando se necesita convertir las predicciones o los datos transformados de nuevo a la escala original.

Este ejemplo de código muestra el flujo completo de uso de RobustScaler, desde la preparación de los datos hasta el escalado y la retrotransformación. Resalta la capacidad del escalador para manejar valores atípicos y proporciona una clara comparación entre los datos originales y los escalados.

En este ejemplo, el escalado robusto garantiza que los valores extremos (valores atípicos) tengan una influencia menor en el proceso de escalado. Esto es particularmente útil en conjuntos de datos donde los valores atípicos están presentes, pero no deben dominar el entrenamiento del modelo.

3.4.5. Transformaciones Logarítmicas

En casos donde las características exhiben una distribución muy sesgada, una transformación logarítmica puede ser una herramienta invaluable para comprimir el rango de valores y reducir la asimetría. Esta técnica es particularmente útil para características como ingresospoblación o precios de acciones, donde los valores pueden abarcar varios órdenes de magnitud.

La transformación logarítmica funciona aplicando la función logaritmo a cada valor en el conjunto de datos. Esto tiene varios efectos beneficiosos:

  • Compresión de valores grandes: Los valores extremadamente grandes se acercan más al resto de los datos, reduciendo el impacto de los valores atípicos.
  • Expansión de valores pequeños: Los valores pequeños se expanden, permitiendo una mejor diferenciación entre ellos.
  • Normalización de la distribución: La transformación a menudo da como resultado una distribución más parecida a la normal, lo que es beneficioso para muchos métodos estadísticos y algoritmos de aprendizaje automático.

Por ejemplo, considera una distribución de ingresos donde los valores varían entre $10,000 y $1,000,000. Después de aplicar una transformación logarítmica:

  • $10,000 se convierte en log(10,000) ≈ 9.21
  • $100,000 se convierte en log(100,000) ≈ 11.51
  • $1,000,000 se convierte en log(1,000,000) ≈ 13.82

Como puedes ver, la gran diferencia entre los valores más altos y más bajos se ha reducido significativamente, haciendo que los datos sean más fáciles de interpretar y procesar para los modelos. Esto puede mejorar el rendimiento del modelo, especialmente en algoritmos que son sensibles a la escala de las características de entrada.

Sin embargo, es importante tener en cuenta que las transformaciones logarítmicas deben usarse con precaución. Son más efectivas cuando los datos están sesgados positivamente y abarcan varios órdenes de magnitud. Además, las transformaciones logarítmicas solo pueden aplicarse a valores positivos, ya que el logaritmo de cero o números negativos no está definido en los sistemas de números reales.

Aplicación de Transformaciones Logarítmicas

Las transformaciones logarítmicas se usan comúnmente para características con una distribución sesgada a la derecha, como ingresos o precios de propiedades.

Ejemplo: Transformación Logarítmica con NumPy

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

# Create a sample dataset
np.random.seed(42)
income = np.random.lognormal(mean=10, sigma=1, size=1000)
df = pd.DataFrame({'Income': income})

# Apply log transformation
df['Log_Income'] = np.log(df['Income'])

# Print summary statistics
print("Original Income:")
print(df['Income'].describe())
print("\nLog-transformed Income:")
print(df['Log_Income'].describe())

# Visualize the distributions
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))

ax1.hist(df['Income'], bins=50, edgecolor='black')
ax1.set_title('Original Income Distribution')
ax1.set_xlabel('Income')
ax1.set_ylabel('Frequency')

ax2.hist(df['Log_Income'], bins=50, edgecolor='black')
ax2.set_title('Log-transformed Income Distribution')
ax2.set_xlabel('Log(Income)')
ax2.set_ylabel('Frequency')

plt.tight_layout()
plt.show()

# Calculate skewness
original_skewness = np.mean(((df['Income'] - df['Income'].mean()) / df['Income'].std())**3)
log_skewness = np.mean(((df['Log_Income'] - df['Log_Income'].mean()) / df['Log_Income'].std())**3)

print(f"\nOriginal Income Skewness: {original_skewness:.2f}")
print(f"Log-transformed Income Skewness: {log_skewness:.2f}")

# Demonstrate inverse transformation
inverse_income = np.exp(df['Log_Income'])
print("\nInverse Transformation (first 5 rows):")
print(pd.DataFrame({'Original': df['Income'][:5], 'Log': df['Log_Income'][:5], 'Inverse': inverse_income[:5]}))

Desglose del Código:

  1. Generación de Datos:
    • Usamos random.lognormal() de NumPy para generar un conjunto de datos de muestra de 1000 valores de ingresos.
    • La distribución lognormal se utiliza a menudo para modelar ingresos, ya que naturalmente produce una distribución sesgada a la derecha.
    • Establecemos una semilla aleatoria para garantizar la reproducibilidad.
  2. Transformación Logarítmica:
    • Aplicamos el logaritmo natural (base e) a la columna 'Income' utilizando la función log() de NumPy.
    • Esto crea una nueva columna 'Log_Income' en nuestro DataFrame.
  3. Estadísticas Resumidas:
    • Imprimimos estadísticas descriptivas tanto de los ingresos originales como de los transformados mediante logaritmo utilizando el método describe() de Pandas.
    • Esto nos permite comparar las características de la distribución antes y después de la transformación.
  4. Visualización:
    • Creamos histogramas de las distribuciones de ingresos originales y transformados logarítmicamente.
    • Esta representación visual ayuda a ver claramente el efecto de la transformación logarítmica en la distribución de los datos.
  5. Cálculo de la Asimetría:
    • Calculamos la asimetría de ambas distribuciones utilizando operaciones de NumPy.
    • La asimetría cuantifica la falta de simetría en la distribución. Un valor cercano a 0 indica una distribución más simétrica.
  6. Transformación Inversa:
    • Demostramos cómo revertir la transformación logarítmica utilizando la función exp() de NumPy.
    • Esto es crucial cuando se necesita interpretar los resultados en la escala original después de realizar análisis sobre datos transformados.

Este ejemplo muestra todo el proceso de transformación logarítmica, desde la generación de datos hasta el análisis y la visualización, utilizando principalmente operaciones de NumPy. Demuestra cómo la transformación logarítmica puede hacer que una distribución sesgada a la derecha sea más simétrica, lo que a menudo es beneficioso para el análisis estadístico y los algoritmos de aprendizaje automático.

En este ejemplo, la transformación logarítmica reduce el amplio rango de valores de ingresos, haciendo que la distribución sea más manejable para los algoritmos de aprendizaje automático. Es importante destacar que las transformaciones logarítmicas solo deben aplicarse a valores positivos, ya que el logaritmo de un número negativo no está definido.

3.4.6 Transformaciones de Potencia

Las transformaciones de potencia son técnicas estadísticas avanzadas utilizadas para modificar la distribución de los datos. Dos ejemplos prominentes son las transformaciones Box-Cox y Yeo-Johnson. Estos métodos tienen dos propósitos principales:

  1. Estabilización de la varianza: Estas transformaciones ayudan a asegurar que la variabilidad de los datos se mantenga constante en todo su rango, lo cual es una suposición crucial en muchos análisis estadísticos. Al aplicar transformaciones de potencia, los investigadores pueden mitigar problemas relacionados con la heterocedasticidad, donde la dispersión de los residuos varía a lo largo del rango de una variable predictora. Esta estabilización de la varianza puede conducir a inferencias estadísticas más confiables y un mejor rendimiento del modelo.
  2. Normalización de distribuciones: Las transformaciones de potencia buscan hacer que los datos se asemejen más a una distribución normal (gaussiana), lo que es beneficioso para muchas pruebas estadísticas y algoritmos de aprendizaje automático. Al cambiar la forma de la distribución de los datos, estas transformaciones pueden ayudar a cumplir con la suposición de normalidad requerida por muchos métodos estadísticos paramétricos. Este proceso de normalización puede revelar patrones ocultos en los datos, mejorar la interpretabilidad de los resultados y potencialmente mejorar el poder predictivo de varios modelos de aprendizaje automático, especialmente aquellos que asumen entradas distribuidas normalmente.

Las transformaciones de potencia son especialmente valiosas cuando se trabaja con características que exhiben distribuciones no normales, como aquellas con una asimetría o curtosis significativa. Al aplicar estas transformaciones, los científicos de datos pueden mejorar el rendimiento y la confiabilidad de sus modelos, especialmente aquellos que asumen entradas distribuidas normalmente.

La transformación de Box-Cox, introducida por los estadísticos George Box y David Cox en 1964, es aplicable solo a datos positivos. Involucra encontrar un parámetro óptimo λ (lambda) que determina la transformación de potencia específica a aplicar. Por otro lado, la transformación de Yeo-Johnson, desarrollada por In-Kwon Yeo y Richard Johnson en el año 2000, extiende el concepto para manejar tanto valores positivos como negativos, haciéndola más versátil en la práctica.

Al emplear estas transformaciones, los analistas pueden descubrir relaciones en los datos que de otro modo podrían estar ocultas, lo que conduce a predicciones más precisas y mejores conocimientos en diversos campos como finanzas, biología y ciencias sociales.

a. Transformación de Box-Cox

La transformación de Box-Cox es una poderosa técnica estadística que solo se puede aplicar a datos positivos. Este método es particularmente útil para abordar la no normalidad en las distribuciones de datos y estabilizar la varianza. Aquí tienes una explicación más detallada:

  1. Selección del parámetro óptimo: La transformación de Box-Cox encuentra un parámetro de transformación óptimo, denotado como λ (lambda). Este parámetro determina la transformación de potencia específica que se aplicará a los datos.
  2. Estabilización de la varianza: Uno de los principales objetivos de la transformación de Box-Cox es estabilizar la varianza a lo largo del rango de los datos. Esto es crucial para muchos análisis estadísticos que asumen homocedasticidad (varianza constante).
  3. Normalización: La transformación busca hacer que los datos se asemejen más a una distribución normal. Esto es beneficioso para muchas pruebas estadísticas y algoritmos de aprendizaje automático que asumen normalidad.
  4. Fórmula matemática: La transformación de Box-Cox se define como:
    • y(λ) = (x^λ - 1) / λ,  if λ ≠ 0
    • y(λ) = log(x),         if λ = 0
      Donde x son los datos originales y y(\lambda) son los datos transformados.
  5. Interpretación: Diferentes valores de λ resultan en diferentes transformaciones. Por ejemplo, λ = 1 significa que no hay transformación, λ = 0 es equivalente a una transformación logarítmica, y λ = 0.5 es equivalente a una transformación por raíz cuadrada.

Al aplicar esta transformación, los analistas pueden descubrir relaciones en los datos que de otro modo podrían estar ocultas, lo que lleva a predicciones más precisas y mejores conocimientos en campos como finanzas, biología y ciencias sociales.

Ejemplo: Transformación de Box-Cox con Scikit-learn

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import PowerTransformer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

# Create a sample dataset
np.random.seed(42)
income = np.random.lognormal(mean=10, sigma=1, size=1000)
age = np.random.normal(loc=40, scale=10, size=1000)
df = pd.DataFrame({'Income': income, 'Age': age})

# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(
    df[['Income', 'Age']], df['Income'], test_size=0.2, random_state=42)

# Initialize the PowerTransformer for Box-Cox (only for positive data)
boxcox_transformer = PowerTransformer(method='box-cox', standardize=True)

# Fit and transform the training data
X_train_transformed = boxcox_transformer.fit_transform(X_train)

# Transform the test data
X_test_transformed = boxcox_transformer.transform(X_test)

# Train a linear regression model on the original data
model_original = LinearRegression()
model_original.fit(X_train, y_train)

# Train a linear regression model on the transformed data
model_transformed = LinearRegression()
model_transformed.fit(X_train_transformed, y_train)

# Make predictions
y_pred_original = model_original.predict(X_test)
y_pred_transformed = model_transformed.predict(X_test_transformed)

# Calculate performance metrics
mse_original = mean_squared_error(y_test, y_pred_original)
r2_original = r2_score(y_test, y_pred_original)
mse_transformed = mean_squared_error(y_test, y_pred_transformed)
r2_transformed = r2_score(y_test, y_pred_transformed)

# Print results
print("Original Data Performance:")
print(f"Mean Squared Error: {mse_original:.2f}")
print(f"R-squared Score: {r2_original:.2f}")
print("\nTransformed Data Performance:")
print(f"Mean Squared Error: {mse_transformed:.2f}")
print(f"R-squared Score: {r2_transformed:.2f}")

# Visualize the distributions
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))

ax1.hist(X_train['Income'], bins=50, edgecolor='black')
ax1.set_title('Original Income Distribution')
ax1.set_xlabel('Income')
ax1.set_ylabel('Frequency')

ax2.hist(X_train_transformed[:, 0], bins=50, edgecolor='black')
ax2.set_title('Box-Cox Transformed Income Distribution')
ax2.set_xlabel('Transformed Income')
ax2.set_ylabel('Frequency')

plt.tight_layout()
plt.show()

Desglose del Código:

  1. Importar bibliotecas necesarias: Importamos NumPy, Pandas, Matplotlib y varios módulos de Scikit-learn para la manipulación de datos, visualización y tareas de aprendizaje automático.
  2. Crear un conjunto de datos de muestra: Generamos un conjunto de datos sintético con características de 'Ingresos' (distribuidos lognormalmente) y 'Edad' (distribuida normalmente).
  3. Dividir los datos: Usamos train_test_split de Scikit-learn para dividir los datos en conjuntos de entrenamiento y prueba.
  4. Inicializar PowerTransformer: Creamos un objeto PowerTransformer para la transformación de Box-Cox, estableciendo standardize=True para asegurar que la salida tenga una media de cero y una varianza unitaria.
  5. Aplicar la transformación de Box-Cox: Ajustamos el transformador con los datos de entrenamiento y transformamos tanto los datos de entrenamiento como los de prueba.
  6. Entrenar modelos de regresión lineal: Creamos dos modelos de LinearRegression: uno para los datos originales y otro para los datos transformados.
  7. Hacer predicciones y evaluar: Usamos ambos modelos para hacer predicciones en el conjunto de prueba y calculamos el error cuadrático medio (MSE) y los puntajes de R-cuadrado utilizando las métricas de Scikit-learn.
  8. Visualizar distribuciones: Creamos histogramas para comparar las distribuciones de ingresos originales y transformados.

Este ejemplo integral muestra todo el proceso de aplicar una transformación de Box-Cox usando Scikit-learn, desde la preparación de los datos hasta la evaluación del modelo. Demuestra cómo la transformación puede afectar el rendimiento del modelo y la distribución de los datos, proporcionando un contexto práctico para entender el impacto de las transformaciones de potencia en los flujos de trabajo de aprendizaje automático.

b. Transformación de Yeo-Johnson

La transformación de Yeo-Johnson es una extensión de la transformación de Box-Cox que ofrece una mayor flexibilidad en el preprocesamiento de datos. Mientras que Box-Cox está limitado a datos estrictamente positivos, Yeo-Johnson puede manejar tanto valores positivos como negativos, lo que la hace más versátil para conjuntos de datos del mundo real. Esta transformación fue desarrollada por In-Kwon Yeo y Richard A. Johnson en el año 2000 para abordar las limitaciones de Box-Cox.

Las características clave de la transformación de Yeo-Johnson incluyen:

  • Aplicabilidad a todos los números reales: A diferencia de Box-Cox, Yeo-Johnson puede aplicarse a valores cero y negativos, eliminando la necesidad de ajustar los datos.
  • Continuidad en cero: La transformación es continua en λ = 0, lo que asegura transiciones suaves entre diferentes transformaciones de potencia.
  • Efecto de normalización: Similar a Box-Cox, ayuda a normalizar datos sesgados, lo que potencialmente mejora el rendimiento de los algoritmos de aprendizaje automático que asumen entradas distribuidas normalmente.
  • Estabilización de la varianza: Puede ayudar a estabilizar la varianza en todo el rango de los datos, abordando problemas de heterocedasticidad en los análisis estadísticos.

La formulación matemática de la transformación de Yeo-Johnson es un poco más compleja que la de Box-Cox, ya que acomoda tanto valores positivos como negativos a través de diferentes ecuaciones basadas en el signo del valor de entrada. Esta complejidad adicional permite una mayor adaptabilidad a diversos conjuntos de datos, lo que la convierte en una herramienta poderosa en el conjunto de herramientas de preprocesamiento de un científico de datos.

Ejemplo: Transformación de Yeo-Johnson con Scikit-learn

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import PowerTransformer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

# Create a sample dataset with both positive and negative values
np.random.seed(42)
income = np.random.lognormal(mean=10, sigma=1, size=1000)
expenses = np.random.normal(loc=50000, scale=10000, size=1000)
net_income = income - expenses
df = pd.DataFrame({'Income': income, 'Expenses': expenses, 'NetIncome': net_income})

# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(
    df[['Income', 'Expenses']], df['NetIncome'], test_size=0.2, random_state=42)

# Initialize the PowerTransformer for Yeo-Johnson
yeojohnson_transformer = PowerTransformer(method='yeo-johnson', standardize=True)

# Fit and transform the training data
X_train_transformed = yeojohnson_transformer.fit_transform(X_train)

# Transform the test data
X_test_transformed = yeojohnson_transformer.transform(X_test)

# Train linear regression models on original and transformed data
model_original = LinearRegression().fit(X_train, y_train)
model_transformed = LinearRegression().fit(X_train_transformed, y_train)

# Make predictions
y_pred_original = model_original.predict(X_test)
y_pred_transformed = model_transformed.predict(X_test_transformed)

# Calculate performance metrics
mse_original = mean_squared_error(y_test, y_pred_original)
r2_original = r2_score(y_test, y_pred_original)
mse_transformed = mean_squared_error(y_test, y_pred_transformed)
r2_transformed = r2_score(y_test, y_pred_transformed)

# Print results
print("Original Data Performance:")
print(f"Mean Squared Error: {mse_original:.2f}")
print(f"R-squared Score: {r2_original:.2f}")
print("\nTransformed Data Performance:")
print(f"Mean Squared Error: {mse_transformed:.2f}")
print(f"R-squared Score: {r2_transformed:.2f}")

# Visualize the distributions
fig, axs = plt.subplots(2, 2, figsize=(15, 15))

axs[0, 0].hist(X_train['Income'], bins=50, edgecolor='black')
axs[0, 0].set_title('Original Income Distribution')
axs[0, 0].set_xlabel('Income')
axs[0, 0].set_ylabel('Frequency')

axs[0, 1].hist(X_train_transformed[:, 0], bins=50, edgecolor='black')
axs[0, 1].set_title('Yeo-Johnson Transformed Income Distribution')
axs[0, 1].set_xlabel('Transformed Income')
axs[0, 1].set_ylabel('Frequency')

axs[1, 0].hist(X_train['Expenses'], bins=50, edgecolor='black')
axs[1, 0].set_title('Original Expenses Distribution')
axs[1, 0].set_xlabel('Expenses')
axs[1, 0].set_ylabel('Frequency')

axs[1, 1].hist(X_train_transformed[:, 1], bins=50, edgecolor='black')
axs[1, 1].set_title('Yeo-Johnson Transformed Expenses Distribution')
axs[1, 1].set_xlabel('Transformed Expenses')
axs[1, 1].set_ylabel('Frequency')

plt.tight_layout()
plt.show()

# Print the lambda values used for transformation
print("\nLambda values used for Yeo-Johnson transformation:")
print(yeojohnson_transformer.lambdas_)

Desglose del Código:

  1. Generación de datos: Creamos un conjunto de datos sintético con 'Ingresos' (distribuidos lognormalmente), 'Gastos' (distribuidos normalmente) y 'IngresoNeto' (diferencia entre Ingresos y Gastos). Este conjunto de datos incluye valores tanto positivos como negativos, lo que muestra la capacidad de Yeo-Johnson para manejar tales datos.
  2. División de datos: Usamos train_test_split de Scikit-learn para dividir nuestros datos en conjuntos de entrenamiento y prueba. Esto es crucial para evaluar el rendimiento del modelo con datos no vistos.
  3. Transformación de Yeo-Johnson: Inicializamos un PowerTransformer con method='yeo-johnson'. El parámetro standardize=True asegura que la salida transformada tenga media cero y varianza unitaria.
  4. Entrenamiento del modelo: Entrenamos dos modelos de regresión lineal (LinearRegression): uno con los datos originales y otro con los datos transformados por Yeo-Johnson. Esto nos permite comparar el rendimiento de los modelos con y sin la transformación.
  5. Predicción y evaluación: Usamos ambos modelos para hacer predicciones en el conjunto de prueba y calculamos el error cuadrático medio (MSE) y los puntajes de R-cuadrado utilizando las métricas de Scikit-learn. Esto nos ayuda a cuantificar el impacto de la transformación Yeo-Johnson en el rendimiento del modelo.
  6. Visualización: Creamos histogramas para comparar las distribuciones originales y transformadas tanto de Ingresos como de Gastos. Esta representación visual ayuda a entender cómo la transformación Yeo-Johnson afecta la distribución de los datos.
  7. Valores de Lambda: Imprimimos los valores de lambda utilizados para la transformación de Yeo-Johnson. Estos valores indican la transformación de potencia específica aplicada a cada característica.

Este ejemplo demuestra todo el proceso de aplicar una transformación Yeo-Johnson usando Scikit-learn, desde la preparación de los datos hasta la evaluación y visualización del modelo. Muestra cómo la transformación puede afectar el rendimiento del modelo y la distribución de los datos, proporcionando un contexto práctico para entender el impacto de las transformaciones de potencia en los flujos de trabajo de aprendizaje automático, especialmente cuando se trabaja con conjuntos de datos que incluyen tanto valores positivos como negativos.

3.4.7 Normalización (L1 y L2)

La normalización es una técnica crucial en el preprocesamiento de datos que se utiliza para volver a escalar las características de modo que la norma del vector de características sea 1. Este proceso asegura que todas las características contribuyan de manera equitativa al análisis, evitando que aquellas con magnitudes mayores dominen el modelo. La normalización es particularmente valiosa en algoritmos de aprendizaje automático que dependen de cálculos de distancia, como K-Nearest Neighbors (KNN) o la agrupación K-means.

En KNN, por ejemplo, la normalización ayuda a evitar que las características con escalas más grandes tengan una influencia desproporcionada en los cálculos de distancia. De manera similar, en K-means, las características normalizadas aseguran que la agrupación se base en la importancia relativa de las características en lugar de sus escalas absolutas.

Hay dos tipos principales de normalización:

a. Normalización L1 (norma Manhattan)

La normalización L1, también conocida como norma Manhattan, es un método que asegura que la suma de los valores absolutos de un vector de características sea igual a 1. Esta técnica es particularmente útil en el preprocesamiento de datos para algoritmos de aprendizaje automático. Para entender la normalización L1, desglosémosla matemáticamente:

Para un vector de características x = (x₁, ..., xₙ), la norma L1 se calcula como:

||x||₁ = |x₁| + |x₂| + ... + |xₙ|

donde |xᵢ| representa el valor absoluto de cada característica.

Para lograr la normalización L1, dividimos cada característica por la norma L1:

x_normalizado = x / ||x||₁

Este proceso resulta en un vector de características normalizado donde la suma de los valores absolutos es igual a 1.

Una ventaja notable de la normalización L1 es su menor sensibilidad a los valores atípicos en comparación con la normalización L2. Esta característica la hace particularmente útil en escenarios donde los valores extremos podrían influir desproporcionadamente en el rendimiento del modelo. Además, la normalización L1 puede llevar a vectores de características dispersos, lo que puede ser beneficioso en ciertas aplicaciones de aprendizaje automático, como la selección de características o técnicas de regularización como la regresión Lasso.

Ejemplo de código de normalización L1:

import numpy as np
import pandas as pd
from sklearn.preprocessing import Normalizer
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score

# Create a sample dataset
np.random.seed(42)
X = np.random.rand(100, 3) * 100  # 100 samples, 3 features
y = np.random.randint(0, 2, 100)  # Binary classification

# Split the data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Initialize L1 normalizer
l1_normalizer = Normalizer(norm='l1')

# Fit and transform the training data
X_train_normalized = l1_normalizer.fit_transform(X_train)

# Transform the test data
X_test_normalized = l1_normalizer.transform(X_test)

# Train KNN classifier on original data
knn_original = KNeighborsClassifier(n_neighbors=3)
knn_original.fit(X_train, y_train)
y_pred_original = knn_original.predict(X_test)

# Train KNN classifier on normalized data
knn_normalized = KNeighborsClassifier(n_neighbors=3)
knn_normalized.fit(X_train_normalized, y_train)
y_pred_normalized = knn_normalized.predict(X_test_normalized)

# Calculate accuracies
accuracy_original = accuracy_score(y_test, y_pred_original)
accuracy_normalized = accuracy_score(y_test, y_pred_normalized)

print("Original Data Accuracy:", accuracy_original)
print("L1 Normalized Data Accuracy:", accuracy_normalized)

# Display a sample of original and normalized data
sample_original = X_train[:5]
sample_normalized = X_train_normalized[:5]

print("\nOriginal Data Sample:")
print(pd.DataFrame(sample_original, columns=['Feature 1', 'Feature 2', 'Feature 3']))

print("\nL1 Normalized Data Sample:")
print(pd.DataFrame(sample_normalized, columns=['Feature 1', 'Feature 2', 'Feature 3']))

# Verify L1 norm
print("\nL1 Norm of normalized samples:")
print(np.sum(np.abs(sample_normalized), axis=1))

Desglose del Código:

  1. Generación de datos: Creamos un conjunto de datos sintético con 100 muestras y 3 características, junto con etiquetas de clasificación binaria. Esto simula un escenario del mundo real donde las características pueden tener diferentes escalas.
  2. División de datos: Usamos train_test_split para dividir nuestros datos en conjuntos de entrenamiento y prueba. Esto es crucial para evaluar el rendimiento del modelo con datos no vistos.
  3. Normalización L1: Inicializamos un Normalizer con norm='l1'. Este normalizador se ajusta a los datos de entrenamiento y luego se usa para transformar tanto los datos de entrenamiento como los de prueba.
  4. Entrenamiento del modelo: Entrenamos dos clasificadores KNN: uno con los datos originales y otro con los datos normalizados con L1. Esto nos permite comparar el rendimiento de los modelos con y sin normalización.
  5. Predicción y evaluación: Ambos modelos hacen predicciones en sus respectivos conjuntos de prueba (original y normalizado). Luego, calculamos y comparamos las puntuaciones de precisión para ver el impacto de la normalización L1.
  6. Visualización de datos: Mostramos muestras de los datos originales y normalizados para ilustrar cómo la normalización L1 afecta los valores de las características.
  7. Verificación de la norma L1: Calculamos la suma de los valores absolutos de cada muestra normalizada para verificar que la norma L1 sea igual a 1 después de la normalización.

Este ejemplo muestra todo el proceso de aplicar la normalización L1 utilizando Scikit-learn, desde la preparación de los datos hasta la evaluación del modelo. Demuestra cómo la normalización puede afectar el rendimiento del modelo y la representación de los datos, proporcionando un contexto práctico para comprender el impacto de la normalización L1 en los flujos de trabajo de aprendizaje automático.

b. Normalización L2 (norma euclidiana):

La normalización L2, también conocida como norma euclidiana, es una técnica poderosa que asegura que la suma de los valores cuadrados dentro de un vector de características sea igual a 1. Este método es particularmente efectivo para estandarizar datos en diferentes escalas y dimensiones. Para ilustrarlo, consideremos un vector de características x = (x₁, ..., xₙ). La norma L2 para este vector se calcula utilizando la siguiente fórmula:

||x||₂ = √(x₁² + x₂² + ... + xₙ²)

Una vez que hemos calculado la norma L2, podemos proceder con el proceso de normalización. Esto se logra dividiendo cada característica individual por la norma L2 calculada:

x_normalizado = x / ||x||₂

El vector normalizado resultante mantiene las mismas propiedades direccionales que el original, pero con una longitud unitaria. Esta transformación tiene varias ventajas en las aplicaciones de aprendizaje automático. Por ejemplo, ayuda a mitigar el impacto de los valores atípicos y asegura que todas las características contribuyan de manera equitativa al modelo, independientemente de su escala original.

La normalización L2 se adopta ampliamente en varios algoritmos de aprendizaje automático y es especialmente beneficiosa cuando se trabaja con vectores dispersos. Su popularidad se debe a su capacidad para preservar la importancia relativa de las características mientras se estandarizan sus magnitudes. Esta característica la hace particularmente útil en escenarios como la clasificación de texto, el reconocimiento de imágenes y los sistemas de recomendación, donde el escalado de características puede afectar significativamente el rendimiento del modelo.

Ejemplo de código de normalización L2:

import numpy as np
import pandas as pd
from sklearn.preprocessing import Normalizer
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score

# Create a sample dataset
np.random.seed(42)
X = np.random.rand(100, 3) * 100  # 100 samples, 3 features
y = np.random.randint(0, 2, 100)  # Binary classification

# Split the data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Initialize L2 normalizer
l2_normalizer = Normalizer(norm='l2')

# Fit and transform the training data
X_train_normalized = l2_normalizer.fit_transform(X_train)

# Transform the test data
X_test_normalized = l2_normalizer.transform(X_test)

# Train KNN classifier on original data
knn_original = KNeighborsClassifier(n_neighbors=3)
knn_original.fit(X_train, y_train)
y_pred_original = knn_original.predict(X_test)

# Train KNN classifier on normalized data
knn_normalized = KNeighborsClassifier(n_neighbors=3)
knn_normalized.fit(X_train_normalized, y_train)
y_pred_normalized = knn_normalized.predict(X_test_normalized)

# Calculate accuracies
accuracy_original = accuracy_score(y_test, y_pred_original)
accuracy_normalized = accuracy_score(y_test, y_pred_normalized)

print("Original Data Accuracy:", accuracy_original)
print("L2 Normalized Data Accuracy:", accuracy_normalized)

# Display a sample of original and normalized data
sample_original = X_train[:5]
sample_normalized = X_train_normalized[:5]

print("\nOriginal Data Sample:")
print(pd.DataFrame(sample_original, columns=['Feature 1', 'Feature 2', 'Feature 3']))

print("\nL2 Normalized Data Sample:")
print(pd.DataFrame(sample_normalized, columns=['Feature 1', 'Feature 2', 'Feature 3']))

# Verify L2 norm
print("\nL2 Norm of normalized samples:")
print(np.sqrt(np.sum(np.square(sample_normalized), axis=1)))

Desglose del Código:

  1. Generación de datos: Creamos un conjunto de datos sintético con 100 muestras y 3 características, junto con etiquetas de clasificación binaria. Esto simula un escenario del mundo real donde las características pueden tener diferentes escalas.
  2. División de datos: Usamos train_test_split para dividir nuestros datos en conjuntos de entrenamiento y prueba. Esto es crucial para evaluar el rendimiento del modelo con datos no vistos.
  3. Normalización L2: Inicializamos un Normalizer con norm='l2'. Este normalizador se ajusta a los datos de entrenamiento y luego se usa para transformar tanto los datos de entrenamiento como los de prueba.
  4. Entrenamiento del modelo: Entrenamos dos clasificadores KNN: uno con los datos originales y otro con los datos normalizados con L2. Esto nos permite comparar el rendimiento de los modelos con y sin normalización.
  5. Predicción y evaluación: Ambos modelos hacen predicciones en sus respectivos conjuntos de prueba (original y normalizado). Luego calculamos y comparamos las puntuaciones de precisión para ver el impacto de la normalización L2.
  6. Visualización de datos: Mostramos muestras de los datos originales y normalizados para ilustrar cómo la normalización L2 afecta los valores de las características.
  7. Verificación de la norma L2: Calculamos la norma L2 para cada muestra normalizada para verificar que sea igual a 1 después de la normalización.

Este ejemplo demuestra todo el proceso de aplicar la normalización L2 utilizando Scikit-learn, desde la preparación de los datos hasta la evaluación del modelo. Demuestra cómo la normalización puede afectar el rendimiento del modelo y la representación de los datos, proporcionando un contexto práctico para entender el impacto de la normalización L2 en los flujos de trabajo de aprendizaje automático. La comparación entre las precisiones de los datos originales y normalizados ayuda a ilustrar los posibles beneficios de la normalización L2 para mejorar el rendimiento del modelo, especialmente para algoritmos basados en distancia como KNN.

La elección entre la normalización L1 y L2 depende de los requisitos específicos de tu tarea de aprendizaje automático y la naturaleza de tus datos. Ambos métodos tienen sus fortalezas y son herramientas valiosas en el conjunto de herramientas del científico de datos para preparar características para análisis y entrenamiento de modelos.

3.4 Técnicas de escalado, normalización y transformación de datos

La escala y distribución de tu conjunto de datos puede influir profundamente en la efectividad de muchos modelos, especialmente aquellos que dependen en gran medida de cálculos de distancia o que emplean técnicas de optimización basadas en gradientes.

Muchos algoritmos de aprendizaje automático operan bajo la suposición de que todas las características tienen una escala uniforme. Si no se aborda esta suposición, las características con rangos más amplios pueden dominar el proceso de entrenamiento, mientras que las características con rangos más estrechos pueden perderse. Para mitigar estos desafíos y garantizar un rendimiento óptimo del modelo, se emplean diversas técnicas de preprocesamiento de datos, como el escalado, la normalización y otros métodos transformativos.

3.4.1 Por qué el escalado y la normalización de datos son importantes

Los modelos de aprendizaje automático, especialmente aquellos que dependen de cálculos de distancia o de optimización basada en gradientes, son muy sensibles a la escala y el rango de las características de entrada. Esta sensibilidad puede conducir a problemas significativos en el rendimiento del modelo si no se abordan correctamente.

1. K-Nearest Neighbors (KNN)

KNN es un algoritmo de aprendizaje automático que se basa en gran medida en cálculos de distancia entre puntos de datos para hacer predicciones o clasificaciones. Sin embargo, la efectividad de KNN puede verse significativamente afectada por la escala de las diferentes características en el conjunto de datos.

Cuando las características en un conjunto de datos tienen escalas muy diferentes, esto puede conducir a resultados sesgados e inexactos en los algoritmos KNN. Las características con rangos numéricos más grandes influirán de manera desproporcionada en los cálculos de distancia, eclipsando el impacto de las características con rangos más pequeños.

Ejemplo concreto:

Considera un conjunto de datos con dos características: ingresos anuales y edad. Los ingresos anuales pueden variar de miles a millones (por ejemplo, $30,000 a $1,000,000), mientras que la edad típicamente varía de 0 a 100. En este escenario:

  • La característica de ingresos, debido a su escala mucho más grande, dominará los cálculos de distancia. Incluso una pequeña diferencia en ingresos (por ejemplo, $10,000) crearía una distancia mucho mayor que una diferencia significativa en la edad (por ejemplo, 20 años).
  • Esto significa que el algoritmo ignoraría prácticamente la característica de la edad, basando sus decisiones casi exclusivamente en las diferencias de ingresos.
  • Como resultado, dos personas con ingresos similares pero edades muy diferentes podrían ser consideradas "vecinos cercanos" por el algoritmo, incluso si la diferencia de edad es crucial para el análisis.

Esto puede conducir a problemas como:

  • Clasificación incorrecta: El algoritmo puede clasificar incorrectamente los puntos de datos basándose en la característica que domina.
  • Pérdida de información: Se pierden perspectivas valiosas de características con escalas más pequeñas, como la edad.
  • Reducción del rendimiento del modelo: La precisión y fiabilidad del modelo KNN pueden verse comprometidas.

Para mitigar estos problemas, es crucial aplicar técnicas adecuadas de escalado (como la estandarización o normalización) para garantizar que todas las características contribuyan proporcionalmente a los cálculos de distancia. Este paso de preprocesamiento ayuda a crear un campo de juego equilibrado para todas las características, lo que permite que el algoritmo KNN haga predicciones más precisas basadas en similitudes relevantes entre puntos de datos.

2. Máquinas de Vectores de Soporte (SVM)

Las Máquinas de Vectores de Soporte (SVM) son algoritmos poderosos utilizados para tareas de clasificación y regresión. Funcionan encontrando el hiperplano óptimo que mejor separa las diferentes clases en el espacio de características. Sin embargo, cuando las características están en diferentes escalas, las SVM pueden enfrentar desafíos significativos:

  • Determinación del hiperplano: El principio central de las SVM es maximizar el margen entre las clases. Cuando las características tienen escalas muy diferentes, el algoritmo puede tener dificultades para encontrar este hiperplano óptimo de manera eficiente. Esto se debe a que la característica con la escala más grande dominará los cálculos de distancia utilizados para determinar el margen.
  • Sesgo en la importancia de las características: Las características con magnitudes mayores podrían recibir una importancia indebida al determinar la frontera de decisión. Por ejemplo, si una característica varía de 0 a 1 y otra de 0 a 1000, la segunda tendrá una influencia mucho mayor en el proceso de toma de decisiones de la SVM, incluso si no es inherentemente más importante para la tarea de clasificación.
  • Impacto en la función kernel: Muchas SVM utilizan funciones kernel (como el kernel RBF) para mapear los datos a espacios de mayor dimensión. Estas funciones suelen depender de los cálculos de distancia entre puntos de datos. Cuando las características están en diferentes escalas, estos cálculos de distancia pueden distorsionarse, lo que lleva a un rendimiento subóptimo de la función kernel.
  • Problemas de convergencia: El proceso de optimización en las SVM puede volverse más lento y menos estable cuando las características no están escaladas de manera uniforme. Esto se debe a que el paisaje de optimización se vuelve más complejo y potencialmente más difícil de navegar cuando las características tienen rangos muy diferentes.
  • Dificultades de interpretación: En las SVM lineales, los coeficientes de la función de decisión pueden interpretarse como la importancia de las características. Sin embargo, cuando las características están en diferentes escalas, estos coeficientes se vuelven difíciles de comparar e interpretar con precisión.

Para mitigar estos problemas, es crucial aplicar técnicas de escalado adecuadas (como la estandarización o normalización) antes de entrenar una SVM. Esto asegura que todas las características contribuyan proporcionalmente al proceso de toma de decisiones del modelo, lo que lleva a resultados más precisos y confiables.

3. Algoritmos basados en gradiente

Las redes neuronales y otros métodos basados en gradiente emplean con frecuencia técnicas de optimización como el descenso de gradiente. Estos algoritmos son particularmente sensibles a la escala de las características de entrada, y cuando las características tienen escalas muy diferentes, pueden surgir varios problemas:

  • Paisaje de optimización alargado: Cuando las características tienen escalas diferentes, el paisaje de optimización se alarga y distorsiona. Esto significa que los contornos de la función de pérdida se estiran en la dirección de la característica con la mayor escala. Como resultado, el algoritmo de descenso de gradiente puede zigzaguear de un lado a otro a lo largo del valle estrecho de la superficie de error alargada, lo que dificulta la convergencia eficiente hacia la solución óptima.
  • Sensibilidad a la tasa de aprendizaje: La tasa de aprendizaje, un hiperparámetro crucial en el descenso de gradiente, se vuelve más difícil de ajustar correctamente cuando las características tienen escalas diferentes. Una tasa de aprendizaje que funcione bien para una característica podría ser demasiado grande o pequeña para otra, lo que provocaría que se sobrepase el mínimo o que la convergencia sea lenta.
  • Dominancia de características: Las características con escalas más grandes pueden dominar el proceso de aprendizaje, lo que hace que el modelo sea demasiado sensible a los cambios en estas características mientras subvalora el impacto de las características con escalas más pequeñas. Esto puede llevar a un modelo sesgado que no capture con precisión las relaciones verdaderas en los datos.
  • Convergencia más lenta: Debido a los desafíos mencionados, el proceso de optimización a menudo requiere más iteraciones para converger. Esto resulta en tiempos de entrenamiento más largos, lo que puede ser problemático al trabajar con conjuntos de datos grandes o modelos complejos.
  • Soluciones subóptimas: En algunos casos, las dificultades para navegar por el paisaje de optimización pueden causar que el algoritmo se atasque en mínimos locales o puntos de silla, lo que lleva a soluciones subóptimas. Esto significa que el modelo final podría no rendir tan bien como lo haría si las características estuvieran adecuadamente escaladas.
  • Inestabilidad numérica: Las grandes diferencias en las escalas de las características a veces pueden llevar a inestabilidad numérica durante el cálculo de gradientes, especialmente cuando se utiliza aritmética de punto flotante. Esto puede resultar en problemas como gradientes que explotan o desaparecen, lo que es particularmente problemático en redes neuronales profundas.

Para mitigar estos problemas, es crucial aplicar técnicas de escalado adecuadas, como la estandarización o normalización, antes de entrenar modelos basados en gradiente. Esto asegura que todas las características contribuyan proporcionalmente al proceso de optimización, lo que lleva a una convergencia más rápida, un entrenamiento más estable y un rendimiento potencialmente mejor del modelo.

4. Modelos lineales

En la regresión lineal o regresión logística, los coeficientes del modelo representan directamente el impacto o la importancia de cada característica sobre el resultado predicho. Esta interpretabilidad es una de las ventajas clave de los modelos lineales. Sin embargo, cuando las características están en escalas muy diferentes, comparar estos coeficientes se vuelve problemático y puede llevar a una mala interpretación de la importancia de las características.

Por ejemplo, considera un modelo de regresión lineal que predice precios de casas basado en dos características: el número de habitaciones (que típicamente varía de 1 a 10) y el área en pies cuadrados (que podría variar de 500 a 5000). Sin un escalado adecuado:

  • El coeficiente para el área en pies cuadrados probablemente sería mucho menor que el coeficiente para el número de habitaciones, simplemente debido a la diferencia en la escala de los datos.
  • Esto podría sugerir erróneamente que el número de habitaciones tiene un impacto más significativo en el precio de la casa que el área en pies cuadrados, cuando en realidad ambas características podrían ser igualmente importantes o el área en pies cuadrados podría ser más influyente.

Además, cuando las características están en diferentes escalas:

  • El proceso de optimización durante el entrenamiento del modelo puede verse afectado negativamente, lo que lleva a una convergencia más lenta o soluciones subóptimas.
  • Algunas características podrían dominar a otras únicamente debido a su escala más grande, en lugar de su verdadero poder predictivo.
  • El modelo se vuelve más sensible a pequeños cambios en las características con escalas más grandes, lo que puede provocar inestabilidad en las predicciones.

Al aplicar técnicas de escalado adecuadas, nos aseguramos de que todas las características contribuyan proporcionalmente al modelo, en función de su verdadera importancia y no de su escala numérica. Esto no solo mejora el rendimiento del modelo, sino que también mejora su interpretabilidad, permitiendo comparaciones más precisas y significativas de la importancia de las características a través de sus respectivos coeficientes.

Para ilustrar, considera un conjunto de datos donde una característica representa ingresos (que varía de miles a millones) y otra representa la edad (que varía de 0 a 100). Sin un escalado adecuado:

  • La característica de ingresos dominaría los cálculos de distancia en KNN.
  • Las SVM podrían tener dificultades para encontrar una frontera de decisión óptima.
  • Las redes neuronales podrían enfrentar dificultades en la optimización de los pesos.
  • Los modelos lineales producirían coeficientes que no son directamente comparables.

Para abordar estos problemas, empleamos técnicas de escalado y normalización. Estos métodos transforman todas las características a una escala común, asegurando que cada característica contribuya de manera proporcional al proceso de toma de decisiones del modelo. Las técnicas comunes incluyen:

  • Escalado Min-Max: Escala las características a un rango fijo, típicamente [0, 1].
  • Estandarización: Transforma las características para que tengan una media de cero y una varianza unitaria.
  • Escalado robusto: Utiliza estadísticas que son robustas a valores atípicos, como la mediana y el rango intercuartílico.

Al aplicar estas técnicas, creamos un campo de juego equilibrado para todas las características, permitiendo que los modelos aprendan de cada característica de manera equitativa. Esto no solo mejora el rendimiento del modelo, sino que también mejora la interpretabilidad y la capacidad de generalización a nuevos datos no vistos.

3.4.2 Escalado Min-Max

El escalado min-max, también conocido como normalización, es una técnica fundamental de preprocesamiento de datos que transforma las características a un rango específico, típicamente entre 0 y 1. Este método es esencial en el aprendizaje automático por varias razones:

  1. Escalado de características: Esta técnica asegura que todas las características estén en una escala comparable, evitando que aquellas con magnitudes mayores eclipsen a las que tienen magnitudes menores. Por ejemplo, si una característica varía entre 0 y 100 y otra entre 0 y 1, el escalado min-max normalizaría ambas al rango 0-1, permitiendo que contribuyan de manera equitativa al proceso de toma de decisiones del modelo.
  2. Mejora de la eficiencia de los algoritmos: Muchos algoritmos de aprendizaje automático, especialmente aquellos que dependen de cálculos de distancia o de optimización mediante descenso de gradiente, muestran un mejor rendimiento cuando las características están escaladas de manera similar. Esto incluye algoritmos populares como K-Nearest Neighbors (KNN), Máquinas de Vectores de Soporte (SVM) y varias arquitecturas de redes neuronales. Al igualar las escalas de las características, creamos un espacio de características más equilibrado para que estos algoritmos funcionen de manera óptima.
  3. Retención de valores cero: A diferencia de otros métodos de escalado como la estandarización, el escalado min-max mantiene los valores cero en conjuntos de datos dispersos. Esta característica es particularmente crucial para ciertos tipos de datos o algoritmos donde los valores cero tienen un significado importante, como en el análisis de texto o los sistemas de recomendación.
  4. Manejo de valores atípicos: Aunque el escalado min-max es sensible a los valores atípicos, puede ser ventajoso en escenarios donde se desea preservar la distribución relativa de los valores de las características, al tiempo que se comprime el rango general. Este enfoque puede ayudar a mitigar el impacto de los valores extremos sin eliminar por completo su influencia en el modelo.
  5. Facilidad de interpretación: Los valores escalados resultantes de la normalización min-max son fáciles de interpretar, ya que representan la posición relativa del valor original dentro de su rango. Esta propiedad facilita la comprensión de la importancia de las características y las comparaciones relativas entre diferentes puntos de datos.

Sin embargo, es importante tener en cuenta que el escalado min-max tiene limitaciones. No centra los datos alrededor de cero, lo que puede ser problemático para algunos algoritmos. Además, no maneja bien los valores atípicos, ya que los valores extremos pueden comprimir el rango escalado de la mayoría de los puntos de datos. Por lo tanto, la decisión de utilizar el escalado min-max debe tomarse en función de los requisitos específicos de los datos y los algoritmos que se planea utilizar.

La fórmula para el escalado min-max es:

Donde:

  • X es el valor original de la característica,
  • X' es el valor escalado,
  • X_{min} y X_{max} son los valores mínimos y máximos de la característica, respectivamente.

Aplicación del Escalado Min-Max con Scikit-learn

Scikit-learn ofrece una clase poderosa y fácil de usar llamada MinMaxScaler para implementar el escalado min-max. Esta herramienta versátil simplifica el proceso de transformar las características a un rango específico, típicamente entre 0 y 1, asegurando que todas las variables contribuyan de manera equitativa al proceso de toma de decisiones del modelo.

Al aprovechar este escalador, los científicos de datos pueden normalizar eficientemente sus conjuntos de datos, lo que facilita la creación de modelos de aprendizaje automático más precisos y robustos.

Ejemplo: Escalado Min-Max con Scikit-learn

from sklearn.preprocessing import MinMaxScaler
import pandas as pd

# Sample data
data = {'Age': [25, 30, 35, 40],
        'Income': [50000, 60000, 70000, 80000]}
df = pd.DataFrame(data)

# Initialize the MinMaxScaler
scaler = MinMaxScaler()

# Fit and transform the data
scaled_data = scaler.fit_transform(df)

# Convert the scaled data back to a DataFrame
df_scaled = pd.DataFrame(scaled_data, columns=['Age', 'Income'])
print(df_scaled)

3.4.3 Estandarización (Normalización Z-Score)

La estandarización (también conocida como normalización Z-score) transforma los datos para que tengan una media de 0 y una desviación estándar de 1. Esta técnica es particularmente útil para modelos que asumen que los datos están distribuidos normalmente, como la regresión lineal y la regresión logística. La estandarización es menos afectada por valores atípicos que el escalado min-max porque se centra en la distribución de los datos en lugar de su rango.

La fórmula para la estandarización es:


Z = \frac {X - \mu}{\sigma}


Donde:

  • X es el valor original de la característica,
  • \mu es la media de la característica,
  • \sigma es la desviación estándar de la característica.

Aplicación de la estandarización con Scikit-learn

Scikit-learn proporciona un StandardScaler para estandarizar características.

Ejemplo: Estandarización con Scikit-learn

from sklearn.preprocessing import StandardScaler

# Initialize the StandardScaler
scaler = StandardScaler()

# Fit and transform the data
standardized_data = scaler.fit_transform(df)

# Convert the standardized data back to a DataFrame
df_standardized = pd.DataFrame(standardized_data, columns=['Age', 'Income'])
print(df_standardized)

Aquí, "Edad" e "Ingreso" se transforman para tener una media de 0 y una desviación estándar de 1. Esto asegura que las características contribuyan equitativamente al modelo, especialmente en algoritmos como la regresión logística o redes neuronales.

3.4.4 Escalado Robusto

El escalado robusto es otra técnica de escalado que es particularmente efectiva cuando se trata con datos que contienen valores atípicos. A diferencia de la estandarización y el escalado min-max, que pueden verse fuertemente influenciados por valores extremos, el escalado robusto utiliza la mediana y el rango intercuartílico (IQR) para escalar los datos, haciéndolo más resistente a los valores atípicos.

La fórmula para el escalado robusto es:


X' = \frac{X - Q_2}{IQR}

Donde:

  • Q_2 es la mediana de los datos,
  • IQR es el rango intercuartílico, es decir, la diferencia entre los percentiles 75 y 25.

Aplicación del Escalado Robusto con Scikit-learn

Scikit-learn ofrece una clase potente y versátil llamada RobustScaler, que aplica eficientemente el escalado robusto a las características. Este escalador es particularmente útil cuando se trabaja con conjuntos de datos que contienen valores atípicos o cuando se desea que el método de escalado sea menos sensible a valores extremos.

Al aprovechar la mediana y el rango intercuartílico (IQR) en lugar de la media y la desviación estándar, el RobustScaler ofrece un enfoque más robusto para el escalado de características, manteniendo la integridad de la distribución de los datos incluso en presencia de valores atípicos.

Ejemplo: Escalado Robusto con Scikit-learn

import numpy as np
import pandas as pd
from sklearn.preprocessing import RobustScaler
from sklearn.datasets import make_regression

# Generate sample data
X, y = make_regression(n_samples=100, n_features=2, noise=0.1, random_state=42)
df = pd.DataFrame(X, columns=['Feature1', 'Feature2'])

# Add some outliers
df.loc[0, 'Feature1'] = 1000
df.loc[1, 'Feature2'] = -1000

print("Original data:")
print(df.describe())

# Initialize the RobustScaler
scaler = RobustScaler()

# Fit and transform the data
robust_scaled_data = scaler.fit_transform(df)

# Convert the robust scaled data back to a DataFrame
df_robust_scaled = pd.DataFrame(robust_scaled_data, columns=['Feature1', 'Feature2'])

print("\nRobust scaled data:")
print(df_robust_scaled.describe())

# Compare original and scaled data for a few samples
print("\nComparison of original and scaled data:")
print(pd.concat([df.head(), df_robust_scaled.head()], axis=1))

# Inverse transform to get back original scale
df_inverse = pd.DataFrame(scaler.inverse_transform(robust_scaled_data), columns=['Feature1', 'Feature2'])

print("\nInverse transformed data:")
print(df_inverse.head())

Desglose del Código:

  1. Generación de datos:
    • Usamos make_regression de Scikit-learn para crear un conjunto de datos de muestra con 100 muestras y 2 características.
    • Se agregan valores atípicos artificiales para demostrar la robustez del escalado.
  2. Inicialización de RobustScaler:
    • Creamos una instancia de RobustScaler de Scikit-learn.
    • Por defecto, utiliza el rango intercuartílico (IQR) y la mediana para el escalado.
  3. Ajuste y Transformación:
    • Usamos el método fit_transform() para ajustar el escalador a los datos y transformarlos.
    • Este método calcula la mediana y el IQR para cada característica y luego aplica la transformación.
  4. Creación de un DataFrame:
    • Los datos escalados se convierten de nuevo en un DataFrame de pandas para facilitar la visualización y comparación.
  5. Análisis de Resultados:
    • Imprimimos estadísticas descriptivas tanto de los datos originales como de los escalados.
    • Los datos escalados deberían tener una mediana cercana a 0 y un IQR cercano a 1 para cada característica.
  6. Comparación:
    • Mostramos algunas muestras de los datos originales y escalados lado a lado.
    • Esto ayuda a visualizar cómo afecta el escalado a puntos de datos individuales.
  7. Transformación Inversa:
    • Demostramos cómo revertir el escalado usando inverse_transform().
    • Esto es útil cuando se necesita convertir las predicciones o los datos transformados de nuevo a la escala original.

Este ejemplo de código muestra el flujo completo de uso de RobustScaler, desde la preparación de los datos hasta el escalado y la retrotransformación. Resalta la capacidad del escalador para manejar valores atípicos y proporciona una clara comparación entre los datos originales y los escalados.

En este ejemplo, el escalado robusto garantiza que los valores extremos (valores atípicos) tengan una influencia menor en el proceso de escalado. Esto es particularmente útil en conjuntos de datos donde los valores atípicos están presentes, pero no deben dominar el entrenamiento del modelo.

3.4.5. Transformaciones Logarítmicas

En casos donde las características exhiben una distribución muy sesgada, una transformación logarítmica puede ser una herramienta invaluable para comprimir el rango de valores y reducir la asimetría. Esta técnica es particularmente útil para características como ingresospoblación o precios de acciones, donde los valores pueden abarcar varios órdenes de magnitud.

La transformación logarítmica funciona aplicando la función logaritmo a cada valor en el conjunto de datos. Esto tiene varios efectos beneficiosos:

  • Compresión de valores grandes: Los valores extremadamente grandes se acercan más al resto de los datos, reduciendo el impacto de los valores atípicos.
  • Expansión de valores pequeños: Los valores pequeños se expanden, permitiendo una mejor diferenciación entre ellos.
  • Normalización de la distribución: La transformación a menudo da como resultado una distribución más parecida a la normal, lo que es beneficioso para muchos métodos estadísticos y algoritmos de aprendizaje automático.

Por ejemplo, considera una distribución de ingresos donde los valores varían entre $10,000 y $1,000,000. Después de aplicar una transformación logarítmica:

  • $10,000 se convierte en log(10,000) ≈ 9.21
  • $100,000 se convierte en log(100,000) ≈ 11.51
  • $1,000,000 se convierte en log(1,000,000) ≈ 13.82

Como puedes ver, la gran diferencia entre los valores más altos y más bajos se ha reducido significativamente, haciendo que los datos sean más fáciles de interpretar y procesar para los modelos. Esto puede mejorar el rendimiento del modelo, especialmente en algoritmos que son sensibles a la escala de las características de entrada.

Sin embargo, es importante tener en cuenta que las transformaciones logarítmicas deben usarse con precaución. Son más efectivas cuando los datos están sesgados positivamente y abarcan varios órdenes de magnitud. Además, las transformaciones logarítmicas solo pueden aplicarse a valores positivos, ya que el logaritmo de cero o números negativos no está definido en los sistemas de números reales.

Aplicación de Transformaciones Logarítmicas

Las transformaciones logarítmicas se usan comúnmente para características con una distribución sesgada a la derecha, como ingresos o precios de propiedades.

Ejemplo: Transformación Logarítmica con NumPy

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

# Create a sample dataset
np.random.seed(42)
income = np.random.lognormal(mean=10, sigma=1, size=1000)
df = pd.DataFrame({'Income': income})

# Apply log transformation
df['Log_Income'] = np.log(df['Income'])

# Print summary statistics
print("Original Income:")
print(df['Income'].describe())
print("\nLog-transformed Income:")
print(df['Log_Income'].describe())

# Visualize the distributions
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))

ax1.hist(df['Income'], bins=50, edgecolor='black')
ax1.set_title('Original Income Distribution')
ax1.set_xlabel('Income')
ax1.set_ylabel('Frequency')

ax2.hist(df['Log_Income'], bins=50, edgecolor='black')
ax2.set_title('Log-transformed Income Distribution')
ax2.set_xlabel('Log(Income)')
ax2.set_ylabel('Frequency')

plt.tight_layout()
plt.show()

# Calculate skewness
original_skewness = np.mean(((df['Income'] - df['Income'].mean()) / df['Income'].std())**3)
log_skewness = np.mean(((df['Log_Income'] - df['Log_Income'].mean()) / df['Log_Income'].std())**3)

print(f"\nOriginal Income Skewness: {original_skewness:.2f}")
print(f"Log-transformed Income Skewness: {log_skewness:.2f}")

# Demonstrate inverse transformation
inverse_income = np.exp(df['Log_Income'])
print("\nInverse Transformation (first 5 rows):")
print(pd.DataFrame({'Original': df['Income'][:5], 'Log': df['Log_Income'][:5], 'Inverse': inverse_income[:5]}))

Desglose del Código:

  1. Generación de Datos:
    • Usamos random.lognormal() de NumPy para generar un conjunto de datos de muestra de 1000 valores de ingresos.
    • La distribución lognormal se utiliza a menudo para modelar ingresos, ya que naturalmente produce una distribución sesgada a la derecha.
    • Establecemos una semilla aleatoria para garantizar la reproducibilidad.
  2. Transformación Logarítmica:
    • Aplicamos el logaritmo natural (base e) a la columna 'Income' utilizando la función log() de NumPy.
    • Esto crea una nueva columna 'Log_Income' en nuestro DataFrame.
  3. Estadísticas Resumidas:
    • Imprimimos estadísticas descriptivas tanto de los ingresos originales como de los transformados mediante logaritmo utilizando el método describe() de Pandas.
    • Esto nos permite comparar las características de la distribución antes y después de la transformación.
  4. Visualización:
    • Creamos histogramas de las distribuciones de ingresos originales y transformados logarítmicamente.
    • Esta representación visual ayuda a ver claramente el efecto de la transformación logarítmica en la distribución de los datos.
  5. Cálculo de la Asimetría:
    • Calculamos la asimetría de ambas distribuciones utilizando operaciones de NumPy.
    • La asimetría cuantifica la falta de simetría en la distribución. Un valor cercano a 0 indica una distribución más simétrica.
  6. Transformación Inversa:
    • Demostramos cómo revertir la transformación logarítmica utilizando la función exp() de NumPy.
    • Esto es crucial cuando se necesita interpretar los resultados en la escala original después de realizar análisis sobre datos transformados.

Este ejemplo muestra todo el proceso de transformación logarítmica, desde la generación de datos hasta el análisis y la visualización, utilizando principalmente operaciones de NumPy. Demuestra cómo la transformación logarítmica puede hacer que una distribución sesgada a la derecha sea más simétrica, lo que a menudo es beneficioso para el análisis estadístico y los algoritmos de aprendizaje automático.

En este ejemplo, la transformación logarítmica reduce el amplio rango de valores de ingresos, haciendo que la distribución sea más manejable para los algoritmos de aprendizaje automático. Es importante destacar que las transformaciones logarítmicas solo deben aplicarse a valores positivos, ya que el logaritmo de un número negativo no está definido.

3.4.6 Transformaciones de Potencia

Las transformaciones de potencia son técnicas estadísticas avanzadas utilizadas para modificar la distribución de los datos. Dos ejemplos prominentes son las transformaciones Box-Cox y Yeo-Johnson. Estos métodos tienen dos propósitos principales:

  1. Estabilización de la varianza: Estas transformaciones ayudan a asegurar que la variabilidad de los datos se mantenga constante en todo su rango, lo cual es una suposición crucial en muchos análisis estadísticos. Al aplicar transformaciones de potencia, los investigadores pueden mitigar problemas relacionados con la heterocedasticidad, donde la dispersión de los residuos varía a lo largo del rango de una variable predictora. Esta estabilización de la varianza puede conducir a inferencias estadísticas más confiables y un mejor rendimiento del modelo.
  2. Normalización de distribuciones: Las transformaciones de potencia buscan hacer que los datos se asemejen más a una distribución normal (gaussiana), lo que es beneficioso para muchas pruebas estadísticas y algoritmos de aprendizaje automático. Al cambiar la forma de la distribución de los datos, estas transformaciones pueden ayudar a cumplir con la suposición de normalidad requerida por muchos métodos estadísticos paramétricos. Este proceso de normalización puede revelar patrones ocultos en los datos, mejorar la interpretabilidad de los resultados y potencialmente mejorar el poder predictivo de varios modelos de aprendizaje automático, especialmente aquellos que asumen entradas distribuidas normalmente.

Las transformaciones de potencia son especialmente valiosas cuando se trabaja con características que exhiben distribuciones no normales, como aquellas con una asimetría o curtosis significativa. Al aplicar estas transformaciones, los científicos de datos pueden mejorar el rendimiento y la confiabilidad de sus modelos, especialmente aquellos que asumen entradas distribuidas normalmente.

La transformación de Box-Cox, introducida por los estadísticos George Box y David Cox en 1964, es aplicable solo a datos positivos. Involucra encontrar un parámetro óptimo λ (lambda) que determina la transformación de potencia específica a aplicar. Por otro lado, la transformación de Yeo-Johnson, desarrollada por In-Kwon Yeo y Richard Johnson en el año 2000, extiende el concepto para manejar tanto valores positivos como negativos, haciéndola más versátil en la práctica.

Al emplear estas transformaciones, los analistas pueden descubrir relaciones en los datos que de otro modo podrían estar ocultas, lo que conduce a predicciones más precisas y mejores conocimientos en diversos campos como finanzas, biología y ciencias sociales.

a. Transformación de Box-Cox

La transformación de Box-Cox es una poderosa técnica estadística que solo se puede aplicar a datos positivos. Este método es particularmente útil para abordar la no normalidad en las distribuciones de datos y estabilizar la varianza. Aquí tienes una explicación más detallada:

  1. Selección del parámetro óptimo: La transformación de Box-Cox encuentra un parámetro de transformación óptimo, denotado como λ (lambda). Este parámetro determina la transformación de potencia específica que se aplicará a los datos.
  2. Estabilización de la varianza: Uno de los principales objetivos de la transformación de Box-Cox es estabilizar la varianza a lo largo del rango de los datos. Esto es crucial para muchos análisis estadísticos que asumen homocedasticidad (varianza constante).
  3. Normalización: La transformación busca hacer que los datos se asemejen más a una distribución normal. Esto es beneficioso para muchas pruebas estadísticas y algoritmos de aprendizaje automático que asumen normalidad.
  4. Fórmula matemática: La transformación de Box-Cox se define como:
    • y(λ) = (x^λ - 1) / λ,  if λ ≠ 0
    • y(λ) = log(x),         if λ = 0
      Donde x son los datos originales y y(\lambda) son los datos transformados.
  5. Interpretación: Diferentes valores de λ resultan en diferentes transformaciones. Por ejemplo, λ = 1 significa que no hay transformación, λ = 0 es equivalente a una transformación logarítmica, y λ = 0.5 es equivalente a una transformación por raíz cuadrada.

Al aplicar esta transformación, los analistas pueden descubrir relaciones en los datos que de otro modo podrían estar ocultas, lo que lleva a predicciones más precisas y mejores conocimientos en campos como finanzas, biología y ciencias sociales.

Ejemplo: Transformación de Box-Cox con Scikit-learn

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import PowerTransformer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

# Create a sample dataset
np.random.seed(42)
income = np.random.lognormal(mean=10, sigma=1, size=1000)
age = np.random.normal(loc=40, scale=10, size=1000)
df = pd.DataFrame({'Income': income, 'Age': age})

# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(
    df[['Income', 'Age']], df['Income'], test_size=0.2, random_state=42)

# Initialize the PowerTransformer for Box-Cox (only for positive data)
boxcox_transformer = PowerTransformer(method='box-cox', standardize=True)

# Fit and transform the training data
X_train_transformed = boxcox_transformer.fit_transform(X_train)

# Transform the test data
X_test_transformed = boxcox_transformer.transform(X_test)

# Train a linear regression model on the original data
model_original = LinearRegression()
model_original.fit(X_train, y_train)

# Train a linear regression model on the transformed data
model_transformed = LinearRegression()
model_transformed.fit(X_train_transformed, y_train)

# Make predictions
y_pred_original = model_original.predict(X_test)
y_pred_transformed = model_transformed.predict(X_test_transformed)

# Calculate performance metrics
mse_original = mean_squared_error(y_test, y_pred_original)
r2_original = r2_score(y_test, y_pred_original)
mse_transformed = mean_squared_error(y_test, y_pred_transformed)
r2_transformed = r2_score(y_test, y_pred_transformed)

# Print results
print("Original Data Performance:")
print(f"Mean Squared Error: {mse_original:.2f}")
print(f"R-squared Score: {r2_original:.2f}")
print("\nTransformed Data Performance:")
print(f"Mean Squared Error: {mse_transformed:.2f}")
print(f"R-squared Score: {r2_transformed:.2f}")

# Visualize the distributions
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))

ax1.hist(X_train['Income'], bins=50, edgecolor='black')
ax1.set_title('Original Income Distribution')
ax1.set_xlabel('Income')
ax1.set_ylabel('Frequency')

ax2.hist(X_train_transformed[:, 0], bins=50, edgecolor='black')
ax2.set_title('Box-Cox Transformed Income Distribution')
ax2.set_xlabel('Transformed Income')
ax2.set_ylabel('Frequency')

plt.tight_layout()
plt.show()

Desglose del Código:

  1. Importar bibliotecas necesarias: Importamos NumPy, Pandas, Matplotlib y varios módulos de Scikit-learn para la manipulación de datos, visualización y tareas de aprendizaje automático.
  2. Crear un conjunto de datos de muestra: Generamos un conjunto de datos sintético con características de 'Ingresos' (distribuidos lognormalmente) y 'Edad' (distribuida normalmente).
  3. Dividir los datos: Usamos train_test_split de Scikit-learn para dividir los datos en conjuntos de entrenamiento y prueba.
  4. Inicializar PowerTransformer: Creamos un objeto PowerTransformer para la transformación de Box-Cox, estableciendo standardize=True para asegurar que la salida tenga una media de cero y una varianza unitaria.
  5. Aplicar la transformación de Box-Cox: Ajustamos el transformador con los datos de entrenamiento y transformamos tanto los datos de entrenamiento como los de prueba.
  6. Entrenar modelos de regresión lineal: Creamos dos modelos de LinearRegression: uno para los datos originales y otro para los datos transformados.
  7. Hacer predicciones y evaluar: Usamos ambos modelos para hacer predicciones en el conjunto de prueba y calculamos el error cuadrático medio (MSE) y los puntajes de R-cuadrado utilizando las métricas de Scikit-learn.
  8. Visualizar distribuciones: Creamos histogramas para comparar las distribuciones de ingresos originales y transformados.

Este ejemplo integral muestra todo el proceso de aplicar una transformación de Box-Cox usando Scikit-learn, desde la preparación de los datos hasta la evaluación del modelo. Demuestra cómo la transformación puede afectar el rendimiento del modelo y la distribución de los datos, proporcionando un contexto práctico para entender el impacto de las transformaciones de potencia en los flujos de trabajo de aprendizaje automático.

b. Transformación de Yeo-Johnson

La transformación de Yeo-Johnson es una extensión de la transformación de Box-Cox que ofrece una mayor flexibilidad en el preprocesamiento de datos. Mientras que Box-Cox está limitado a datos estrictamente positivos, Yeo-Johnson puede manejar tanto valores positivos como negativos, lo que la hace más versátil para conjuntos de datos del mundo real. Esta transformación fue desarrollada por In-Kwon Yeo y Richard A. Johnson en el año 2000 para abordar las limitaciones de Box-Cox.

Las características clave de la transformación de Yeo-Johnson incluyen:

  • Aplicabilidad a todos los números reales: A diferencia de Box-Cox, Yeo-Johnson puede aplicarse a valores cero y negativos, eliminando la necesidad de ajustar los datos.
  • Continuidad en cero: La transformación es continua en λ = 0, lo que asegura transiciones suaves entre diferentes transformaciones de potencia.
  • Efecto de normalización: Similar a Box-Cox, ayuda a normalizar datos sesgados, lo que potencialmente mejora el rendimiento de los algoritmos de aprendizaje automático que asumen entradas distribuidas normalmente.
  • Estabilización de la varianza: Puede ayudar a estabilizar la varianza en todo el rango de los datos, abordando problemas de heterocedasticidad en los análisis estadísticos.

La formulación matemática de la transformación de Yeo-Johnson es un poco más compleja que la de Box-Cox, ya que acomoda tanto valores positivos como negativos a través de diferentes ecuaciones basadas en el signo del valor de entrada. Esta complejidad adicional permite una mayor adaptabilidad a diversos conjuntos de datos, lo que la convierte en una herramienta poderosa en el conjunto de herramientas de preprocesamiento de un científico de datos.

Ejemplo: Transformación de Yeo-Johnson con Scikit-learn

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import PowerTransformer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

# Create a sample dataset with both positive and negative values
np.random.seed(42)
income = np.random.lognormal(mean=10, sigma=1, size=1000)
expenses = np.random.normal(loc=50000, scale=10000, size=1000)
net_income = income - expenses
df = pd.DataFrame({'Income': income, 'Expenses': expenses, 'NetIncome': net_income})

# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(
    df[['Income', 'Expenses']], df['NetIncome'], test_size=0.2, random_state=42)

# Initialize the PowerTransformer for Yeo-Johnson
yeojohnson_transformer = PowerTransformer(method='yeo-johnson', standardize=True)

# Fit and transform the training data
X_train_transformed = yeojohnson_transformer.fit_transform(X_train)

# Transform the test data
X_test_transformed = yeojohnson_transformer.transform(X_test)

# Train linear regression models on original and transformed data
model_original = LinearRegression().fit(X_train, y_train)
model_transformed = LinearRegression().fit(X_train_transformed, y_train)

# Make predictions
y_pred_original = model_original.predict(X_test)
y_pred_transformed = model_transformed.predict(X_test_transformed)

# Calculate performance metrics
mse_original = mean_squared_error(y_test, y_pred_original)
r2_original = r2_score(y_test, y_pred_original)
mse_transformed = mean_squared_error(y_test, y_pred_transformed)
r2_transformed = r2_score(y_test, y_pred_transformed)

# Print results
print("Original Data Performance:")
print(f"Mean Squared Error: {mse_original:.2f}")
print(f"R-squared Score: {r2_original:.2f}")
print("\nTransformed Data Performance:")
print(f"Mean Squared Error: {mse_transformed:.2f}")
print(f"R-squared Score: {r2_transformed:.2f}")

# Visualize the distributions
fig, axs = plt.subplots(2, 2, figsize=(15, 15))

axs[0, 0].hist(X_train['Income'], bins=50, edgecolor='black')
axs[0, 0].set_title('Original Income Distribution')
axs[0, 0].set_xlabel('Income')
axs[0, 0].set_ylabel('Frequency')

axs[0, 1].hist(X_train_transformed[:, 0], bins=50, edgecolor='black')
axs[0, 1].set_title('Yeo-Johnson Transformed Income Distribution')
axs[0, 1].set_xlabel('Transformed Income')
axs[0, 1].set_ylabel('Frequency')

axs[1, 0].hist(X_train['Expenses'], bins=50, edgecolor='black')
axs[1, 0].set_title('Original Expenses Distribution')
axs[1, 0].set_xlabel('Expenses')
axs[1, 0].set_ylabel('Frequency')

axs[1, 1].hist(X_train_transformed[:, 1], bins=50, edgecolor='black')
axs[1, 1].set_title('Yeo-Johnson Transformed Expenses Distribution')
axs[1, 1].set_xlabel('Transformed Expenses')
axs[1, 1].set_ylabel('Frequency')

plt.tight_layout()
plt.show()

# Print the lambda values used for transformation
print("\nLambda values used for Yeo-Johnson transformation:")
print(yeojohnson_transformer.lambdas_)

Desglose del Código:

  1. Generación de datos: Creamos un conjunto de datos sintético con 'Ingresos' (distribuidos lognormalmente), 'Gastos' (distribuidos normalmente) y 'IngresoNeto' (diferencia entre Ingresos y Gastos). Este conjunto de datos incluye valores tanto positivos como negativos, lo que muestra la capacidad de Yeo-Johnson para manejar tales datos.
  2. División de datos: Usamos train_test_split de Scikit-learn para dividir nuestros datos en conjuntos de entrenamiento y prueba. Esto es crucial para evaluar el rendimiento del modelo con datos no vistos.
  3. Transformación de Yeo-Johnson: Inicializamos un PowerTransformer con method='yeo-johnson'. El parámetro standardize=True asegura que la salida transformada tenga media cero y varianza unitaria.
  4. Entrenamiento del modelo: Entrenamos dos modelos de regresión lineal (LinearRegression): uno con los datos originales y otro con los datos transformados por Yeo-Johnson. Esto nos permite comparar el rendimiento de los modelos con y sin la transformación.
  5. Predicción y evaluación: Usamos ambos modelos para hacer predicciones en el conjunto de prueba y calculamos el error cuadrático medio (MSE) y los puntajes de R-cuadrado utilizando las métricas de Scikit-learn. Esto nos ayuda a cuantificar el impacto de la transformación Yeo-Johnson en el rendimiento del modelo.
  6. Visualización: Creamos histogramas para comparar las distribuciones originales y transformadas tanto de Ingresos como de Gastos. Esta representación visual ayuda a entender cómo la transformación Yeo-Johnson afecta la distribución de los datos.
  7. Valores de Lambda: Imprimimos los valores de lambda utilizados para la transformación de Yeo-Johnson. Estos valores indican la transformación de potencia específica aplicada a cada característica.

Este ejemplo demuestra todo el proceso de aplicar una transformación Yeo-Johnson usando Scikit-learn, desde la preparación de los datos hasta la evaluación y visualización del modelo. Muestra cómo la transformación puede afectar el rendimiento del modelo y la distribución de los datos, proporcionando un contexto práctico para entender el impacto de las transformaciones de potencia en los flujos de trabajo de aprendizaje automático, especialmente cuando se trabaja con conjuntos de datos que incluyen tanto valores positivos como negativos.

3.4.7 Normalización (L1 y L2)

La normalización es una técnica crucial en el preprocesamiento de datos que se utiliza para volver a escalar las características de modo que la norma del vector de características sea 1. Este proceso asegura que todas las características contribuyan de manera equitativa al análisis, evitando que aquellas con magnitudes mayores dominen el modelo. La normalización es particularmente valiosa en algoritmos de aprendizaje automático que dependen de cálculos de distancia, como K-Nearest Neighbors (KNN) o la agrupación K-means.

En KNN, por ejemplo, la normalización ayuda a evitar que las características con escalas más grandes tengan una influencia desproporcionada en los cálculos de distancia. De manera similar, en K-means, las características normalizadas aseguran que la agrupación se base en la importancia relativa de las características en lugar de sus escalas absolutas.

Hay dos tipos principales de normalización:

a. Normalización L1 (norma Manhattan)

La normalización L1, también conocida como norma Manhattan, es un método que asegura que la suma de los valores absolutos de un vector de características sea igual a 1. Esta técnica es particularmente útil en el preprocesamiento de datos para algoritmos de aprendizaje automático. Para entender la normalización L1, desglosémosla matemáticamente:

Para un vector de características x = (x₁, ..., xₙ), la norma L1 se calcula como:

||x||₁ = |x₁| + |x₂| + ... + |xₙ|

donde |xᵢ| representa el valor absoluto de cada característica.

Para lograr la normalización L1, dividimos cada característica por la norma L1:

x_normalizado = x / ||x||₁

Este proceso resulta en un vector de características normalizado donde la suma de los valores absolutos es igual a 1.

Una ventaja notable de la normalización L1 es su menor sensibilidad a los valores atípicos en comparación con la normalización L2. Esta característica la hace particularmente útil en escenarios donde los valores extremos podrían influir desproporcionadamente en el rendimiento del modelo. Además, la normalización L1 puede llevar a vectores de características dispersos, lo que puede ser beneficioso en ciertas aplicaciones de aprendizaje automático, como la selección de características o técnicas de regularización como la regresión Lasso.

Ejemplo de código de normalización L1:

import numpy as np
import pandas as pd
from sklearn.preprocessing import Normalizer
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score

# Create a sample dataset
np.random.seed(42)
X = np.random.rand(100, 3) * 100  # 100 samples, 3 features
y = np.random.randint(0, 2, 100)  # Binary classification

# Split the data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Initialize L1 normalizer
l1_normalizer = Normalizer(norm='l1')

# Fit and transform the training data
X_train_normalized = l1_normalizer.fit_transform(X_train)

# Transform the test data
X_test_normalized = l1_normalizer.transform(X_test)

# Train KNN classifier on original data
knn_original = KNeighborsClassifier(n_neighbors=3)
knn_original.fit(X_train, y_train)
y_pred_original = knn_original.predict(X_test)

# Train KNN classifier on normalized data
knn_normalized = KNeighborsClassifier(n_neighbors=3)
knn_normalized.fit(X_train_normalized, y_train)
y_pred_normalized = knn_normalized.predict(X_test_normalized)

# Calculate accuracies
accuracy_original = accuracy_score(y_test, y_pred_original)
accuracy_normalized = accuracy_score(y_test, y_pred_normalized)

print("Original Data Accuracy:", accuracy_original)
print("L1 Normalized Data Accuracy:", accuracy_normalized)

# Display a sample of original and normalized data
sample_original = X_train[:5]
sample_normalized = X_train_normalized[:5]

print("\nOriginal Data Sample:")
print(pd.DataFrame(sample_original, columns=['Feature 1', 'Feature 2', 'Feature 3']))

print("\nL1 Normalized Data Sample:")
print(pd.DataFrame(sample_normalized, columns=['Feature 1', 'Feature 2', 'Feature 3']))

# Verify L1 norm
print("\nL1 Norm of normalized samples:")
print(np.sum(np.abs(sample_normalized), axis=1))

Desglose del Código:

  1. Generación de datos: Creamos un conjunto de datos sintético con 100 muestras y 3 características, junto con etiquetas de clasificación binaria. Esto simula un escenario del mundo real donde las características pueden tener diferentes escalas.
  2. División de datos: Usamos train_test_split para dividir nuestros datos en conjuntos de entrenamiento y prueba. Esto es crucial para evaluar el rendimiento del modelo con datos no vistos.
  3. Normalización L1: Inicializamos un Normalizer con norm='l1'. Este normalizador se ajusta a los datos de entrenamiento y luego se usa para transformar tanto los datos de entrenamiento como los de prueba.
  4. Entrenamiento del modelo: Entrenamos dos clasificadores KNN: uno con los datos originales y otro con los datos normalizados con L1. Esto nos permite comparar el rendimiento de los modelos con y sin normalización.
  5. Predicción y evaluación: Ambos modelos hacen predicciones en sus respectivos conjuntos de prueba (original y normalizado). Luego, calculamos y comparamos las puntuaciones de precisión para ver el impacto de la normalización L1.
  6. Visualización de datos: Mostramos muestras de los datos originales y normalizados para ilustrar cómo la normalización L1 afecta los valores de las características.
  7. Verificación de la norma L1: Calculamos la suma de los valores absolutos de cada muestra normalizada para verificar que la norma L1 sea igual a 1 después de la normalización.

Este ejemplo muestra todo el proceso de aplicar la normalización L1 utilizando Scikit-learn, desde la preparación de los datos hasta la evaluación del modelo. Demuestra cómo la normalización puede afectar el rendimiento del modelo y la representación de los datos, proporcionando un contexto práctico para comprender el impacto de la normalización L1 en los flujos de trabajo de aprendizaje automático.

b. Normalización L2 (norma euclidiana):

La normalización L2, también conocida como norma euclidiana, es una técnica poderosa que asegura que la suma de los valores cuadrados dentro de un vector de características sea igual a 1. Este método es particularmente efectivo para estandarizar datos en diferentes escalas y dimensiones. Para ilustrarlo, consideremos un vector de características x = (x₁, ..., xₙ). La norma L2 para este vector se calcula utilizando la siguiente fórmula:

||x||₂ = √(x₁² + x₂² + ... + xₙ²)

Una vez que hemos calculado la norma L2, podemos proceder con el proceso de normalización. Esto se logra dividiendo cada característica individual por la norma L2 calculada:

x_normalizado = x / ||x||₂

El vector normalizado resultante mantiene las mismas propiedades direccionales que el original, pero con una longitud unitaria. Esta transformación tiene varias ventajas en las aplicaciones de aprendizaje automático. Por ejemplo, ayuda a mitigar el impacto de los valores atípicos y asegura que todas las características contribuyan de manera equitativa al modelo, independientemente de su escala original.

La normalización L2 se adopta ampliamente en varios algoritmos de aprendizaje automático y es especialmente beneficiosa cuando se trabaja con vectores dispersos. Su popularidad se debe a su capacidad para preservar la importancia relativa de las características mientras se estandarizan sus magnitudes. Esta característica la hace particularmente útil en escenarios como la clasificación de texto, el reconocimiento de imágenes y los sistemas de recomendación, donde el escalado de características puede afectar significativamente el rendimiento del modelo.

Ejemplo de código de normalización L2:

import numpy as np
import pandas as pd
from sklearn.preprocessing import Normalizer
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score

# Create a sample dataset
np.random.seed(42)
X = np.random.rand(100, 3) * 100  # 100 samples, 3 features
y = np.random.randint(0, 2, 100)  # Binary classification

# Split the data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Initialize L2 normalizer
l2_normalizer = Normalizer(norm='l2')

# Fit and transform the training data
X_train_normalized = l2_normalizer.fit_transform(X_train)

# Transform the test data
X_test_normalized = l2_normalizer.transform(X_test)

# Train KNN classifier on original data
knn_original = KNeighborsClassifier(n_neighbors=3)
knn_original.fit(X_train, y_train)
y_pred_original = knn_original.predict(X_test)

# Train KNN classifier on normalized data
knn_normalized = KNeighborsClassifier(n_neighbors=3)
knn_normalized.fit(X_train_normalized, y_train)
y_pred_normalized = knn_normalized.predict(X_test_normalized)

# Calculate accuracies
accuracy_original = accuracy_score(y_test, y_pred_original)
accuracy_normalized = accuracy_score(y_test, y_pred_normalized)

print("Original Data Accuracy:", accuracy_original)
print("L2 Normalized Data Accuracy:", accuracy_normalized)

# Display a sample of original and normalized data
sample_original = X_train[:5]
sample_normalized = X_train_normalized[:5]

print("\nOriginal Data Sample:")
print(pd.DataFrame(sample_original, columns=['Feature 1', 'Feature 2', 'Feature 3']))

print("\nL2 Normalized Data Sample:")
print(pd.DataFrame(sample_normalized, columns=['Feature 1', 'Feature 2', 'Feature 3']))

# Verify L2 norm
print("\nL2 Norm of normalized samples:")
print(np.sqrt(np.sum(np.square(sample_normalized), axis=1)))

Desglose del Código:

  1. Generación de datos: Creamos un conjunto de datos sintético con 100 muestras y 3 características, junto con etiquetas de clasificación binaria. Esto simula un escenario del mundo real donde las características pueden tener diferentes escalas.
  2. División de datos: Usamos train_test_split para dividir nuestros datos en conjuntos de entrenamiento y prueba. Esto es crucial para evaluar el rendimiento del modelo con datos no vistos.
  3. Normalización L2: Inicializamos un Normalizer con norm='l2'. Este normalizador se ajusta a los datos de entrenamiento y luego se usa para transformar tanto los datos de entrenamiento como los de prueba.
  4. Entrenamiento del modelo: Entrenamos dos clasificadores KNN: uno con los datos originales y otro con los datos normalizados con L2. Esto nos permite comparar el rendimiento de los modelos con y sin normalización.
  5. Predicción y evaluación: Ambos modelos hacen predicciones en sus respectivos conjuntos de prueba (original y normalizado). Luego calculamos y comparamos las puntuaciones de precisión para ver el impacto de la normalización L2.
  6. Visualización de datos: Mostramos muestras de los datos originales y normalizados para ilustrar cómo la normalización L2 afecta los valores de las características.
  7. Verificación de la norma L2: Calculamos la norma L2 para cada muestra normalizada para verificar que sea igual a 1 después de la normalización.

Este ejemplo demuestra todo el proceso de aplicar la normalización L2 utilizando Scikit-learn, desde la preparación de los datos hasta la evaluación del modelo. Demuestra cómo la normalización puede afectar el rendimiento del modelo y la representación de los datos, proporcionando un contexto práctico para entender el impacto de la normalización L2 en los flujos de trabajo de aprendizaje automático. La comparación entre las precisiones de los datos originales y normalizados ayuda a ilustrar los posibles beneficios de la normalización L2 para mejorar el rendimiento del modelo, especialmente para algoritmos basados en distancia como KNN.

La elección entre la normalización L1 y L2 depende de los requisitos específicos de tu tarea de aprendizaje automático y la naturaleza de tus datos. Ambos métodos tienen sus fortalezas y son herramientas valiosas en el conjunto de herramientas del científico de datos para preparar características para análisis y entrenamiento de modelos.