Selección de Variables atendiendo a la multicolinealidad

Como habrás visto y leído en numerosísimas ocasiones, muchas de las situaciones a las que nos enfrentamos como analistas de datos o ‘data scientists’ es la de predecir los valores que toma una variable objetivo en función un conjunto de variables predictoras.

En el argot habitual esto es, tener una variable dependiente (la variable objetivo) que, como su nombre indica, depende de una/s variable/s independiente/s (las variables predictoras)

Entonces, ¿qué ocurre si las variables «independientes» no son del todo independientes?

En ocasiones puede ocurrir que, en efecto, existe una dependencia entre las propias variables predictoras. A este hecho se le conoce como multicolinealidad, y su presencia no es que se diga muy satisfactoria cuando se trata de resolver problemas de predicción de variables.

Se dice que existe un grado de multicolinealidad si un conjunto de las características independientes puede ser calculado como combinación lineal del resto.

Y esto puede ser un problema y es que si se tiene un conjunto de datos que presenta multicolinealidad tenemos el riesgo de que en el proceso de entrenamiento del modelo de Aprendizaje Automático Supervisado se esté utilizando información «duplicada» y, debido a esta duplicidad en la información, los procesos de entrenamiento no pueden encontrar los parámetros adecuados para la correcta construcción de los modelos predictivos.

¿Y esto tiene solución?

Una solución a este problema es utilizar el Factor de Inflación de la Varianza (VIF, de sus siglas en inglés, Variance Inflation Factor), que permite cuantificar la intensidad de la multicolinealidad. La definición de este factor es:

    \[ VIF_{i} = \frac{1}{1-R_{i}^2} \]

donde VIF_{i} es el valor del factor para la característica i y R_{i} es coeficiente de determinación (es decir el famoso R de la regresión…) para la característica i.

El valor del VIF es siempre positivo y crece en un conjunto de datos a medida que aumenta la multicolinealidad entre las características. En caso de que en un conjunto de características exista multicolinealidad perfecta, es decir, que una característica se pueda explicar perfectamente mediante la combinación lineal del resto, el valor será infinito, ya que, en tal caso, el R_{i}^2 de la regresión será igual la unidad.

En general se suele emplear el criterio expuesto en la siguiente tabla:

Valor de VIFGrado de Muticolinealidad
Hasta 5Débil/Moderado
De 5 a 10Elevado
Mayor a 10Muy elevado

Por desgracia Python no tiene implementado entre sus librerías de análisis estadístico este método así que aprovecho la ocasión de este artículo para explicaros cómo hacerlo.

Implementación en Python del cálculo del Factor de Inflación de la Varianza.

Para ilustrar la implementación en Python de un método que nos ayude a calcular los Factores de Inflación de la Varianza permitiéndo una mejor selección de variables dentro del conjunto de variables predictoras usaré como ejemplo el conjunto de datos de las «calidades» del vino. Y dentro de éste, en concreto, las del vino blanco.

Este set de datos, empleado muy generalmente en ejercicios de regresión, clasificación, etcétera se encuentra dentro del Repositorio de Bases de Datos para Aprendizaje Automático de la Universidad Pública de Irvine en California.

En concreto el CSV de datos se puede encontrar en la siguiente URL:

https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-white.csv

Comenzamos.

Como siempre comenzaré por la carga del dataset y la comprobación de que su carga es correcta. En mi caso particular, si el dataset puede ser guardado en una copia local dentro de mi equipo, suelo preferir hacerlo de ese modo. Por eso verás que la carga del conjunto de datos se realiza desde una dirección local:

# Importamos librería pandas
import pandas as pd

df_vinos = pd.read_csv("./datasets/winequality-white.csv", sep =";")
df_vinos.head()

A continuación realizo una serie de acciones para dejar preparados mi conjunto de datos predictores y mi variable objetivo:

# Analizamos la shape del dataset original:

print(df_vinos.shape)


# Dividimos entre conjunto de variables predictoras y conjunto variables objetivo:

target_label = "quality"
pred_labels = df_vinos.columns.to_list()
pred_labels.remove(target_label)

# Comprobamos que las dimensiones cuadran con lo que debería ser. Es decir el total de var. predictoras debe ser 11.
print(len(pred_labels))

# Construimos el conjunto de datos predictor y el conjunto objetivo:

x_pred = df_vinos[pred_labels]
y_target = df_vinos[target_label]

Y ya damos paso al bloque que estabas esperando: la función para el cálculo del VIF de todas las variables contenidas en el conjunto de datos predictor:

# Debo importar LinearRegression para el calculo de las Ri
from sklearn.linear_model import LinearRegression


def calculateVIF(var_predictoras_df):
    var_pred_labels = list(var_predictoras_df.columns)
    num_var_pred = len(var_pred_labels)
    
    lr_model = LinearRegression()
    
    result = pd.DataFrame(index = ['VIF'], columns = var_pred_labels)
    result = result.fillna(0)
    
    for ite in range(num_var_pred):
        x_features = var_pred_labels[:]
        y_feature = var_pred_labels[ite]
        x_features.remove(y_feature)
        
        x = var_predictoras_df[x_features]
        y = var_predictoras_df[y_feature]
        
        lr_model.fit(var_predictoras_df[x_features], var_predictoras_df[y_feature])
        
        result[y_feature] = 1/(1 - lr_model.score(var_predictoras_df[x_features], var_predictoras_df[y_feature]))
    
    return result

Esta función ‘CalculateVIF’ deber recibir un DataFrame de pandas que contenga el conjunto de variables de predictoras ¡siendo todas ellas numéricas!. Efectivamente se tratará de analizar la colinealidad entre variables que son numéricas sino, el coeficiente de determinación no puede ser, valga la redundancia, determinado.

En nuestro caso pasaremos una copia ‘deep’ del conjunto de datos de las variables predictoras «x_pred»:

calculateVIF(x_pred.copy(deep = True)).T

Y el output que se obtiene es:

Valores VIF de cada una de las variables presentes en «x_pred»

De acuerdo a la tabla de criterios que mostraba más arriba, al revisar el listado, se observa que hay dos características con VIF muy elevado: residual sugar y density. También se aprecia una variable con VIF elevado: alcohol.

En el listado también se puede ver que no se ha incluido la característica dependiente, lo cual sería un error, ya que, en la construcción del modelo, lo que se busca es justamente una relación entre la característica dependiente y las independientes.

Una vez obtenido el VIF, hay que definir un proceso para la eliminación de características. El proceso se puede resumir en los siguientes pasos:

  1. Seleccionar un valor umbral del VIF para la eliminación: Suele emplearse el valor de 5 de acuerdo a lo mostrado en la Tabla de Criterios que te mostré anteriormente.
  2. Obtener el valor del VIF de todas las características/variables
  3. Identificar la característica con VIF más elevado.
  4. Si este valor supera el umbral definido, la característica se elimina del conjunto y se vuelve al punto 2, en caso contrario, el proceso termina.

¡¡IMPORTANTE!! En el punto 4 del proceso solamente se elimina una característica cada vez, esto es así porque puede que todas las características con VIF elevado sean colineales y al eliminar una del conjunto esta relación desaparecería. Eliminar más de una característica en cada paso puede llevar a despreciar características que no son redundantes.

A continuación te enseño cómo implementar este proceso de 4 pasos para la selección de variables con la función ‘SelectDataUsingVF’. Esta función, se apoya en la anteriormente creada ‘calculateVIF’.

def selectDataUsingVIF(var_predictoras_df, max_VIF = 5):
    result = var_predictoras_df.copy(deep = True)
    
    VIF = calculateVIF(result)
    
    while VIF.values.max() > max_VIF:
        col_max = np.where(VIF == VIF.values.max())[1][0]
        features = list(result.columns)
        features.remove(features[col_max])
        result = result[features]
        
        VIF = calculateVIF(result)
        
    return result

Esta función cuenta con dos parámetros de entrada:

  • El conjunto de datos de las variables predictoras (var_predictoras_df): en formato de DataFrame
  • El criterio de eliminación (max_VIF): que es opcional pasarlo ya que por defecto se estipula en 5 (de acuerdo a la Tabla que vimos)

El proceso copia los datos de entrada en un nuevo conjunto de datos y calcula el VIF. A partir de aquí, se entra en un bucle que termina cuando el valor máximo del VIF es inferior al criterio. En el bucle se elimina la columna de dataframe con el valor de VIF más elevado en cada momento y se vuelve a calcular el VIF de todas las variables.

Ejecutando la función:

calculateVIF(selectDataUsingVIF(x_pred)).T

Se obtiene:

Fijándonos en esta lista observamos que efectivamente ningún valor de VIF es mayor a 5 con lo que aceptamos que todas las variables ahí representadas no presentan multicolinealidad.

Para saber que variable/s hemos quitado podemos comparar visualmente esta tabla con la inmediatamente anterior o emplear las poderosas «list comprehensions» de Python:

VIF_orig = calculateVIF(x_pred.copy(deep = True)).columns.to_list()
VIF_less_th_5 = calculateVIF(selectDataUsingVIF(x_pred)).columns.to_list()

[feat for feat in VIF_orig if feat not in VIF_less_th_5]

Y ejecutando ese código vemos que solamente ha sido necesario eliminar una característica, density, para que el valor máximo del VIF pase a ser 2,15. Esto es así porque todas las características que tenían VIF elevado estaban relacionadas con la densidad y, al desaparecer esta, pues los valores de los nuevos VIF se ven reducidos.

Con esta implementación hemos conseguido un conjunto de datos predictores que no adolece de multicolinealidad y se comportará, al menos en ese sentido, de un modo óptimo cuando lo usemos para entrena nuestro modelo de aprendizaje automático.

De nuevo, muchas gracias por leerme y mucha suerte en tu camino hacia convertirte en Data Scientist!

¿Crees que puede serle útil a alguien más? ¡Comparte! :)
0 0 votos
Valora este artículo!
Suscribirse a los comentarios de este artículo
Notificarme de
guest
0 Comments
Inline Feedbacks
Ver todos los comentarios