Está en la página 1de 42

Regresión Lineal

March 18, 2021

1 Regresión Lineal Simple

[73]: ### Load relevant packages


import pandas as pd
from scipy import stats
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
#import statsmodels.formula.api as sm
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
import chart_studio.plotly as py
import statsmodels.formula.api as smf
import statsmodels.api as sm
# https://community.plot.ly/t/solved-update-to-plotly-4-0-0-broke-application/
,→26526/2

import os
from sklearn.metrics import mean_squared_error
%matplotlib inline
plt.style.use('ggplot')

[61]: #Cuando no tenga una libreria con la siguiente linea la pueden instalar
#pip install char_studio

1.1 Introducción
Contexto. Eres un científico de datos en una gran organización. Su empresa está pasando por una
revisión interna de sus prácticas de contratación y compensación a los empleados. En los últimos
años, su empresa ha tenido poco éxito en la conversión de candidatas de alta calidad que deseaba
contratar. La gerencia plantea la hipótesis de que esto se debe a una posible discriminación salarial
y quiere averiguar qué la está causando.
En general, ¿se les paga más a los hombres que a las mujeres en su organización? Si es así, ¿qué
conduciendo esta brecha?
Se tiene una base de datos de empleados que contiene información sobre varios atributos como
desempeño, educación, ingresos, antigüedad, etc

1
1.2 Estadística Descriptiva

[9]: Data = pd.read_csv('glassdoordata.csv')

[10]: Data.head()

[10]: jobtitle gender age performance education department \


0 Graphic Designer Female 18 5 College Operations
1 Software Engineer Male 21 5 College Management
2 Warehouse Associate Female 19 4 PhD Administration
3 Software Engineer Male 20 5 Masters Sales
4 Graphic Designer Male 26 5 Masters Engineering

seniority income bonus


0 2 42363 9938
1 5 108476 11128
2 5 90208 9268
3 4 108080 10154
4 5 99464 9319

• job title: el título del trabajo (por ejemplo, “Diseñador gráfico”, “Ingeniero de software”,
etc.);
• gender: hombre o mujer;
• age: edad;
• performance: desempeño en una escala del 1 al 5, siendo 1 el más bajo y 5 el más alto;
• education: diferentes niveles de educación (por ejemplo, “Universidad”, “Doctorado”,
“Maestría”, “Escuela secundaria”);
• departament: diferentes departamentos de la organización (por ejemplo, “Operaciones”,
“Gestión”, etc.);
• seniority: antiguedad en una escala de 1 a 5, siendo 1 la más baja y 5 la más alta;
• income, bonus: ingresos y bonificación ambos expresados en dólares
Se crea una nueva columna con el ingreso total

[11]: #El ingreso total se define como el ingreso + bonificación


Data['pay'] = Data['income'] + Data['bonus']

[12]: Data.head()

[12]: jobtitle gender age performance education department \


0 Graphic Designer Female 18 5 College Operations
1 Software Engineer Male 21 5 College Management
2 Warehouse Associate Female 19 4 PhD Administration
3 Software Engineer Male 20 5 Masters Sales
4 Graphic Designer Male 26 5 Masters Engineering

seniority income bonus pay


0 2 42363 9938 52301

2
1 5 108476 11128 119604
2 5 90208 9268 99476
3 4 108080 10154 118234
4 5 99464 9319 108783

[182]: #construimos un bloxplot del pago total dado el género


sns.boxplot(x='department', y = 'pay', data = Data, palette="Set3")
plt.title("Pay vs deparment");

3
Inferencia - Pruebas de Hipótesis Probar si hay una diferencia en el pago promedio entre hom-
bres y mujeres con una prueba t-student a un nivel de confianza del 95%

[60]: #Pago promedio por genero


pay_by_gender = Data.groupby('gender')['pay']
pay_by_gender.mean()

[60]: gender
Female 96416.831197
Male 104918.678571
Name: pay, dtype: float64

[59]: ## test diferencia de medias


t2, p2 = stats.ttest_ind(Data.loc[Data['gender'] == 'Male', 'pay'],Data.
,→loc[Data['gender'] == 'Female', 'pay'])

print("t = " + str(t2))


print("p = " + str(p2))

t = 5.407461816876623
p = 8.000016978237565e-08
Rta. Dado que el p valor < 0.05. Rechazo la hipótesis nula. Así, con un nivel de confianza del 95
existe una diferencia significativa entre el ingreso promedio total de los hombres y mujeres.

[61]: pay_by_gender.mean()['Male'] - pay_by_gender.mean()['Female']

4
[61]: 8501.847374847363

Identificando otros atributos


[62]: # Ingreso total vs Edad
plt.scatter(Data['age'],Data['pay'])
plt.title("Pay vs. Age", fontsize=20, verticalalignment='bottom');
plt.xlabel("Age");
plt.ylabel("Pay");

[63]: # ingresos vs Educación


sns.boxplot(x='education', y = 'pay', data = Data, palette="Set3")
plt.title("Pay vs. Education", fontsize=20, verticalalignment='bottom');

5
[64]: # ingresos totales vs antiguedad
sns.boxplot(x='seniority', y = 'pay', data = Data, palette="Set3")
plt.title("Pay vs. Seniority", fontsize=20, verticalalignment='bottom');

6
[26]: # inbresos totales vs desempeño
sns.boxplot(x='performance', y = 'pay', data = Data, palette="Set3")
plt.title("Pay vs. Performance", fontsize=20, verticalalignment='bottom');

7
[27]: # ingresos vs tipo de trabajo
sns.boxplot(x='jobtitle', y = 'pay', data = Data, palette="Set3")
plt.title("Pay vs. Jobtitle", fontsize=20, verticalalignment='bottom')
plt.xticks(rotation=90);

8
1.2.1 Evaluando los atributos por Género

[65]: sns.boxplot(x='education', y = 'pay', hue = 'gender', data = Data,␣


,→palette="Set3" )

plt.title("Pay vs. Education", fontsize=20, verticalalignment='bottom');

9
[29]: sns.boxplot(x='seniority', y = 'pay', hue = 'gender', data = Data,␣
,→palette="Set3")

plt.title("Pay vs. Seniority", fontsize=20, verticalalignment='bottom');

10
[30]: sns.boxplot(x='performance', y = 'pay', hue = 'gender', data = Data,␣
,→palette="Set3")

plt.title("Pay vs. Performance", fontsize=20, verticalalignment='bottom');

11
[31]: sns.boxplot(x='jobtitle', y = 'pay', hue = 'gender',data = Data, palette="Set3")
plt.title("Pay vs. Jobtitle", fontsize=20, verticalalignment='bottom')
plt.xticks(rotation=90);

12
1.3 ¿Cuáles son las variables que influyen en la remuneración?
1.3.1 La correlación mide la relación lineal entre dos variables
Vemos una relación lineal entre salario y edad. El salario parece estar correlacionado positiva-
mente con la edad; es decir, cuanto mayor es una persona, más tienden a ganar. Por lo tanto,
podría ser que hay más hombres en nuestro conjunto de datos que sean mayores y la diferencia
salarial entre hombres y mujeres que vemos podría ser una consecuencia de esto. La correlación
cuantitativa mide qué tan lineal es la relación entre dos variables.

[49]: # Ingreso total vs Edad


plt.scatter(Data['age'],Data['pay'])
plt.title("Pay vs. Age", fontsize=20, verticalalignment='bottom');
plt.xlabel("Age");
plt.ylabel("Pay");

13
[50]: plt.figure(figsize=(12,10))
rho = [0.999, -0.999, 0.5, -0.7,0.001,-0.3]
cor_list = []
np.random.seed(10)
for i, r in enumerate(rho):
plt.subplot(2,3,i+1)
mean, cov = [4, 6], [(1, r), (r, 1)]
x, y = np.random.multivariate_normal(mean, cov, 150).T
ax = sns.scatterplot(x=x, y=y, color="g")
cor_list.append(np.corrcoef(x,y)[0,1])
plt.xlabel("x")
plt.ylabel("y")
plt.title("Correlación")

14
[11]: cor_list

[11]: [0.9990081442281219,
-0.9992506121564401,
0.5047051300834762,
-0.7228982219723221,
-0.04041125768713201,
-0.32083184263386666]

Para encontrar las variables que tienen la mayor influencia en el salario, podemos calcular una
matriz de correlación que mide las correlaciones por pares entre dos variables cualesquiera:

[51]: corr_mat = Data[['pay', 'age', 'seniority','performance']].


,→corr(method='pearson')

corr_mat

15
[51]: pay age seniority performance
pay 1.000000 0.533715 0.530307 0.014155
age 0.533715 1.000000 -0.021414 -0.056875
seniority 0.530307 -0.021414 1.000000 -0.021127
performance 0.014155 -0.056875 -0.021127 1.000000

[16]: sns.heatmap(corr_mat,cmap=sns.diverging_palette(220, 10, as_cmap=True))


plt.title("Matriz Correlaciones")

[16]: Text(0.5, 1.0, 'Matriz Correlaciones')

1.4 Uso de modelos lineales para tener en cuenta las variables correlacionadas con el
ingreso
Una vez que identificamos algunas variables independientes que están correlacionadas con la
variable dependiente, se puede utilizar un modelo lineal para capturar esta relación cuantitativa-
mente.
[52]: sns.lmplot(x = 'age', y = 'pay', data = Data, scatter_kws = {'color': (174/
,→255,199/255,14/255)})

plt.title("Pay vs. Age", fontsize=20, verticalalignment='bottom')


plt.xlabel("Age")
plt.ylabel("Pay")

16
[52]: Text(-15.450000000000003, 0.5, 'Pay')

Una línea tiene dos parámetros: intersección o intercepto (β 0 ) y pendiente (β 1 ). Por lo tanto, un
modelo lineal de ingreso total frente a la edad se puede representar como

pay = β 0 + β 1 age + error.

La interpretación del coeficiente β 1 es la siguiente: un aumento de un año en la edad conducirá en


promedio a un cambio de β 1 USD en el ingreso total. La intersección β 0 se puede considerar como
una especie de ingreso “básico” o SMMLV ejemplo.

17
1.4.1 Escenarios
[53]: from bqplot import *
from IPython.display import display
from bqplot.interacts import (
FastIntervalSelector, IndexSelector, BrushIntervalSelector,
BrushSelector, MultiSelector, LassoSelector, PanZoom, HandDraw
)
import ipywidgets as widgets
from ipywidgets import *

def run_scenario(scenario='1'):

#determing which scenario this is


if (scenario == '1'):
# defining x,y coordinates, scenario 1
x_coor = [18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40]
y_coor = [50000, 53000, 60000, 59000, 63000, 70000, 67000, 80000, 75000,␣
,→88000, 90000, 92000]

elif (scenario=='2'):
# defining x,y coordinates, scenario 1
x_coor = [18, 26, 28, 30, 32, 34, 36, 38, 40, 20, 24]
y_coor = [50000, 63000, 70000, 67000, 80000, 75000, 88000, 90000, 92000,␣
,→85000, 70000]

elif (scenario=='3'):
# defining x,y coordinates, scenario 1
x_coor = [18, 20, 22, 32, 34, 36, 38, 40, 38, 36]
y_coor = [50000, 53000, 60000, 80000, 75000, 88000, 90000, 92000, 50000,␣
,→52000]

elif (scenario=='4'):
# defining x,y coordinates, scenario 1
x_coor = [18, 20, 22, 24, 26, 28, 30, 38, 40, 35, 22]
y_coor = [50000, 53000, 60000, 59000, 63000, 70000, 67000, 90000, 92000,␣
,→55000, 90000]

# defining linear scale for x,y axes


x_sc = LinearScale(min=min(x_coor), max=max(x_coor))
y_sc = LinearScale(min=min(y_coor), max=max(y_coor))

# creating x,y axes using linear scales above


ax_x = Axis(label='Age', scale=x_sc, grid_lines='dashed', num_ticks=8,␣
,→label_color='blue')

ax_y = Axis(label='Income', scale=y_sc, orientation='vertical',␣


,→label_color='blue', label_offset='50px')

#adding scatter marks


names = []

18
for i in range(0, len(x_coor)):
name = str(x_coor[i]) + ', ' + str(y_coor[i])
names.append(name)

def_tt = Tooltip(fields=['x', 'y'], formats=['', '.2f'])

scatter_dots = Scatter(x=x_coor, y=y_coor, scales={'x': x_sc, 'y': y_sc},␣


,→tooltip=def_tt, unhovered_style={'opacity': 0.5})

# scatter2 = Scatter(x=[x_coor[1]], y=[y_coor[1]], scales={'x': x_sc, 'y':␣


,→y_sc}, enable_move=True)

# defining in regression line


sum_x = 0
sum_y = 0
sum_xy = 0
sum_x_squared = 0
sum_y_squared = 0
n = len(x_coor)

for i in range(0, len(x_coor)):


sum_x += x_coor[i]
sum_y += y_coor[i]
sum_xy += sum_x * sum_y
sum_x_squared += sum_x * sum_x
sum_y_squared += sum_y * sum_y

a = ((sum_y * sum_x_squared) - (sum_x * sum_xy)) / ((n * sum_x_squared) -␣


,→(sum_x * sum_x))
b = ((n * sum_xy) - (sum_x * sum_y)) / ((n * sum_x_squared) - (sum_x *␣
,→sum_x))

y_hat_values = []

for x in x_coor:
new_y_val = a + (b * x)
y_hat_values.append(new_y_val)

regression_line = Lines(x=x_coor, y=y_hat_values, scales={'x': x_sc, 'y':␣


,→ y_sc}, colors=['red'], visible=False)

#user interaction, drawing a line

user_line_x = [19, 40]


user_line_y = [60000, 60000]

19
starting_user_line = Lines(x=user_line_x, y=user_line_y, scales={'x': x_sc,␣
,→ 'y': y_sc}, colors=['green'])

scatter = Scatter(x=user_line_x, y=user_line_y, scales={'x': x_sc, 'y':␣


,→ y_sc}, unhovered_style={'opacity': 0.5}, enable_move=True, colors=['black'])

#create figures and render


fig = Figure(marks=[scatter_dots, scatter, starting_user_line,␣
,→regression_line], axes=[ax_x, ax_y], title="Modelo de Regresión Lineal")

#add in dropdown menu


dropdown = widgets.Dropdown(
options=['1', '2', '3', '4'],
value=scenario,
description='Escenario:',
disabled=False,
)

out = Output()
display(out)

def handler(x):
print('hi')

@out.capture()
def observe_scatter_x_y(point, change):

x_array = point.x
y_array = point.y
find_change_x = change['point']['x']
find_change_y = change['point']['y']
hovered_point = point.hovered_point

if (hovered_point == 0):
starting_user_line.set_trait('x', [find_change_x, x_array[1]])
starting_user_line.set_trait('y', [find_change_y, y_array[1]])
# starting_user_line.y[0] = find_change_y
# starting_user_line.x[0] = starting_user_line.x.pop(0)
# starting_user_line.y = starting_user_line.y.pop(0)
# starting_user_line.x = starting_user_line.x.insert(0,␣
,→find_change_y)

# starting_user_line.y = starting_user_line.y.insert(0,␣
,→find_change_y)

20
elif (hovered_point == 1):
starting_user_line.set_trait('x', [x_array[0], find_change_x])
starting_user_line.set_trait('y', [y_array[0], find_change_y])
# starting_user_line.x = starting_user_line.x.pop(1)
# starting_user_line.y = starting_user_line.y.pop(1)
# starting_user_line.x = starting_user_line.x.insert(1,␣
,→find_change_y)

# starting_user_line.y = starting_user_line.y.insert(1,␣
,→find_change_y)

# if change['name'] == 'x':
# line.x = line.x + change['new'] - change['old']
# else:
# line.y = line.y + change['new'] - change['old']

scatter.on_drag(observe_scatter_x_y)

@out.capture()
def on_dropdown_change(b):
fig.close()
button.close()
dropdown.close()
run_scenario(dropdown.value)

dropdown.observe(on_dropdown_change, 'value')

#add clicking action for submit


button = widgets.Button(description="See Answer", button_style='primary', ␣
,→margin_left='40px', layout=Layout(display='flex', button_color='white',␣

,→justify_content='center', width='300px'))

def on_button_clicked(b):
regression_line.set_trait('visible', True)

button.on_click(on_button_clicked)

box_layout = Layout(display='flex',
justify_content='center',
width='50%')

box = Box(children=[button], layout=box_layout)

display(dropdown, fig, box)

21
run_scenario()

Output()
Dropdown(description='Escenario:', options=('1', '2', '3', '4'), value='1')
Figure(axes=[Axis(grid_lines='dashed', label='Age', label_color='blue',␣
,→num_ticks=8, scale=LinearScale(max=40....

Box(children=(Button(button_style='primary', description='See Answer',␣


,→layout=Layout(display='flex', justify_c...

1.4.2 Residuales
El residual representa la diferencia entre la observación real y la predicha por la línea de regresión
lineal, como se muestra a continuación:
[54]: from bqplot import *
from IPython.display import display
from bqplot.interacts import (
FastIntervalSelector, IndexSelector, BrushIntervalSelector,
BrushSelector, MultiSelector, LassoSelector, PanZoom, HandDraw
)
import ipywidgets as widgets
from ipywidgets import *

# defining x,y coordinates, scenario 1


x_coor = [18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 39]
y_coor = [50000, 53000, 60000, 59000, 63000, 70000, 67000, 80000, 75000, 88000,␣
,→90000, 92000, 52000]

# defining linear scale for x,y axes


x_sc = LinearScale(min=min(x_coor), max=max(x_coor))
y_sc = LinearScale(min=min(y_coor), max=max(y_coor))

# creating x,y axes using linear scales above


ax_x = Axis(label='Age', scale=x_sc, grid_lines='dashed', num_ticks=8,␣
,→label_color='blue')

ax_y = Axis(label='Income', scale=y_sc, orientation='vertical',␣


,→label_color='blue', label_offset='50px')

#adding scatter marks


names = []

for i in range(0, len(x_coor)):


name = str(x_coor[i]) + ', ' + str(y_coor[i])
names.append(name)

22
def_tt = Tooltip(fields=['x', 'y'], formats=['', '.2f'])

scatter = Scatter(x=x_coor, y=y_coor, scales={'x': x_sc, 'y': y_sc}, opacity=[0.


,→5], tooltip=def_tt, selected_style={'fill':'red', 'stroke': 'red'},␣

,→selected=[5])

out = Output()
display(out)

@out.capture()
def on_scatter_click(marks, dot):
marks.set_trait('selected', [dot['data']['index']])

scatter.on_element_click(on_scatter_click)

# defining in regression line


sum_x = 0
sum_y = 0
sum_xy = 0
sum_x_squared = 0
sum_y_squared = 0
n = len(x_coor)

for i in range(0, len(x_coor)):


sum_x += x_coor[i]
sum_y += y_coor[i]
sum_xy += sum_x * sum_y
sum_x_squared += sum_x * sum_x
sum_y_squared += sum_y * sum_y

a = ((sum_y * sum_x_squared) - (sum_x * sum_xy)) / ((n * sum_x_squared) - (sum_x␣


,→* sum_x))

b = ((n * sum_xy) - (sum_x * sum_y)) / ((n * sum_x_squared) - (sum_x * sum_x))

y_hat_values = []

for x in x_coor:
new_y_val = a + (b * x)
y_hat_values.append(new_y_val)

regression_line = Lines(x=x_coor, y=y_hat_values, scales={'x': x_sc, 'y': y_sc},␣


,→colors=['red'])

#create figures and render

23
fig = Figure(marks=[scatter, regression_line], axes=[ax_x, ax_y],␣
,→title='Residuales',)

#add clicking action for submit


button = widgets.Button(description="See Answer", button_style='primary', ␣
,→margin_left='40px', layout=Layout(display='flex', button_color='white',␣

,→justify_content='center', width='300px'))

@out.capture()
def on_button_clicked(b):
answer = 12
scatter.set_trait('selected', [answer])
residual_line=Lines(x=[x_coor[answer], x_coor[answer]], y=[y_coor[answer],␣
,→y_hat_values[answer]], scales={'x': x_sc, 'y': y_sc}, colors=['red'])

fig.set_trait('marks', [scatter, regression_line, residual_line])

button.on_click(on_button_clicked)

box_layout = Layout(display='flex',
justify_content='center',
width='50%')

box = Box(children=[button], layout=box_layout)

display(fig, box)

Output()
Figure(axes=[Axis(grid_lines='dashed', label='Age', label_color='blue',␣
,→num_ticks=8, scale=LinearScale(max=40....

Box(children=(Button(button_style='primary', description='See Answer',␣


,→layout=Layout(display='flex', justify_c...

1.5 Ajuste del Modelo


[13]: #Dividir Muestra
# División de los datos en train y test
# ==============================================================================
X = Data[['age']]
y = Data[['pay']]

X_train, X_test, y_train, y_test = train_test_split(


X.values.reshape(-1,1),
y.values.reshape(-1,1),
train_size = 0.8,

24
random_state = 1234,
shuffle = True
)

[181]: #total modelo


#model1 = 'pay~age'
#lm1 = sm.ols(formula = model1, data = Data).fit()
#print(lm1.summary())

# Creación del modelo entrenamiento


# ==============================================================================
#lm1 = LinearRegression()
#lm1.fit(X = X_train.reshape(-1, 1), y = y_train)
#print(lm1.summary())

X_train = sm.add_constant(X_train, prepend=True)


lm1 = sm.OLS(endog=y_train, exog=X_train,)
lm1 = lm1.fit()
print(lm1.summary())

OLS Regression Results


==============================================================================
Dep. Variable: y R-squared: 0.300
Model: OLS Adj. R-squared: 0.299
Method: Least Squares F-statistic: 341.8
Date: Wed, 17 Mar 2021 Prob (F-statistic): 8.56e-64
Time: 22:50:44 Log-Likelihood: -9099.3
No. Observations: 800 AIC: 1.820e+04
Df Residuals: 798 BIC: 1.821e+04
Df Model: 1
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
const 6.056e+04 2286.541 26.485 0.000 5.61e+04 6.5e+04
x1 967.5689 52.334 18.488 0.000 864.840 1070.297
==============================================================================
Omnibus: 5.418 Durbin-Watson: 2.105
Prob(Omnibus): 0.067 Jarque-Bera (JB): 5.496
Skew: 0.190 Prob(JB): 0.0640
Kurtosis: 2.855 Cond. No. 134.
==============================================================================

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

25
[ ]:

1.5.1 Coeficientes
La intersección β 0 es de aproximadamente 60560 USD. Esto se puede considerar como el salario
base. (Con frecuencia, la intersección no tiene una interpretación significativa, eso está bien). La
pendiente (el coeficiente β 1 para la edad) es 939,25. La interpretación de este coeficiente es si un
empleado envejece un año, se espera que su salario aumente en 967,56 USD en promedio.
Es importante tener en cuenta que la forma en que limpiamos / imputamos los datos antes del
modelado tendrá enormes efectos en los coeficientes que obtenga. Un ejemplo simple es si conver-
timos arbitrariamente todos los valores faltantes a la media de ese parámetro. Esto conducirá a un
coeficiente completamente diferente que si usáramos k - imputación de vecinos más cercanos. Por
eso es tan importante elegir cuidadosa y deliberadamente un método de imputación apropiado
cuando se trata de datos faltantes o incorrectos.

1.5.2 $ p $ -valor
El sistema de hipótesis que estamos probando es

H0 : β i = 0

Ha : β i 6= 0.

El p-valor de β 1 (dado debajo de la columna: ”P > |t|”) es 0.000. Por lo tanto, es estadísticamente
significativo al nivel de significancia del 0.05 y rechazamos la hipótesis nula. Esto implica que la
edad explica algunas de las diferencias salariales.

[15]: # Intervalos de confianza para los coeficientes del modelo


# ==============================================================================
lm1.conf_int(alpha=0.05)

[15]: array([[56070.32416867, 65047.01390733],


[ 864.84045519, 1070.29738154]])

1.6 $ R $ Cuadrado
Una de las cantidades clave a las que se debe prestar atención al interpretar una tabla de regresión
es la cantidad [$ R $ -cuadrado]. Tenga en cuenta que la tabla muestra R cuadrado y $ R $ cuadrado
ajustado. Nos centraremos en $ R $ cuadrado. Esta cantidad siempre estará entre 0 y 1. Para el
modelo de salario vs. edad, esta cantidad es 0.30 = 30%.
Un $ R $ cuadrado de 30% en este modelo lineal significa que esta variación observada en el
salario no se debe a una casualidad; más bien, el factor edad explica sistemáticamente el 30% de
esta variación salarial. Cuanto mayor sea este parametro, mayor será el porcentaje de variación
observada que puede ser explicada por el modelo. Dado que model1 solo explica aproximada-
mente el 30% de la variación, esto nos motiva a investigar si otros factores además de la edad
pueden utilizarse para explicar las diferencias salariales.

26
1.6.1 Relación entre correlación y $ R $ cuadrado

[27]: corr_age_pay = np.corrcoef(Data['pay'],Data['age'])[0,1]


corr_age_pay*corr_age_pay

[27]: 0.28485128851108266

1.6.2 Interpretación geométrica de $ R $ Cuadrado


La formula para esta medición es:
RSS
R2 = 1 −
TSS
Aquí [RSS] y [TSS] denotan la suma total y residual de cuadrados respectivamente.

[22]: sns.lmplot(x = 'age', y = 'pay', data = Data, scatter_kws = {'color': (174/


,→255,199/255,14/255)})

plt.title("Pay vs. Age", fontsize=20, verticalalignment='bottom')


plt.axhline(Data['pay'].mean(), ls='--',color = 'b')
plt.xlabel("Age")
plt.ylabel("Pay");

27
Considere el model0 dado por
pay = β 0 + error
La mejor estimación para β 0 es solo el salario promedio. R cuadrado mide qué tan bien la línea de
regresión de model1 (ingresos frente a edad) dada por la línea roja explica la variación observada
en comparación con model0 . La suma de los cuadrados residuales para este modelo es el RSS

[57]: # Error de test del modelo


# ==============================================================================
X_test = sm.add_constant(X_test, prepend=True)
predicciones = lm1.predict(exog = X_test)
rmse = mean_squared_error(
y_true = y_test,
y_pred = predicciones,
squared = False
)

28
print("")
print(f"El error (rmse) del test es: {rmse}")

El error (rmse) del test es: 22088.470862161113


El error de test del modelo es de 22088. Las predicciones del modelo final se alejan en promedio
22088 USD del valor real.
[58]: # Error de train del modelo
# ==============================================================================
X_test = sm.add_constant(X_train, prepend=True)
predicciones = lm1.predict(exog = X_train)
rmse = mean_squared_error(
y_true = y_train,
y_pred = predicciones,
squared = False
)
print("")
print(f"El error (rmse) del train es: {rmse}")

El error (rmse) del train es: 21060.1927021973


El error de train del modelo es de 21060. Las predicciones del modelo final se alejan en promedio
21060 USD del valor real.

1.6.3 AIC - Criterio de Akaike y BIC - Criterio de Información Bayesiano


Hay varios criterios de selección de modelos que cuantifican la calidad de un modelo gestionando
el equilibrio entre bondad de ajuste y simplicidad. El más común es el AIC(criterio de informa-
ción de Akaike). El AIC penaliza la adición de más términos a un modelo, por lo que para que un
modelo actualizado tenga un mejor AIC, su R2 debe mejorar al menos tanto como la penalización
adicional impuesta. Dados varios modelos, el que tiene el AIC más bajo es el recomendado
AIC = 2 k – 2 ln(L)
Donde k es el número de parámetros y L el valor máximo en la función de verosimilitud (Maxi-
mum likelihood).
Muy similar al AIC es el criterio bayesiano de información, BIC (Bayesian Information Criterion)
de Schwarz, que viene dado por:
BIC = k ln(n) – 2 ln(L)
Donde, de nuevo k es el número de parámetros, L es el valor de máxima verosimilitud y n es el
número de datos. Dados varios modelos, el que tiene el BIC más bajo es el recomendado

1.7 Analizando el genero: variable categórica


Ahora que hemos visto que la edad explica parte de la relación con el salario, consideremos un
modelo en el que tengamos en cuenta el género. En el ejercicio anterior la edad es una variable

29
numérica (por ejemplo, 26,5, 32). Por el contrario, el género toma solo dos valores: masculino y
femenino. Estas variables se denominan variables categóricas . La forma en que interpretamos los
coeficientes de las variables factoriales en el modelo lineal es ligeramente diferente de los de las
variables numéricas:
[35]: # otra forma de dividir la base de datos
np.random.seed(10000) # todos usamos la misma semilla para obtener la misma␣
,→muestra

ndata = len(Data)
# Selección del 80% de la muestra para el entrenamiento del modelo
idx_train = np.random.choice(range(ndata),int(0.8*ndata),replace=False)
idx_test = np.asarray(list(set(range(ndata)) - set(idx_train)))
train = Data.iloc[idx_train] # the training data set
test = Data.iloc[idx_test] # the test data set
print(train.shape) # 800 empleados
print(test.shape) # 200 empleados

(800, 10)
(200, 10)

[43]: model2 = 'pay~gender'


lm2 = smf.ols(formula = model2, data = train).fit()
lm2.summary()

[43]: <class 'statsmodels.iolib.summary.Summary'>


"""
OLS Regression Results
==============================================================================
Dep. Variable: pay R-squared: 0.031
Model: OLS Adj. R-squared: 0.030
Method: Least Squares F-statistic: 25.38
Date: Tue, 16 Mar 2021 Prob (F-statistic): 5.82e-07
Time: 20:00:48 Log-Likelihood: -9225.5
No. Observations: 800 AIC: 1.846e+04
Df Residuals: 798 BIC: 1.846e+04
Df Model: 1
Covariance Type: nonrobust
================================================================================
==
coef std err t P>|t| [0.025
0.975]
--------------------------------------------------------------------------------
--
Intercept 9.636e+04 1270.022 75.871 0.000 9.39e+04
9.89e+04
gender[T.Male] 8809.6508 1748.638 5.038 0.000 5377.177
1.22e+04
==============================================================================

30
Omnibus: 2.118 Durbin-Watson: 1.947
Prob(Omnibus): 0.347 Jarque-Bera (JB): 2.167
Skew: 0.100 Prob(JB): 0.338
Kurtosis: 2.842 Cond. No. 2.69
==============================================================================

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

[44]: # La función C() me ayuda para indicarle al modelo que es una variable categórica
model2 = 'pay~C(gender)'
lm2 = smf.ols(formula = model2, data = train).fit()
lm2.summary()

[44]: <class 'statsmodels.iolib.summary.Summary'>


"""
OLS Regression Results
==============================================================================
Dep. Variable: pay R-squared: 0.031
Model: OLS Adj. R-squared: 0.030
Method: Least Squares F-statistic: 25.38
Date: Tue, 16 Mar 2021 Prob (F-statistic): 5.82e-07
Time: 20:08:04 Log-Likelihood: -9225.5
No. Observations: 800 AIC: 1.846e+04
Df Residuals: 798 BIC: 1.846e+04
Df Model: 1
Covariance Type: nonrobust
================================================================================
=====
coef std err t P>|t| [0.025
0.975]
--------------------------------------------------------------------------------
-----
Intercept 9.636e+04 1270.022 75.871 0.000 9.39e+04
9.89e+04
C(gender)[T.Male] 8809.6508 1748.638 5.038 0.000 5377.177
1.22e+04
==============================================================================
Omnibus: 2.118 Durbin-Watson: 1.947
Prob(Omnibus): 0.347 Jarque-Bera (JB): 2.167
Skew: 0.100 Prob(JB): 0.338
Kurtosis: 2.842 Cond. No. 2.69
==============================================================================

Warnings:

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

En el coeficiente de género. Sólo muestra masculino (T.male), porque la categoría femenina se


toma como la categoría predeterminada. (Tenga en cuenta que la elección de la categoría pre-
determinada no importa; fácilmente podríamos haber elegido hacer masculino como categoría
predeterminada y, por lo tanto, el coeficiente de género sería T.female). El coeficiente 8809 se in-
terpreta de la siguiente manera: En promedio, los hombres ganan 8809 USD más que las mujeres.

[46]: print(lm2.params)

Intercept 96357.235450
C(gender)[T.Male] 8809.650806
dtype: float64

[48]: print(lm2.aic)

18455.077458508338

[49]: print(lm2.bic)

18464.446681963673

[62]: print(lm2.rsquared*100)

3.082594426218521

1.7.1 Cambiar el nivel base de una variable


[95]: #Muestra las n categorias
train_dummy=pd.
,→get_dummies(train,columns=['gender'],prefix='',prefix_sep='',drop_first=False)

#Muestra las n-1 categorias y quita la base


#train_dummy=pd.
,→get_dummies(train,columns=['gender'],prefix='',prefix_sep='',drop_first=True)

[96]: train_dummy

[96]: jobtitle age performance education department \


807 Graphic Designer 43 2 PhD Management
871 Financial Analyst 48 1 College Engineering
646 Data Scientist 39 3 Masters Operations
178 Driver 42 4 Masters Management
643 Financial Analyst 36 3 PhD Operations
.. ... ... ... ... ...
793 Manager 30 1 High School Sales
783 Financial Analyst 27 2 PhD Administration

32
5 IT 20 5 PhD Operations
165 Software Engineer 49 5 Masters Administration
42 Software Engineer 31 5 PhD Sales

seniority income bonus pay Female Male


807 2 82057 5268 87325 0 1
871 5 119058 4037 123095 1 0
646 3 91711 6775 98486 1 0
178 4 106788 8974 115762 1 0
643 1 93067 5103 98170 0 1
.. ... ... ... ... ... ...
793 5 138163 3415 141578 0 1
783 4 99134 6255 105389 0 1
5 4 70890 10126 81016 1 0
165 5 142105 9653 151758 0 1
42 2 88724 8949 97673 0 1

[800 rows x 11 columns]

[102]: # nuevo modelo one-hot encoding


model3 = 'pay~Male'
lm3 = smf.ols(formula = model3, data = train_dummy).fit()
lm3.summary()

[102]: <class 'statsmodels.iolib.summary.Summary'>


"""
OLS Regression Results
==============================================================================
Dep. Variable: pay R-squared: 0.031
Model: OLS Adj. R-squared: 0.030
Method: Least Squares F-statistic: 25.38
Date: Wed, 17 Mar 2021 Prob (F-statistic): 5.82e-07
Time: 19:02:53 Log-Likelihood: -9225.5
No. Observations: 800 AIC: 1.846e+04
Df Residuals: 798 BIC: 1.846e+04
Df Model: 1
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
Intercept 9.636e+04 1270.022 75.871 0.000 9.39e+04 9.89e+04
Male 8809.6508 1748.638 5.038 0.000 5377.177 1.22e+04
==============================================================================
Omnibus: 2.118 Durbin-Watson: 1.947
Prob(Omnibus): 0.347 Jarque-Bera (JB): 2.167
Skew: 0.100 Prob(JB): 0.338
Kurtosis: 2.842 Cond. No. 2.69

33
==============================================================================

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

[103]: # nuevo modelo one-hot encoding


model3 = 'pay~Female'
lm3 = smf.ols(formula = model3, data = train_dummy).fit()
lm3.summary()

[103]: <class 'statsmodels.iolib.summary.Summary'>


"""
OLS Regression Results
==============================================================================
Dep. Variable: pay R-squared: 0.031
Model: OLS Adj. R-squared: 0.030
Method: Least Squares F-statistic: 25.38
Date: Wed, 17 Mar 2021 Prob (F-statistic): 5.82e-07
Time: 19:03:08 Log-Likelihood: -9225.5
No. Observations: 800 AIC: 1.846e+04
Df Residuals: 798 BIC: 1.846e+04
Df Model: 1
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
Intercept 1.052e+05 1201.990 87.494 0.000 1.03e+05 1.08e+05
Female -8809.6508 1748.638 -5.038 0.000 -1.22e+04 -5377.177
==============================================================================
Omnibus: 2.118 Durbin-Watson: 1.947
Prob(Omnibus): 0.347 Jarque-Bera (JB): 2.167
Skew: 0.100 Prob(JB): 0.338
Kurtosis: 2.842 Cond. No. 2.56
==============================================================================

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

1.7.2 Ejercicio 1
Construya un modelo lineal simple donde explique el ingreso total de acuerdo al nivel de Edu-
cación

34
1.7.3 Ejercicio 2
Construya un modelo lineal simple donde explique el ingreso total de acuerdo al título del em-
pleado

1.7.4 Ejercicio 3
Construya un modelo lineal simple donde explique el ingreso total de acuerdo a la antiguedad del
empleado

1.7.5 Ejercicio 4
Compare todos los R-Cuadrados de los modelos previos. Que puede concluir?

1.7.6 RESIDUALES
[111]: #Retomando el modelo de ingresos totales vs la edad en la prueba de entrenamiento
lm1.summary()

[111]: <class 'statsmodels.iolib.summary.Summary'>


"""
OLS Regression Results
==============================================================================
Dep. Variable: y R-squared: 0.300
Model: OLS Adj. R-squared: 0.299
Method: Least Squares F-statistic: 341.8
Date: Wed, 17 Mar 2021 Prob (F-statistic): 8.56e-64
Time: 19:36:56 Log-Likelihood: -9099.3
No. Observations: 800 AIC: 1.820e+04
Df Residuals: 798 BIC: 1.821e+04
Df Model: 1
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
const 6.056e+04 2286.541 26.485 0.000 5.61e+04 6.5e+04
x1 967.5689 52.334 18.488 0.000 864.840 1070.297
==============================================================================
Omnibus: 5.418 Durbin-Watson: 2.105
Prob(Omnibus): 0.067 Jarque-Bera (JB): 5.496
Skew: 0.190 Prob(JB): 0.0640
Kurtosis: 2.855 Cond. No. 134.
==============================================================================

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

35
2 Aleatorios
Lo que se busca es una distribución aleatoria de los residuales. Esto podría indicar que un modelo
lineal probablemente da un buen ajuste. Si se observa una tendencia, podría indicar presencia de
autocorrelación o heteroscedasticidad en el modelo.

[136]: residuales= lm1.resid


plt.scatter(lm1.predict(),residuales)

[136]: <matplotlib.collections.PathCollection at 0x1cf43b4eca0>

36
3 NORMALIDAD
3.0.1 Jarque Bera
El estadístico de prueba de Jarque-Bera prueba
H_0: Los residuales se distribuyen normalmente
H_1: Los resiudales siguen alguna otra distribución
El estadístico de prueba se basa en dos momentos de los datos, la asimetría y la curtosis, y tiene
una distribución asintótica de χ2 .
La estadística de prueba está definida
JB = n (S^2 / 6 + (K 3) 2/24)

donde n es el número de datos, S es la asimetría muestral y K es la curtosis muestral de los datos.

[152]: stats.jarque_bera(residuales)

[152]: Jarque_beraResult(statistic=5.496409503350214, pvalue=0.06404273067120858)

[153]: stats.normaltest(residuales)

[153]: NormaltestResult(statistic=5.41804374924622, pvalue=0.06660191989339681)

37
[180]: sm.graphics.qqplot(residuales, dist=stats.norm, line='45', fit=True)
[180]:

38
39
4 Heterocedasticidad

[145]: import statsmodels.stats.diagnostic as smd

40
Un test muy común para probar la presencia de heteroscedasticidad es la prueba de hipótesis
Breusch-Pagan, también podemos utilizar el test de White

[149]: # p value < alpha rechazo Ho, Ho: Los residuos son homocedasticos vs Ha: Los␣
,→residuos son heterocedasticos

smd.het_breuschpagan(residuales, lm1.model.exog)[1]
#en este caso no Rechazo la Hipótesis nula

[149]: 0.6289249851413743

[156]: smd.het_white(residuales, lm1.model.exog)[1]


#en este caso no Rechazo la Hipótesis nula

[156]: 0.8856432056843238

5 AUTOCORRELACIÓN
Otro de los supuestos detrás del modelo de regresión lineal es que los residuales no tienen autocor-
relación seriál. Una serie está autocorrelacionada cuando tiene correlación con su serie rezagada.
Lo evaluamos mediante el test Ljung-Box.

[163]: autocorr=smd.acorr_ljungbox(residuales, lags=10)

[165]: autocorr[1]

41
[165]: array([0.13274535, 0.32285689, 0.26125155, 0.28156729, 0.4090433 ,
0.53061748, 0.62949274, 0.42767392, 0.38785897, 0.17654873])

[167]: # si cualquier de los p valores asociados al vector de los 10 rezagos es menor␣


,→al nivel significancia rechazo Ho,

#es decir los residuales estan correlacionados


if any(autocorr[1]<0.05):
print("los residuales estan correlacionados")
else:
print("los residuales no estan correlacionados")

los residuales no estan correlacionados


Durbin Watson ( Se basa en un modelo temporal AR(1))

Esta prueba utiliza las siguientes hipótesis:


H0: No hay correlación entre los residuales.
H_1: Los residuales están autorelacionados.
La estadística de prueba es aproximadamente igual a 2*(1-r) donde r es la autocorrelación muestral
de los residuales. Por lo tanto, la estadística de prueba siempre estará entre 0 y 4 con la siguiente
interpretación:
1. Una estadística de prueba de 2 indica que no hay correlación serial.
2. Cuanto más cerca estén las estadísticas de prueba de 0,más evidencia de correlación serial
positiva.
3. Cuanto más cerca estén las estadísticas de prueba de 4,más evidencia de correlación serial
negativa.
Como regla general, los valores estadísticos de prueba entre el rango de 1,5 y 2,5 se consideran
normales. Sin embargo, los valores fuera de este intervalo podrían indicar que la autocorrelación
es un problema.

[170]: from statsmodels.stats.stattools import durbin_watson


#Durbin-Watson test
durbin_watson(residuales)

[170]: 2.104969794184385

5.1 Bibliografía:
Este notebook y la base de datos están basados en el curso Data Science for All DS4A - Correlation
One y el Ministerio de Tecnología MINTIC

42

También podría gustarte