Hasta ahora sólo hemos revisado el caso en el que \(y\) es una variable continua. Pero hay casos en los que nos interesa que la variable sea binaria: Por ejemplo, en calidad si un producto es rechazado o aceptado. En este caso aceptado = 1y rechazado = 0.
Para ilustrar el modelo que vamos a revisar, vamos a usar la libreria ISLR que contiene el dataset Smarket que se refiere al retorno diario del S&P500 entre 2001 y 2005. No olvides instalar la libreria con install.packages('ISLR')
require(ISLR)
require(tidyverse)
#Explorando los datos
head(Smarket)
La variable que nos interesa predecir es Direction que tiene valores Up cuando el retorno del dia es positivo y Down cuando el retorno es negativo.
Vamos a utilizar la herramienta que hemos visto hasta ahora y haremos una regresión lineal simple entre Direction ~ Lag1. Pero antes, tenemos que codificar la variable Direction. Para facilitar el manejo, vamos a codificar Up = 1 y Down = 0 (La variable Lag1 se refiere al rendimiento del día anterior)
#Codificación de la variable Direction
Smarket$Direction_coded = ifelse(Smarket$Direction == 'Up', 0, 1)
#Visualizacion de los datos
ggplot(Smarket, aes(x = Lag1, y = Direction_coded)) +
geom_point() +
geom_smooth(method = 'lm') +
labs(title = 'Regresion Lineal')
## `geom_smooth()` using formula 'y ~ x'
#Análisis de Regresión
m.lineal = lm(Direction_coded ~ Lag1, data = Smarket)
summary(m.lineal)
##
## Call:
## lm(formula = Direction_coded ~ Lag1, data = Smarket)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.5761 -0.4814 -0.4469 0.5161 0.6046
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 0.48153 0.01413 34.072 <2e-16 ***
## Lag1 0.01749 0.01244 1.406 0.16
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.4997 on 1248 degrees of freedom
## Multiple R-squared: 0.001581, Adjusted R-squared: 0.0007806
## F-statistic: 1.976 on 1 and 1248 DF, p-value: 0.1601
Después de revisar el resumen, vemos que la variable Lag1 es estadíticamente no significativa y que la \(R_{adj}^2 = 0.00\) lo que demuestra que este modelo quizás no es aecuado. Ahora vamos a revisar los residuales:
par(mfrow = c(2,2))
residuals = m.lineal$residuals
#Normalidad
plot(density(residuals), col = 'red', main = 'Desityplot de residuales') # density plot for 'speed'
polygon(density(residuals), col="red")
qqnorm(residuals)
qqline(residuals, col = 'red')
#Varianza Constante
plot(residuals, main = 'Constant Variance', type = 'l')
#Residuales independientes
acf(residuals)
Una imagen dice más que mil palabras!!! el modelo claramente viola los supuestos de normalidad y aunque la media del error es \(0\), la distribución parece ser bimodal. Esto es porque el modelo no está ayudando a clasificar correctamente si el mercado sube o baja. La mejor estimación es quedarse a la mitad del camino y decir: no se que va a suceder.
En estadística, la distribución binomial una distribución de probabilidad discreta que cuenta el número de éxitos en una secuencia \(n\) nensayos de Bernoulli independientes entre sí con una probabilidad fija \(p\) de ocurrencia de éxito entre los ensayos.
Un experimento de Bernoulli se caracteriza por ser dicotómico, esto es, solo dos resultados son posibles, a uno de estos se le denomina éxito y tiene una probabilidad de ocurrencia \(p\) y al otro se le denomina fracaso y tiene una probabilidad \(q = 1-p\).
En general, una variable aleatoria discreta \(X\) tiene una distribución binomial con parámetros \(n\) y \(0\leq p \leq 1\) y escribimos \(x\backsim\text{Binom}(n, p)\) con función de probabilidades:
\(f(x) = {n\choose x}p^x(1-p)^{n-x}\)
con la función rbino(n, x, p) podemos simular algunas distribuciones binomiales asumiendo que en 10 intentos queremos el 30%, 50% y 95% de éxito del evento de interés.
#1,000 numeros aleatorios con distribucion binomial con exito de 30%
x1 = rbinom(1000, 10, 0.3)
#1,000 numeros aleatorios con distribucion binomial con exito de 50%
x2 = rbinom(1000, 10, 0.5)
#1,000 numeros aleatorios con distribucion binomial con exito de 95%
x3 = rbinom(1000, 10, 0.95)
#hacer 3 histigramas en 1 panel
par(mfrow = c(1, 3))
hist(x1)
hist(x2)
hist(x3)
Una forma de modelar esto es determinar \(P(Direccion = Up|Lag1)\) (la probabilidad de que la dirección sea Updado Lag1). De una forma mas general, podemos escribirlo como \(P(Y = 1 | X)\)
Pariendo de la base de un modelo lineal:
\(log(\frac{p(x)}{1-p(x)}) = \beta_0 + \beta_1x\)
A la fracción \(\frac{p(x)}{1-p(x)}\) se le conoce como razón de momios o en inglés odds ratio.
Si elevamos todo al número \(e\):
\(\frac{p(x)}{1-p(x)} = e^{\beta_0 + \beta_1x}\)
Despejando \(p(x)\):
\(p(x) = \frac{e^{\beta_0 + \beta_1x}}{1+e^{\beta_0 + \beta_1x}}\)
En la regresión lineal vimos que podemos estimar los parámetros utilizando el método de mínimos cuadrados. sin embargo, en la regresión logística, no es posible llegar a una forma cerrada como lo hicimos en el caso de la regresión lineal.
La función de verosimilitud en el caso de la regresión logística es:
\(\ell(\beta_0,\beta_1) = \prod_{i=1}^np(x_i)^{y_{i}}(1-p(x_i))^{1-y_i}\)
Tomando logaritmo natural:
\(log(\ell(\beta_0,\beta_1)) = \sum_{i=1}^n[y_i(\beta_0+\beta_1x_i) - log(1+e^{\beta_0 + \beta_1x_i})]\)
Esta es la función que tenemos que optimizar para encontrar los parámetros \(\beta_0,\beta_1\)
Solo como demostración vamos a optimizar la función de maxima verosimilitud from the cratch!!
Vamos a usar el dataset Default en la libreria ISLR que contiene créditos vencidos:
head(Default)
Vamos a hacer la regresión default ~ balance. Este proceso requiere 2 pasos:
Computar la máxima verosimilitud dados x, y y un vector de parámetros
Optimizar lal función. Vamos a usar la librería optimx que pide par = vector de valores iniciales, fn = funcion a optimizar. Recordemos que queremos encontrar \(\beta_0, \beta_1\); es decir, 2 parámetros!
x2 = model.matrix(default ~ balance, data = Default)
y2 = ifelse(Default$default == 'No', 0, 1)
#beta_op = as.numeric(c(-3.09, -8.353e-06)) #Como referencia este es el valor optimo
logVerosimilitud = function(beta){
#beta = vector de parámetros
#x = matriz variable explicativa con la primer columna con 1's
#y = vector de respuestas entre 0 y 1
x = model.matrix(default ~ balance, data = Default)
y = ifelse(Default$default == 'No', 0, 1)
linearTransf = x %*% beta
parcial1 = y * linearTransf
parcial2 = log(1 + exp(linearTransf))
likelihood = -sum(parcial1 - parcial2)
return (likelihood)
}
require(optimx)
## Loading required package: optimx
beta_min = optimx(par = c(0, 0), fn = logVerosimilitud)
beta_min$p1[1]
## [1] -10.65206
beta_min$p2[1]
## [1] 0.005499188
En R no es necesario hacer optimizaciones para estimar los valores de la regresión. Podemos usar la función glm(formula, data, family = 'binomial') para estimar nuestra regresión logística:
m.logistica = glm(default ~ balance, data = Default, family = 'binomial')
summary(m.logistica)
##
## Call:
## glm(formula = default ~ balance, family = "binomial", data = Default)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.2697 -0.1465 -0.0589 -0.0221 3.7589
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -1.065e+01 3.612e-01 -29.49 <2e-16 ***
## balance 5.499e-03 2.204e-04 24.95 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 2920.6 on 9999 degrees of freedom
## Residual deviance: 1596.5 on 9998 degrees of freedom
## AIC: 1600.5
##
## Number of Fisher Scoring iterations: 8
Gráficamente, la solución se ve así:
Default$y_code = ifelse(Default$default == 'No', 0, 1)
ggplot(Default, aes(x = balance, y = y_code)) +
geom_smooth(method = 'glm', method.args = list(family = "binomial"))
## `geom_smooth()` using formula 'y ~ x'
La interpretación de los coeficientes de regresión en el caso de la Regresión Logística no es tan directa. Para poder obtener algo de significado, tenemos que calcular la razón de momios u odds ratio:
\(\text{odds} = \frac{p(y|x)}{1-p(y|x)} = e^{\beta_i}\)
En donde \(e\approx2.718\)
De esta forma, para la variable balance el odd ratio es:
odd_beta1 = exp(coef(m.logistica)[2])
print(paste("El odd ratio es: ", odd_beta1, sep=' '))
## [1] "El odd ratio es: 1.00551406372555"
La interpretación práctica es que por cada dolar de balance en deuda la probabilidad de pasar a cartera vencida se incrementa 1.005. Entonces, si tenemos 1,000 dolares de deuda, la probabilidad de pasar a cartera vencida es:
riesgo = exp(coef(m.logistica)[2] * 1000)
print(paste("Si tenemos $1000 usd en deuda, la probabilidad de pasar a cartera vencida se incrementa: ", riesgo, sep = ' '))
## [1] "Si tenemos $1000 usd en deuda, la probabilidad de pasar a cartera vencida se incrementa: 244.42705746094"
En este caso, no existe un valor de \(R^2\) como en la regresión lineal, pero sí podemos computar un equivalente: McFadden´s Pseudo R-squared
\(^2_{Mc} = 1 - \frac{ln \mathscr{L_c}}{ln \mathscr{L}_{null}}\)
En donde \(ln \mathscr{L_c}\) es el velor de la función de log-verosimilitud del modelo y \(ln \mathscr{L}_{null}\) es el valor de la función de log-verosimilitud del modelo nulo \(y_i = \beta_0\) (un modelo que sólo tiene la constante o intercepto):
#Computo del modelo nulo
m.null = glm(default ~ 1, data = Default, family = 'binomial')
#Computo de la pseudo r2
r2 = 1 -(logLik(m.logistica) / logLik(m.null))
print(paste("La pseudo-R2 es:", r2, sep = ' '))
## [1] "La pseudo-R2 es: 0.453391593901766"
Esto quiere decir que el modelo default ~ balance explica el 45% de la variación.
Ahora vamos a intentar predecir casos de Diabetes en mujeres en el dataset PimaIndiansDiabetes2 dentro de la libreria mlbench. Para ayudar a visualizar el modelo vamos a requerir las librerias visreg, margins, rcompanion
library(mlbench) # for PimaIndiansDiabetes2 dataset
library(visreg) # for potting logodds and probability
library(margins) # to calculate Average Marginal Effects
library(rcompanion) # to calculate pseudo R2
#Loa Data
data(PimaIndiansDiabetes2)
# See the data strcuture
glimpse(PimaIndiansDiabetes2, width = 50)
## Rows: 768
## Columns: 9
## $ pregnant <dbl> 6, 1, 8, 1, 0, 5, 3, 10, 2, 8, ~
## $ glucose <dbl> 148, 85, 183, 89, 137, 116, 78,~
## $ pressure <dbl> 72, 66, 64, 66, 40, 74, 50, NA,~
## $ triceps <dbl> 35, 29, NA, 23, 35, NA, 32, NA,~
## $ insulin <dbl> NA, NA, NA, 94, 168, NA, 88, NA~
## $ mass <dbl> 33.6, 26.6, 23.3, 28.1, 43.1, 2~
## $ pedigree <dbl> 0.627, 0.351, 0.672, 0.167, 2.2~
## $ age <dbl> 50, 31, 32, 21, 33, 30, 26, 29,~
## $ diabetes <fct> pos, neg, pos, neg, pos, neg, p~
Haciendo una inspección rápida con glimpse podemos ver que tenermos 768 casos o renglones y 9 columnas. La columna diabetes es nuestra variable de respuesta. Las variables explicativas son pregnant, glucose, pressure, triceps, insulin, pedigree, age. También podemos ver que hay iformación faltante o con NA.
Em R podemos eliminar los renglones que al menos tienen 1 valor de NA o faltante (missing data) con la función na.omit(). Ya que los datos no tienen NA´s, vamos a correr la regresión con glm():
#Eliminar los datos faltantes
data_full = na.omit(PimaIndiansDiabetes2)
#Correr el modelo
m.diabetes = glm(diabetes ~., data = data_full, family = 'binomial')
summary(m.diabetes)
##
## Call:
## glm(formula = diabetes ~ ., family = "binomial", data = data_full)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.7823 -0.6603 -0.3642 0.6409 2.5612
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -1.004e+01 1.218e+00 -8.246 < 2e-16 ***
## pregnant 8.216e-02 5.543e-02 1.482 0.13825
## glucose 3.827e-02 5.768e-03 6.635 3.24e-11 ***
## pressure -1.420e-03 1.183e-02 -0.120 0.90446
## triceps 1.122e-02 1.708e-02 0.657 0.51128
## insulin -8.253e-04 1.306e-03 -0.632 0.52757
## mass 7.054e-02 2.734e-02 2.580 0.00989 **
## pedigree 1.141e+00 4.274e-01 2.669 0.00760 **
## age 3.395e-02 1.838e-02 1.847 0.06474 .
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 498.10 on 391 degrees of freedom
## Residual deviance: 344.02 on 383 degrees of freedom
## AIC: 362.02
##
## Number of Fisher Scoring iterations: 5
Después de correr el modelo notamos que hay algunas variables que nos significativas por ejemplo preassure cuyo Pr(>z) = 0.904 que es mayor de 0.05. Por lo tanto, la variable no es estadíticamente significativa.
Antes de continuar, ahora es un buen momento para revisar el conceto de stepwise selection
La selección de vriables o Feature Selection se refiere al procedimiento para descartar variables no significtivas en los modelos. Lo que buscamos es Parsimonia es decir, tener un modelo lo más simple posible y que explique la mayor cantidad de variación.
Existen varios algoritmos para lograr parsimonia pero ahora vamos a revisar el algoritmo de Stepwise. El objetivo de este algoritmo es introducir o eliminar variables de tal forma que la menor cantidad de variables presenten un impacto significativo para el problema que estamos modelando.
Podemos correr el algoritmo con la función step(modelo)
m.diabetes2 = step(m.diabetes)
## Start: AIC=362.02
## diabetes ~ pregnant + glucose + pressure + triceps + insulin +
## mass + pedigree + age
##
## Df Deviance AIC
## - pressure 1 344.04 360.04
## - insulin 1 344.42 360.42
## - triceps 1 344.45 360.45
## <none> 344.02 362.02
## - pregnant 1 346.24 362.24
## - age 1 347.55 363.55
## - mass 1 350.89 366.89
## - pedigree 1 351.58 367.58
## - glucose 1 396.95 412.95
##
## Step: AIC=360.04
## diabetes ~ pregnant + glucose + triceps + insulin + mass + pedigree +
## age
##
## Df Deviance AIC
## - insulin 1 344.42 358.42
## - triceps 1 344.46 358.46
## <none> 344.04 360.04
## - pregnant 1 346.24 360.24
## - age 1 347.60 361.60
## - mass 1 351.28 365.28
## - pedigree 1 351.67 365.67
## - glucose 1 397.31 411.31
##
## Step: AIC=358.42
## diabetes ~ pregnant + glucose + triceps + mass + pedigree + age
##
## Df Deviance AIC
## - triceps 1 344.89 356.89
## <none> 344.42 358.42
## - pregnant 1 346.74 358.74
## - age 1 347.87 359.87
## - mass 1 351.32 363.32
## - pedigree 1 351.90 363.90
## - glucose 1 411.11 423.11
##
## Step: AIC=356.89
## diabetes ~ pregnant + glucose + mass + pedigree + age
##
## Df Deviance AIC
## <none> 344.89 356.89
## - pregnant 1 347.23 357.23
## - age 1 348.72 358.72
## - pedigree 1 352.72 362.72
## - mass 1 360.44 370.44
## - glucose 1 411.85 421.85
summary(m.diabetes2)
##
## Call:
## glm(formula = diabetes ~ pregnant + glucose + mass + pedigree +
## age, family = "binomial", data = data_full)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.8827 -0.6535 -0.3694 0.6521 2.5814
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -9.992080 1.086866 -9.193 < 2e-16 ***
## pregnant 0.083953 0.055031 1.526 0.127117
## glucose 0.036458 0.004978 7.324 2.41e-13 ***
## mass 0.078139 0.020605 3.792 0.000149 ***
## pedigree 1.150913 0.424242 2.713 0.006670 **
## age 0.034360 0.017810 1.929 0.053692 .
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 498.10 on 391 degrees of freedom
## Residual deviance: 344.89 on 386 degrees of freedom
## AIC: 356.89
##
## Number of Fisher Scoring iterations: 5
Ahora tenemos un modelo con 5 variables vs 8 variables del modelo anterior. Si calculamos la \(R^2\) tenemos:
#Computo del modelo nulo
m.null = glm(diabetes ~ 1, data = data_full, family = 'binomial')
#Computo de la pseudo r2
r2_m1 = 1 -(logLik(m.diabetes) / logLik(m.null))
r2_m2 = 1 -(logLik(m.diabetes2) / logLik(m.null))
print(paste("La pseudo-R2 del modelo de 8 var es:", r2_m1, sep = ' '))
## [1] "La pseudo-R2 del modelo de 8 var es: 0.309329958576541"
print(paste("La pseudo-R2 del modelo de 5 var es:", r2_m2, sep = ' '))
## [1] "La pseudo-R2 del modelo de 5 var es: 0.307595560186559"
Y lo que observamos es que nuestro 2do modelo es mas simple (tiene menos variables) y tiene prácticamente el mismo desempeño.
En teoría de probabilidad y estadística, la distribución de Poisson es una distribución de probabilidad discreta que expresa, a partir de una frecuencia de ocurrencia media, la probabilidad de que ocurra un determinado número de eventos durante cierto período de tiempo.
Fue propuesta por Siméon-Denis Poisson, que la dio a conocer en 1838 en su trabajo Recherches sur la probabilité des jugements en matières criminelles et matière civile (Investigación sobre la probabilidad de los juicios en materias criminales y civiles).
Sea \(\lambda > 0\) un parámetro y \(X\) una variable aleatoria discreta, se dice que la variable aleatoria \(X\) tiene una distribución de Poisson con parámetro \(\lambda\) y escribimos \(X \sim \text{Poisson}(\lambda)\) con función de probabilidades:
\(p(k) = P[X=k] = \frac{e^{-\lambda}\lambda^k}{k!}\)
en donde \(k=0,1,2,..,n\) es el número de ocurrencias de un evento. Esta distribución tiene la eculariedad de:
\(\mu = \lambda\)
\(\sigma^2 = \lambda\)
https://es.wikipedia.org/wiki/Distribuci%C3%B3n_de_Poisson
Supongamos que una enfermedad se propaga una tasa de 2.3 casos por dia. Si asumimos que el número de casos por dia sigue una distribución de Poisson entonces, la distribución de probabilidad es:
lambda = 2.3
x = rpois(1000, lambda)
hist(x)
Qué pasa si asumimos que la distribución de casos por día siguiera una distibución normal?
#simulamos 1000 datos con distribución normal
normal = rnorm(1000, lambda, lambda)
#Ponemos todo en un dataframe
hist(normal)
abline(v = 0, col = 'red', lty = 2)
Lo que vemos, es que asumiendo de manera equivocada este comportamiento, tendríamos una probabilidad de generar eventos negativos, cuando sabemos que no podemos tener casos diarios negativos. En este caso la distribución de Poisson es un supuesto más adecuado.
Si se cumple que \(y_i \sim \text{Poisson}(\lambda)\) entonces:
\(\hat{y_i} = \text{exp}(\sum_{j=1}^p \beta_jx_{ij})\)
Es común hacer \(log(y_i)\), entonces:
\(\text{log}(y_i) = \sum_{j=1}^p \beta_jx_{ij} + \epsilon_i\)
Con el cual podemos usar mínimos cuadrados para resolver y encontrar los valores de \(\beta_j\).
Vamos a retomar los datos del precio de diamantes de diamonds y vamos a generar una regresión de poisson de price~carat:
m.poisson = glm(price ~ carat, data = diamonds, family = 'poisson')
summary(m.poisson)
##
## Call:
## glm(formula = price ~ carat, family = "poisson", data = diamonds)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -1200.42 -22.44 -8.66 8.53 162.42
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 6.965e+00 1.379e-04 50513 <2e-16 ***
## carat 1.324e+00 9.636e-05 13745 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for poisson family taken to be 1)
##
## Null deviance: 183127012 on 53939 degrees of freedom
## Residual deviance: 42213813 on 53938 degrees of freedom
## AIC: 42732976
##
## Number of Fisher Scoring iterations: 5
Computamos la medida de desempeño \(R^2\)
#El modelo nulo es:
m.null = glm(price ~ 1, data = diamonds, family = 'poisson')
#Computo de R2
LogLik.model = logLik(m.poisson)
LogLik.null = logLik(m.null)
r2_m1 = 1 -(LogLik.model / LogLik.null)
r2_m1
## 'log Lik.' 0.7673081 (df=2)
Usando la libreria visreg para visualizar:
visreg(m.poisson, 'carat', scale = 'response', rug = 2)