Que se mueran los pobres - Un modelo de regresión logística para predecir la supervivencia en el Titanic

RMS Titanic 3.jpg              En este post, vamos a crear un modelo de regresión logística para calcular la probabilidad de supervivencia en el Titanic.

Las explicaciones y el código en R están debajo, pero me gustaría comentar algunas cosas previamente.

 

SOBRE LA IMPUTACIÓN DE DATOS FALTANTES

Una de las principales dificultades del modelo es la cantidad de datos con NA que tenemos. La imputación, o eliminación, de datos faltantes es una de las tareas más importantes en el pre-procesamiento en Machine Learning.

En este caso, la cantidad de veces que aparece Age = NA es demasiado grande para borrarlos. Lo que tenemos que hacer es imputarlos, que puede hacerse de varias formas. La más sencilla es imputalos a la media (datos no sesgados) o a la mediana (datos sesgados). Y una forma más compleja, pero que puede dar mejores resultados es usar regresión logística en otras variables para estimar su valor. Esta última, en una versión sencilla, es la que haremos.

 

¿CÓMO SE ELIGEN LAS VARIABLES?

De las muchas formas que puede modelizarse el problema, hay que ir eligiendo sobre la marcha qué variables queremos incluir y cuáles excluiremos. Dado que estamos interesados en un modelo predictivo, deberemos tener cuidado de no suprimir variables que resten capacidad de explicar los resultados. 

Pese a todo, no toda la información del modelo es útil. El número de billete, por ejemplo, no nos ofrece nada interesante. Como mucho, el lugar donde fue expedido y el precio, pero esta información está contenida en las variables Embark y Pclass respectivamente. De hecho, más adelante veremos que Fare, el precio del billete, no ofrece nada relevante.

 

LA VALIDACIÓN DEL MODELO

Para poder realizar una predicción con seguridad, deberemos previamente adoptar un protocolo para la validación de los datos. El elegido en este problema se llama hold-out, y consiste en reservar una parte de los datos para entrenar el modelo y otra para validarlos. 

Seguiremos esta misma idea tanto en la imputación de Age, para todos esos NA, como en el cálculo de las predicciones para la probabilidad de supervivencia.

 

EVALUANDO LAS PREDICCIONES

De las formas que hay para evaluar las predicciones hemos elegido la basada en la matriz de confusión. Al final del modelo, calcularemos la matriz de predicción (falsos positivos, verdaderos positivos, falsos negativos y verdaderos negativos) sobre los datos de test y con ello mediremos la capacidad predictiva esperada de nuestro modelo.

Con ayuda de la curva ROC, podremos configurar un valor de corte, que en nuestro caso resultará ser p=0.5, para la probabilidad de que alguien viva o muera. Si p>0.5 determinaremos que esa persona muere y si es menor o igual que sobrevive.

 

LOS RESULTADOS

El sentido común, la historia y la triste lógica de la vida llevan a pensar que el modelo lo único que hace es confirmar lo obvio: los pobres mueren a una tasa mucho mayor que los ricos. Los hombres mueren más que las mujeres.

 Datos del problema 

 

TITANIC

CARGA DE LIBRERÍAS y DATOS

Cargamos las librerias y los datos de entrenamiento

library(corrplot)
## corrplot 0.92 loaded
library(caret)
## Loading required package: ggplot2
## Loading required package: lattice
library(e1071) #normalidad
library(nortest) #coef.asimetría de Fisher
library(dplyr) # manipulacion de datos
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(broom) # resumenes de los modelos
library(visreg) # graficos para regresion logistica
library(margins) # efecto marginal medio
library(rcompanion) # "coeficiente de determinacion"
library(ROCR) # curvas ROC
## 
## Attaching package: 'ROCR'
## The following object is masked from 'package:margins':
## 
##     prediction
library(caTools)

train <- read.csv("train.csv")
test  <- read.csv("test.csv")

head(train)
##   PassengerId Survived Pclass
## 1           1        0      3
## 2           2        1      1
## 3           3        1      3
## 4           4        1      1
## 5           5        0      3
## 6           6        0      3
##                                                  Name Sex Age SibSp Parch
## 1                             Braund, Mr. Owen Harris   1  22     1     0
## 2 Cumings, Mrs. John Bradley (Florence Briggs Thayer)   0  38     1     0
## 3                              Heikkinen, Miss. Laina   0  26     0     0
## 4        Futrelle, Mrs. Jacques Heath (Lily May Peel)   0  35     1     0
## 5                            Allen, Mr. William Henry   1  35     0     0
## 6                                    Moran, Mr. James   1  NA     0     0
##             Ticket    Fare Cabin Embarked
## 1        A/5 21171  7.2500              S
## 2         PC 17599 71.2833   C85        C
## 3 STON/O2. 3101282  7.9250              S
## 4           113803 53.1000  C123        S
## 5           373450  8.0500              S
## 6           330877  8.4583              Q
head(test)
##   PassengerId Pclass                                         Name    Sex  Age
## 1         892      3                             Kelly, Mr. James   male 34.5
## 2         893      3             Wilkes, Mrs. James (Ellen Needs) female 47.0
## 3         894      2                    Myles, Mr. Thomas Francis   male 62.0
## 4         895      3                             Wirz, Mr. Albert   male 27.0
## 5         896      3 Hirvonen, Mrs. Alexander (Helga E Lindqvist) female 22.0
## 6         897      3                   Svensson, Mr. Johan Cervin   male 14.0
##   SibSp Parch  Ticket    Fare Cabin Embarked
## 1     0     0  330911  7.8292              Q
## 2     1     0  363272  7.0000              S
## 3     0     0  240276  9.6875              Q
## 4     0     0  315154  8.6625              S
## 5     1     1 3101298 12.2875              S
## 6     0     0    7538  9.2250              S

PRIMER VISTAZO A LOS DATOS

Significado de cada una de las variables

Vamos a hacernos una idea de lo que contienen las tablas. Esta es una breve explicacion de cada variable:

  • PassengerId is a unique identifying number assigned to each passenger.

  • Survived is a flag that indicates if a passenger survived or died ( i.e., 0 = No, 1 = Yes).

  • Pclass is the passenger class (i.e., 1 = 1st class, 2 = 2nd class, 3 = 3rd class).

  • Name is the name of the passenger.

  • Sex indicates the gender of the passenger (i.e., Male or female).

  • Age indicates the age of the passenger.

  • Sibsp is the number of siblings/spouses aboard.

  • Parch is the number of parents/children aboard.

  • Ticket indicates the ticket number issued to the passenger.

  • Fare indicates the amount of money spent on their ticket.

  • Cabin indicates the cabin category occupied by the passenger.

  • Embarked indicates the port where the passenger embarked from (i.e., C = Cherbourg, Q = Queenstown, S = Southampton)

Survived es la variable que queremos predecir.

Exámen preliminar de la información en la tabla

Lo primero es hacer factors todas las variables categoricas. De otra forma, R se empeña en sacarnos medias y medianas y no tiene sentido (así nos mostrará porcentajes y se entiende mejor)

train$Survived<-as.factor(train$Survived)
train$Pclass<-as.factor(train$Pclass)
train$Sex<-as.factor(train$Sex)
train$SibSp<-as.factor(train$SibSp)
train$Parch<-as.factor(train$Parch)
train$Cabin<-as.factor(train$Cabin)
train$Embarked<-as.factor(train$Embarked)

#test$<-as.factor(test$Survived) #esta no existe
test$Pclass<-as.factor(test$Pclass)
#test$Sex<-as.factor(test$Sex) #esto hay que hacerlo mas adelante
test$SibSp<-as.factor(test$SibSp)
test$Parch<-as.factor(test$Parch)
test$Cabin<-as.factor(test$Cabin)
test$Embarked<-as.factor(test$Embarked)

Ahora sí, si hacemos summary nos saldrá algo comprensible:

summary(train)
##   PassengerId    Survived Pclass      Name           Sex          Age       
##  Min.   :  1.0   0:549    1:216   Length:891         0:314   Min.   : 0.42  
##  1st Qu.:223.5   1:342    2:184   Class :character   1:577   1st Qu.:20.12  
##  Median :446.0            3:491   Mode  :character           Median :28.00  
##  Mean   :446.0                                               Mean   :29.70  
##  3rd Qu.:668.5                                               3rd Qu.:38.00  
##  Max.   :891.0                                               Max.   :80.00  
##                                                              NA's   :177    
##  SibSp   Parch      Ticket               Fare                Cabin     Embarked
##  0:608   0:678   Length:891         Min.   :  0.00              :687    :  2   
##  1:209   1:118   Class :character   1st Qu.:  7.91   B96 B98    :  4   C:168   
##  2: 28   2: 80   Mode  :character   Median : 14.45   C23 C25 C27:  4   Q: 77   
##  3: 16   3:  5                      Mean   : 32.20   G6         :  4   S:644   
##  4: 18   4:  4                      3rd Qu.: 31.00   C22 C26    :  3           
##  5:  5   5:  5                      Max.   :512.33   D          :  3           
##  8:  7   6:  1                                       (Other)    :186
summary(test)
##   PassengerId     Pclass      Name               Sex                 Age       
##  Min.   : 892.0   1:107   Length:418         Length:418         Min.   : 0.17  
##  1st Qu.: 996.2   2: 93   Class :character   Class :character   1st Qu.:21.00  
##  Median :1100.5   3:218   Mode  :character   Mode  :character   Median :27.00  
##  Mean   :1100.5                                                 Mean   :30.27  
##  3rd Qu.:1204.8                                                 3rd Qu.:39.00  
##  Max.   :1309.0                                                 Max.   :76.00  
##                                                                 NA's   :86     
##  SibSp       Parch        Ticket               Fare        
##  0:283   0      :324   Length:418         Min.   :  0.000  
##  1:110   1      : 52   Class :character   1st Qu.:  7.896  
##  2: 14   2      : 33   Mode  :character   Median : 14.454  
##  3:  4   3      :  3                      Mean   : 35.627  
##  4:  4   4      :  2                      3rd Qu.: 31.500  
##  5:  1   9      :  2                      Max.   :512.329  
##  8:  2   (Other):  2                      NA's   :1        
##              Cabin     Embarked
##                 :327   C:102   
##  B57 B59 B63 B66:  3   Q: 46   
##  A34            :  2   S:270   
##  B45            :  2           
##  C101           :  2           
##  C116           :  2           
##  (Other)        : 80

Vamos a tener que hacer limpieza de los datos. A simple vista tenemos: - 177 valores de age con NA - 2 valores de embark en blancos - Casi todos los valores de Cabin sin informar - Un blanco en Fare, aparte de los ceros que comentábamos antes.

Afortunadamente, no tenemos que preocuparnos de los outliers. También vemos que sexo en una de las tablas está como 0,1 y en la otra como female, male. Esto habrá que modificarlo.

PRIMERAS CORRECCIONES A LOS DATOS

Male y female en la tabla de test

Como comentábamos antes, Sex toma valores female,male en lugar de 0,1. Tenemos que convertirlos adecuadamente. Male=1, Female=0.

for(i in 1:nrow(test))
    {
    if (test$Sex[i]=='male') test$Sex[i]<-1  
    if (test$Sex[i]=='female') test$Sex[i]<-0  
}

#ahora si lo convertimos a variable categorica. Si lo hacemos antes da error aqui.
test$Sex<-as.factor(test$Sex)

Creación de una tabla conjunta para las imputaciones de Age, Fare,…

#creamos la tabla conjunta con toda la info, salvo la columna de supervivencia
#es necesaria para las imputaciones

AGE <- rbind(test, train[, names(test)])

Corrigiendo las variables numericas

También salta a la vista que el tipo de Age es double pero debería ser un entero. Esto no lo vamos a tocar porque más adelante, al imputarle valores, aumentaríamos el error por redondeo, dado que los valores que calculemos serán doubles por usar un pequeño modelo de regresión logística para esto.

Imputando valores a embarked

En embarked tenemos dos valores sin definir - cuidado, no son NA, son blancos. Ambos tienen los mismos valores de cabin, survived y ticked.

which(train$Embarked=="")
## [1]  62 830

Los pasajeros con numero de ticket parecido a ellos, en su mayoria, embarcaron en S, que es donde los vamos a poner y que coincide con la moda de la variable embarked. Por lo tanto es un valor más que razonable

train$Embarked[62]<-"S"
train$Embarked[830]<-"S"
summary(train$Embarked)    #comprobamos que este bien.
##       C   Q   S 
##   0 168  77 646

Analogamente en la tabla AGE las mismas imputaciones (con cuidado, porque el numero de fila en la tabla AGE no tiene porque ser el mismo).

which(AGE$Embarked=="")
## [1]  480 1248
AGE$Embarked[480 ]<-"S"
AGE$Embarked[1248]<-"S"

Imputando valores a FARE

La columna Fare, como puede verse a continuación, contiene valores que son cero. Bien puede ser gente que tenga el ticket gratis por algún motivo o un error en las notas. Como no lo sabemos, y ambas cosas tienen sentido, los dejaremos tal y como están. Si bien ponerlas a la mediana tampoco sería aceptable.

summary(train$Fare)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    0.00    7.91   14.45   32.20   31.00  512.33

Muchas de las decisiones que tomemos en la imputación serán parecidas a esto: varias opciones igualmente razonables para elegir. Siempre nos movemos en el terreno de la ambigüedad.

Hay un unico valor de fare a NA en toda la tabla, concretamente este, que aparece tambien en la tabla test:

which(is.na(AGE$Fare))
## [1] 153

Realmente, un valor entre tantos no es una preocupación, y más teniendo en cuenta que Fare no va a ser determinante en los resultados. Como FARE es una variable claramente sesgada lo imputaremos a su mediana

#fare es sesgada a derecha (asimetría positiva)
hist(AGE$Fare)

#imputación a la mediana
AGE$Fare[153]<-median(AGE$Fare[-153])
AGE$Fare[153]
## [1] 14.4542
#no olvidemos actualizar todas las tablas
which(is.na(train$Fare))
## integer(0)
test$Fare[153]<-AGE$Fare[153] 

IMPUTANDO VALORES A AGE

Para imputar valores necesitaremos sacar informacion de los que ya estan informados. Tenemos principalmente tres opciones para imputar Age:

  • Imputar a la media o la mediana, según los datos sean o no sesgados.
  • Usar un modelo de regresión logística para extraer age del resto de las variables e imputar usando este modelo.
  • Ver con qué variables varía age, dividir en grupos e imputar a la media o mediana de cada uno de esos grupos.

La primera alternativa es la más sencilla pero la segunda, al margen de su complejidad, es la que mejores resultados, al menos teoricamente, debería ofrecer. La tercera es un punto intermedio.

AGE_YA_IMPUTADOS<-subset(AGE,is.na(AGE$Age)==FALSE)
AGE_POR_IMPUTAR<-subset(AGE,is.na(AGE$Age)==TRUE)

Tenemos 263 valores de Age para imputar, más o menos el 20% del total. Una cantidad más que considerable. Notar que sería un error gravísimo borrar las filas que no tienen Age informado, dado que perderíamos mucha información.

Imputación de Age por la mediana

La variable age es sesgada a derecha, por lo que, como primera aproximacion, podríamos imputar a la mediana si no encontramos nada mejor. Si lo hiciéramos a la media, estaríamos cometiendo un error.

skewness(AGE_YA_IMPUTADOS$Age) #coef. de simetria de fisher
## [1] 0.4065061
mean(AGE_YA_IMPUTADOS$Age) #media
## [1] 29.88114
median(AGE_YA_IMPUTADOS$Age) #mediana = 28
## [1] 28
hist(AGE_YA_IMPUTADOS$Age)

Pero vamos a intentar obtener algo más preciso, dado que tenemos un 20% de edades con NA. Veamos con qué variable categórica varía age.

Imputando Age mediante regresión logística

¿Qué variables incluímos en la imputación de Age?

Con un boxplot por pares, podemos ver que cualquiera de SibSp, Pclass y Parch seguramente sean buenos predictores. Fare no lo será, dado que tiene una correlación muy pequeña con age. Puede verse cómo va variando por grupo:

cor(AGE_YA_IMPUTADOS$Age, AGE_YA_IMPUTADOS$Fare) #correlación AGE, FARE
## [1] 0.1775283
boxplot(AGE_YA_IMPUTADOS$Age~AGE_YA_IMPUTADOS$SibSp)

boxplot(AGE_YA_IMPUTADOS$Age~AGE_YA_IMPUTADOS$Pclass)

boxplot(AGE_YA_IMPUTADOS$Age~AGE_YA_IMPUTADOS$Parch)

Y de aquí que construyamos este modelo para predecir la edad. De todas ellas, la que mejor parece funcionar es Pclass, que es la que usaremos.

Datos de test y de validación para Age

Vamos dividir los datos de los que conocemos la edad en dos partes. Una de ellas para ajustar el modelo y otra para validarlo.

#make this example reproducible
set.seed(123)

#usamos el 70% de los datos para crear el modelo y el 30% para su validación.
sample <- sample.split(AGE_YA_IMPUTADOS$Age, SplitRatio = 0.7)
AGEtrain  <- subset(AGE_YA_IMPUTADOS, sample == TRUE)
AGEtest   <- subset(AGE_YA_IMPUTADOS, sample == FALSE)

Y ahora sí podemos calcular el modelo con los datos de entrenamiento para age. Usaremos un modelo de regresión logística para ajustar las predicciones.

Regresión logísica para imputar Age

modelo_age<-glm(Age~Pclass,data=AGEtrain)
summary(modelo_age)
## 
## Call:
## glm(formula = Age ~ Pclass, data = AGEtrain)
## 
## Deviance Residuals: 
##    Min      1Q  Median      3Q     Max  
## -37.12   -8.06   -1.06    7.94   48.94  
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  39.1224     0.9699  40.337  < 2e-16 ***
## Pclass2      -8.9921     1.3752  -6.539 1.16e-10 ***
## Pclass3     -14.0622     1.2033 -11.686  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for gaussian family taken to be 180.6119)
## 
##     Null deviance: 157417  on 737  degrees of freedom
## Residual deviance: 132750  on 735  degrees of freedom
## AIC: 5934.3
## 
## Number of Fisher Scoring iterations: 2

Validando el modelo

Ahora sí tenemos un buen modelo para predecir Age. Veamos su grado de acierto sobre el conjunto de datos de test:

predictions<-predict(modelo_age,newdata=AGEtrain,type="response")

#calculamos la raiz del error cuadratico medio de las predicciones
predictions<-as.double(predictions)
mean(predictions-AGEtrain$Age)
## [1] 7.675411e-14
sd(predictions-AGEtrain$Age)
## [1] 13.42094
summary(predictions)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   25.06   25.06   30.13   30.02   39.12   39.12

Sin ser excepcional, el resultado no es malo y desde luego será mejor que imputar a la mediana y olvidarnos del asunto. Ya podemos calcular la edad para los datos faltantes.

PREDICTED_AGES<-predict(modelo_age,newdata=AGE_POR_IMPUTAR,type="response")
summary(PREDICTED_AGES)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   25.06   25.06   25.06   27.45   25.06   39.12

Cargando la variable Age calculada

Y metemos los datos

for(i in 1:nrow(AGE_POR_IMPUTAR)) AGE_POR_IMPUTAR$Age[i]<-PREDICTED_AGES[i]
head(AGE_POR_IMPUTAR$Age)
## [1] 25.06017 39.12240 25.06017 25.06017 25.06017 25.06017

La imputación de la edad ha funcionado estupendamente. Volcamos los datos en las tablas del problema original y podemos pasar a la regresión logística para la supervivencia.

No vamos a redondear las edades imputadas, ya que perderíamos información.

for (i in 1:nrow(AGE_POR_IMPUTAR)) 
{
    fila <- AGE_POR_IMPUTAR$PassengerId[i]; 
    
    if(fila<=891) 
            train$Age[fila]<-AGE_POR_IMPUTAR$Age[i] 
      else 
            test$Age[fila-891]<-AGE_POR_IMPUTAR$Age[i]
      }

Ahora ya tanto la tabla train como test tienen age imputado.

summary(train$Age)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    0.42   22.00   26.00   29.31   37.00   80.00
summary(test$Age)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    0.17   23.00   25.06   29.56   36.38   76.00

El modelo de regresión logística

¿Que tipo de resultados esperamos?

De un simple vistazo a la tabla vemos que la probabilidad de sobrevivir depende claramente del precio del billete que compres:

TABLA<-table(train$Survived,train$Pclass)
TABLA
##    
##       1   2   3
##   0  80  97 372
##   1 136  87 119

El porcentaje de supervivientes de primera clase es mucho mayor que el de tercera. Y esto no puede ser casualidad. Vamos a confirmar nuestra intuición con un test de independencia chi-cuadrado:

chisq.test(TABLA)
## 
##  Pearson's Chi-squared test
## 
## data:  TABLA
## X-squared = 102.89, df = 2, p-value < 2.2e-16

Por lo tanto está claro que deberemos incluir el precio del billete en nuestro modelo predictivo. Los pasajeros de primera clase sobrevivieron mucho más que los de tercera.

Variables que NO deberíamos incluir

No todas las variables son relevantes en el modelo. El hecho de incluirlas, puede resultar en p-valores pequeños y un aumento artificial del ajuste del modelo, que llevará a malos resultados.

No vamos a incluir cabin, porque tiene muy pocos valores informados, y tampoco el nombre - por no tener ninguna relación con lo demás.

El modelo de regresión logística para la supervivencia

modelo_Survived <- glm(Survived ~ Pclass + Sex + Age + SibSp + Parch + Fare  +  Embarked, data=train, family="binomial")
summary(modelo_Survived)
## 
## Call:
## glm(formula = Survived ~ Pclass + Sex + Age + SibSp + Parch + 
##     Fare + Embarked, family = "binomial", data = train)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -2.8248  -0.6164  -0.4157   0.5810   2.4880  
## 
## Coefficients:
##               Estimate Std. Error z value Pr(>|z|)    
## (Intercept)  3.920e+00  5.233e-01   7.491 6.84e-14 ***
## Pclass2     -1.105e+00  3.103e-01  -3.561 0.000369 ***
## Pclass3     -2.213e+00  3.173e-01  -6.974 3.07e-12 ***
## Sex1        -2.687e+00  2.038e-01 -13.182  < 2e-16 ***
## Age         -4.003e-02  8.765e-03  -4.566 4.96e-06 ***
## SibSp1       7.171e-02  2.247e-01   0.319 0.749659    
## SibSp2      -3.008e-01  5.375e-01  -0.560 0.575752    
## SibSp3      -2.248e+00  7.216e-01  -3.115 0.001839 ** 
## SibSp4      -1.687e+00  7.624e-01  -2.213 0.026894 *  
## SibSp5      -1.595e+01  9.584e+02  -0.017 0.986720    
## SibSp8      -1.604e+01  7.573e+02  -0.021 0.983105    
## Parch1       3.650e-01  2.901e-01   1.258 0.208361    
## Parch2       2.517e-02  3.830e-01   0.066 0.947602    
## Parch3       3.603e-01  1.057e+00   0.341 0.733099    
## Parch4      -1.587e+01  1.054e+03  -0.015 0.987987    
## Parch5      -1.166e+00  1.172e+00  -0.995 0.319750    
## Parch6      -1.644e+01  2.400e+03  -0.007 0.994534    
## Fare         2.101e-03  2.499e-03   0.841 0.400411    
## EmbarkedQ    3.122e-02  3.869e-01   0.081 0.935673    
## EmbarkedS   -2.845e-01  2.450e-01  -1.161 0.245507    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 1186.66  on 890  degrees of freedom
## Residual deviance:  763.58  on 871  degrees of freedom
## AIC: 803.58
## 
## Number of Fisher Scoring iterations: 15

Vamos poco a poco. Primero quitaremos embarked

modelo_Survived <- glm(Survived ~ Pclass + Sex + Age + SibSp + Parch + Fare, data=train, family="binomial")
summary(modelo_Survived)
## 
## Call:
## glm(formula = Survived ~ Pclass + Sex + Age + SibSp + Parch + 
##     Fare, family = "binomial", data = train)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -2.8825  -0.6188  -0.4193   0.5956   2.4577  
## 
## Coefficients:
##               Estimate Std. Error z value Pr(>|z|)    
## (Intercept)  3.772e+00  4.957e-01   7.611 2.73e-14 ***
## Pclass2     -1.173e+00  3.060e-01  -3.833 0.000127 ***
## Pclass3     -2.209e+00  3.116e-01  -7.090 1.35e-12 ***
## Sex1        -2.720e+00  2.015e-01 -13.499  < 2e-16 ***
## Age         -4.077e-02  8.767e-03  -4.650 3.32e-06 ***
## SibSp1       6.213e-02  2.246e-01   0.277 0.782068    
## SibSp2      -3.141e-01  5.362e-01  -0.586 0.558072    
## SibSp3      -2.392e+00  7.187e-01  -3.328 0.000875 ***
## SibSp4      -1.766e+00  7.669e-01  -2.302 0.021322 *  
## SibSp5      -1.606e+01  9.565e+02  -0.017 0.986601    
## SibSp8      -1.615e+01  7.548e+02  -0.021 0.982926    
## Parch1       3.693e-01  2.897e-01   1.275 0.202426    
## Parch2      -2.153e-03  3.814e-01  -0.006 0.995497    
## Parch3       3.311e-01  1.043e+00   0.318 0.750846    
## Parch4      -1.599e+01  1.052e+03  -0.015 0.987870    
## Parch5      -1.207e+00  1.170e+00  -1.032 0.302273    
## Parch6      -1.656e+01  2.400e+03  -0.007 0.994494    
## Fare         2.558e-03  2.480e-03   1.032 0.302221    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 1186.66  on 890  degrees of freedom
## Residual deviance:  765.54  on 873  degrees of freedom
## AIC: 801.54
## 
## Number of Fisher Scoring iterations: 15

Y ahora es el turno de Fare

modelo_Survived <- glm(Survived ~ Pclass + Sex + Age + SibSp + Parch, data=train, family="binomial")
summary(modelo_Survived)
## 
## Call:
## glm(formula = Survived ~ Pclass + Sex + Age + SibSp + Parch, 
##     family = "binomial", data = train)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -2.8480  -0.5989  -0.4173   0.5896   2.4673  
## 
## Coefficients:
##               Estimate Std. Error z value Pr(>|z|)    
## (Intercept)  3.975e+00  4.571e-01   8.698  < 2e-16 ***
## Pclass2     -1.318e+00  2.735e-01  -4.821 1.43e-06 ***
## Pclass3     -2.379e+00  2.671e-01  -8.909  < 2e-16 ***
## Sex1        -2.729e+00  2.011e-01 -13.572  < 2e-16 ***
## Age         -4.139e-02  8.739e-03  -4.736 2.18e-06 ***
## SibSp1       7.327e-02  2.238e-01   0.327 0.743376    
## SibSp2      -2.715e-01  5.383e-01  -0.504 0.613980    
## SibSp3      -2.300e+00  6.984e-01  -3.292 0.000994 ***
## SibSp4      -1.781e+00  7.697e-01  -2.314 0.020692 *  
## SibSp5      -1.604e+01  9.561e+02  -0.017 0.986616    
## SibSp8      -1.606e+01  7.539e+02  -0.021 0.983000    
## Parch1       4.090e-01  2.862e-01   1.429 0.152981    
## Parch2       7.202e-02  3.746e-01   0.192 0.847532    
## Parch3       3.679e-01  1.048e+00   0.351 0.725493    
## Parch4      -1.587e+01  1.040e+03  -0.015 0.987827    
## Parch5      -1.138e+00  1.168e+00  -0.974 0.330205    
## Parch6      -1.646e+01  2.400e+03  -0.007 0.994528    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 1186.66  on 890  degrees of freedom
## Residual deviance:  766.71  on 874  degrees of freedom
## AIC: 800.71
## 
## Number of Fisher Scoring iterations: 15

Claramente Parch debe ser eliminada, dado que no es significativa.

modelo_Survived <- glm(Survived ~ Pclass + Sex + Age + SibSp, data=train, family="binomial")
summary(modelo_Survived)
## 
## Call:
## glm(formula = Survived ~ Pclass + Sex + Age + SibSp, family = "binomial", 
##     data = train)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -2.9022  -0.5985  -0.4180   0.6187   2.5000  
## 
## Coefficients:
##              Estimate Std. Error z value Pr(>|z|)    
## (Intercept)   4.16757    0.43315   9.621  < 2e-16 ***
## Pclass2      -1.33204    0.27084  -4.918 8.74e-07 ***
## Pclass3      -2.47836    0.26104  -9.494  < 2e-16 ***
## Sex1         -2.70949    0.19615 -13.813  < 2e-16 ***
## Age          -0.04577    0.00837  -5.469 4.53e-08 ***
## SibSp1        0.12033    0.21109   0.570  0.56864    
## SibSp2       -0.18292    0.52249  -0.350  0.72626    
## SibSp3       -2.14936    0.68939  -3.118  0.00182 ** 
## SibSp4       -1.69904    0.74430  -2.283  0.02245 *  
## SibSp5      -16.01036  958.62424  -0.017  0.98667    
## SibSp8      -15.97828  754.65794  -0.021  0.98311    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 1186.66  on 890  degrees of freedom
## Residual deviance:  774.98  on 880  degrees of freedom
## AIC: 796.98
## 
## Number of Fisher Scoring iterations: 15

Sisbp aparentemente contiene información relevante. Como estamos en un modelo predictivo, no nos conviene eliminar variables salvo que esté claro que así debe hacerse. Dado que cada variable que eliminamos hace que se reduzca la capacidad de explicar la variación en los datos.

Predicciones del modelo

Veamos que tal funciona el modelo sobre los datos de test. Tomaremos p>0.5 como sobrevivir=TRUE y p<0.5 como sobrevivir=FALSE.

predictions<-predict(modelo_Survived,newdata=train,type="response")
predictions <- ifelse(predictions > 0.5,1,0)
predictions<-as.factor(predictions)

Validación del modelo

Dos de los métodos más usados para validar los modelos de regresión logística son la matriz de confusión, con sus indicadores asociados (sensibilidad, especificidad, precisión, F1-score) y las curvas ROC, que proporcionan una forma gráfica de ver qué bien ajusta el modelo.

Matriz de confusión

Creamos la matriz de confusion y las estadisticas para las predicciones.

## Confusion matrix and statistics
library(caret)
confusionMatrix(data=predictions, reference=train$Survived)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   0   1
##          0 480  93
##          1  69 249
##                                          
##                Accuracy : 0.8182         
##                  95% CI : (0.7913, 0.843)
##     No Information Rate : 0.6162         
##     P-Value [Acc > NIR] : < 2e-16        
##                                          
##                   Kappa : 0.6105         
##                                          
##  Mcnemar's Test P-Value : 0.07075        
##                                          
##             Sensitivity : 0.8743         
##             Specificity : 0.7281         
##          Pos Pred Value : 0.8377         
##          Neg Pred Value : 0.7830         
##              Prevalence : 0.6162         
##          Detection Rate : 0.5387         
##    Detection Prevalence : 0.6431         
##       Balanced Accuracy : 0.8012         
##                                          
##        'Positive' Class : 0              
## 

Podemos ver que la precisión del modelo es bastante alta, un 81%. También los valores de la sensibilidad y especificidad son muy buenos.

Curva ROC y área bajo la curva

Ahora optimizaremos el modelo calculando la curva ROC

## ROC Curve and calculating the area under the curve(AUC)
library(ROCR)
predictions <- predict(modelo_Survived, newdata=train, type="response")
ROCRpred <- prediction(predictions, train$Survived)
ROCRperf <- performance(ROCRpred, measure = "tpr", x.measure = "fpr")

plot(ROCRperf, colorize = TRUE, text.adj = c(-0.2,1.7), print.cutoffs.at = seq(0,1,0.1))

El area bajo la curva es la medida del performance del test

auc <- performance(ROCRpred, measure = "auc")
auc <- auc@y.values[[1]]
auc
## [1] 0.8580726

La medida del área bajo la curva ROC nos dice que el modelo, con un 0.85, es francamente bueno. A partir de 0.9 se consideraría excepcional.

Observar también que el valor 0.5, como valor de corte, es un buen punto intermedio en cuanto a la sensibilidad/especifidad se refiere.

PREDICCIONES DE SUPERVIVENCIA

Veamos quién vive y quién muere según nuestro modelo:

test<-cbind(test,Survived=0)
#test$Survived<-as.factor(test$Survived)
predictions<-predict(modelo_Survived,newdata=test,type="response")
summary(predictions)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.0000  0.1073  0.2964  0.4026  0.6627  0.9696
predictions <- ifelse(predictions > 0.5,1,0) #metemos el valor de corte calculado con la ROC

Finalmente, guardamos en un fichero las predicciones

for(i in 1:nrow(test))
    test$Survived[i]<-predictions[i]

Y vemos cuántos sobreviven

table(test$Survived)
## 
##   0   1 
## 258 160

CONCLUSIONES

En este trabajo, hemos creado un modelo de regresión logística relativamente sencillo para calcular la probabilidad de supervivencia en el hundimiento del Titanic.

La tarea que más trabajo ha sido la imputación a la edad, que podíamos haberla hecho de varias formas posibles.

como suele pasar en la regresión logística, vamos navegando por la ambigüedad de la elección de variables y diversos métodos de imputación. Así pues, según lo que nos pregunten, deberemos elegir una forma de trabajo adecuada.

Comentarios

Entradas populares de este blog

UNA BREVE EXPLICACIÓN DE LA LEY DE LAS ESPERANZAS ITERADAS

UN EJEMPLO DETALLADO DE PRE-PROCESAMIENTO EN R - IMPUTACIÓN DE DATOS FALTANTES

Un mundo de sucesos imposibles - El "tongo" de la Bonoloto.