J'essaie de calculer le facteur d'inflation de la variance (VIF) pour chaque colonne d'un jeu de données simple en python:
a b c d
1 2 4 4
1 2 6 3
2 3 7 4
3 2 8 5
4 1 9 4
Je l'ai déjà fait dans R en utilisant la fonction vif de la bibliothèque usdm qui donne les résultats suivants:
a <- c(1, 1, 2, 3, 4)
b <- c(2, 2, 3, 2, 1)
c <- c(4, 6, 7, 8, 9)
d <- c(4, 3, 4, 5, 4)
df <- data.frame(a, b, c, d)
vif_df <- vif(df)
print(vif_df)
Variables VIF
a 22.95
b 3.00
c 12.95
d 3.00
Cependant, lorsque je fais la même chose en python en utilisant la fonction statsmodel vif , mes résultats sont les suivants:
a = [1, 1, 2, 3, 4]
b = [2, 2, 3, 2, 1]
c = [4, 6, 7, 8, 9]
d = [4, 3, 4, 5, 4]
ck = np.column_stack([a, b, c, d])
vif = [variance_inflation_factor(ck, i) for i in range(ck.shape[1])]
print(vif)
Variables VIF
a 47.136986301369774
b 28.931506849315081
c 80.31506849315096
d 40.438356164383549
Les résultats sont très différents, même si les entrées sont les mêmes. En général, les résultats de la fonction VIF de statsmodel semblent erronés, mais je ne suis pas sûr que cela soit dû à la façon dont je l'appelle ou s'il s'agit d'un problème avec la fonction elle-même.
J'espérais que quelqu'un pourrait m'aider à déterminer si j'appelais de manière incorrecte la fonction statsmodel ou à expliquer les divergences dans les résultats. S'il y a un problème avec la fonction, existe-t-il des alternatives VIF en python?
Je crois que la raison en est due à une différence dans les MLS de Python. OLS, qui est utilisé dans le calcul du facteur d’inflation de la variance python, n’ajoute pas d’interception par défaut. Vous voulez vraiment une interception ici cependant.
Ce que vous voudriez faire est d’ajouter une colonne de plus à votre matrice, ck, remplie de celles représentant une constante. Ce sera le terme d'interception de l'équation. Une fois que cela est fait, vos valeurs doivent correspondre correctement.
Édité: zéros remplacés par des zéros
Comme mentionné par d'autres et dans ce message de Josef Perktold, l'auteur de la fonction, variance_inflation_factor
s'attend à la présence d'une constante dans la matrice de variables explicatives. On peut utiliser add_constant
de statsmodels pour ajouter la constante requise à la structure de données avant de transmettre ses valeurs à la fonction.
from statsmodels.stats.outliers_influence import variance_inflation_factor
from statsmodels.tools.tools import add_constant
df = pd.DataFrame(
{'a': [1, 1, 2, 3, 4],
'b': [2, 2, 3, 2, 1],
'c': [4, 6, 7, 8, 9],
'd': [4, 3, 4, 5, 4]}
)
X = add_constant(df)
>>> pd.Series([variance_inflation_factor(X.values, i)
for i in range(X.shape[1])],
index=X.columns)
const 136.875
a 22.950
b 3.000
c 12.950
d 3.000
dtype: float64
Je pense que vous pouvez également ajouter la constante à la colonne la plus à droite du cadre de données en utilisant assign
:
X = df.assign(const=1)
>>> pd.Series([variance_inflation_factor(X.values, i)
for i in range(X.shape[1])],
index=X.columns)
a 22.950
b 3.000
c 12.950
d 3.000
const 136.875
dtype: float64
Le code source lui-même est plutôt concis:
def variance_inflation_factor(exog, exog_idx):
"""
exog : ndarray, (nobs, k_vars)
design matrix with all explanatory variables, as for example used in
regression
exog_idx : int
index of the exogenous variable in the columns of exog
"""
k_vars = exog.shape[1]
x_i = exog[:, exog_idx]
mask = np.arange(k_vars) != exog_idx
x_noti = exog[:, mask]
r_squared_i = OLS(x_i, x_noti).fit().rsquared
vif = 1. / (1. - r_squared_i)
return vif
Il est également assez simple de modifier le code pour renvoyer tous les fichiers VIF sous forme de série:
from statsmodels.regression.linear_model import OLS
from statsmodels.tools.tools import add_constant
def variance_inflation_factors(exog_df):
'''
Parameters
----------
exog_df : dataframe, (nobs, k_vars)
design matrix with all explanatory variables, as for example used in
regression.
Returns
-------
vif : Series
variance inflation factors
'''
exog_df = add_constant(exog_df)
vifs = pd.Series(
[1 / (1. - OLS(exog_df[col].values,
exog_df.loc[:, exog_df.columns != col].values).fit().rsquared)
for col in exog_df],
index=exog_df.columns,
name='VIF'
)
return vifs
>>> variance_inflation_factors(df)
const 136.875
a 22.950
b 3.000
c 12.950
Name: VIF, dtype: float64
Pour les futurs venus sur ce sujet (comme moi):
import numpy as np
import scipy as sp
a = [1, 1, 2, 3, 4]
b = [2, 2, 3, 2, 1]
c = [4, 6, 7, 8, 9]
d = [4, 3, 4, 5, 4]
ck = np.column_stack([a, b, c, d])
cc = sp.corrcoef(ck, rowvar=False)
VIF = np.linalg.inv(cc)
VIF.diagonal()
Ce code donne
array([22.95, 3. , 12.95, 3. ])
[MODIFIER]
En réponse à un commentaire, j'ai essayé d'utiliser DataFrame
autant que possible (numpy
est requis pour inverser une matrice).
import pandas as pd
import numpy as np
a = [1, 1, 2, 3, 4]
b = [2, 2, 3, 2, 1]
c = [4, 6, 7, 8, 9]
d = [4, 3, 4, 5, 4]
df = pd.DataFrame({'a':a,'b':b,'c':c,'d':d})
df_cor = df.corr()
pd.DataFrame(np.linalg.inv(df.corr().values), index = df_cor.index, columns=df_cor.columns)
Le code donne
a b c d
a 22.950000 6.453681 -16.301917 -6.453681
b 6.453681 3.000000 -4.080441 -2.000000
c -16.301917 -4.080441 12.950000 4.080441
d -6.453681 -2.000000 4.080441 3.000000
Les éléments en diagonale donnent VIF.
Exemple pour Boston Data :
VIFest calculé par régression auxiliaire, il ne dépend donc pas de l'ajustement réel.
Voir ci-dessous:
from patsy import dmatrices
from statsmodels.stats.outliers_influence import variance_inflation_factor
import statsmodels.api as sm
# Break into left and right hand side; y and X
y, X = dmatrices(formula="medv ~ crim + zn + nox + ptratio + black + rm ", data=boston, return_type="dataframe")
# For each Xi, calculate VIF
vif = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])]
# Fit X to y
result = sm.OLS(y, X).fit()
J'ai écrit cette fonction à partir d'autres articles que j'ai vus sur Stack et CrossValidated. Il affiche les fonctionnalités dépassant le seuil et renvoie une nouvelle image de données avec les fonctionnalités supprimées.
from statsmodels.stats.outliers_influence import variance_inflation_factor
from statsmodels.tools.tools import add_constant
def calculate_vif_(df, thresh=5):
'''
Calculates VIF each feature in a pandas dataframe
A constant must be added to variance_inflation_factor or the results will be incorrect
:param X: the pandas dataframe
:param thresh: the max VIF value before the feature is removed from the dataframe
:return: dataframe with features removed
'''
const = add_constant(df)
cols = const.columns
variables = np.arange(const.shape[1])
vif_df = pd.Series([variance_inflation_factor(const.values, i)
for i in range(const.shape[1])],
index=const.columns).to_frame()
vif_df = vif_df.sort_values(by=0, ascending=False).rename(columns={0: 'VIF'})
vif_df = vif_df.drop('const')
vif_df = vif_df[vif_df['VIF'] > thresh]
print 'Features above VIF threshold:\n'
print vif_df[vif_df['VIF'] > thresh]
col_to_drop = list(vif_df.index)
for i in col_to_drop:
print 'Dropping: {}'.format(i)
df = df.drop(columns=i)
return df
Au cas où vous ne voudriez pas vous occuper de variance_inflation_factor
et add_constant
et que vous souhaitiez simplement utiliser une formule Veuillez considérer la fonction suivante.
import pandas as pd
import seaborn as sns
import numpy as np
import statsmodels.formula.api as smf
# define function
def get_vif(exogs, data):
'''Return VIF (variance inflation factor) DataFrame
Args:
exogs (list): list of exogenous/independent variables
data (DataFrame): the df storing all variables
Returns:
VIF and Tolerance DataFrame for each exogenous variable
Notes:
Assume we have a list of exogenous variable [X1, X2, X3, X4].
To calculate the VIF and Tolerance for each variable, we regress
each of them against other exogenous variables. For instance, the
regression model for X3 is defined as:
X3 ~ X1 + X2 + X4
And then we extract the R-squared from the model to calculate:
VIF = 1 / (1 - R-squared)
Tolerance = 1 - R-squared
The cutoff to detect multicollinearity:
VIF > 10 or Tolerance < 0.2
'''
# initialize arrays
vif_array = np.array([])
tolerance_array = np.array([])
# create formula for each exogenous variable
for exog in exogs:
not_exog = [i for i in exogs if i != exog]
formula = f"{exog} ~ {' + '.join(not_exog)}"
# extract r-squared from the fit
r_squared = smf.ols(formula, data=data).fit().rsquared
# calculate VIF
vif = 1/(1-r_squared)
vif_array = np.append(vif_array, vif).round(2)
# calculate tolerance
tolerance = 1-r_squared
tolerance_array = np.append(tolerance_array, tolerance)
# return VIF DataFrame
df_vif = pd.DataFrame({'VIF': vif_array, 'Tolerance': tolerance_array},
index=exogs)
return df_vif
[In]
df = sns.load_dataset('car_crashes')
exogs = ['alcohol', 'speeding', 'no_previous', 'not_distracted']
get_vif(exogs=exogs, data=df)
[Out]
VIF Tolerance
alcohol 3.44 0.291030
speeding 1.88 0.530690
no_previous 3.11 0.321132
not_distracted 2.67 0.374749