Линейная регрессия


In [1]:
import statsmodels
import scipy as sc
import numpy as np
import pandas as pd
import statsmodels.formula.api as smf
import statsmodels.stats.api as sms
from statsmodels.graphics.regressionplots import plot_leverage_resid2
import matplotlib.pyplot as plt

In [2]:
%pylab inline


Populating the interactive namespace from numpy and matplotlib

Постановка

По 1260 опрошенным имеются следующие данные:

  • заработная плата за час работы, $;
  • опыт работы, лет;
  • образование, лет;
  • внешняя привлекательность, в баллах от 1 до 5;
  • бинарные признаки: пол, семейное положение, состояние здоровья (хорошее/плохое), членство в профсоюзе, цвет кожи (белый/чёрный), занятость в сфере обслуживания (да/нет).

Требуется оценить влияние внешней привлекательности на уровень заработка с учётом всех остальных факторов.

Hamermesh D.S., Biddle J.E. (1994) Beauty and the Labor Market, American Economic Review, 84, 1174–1194.

Данные:


In [3]:
raw = pd.read_csv("beauty.csv", sep=";", index_col=False) 
raw.head()


Out[3]:
wage exper union goodhlth black female married service educ looks
0 5.73 30 0 1 0 1 1 1 14 4
1 4.28 28 0 1 0 1 1 0 12 3
2 7.96 35 0 1 0 1 0 0 10 4
3 11.57 38 0 1 0 0 1 1 16 3
4 11.42 27 0 1 0 0 1 0 16 3

Посмотрим на матрицу диаграмм рассеяния по количественным признакам:


In [4]:
pd.tools.plotting.scatter_matrix(raw[['wage', 'exper', 'educ', 'looks']], alpha=0.2, 
                                 figsize=(15, 15), diagonal='hist')
pylab.show()


Оценим сбалансированность выборки по категориальным признакам:


In [5]:
print raw.union.value_counts()
print raw.goodhlth.value_counts()
print raw.black.value_counts()
print raw.female.value_counts()
print raw.married.value_counts()
print raw.service.value_counts()


0    917
1    343
Name: union, dtype: int64
1    1176
0      84
Name: goodhlth, dtype: int64
0    1167
1      93
Name: black, dtype: int64
0    824
1    436
Name: female, dtype: int64
1    871
0    389
Name: married, dtype: int64
0    915
1    345
Name: service, dtype: int64

У каждого признака все значения встречаются достаточно много раз, так что всё в порядке.

Предобработка


In [6]:
data = raw

Посмотрим на распределение целевого признака — уровня заработной платы:


In [7]:
plt.figure(figsize(16,7))
plt.subplot(121)
data['wage'].plot.hist()
plt.xlabel('Wage', fontsize=14)

plt.subplot(122)
np.log(data['wage']).plot.hist()
plt.xlabel('Log wage', fontsize=14)
pylab.show()


Один человек в выборке получает 77.72\$ в час, остальные — меньше 45\$; удалим этого человека, чтобы регрессия на него не перенастроилась.


In [8]:
data = data[data['wage'] < 77]

Посмотрим на распределение оценок привлекательности:


In [9]:
plt.figure(figsize(8,7))
data.groupby('looks')['looks'].agg(lambda x: len(x)).plot(kind='bar', width=0.9)
plt.xticks(rotation=0)
plt.xlabel('Looks', fontsize=14)
pylab.show()


В группах looks=1 и looks=5 слишком мало наблюдений. Превратим признак looks в категориальный и закодируем с помощью фиктивных переменных:


In [10]:
data['belowavg'] = data['looks'].apply(lambda x : 1 if x < 3 else 0)
data['aboveavg'] = data['looks'].apply(lambda x : 1 if x > 3 else 0)
data.drop('looks', axis=1, inplace=True)


/home/astar/anaconda2/lib/python2.7/site-packages/ipykernel/__main__.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  if __name__ == '__main__':
/home/astar/anaconda2/lib/python2.7/site-packages/ipykernel/__main__.py:2: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  from ipykernel import kernelapp as app
/home/astar/anaconda2/lib/python2.7/site-packages/ipykernel/__main__.py:3: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  app.launch_new_instance()

Данные теперь:


In [11]:
data.head()


Out[11]:
wage exper union goodhlth black female married service educ belowavg aboveavg
0 5.73 30 0 1 0 1 1 1 14 0 1
1 4.28 28 0 1 0 1 1 0 12 0 0
2 7.96 35 0 1 0 1 0 0 10 0 1
3 11.57 38 0 1 0 0 1 1 16 0 0
4 11.42 27 0 1 0 0 1 0 16 0 0

Построение модели

Простейшая модель

Построим линейную модель по всем признакам.


In [12]:
m1 = smf.ols('wage ~ exper + union + goodhlth + black + female + married +'\
                    'service + educ + belowavg + aboveavg', 
             data=data)
fitted = m1.fit()
print fitted.summary()


                            OLS Regression Results                            
==============================================================================
Dep. Variable:                   wage   R-squared:                       0.262
Model:                            OLS   Adj. R-squared:                  0.256
Method:                 Least Squares   F-statistic:                     44.31
Date:                Wed, 13 Jul 2016   Prob (F-statistic):           1.42e-75
Time:                        22:56:45   Log-Likelihood:                -3402.9
No. Observations:                1259   AIC:                             6828.
Df Residuals:                    1248   BIC:                             6884.
Df Model:                          10                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [95.0% Conf. Int.]
------------------------------------------------------------------------------
Intercept     -0.5898      0.743     -0.793      0.428        -2.048     0.869
exper          0.0850      0.009      9.118      0.000         0.067     0.103
union          0.4786      0.234      2.048      0.041         0.020     0.937
goodhlth      -0.0444      0.417     -0.107      0.915        -0.862     0.773
black         -0.6748      0.403     -1.674      0.094        -1.466     0.116
female        -2.3058      0.242     -9.522      0.000        -2.781    -1.831
married        0.4569      0.240      1.905      0.057        -0.014     0.927
service       -0.7303      0.252     -2.896      0.004        -1.225    -0.236
educ           0.4820      0.043     11.272      0.000         0.398     0.566
belowavg      -0.8185      0.323     -2.532      0.011        -1.453    -0.184
aboveavg      -0.0729      0.234     -0.311      0.756        -0.532     0.387
==============================================================================
Omnibus:                      898.031   Durbin-Watson:                   1.858
Prob(Omnibus):                  0.000   Jarque-Bera (JB):            17969.693
Skew:                           3.076   Prob(JB):                         0.00
Kurtosis:                      20.456   Cond. No.                         189.
==============================================================================

Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Посмотрим на распределение остатков:


In [13]:
plt.figure(figsize(16,7))
plt.subplot(121)
sc.stats.probplot(fitted.resid, dist="norm", plot=pylab)
plt.subplot(122)
np.log(fitted.resid).plot.hist()
plt.xlabel('Residuals', fontsize=14)
pylab.show()


Оно скошенное, как и исходный признак. В таких ситуациях часто помогает перейти от регрессии исходного признака к регрессии его логарифма.

Логарифмируем отклик


In [14]:
m2 = smf.ols('np.log(wage) ~ exper + union + goodhlth + black + female + married +'\
                            'service + educ + belowavg + aboveavg', data=data)
fitted = m2.fit()
print fitted.summary()

plt.figure(figsize(16,7))
plt.subplot(121)
sc.stats.probplot(fitted.resid, dist="norm", plot=pylab)
plt.subplot(122)
np.log(fitted.resid).plot.hist()
plt.xlabel('Residuals', fontsize=14)
pylab.show()


                            OLS Regression Results                            
==============================================================================
Dep. Variable:           np.log(wage)   R-squared:                       0.383
Model:                            OLS   Adj. R-squared:                  0.379
Method:                 Least Squares   F-statistic:                     77.63
Date:                Sun, 29 May 2016   Prob (F-statistic):          1.18e-123
Time:                        14:22:10   Log-Likelihood:                -816.90
No. Observations:                1259   AIC:                             1656.
Df Residuals:                    1248   BIC:                             1712.
Df Model:                          10                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [95.0% Conf. Int.]
------------------------------------------------------------------------------
Intercept      0.4515      0.095      4.737      0.000         0.265     0.639
exper          0.0138      0.001     11.546      0.000         0.011     0.016
union          0.1785      0.030      5.957      0.000         0.120     0.237
goodhlth       0.0785      0.053      1.470      0.142        -0.026     0.183
black         -0.0989      0.052     -1.913      0.056        -0.200     0.003
female        -0.3938      0.031    -12.684      0.000        -0.455    -0.333
married        0.0425      0.031      1.383      0.167        -0.018     0.103
service       -0.1505      0.032     -4.656      0.000        -0.214    -0.087
educ           0.0799      0.005     14.581      0.000         0.069     0.091
belowavg      -0.1305      0.041     -3.148      0.002        -0.212    -0.049
aboveavg      -0.0041      0.030     -0.138      0.890        -0.063     0.055
==============================================================================
Omnibus:                       27.318   Durbin-Watson:                   1.853
Prob(Omnibus):                  0.000   Jarque-Bera (JB):               46.550
Skew:                           0.159   Prob(JB):                     7.80e-11
Kurtosis:                       3.887   Cond. No.                         189.
==============================================================================

Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Теперь стало лучше. Посмотрим теперь на зависимость остатков от непрерывных признаков:


In [15]:
plt.figure(figsize(16,7))
plt.subplot(121)
scatter(data['educ'],fitted.resid)
plt.xlabel('Education', fontsize=14)
plt.ylabel('Residuals', fontsize=14)
plt.subplot(122)
scatter(data['exper'],fitted.resid)
plt.xlabel('Experience', fontsize=14)
plt.ylabel('Residuals', fontsize=14)
pylab.show()


На втором графике видна квадратичная зависимость остатков от опыта работы. Попробуем добавить к признакам квадрат опыта работы, чтобы учесть этот эффект.

Добавляем квадрат опыта работы


In [16]:
m3 = smf.ols('np.log(wage) ~ exper + np.power(exper,2) + union + goodhlth + black + female +'\
                            'married + service + educ + belowavg + aboveavg', data=data)
fitted = m3.fit()
print fitted.summary()

plt.figure(figsize(16,7))
plt.subplot(121)
sc.stats.probplot(fitted.resid, dist="norm", plot=pylab)
plt.subplot(122)
np.log(fitted.resid).plot.hist()
plt.xlabel('Residuals', fontsize=14)
plt.figure(figsize(16,5))
plt.subplot(131)
scatter(data['educ'],fitted.resid)
plt.xlabel('Education', fontsize=14)
plt.ylabel('Residuals', fontsize=14)
plt.subplot(132)
scatter(data['exper'],fitted.resid)
plt.xlabel('Experience', fontsize=14)
plt.ylabel('Residuals', fontsize=14)
plt.subplot(133)
scatter(data['exper']**2,fitted.resid)
plt.xlabel('Experience^2', fontsize=14)
plt.ylabel('Residuals', fontsize=14)
pylab.show()


                            OLS Regression Results                            
==============================================================================
Dep. Variable:           np.log(wage)   R-squared:                       0.403
Model:                            OLS   Adj. R-squared:                  0.398
Method:                 Least Squares   F-statistic:                     76.46
Date:                Sun, 29 May 2016   Prob (F-statistic):          3.19e-131
Time:                        14:22:11   Log-Likelihood:                -796.86
No. Observations:                1259   AIC:                             1618.
Df Residuals:                    1247   BIC:                             1679.
Df Model:                          11                                         
Covariance Type:            nonrobust                                         
======================================================================================
                         coef    std err          t      P>|t|      [95.0% Conf. Int.]
--------------------------------------------------------------------------------------
Intercept              0.3424      0.095      3.588      0.000         0.155     0.530
exper                  0.0404      0.004      9.290      0.000         0.032     0.049
np.power(exper, 2)    -0.0006   9.63e-05     -6.351      0.000        -0.001    -0.000
union                  0.1710      0.030      5.793      0.000         0.113     0.229
goodhlth               0.0716      0.053      1.361      0.174        -0.032     0.175
black                 -0.0831      0.051     -1.631      0.103        -0.183     0.017
female                -0.3936      0.031    -12.875      0.000        -0.454    -0.334
married                0.0101      0.031      0.329      0.742        -0.050     0.070
service               -0.1599      0.032     -5.018      0.000        -0.222    -0.097
educ                   0.0758      0.005     13.941      0.000         0.065     0.086
belowavg              -0.1352      0.041     -3.313      0.001        -0.215    -0.055
aboveavg              -0.0025      0.030     -0.084      0.933        -0.061     0.056
==============================================================================
Omnibus:                       30.019   Durbin-Watson:                   1.849
Prob(Omnibus):                  0.000   Jarque-Bera (JB):               56.257
Skew:                           0.140   Prob(JB):                     6.08e-13
Kurtosis:                       3.997   Cond. No.                     5.62e+03
==============================================================================

Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 5.62e+03. This might indicate that there are
strong multicollinearity or other numerical problems.

Используем критерий Бройша-Пагана для проверки гомоскедастичности ошибок:


In [17]:
print 'Breusch-Pagan test: p=%f' % sms.het_breushpagan(fitted.resid, fitted.model.exog)[1]


Breusch-Pagan test: p=0.000004

Ошибки гетероскедастичны, значит, значимость признаков может определяться неверно. Сделаем поправку Уайта:


In [18]:
m4 = smf.ols('np.log(wage) ~ exper + np.power(exper,2) + union + goodhlth + black + female +'\
                            'married + service + educ + belowavg + aboveavg', data=data)
fitted = m4.fit(cov_type='HC1')
print fitted.summary()

plt.figure(figsize(16,7))
plt.subplot(121)
sc.stats.probplot(fitted.resid, dist="norm", plot=pylab)
plt.subplot(122)
np.log(fitted.resid).plot.hist()
plt.xlabel('Residuals', fontsize=14)
pylab.show()


                            OLS Regression Results                            
==============================================================================
Dep. Variable:           np.log(wage)   R-squared:                       0.403
Model:                            OLS   Adj. R-squared:                  0.398
Method:                 Least Squares   F-statistic:                     87.29
Date:                Sun, 29 May 2016   Prob (F-statistic):          4.23e-146
Time:                        14:22:12   Log-Likelihood:                -796.86
No. Observations:                1259   AIC:                             1618.
Df Residuals:                    1247   BIC:                             1679.
Df Model:                          11                                         
Covariance Type:                  HC1                                         
======================================================================================
                         coef    std err          z      P>|z|      [95.0% Conf. Int.]
--------------------------------------------------------------------------------------
Intercept              0.3424      0.104      3.282      0.001         0.138     0.547
exper                  0.0404      0.004      9.511      0.000         0.032     0.049
np.power(exper, 2)    -0.0006   9.46e-05     -6.469      0.000        -0.001    -0.000
union                  0.1710      0.026      6.463      0.000         0.119     0.223
goodhlth               0.0716      0.064      1.123      0.262        -0.053     0.197
black                 -0.0831      0.052     -1.599      0.110        -0.185     0.019
female                -0.3936      0.031    -12.702      0.000        -0.454    -0.333
married                0.0101      0.030      0.340      0.734        -0.048     0.068
service               -0.1599      0.033     -4.786      0.000        -0.225    -0.094
educ                   0.0758      0.006     13.387      0.000         0.065     0.087
belowavg              -0.1352      0.040     -3.384      0.001        -0.214    -0.057
aboveavg              -0.0025      0.030     -0.083      0.934        -0.061     0.056
==============================================================================
Omnibus:                       30.019   Durbin-Watson:                   1.849
Prob(Omnibus):                  0.000   Jarque-Bera (JB):               56.257
Skew:                           0.140   Prob(JB):                     6.08e-13
Kurtosis:                       3.997   Cond. No.                     5.62e+03
==============================================================================

Warnings:
[1] Standard Errors are heteroscedasticity robust (HC1)
[2] The condition number is large, 5.62e+03. This might indicate that there are
strong multicollinearity or other numerical problems.

Удаляем незначимые признаки

В предыдущей модели незначимы: цвет кожи, здоровье, семейное положение. Удалим их. Индикатор привлекательности выше среднего тоже незначим, но удалять его не будем, потому что это одна из переменных, по которым на нужно в конце ответить на вопрос.


In [19]:
m5 = smf.ols('np.log(wage) ~ exper + np.power(exper,2) + union + female + service + educ +'\
                            'belowavg + aboveavg', data=data)
fitted = m5.fit(cov_type='HC1')
print fitted.summary()

plt.figure(figsize(16,7))
plt.subplot(121)
sc.stats.probplot(fitted.resid, dist="norm", plot=pylab)
plt.subplot(122)
np.log(fitted.resid).plot.hist()
plt.xlabel('Residuals', fontsize=14)
pylab.show()


                            OLS Regression Results                            
==============================================================================
Dep. Variable:           np.log(wage)   R-squared:                       0.400
Model:                            OLS   Adj. R-squared:                  0.397
Method:                 Least Squares   F-statistic:                     121.1
Date:                Sun, 29 May 2016   Prob (F-statistic):          6.49e-150
Time:                        14:22:12   Log-Likelihood:                -799.30
No. Observations:                1259   AIC:                             1617.
Df Residuals:                    1250   BIC:                             1663.
Df Model:                           8                                         
Covariance Type:                  HC1                                         
======================================================================================
                         coef    std err          z      P>|z|      [95.0% Conf. Int.]
--------------------------------------------------------------------------------------
Intercept              0.3906      0.084      4.674      0.000         0.227     0.554
exper                  0.0410      0.004      9.781      0.000         0.033     0.049
np.power(exper, 2)    -0.0006   9.35e-05     -6.748      0.000        -0.001    -0.000
union                  0.1695      0.026      6.414      0.000         0.118     0.221
female                -0.4043      0.030    -13.560      0.000        -0.463    -0.346
service               -0.1600      0.033     -4.785      0.000        -0.225    -0.094
educ                   0.0773      0.006     13.549      0.000         0.066     0.089
belowavg              -0.1307      0.040     -3.279      0.001        -0.209    -0.053
aboveavg              -0.0010      0.030     -0.035      0.972        -0.059     0.057
==============================================================================
Omnibus:                       26.927   Durbin-Watson:                   1.842
Prob(Omnibus):                  0.000   Jarque-Bera (JB):               49.409
Skew:                           0.120   Prob(JB):                     1.87e-11
Kurtosis:                       3.941   Cond. No.                     4.49e+03
==============================================================================

Warnings:
[1] Standard Errors are heteroscedasticity robust (HC1)
[2] The condition number is large, 4.49e+03. This might indicate that there are
strong multicollinearity or other numerical problems.

Посмотрим, не стала ли модель от удаления трёх признаков значимо хуже, с помощью критерия Фишера:


In [20]:
print "F=%f, p=%f, k1=%f" % m4.fit().compare_f_test(m5.fit())


F=1.611478, p=0.184911, k1=3.000000

Не стала.

Проверим, нет ли наблюдений, которые слишком сильно влияют на регрессионное уравнение:


In [21]:
plt.figure(figsize(8,7))
plot_leverage_resid2(fitted)
pylab.show()


<matplotlib.figure.Figure at 0xbbb0c18>

In [22]:
data.loc[[1122]]


Out[22]:
wage exper union goodhlth black female married service educ belowavg aboveavg
1122 6.25 47 0 0 1 1 1 0 5 0 1

In [23]:
data.loc[[269]]


Out[23]:
wage exper union goodhlth black female married service educ belowavg aboveavg
269 41.67 16 0 0 0 0 1 0 13 0 1

Выводы

Итоговая модель объясняет 40% вариации логарифма отклика.


In [24]:
plt.figure(figsize(16,7))
plt.subplot(121)
scatter(data['wage'],np.exp(fitted.fittedvalues))
plt.xlabel('Wage', fontsize=14)
plt.ylabel('Exponentiated predictions', fontsize=14)
plt.xlim([0,50])

plt.subplot(122)
scatter(np.log(data['wage']),fitted.fittedvalues)
plt.xlabel('Log wage', fontsize=14)
plt.ylabel('Predictions', fontsize=14)
plt.xlim([0,4])
pylab.show()


При интересующих нас факторах привлекательности стоят коэффициенты -0.1307 (ниже среднего) и -0.0010 (выше среднего).

Поскольку регрессия делалась на логарифм отклика, интерпретировать их можно как прирост в процентах. С учётом дополнительных факторов представители генеральной совокупности, из которой взята выборка, получают в среднем:

  • на 13% меньше, если их привлекательность ниже среднего (p=0.001, 95% доверительный интервал — [5,21]%);
  • столько же, если их привлекательность выше среднего (p=0.972, 95% доверительный интервал — [-6,6]%).