Я пытаюсь рассчитать фактор инфляции дисперсии (VIF) для каждого столбца в простом наборе данных на Python.
a b c d12441263237432854194
Я уже сделал это в R с помощью функции vif из [библиотеки usdm](https://cran.r-project.org/web/packages/usdm/usdm.pdf), что дало следующие результаты:
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
Однако, когда я делаю то же самое в Python, используя функцию VIF в statsmodel, мои результаты следующие:
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 inrange(ck.shape[1])]print(vif)Variables VIF a 47.136986301369774 b 28.931506849315081 c 80.31506849315096 d 40.438356164383549
Полученные результаты существенно различаются, хотя входные данные одинаковы. Как правило, результаты, полученные с использованием функции VIF из пакета statsmodel, кажутся неверными, но я не уверен, связано ли это с тем, как я вызываю эту функцию, или это проблема самой функции.
Я надеялся, что кто-нибудь сможет помочь мне понять, неправильно ли я вызывал функцию statsmodel или объяснить расхождения в результатах. Если это проблема с функцией, то есть ли альтернативы VIF на Python?
Как упоминалось другими и в этом посте от автора функции Йозефа Перктольда, функция variance_inflation_factor требует наличия константы в матрице объясняющих переменных. Можно использовать add_constant из statsmodels, чтобы добавить необходимую константу к датафрейму перед передачей его значений в функцию.
Я верю, вы также можете добавить константу в крайнюю правую колонку dataframe, используя
X = df.assign(const=1)>>> pd.Series([variance_inflation_factor(X.values, i) for i inrange(X.shape[1])], index=X.columns)a 22.950b 3.000c 12.950d 3.000const 136.875dtype: float64
Исходный код довольно лаконичный:
defvariance_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
Также довольно просто изменить код, чтобы вернуть все VIF в виде серии:
from statsmodels.regression.linear_model import OLSfrom statsmodels.tools.tools import add_constantdefvariance_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.875a 22.950b 3.000c 12.950Name: VIF, dtype: float64
Согласно решению @T_T, можно также просто сделать следующее:
Я считаю, что причина этого заключается в отличии OLS в Python. OLS, который используется при расчете фактора инфляции дисперсии в Python, по умолчанию не добавляет константу. Однако добавление константы действительно желательно.
Чтобы добавить еще один столбец к вашей матрице, ck, заполненный единицами для представления константы, это будет константа уравнения пересечения. После этого ваши значения должны правильно совпасть.
In response to a comment, I tried to use DataFrame as much as possible (numpy is required to invert a matrix).
import pandas as pdimport numpy as npa = [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)
The code gives
a b c da 22.9500006.453681-16.301917-6.453681b 6.4536813.000000-4.080441-2.000000c -16.301917-4.08044112.9500004.080441d -6.453681-2.0000004.0804413.000000
_TT_T
1,23817 silver badges23 bronze badges
3
In case you don't wanna deal with variance_inflation_factor and add_constant. Please consider the following two functions.
1. Use formula in statasmodels:
import pandas as pdimport statsmodels.formula.api as smfdefget_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.1 '''# initialize dictionaries vif_dict, tolerance_dict ={},{}# create formula for each exogenous variablefor 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_dict[exog]= vif# calculate tolerance tolerance =1- r_squared tolerance_dict[exog]= tolerance# return VIF DataFrame df_vif = pd.DataFrame({'VIF': vif_dict, 'Tolerance': tolerance_dict})return df_vif
2. Use LinearRegression in sklearn:
# import warnings# warnings.simplefilter(action='ignore', category=FutureWarning)import pandas as pdfrom sklearn.linear_model import LinearRegressiondefsklearn_vif(exogs,data):# initialize dictionaries vif_dict, tolerance_dict ={},{}# form input data for each exogenous variablefor exog in exogs: not_exog = [i for i in exogs if i != exog] X, y = data[not_exog], data[exog]# extract r-squared from the fit r_squared =LinearRegression().fit(X, y).score(X, y)# calculate VIF vif =1/(1- r_squared) vif_dict[exog]= vif# calculate tolerance tolerance =1- r_squared tolerance_dict[exog]= tolerance# return VIF DataFrame df_vif = pd.DataFrame({'VIF': vif_dict, 'Tolerance': tolerance_dict})return df_vif
Example:
import seaborn as snsdf = sns.load_dataset('car_crashes')exogs = ['alcohol','speeding','no_previous','not_distracted'][In] %%timeit -n 100get_vif(exogs=exogs, data=df)[Out] VIF Tolerancealcohol 3.4360720.291030no_previous 3.1139840.321132not_distracted 2.6684560.374749speeding 1.8843400.53069069.6 ms ± 8.96 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)[In] %%timeit -n 100sklearn_vif(exogs=exogs, data=df)[Out] VIF Tolerancealcohol 3.4360720.291030no_previous 3.1139840.321132not_distracted 2.6684560.374749speeding 1.8843400.53069015.7 ms ± 1.4 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
answered Feb 24, 2019 at 23:06
stevensteven
2,32921 silver badges40 bronze badges
3
Хотя уже поздно, я вношу некоторые изменения в данном ответе. Чтобы получить лучший набор после удаления мультиколлинеарности, если мы используем решение @Chef1075, тогда мы потеряем переменные, которые коррелируют. Нам нужно удалить только одну из них. Для этого я пришел к следующему решению, используя ответ @steve:
import pandas as pdfrom sklearn.linear_model import LinearRegressiondefsklearn_vif(exogs,data):''' This function calculates variance inflation function in sklearn way. It is a comparatively faster process. '''# initialize dictionaries vif_dict, tolerance_dict ={},{}# form input data for each exogenous variablefor exog in exogs: not_exog = [i for i in exogs if i != exog] X, y = data[not_exog], data[exog]# extract r-squared from the fit r_squared =LinearRegression().fit(X, y).score(X, y)# calculate VIF vif =1/(1- r_squared) vif_dict[exog]= vif# calculate tolerance tolerance =1- r_squared tolerance_dict[exog]= tolerance# return VIF DataFrame df_vif = pd.DataFrame({'VIF': vif_dict, 'Tolerance': tolerance_dict})return df_vifdf = pd.DataFrame({'a': [1, 1, 2, 3, 4,1],'b': [2, 2, 3, 2, 1,3],'c': [4, 6, 7, 8, 9,5],'d': [4, 3, 4, 5, 4,6],'e': [8,8,14,15,17,20]} )df_vif=sklearn_vif(exogs=df.columns, data=df).sort_values(by='VIF',ascending=False)while (df_vif.VIF>5).any()==True: red_df_vif= df_vif.drop(df_vif.index[0]) df= df[red_df_vif.index] df_vif=sklearn_vif(exogs=df.columns,data=df).sort_values(by='VIF',ascending=False)print(df) d c b044213622473358244915653
answered Apr 26, 2020 at 13:36
asteroidasteroid
591 silver badge9 bronze badges
4
Example for Boston Data:
VIF (Коэффициент инфляции дисперсии) вычисляется с помощью вспомогательной регрессии, поэтому не зависит от фактического соответствия модели.
See below:
from patsy import dmatricesfrom statsmodels.stats.outliers_influence import variance_inflation_factorimport statsmodels.api as sm# Break into left and right hand side; y and Xy, X =dmatrices(formula="medv ~ crim + zn + nox + ptratio + black + rm ", data=boston, return_type="dataframe")# For each Xi, calculate VIFvif = [variance_inflation_factor(X.values, i)for i inrange(X.shape[1])]# Fit X to yresult = sm.OLS(y, X).fit()
answered Aug 18, 2017 at 6:22
s_mjs_mj
54011 silver badges28 bronze badges
Я написал эту функцию, основываясь на некоторых других публикациях, которые видел на Stack Overflow и CrossValidated. Она отображает признаки, которые превышают порог, и возвращает новый DataFrame без этих признаков.
from statsmodels.stats.outliers_influence import variance_inflation_factor from statsmodels.tools.tools import add_constantdefcalculate_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 df: the pandas dataframe containing only the predictor features, not the response variable :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 inrange(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
import pandas as pddata = pd.DataFrame()data["a"] = adata["b"] = bdata["c"] = cdata["d"] = d
Calculate VIF
cc = np.corrcoef(data, rowvar=False)VIF = np.linalg.inv(cc)VIF.diagonal()
Result
array([22.95, 3. , 12.95, 3. ])
answered Jan 24, 2020 at 15:23
Еще одно решение. Следующий код дает точно такие же результаты VIF, как и пакет R car.
defcalc_reg_return_vif(X,y):""" Utility function to calculate the VIF. This section calculates the linear regression inverse R squared. Parameters ---------- X : DataFrame Input data. y : Series Target. Returns ------- vif : float Calculated VIF value. """ X = X.values y = y.valuesif X.shape[1]==1:print("Note, there is only one predictor here") X = X.reshape(-1, 1) reg =LinearRegression().fit(X, y) vif =1/ (1- reg.score(X, y))return vifdefcalc_vif_from_scratch(df):""" Calculating VIF using function from scratch Parameters ---------- df : DataFrame without target variable. Returns ------- vif : DataFrame giving the feature - VIF value pair. """ vif = pd.DataFrame() vif_list = []for feature inlist(df.columns): y = df[feature] X = df.drop(feature, axis="columns") vif_list.append(calc_reg_return_vif(X, y)) vif["feature"]= df.columns vif["VIF"]= vif_listreturn vif