Este conjunto de datos contiene detalles de los clientes de un banco y la variable objetivo es una variable binaria que refleja el hecho de si el cliente dejó el banco (cerró su cuenta) o si continúa siendo un cliente.
VARIABLES QUE CONTIENE EL DATASET
CreditScore: el rango de puntaje crediticio es de 350 a 850.
Geography: el banco regional tiene clientes de tres países: Francia, Alemania y España.
Age: el rango de edad del cliente es de 18 a 92.
Tenure:años que el cliente ha permanecido en el banco.
Balance: la cantidad de dinero disponible para retirar.
NumOfProducts: número de productos que los clientes utilizan en el banco.
IsActiveMember: 1 indica que está activo.
EstimatedSalary: el salario anual informado por el cliente.
Exited: si el cliente ha abandonado (cerrado la cuenta bancaria), 1 indica abandono.
FUENTE: https://www.kaggle.com/shrutimechlearn/churn-modelling
library(tidyverse)
library(kableExtra)
library(caret)
library(corrplot)
library(ggpubr)
library(cowplot)
library(pROC)
library(kableExtra)
library(vip)
library(pdp)
library(tidymodels)
library(doParallel)
library(randomForest)
library(rpart)
library(xgboost)
library(embed)
library(rpart.plot)
library(rattle)
library(ROCR)
options(scipen=999)#Desactiva la notacion cientificachurn <- read.csv("Churn_Modelling.csv")Comprobamos si hay valores NA en nuestros datos.
colSums(is.na(churn))## RowNumber CustomerId Surname CreditScore Geography
## 0 0 0 0 0
## Gender Age Tenure Balance NumOfProducts
## 0 0 0 0 0
## HasCrCard IsActiveMember EstimatedSalary Exited
## 0 0 0 0
y vemos que no tenemos y comprobamos la estructura de nuestros datos.
str(churn)## 'data.frame': 10000 obs. of 14 variables:
## $ RowNumber : int 1 2 3 4 5 6 7 8 9 10 ...
## $ CustomerId : int 15634602 15647311 15619304 15701354 15737888 15574012 15592531 15656148 15792365 15592389 ...
## $ Surname : chr "Hargrave" "Hill" "Onio" "Boni" ...
## $ CreditScore : int 619 608 502 699 850 645 822 376 501 684 ...
## $ Geography : chr "France" "Spain" "France" "France" ...
## $ Gender : chr "Female" "Female" "Female" "Female" ...
## $ Age : int 42 41 42 39 43 44 50 29 44 27 ...
## $ Tenure : int 2 1 8 1 2 8 7 4 4 2 ...
## $ Balance : num 0 83808 159661 0 125511 ...
## $ NumOfProducts : int 1 1 3 2 1 2 2 4 2 1 ...
## $ HasCrCard : int 1 0 1 0 1 1 1 1 0 1 ...
## $ IsActiveMember : int 1 1 0 0 1 0 1 0 1 1 ...
## $ EstimatedSalary: num 101349 112543 113932 93827 79084 ...
## $ Exited : int 1 0 1 0 0 1 0 1 0 0 ...
summary(churn)## RowNumber CustomerId Surname CreditScore
## Min. : 1 Min. :15565701 Length:10000 Min. :350.0
## 1st Qu.: 2501 1st Qu.:15628528 Class :character 1st Qu.:584.0
## Median : 5000 Median :15690738 Mode :character Median :652.0
## Mean : 5000 Mean :15690941 Mean :650.5
## 3rd Qu.: 7500 3rd Qu.:15753234 3rd Qu.:718.0
## Max. :10000 Max. :15815690 Max. :850.0
## Geography Gender Age Tenure
## Length:10000 Length:10000 Min. :18.00 Min. : 0.000
## Class :character Class :character 1st Qu.:32.00 1st Qu.: 3.000
## Mode :character Mode :character Median :37.00 Median : 5.000
## Mean :38.92 Mean : 5.013
## 3rd Qu.:44.00 3rd Qu.: 7.000
## Max. :92.00 Max. :10.000
## Balance NumOfProducts HasCrCard IsActiveMember
## Min. : 0 Min. :1.00 Min. :0.0000 Min. :0.0000
## 1st Qu.: 0 1st Qu.:1.00 1st Qu.:0.0000 1st Qu.:0.0000
## Median : 97199 Median :1.00 Median :1.0000 Median :1.0000
## Mean : 76486 Mean :1.53 Mean :0.7055 Mean :0.5151
## 3rd Qu.:127644 3rd Qu.:2.00 3rd Qu.:1.0000 3rd Qu.:1.0000
## Max. :250898 Max. :4.00 Max. :1.0000 Max. :1.0000
## EstimatedSalary Exited
## Min. : 11.58 Min. :0.0000
## 1st Qu.: 51002.11 1st Qu.:0.0000
## Median :100193.91 Median :0.0000
## Mean :100090.24 Mean :0.2037
## 3rd Qu.:149388.25 3rd Qu.:0.0000
## Max. :199992.48 Max. :1.0000
Comprobamos los estadisticos de nuestros datos, para ver cualquier indicio a destacar o anormal en nuestros datos.
lapply(churn, summary)## $RowNumber
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 1 2501 5000 5000 7500 10000
##
## $CustomerId
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 15565701 15628528 15690738 15690941 15753234 15815690
##
## $Surname
## Length Class Mode
## 10000 character character
##
## $CreditScore
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 350.0 584.0 652.0 650.5 718.0 850.0
##
## $Geography
## Length Class Mode
## 10000 character character
##
## $Gender
## Length Class Mode
## 10000 character character
##
## $Age
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 18.00 32.00 37.00 38.92 44.00 92.00
##
## $Tenure
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.000 3.000 5.000 5.013 7.000 10.000
##
## $Balance
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0 0 97199 76486 127644 250898
##
## $NumOfProducts
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 1.00 1.00 1.00 1.53 2.00 4.00
##
## $HasCrCard
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.0000 0.0000 1.0000 0.7055 1.0000 1.0000
##
## $IsActiveMember
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.0000 0.0000 1.0000 0.5151 1.0000 1.0000
##
## $EstimatedSalary
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 11.58 51002.11 100193.91 100090.24 149388.25 199992.48
##
## $Exited
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.0000 0.0000 0.0000 0.2037 0.0000 1.0000
segun su estructura podemos deducir que podemos poner algunas variables como factores:
churn$Geography <- as.factor(churn$Geography)
churn$Gender <- as.factor(churn$Gender)
churn$HasCrCard <- as.factor(churn$HasCrCard)
churn$IsActiveMember <- as.factor(churn$IsActiveMember)
churn$Exited <- as.factor(churn$Exited)
churn$Tenure <- as.factor(churn$Tenure)
churn$NumOfProducts <- as.factor(churn$NumOfProducts)Comprobamos la conversion hecha de factores:
str(churn)## 'data.frame': 10000 obs. of 14 variables:
## $ RowNumber : int 1 2 3 4 5 6 7 8 9 10 ...
## $ CustomerId : int 15634602 15647311 15619304 15701354 15737888 15574012 15592531 15656148 15792365 15592389 ...
## $ Surname : chr "Hargrave" "Hill" "Onio" "Boni" ...
## $ CreditScore : int 619 608 502 699 850 645 822 376 501 684 ...
## $ Geography : Factor w/ 3 levels "France","Germany",..: 1 3 1 1 3 3 1 2 1 1 ...
## $ Gender : Factor w/ 2 levels "Female","Male": 1 1 1 1 1 2 2 1 2 2 ...
## $ Age : int 42 41 42 39 43 44 50 29 44 27 ...
## $ Tenure : Factor w/ 11 levels "0","1","2","3",..: 3 2 9 2 3 9 8 5 5 3 ...
## $ Balance : num 0 83808 159661 0 125511 ...
## $ NumOfProducts : Factor w/ 4 levels "1","2","3","4": 1 1 3 2 1 2 2 4 2 1 ...
## $ HasCrCard : Factor w/ 2 levels "0","1": 2 1 2 1 2 2 2 2 1 2 ...
## $ IsActiveMember : Factor w/ 2 levels "0","1": 2 2 1 1 2 1 2 1 2 2 ...
## $ EstimatedSalary: num 101349 112543 113932 93827 79084 ...
## $ Exited : Factor w/ 2 levels "0","1": 2 1 2 1 1 2 1 2 1 1 ...
Eliminamos variables que no aportan a las operaciones ni al modelaje y guardamos algunas de ellas en una nueva variable para unirlas al conjunto de datos final.
dt1 <- churn%>%select(CustomerId, Surname)
churn$RowNumber=NULL
churn$CustomerId=NULL
churn$Surname=NULLy hechamos un vistazo preliminar a como nos queda nuestro dataset finalmente:
str(churn)## 'data.frame': 10000 obs. of 11 variables:
## $ CreditScore : int 619 608 502 699 850 645 822 376 501 684 ...
## $ Geography : Factor w/ 3 levels "France","Germany",..: 1 3 1 1 3 3 1 2 1 1 ...
## $ Gender : Factor w/ 2 levels "Female","Male": 1 1 1 1 1 2 2 1 2 2 ...
## $ Age : int 42 41 42 39 43 44 50 29 44 27 ...
## $ Tenure : Factor w/ 11 levels "0","1","2","3",..: 3 2 9 2 3 9 8 5 5 3 ...
## $ Balance : num 0 83808 159661 0 125511 ...
## $ NumOfProducts : Factor w/ 4 levels "1","2","3","4": 1 1 3 2 1 2 2 4 2 1 ...
## $ HasCrCard : Factor w/ 2 levels "0","1": 2 1 2 1 2 2 2 2 1 2 ...
## $ IsActiveMember : Factor w/ 2 levels "0","1": 2 2 1 1 2 1 2 1 2 2 ...
## $ EstimatedSalary: num 101349 112543 113932 93827 79084 ...
## $ Exited : Factor w/ 2 levels "0","1": 2 1 2 1 1 2 1 2 1 1 ...
kable(head(churn,6), booktabs = T) %>%
kable_styling(font_size=10)| CreditScore | Geography | Gender | Age | Tenure | Balance | NumOfProducts | HasCrCard | IsActiveMember | EstimatedSalary | Exited |
|---|---|---|---|---|---|---|---|---|---|---|
| 619 | France | Female | 42 | 2 | 0.00 | 1 | 1 | 1 | 101348.88 | 1 |
| 608 | Spain | Female | 41 | 1 | 83807.86 | 1 | 0 | 1 | 112542.58 | 0 |
| 502 | France | Female | 42 | 8 | 159660.80 | 3 | 1 | 0 | 113931.57 | 1 |
| 699 | France | Female | 39 | 1 | 0.00 | 2 | 0 | 0 | 93826.63 | 0 |
| 850 | Spain | Female | 43 | 2 | 125510.82 | 1 | 1 | 1 | 79084.10 | 0 |
| 645 | Spain | Male | 44 | 8 | 113755.78 | 2 | 1 | 0 | 149756.71 | 1 |
Vemos la tabla de contingencia y el porcentaje de abandono graficamentente que representan nuestros datos.
table(churn$Exited)##
## 0 1
## 7963 2037
levels(churn$Exited)=c("No_abandono", "abandono")
table(churn$Exited)##
## No_abandono abandono
## 7963 2037
prop.table(table(churn$Exited))##
## No_abandono abandono
## 0.7963 0.2037
ggplot(data = churn, aes(x = factor(Exited),
y = prop.table(stat(count)), fill = factor(Exited),
label = scales::percent_format(accuracy=0.01)(prop.table(stat(count))))) +
geom_bar(position = "dodge") +
geom_text(stat = 'count',
position = position_dodge(0.9),
vjust = -0.5,
size = 3.5) + coord_flip()+
scale_y_continuous(labels = scales::percent_format(accuracy=0.01))+labs(
x ='Abandono',y ='Porcentaje de Abandono')+ggtitle('TASA DE ABANDONO')En primer lugar vamos a ver los datos que nos arroja en proporcion nuestra variable objetivo, por si baja de un nivel minimo aceptable alguno de sus niveles o factores y hubiera que balancear los datos (entendiendo cuestionarselo por debajo del 10% de algun nivel)
y comprobamos que no se da el caso ya que por lo menos tenemos un nivel con 20.37%, por lo tanto lo consideramos suficiente.
Vamos ahora a mostrar los histogramas de las variables continuas:
s1 <-ggplot(data = churn, aes(x=CreditScore))+
geom_histogram(color="white", fill="blue")+
theme_bw() +
theme(legend.position = "none")
s2 <- ggplot(data = churn, aes(x=Age))+
geom_histogram(color="white", fill="purple") +
theme_bw() +
theme(legend.position = "none")
s3 <- ggplot(data = churn, aes(x=Balance))+
geom_histogram(color="white", fill="green") +
theme_bw() +
theme(legend.position = "none")
s4 <- ggplot(data = churn, aes(x=EstimatedSalary))+
geom_histogram(color="white", fill="red") +
theme_bw() +
theme(legend.position = "none")plot_grid(s1, s2, s3,s4,labels = "AUTO")Por edad se puede ver que el numero de clientes del banco, es sensiblemente superior entre los 25 y los 50 años (y dentro de este intervalo entre 30 y 40 años aprox.), siendo en las demás franjas de edad mucho menores el número de clientes y a partir de 50 se van reduciendo ostensiblemente. (curva con mayor asimetría hacia la derecha).
El rango de puntaje crediticio (CreditScore), se distribuye como una normal, estableciéndose entre 600 y 700 su mayor puntaje de crédito.
Hay una gran cantidad de clientes con un Balance a 0 importante más allá de los 3500 clientes, el resto de clientes tiene un Balance acorde a una distribución normal.
En cuanto al salario estimado esta bastante igualado en todos los niveles salariales de los clientes, exceptuando los de 0 y 200000 que son los 2 grupos de clientes que están a distinto nivel en general en cuanto a cantidad de clientes de esta relación.
BOXPLOT VARIABLES CUANTITATIVAS RESPECTO TASA DE ABANDONO
Vamos a comparar nuestras variables continuas con la variable objetivo especificada.
a1 <- ggplot(churn, aes(x = Exited, y = CreditScore, fill = Exited)) +
geom_boxplot()+ggtitle("CHURN-CREDITSCORE")
a2 <- ggplot(churn, aes(x = Exited, y = Age , fill = Exited)) +
geom_boxplot()+ggtitle("CHURN-AGE")
a3 <- ggplot(churn,aes(x = Exited, y = Balance, fill = Exited)) +
geom_boxplot()+ggtitle("CHURN-BALANCE")
a4 <- ggplot(churn,aes(x = Exited, y = EstimatedSalary, fill = Exited)) +
geom_boxplot()+ggtitle("CHURN-ESTIMATEDSALARY")ggarrange(a1, a2,a3,a4,
labels = "AUTO",
common.legend = T,
legend = "bottom",
align = "hv",
ncol = 2)$1 $
2 attr(,“class”) [1] “list” “ggarrange”
Se puede decir con respecto al rango de puntaje crediticio (CreditScore) que en cuanto al abandono es muy similar al de los clientes que no lo hacen, inclusive dentro de un rango de valores equivalente entre aprox. 600-700 de puntaje crediticio, siendo su varianza prácticamente muy similar en torno a la media.
En la mayoría de casos los clientes de edad entre 30 y 40 años no abandonan el banco, a partir de los 40 hasta los 50 hay la mayor relación de abandonos, la varianza de los que no abandonan es un poco menor a la de los clientes que abandonan en este aspecto.
En cuanto al balance la tasa de abandono y no abandono esta equilibrada entre los clientes entre aprox. 40000 y un poco superior a 110000, siendo sus medias respectivas entre aprox.90000(No_abandono) y 110000(abandono). La varianza de los que no abandonan es superior a los que abandonan, los que abandonan lo hacen a partir de un balance aprox. de 40000 en un rango entre 40000 y 130000 aprox.
El salario estimado(estimatedsalary), es prácticamente igual en proporción entre los que abandonan y los que no (misma varianza aprox.) y su media tanto de unos como de otros esta sobre los 100000 de salario estimado.
Vamos ahora a comprobar si existe correlacion entre las variables continuas con el fin de evitar una posible multicolinealidad.
Para ello hacemos una matriz de correlacion de dichas variables.
dt <- churn%>%select(c("CreditScore", "Age", "Balance","EstimatedSalary"))
dat.cor <- cor(dt, method = "pearson")
corrplot(dat.cor, method = "shade",
shade.col = NA, tl.col = "black",
tl.srt = 90,
addCoef.col = "black", addcolorlabel = "no",
order = "AOE")La correlacion es muy baja por tanto no existen problemas de multicolinealidad con nuestras variables.
Veremos ahora graficamente los datos referentes a nuestras variables categoricas. (Exceptuando variable objetivo ya vista)
Geography <- churn%>%
group_by(Geography)%>%
summarise(Count= n())
p1 <- ggbarplot(Geography, x = "Geography", y = "Count",
fill = "Geography", color = "Geography", palette = "jco",label=TRUE,lab.size=2.5,lab.pos = "out", lab.col = "black", sort.val="desc",sort.by.groups = FALSE)+theme(legend.position="none")
Gender <- churn%>%
group_by(Gender)%>%
summarise(Count= n())
p2 <- ggbarplot(Gender, x = "Gender", y = "Count",
fill = "Gender", color = "Gender", palette = "jco",label=TRUE,lab.size=2.5,lab.pos = "out", lab.col = "black", sort.val="desc",sort.by.groups = FALSE)+theme(legend.position="none")
HasCrCard <- churn%>%
group_by(HasCrCard)%>%
summarise(Count= n())
p3 <- ggbarplot(HasCrCard, x = "HasCrCard", y = "Count",
fill = "HasCrCard", color = "HasCrCard", palette = "jco",label=TRUE,lab.size=2.5,lab.pos = "out", lab.col = "black", sort.val="desc",sort.by.groups = FALSE)+theme(legend.position="none")
IsActiveMember <- churn%>%
group_by(IsActiveMember)%>%
summarise(Count= n())
p4 <- ggbarplot(IsActiveMember, x = "IsActiveMember", y = "Count",
fill = "IsActiveMember", color = "IsActiveMember", palette = "jco",label=TRUE,lab.size=2.5,lab.pos = "out", lab.col = "black", sort.val="desc",sort.by.groups = FALSE)+theme(legend.position="none")
Tenure <-churn%>%
group_by(Tenure)%>%
summarise(Count= n())
p5 <- ggbarplot(Tenure, x = "Tenure", y = "Count",
fill = "Tenure", color = "Tenure", palette = c("blue", "purple", "brown2", "red", "cyan1", "green", "yellow", "deeppink1", "forestgreen", "darksalmon", "gold1"),label=TRUE,lab.size=2.5,lab.pos = "out", lab.col = "black", sort.val="desc",sort.by.groups = FALSE)+theme(legend.position="none")
NumOfProducts <-churn%>%
group_by(NumOfProducts)%>%
summarise(Count= n())
p6 <- ggbarplot(NumOfProducts, x = "NumOfProducts", y = "Count",
fill = "NumOfProducts", color = "NumOfProducts", palette = "jco",label=TRUE,lab.size=2.5,lab.pos = "out", lab.col = "black", sort.val="desc",sort.by.groups = FALSE)+theme(legend.position="none")ggarrange(p1, p2,p3,p4,p5,p6,
labels = "AUTO",
common.legend = F,
legend = NULL,
align = "hv",
ncol = 2)$1 $
2 $
3 attr(,“class”) [1] “list” “ggarrange”
Los clientes son fundamentalmente de 3 países, Francia, Alemania y España, mayoritariamente son de Francia como se puede ver en la gráfica, seguidos con un número similar de clientes y a distancia del primero de Alemania y España.
Hay un numero mas o menos equilibrado entre hombres y mujeres clientes y de miembros activos y no activos, los que tienen tarjeta superan ampliamente a los que no la tienen.
La mayoría de los clientes son poseedores de uno o dos productos financieros del banco, muy significativamente por encima de los que tienen 3 o 4 como grupos minoritarios.
Los clientes que se quedan en el banco son significativamente superiores a los que abandonan el mismo.
Del numero de años que permanece un cliente en el banco, los clientes que no tienen 1 año y los que tienen 10 en el mismo son significativamente inferiores en número al número que tiene el resto en función de los años de permanencia en el banco.
Comparamos ahora cada variable categorica con respecto a nuestra variable objetivo para sacar conclusiones:
Geo <- churn%>%
group_by(Geography,Exited)%>%
summarise(Count= n())%>%
mutate(porcentaje=round(Count/sum(Count)*100))
c1 <- ggbarplot(Geo, x = "Geography", y = "porcentaje",
fill = "Exited", color = "Exited", palette = "Paired",label=TRUE,lab.pos = "in",lab.size=2.5, lab.col = "white")+theme(legend.position="none")
Gen <- churn%>%
group_by(Gender,Exited)%>%
summarise(Count= n())%>%
mutate(porcentaje=round(Count/sum(Count)*100))
c2 <- ggbarplot(Gen, x = "Gender", y = "porcentaje",
fill = "Exited", color = "Exited", palette = "Paired",label=TRUE,lab.pos = "in",lab.size=2.5, lab.col = "white")+theme(legend.position="none")
HCrCard <- churn%>%
group_by(HasCrCard,Exited)%>%
summarise(Count= n())%>%
mutate(porcentaje=round(Count/sum(Count)*100))
c3 <- ggbarplot(HCrCard, x = "HasCrCard", y = "porcentaje",
fill = "Exited", color = "Exited", palette = "Paired",label=TRUE,lab.pos = "in",lab.size=2.5, lab.col = "white")+theme(legend.position="none")
IsActMember <- churn%>%
group_by(IsActiveMember,Exited)%>%
summarise(Count= n())%>%
mutate(porcentaje=round(Count/sum(Count)*100))
c4 <- ggbarplot(IsActMember, x = "IsActiveMember", y = "porcentaje",
fill = "Exited", color = "Exited", palette = "Paired",label=TRUE,lab.pos = "in",lab.size=2.5, lab.col = "white")+theme(legend.position="none")
Ten <-churn%>%
group_by(Tenure,Exited)%>%
summarise(Count= n())%>%
mutate(porcentaje=round(Count/sum(Count)*100))
c5 <- ggbarplot(Ten, x = "Tenure", y = "porcentaje",
fill = "Exited", color = "Exited", palette = "Paired",label=TRUE,lab.pos = "in",lab.size=2.5, lab.col = "white")+theme(legend.position="none")
NumOfProd <-churn%>%
group_by(NumOfProducts,Exited)%>%
summarise(Count= n())%>%
mutate(porcentaje=round(Count/sum(Count)*100))
c6 <- ggbarplot(NumOfProd, x = "NumOfProducts", y = "porcentaje",
fill = "Exited", color = "Exited", palette = "Paired",label=TRUE,lab.pos = "in", lab.size=2.5, lab.col = "white")+theme(legend.position="none")ggarrange(c1,c2,c3,c4,c5,c6,
labels = "AUTO",
common.legend = T,
legend = "bottom",
align = "hv",
ncol = 2)$1 $
2 $
3 attr(,“class”) [1] “list” “ggarrange”
De entre los 3 países con clientes en el banco, los clientes alemanes son los que mas abandonan, mientras que los 2 países restantes tienen una tasa de abandono similar en porcentaje en ambos casos.
De los clientes abandonan mas mujeres que hombres con escasa diferencia.
El abandono es similar entre los clientes que tienen tarjeta y los que no la tienen.
Hay una diferencia del doble significativa de abandono de los clientes que no son miembros activos con respecto a los clientes que si lo son.
La tasa de abandono de los clientes teniendo en cuenta el número de años de permanencia en el banco como cliente, es similar en todos los casos independientemente del numero de años que lleven estos en el mismo.
Es muy significativamente superior la tasa de abandono de los clientes que tienen entre 3 y 4 como numero de productos en el banco que los que tienen 1 o 2 de los mismos.(La tasa de abandono de los que tienen 4 es del 100%).
Ahora cambiamos los niveles de la estructura de la target por 0 y 1 en vez de 1 y 2.
levels(churn$Exited)<-c('0','1')
table(churn$Exited)##
## 0 1
## 7963 2037
churn2 <- recipe(Exited~.,
data = churn)%>%
step_discretize_xgb(all_numeric(), outcome = "Exited")%>%
prep()%>%
juiceFUNCIONES QUE VAMOS A APLICAR A LOS MODELOS
Funcion para crear una matriz de confusion
confusion<-function(real,scoring,umbral){
conf<-table(real,scoring>=umbral)
if(ncol(conf)==2) return(conf) else return(NULL)
}Funcion para calcular las metricas de los modelos: acierto, precision, cobertura y F1
metricas<-function(matriz_conf){
acierto <- (matriz_conf[1,1] + matriz_conf[2,2]) / sum(matriz_conf) *100
precision <- matriz_conf[2,2] / (matriz_conf[2,2] + matriz_conf[1,2]) *100
cobertura <- matriz_conf[2,2] / (matriz_conf[2,2] + matriz_conf[2,1]) *100
F1 <- 2*precision*cobertura/(precision+cobertura)
salida<-c(acierto,precision,cobertura,F1)
return(salida)
}Funcion para probar distintos umbrales y ver el efecto sobre precision y cobertura
umbrales<-function(real,scoring){
umbrales<-data.frame(umbral=rep(0,times=19),acierto=rep(0,times=19),precision=rep(0,times=19),cobertura=rep(0,times=19),F1=rep(0,times=19))
cont <- 1
for (cada in seq(0.05,0.95,by = 0.05)){
datos<-metricas(confusion(real,scoring,cada))
registro<-c(cada,datos)
umbrales[cont,]<-registro
cont <- cont + 1
}
return(umbrales)
}Funciones que calculan la curva ROC y el AUC
roc<-function(prediction){
r<-performance(prediction,'tpr','fpr')
plot(r)
}auc<-function(prediction){
a<-performance(prediction,'auc')
return(a@y.values[[1]])
}DIVISION DE LOS DATOS ENTRENAMIENTO Y PRUEBA
Creamos las particiones de entrenamiento y prueba, 70% y 30% respectivamente.
Vamos a aplicar un cross-validation a todos los modelos de vfold=10.
set.seed(45)
split_inicial <- initial_split(
data = churn2,
prop = 0.7,
strata = Exited
)
train <- training(split_inicial)
test <- testing(split_inicial)
# Hacemos preprocesamiento de datos
mod_rcp <- recipe(Exited~.,
data = train)
# y construimos el modelo
mod_rl <- logistic_reg(mode = 'classification') %>%
set_engine(engine = "glm",family=binomial(link='logit'))
set.seed(45)
cv_folds <- vfold_cv(
data = train,
v = 10,
strata = Exited
)
wf_m <- workflow()%>%
add_recipe(mod_rcp)%>%
add_model(mod_rl)
my_metrics <- metric_set(accuracy,roc_auc,kap)set.seed(45)
rlog <- wf_m%>%
fit_resamples(
resamples = cv_folds,
metrics = my_metrics,
control = control_resamples(save_pred = TRUE)
)collect_metrics(rlog)## # A tibble: 3 x 6
## .metric .estimator mean n std_err .config
## <chr> <chr> <dbl> <int> <dbl> <chr>
## 1 accuracy binary 0.858 10 0.00388 Preprocessor1_Model1
## 2 kap binary 0.486 10 0.0148 Preprocessor1_Model1
## 3 roc_auc binary 0.847 10 0.00561 Preprocessor1_Model1
show_best(rlog, metric = "roc_auc")## # A tibble: 1 x 6
## .metric .estimator mean n std_err .config
## <chr> <chr> <dbl> <int> <dbl> <chr>
## 1 roc_auc binary 0.847 10 0.00561 Preprocessor1_Model1
best_rlog2 <- rlog%>%
select_best("roc_auc")
wf_final <-
wf_m%>%
finalize_workflow(best_rlog2)
set.seed(45)
final_model_rl <- fit(object = wf_final, data = train)
model_extr1 <- final_model_rl%>%extract_fit_engine()
summary(model_extr1)##
## Call:
## stats::glm(formula = ..y ~ ., family = ~binomial(link = "logit"),
## data = data)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.4098 -0.5503 -0.3238 -0.1512 3.2082
##
## Coefficients:
## Estimate Std. Error z value
## (Intercept) -1.186333 0.259253 -4.576
## CreditScore[521,564) 0.135007 0.158926 0.849
## CreditScore[564,625) -0.164440 0.140742 -1.168
## CreditScore[625,648) -0.039350 0.164362 -0.239
## CreditScore[648,727) -0.265109 0.133078 -1.992
## CreditScore[727, Inf] 0.015334 0.137038 0.112
## GeographyGermany 0.961644 0.091350 10.527
## GeographySpain 0.049752 0.095092 0.523
## GenderMale -0.569275 0.073256 -7.771
## Age[35,37) 0.375462 0.161838 2.320
## Age[37,40) 0.598920 0.129947 4.609
## Age[40,42) 0.990112 0.147255 6.724
## Age[42,46) 1.503760 0.120664 12.462
## Age[46,53) 2.392812 0.117030 20.446
## Age[53, Inf] 2.427798 0.124060 19.570
## Tenure1 -0.173201 0.201117 -0.861
## Tenure2 -0.205613 0.202676 -1.014
## Tenure3 -0.234698 0.203052 -1.156
## Tenure4 -0.163163 0.203253 -0.803
## Tenure5 -0.313372 0.203507 -1.540
## Tenure6 -0.218890 0.203744 -1.074
## Tenure7 -0.277860 0.203017 -1.369
## Tenure8 -0.372963 0.203247 -1.835
## Tenure9 -0.329110 0.204302 -1.611
## Tenure10 -0.340140 0.234921 -1.448
## Balance[8.108e+04,1.043e+05) -0.430797 0.125569 -3.431
## Balance[1.043e+05,1.2e+05) 0.189442 0.118055 1.605
## Balance[1.2e+05,1.346e+05) -0.078067 0.121546 -0.642
## Balance[1.346e+05,1.443e+05) -0.254790 0.155394 -1.640
## Balance[1.443e+05,1.571e+05) -0.185218 0.160250 -1.156
## Balance[1.571e+05, Inf] 0.049836 0.150163 0.332
## NumOfProducts2 -1.529965 0.086785 -17.629
## NumOfProducts3 2.697017 0.230612 11.695
## NumOfProducts4 16.523091 197.073682 0.084
## HasCrCard1 -0.073725 0.079836 -0.923
## IsActiveMember1 -1.119328 0.077241 -14.491
## EstimatedSalary[2.051e+04,4.055e+04) 0.090590 0.167554 0.541
## EstimatedSalary[4.055e+04,8.082e+04) 0.106726 0.144300 0.740
## EstimatedSalary[8.082e+04,1.191e+05) 0.009553 0.146075 0.065
## EstimatedSalary[1.191e+05,1.596e+05) 0.046448 0.145106 0.320
## EstimatedSalary[1.596e+05, Inf] 0.101894 0.144955 0.703
## Pr(>|z|)
## (Intercept) 0.00000474040207982 ***
## CreditScore[521,564) 0.395605
## CreditScore[564,625) 0.242655
## CreditScore[625,648) 0.810789
## CreditScore[648,727) 0.046356 *
## CreditScore[727, Inf] 0.910906
## GeographyGermany < 0.0000000000000002 ***
## GeographySpain 0.600835
## GenderMale 0.00000000000000779 ***
## Age[35,37) 0.020342 *
## Age[37,40) 0.00000404717982816 ***
## Age[40,42) 0.00000000001770755 ***
## Age[42,46) < 0.0000000000000002 ***
## Age[46,53) < 0.0000000000000002 ***
## Age[53, Inf] < 0.0000000000000002 ***
## Tenure1 0.389131
## Tenure2 0.310347
## Tenure3 0.247741
## Tenure4 0.422115
## Tenure5 0.123596
## Tenure6 0.282670
## Tenure7 0.171107
## Tenure8 0.066501 .
## Tenure9 0.107202
## Tenure10 0.147648
## Balance[8.108e+04,1.043e+05) 0.000602 ***
## Balance[1.043e+05,1.2e+05) 0.108559
## Balance[1.2e+05,1.346e+05) 0.520686
## Balance[1.346e+05,1.443e+05) 0.101081
## Balance[1.443e+05,1.571e+05) 0.247761
## Balance[1.571e+05, Inf] 0.739981
## NumOfProducts2 < 0.0000000000000002 ***
## NumOfProducts3 < 0.0000000000000002 ***
## NumOfProducts4 0.933182
## HasCrCard1 0.355769
## IsActiveMember1 < 0.0000000000000002 ***
## EstimatedSalary[2.051e+04,4.055e+04) 0.588740
## EstimatedSalary[4.055e+04,8.082e+04) 0.459534
## EstimatedSalary[8.082e+04,1.191e+05) 0.947855
## EstimatedSalary[1.191e+05,1.596e+05) 0.748895
## EstimatedSalary[1.596e+05, Inf] 0.482095
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 7073.9 on 6998 degrees of freedom
## Residual deviance: 4888.4 on 6958 degrees of freedom
## AIC: 4970.4
##
## Number of Fisher Scoring iterations: 14
Vamos a seleccionar las variables altamente signifcativas(***), para volver a pasar el modelo solo con estas, asi pues:
df <- churn2%>%select(-CreditScore,-Tenure,-HasCrCard,-EstimatedSalary)set.seed(45)
split_inicial <- initial_split(
data = df,
prop = 0.7,
strata = Exited
)
train <- training(split_inicial)
test <- testing(split_inicial)
# Hacemos preprocesamiento de datos
mod_rcp <- recipe(Exited~.,
data = train)
# y construimos el modelo
mod_rl <- logistic_reg(mode = 'classification') %>%
set_engine(engine = "glm",family=binomial(link='logit'))
set.seed(45)
cv_folds <- vfold_cv(
data = train,
v = 10,
strata = Exited)
wf_m <- workflow()%>%
add_recipe(mod_rcp)%>%
add_model(mod_rl)
my_metrics <- metric_set(accuracy,roc_auc,kap)set.seed(45)
rlog <- wf_m%>%
fit_resamples(
resamples = cv_folds,
metrics = my_metrics,
control = control_resamples(save_pred = TRUE)
)collect_metrics(rlog)## # A tibble: 3 x 6
## .metric .estimator mean n std_err .config
## <chr> <chr> <dbl> <int> <dbl> <chr>
## 1 accuracy binary 0.856 10 0.00427 Preprocessor1_Model1
## 2 kap binary 0.479 10 0.0158 Preprocessor1_Model1
## 3 roc_auc binary 0.848 10 0.00582 Preprocessor1_Model1
show_best(rlog, metric = "roc_auc")## # A tibble: 1 x 6
## .metric .estimator mean n std_err .config
## <chr> <chr> <dbl> <int> <dbl> <chr>
## 1 roc_auc binary 0.848 10 0.00582 Preprocessor1_Model1
best_rlog3 <- rlog%>%
select_best("roc_auc")
wf_final <-
wf_m%>%
finalize_workflow(best_rlog3)
set.seed(45)
final_model_rl <- fit(object = wf_final, data = train)
model_extr2 <- final_model_rl%>%extract_fit_engine()
summary(model_extr2)##
## Call:
## stats::glm(formula = ..y ~ ., family = ~binomial(link = "logit"),
## data = data)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.3370 -0.5566 -0.3119 -0.1606 3.2073
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -1.50792 0.11298 -13.346 < 0.0000000000000002
## GeographyGermany 0.95511 0.09096 10.500 < 0.0000000000000002
## GeographySpain 0.03624 0.09467 0.383 0.701863
## GenderMale -0.56434 0.07296 -7.735 0.0000000000000103
## Age[35,37) 0.36688 0.16146 2.272 0.023074
## Age[37,40) 0.59914 0.12953 4.626 0.0000037354500132
## Age[40,42) 0.97065 0.14675 6.614 0.0000000000373779
## Age[42,46) 1.49764 0.12026 12.454 < 0.0000000000000002
## Age[46,53) 2.37758 0.11631 20.441 < 0.0000000000000002
## Age[53, Inf] 2.41459 0.12331 19.581 < 0.0000000000000002
## Balance[8.108e+04,1.043e+05) -0.42791 0.12492 -3.425 0.000614
## Balance[1.043e+05,1.2e+05) 0.18154 0.11764 1.543 0.122794
## Balance[1.2e+05,1.346e+05) -0.07128 0.12088 -0.590 0.555402
## Balance[1.346e+05,1.443e+05) -0.24685 0.15501 -1.593 0.111269
## Balance[1.443e+05,1.571e+05) -0.20058 0.15988 -1.255 0.209643
## Balance[1.571e+05, Inf] 0.04917 0.14957 0.329 0.742333
## NumOfProducts2 -1.52930 0.08646 -17.688 < 0.0000000000000002
## NumOfProducts3 2.62837 0.22844 11.506 < 0.0000000000000002
## NumOfProducts4 16.47790 197.25339 0.084 0.933425
## IsActiveMember1 -1.10792 0.07680 -14.427 < 0.0000000000000002
##
## (Intercept) ***
## GeographyGermany ***
## GeographySpain
## GenderMale ***
## Age[35,37) *
## Age[37,40) ***
## Age[40,42) ***
## Age[42,46) ***
## Age[46,53) ***
## Age[53, Inf] ***
## Balance[8.108e+04,1.043e+05) ***
## Balance[1.043e+05,1.2e+05)
## Balance[1.2e+05,1.346e+05)
## Balance[1.346e+05,1.443e+05)
## Balance[1.443e+05,1.571e+05)
## Balance[1.571e+05, Inf]
## NumOfProducts2 ***
## NumOfProducts3 ***
## NumOfProducts4
## IsActiveMember1 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 7073.9 on 6998 degrees of freedom
## Residual deviance: 4910.1 on 6979 degrees of freedom
## AIC: 4950.1
##
## Number of Fisher Scoring iterations: 14
y vemos ahora que todas las variables cuentan al menos en alguno de sus niveles con 3 estrellas(***).
#La Prediccion del scoring del modelo (conjunto test) sera:
rl_predict <- predict(model_extr2,test,type = "response")
head(rl_predict,20)## 1 2 3 4 5 6
## 0.935066782 0.239896785 0.131684892 0.030510660 0.017472975 0.368563217
## 7 8 9 10 11 12
## 0.047381495 0.294724868 0.008928977 0.037643880 0.183282657 0.256283706
## 13 14 15 16 17 18
## 0.689627315 0.409606253 0.113664400 0.077260927 0.045772279 0.445767073
## 19 20
## 0.488146069 0.719686411
El resultado es el scoring de la probabilidad de un cliente de que abandone la empresa.
Veamos graficamente la pinta que tiene:
plot(rl_predict~test$Exited)Calculamos ahora el pseudo R cuadrado, visualizando con anterioridad los coeficientes y significancia de acuerdo con lo que hemos decidido escoger para nuestro modelo:
psdo_R <- 1-(model_extr1$deviance/model_extr1$null.deviance)
psdo_R## [1] 0.3089571
El valor que nos da es muy bueno, este valor nos indica que con las variables que tenemos somos capaces de explicar aprox. el 69% de los motivos por los que un cliente abandona, hay solo aprox. un 30% que no somos capaces de explicar.
Ahora tenemos que transformar la probabilidad en una decision de si el cliente va a abandonar o se mantiene.
Con la funcion umbrales probamos diferentes cortes y para este caso vamos a determinar el punto de corte que maximiza a la F1(Punto intermedio entre precision y cobertura).
umb_rl<-umbrales(test$Exited,rl_predict)
umb_rl## umbral acierto precision cobertura F1
## 1 0.05 51.18294 28.91745 95.588235 44.402277
## 2 0.10 64.64512 35.29797 88.071895 50.397382
## 3 0.15 71.70943 40.21470 79.575163 53.428415
## 4 0.20 77.24092 46.30593 72.712418 56.579784
## 5 0.25 80.13995 50.98765 67.483660 58.087201
## 6 0.30 82.10596 55.30410 63.888889 59.287339
## 7 0.35 83.87204 60.95890 58.169935 59.531773
## 8 0.40 84.70510 65.45455 52.941176 58.536585
## 9 0.45 84.77174 68.23529 47.385621 55.930569
## 10 0.50 85.13829 72.67760 43.464052 54.396728
## 11 0.55 84.77174 73.99381 39.052288 51.122995
## 12 0.60 84.37188 77.60618 32.843137 46.153846
## 13 0.65 84.23859 81.44796 29.411765 43.217287
## 14 0.70 84.13862 84.34343 27.287582 41.234568
## 15 0.75 82.80573 88.09524 18.137255 30.081301
## 16 0.80 82.37254 89.52381 15.359477 26.220363
## 17 0.85 81.67278 91.89189 11.111111 19.825073
## 18 0.90 81.37288 100.00000 8.660131 15.939850
## 19 0.95 80.60646 100.00000 4.901961 9.345794
Seleccionamos la formula para hallarlo, aunque tambien podemos deducirlo de la tabla que seria el numero mas alto de la tabla F1 del cual el siguiente baja, el que de el umbral corresponderia a esa fila.
umbral_final_rl<-umb_rl[which.max(umb_rl$F1),1]
umbral_final_rl## [1] 0.35
Hallamos la matriz de confusion del umbral optimizado.
confusion(test$Exited,rl_predict,0.35)##
## real FALSE TRUE
## 0 2161 228
## 1 256 356
rl_metricas<-filter(umb_rl,umbral==umbral_final_rl)
rl_metricas## umbral acierto precision cobertura F1
## 1 0.35 83.87204 60.9589 58.16993 59.53177
Evaluamos la ROC
#creamos el objeto prediction
rl_prediction<-prediction(rl_predict,test$Exited)
#visualizamos la ROC
roc(rl_prediction)#Sacamos las metricas definitivas incluyendo el AUC
rl_metricas<-cbind(rl_metricas,AUC=round(auc(rl_prediction),2)*100)
print(t(rl_metricas))## [,1]
## umbral 0.35000
## acierto 83.87204
## precision 60.95890
## cobertura 58.16993
## F1 59.53177
## AUC 84.00000
set.seed(45)
split_inicial <- initial_split(
data = churn2,
prop = 0.7,
strata = Exited
)
train <- training(split_inicial)
test <- testing(split_inicial)
mod_rp <- decision_tree(
mode = 'classification',
cost_complexity=0.00001,
tree_depth = tune(),
min_n = tune()
)%>%
set_engine(engine = "rpart",parms = list(split="information"))
mod_rcp <- recipe(Exited~.,
data = train)
set.seed(45)
cv_folds <- vfold_cv(
data = train,
v = 10,
strata = Exited
)
wf_rp <- workflow()%>%
add_recipe(mod_rcp)%>%
add_model(mod_rp)registerDoParallel(cores = parallel::detectCores() - 1)
set.seed(45)
rp_fit <- wf_rp%>%tune_bayes(
resamples = cv_folds,
initial = 15,
iter = 20,
metrics = metric_set(roc_auc),
control = control_bayes(save_pred=TRUE,no_improve = 10, verbose = FALSE))
stopImplicitCluster()show_best(rp_fit, metric = "roc_auc")## # A tibble: 5 x 9
## tree_depth min_n .metric .estimator mean n std_err .config .iter
## <int> <int> <chr> <chr> <dbl> <int> <dbl> <chr> <int>
## 1 13 29 roc_auc binary 0.843 10 0.00816 Iter13 13
## 2 13 32 roc_auc binary 0.842 10 0.00762 Iter4 4
## 3 9 28 roc_auc binary 0.842 10 0.00750 Iter14 14
## 4 10 38 roc_auc binary 0.842 10 0.00790 Iter20 20
## 5 10 40 roc_auc binary 0.842 10 0.00809 Iter8 8
mejor_hiper1 <- select_best(rp_fit, metric="roc_auc")
mejor_hiper1## # A tibble: 1 x 3
## tree_depth min_n .config
## <int> <int> <chr>
## 1 13 29 Iter13
wf_finalrp <-
wf_rp%>%
finalize_workflow(mejor_hiper1)
set.seed(45)
final_model_rp <- fit(object = wf_finalrp, data = train)
model_ext <- final_model_rp%>%
extract_fit_engine()# Visualizamos el arbol del modelo.
fancyRpartPlot(model_ext)## Warning: labs do not fit even at cex 0.15, there may be some overplotting
Ahora revisamos la tabla donde podemos ver en primer termino el parametro de complejidad, error relativo,error absoluto, y desviacion estandar.
printcp(model_ext)##
## Classification tree:
## rpart::rpart(formula = ..y ~ ., data = data, parms = ~list(split = "information"),
## cp = ~0.00001, maxdepth = ~13, minsplit = min_rows(29, data))
##
## Variables actually used in tree construction:
## [1] Age Balance CreditScore EstimatedSalary
## [5] Gender Geography HasCrCard IsActiveMember
## [9] NumOfProducts Tenure
##
## Root node error: 1425/6999 = 0.2036
##
## n= 6999
##
## CP nsplit rel error xerror xstd
## 1 0.07017544 0 1.00000 1.00000 0.023641
## 2 0.02807018 3 0.78947 0.78947 0.021563
## 3 0.01684211 4 0.76140 0.75579 0.021184
## 4 0.00947368 6 0.72772 0.72772 0.020857
## 5 0.00701754 8 0.70877 0.72561 0.020832
## 6 0.00678363 9 0.70175 0.72702 0.020849
## 7 0.00421053 12 0.68140 0.71228 0.020673
## 8 0.00350877 14 0.67298 0.71509 0.020706
## 9 0.00327485 19 0.65404 0.71088 0.020656
## 10 0.00280702 22 0.64421 0.70807 0.020622
## 11 0.00245614 24 0.63860 0.70316 0.020562
## 12 0.00210526 26 0.63368 0.70877 0.020630
## 13 0.00175439 28 0.62947 0.70456 0.020579
## 14 0.00163743 34 0.61895 0.71298 0.020681
## 15 0.00154386 37 0.61404 0.71298 0.020681
## 16 0.00112281 43 0.60351 0.71368 0.020690
## 17 0.00105263 55 0.58175 0.70807 0.020622
## 18 0.00070175 59 0.57754 0.71789 0.020740
## 19 0.00060150 66 0.57263 0.74105 0.021014
## 20 0.00056140 74 0.56702 0.74105 0.021014
## 21 0.00035088 79 0.56421 0.74175 0.021022
## 22 0.00023392 81 0.56351 0.75018 0.021119
## 23 0.00001000 84 0.56281 0.76140 0.021248
En ella nos fijamos en el parametro de complejidad desde el que tomamos de partida 0.00001 abajo del todo hasta arriba de esa relacion, tenemos que mirar en la columna xerror(error absoluto), que empieza a bajar desde arriba.
En el momento que encontramos un numero donde empieza a cambiar la tendencia (a aumentar) ese seria, el parametro de complejidad que tenemos que tomar correspondiente a ese valor, y por ahi vamos a podar el arbol y ejecutar otra vez el modelo.
En este caso el parametro de complejidad que corresponderia a lo dicho anteriormente parece que minimiza aproximadamente en 0.001.
Tambien lo podemos interpretar viendo la representacion grafica (ejes: y- xerror x-cp) como vemos a continuacion.
plotcp(model_ext)Cambiamos el parametro de complejidad de partida a 0.001 e incluimos en el arbol que no tenga mas de 5 niveles.
set.seed(45)
split_inicial <- initial_split(
data = churn2,
prop = 0.7,
strata = Exited
)
train <- training(split_inicial)
test <- testing(split_inicial)
mod_rp <- decision_tree(
mode = 'classification',
cost_complexity=0.001,
tree_depth =5,
min_n = tune()
)%>%
set_engine(engine = "rpart",parms = list(split="information"))
mod_rcp <- recipe(Exited~.,
data = train)
set.seed(45)
cv_folds <- vfold_cv(
data = train,
v = 10,
strata = Exited)
wf_rp <- workflow()%>%
add_recipe(mod_rcp)%>%
add_model(mod_rp)registerDoParallel(cores = parallel::detectCores() - 1)
set.seed(45)
rp_fit <- wf_rp%>%tune_bayes(
resamples = cv_folds,
initial = 15,
iter = 30,
metrics = metric_set(roc_auc),
control = control_bayes(save_pred=TRUE,no_improve = 10, verbose = FALSE))
stopImplicitCluster()show_best(rp_fit, metric = "roc_auc")## # A tibble: 5 x 8
## min_n .metric .estimator mean n std_err .config .iter
## <int> <chr> <chr> <dbl> <int> <dbl> <chr> <int>
## 1 37 roc_auc binary 0.799 10 0.00920 Preprocessor1_Model06 0
## 2 38 roc_auc binary 0.799 10 0.00920 Iter6 6
## 3 39 roc_auc binary 0.799 10 0.00924 Preprocessor1_Model13 0
## 4 40 roc_auc binary 0.799 10 0.00925 Iter9 9
## 5 34 roc_auc binary 0.799 10 0.00913 Preprocessor1_Model01 0
mejor_hiper2 <- select_best(rp_fit, metric="roc_auc")
mejor_hiper2## # A tibble: 1 x 2
## min_n .config
## <int> <chr>
## 1 37 Preprocessor1_Model06
wf_finalrp <-
wf_rp%>%
finalize_workflow(mejor_hiper2)
set.seed(45)
final_model_rp <- fit(object = wf_finalrp, data = train)
model_extr2 <- final_model_rp%>%
extract_fit_engine()#Visualizamos tabla complejidad-errores.
printcp(model_extr2)##
## Classification tree:
## rpart::rpart(formula = ..y ~ ., data = data, parms = ~list(split = "information"),
## cp = ~0.001, maxdepth = ~5, minsplit = min_rows(37, data))
##
## Variables actually used in tree construction:
## [1] Age Balance Geography IsActiveMember NumOfProducts
## [6] Tenure
##
## Root node error: 1425/6999 = 0.2036
##
## n= 6999
##
## CP nsplit rel error xerror xstd
## 1 0.0701754 0 1.00000 1.00000 0.023641
## 2 0.0280702 3 0.78947 0.78947 0.021563
## 3 0.0168421 4 0.76140 0.75579 0.021184
## 4 0.0063158 6 0.72772 0.72772 0.020857
## 5 0.0035088 7 0.72140 0.73965 0.020997
## 6 0.0028070 10 0.71088 0.73895 0.020989
## 7 0.0024561 12 0.70526 0.73404 0.020932
## 8 0.0017544 14 0.70035 0.72842 0.020865
## 9 0.0010000 16 0.69684 0.72702 0.020849
Podemos ver una clara tendencia descendente de xerror(error absoluto) hasta llegar al parametro de complejidad 0.001 de partida y tanto su error relativo como el absoluto son bastane proximos en dicho parametro y en los demas.
Por tanto consideramos como buena dicha aproximacion.
#Visualizamos grafica error validacion cruzada y complejidad.
plotcp(model_extr2)# Visualizamos el arbol del modelo.
fancyRpartPlot(model_extr2)Reglas que podrian ser utiizadas para hacer implantacion del arbol.(y es Churn–la variable objetivo(dependiente) en las reglas del arbol)
rpart.rules(model_extr2, style = 'tall', cover = T,roundint=FALSE)## ..y is 0.04 with cover 5% when
## Age is [42,46) or [46,53) or [53, Inf]
## NumOfProducts is 2
## IsActiveMember is 1
## Balance is [-Inf,8.108e+04) or [1.443e+05,1.571e+05)
##
## ..y is 0.04 with cover 33% when
## Age is [-Inf,35) or [35,37) or [37,40) or [40,42)
## NumOfProducts is 2
##
## ..y is 0.11 with cover 3% when
## Age is [42,46) or [46,53)
## NumOfProducts is 2
## IsActiveMember is 0
## Balance is [-Inf,8.108e+04) or [1.443e+05,1.571e+05)
##
## ..y is 0.14 with cover 2% when
## Age is [42,46)
## NumOfProducts is 2
## Balance is [8.108e+04,1.043e+05) or [1.043e+05,1.2e+05) or [1.2e+05,1.346e+05) or [1.346e+05,1.443e+05) or [1.571e+05, Inf]
##
## ..y is 0.15 with cover 33% when
## Age is [-Inf,35) or [35,37) or [37,40) or [40,42)
## NumOfProducts is 1
##
## ..y is 0.25 with cover 7% when
## Age is [42,46) or [46,53) or [53, Inf]
## NumOfProducts is 1
## IsActiveMember is 1
## Geography is France or Spain
##
## ..y is 0.27 with cover 2% when
## Age is [46,53) or [53, Inf]
## NumOfProducts is 2
## IsActiveMember is 1
## Balance is [8.108e+04,1.043e+05) or [1.043e+05,1.2e+05) or [1.2e+05,1.346e+05) or [1.346e+05,1.443e+05) or [1.571e+05, Inf]
##
## ..y is 0.29 with cover 0% when
## Age is [-Inf,35) or [35,37) or [37,40) or [40,42)
## NumOfProducts is 3 or 4
## Balance is [-Inf,8.108e+04)
## Tenure is 4 or 7 or 8 or 9
##
## ..y is 0.48 with cover 3% when
## Age is [42,46)
## NumOfProducts is 1
## IsActiveMember is 0
##
## ..y is 0.53 with cover 3% when
## Age is [42,46) or [46,53) or [53, Inf]
## NumOfProducts is 1
## IsActiveMember is 1
## Geography is Germany
##
## ..y is 0.61 with cover 1% when
## Age is [46,53) or [53, Inf]
## NumOfProducts is 2
## IsActiveMember is 0
## Balance is [8.108e+04,1.043e+05) or [1.043e+05,1.2e+05) or [1.2e+05,1.346e+05) or [1.346e+05,1.443e+05) or [1.571e+05, Inf]
##
## ..y is 0.61 with cover 0% when
## Age is [53, Inf]
## NumOfProducts is 2
## IsActiveMember is 0
## Balance is [-Inf,8.108e+04) or [1.443e+05,1.571e+05)
##
## ..y is 0.76 with cover 0% when
## Age is [-Inf,35) or [35,37) or [37,40) or [40,42)
## NumOfProducts is 3 or 4
## Balance is [-Inf,8.108e+04)
## Tenure is 1 or 2 or 3 or 5 or 6 or 10
##
## ..y is 0.81 with cover 7% when
## Age is [46,53) or [53, Inf]
## NumOfProducts is 1 or 3 or 4
## IsActiveMember is 0
##
## ..y is 0.88 with cover 1% when
## Age is [42,46) or [46,53) or [53, Inf]
## NumOfProducts is 3 or 4
## IsActiveMember is 1
##
## ..y is 0.89 with cover 1% when
## Age is [-Inf,35) or [35,37) or [37,40) or [40,42)
## NumOfProducts is 3 or 4
## Balance is [8.108e+04,1.043e+05) or [1.043e+05,1.2e+05) or [1.2e+05,1.346e+05) or [1.346e+05,1.443e+05) or [1.443e+05,1.571e+05) or [1.571e+05, Inf]
##
## ..y is 1.00 with cover 0% when
## Age is [42,46)
## NumOfProducts is 3 or 4
## IsActiveMember is 0
Vemos el numero de nodos final por cliente.
rp_numnod <- rpart.predict(model_extr2,test,nn=T)
head(rp_numnod)## 0 1 nn
## 1 0.0000000 1.00000000 61
## 2 0.7505423 0.24945770 56
## 3 0.8560606 0.14393939 26
## 4 0.8560606 0.14393939 26
## 5 0.9564655 0.04353448 4
## 6 0.5181818 0.48181818 60
Calculo de prediccion del modelo (scorings).
rp_predict <- predict(model_extr2, test, type = 'prob')[,2]
head(rp_predict,20)## 1 2 3 4 5 6 7
## 1.00000000 0.24945770 0.14393939 0.14393939 0.04353448 0.48181818 0.04353448
## 8 9 10 11 12 13 14
## 0.15197568 0.04353448 0.15197568 0.04353448 0.15197568 0.80901288 0.24945770
## 15 16 17 18 19 20
## 0.15197568 0.04353448 0.04353448 0.15197568 0.48181818 0.80901288
Veamos graficamente como se ve:
plot(rp_predict~test$Exited)Probamos cortes con la funcion umbrales, visualizamos la tabla respectiva.
umb_rp<-umbrales(test$Exited,rp_predict)
umb_rp## umbral acierto precision cobertura F1
## 1 0.05 56.51450 30.99287 92.320261 46.406571
## 2 0.10 56.51450 30.99287 92.320261 46.406571
## 3 0.15 59.04698 31.92736 89.052288 47.003019
## 4 0.20 81.13962 53.16804 63.071895 57.698057
## 5 0.25 84.07198 62.78626 53.758170 57.922535
## 6 0.30 84.87171 66.73729 51.470588 58.118081
## 7 0.35 84.87171 66.73729 51.470588 58.118081
## 8 0.40 84.87171 66.73729 51.470588 58.118081
## 9 0.45 84.87171 66.73729 51.470588 58.118081
## 10 0.50 85.10497 71.65354 44.607843 54.984894
## 11 0.55 85.07164 76.45161 38.725490 51.409978
## 12 0.60 85.07164 76.45161 38.725490 51.409978
## 13 0.65 85.00500 80.45113 34.967320 48.747153
## 14 0.70 85.00500 80.45113 34.967320 48.747153
## 15 0.75 85.00500 80.45113 34.967320 48.747153
## 16 0.80 85.03832 82.47012 33.823529 47.972190
## 17 0.85 81.50616 92.53731 10.130719 18.262150
## 18 0.90 80.00666 92.85714 2.124183 4.153355
## 19 0.95 80.00666 92.85714 2.124183 4.153355
Lo hallamos directamente con la siguiente sintaxis.
umbral_final_rp<-umb_rp[which.max(umb_rp$F1),1]
umbral_final_rp## [1] 0.3
Hallamos la matriz de confusion del umbral optimizado.
confusion(test$Exited,rp_predict,0.3)##
## real FALSE TRUE
## 0 2232 157
## 1 297 315
rp_metricas<-filter(umb_rp,umbral==umbral_final_rp)
rp_metricas## umbral acierto precision cobertura F1
## 1 0.3 84.87171 66.73729 51.47059 58.11808
Evaluamos la ROC
#creamos el objeto prediction
rp_prediction<-prediction(rp_predict,test$Exited)
#visualizamos la ROC
roc(rp_prediction)#Sacamos las metricas definitivas incluyendo el AUC
rp_metricas<-cbind(rp_metricas,AUC=round(auc(rp_prediction),2)*100)
print(t(rp_metricas))## [,1]
## umbral 0.30000
## acierto 84.87171
## precision 66.73729
## cobertura 51.47059
## F1 58.11808
## AUC 82.00000
set.seed(45)
split_inicial <- initial_split(
data = churn2,
prop = 0.7,
strata = Exited
)
train <- training(split_inicial)
test <- testing(split_inicial)
mod_rf <- rand_forest(
mode = "classification",
mtry = tune(),
trees = 500,
min_n = tune()
)%>%
set_engine(engine = "randomForest",importance=T)
set.seed(45)
cv_folds <- vfold_cv(
data = train,
v = 10,
strata = Exited)
mod_rcp <- recipe(Exited~.,
data = train)
wf_rf <- workflow()%>%
add_recipe(mod_rcp)%>%
add_model(mod_rf)
hiperpar_grid=grid_max_entropy(
# Rango de busqueda para cada hiperparametro
mtry(range = c(1L, 10L), trans = NULL),
min_n(range = c(2L, 100L), trans = NULL),
# Numero de combinaciones totales
size = 100
)registerDoParallel(cores = parallel::detectCores() - 1)
set.seed(45)
rf_fit <- wf_rf%>%tune_grid(
resamples = cv_folds,
metrics = metric_set(roc_auc),
control = control_resamples(save_pred = TRUE),
grid = hiperpar_grid)
stopImplicitCluster()show_best(rf_fit, metric = "roc_auc")## # A tibble: 5 x 8
## mtry min_n .metric .estimator mean n std_err .config
## <int> <int> <chr> <chr> <dbl> <int> <dbl> <chr>
## 1 5 36 roc_auc binary 0.862 10 0.00684 Preprocessor1_Model34
## 2 5 29 roc_auc binary 0.862 10 0.00620 Preprocessor1_Model20
## 3 5 25 roc_auc binary 0.862 10 0.00623 Preprocessor1_Model07
## 4 5 33 roc_auc binary 0.862 10 0.00647 Preprocessor1_Model69
## 5 5 20 roc_auc binary 0.862 10 0.00649 Preprocessor1_Model85
mejor_hiper3 <- select_best(rf_fit, metric="roc_auc")
mejor_hiper3## # A tibble: 1 x 3
## mtry min_n .config
## <int> <int> <chr>
## 1 5 36 Preprocessor1_Model34
wf_finalrf <-
wf_rf%>%
finalize_workflow(mejor_hiper3)
set.seed(45)
final_model_rf <- fit(object = wf_finalrf, data = train)
model_xtr <- final_model_rf%>%
extract_fit_engine()Como hay 2 criterios, creamos una unica variable agregada para tener una mejor idea de la importancia de cada variable.
importancia <- randomForest::importance(model_xtr)[,3:4]
#normalizamos para poner las dos variables en la misma escala. los valores negativos son las que menos predicen y los positivos las que mas
importancia_norm <- as.data.frame(scale(importancia))
#creamos una unica variable como suma de las otras
importancia_norm <- importancia_norm %>% mutate(
Variable = rownames(importancia_norm),
Imp_tot = MeanDecreaseAccuracy + MeanDecreaseGini) %>%
mutate(Imp_tot = Imp_tot + abs(min(Imp_tot))) %>%
arrange(desc(Imp_tot)) %>%
select(Variable,Imp_tot,MeanDecreaseAccuracy,MeanDecreaseGini)
#hacemos un grafico para ver la curva de caida de importancia
ggplot(importancia_norm, aes(reorder(Variable,-Imp_tot),Imp_tot)) + geom_bar(stat = "identity") + theme(axis.text.x = element_text(angle = 90,size = 7))importancia_norm## Variable Imp_tot MeanDecreaseAccuracy MeanDecreaseGini
## Age Age 5.2719011 1.58237871 1.94997401
## NumOfProducts NumOfProducts 5.1802924 1.80058147 1.64016262
## IsActiveMember IsActiveMember 2.2400630 0.57662585 -0.07611118
## Balance Balance 1.5377288 -0.11100592 -0.09081359
## Geography Geography 1.2275198 -0.06557148 -0.44645704
## Tenure Tenure 1.0650326 -0.66429186 -0.01022388
## CreditScore CreditScore 0.3753788 -0.78527792 -0.57889162
## Gender Gender 0.2533321 -0.64085754 -0.84535867
## EstimatedSalary EstimatedSalary 0.2442347 -0.88991090 -0.60540274
## HasCrCard HasCrCard 0.0000000 -0.80267041 -0.93687792
Vemos el grafico y nuestros datos en la tabla de forma decreciente segun nivel de importancia, y vemos donde puede estar un corte claro, diferencia de una con respecto a la anterior, y vemos de la variable ‘Tenure’ a ‘CreditScore’ un corte interesante.
Vamos a deseleccionar de nuestros datos esas 4 variables sobrantes por debajo del corte de ‘Tenure’ y nos quedaremos con esta y las de la parte superior.
churn3 <- churn2%>%select(-CreditScore,-Gender,-EstimatedSalary,-HasCrCard)y ahora vamos a realizar de nuevo el modelo con las nuevas variables.
set.seed(45)
split_inicial <- initial_split(
data = churn3,
prop = 0.7,
strata = Exited
)
train <- training(split_inicial)
test <- testing(split_inicial)
mod_rf <- rand_forest(
mode = "classification",
mtry = tune(),
trees = 500,
min_n = tune()
) %>%
set_engine(engine = "randomForest",importance=T)
mod_rcp <- recipe(Exited~.,
data = train)
set.seed(45)
cv_folds <- vfold_cv(
data = train,
v = 10,
strata = Exited)
wf_rf <- workflow()%>%
add_recipe(mod_rcp)%>%
add_model(mod_rf)
hiperpar_grid=grid_max_entropy(
# Rango de busqueda para cada hiperparametro
mtry(range = c(1L, 10L), trans = NULL),
min_n(range = c(2L, 100L), trans = NULL),
# Numero de combinaciones totales
size = 100
)registerDoParallel(cores = parallel::detectCores() - 1)
set.seed(45)
rf_fit <- wf_rf%>%tune_grid(
resamples = cv_folds,
metrics = metric_set(roc_auc),
control = control_resamples(save_pred = TRUE),
grid = hiperpar_grid)
stopImplicitCluster()show_best(rf_fit, metric = "roc_auc")## # A tibble: 5 x 8
## mtry min_n .metric .estimator mean n std_err .config
## <int> <int> <chr> <chr> <dbl> <int> <dbl> <chr>
## 1 5 25 roc_auc binary 0.855 10 0.00693 Preprocessor1_Model07
## 2 3 27 roc_auc binary 0.854 10 0.00791 Preprocessor1_Model21
## 3 3 37 roc_auc binary 0.854 10 0.00756 Preprocessor1_Model52
## 4 4 44 roc_auc binary 0.854 10 0.00731 Preprocessor1_Model45
## 5 2 37 roc_auc binary 0.853 10 0.00786 Preprocessor1_Model04
mejor_hiper4 <- select_best(rf_fit, metric="roc_auc")
mejor_hiper4## # A tibble: 1 x 3
## mtry min_n .config
## <int> <int> <chr>
## 1 5 25 Preprocessor1_Model07
wf_finalrf <-
wf_rf%>%
finalize_workflow(mejor_hiper4)
set.seed(45)
final_model_rf <- fit(object = wf_finalrf, data = train)
model_extr3 <- final_model_rf%>%
extract_fit_engine()Calculo de prediccion del modelo (scorings).
rf_predict <- predict(model_extr3, test, type = 'prob')[,2]
head(rf_predict,20)## 1 2 3 4 5 6 7 8 9 10 11 12 13
## 0.974 0.120 0.070 0.074 0.010 0.514 0.000 0.096 0.000 0.000 0.106 0.262 0.300
## 14 15 16 17 18 19 20
## 0.118 0.178 0.002 0.000 0.408 0.520 0.992
Veamos graficamente como se ve:
plot(rf_predict~test$Exited)Probamos cortes con la funcion umbrales, visualizamos la tabla respectiva.
umb_rf<-umbrales(test$Exited,rf_predict)
umb_rf## umbral acierto precision cobertura F1
## 1 0.05 74.94169 43.47015 76.14379 55.34442
## 2 0.10 79.14029 49.18415 68.95425 57.41497
## 3 0.15 81.87271 54.65753 65.19608 59.46349
## 4 0.20 83.53882 59.24765 61.76471 60.48000
## 5 0.25 84.30523 61.80905 60.29412 61.04218
## 6 0.30 84.73842 64.31227 56.53595 60.17391
## 7 0.35 85.07164 66.53226 53.92157 59.56679
## 8 0.40 85.20493 68.18182 51.47059 58.65922
## 9 0.45 85.17161 69.64706 48.36601 57.08775
## 10 0.50 85.23825 71.50127 45.91503 55.92040
## 11 0.55 85.30490 73.29700 43.95425 54.95403
## 12 0.60 85.17161 76.01246 39.86928 52.30439
## 13 0.65 85.27158 77.59740 39.05229 51.95652
## 14 0.70 85.23825 79.23875 37.41830 50.83241
## 15 0.75 85.07164 80.37037 35.45752 49.20635
## 16 0.80 84.77174 82.15768 32.35294 46.42438
## 17 0.85 84.70510 85.25346 30.22876 44.63209
## 18 0.90 84.03865 85.18519 26.30719 40.19975
## 19 0.95 82.77241 83.68794 19.28105 31.34130
Lo hallamos directamente con la siguiente sintaxis.
umbral_final_rf<-umb_rf[which.max(umb_rf$F1),1]
umbral_final_rf## [1] 0.25
Hallamos la matriz de confusion del umbral optimizado.
confusion(test$Exited,rf_predict,0.25)##
## real FALSE TRUE
## 0 2161 228
## 1 243 369
rf_metricas<-filter(umb_rf,umbral==umbral_final_rf)
rf_metricas## umbral acierto precision cobertura F1
## 1 0.25 84.30523 61.80905 60.29412 61.04218
#Evaluamos la ROC
#creamos el objeto prediction
rf_prediction<-prediction(rf_predict,test$Exited)
#visualizamos la ROC
roc(rf_prediction)#Sacamos las metricas definitivas incluyendo el AUC
rf_metricas<-cbind(rf_metricas,AUC=round(auc(rf_prediction),2)*100)
print(t(rf_metricas))## [,1]
## umbral 0.25000
## acierto 84.30523
## precision 61.80905
## cobertura 60.29412
## F1 61.04218
## AUC 83.00000
set.seed(45)
split_inicial <- initial_split(
data = churn2,
prop = 0.7,
strata = Exited
)
train <- training(split_inicial)
test <- testing(split_inicial)
mod_xg <- boost_tree(mtry = tune(),
min_n = tune(),
tree_depth = tune(),
trees = 500,
learn_rate = 0.1,
sample_size = 0.8) %>%
set_engine("xgboost") %>%
set_mode("classification")
set.seed(3001)
cv_folds <- vfold_cv(
data = train,
v = 10,
strata = Exited)
wf_xg<- workflow()%>%
add_formula(Exited~.)%>%
add_model(mod_xg)
# Parametros para tuning
params_xgb <- parameters(
finalize(mtry(), x = train[, -1]),
min_n(range = c(2L, 50L)),
tree_depth(range = c(3L, 8L))
)
# Grid
set.seed(45)
grid_xgb <- params_xgb %>%
grid_max_entropy(size = 10)registerDoParallel(cores = parallel::detectCores() - 1)
set.seed(2631)
xg_fit <- wf_xg%>%tune_grid(
resamples = cv_folds,
metrics = metric_set(roc_auc),
grid = grid_xgb,
control = control_grid(save_pred = TRUE))
stopImplicitCluster()show_best(xg_fit, metric = "roc_auc")## # A tibble: 5 x 9
## mtry min_n tree_depth .metric .estimator mean n std_err .config
## <int> <int> <int> <chr> <chr> <dbl> <int> <dbl> <chr>
## 1 10 50 5 roc_auc binary 0.860 10 0.00374 Preprocessor1_M~
## 2 8 38 7 roc_auc binary 0.860 10 0.00408 Preprocessor1_M~
## 3 8 31 5 roc_auc binary 0.860 10 0.00398 Preprocessor1_M~
## 4 4 31 5 roc_auc binary 0.860 10 0.00402 Preprocessor1_M~
## 5 8 11 3 roc_auc binary 0.859 10 0.00440 Preprocessor1_M~
mejor_hiper5 <- select_best(xg_fit, metric="roc_auc")
mejor_hiper5## # A tibble: 1 x 4
## mtry min_n tree_depth .config
## <int> <int> <int> <chr>
## 1 10 50 5 Preprocessor1_Model05
wf_finalxg <-
wf_rf%>%
finalize_workflow(mejor_hiper5)
set.seed(45)
final_model_xg <- fit(object = wf_finalxg, data = train)## Warning: 10 columns were requested but there were 6 predictors in the data. 6
## will be used.
model_extr4 <- final_model_xg%>%
extract_fit_engine()Calculo de prediccion del modelo (scorings).
xg_predict <- predict(model_extr4, test, type = 'prob')[,2]
head(xg_predict,20)## 1 2 3 4 5 6 7 8 9 10 11 12 13
## 0.942 0.102 0.046 0.016 0.000 0.524 0.000 0.044 0.000 0.000 0.038 0.222 0.346
## 14 15 16 17 18 19 20
## 0.048 0.096 0.000 0.000 0.392 0.546 1.000
Veamos graficamente como se ve:
plot(xg_predict~test$Exited)Probamos cortes con la funcion umbrales, visualizamos la tabla respectiva.
umb_xg<-umbrales(test$Exited,xg_predict)
umb_xg## umbral acierto precision cobertura F1
## 1 0.05 79.30690 49.48216 70.26144 58.06887
## 2 0.10 82.40586 55.88235 65.19608 60.18100
## 3 0.15 83.50550 59.27100 61.11111 60.17699
## 4 0.20 84.17194 61.79002 58.66013 60.18441
## 5 0.25 84.53849 63.60294 56.53595 59.86159
## 6 0.30 84.83839 65.24272 54.90196 59.62733
## 7 0.35 85.13829 67.29167 52.77778 59.15751
## 8 0.40 85.40487 69.41964 50.81699 58.67925
## 9 0.45 85.60480 71.32701 49.18301 58.22050
## 10 0.50 85.27158 71.03960 46.89542 56.49606
## 11 0.55 85.10497 71.88329 44.28105 54.80283
## 12 0.60 85.30490 74.92711 41.99346 53.82199
## 13 0.65 85.47151 77.32919 40.68627 53.31906
## 14 0.70 85.37154 78.73754 38.72549 51.91676
## 15 0.75 85.03832 78.59649 36.60131 49.94426
## 16 0.80 84.97168 79.92565 35.13072 48.80817
## 17 0.85 84.73842 82.08333 32.18954 46.24413
## 18 0.90 84.13862 83.00971 27.94118 41.80929
## 19 0.95 83.40553 85.18519 22.54902 35.65891
Lo hallamos directamente con la siguiente sintaxis.
umbral_final_xg<-umb_xg[which.max(umb_xg$F1),1]
umbral_final_xg## [1] 0.2
Hallamos la matriz de confusion del umbral optimizado.
confusion(test$Exited,xg_predict,0.2)##
## real FALSE TRUE
## 0 2167 222
## 1 253 359
xg_metricas<-filter(umb_xg,umbral==umbral_final_xg)
xg_metricas## umbral acierto precision cobertura F1
## 1 0.2 84.17194 61.79002 58.66013 60.18441
Evaluamos la ROC
#creamos el objeto prediction
xg_prediction<-prediction(xg_predict,test$Exited)
#visualizamos la ROC
roc(xg_prediction)#Sacamos las metricas definitivas incluyendo el AUC
xg_metricas<-cbind(xg_metricas,AUC=round(auc(xg_prediction),2)*100)
print(t(xg_metricas))## [,1]
## umbral 0.20000
## acierto 84.17194
## precision 61.79002
## cobertura 58.66013
## F1 60.18441
## AUC 83.00000
comparativa <- rbind(rl_metricas,rp_metricas,rf_metricas,xg_metricas)
rownames(comparativa) <- c('Regresion Logistica', 'Arbol Decision', 'Random Forest', 'Extra Gradiant Boosting')
t(comparativa)## Regresion Logistica Arbol Decision Random Forest
## umbral 0.35000 0.30000 0.25000
## acierto 83.87204 84.87171 84.30523
## precision 60.95890 66.73729 61.80905
## cobertura 58.16993 51.47059 60.29412
## F1 59.53177 58.11808 61.04218
## AUC 84.00000 82.00000 83.00000
## Extra Gradiant Boosting
## umbral 0.20000
## acierto 84.17194
## precision 61.79002
## cobertura 58.66013
## F1 60.18441
## AUC 83.00000
rl_auc <- rlog%>%collect_predictions(summarize=TRUE,parameters=best_rlog2)%>%
roc_curve(Exited,.pred_0)%>%
mutate(model = "Regresion Logistica")
rp_auc <- rp_fit%>%collect_predictions(summarize=TRUE,parameters=mejor_hiper2)%>%
roc_curve(Exited,.pred_0)%>%
mutate(model = "Arbol de Decision")
rf_auc <- rf_fit%>%collect_predictions(summarize=TRUE,parameters=mejor_hiper4)%>%
roc_curve(Exited,.pred_0)%>%
mutate(model = "Random Forest")
xg_auc <- xg_fit%>%collect_predictions(summarize=TRUE,parameters=mejor_hiper5)%>%
roc_curve(Exited,.pred_0)%>%
mutate(model = "Xgboost")
bind_rows(rl_auc,rp_auc,rf_auc,xg_auc)%>% # Dibuja las 4 curvas AUC juntas
ggplot(aes(x=1-specificity, y = sensitivity, col = model))+ # Especifica el eje x e y dibuja la columna para usar el nombre de la metrica
geom_path(lwd = 1.5, alpha = 0.8) + # Conecta a las 4 AUC, lwd = anchura de linea, alpha = Color transparencia del valor.
geom_abline(lty = 3) + # abline del dibujo, lty= linea tipo
coord_equal() + # Asegura que los rangos de los ejes sean iguales
scale_color_viridis_d(option = "plasma", end = .6)En base a la comparativa el modelo de Regresion Logistica ha obtenido levemente mejores resultados, asi que seleccionamos este modelo ganador en la comparativa.
Añadimos la variable scoring a nuestro dataframe en base a la prediccion del modelo seleccionado y guardamos el modelo final.
churn2$SCORING_CHURN <- predict(model_extr1,churn2,type='response')
saveRDS(model_extr1,'modelo_final.rds')# Añadimos las variables apartadas que no intervinieron en la ejecucion del modelo
# y visualizamos la tabla con el scoring añadido.
dt2<-bind_cols(dt1, churn2)
kable(head(dt2,6), booktabs = T) %>%
kable_styling(font_size=8)| CustomerId | Surname | CreditScore | Geography | Gender | Age | Tenure | Balance | NumOfProducts | HasCrCard | IsActiveMember | EstimatedSalary | Exited | SCORING_CHURN |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 15634602 | Hargrave | [564,625) | France | Female | [42,46) | 2 | [-Inf,8.108e+04) | 1 | 1 | 1 | [8.082e+04,1.191e+05) | 1 | 0.2251112 |
| 15647311 | Hill | [564,625) | Spain | Female | [40,42) | 1 | [8.108e+04,1.043e+05) | 1 | 0 | 1 | [8.082e+04,1.191e+05) | 0 | 0.1166358 |
| 15619304 | Onio | [-Inf,521) | France | Female | [42,46) | 8 | [1.571e+05, Inf] | 3 | 1 | 0 | [8.082e+04,1.191e+05) | 1 | 0.9325883 |
| 15701354 | Boni | [648,727) | France | Female | [37,40) | 1 | [-Inf,8.108e+04) | 2 | 0 | 0 | [8.082e+04,1.191e+05) | 0 | 0.0726866 |
| 15737888 | Mitchell | [727, Inf] | Spain | Female | [42,46) | 2 | [1.2e+05,1.346e+05) | 1 | 1 | 1 | [4.055e+04,8.082e+04) | 0 | 0.2714073 |
| 15574012 | Chu | [625,648) | Spain | Male | [42,46) | 8 | [1.043e+05,1.2e+05) | 2 | 1 | 0 | [1.191e+05,1.596e+05) | 1 | 0.1210836 |