Unidad II. Regresiones y reducción de dimensionalidad.

Regresión Lineal Múltiple por el método de los Mínimos Cuadrados.

  • Examen de los residuos. Prueba de normalidad de los residuales.
  • Prueba de homogeneidad de varianza.
  • Mínimos Cuadrados Ponderados.
  • Observaciones extremas.
  • Búsqueda de la mejor ecuación de regresión. Stepwise regression

Regresión Lineal Múltiple


In [1]:
k = 2  # variables independientes
N = 10 # observaciones


Out[1]:
10

In [2]:
X = rand(N, k) # simulación: valores observados para las variables independientes


Out[2]:
10×2 Array{Float64,2}:
 0.0307714  0.4821  
 0.286979   0.558296
 0.110116   0.287557
 0.85274    0.191639
 0.895312   0.764406
 0.361209   0.230311
 0.460176   0.601009
 0.8968     0.892419
 0.293208   0.97748 
 0.653764   0.689387

In [3]:
α = rand() # simulación: valor real de la intersección


Out[3]:
0.16119632339800627

In [4]:
β = rand(k) # simulación: valores reales de los coeficientes


Out[4]:
2-element Array{Float64,1}:
 0.18714
 0.85874

In [5]:
E = 0.1 .* rand(N) # simulación: agrega errores al azar al modelo


Out[5]:
10-element Array{Float64,1}:
 0.0719068 
 0.0843114 
 0.0208699 
 0.056578  
 0.0364262 
 0.0962185 
 0.00391276
 0.087665  
 0.0453114 
 0.0335044 

In [6]:
Y = α + X * β + E # valores para la variable dependiente con el modelo lineal que acabamos de armar


Out[6]:
10-element Array{Float64,1}:
 0.65286 
 0.778644
 0.44961 
 0.541924
 1.0216  
 0.522789
 0.767337
 1.18304 
 1.10078 
 0.90905 

La biblioteca MultivariateStats de Julia implementa la regresión lineal por cuadrados mínimos en su función llsq. Este método toma la matriz de valores de la variable dependiente y el vector de la independiente. Retorna un vector de la forma $[\beta_{1}, …, \beta_{k}, \alpha]$.


In [7]:
using MultivariateStats

predicción = llsq(X, Y)


Out[7]:
3-element Array{Float64,1}:
 0.19005 
 0.845895
 0.220747

In [8]:
αₚ = predicción[end] # valor de la intersección del ajuste


Out[8]:
0.2207471881027547

In [9]:
βₚ = predicción[1:end-1] # valores de los coeficientes del ajuste


Out[9]:
2-element Array{Float64,1}:
 0.19005 
 0.845895

In [10]:
Yₚ = αₚ + X*βₚ # valores predichos para la variable dependiente


Out[10]:
10-element Array{Float64,1}:
 0.634401
 0.747547
 0.484918
 0.544916
 1.03751 
 0.484214
 0.816594
 1.14608 
 1.10332 
 0.928144

Diferencias entre los valores reales y los ajustados


In [11]:
α - αₚ


Out[11]:
-0.059550864704748435

In [12]:
β - βₚ


Out[12]:
2-element Array{Float64,1}:
 -0.00290978
  0.0128451 

In [13]:
Eₚ = Y - Yₚ # son los valores residuales de la regresión


Out[13]:
10-element Array{Float64,1}:
  0.018459  
  0.0310968 
 -0.0353076 
 -0.00299254
 -0.015911  
  0.0385749 
 -0.0492571 
  0.0369678 
 -0.00253678
 -0.0190936 

In [14]:
using Plots
pyplot(size=(500,250))

plot(
scatter(Y, Yₚ, legend=false, 
        xlab="Valores", ylab="Predicciones"),
histogram(Eₚ, label="Residuales", bins=5)
)


Out[14]:

Análisis de regresión

Supuestos, muchos de los cuales aplican a los residuos:

  • Linealidad: Si no se cumple se comete un error de especificación, dado que el modelo será incorrecto. En el caso de una o dos variables independientes, ayuda observar un gráfico de dispersión en busca de una tendencia lineal (línea, plano).
  • Independencia de los residuos: Los residuos no deben estar correlacionados (deben ser una variable aleatoria), algo que puede suceder cuando se trabaja con series temporales. El estadístico de Durbin-Watson puede ayudar en la detección de autocorrelaciones.
  • Homocedasticidad: Igualdad de varianza para cada valor o grupo de valores de la/s variable/s independiente/s
  • Normalidad: Para cada valor o grupo de valores de la/s variable/s independiente/s los residuos se distribuyen de manera normal con $\mu = 0$
  • No-colinealidad: Las variables independientes no pueden ser combinaciones lineales unas de otras, no debe haber correlación entre ellas.

In [15]:
using GLM

In [16]:
Xₐ = hcat(ones(N), X) # Y = 1*α + β₁X₁ + β₂X₂ + ...


Out[16]:
10×3 Array{Float64,2}:
 1.0  0.0307714  0.4821  
 1.0  0.286979   0.558296
 1.0  0.110116   0.287557
 1.0  0.85274    0.191639
 1.0  0.895312   0.764406
 1.0  0.361209   0.230311
 1.0  0.460176   0.601009
 1.0  0.8968     0.892419
 1.0  0.293208   0.97748 
 1.0  0.653764   0.689387

In [17]:
modelo = lm(Xₐ,Y)


Out[17]:
GLM.LinearModel{GLM.LmResp{Array{Float64,1}},GLM.DensePredChol{Float64,Base.LinAlg.Cholesky{Float64,Array{Float64,2}}}}:

Coefficients:
     Estimate Std.Error t value Pr(>|t|)
x1   0.220747 0.0289136 7.63471   0.0001
x2    0.19005 0.0371247 5.11923   0.0014
x3   0.845895 0.0440124 19.2195    <1e-6


In [18]:
residuos = residuals(modelo)


Out[18]:
10-element Array{Float64,1}:
  0.018459  
  0.0310968 
 -0.0353076 
 -0.00299254
 -0.015911  
  0.0385749 
 -0.0492571 
  0.0369678 
 -0.00253678
 -0.0190936 

Mínimos Cuadrados Ponderados.

En el caso de no cumplirse con la homocedacia, es posible darle a cada observación un peso inversamente proporcional a la varianza para ese valor o grupo de valores de la/s variable/s independiente/s en una regresion por minimos cuadrados ponderados. Lo óptimo es conocer de antemano cual es la varianza de cada medición. Por ejemplo si medimos cada dato con una herramienta diferente y conocemos su error. Sino existen maneras de determinar los posibles valores de la varianza (o de un estimador robusto correlacionado con ésta) con respecto a las variables independientes.


In [19]:
using DataFrames

In [20]:
N = 100
X = rand(N)
β = rand()
α = 5

E = rand(Normal(1,0.5),N) .* X # heterocedasticidad

Y = α + X * β + E

datos = DataFrame(hcat(X, Y))
names!(datos, [:X, :Y])


Out[20]:
XY
10.75550478025437046.1168419478764635
20.65000335180307616.107586925334288
30.79656600710854926.266883634304559
40.72552221071166685.5623703682775245
50.46852988860631475.42364892432
60.408969916329752445.576719251621155
70.83906470317662725.827034024344083
80.71845576531753165.210219930930277
90.107697651324517365.0982632537600265
100.79884593239557486.12960831676362
110.95396874034411997.009458129620786
120.28172485844178355.243180791595584
130.163311303159301655.214760619037241
140.93331745797869166.790585730556131
150.118509262893335745.15587147072243
160.63338311112346675.941288094515035
170.185177921541449965.257487485007942
180.52120532367957845.670332197426866
190.365698857495648575.729282478654712
200.53798724609157315.727871343786915
210.42979534044448635.454010389252044
220.442588053678910855.8349850031366195
230.31953140444302735.5417992614936855
240.5186131459009485.315563354873516
250.58015538803074955.751732128993892
260.44773183275569235.840175189224592
270.65463776137514025.521643464722013
280.131729677764936385.152039359371673
290.389744906946258635.496211936058352
300.95822898265556925.796509880451708
&vellip&vellip&vellip

In [25]:
modelo = glm(@formula(Y ~ X), datos, Normal(), IdentityLink())


Out[25]:
DataFrames.DataFrameRegressionModel{GLM.GeneralizedLinearModel{GLM.GlmResp{Array{Float64,1},Distributions.Normal{Float64},GLM.IdentityLink},GLM.DensePredChol{Float64,Base.LinAlg.Cholesky{Float64,Array{Float64,2}}}},Array{Float64,2}}

Formula: Y ~ 1 + X

Coefficients:
             Estimate Std.Error z value Pr(>|z|)
(Intercept)   5.00851 0.0541294 92.5285   <1e-99
X              1.3304 0.0974747 13.6487   <1e-41

In [26]:
coeficientes = coef(modelo)


Out[26]:
2-element Array{Float64,1}:
 5.00851
 1.3304 

In [29]:
using StatPlots
pyplot(size=(300,300))

scatter(datos, :X, :Y, legend=false)
Plots.abline!(coeficientes[2], coeficientes[1])


Out[29]:

In [30]:
residuos = Y - predict(modelo)


Out[30]:
100-element Array{Float64,1}:
  0.103207  
  0.234311  
  0.198621  
 -0.411376  
 -0.208195  
  0.0241138 
 -0.297769  
 -0.754125  
 -0.0535298 
  0.058312  
  0.731787  
 -0.140138  
 -0.0110208 
  ⋮         
 -0.518117  
 -0.294746  
 -0.00206862
 -0.348796  
 -0.281484  
  0.175619  
 -0.543289  
 -0.144178  
  0.0167338 
 -0.394444  
 -0.490244  
  0.109015  

In [31]:
scatter(X, residuos, legend=false)


Out[31]:

In [32]:
residuos_modelo = DataFrame(
x = collect(0:0.05:0.8).+0.1, # inicio + paso/2 = centro de la ventana
y = Float64[ mad( residuos[i .<= X .<= i+0.2] ) for i in collect(0:0.05:0.8) ] # paso = 0.2
)


Out[32]:
xy
10.10.02213005266994569
20.150000000000000020.03201768481813598
30.20.06786344863988106
40.250.12769986133560404
50.300000000000000040.1761011520576226
60.350.22300560057832286
70.40.16373083687563167
80.449999999999999960.2042341948078266
90.50.24453736291451844
100.550.26127049064212476
110.60.23706608842726723
120.650.25734220128723023
130.70.28486043807130107
140.750.3905551672455809
150.79999999999999990.3679743427482098
160.850.3453935182508386
170.90.3458504507041181

In [35]:
modelo = glm(@formula(y ~ x), residuos_modelo, Normal(), IdentityLink())


Out[35]:
DataFrames.DataFrameRegressionModel{GLM.GeneralizedLinearModel{GLM.GlmResp{Array{Float64,1},Distributions.Normal{Float64},GLM.IdentityLink},GLM.DensePredChol{Float64,Base.LinAlg.Cholesky{Float64,Array{Float64,2}}}},Array{Float64,2}}

Formula: y ~ 1 + x

Coefficients:
               Estimate Std.Error  z value Pr(>|z|)
(Intercept)  0.00891738 0.0197618 0.451244   0.6518
x              0.423534 0.0354932  11.9328   <1e-32

In [36]:
plot(
scatter(residuos_modelo, :x,:y),
scatter(residuos_modelo[:x],residuos_modelo[:y]-predict(modelo)),
legend=false, size=(400,200))


Out[36]:

In [37]:
C = coef(modelo)


Out[37]:
2-element Array{Float64,1}:
 0.00891738
 0.423534  

In [38]:
pesos = Float64[ 1/(C[1] + C[2]*x) for x in datos[:X] ]


Out[38]:
100-element Array{Float64,1}:
  3.04045
  3.51845
  2.88775
  3.16255
  4.82263
  5.49058
  2.74507
  3.19277
 18.3382 
  2.87972
  2.42157
  7.79804
 12.8065 
  ⋮      
  6.85933
  2.37075
 75.7626 
  2.82398
  7.93179
  3.93092
  4.29418
  4.35399
  8.18937
  4.85282
  2.4049 
 11.6218 

In [39]:
lineal_ponderada = glm(@formula(Y ~ X), datos, Normal(), IdentityLink(), wts=pesos)


Out[39]:
DataFrames.DataFrameRegressionModel{GLM.GeneralizedLinearModel{GLM.GlmResp{Array{Float64,1},Distributions.Normal{Float64},GLM.IdentityLink},GLM.DensePredChol{Float64,Base.LinAlg.Cholesky{Float64,Array{Float64,2}}}},Array{Float64,2}}

Formula: Y ~ 1 + X

Coefficients:
             Estimate  Std.Error z value Pr(>|z|)
(Intercept)   5.00158 0.00661172 756.473   <1e-99
X             1.34475   0.020055 67.0529   <1e-99

In [40]:
reg_plot = scatter(datos, :X, :Y)
Plots.abline!(reg_plot, coef(lineal_ponderada)[2], coef(lineal_ponderada)[1])

residuos = ( datos[:Y] - predict(lineal_ponderada) ) .* pesos

normal = Normal(0,std(residuos))

res_plot = histogram(residuos, bins=10, normed=true)
plot!(res_plot, res -> pdf(normal, res), minimum(residuos), maximum(residuos))

res_scatter = scatter(datos[:X], residuos)

plot(reg_plot, res_scatter, res_plot, legend=false, size=(800,200), layout=grid(1,3))


Out[40]:

En general es preferible usar pesos cuando éstos se conocen de antemano. Por ejemplo si se conoce el error experimental de los instrumentos, B factors o resolución en estructuras cristalográficas, etc. Tratar de encontrar pesos puede ser complicado. En este caso, los pesos hacen que los residuales ponderados cumplan homocedacia, pero quizás a costa de la normalidad.

Ridge Regression

Ridge regression en MultivariateStats y en ScikitLearn.
Esta regresión es mucho más robusta a la presencia de variables explicativas correlacionadas (colineales).

Ridge Regression usando ScikitLearn.jl, el valor de α es seleccionado usando validación cruzada:


In [41]:
k = 3  # variables independientes
@assert k > 2
N = 10 # observaciones
X = rand(N, k) # simulación: valores observados para las variables independientes
X[:,1] = X[:,1].*X[:,2] + X[:,1] # colineales
α = rand() # simulación: valor real de la intersección
β = rand(k) # simulación: valores reales de los coeficientes
E = 0.1 .* rand(N) # simulación: agrega errores al azar al modelo
Y = α + X * β + E # valores para la variable dependiente con el modelo lineal que acabamos de armar


Out[41]:
10-element Array{Float64,1}:
 1.27462 
 1.0329  
 0.8406  
 1.48093 
 0.519938
 0.694567
 1.53851 
 1.98415 
 1.11655 
 0.568023

In [42]:
using ScikitLearn
@sk_import linear_model: RidgeCV


WARNING: Your Python's scikit-learn has version 0.14.1. We recommend updating to 0.18.0 or higher for best compatibility with ScikitLearn.jl.
Out[42]:
PyObject <class 'sklearn.linear_model.ridge.RidgeCV'>

In [43]:
rcv = RidgeCV(alphas=0.01:0.01:10.0)


Out[43]:
PyObject RidgeCV(alphas=[0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.1, 0.11, 0.12, 0.13, 0.14, 0.15, 0.16, 0.17, 0.18, 0.19, 0.2, 0.21, 0.22, 0.23, 0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.3, 0.31, 0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38, 0.39, 0.4, 0.41, 0.42, 0.43, 0.44, 0.45, 0.46, 0.47, 0.48, 0.49, 0....9.84, 9.85, 9.86, 9.87, 9.88, 9.89, 9.9, 9.91, 9.92, 9.93, 9.94, 9.95, 9.96, 9.97, 9.98, 9.99, 10.0],
    cv=None, fit_intercept=True, gcv_mode=None, loss_func=None,
    normalize=False, score_func=None, scoring=None, store_cv_values=False)

In [44]:
ScikitLearn.fit!(rcv, X, Y)


Out[44]:
PyObject RidgeCV(alphas=[0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.1, 0.11, 0.12, 0.13, 0.14, 0.15, 0.16, 0.17, 0.18, 0.19, 0.2, 0.21, 0.22, 0.23, 0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.3, 0.31, 0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38, 0.39, 0.4, 0.41, 0.42, 0.43, 0.44, 0.45, 0.46, 0.47, 0.48, 0.49, 0....9.84, 9.85, 9.86, 9.87, 9.88, 9.89, 9.9, 9.91, 9.92, 9.93, 9.94, 9.95, 9.96, 9.97, 9.98, 9.99, 10.0],
    cv=None, fit_intercept=True, gcv_mode=None, loss_func=None,
    normalize=False, score_func=None, scoring=None, store_cv_values=False)

In [45]:
rcv[:coef_]


Out[45]:
3-element Array{Float64,1}:
 0.288703
 0.80839 
 0.58305 

In [46]:
rcv[:intercept_]


Out[46]:
0.26559505862944266

In [47]:
α = rcv[:alpha_]


Out[47]:
0.01

Ridge Regression usando ScikitLearn.jl:


In [48]:
@sk_import linear_model: Ridge


Out[48]:
PyObject <class 'sklearn.linear_model.ridge.Ridge'>

In [49]:
r = Ridge(alpha = α)


Out[49]:
PyObject Ridge(alpha=0.01, copy_X=True, fit_intercept=True, max_iter=None,
   normalize=False, solver='auto', tol=0.001)

In [50]:
ScikitLearn.fit!(r, X, Y)


Out[50]:
PyObject Ridge(alpha=0.01, copy_X=True, fit_intercept=True, max_iter=None,
   normalize=False, solver='auto', tol=0.001)

In [51]:
r[:coef_]


Out[51]:
3-element Array{Float64,1}:
 0.288703
 0.80839 
 0.58305 

In [52]:
rcv[:intercept_]


Out[52]:
0.26559505862944266

Ridge Regression usando MultivariateStats:


In [53]:
MultivariateStats.ridge(X, Y, α)


Out[53]:
4-element Array{Float64,1}:
 0.288703
 0.80839 
 0.58305 
 0.265595

Ridge Regression usando R and RCall:


In [57]:
using RCall

R"""
library(MASS)
lmr <- lm.ridge($Y ~ $X, lambda=)
"""


Out[57]:
RCall.RObject{RCall.VecSxp}
           `#JL`$X1  `#JL`$X2  `#JL`$X3 
0.2617481 0.2888427 0.8149272 0.5843138