Se cargan los paquetes
library(pROC)
library(dplyr)
library(tidyr)
library(readr)
library(GGally)
library(ggthemes)
library(ggplot2)
library(broom)
library(purrr)
library(modelr)
library(caret)
theme_set(theme_bw()) #carga un theme para todos los gráficos
df <- read_csv("D:/OneDrive/Ciencia de Datos/06 Enfoque estadistico/TP01/TP03/titanic_complete_train.csv")
glimpse(df)
## Observations: 891
## Variables: 12
## $ PassengerId <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, ...
## $ Survived <dbl> 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0...
## $ Pclass <dbl> 3, 1, 3, 1, 3, 3, 1, 3, 3, 2, 3, 1, 3, 3, 3, 2, 3, 2, 3...
## $ Name <chr> "Braund, Mr. Owen Harris", "Cumings, Mrs. John Bradley ...
## $ Sex <chr> "male", "female", "female", "female", "male", "male", "...
## $ Age <dbl> 22.00000, 38.00000, 26.00000, 35.00000, 35.00000, 26.50...
## $ SibSp <dbl> 1, 1, 0, 1, 0, 0, 0, 3, 0, 1, 1, 0, 0, 1, 0, 0, 4, 0, 1...
## $ Parch <dbl> 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 5, 0, 0, 1, 0, 0...
## $ Ticket <chr> "A/5 21171", "PC 17599", "STON/O2. 3101282", "113803", ...
## $ Fare <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4583, 51.86...
## $ Cabin <chr> NA, "C85", NA, "C123", NA, NA, "E46", NA, NA, NA, "G6",...
## $ Embarked <chr> "S", "C", "S", "S", "S", "Q", "S", "S", "S", "C", "S", ...
df = select(df, PassengerId, Survived, Pclass, Sex, Age, SibSp,Parch, Fare, Embarked)
charcol = c('Survived', 'Pclass', "Embarked", 'Sex')
df[charcol] <- lapply(df[charcol], factor)
glimpse(df)
## Observations: 891
## Variables: 9
## $ PassengerId <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, ...
## $ Survived <fct> 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0...
## $ Pclass <fct> 3, 1, 3, 1, 3, 3, 1, 3, 3, 2, 3, 1, 3, 3, 3, 2, 3, 2, 3...
## $ Sex <fct> male, female, female, female, male, male, male, male, f...
## $ Age <dbl> 22.00000, 38.00000, 26.00000, 35.00000, 35.00000, 26.50...
## $ SibSp <dbl> 1, 1, 0, 1, 0, 0, 0, 3, 0, 1, 1, 0, 0, 1, 0, 0, 4, 0, 1...
## $ Parch <dbl> 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 5, 0, 0, 1, 0, 0...
## $ Fare <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4583, 51.86...
## $ Embarked <fct> S, C, S, S, S, Q, S, S, S, C, S, S, S, S, S, S, Q, S, S...
pairs = df %>% select(Survived, Pclass, Sex, Age, Fare)
#función para facilitar la visualización de la distribución de variables continuas
my_dens <- function(data, mapping, ..., low = "#132B43", high = "#56B1F7") {
ggplot(data = data, mapping=mapping) +
geom_density(..., alpha=0.7) }
ggpairs(pairs,mapping = aes(colour= Survived),diag=list(continuous=my_dens)) +
theme(axis.text.x = element_text(angle = 90, hjust = 1))
De la imagen anterior se puede interpretar lo siguiente:
-Existe una proporción mayor de personas que no sobrevivieron que las que sobrevivieron.
-La clase mayoritaria es la #3, la proporción de no sobrevivientes aumenta conforme baja la clase. La tercera clase es la que tiene proporcionalmente menos sobrevivientes.
-Una gran proporción de las mujeres de la primera y segunda clase sobrevivieron. Los hombres de la primera clase sobrevivieron proporcionalmente más que los de segunda y tercera.
-La mayoría de las mujeres sobrevivieron y la mayoría de los hombres perecieron. En el viaje habían más viajeros hombres que mujeres.
-La variable edad agregada no parece ser un buen indicador de la sobrevivencia.
-Si la edad se divide según la clase, se puede observar que las personas más jóvenes en cada clase sobrevivieron más que las más viejas.
-En la primera y segunda clase existe una mayor dispersión de las edades.
-Los hombres sobrevivientes tienen una mayor edad que los no sobrevivientes. No parece haber una diferencia marcada en las edades de las mujeres sobrevivientes y no sobrevivientes.
-La edad disminuye de la primera a la tercera clase. Siendo los más jóvenes los de tercera clase.
-Al comparar la distribución de la edad de los viajeros y su condición de sobrevivencia se puede ver que los niños sobrevivieron en mayor proporción y las personas cerca de los 20 años murieron en mayor proporción.
-La variable tarifa permite hacer un análisis similar al de las clases en donde se observa que los que pagaron una baja tarifa murieron en mayor proporción.
prop.total = prop.table(table(df$Survived))*100
print(prop.total)
##
## 0 1
## 61.61616 38.38384
Se observa que el 62% de los tripulantes murieron en el evento.
set.seed(0)
train_val <- df %>% resample_partition(c(train=0.7,val=0.3))
df_train <- train_val$train %>% as_tibble()
df_val <- train_val$val %>% as_tibble()
prop.train = prop.table(table(df_train$Survived))*100
prop.val = prop.table(table(df_val$Survived))*100
proporciones = rbind(prop.total, prop.train, prop.val)
colnames(proporciones) = c('No sobreviviente', 'Sobreviviente')
proporciones
## No sobreviviente Sobreviviente
## prop.total 61.61616 38.38384
## prop.train 61.95827 38.04173
## prop.val 60.82090 39.17910
Se observa que la distribución de las clases es muy similar entre los grupos.
mod01 <- glm(Survived ~ Pclass + Sex + Age, data = df_train, family = "binomial")
tidy(mod01)
## # A tibble: 5 x 5
## term estimate std.error statistic p.value
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 (Intercept) 3.74 0.467 8.01 1.17e-15
## 2 Pclass2 -1.16 0.317 -3.65 2.58e- 4
## 3 Pclass3 -2.67 0.316 -8.44 3.08e-17
## 4 Sexmale -2.55 0.225 -11.3 1.39e-29
## 5 Age -0.0376 0.00929 -4.05 5.14e- 5
En primer lugar se observa que todos los coeficientes estimados son menores a 0,05 y por ende se puede concluir que son significativamente diferentes de 0.
Todos los coeficientes de las variables elgidas son negativas. Esto implica cuando la variable en cuestión aumenta, la probabilidad estimada de sobrevir disminuye. En estos casos nótese que las clases basales son Pclase1 y Sexo Femenino.
Si se miran las clases se puede establecer esta relación en cuanto a la probabilidad estimada de sobrevivir Clase 1 > Clase 2 > Clase 3 (manteniendo todo constante).
Ser mujer aumenta las probablidades estimadas de sobrevir respecto a los hombres manteniendo todo constante. A sy vez, aumento en la edad es perjudicial para las probabilidades de sobrevivencia manteniendo las demás variables constantes.
#se genera una matriz con las propiedades de los individuos
rose_vs_jack = data.frame(id = c(1,2),
Pclass = as.factor(c(1,3)),
Sex = as.factor(c('female', 'male')),
Age = c(17, 20))
#se predice probabilidad de sobrevivencia
predict(mod01, rose_vs_jack, type = 'response')
## 1 2
## 0.95674501 0.09698184
Las predicciones indican que Rose tiene 95.7% probabilidades de sobrevivir y Jack solo el 9.7%.
Generar 3 modelos de regresión logística sobre el dataset de entrenamiento utilizando diferentes combinaciones de variables. Al menos dos modelos deben ser multivariados. Ordenar por la deviance los 3 modelos creados en el punto 3)a) y el creado en el punto 2)a) y seleccionar el mejor modelo en términos de la deviance explicada
#indico las combinaciones de los modelos. el mod 01 corresponde al modelo indicado en el TP.
logit_formulas <- formulas(.response = ~Survived,
mod01= ~Pclass + Sex + Age,
mod02= ~Pclass + Sex + Embarked,
mod03= ~Pclass + Sex + Parch + SibSp + Age,
mod04= ~Pclass + Sex + Age + SibSp + Parch + Fare + Embarked)
models <- data_frame(logit_formulas) %>% # dataframe a partir del objeto formulas
mutate(models = names(logit_formulas), # columna con los nombres de las formulas
expression = paste(logit_formulas), # columna con las expresiones de las formulas
mod = map(logit_formulas, ~glm(.,family = 'binomial', data = df_train)))
model_tidy = models %>%
mutate(tidy = map(mod,tidy)) %>%
unnest(tidy) %>%
mutate(estimate=round(estimate,4),
p.value=round(p.value,4)) %>%
mutate(p.adjusted = p.adjust(p.value))%>%
filter(term != '(Intercept)')%>%
select(-c(logit_formulas, mod))
#creo un dataset con los terminos no significantes
no_significante = model_tidy %>%
mutate(significante = p.adjusted < 0.05)%>%#se aplica el p valor ajustado por la cantidad de modelos que se realizan.
filter(significante == FALSE)%>%
select(models, expression, term, significante)
no_significante
## # A tibble: 11 x 4
## models expression term significante
## <chr> <chr> <chr> <lgl>
## 1 mod02 Survived ~ Pclass + Sex + Embarked Pclass2 FALSE
## 2 mod02 Survived ~ Pclass + Sex + Embarked Embark~ FALSE
## 3 mod02 Survived ~ Pclass + Sex + Embarked Embark~ FALSE
## 4 mod03 Survived ~ Pclass + Sex + Parch + SibSp + Age Parch FALSE
## 5 mod03 Survived ~ Pclass + Sex + Parch + SibSp + Age SibSp FALSE
## 6 mod04 Survived ~ Pclass + Sex + Age + SibSp + Parch + ~ Pclass2 FALSE
## 7 mod04 Survived ~ Pclass + Sex + Age + SibSp + Parch + ~ SibSp FALSE
## 8 mod04 Survived ~ Pclass + Sex + Age + SibSp + Parch + ~ Parch FALSE
## 9 mod04 Survived ~ Pclass + Sex + Age + SibSp + Parch + ~ Fare FALSE
## 10 mod04 Survived ~ Pclass + Sex + Age + SibSp + Parch + ~ Embark~ FALSE
## 11 mod04 Survived ~ Pclass + Sex + Age + SibSp + Parch + ~ Embark~ FALSE
Cuando se agregan más variables al modelo, empiezan a aparecer niveles y variables no significantes. En el modelo 02, los niveles según el sitio de embarcación no son significantes respecto al sitio de embarca basal. Tampoco logra encontrar una diferencia significativa en la Clase 02 respecto a la Clase 01.
El modelo 03 es igual al modelo 01 pero añade las variables Parch y SibSp que resultan no significativas.
El modelo 04 contiene todas las variables pero no encuentra significancia con las variables SibSP, Parch, Fare y con los niveles EmbarkedQ y Pclass02.
#ploteo los coeficientes para ver su variación según los modelos.
ggplot(model_tidy, aes(term, estimate, colour = as.factor(models)))+
geom_jitter(height=0, width=0.2)+
labs(title ="Variación de coeficientes", x = "Variable", y = "Coeficiente")+
theme(axis.text.x = element_text(angle = 90, hjust = 1, size = 10))+
theme(legend.key = element_blank())
#ploteo la significancia de los coeficientes
ggplot(model_tidy, aes(term, p.adjusted, colour = as.factor(expression)))+
geom_jitter(height=0, width=0.3)+
labs(title ="Significancia de los coeficientes", x = "Variable", y = "P valor ajustado")+
geom_hline(yintercept=0.05, linetype="dashed", color = "red")+
theme(axis.text.x = element_text(angle = 90, hjust = 1, size = 10))+
theme(legend.key = element_blank())
El gráfico de significancia de los coeficientes muestra como en los modelos explorados, existen coeficientes que siempre son significativos y otros que no. El único coeficiente que pierde significatividad en presencia de otras variables es el PClass 2.
Respecto a la variación de los coeficientes se puede observar una gran variación en el coeficiente PClass2 y PClass3, los otros coeficientes se mantienen en rangos similares.
# Calcular las medidas de evaluación para cada modelo
models <- models %>%
mutate(glance = map(mod,glance))
# Obtener las medidas de evaluacion de interes
models %>%
unnest(glance) %>%
# Calculo de la deviance explicada
mutate(perc_explained_dev = 1-deviance/null.deviance) %>%
select(-c(models, df.null, AIC, BIC, mod, logit_formulas)) %>%
arrange(deviance)
## # A tibble: 4 x 6
## expression null.deviance logLik deviance df.residual perc_explained_~
## <chr> <dbl> <dbl> <dbl> <int> <dbl>
## 1 Survived ~ Pclass ~ 828. -270. 540. 613 0.348
## 2 Survived ~ Pclass ~ 828. -271. 543. 616 0.344
## 3 Survived ~ Pclass ~ 828. -277. 554. 618 0.331
## 4 Survived ~ Pclass ~ 828. -283. 567. 617 0.315
Según el TP se elige el modelo con la mejor deviance. En este caso corresponde al modelo 04.
# Añadir las predicciones
models <- models %>%
mutate(pred= map(mod,augment, type.predict = "response"))
# Modelo completo
pred_mod <- models %>%
filter(models=="mod04") %>%
unnest(pred)
# Calculamos curvas ROC
roc_mod <- roc(response=pred_mod$Survived, predictor=pred_mod$.fitted)
## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
print(paste('AUC: Modelo', roc_mod$auc))
## [1] "AUC: Modelo 0.865082748518834"
#grafico curva roc
ggroc(list(Modelo_4=roc_mod), size=1) + geom_abline(slope = 1, intercept = 1, linetype='dashed') + labs(title='Curva ROC', color='Modelo')
El gráfico de la curva ROC muestra como el modelo se aleja de la diagonal punteada del azar. El área bajo la curva es de 0.865 cuando el AUC del azar es 0.5 y el ajuste perfecto un AUC = 1.
#violin plot
violin_full= ggplot(pred_mod, aes(x=Survived, y=.fitted, group=Survived,fill=factor(Survived))) +
geom_violin() +
guides(fill=FALSE) +
labs(title='Violin plot', subtitle='Modelo 04', y='Predicted probability')
print(violin_full)
En el violin plot se puede ver como el modelo predice de mejor manera aquellos pasajeros que no sobrevivieron. Si se elige un punto de corte cerca de 0.25, se predicirá relativamente bien la mayoría de los no sobrevivientes. En cuanto a los sobrevivientes, estos presentan una distribución más homogénea en en las diferentes probabilidades y como es de esperar se ensancha en los valores altos de probabilidada.
Sobre el dataset de validación realizar un gráfico de Accuracy, Specificity, Recall y Precision en función del punto de corte
#modelo entrenado sobre los datos de train sin validation
mod04_train<- glm(logit_formulas$mod04, family = 'binomial', data = df_train)
pred_mod_val = augment(x=mod04_train, newdata=df_val, type.predict='response')
cutoff = seq(0.01,0.95,0.01)
#pred_mod_val %>%
# mutate(predicted_class=if_else(.fitted>cutoff, 1, 0) %>% as.factor(),
# survived= factor(Survived))
#se crea una función de que genere las matrices de confusión para múltiples puntos de corte.
prediction_metrics <- function(cutoff, predictions=pred_mod_val){
table <- predictions %>%
mutate(predicted_class=if_else(.fitted>cutoff, 1, 0) %>% as.factor(),
survived= factor(Survived))
confusionMatrix(table(table$predicted_class, table$survived), positive = "1") %>%
tidy() %>%
select(term, estimate) %>%
filter(term %in% c('accuracy', 'sensitivity', 'specificity', 'precision','recall')) %>%
mutate(cutoff=cutoff)
}
#se crea el dataset con la función anterior para graficar las métricas
logit_pred= map_dfr(cutoff, prediction_metrics)%>% mutate(term=as.factor(term))
ggplot(logit_pred, aes(cutoff,estimate, group=term, color=term)) + geom_line(size=1) +
theme_bw() +
labs(title= 'Accuracy, Sensitivity, Specificity, Recall y Precision', subtitle= 'Modelo 04 en validation set', color="")
En este caso las clases tienen una proporción en el dataset aproximada del 60% (no sobrevive) y 40% (sobrevive). Por estos valores se puede decir que las clases no tienen un problema de desbalanceo. Dependiendo de la aplicación puede convenir utilizar la sensibilidad o la especificidad u otra métrica. Puesto que la clase es balanceada y no hay preferencia por la predicción de algún tipo específico de clase, se utiliza un valor de la probabilidad de corte de 0,5 que coincide con el máximo valor del accuracy.
sel_cutoff = 0.5
# Agregamos la predicciones al dataset de validacion
table= augment(x=mod04_train, newdata=df_val, type.predict='response')
# Clasificamos utilizamos el punto de corte
table=table %>% mutate(predicted_class=if_else(.fitted>sel_cutoff, 1, 0) %>% as.factor(),
survived= factor(Survived))
# Creamos la matriz de confusión
confusionMatrix(table(table$survived, table$predicted_class), positive = "1")
## Confusion Matrix and Statistics
##
##
## 0 1
## 0 148 15
## 1 34 71
##
## Accuracy : 0.8172
## 95% CI : (0.7656, 0.8616)
## No Information Rate : 0.6791
## P-Value [Acc > NIR] : 2.736e-07
##
## Kappa : 0.6036
##
## Mcnemar's Test P-Value : 0.01013
##
## Sensitivity : 0.8256
## Specificity : 0.8132
## Pos Pred Value : 0.6762
## Neg Pred Value : 0.9080
## Prevalence : 0.3209
## Detection Rate : 0.2649
## Detection Prevalence : 0.3918
## Balanced Accuracy : 0.8194
##
## 'Positive' Class : 1
##
La matriz de confusión indica que de los 182 pasajeros que murieron, se logró clasificar correctamente 148 y de los 86 sobrevivientes 71 fueron correctamente clasificados. Se observa como las predicciones eróneas por clase son aproximadamente 1/5 de las correctas en ambos casos. Esto resulta de esta manera por elegir como métrica de punto de corte el accuracy.
Leer el archivo titanic_complete_test.csv y transformar las variables Survived, Pclass y Embarked a factor
df_test <- read_csv("D:/OneDrive/Ciencia de Datos/06 Enfoque estadistico/TP01/TP03/titanic_complete_test.csv")
df_test = select(df_test, PassengerId, Survived, Pclass, Sex, Age, SibSp,Parch, Fare, Embarked)
df_test[charcol] <- lapply(df_test[charcol], factor)
Con el modelo y punto de corte elegidos clasificar a las personas del dataset de testing. Obtener la matriz de confusión y comparar con la obtenida en el punto 5)c)
#entreno con todos los datos del train (validation y train)
mod04_train_val<- glm(logit_formulas$mod04, family = 'binomial', data = df)
# Agregamos la predicciones al dataset de test
table= augment(x=mod04_train_val, newdata=df_test, type.predict='response')
# Clasificamos utilizamos el punto de corte
table=table %>% mutate(predicted_class=if_else(.fitted>sel_cutoff, 1, 0) %>% as.factor(),
survived= factor(Survived))
# Creamos la matriz de confusión
confusionMatrix(table(table$survived, table$predicted_class), positive = "1")
## Confusion Matrix and Statistics
##
##
## 0 1
## 0 215 46
## 1 48 109
##
## Accuracy : 0.7751
## 95% CI : (0.732, 0.8143)
## No Information Rate : 0.6292
## P-Value [Acc > NIR] : 1.055e-10
##
## Kappa : 0.5193
##
## Mcnemar's Test P-Value : 0.9179
##
## Sensitivity : 0.7032
## Specificity : 0.8175
## Pos Pred Value : 0.6943
## Neg Pred Value : 0.8238
## Prevalence : 0.3708
## Detection Rate : 0.2608
## Detection Prevalence : 0.3756
## Balanced Accuracy : 0.7604
##
## 'Positive' Class : 1
##
En este caso, se puede observar como el accuracy que es la métrica elegida, pasó del 82% al 78% al utilizar los datos del test. Resulta interesante notar que el Sensitivity disminuyó de 83% a 70% pero el Specificity se mantuvo práctimente igual. Esto quiere decir que en el test, el modelo predice mejor los no sobrevivientes que los sobrevivientes en comparación con los datos de validación.