RESUMEN
El fraude con tarjetas de crédito le cuesta a los bancos millones de dólares al año en reembolsos, honorarios legales y mitigación. Negar las transacciones fraudulentas antes de que ocurran reduciría en gran medida los gastos.
En este documento, utilizo un conjunto de datos disponible públicamente de 6,3 millones de transacciones con tarjeta de crédito y cada uno se clasifica como Fraude o No Fraude.
La variable de resultado es categórica y binaria.
Según el tipo de variable Resultado, utilizo cuatro modelos diferentes de aprendizaje automático para determinar cuál se ajusta mejor al conjunto de prueba en función de la curva ROC y el Accuracy. Los falsos positivos son una preocupación importante ya que negaremos cualquier transacción que se marque de manera fraudulenta, obligando al cliente a llamar al servicio al cliente para completar la transacción. Pruebo cada modelo con 100,000 transacciones aleatorias clasificadas como Sin Fraude para estimar la tasa de falsos positivos. También capturo el tiempo para procesar las 100,000 transacciones para estimar la intensidad computacional de cada modelo.
Los resultados son increibles. Casi todos los modelos seleccionaron el 97% + de las transacciones de Fraude correctamente.
Exploratory Data Analysis
Cargamos librerias
library(plyr)
library(tidyverse)
## ── Attaching packages ───────────────────────────────────────────────── tidyverse 1.3.0 ──
## ✓ ggplot2 3.3.0 ✓ purrr 0.3.4
## ✓ tibble 2.1.3 ✓ dplyr 0.8.5
## ✓ tidyr 1.0.2 ✓ stringr 1.4.0
## ✓ readr 1.3.1 ✓ forcats 0.5.0
## Warning: package 'purrr' was built under R version 3.6.2
## ── Conflicts ──────────────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::arrange() masks plyr::arrange()
## x purrr::compact() masks plyr::compact()
## x dplyr::count() masks plyr::count()
## x dplyr::failwith() masks plyr::failwith()
## x dplyr::filter() masks stats::filter()
## x dplyr::id() masks plyr::id()
## x dplyr::lag() masks stats::lag()
## x dplyr::mutate() masks plyr::mutate()
## x dplyr::rename() masks plyr::rename()
## x dplyr::summarise() masks plyr::summarise()
## x dplyr::summarize() masks plyr::summarize()
library(caret)
## Loading required package: lattice
##
## Attaching package: 'caret'
## The following object is masked from 'package:purrr':
##
## lift
library(GGally)
## Warning: package 'GGally' was built under R version 3.6.2
## Registered S3 method overwritten by 'GGally':
## method from
## +.gg ggplot2
##
## Attaching package: 'GGally'
## The following object is masked from 'package:dplyr':
##
## nasa
library(stringr)
library(rattle)
## Warning: package 'rattle' was built under R version 3.6.2
## Loading required package: bitops
## Rattle: A free graphical interface for data science with R.
## Versión 5.4.0 Copyright (c) 2006-2020 Togaware Pty Ltd.
## Escriba 'rattle()' para agitar, sacudir y rotar sus datos.
library(pROC)
## Type 'citation("pROC")' for a citation.
##
## Attaching package: 'pROC'
## The following objects are masked from 'package:stats':
##
## cov, smooth, var
library(ROCR)
## Warning: package 'ROCR' was built under R version 3.6.2
Plantamos semilla aleatoria para reproducibilidad
set.seed(317)
Cargamos el dataset y hacemos un Glimpse
df <- read_csv("PS_20174392719_1491204439457_log.csv")
## Parsed with column specification:
## cols(
## step = col_double(),
## type = col_character(),
## amount = col_double(),
## nameOrig = col_character(),
## oldbalanceOrg = col_double(),
## newbalanceOrig = col_double(),
## nameDest = col_character(),
## oldbalanceDest = col_double(),
## newbalanceDest = col_double(),
## isFraud = col_double(),
## isFlaggedFraud = col_double()
## )
glimpse(df)
## Observations: 6,362,620
## Variables: 11
## $ step <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ type <chr> "PAYMENT", "PAYMENT", "TRANSFER", "CASH_OUT", "PAYMENT…
## $ amount <dbl> 9839.64, 1864.28, 181.00, 181.00, 11668.14, 7817.71, 7…
## $ nameOrig <chr> "C1231006815", "C1666544295", "C1305486145", "C8400836…
## $ oldbalanceOrg <dbl> 170136.0, 21249.0, 181.0, 181.0, 41554.0, 53860.0, 183…
## $ newbalanceOrig <dbl> 160296.36, 19384.72, 0.00, 0.00, 29885.86, 46042.29, 1…
## $ nameDest <chr> "M1979787155", "M2044282225", "C553264065", "C38997010…
## $ oldbalanceDest <dbl> 0, 0, 0, 21182, 0, 0, 0, 0, 0, 41898, 10845, 0, 0, 0, …
## $ newbalanceDest <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, …
## $ isFraud <dbl> 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ isFlaggedFraud <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
Las variables nameOrig y nameDest son técnicamente categóricas, pero hay demasiadas para ser útiles para modelar. El prefijo de letra que tienen puede ser importante.
fraud_df <- df %>%
mutate(name_orig_first = str_sub(nameOrig,1,1)) %>%
mutate(name_dest_first = str_sub(nameDest, 1, 1)) %>%
select(-nameOrig, -nameDest)
Cuantos prefijos tiene NameDest?
unique(fraud_df$name_dest_first)
## [1] "M" "C"
Hay dos prefijos en nameDest; “C” y “M”, por lo que se convertirá en un factor.
fraud_df$name_dest_first <- as.factor(fraud_df$name_dest_first)
table(fraud_df$name_dest_first)
##
## C M
## 4211125 2151495
Cuantos prefijos tiene NomOrig?
unique(fraud_df$name_orig_first)
## [1] "C"
Hay un solo prefijo “C” en nameOrig, por lo que no es útil y se eliminará junto con isFlaggedFraud, que se parece a la predicción de otra persona.
Reorganizo las columnas de una manera más lógica.
fraud_df2 <- fraud_df %>%
select(-name_orig_first, -isFlaggedFraud) %>%
select(isFraud, type, step, everything()) # El segundo select sirve para posicionar las variables al principio
glimpse(fraud_df2)
## Observations: 6,362,620
## Variables: 9
## $ isFraud <dbl> 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ type <chr> "PAYMENT", "PAYMENT", "TRANSFER", "CASH_OUT", "PAYMEN…
## $ step <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
## $ amount <dbl> 9839.64, 1864.28, 181.00, 181.00, 11668.14, 7817.71, …
## $ oldbalanceOrg <dbl> 170136.0, 21249.0, 181.0, 181.0, 41554.0, 53860.0, 18…
## $ newbalanceOrig <dbl> 160296.36, 19384.72, 0.00, 0.00, 29885.86, 46042.29, …
## $ oldbalanceDest <dbl> 0, 0, 0, 21182, 0, 0, 0, 0, 0, 41898, 10845, 0, 0, 0,…
## $ newbalanceDest <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,…
## $ name_dest_first <fct> M, M, C, C, M, M, M, M, M, C, C, M, M, M, M, C, M, M,…
Las columnas type & isFraud son categóricas y se cambiarán a factores
fraud_df2$type <- as.factor(fraud_df2$type)
fraud_df2$isFraud <- as.factor(fraud_df2$isFraud)
Algunos modelos que usaremos no tienen me gusta 1 y 0 en el resultado, por lo que los recodificaremos.
fraud_df2$isFraud <- recode_factor(fraud_df2$isFraud, `0` = "No", `1` = "Yes")
# Recodificamos los 0 por "No" y los 1 por "Yes"
Vamos a hacer un Summary.
summary(fraud_df2)
## isFraud type step amount
## No :6354407 CASH_IN :1399284 Min. : 1.0 Min. : 0
## Yes: 8213 CASH_OUT:2237500 1st Qu.:156.0 1st Qu.: 13390
## DEBIT : 41432 Median :239.0 Median : 74872
## PAYMENT :2151495 Mean :243.4 Mean : 179862
## TRANSFER: 532909 3rd Qu.:335.0 3rd Qu.: 208721
## Max. :743.0 Max. :92445517
## oldbalanceOrg newbalanceOrig oldbalanceDest newbalanceDest
## Min. : 0 Min. : 0 Min. : 0 Min. : 0
## 1st Qu.: 0 1st Qu.: 0 1st Qu.: 0 1st Qu.: 0
## Median : 14208 Median : 0 Median : 132706 Median : 214661
## Mean : 833883 Mean : 855114 Mean : 1100702 Mean : 1224996
## 3rd Qu.: 107315 3rd Qu.: 144258 3rd Qu.: 943037 3rd Qu.: 1111909
## Max. :59585040 Max. :49585040 Max. :356015889 Max. :356179279
## name_dest_first
## C:4211125
## M:2151495
##
##
##
##
Solo hay 8213 registros donde isFraud es verdadero.
Lo tendré en cuenta al crear los conjuntos de entrenamiento y prueba.
Mi plan es crear un conjunto de datos de entrenamiento y prueba que sea 50% de fraude / 50% sin fraude.
Ahora vamos a observar mas de cerca las transacciones fraudulentas.
fraud_trans <- fraud_df2 %>%
filter(isFraud == "Yes")
summary(fraud_trans)
## isFraud type step amount
## No : 0 CASH_IN : 0 Min. : 1.0 Min. : 0
## Yes:8213 CASH_OUT:4116 1st Qu.:181.0 1st Qu.: 127091
## DEBIT : 0 Median :367.0 Median : 441423
## PAYMENT : 0 Mean :368.4 Mean : 1467967
## TRANSFER:4097 3rd Qu.:558.0 3rd Qu.: 1517771
## Max. :743.0 Max. :10000000
## oldbalanceOrg newbalanceOrig oldbalanceDest newbalanceDest
## Min. : 0 Min. : 0 Min. : 0 Min. : 0
## 1st Qu.: 125822 1st Qu.: 0 1st Qu.: 0 1st Qu.: 0
## Median : 438983 Median : 0 Median : 0 Median : 4676
## Mean : 1649668 Mean : 192393 Mean : 544250 Mean : 1279708
## 3rd Qu.: 1517771 3rd Qu.: 0 3rd Qu.: 147829 3rd Qu.: 1058725
## Max. :59585040 Max. :49585040 Max. :236230517 Max. :236726495
## name_dest_first
## C:8213
## M: 0
##
##
##
##
Interesante. Cuando el tipo es CASH_IN, DEBIT o PAYMENT, no hay casos de fraude. Esto debe tenerse en cuenta al preparar los datos de train y de test.
En cada caso de fraude, name_dest_first tenía un código de “C”. Podemos filtrar el conjunto de datos principal para eliminar todas las M
El monto del fraude alcanza un máximo de 10,000,000, por lo que también filtraremos cualquier transacción por encima de ese monto.
Reducimos el dataset
Eliminar variables insignificantes; filtrar solo por CASH_OUT y TRANSFERS. Tampoco hay transacciones superiores a 10,000,000, por lo que también se pueden filtrar.
fraud_df3 <- fraud_df2 %>%
filter(type %in% c("CASH_OUT", "TRANSFER")) %>%
filter(name_dest_first == "C") %>%
filter(amount <= 10000000) %>%
select(-name_dest_first)
summary(fraud_df3)
## isFraud type step amount
## No :2759753 CASH_IN : 0 Min. : 1 Min. : 0
## Yes: 8213 CASH_OUT:2237500 1st Qu.:155 1st Qu.: 82900
## DEBIT : 0 Median :236 Median : 171092
## PAYMENT : 0 Mean :242 Mean : 300426
## TRANSFER: 530466 3rd Qu.:332 3rd Qu.: 306252
## Max. :743 Max. :10000000
## oldbalanceOrg newbalanceOrig oldbalanceDest newbalanceDest
## Min. : 0 Min. : 0 Min. : 0 Min. : 0
## 1st Qu.: 0 1st Qu.: 0 1st Qu.: 127785 1st Qu.: 326982
## Median : 311 Median : 0 Median : 554620 Median : 826727
## Mean : 47669 Mean : 16106 Mean : 1670726 Mean : 1998711
## 3rd Qu.: 31013 3rd Qu.: 0 3rd Qu.: 1730370 3rd Qu.: 2116020
## Max. :59585040 Max. :49585040 Max. :356015889 Max. :356179279
Esto reduce el conjunto de datos completo a 2,8 millones de registros. Esa es una reducción del 56% en el conjunto de datos que debería eliminar mucho ruido.
Creamos un “Sample dataset”
not_fraud <- fraud_df3 %>%
filter(isFraud == "No") %>%
sample_n(8213)
is_fraud <- fraud_df3 %>%
filter(isFraud == "Yes")
full_sample <- rbind(not_fraud, is_fraud) %>%
arrange(step)
¿Step tiene algún patrón claro en la distribución entre los casos de fraude?
Tenga en cuenta que el paso indica la hora dentro del mes en que se capturaron estos datos, por lo que estos gráficos deben considerarse series temporales.
ggplot(full_sample, aes(x = step, col = isFraud)) +
geom_histogram(bins = 743)
Hay un patrón para los casos de No Fraude, pero no hay un patrón discernible de los casos de Fraude de esta trama.
Vamos a ver solo los casos de fraude
ggplot(is_fraud, aes(x = step)) +
geom_histogram(bins = 743)
Nada…
Probamos mas cosas: Mire ggpairs para ver si hay correlaciones entre los predictores.
ggpairs(full_sample)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
Existe una alta correlación positiva entre OldBalanceOrig y newBalanceOrig. También una alta correlación entre oldBalanceDest y newBalanceDest. Nos ocuparemos de ellos sistemáticamente más abajo.
Hay algun patron de fraude en funcion del importe de la transaccion?
ggplot(full_sample, aes(type, amount, color = isFraud)) +
geom_point(alpha = 0.01) +
geom_jitter()
Sin patron
summary(full_sample)
## isFraud type step amount
## No :8213 CASH_IN : 0 Min. : 1.0 Min. : 0
## Yes:8213 CASH_OUT:10702 1st Qu.:160.0 1st Qu.: 97924
## DEBIT : 0 Median :282.0 Median : 234723
## PAYMENT : 0 Mean :304.5 Mean : 881574
## TRANSFER: 5724 3rd Qu.:406.0 3rd Qu.: 644315
## Max. :743.0 Max. :10000000
## oldbalanceOrg newbalanceOrig oldbalanceDest newbalanceDest
## Min. : 0 Min. : 0 Min. : 0 Min. : 0
## 1st Qu.: 307 1st Qu.: 0 1st Qu.: 0 1st Qu.: 0
## Median : 64726 Median : 0 Median : 131971 Median : 481431
## Mean : 845631 Mean : 103260 Mean : 1092197 Mean : 1623044
## 3rd Qu.: 461443 3rd Qu.: 0 3rd Qu.: 943131 3rd Qu.: 1691175
## Max. :59585040 Max. :49585040 Max. :236230517 Max. :236726495
Full dataset para modelización
preproc_model <- preProcess(fraud_df3[, -1],
method = c("center", "scale", "nzv"))
fraud_preproc <- predict(preproc_model, newdata = fraud_df3[, -1])
Vincula los resultados a los datos preprocesados
fraud_pp_w_result <- cbind(isFraud = fraud_df3$isFraud, fraud_preproc)
summary(fraud_pp_w_result)
## isFraud type step amount
## No :2759753 CASH_IN : 0 Min. :-1.7008 Min. :-0.49551
## Yes: 8213 CASH_OUT:2237500 1st Qu.:-0.6138 1st Qu.:-0.35878
## DEBIT : 0 Median :-0.0420 Median :-0.21332
## PAYMENT : 0 Mean : 0.0000 Mean : 0.00000
## TRANSFER: 530466 3rd Qu.: 0.6357 3rd Qu.: 0.00961
## Max. : 3.5369 Max. :15.99817
## oldbalanceOrg oldbalanceDest newbalanceDest
## Min. : -0.18960 Min. :-0.42399 Min. :-0.47994
## 1st Qu.: -0.18960 1st Qu.:-0.39156 1st Qu.:-0.40142
## Median : -0.18836 Median :-0.28324 Median :-0.28142
## Mean : 0.00000 Mean : 0.00000 Mean : 0.00000
## 3rd Qu.: -0.06625 3rd Qu.: 0.01514 3rd Qu.: 0.02817
## Max. :236.80038 Max. :89.92368 Max. :85.04701
Observe que la media de todos los campos numéricos es cero. La desviación estándar es 1. Este es el resultado del centrado y la escala que coloca todas las variables numéricas en la misma escala.
Encuentra predictores altamente correlacionados
Seleccionar solo columnas numéricas
Elimine la columna de resultados y las columnas categóricas (no se preocupe, las volveremos a colocar después de esta prueba)
fraud_numeric <- fraud_pp_w_result %>%
select(-isFraud, -type)
Removemos las variables correlacionadas porque distorsionan el analisis
high_cor_cols <- findCorrelation(cor(fraud_numeric), cutoff = .75, verbose = TRUE,
names = TRUE, exact = TRUE)
## Compare row 5 and column 4 with corr 0.975
## Means: 0.338 vs 0.149 so flagging column 5
## All correlations <= 0.75
high_cor_removed <- fraud_pp_w_result %>%
select(-newbalanceDest)
Miramos correlaciones lineales entre predictoras (No hay ninguna)
fraud_numeric <- high_cor_removed %>%
select(-isFraud, -type)
comboInfo <- findLinearCombos(fraud_numeric)
comboInfo
## $linearCombos
## list()
##
## $remove
## NULL
Modelado
model_df <-high_cor_removed
Cree la misma cantidad de datos de Fraude y No Fraude para el entrenamiento
is_fraud <- model_df %>%
filter(isFraud == "Yes")
not_fraud <- model_df %>%
filter(isFraud == "No") %>%
sample_n(8213)
# To mix up the sample set I'll arrange by `step`
model_full_sample <- rbind(is_fraud, not_fraud) %>%
arrange(step)
Dividir la muestra en train y conjuntos de prueba
in_train <- createDataPartition(y = model_full_sample$isFraud, p = .75,
list = FALSE)
train <- model_full_sample[in_train, ]
test <- model_full_sample[-in_train, ]
Create control used to fit all models
control <- trainControl(method = "repeatedcv",
number = 10,
repeats = 3,
classProbs = TRUE,
summaryFunction = twoClassSummary)
Approach
En todos los modelos seguiremos un patrón consistente.
El patrón es: 1. ajustar el modelo a los datos 2. modelo comparado con los datos en los que fue entrenado 3. modelo comparado con el conjunto de datos de prueba que era desconocido para la construcción del modelo 4. compare el modelo con una muestra de 100k de casos sin fraude para determinar los falsos positivos esperados.
En cada uno de los modelos también realizaremos un seguimiento del tiempo para crear el modelo.
Nota: la relación entre el tiempo para crear el modelo y aplicar el modelo a los datos nuevos es muy diferente.
El ajuste del modelo, especialmente para los modelos de tipo caja negra, como Support Vector Machines, puede llevar mucho tiempo, pero el tiempo para aplicar ese modelo a los datos nuevos suele ser insignificante.
No tengas miedo de usar mucho tiempo para entrenar a un modelo si da mejores resultados.
RPART MODEL
start_time <- Sys.time()
rpart_model = train(isFraud ~ .,
data = train,
method = "rpart",
tuneLength = 10,
metric = "ROC",
trControl = control,
parms=list(split='information'))
end_time <- Sys.time()
end_time - start_time
## Time difference of 12.23713 secs
Prediccion en el set de entrenamiento
rpart_train_pred <- predict(rpart_model, train)
confusionMatrix(train$isFraud, rpart_train_pred)
## Confusion Matrix and Statistics
##
## Reference
## Prediction No Yes
## No 5786 374
## Yes 101 6059
##
## Accuracy : 0.9614
## 95% CI : (0.9579, 0.9648)
## No Information Rate : 0.5222
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.9229
##
## Mcnemar's Test P-Value : < 2.2e-16
##
## Sensitivity : 0.9828
## Specificity : 0.9419
## Pos Pred Value : 0.9393
## Neg Pred Value : 0.9836
## Prevalence : 0.4778
## Detection Rate : 0.4696
## Detection Prevalence : 0.5000
## Balanced Accuracy : 0.9624
##
## 'Positive' Class : No
##
Precisión = 0.9489; muchos falsos positivos Sensibilidad: 0.9832 Especificidad: 0.9191
Prediccion en el set de TEST
rpart_test_pred <- predict(rpart_model, test)
confusionMatrix(test$isFraud, rpart_test_pred)
## Confusion Matrix and Statistics
##
## Reference
## Prediction No Yes
## No 1922 131
## Yes 50 2003
##
## Accuracy : 0.9559
## 95% CI : (0.9492, 0.962)
## No Information Rate : 0.5197
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.9118
##
## Mcnemar's Test P-Value : 2.742e-09
##
## Sensitivity : 0.9746
## Specificity : 0.9386
## Pos Pred Value : 0.9362
## Neg Pred Value : 0.9756
## Prevalence : 0.4803
## Detection Rate : 0.4681
## Detection Prevalence : 0.5000
## Balanced Accuracy : 0.9566
##
## 'Positive' Class : No
##
Prediccion en 100K de No Fraud
big_no_sample <- model_df %>%
filter(isFraud == "No") %>%
sample_n(100000)
Tambien vamos a ver cuanto tiempo tarda en procesar 100K
start_time <- Sys.time()
rpart_big_no_pred <- predict(rpart_model, big_no_sample)
end_time <- Sys.time()
end_time - start_time
## Time difference of 0.439199 secs
confusionMatrix(big_no_sample$isFraud, rpart_big_no_pred)
## Confusion Matrix and Statistics
##
## Reference
## Prediction No Yes
## No 93383 6617
## Yes 0 0
##
## Accuracy : 0.9338
## 95% CI : (0.9323, 0.9354)
## No Information Rate : 0.9338
## P-Value [Acc > NIR] : 0.5033
##
## Kappa : 0
##
## Mcnemar's Test P-Value : <2e-16
##
## Sensitivity : 1.0000
## Specificity : 0.0000
## Pos Pred Value : 0.9338
## Neg Pred Value : NaN
## Prevalence : 0.9338
## Detection Rate : 0.9338
## Detection Prevalence : 1.0000
## Balanced Accuracy : 0.5000
##
## 'Positive' Class : No
##
Accuracy : 0.9151 Sensitivity : 1.0000 Specificity : 0.0000 False-positives: 8488 Run-time: 0.2806261 secs
CURVA ROC VS DATOS DE TEST?
rpart_probs <- predict(rpart_model, test, type = "prob")
rpart_ROC <- roc(response = test$isFraud,
predictor = rpart_probs$Yes,
levels = levels(test$isFraud))
## Setting direction: controls < cases
plot(rpart_ROC, col = "blue")
auc(rpart_ROC)
## Area under the curve: 0.9867
RANDOM FOREST MODEL
https://cran.r-project.org/web/packages/randomForest/randomForest.pdf https://www.r-bloggers.com/random-forests-in-r/
grid <- expand.grid(.mtry = 5, .ntree = seq(25, 150, by = 25))
start_time <- Sys.time()
rf_model <- train(isFraud ~ .,
data = train,
method="rf",
metric = "Accuracy",
TuneGrid = grid,
trControl=control)
## Warning in train.default(x, y, weights = w, ...): The metric "Accuracy" was not
## in the result set. ROC will be used instead.
end_time <- Sys.time()
end_time - start_time
## Time difference of 10.01177 mins
library(randomForest)
## randomForest 4.6-14
## Type rfNews() to see new features/changes/bug fixes.
##
## Attaching package: 'randomForest'
## The following object is masked from 'package:rattle':
##
## importance
## The following object is masked from 'package:dplyr':
##
## combine
## The following object is masked from 'package:ggplot2':
##
## margin
print(rf_model$finalModel)
##
## Call:
## randomForest(x = x, y = y, mtry = param$mtry, TuneGrid = ..1)
## Type of random forest: classification
## Number of trees: 500
## No. of variables tried at each split: 5
##
## OOB estimate of error rate: 2.22%
## Confusion matrix:
## No Yes class.error
## No 5971 189 0.03068182
## Yes 84 6076 0.01363636
plot(rf_model$finalModel)
Esta es un plot importante. Observamos que el error se nivela en alrededor de 100 árboles. Esto muestra que el modelo Random Forest es un modelo efectivo en este conjunto de datos. Si estos datos contienen muchas más variables, se necesitarán más árboles para llegar a la meseta, pero generalmente ocurre antes de 500 árboles. El modelo puede reducir de forma segura los árboles a 100 sin ningún impacto negativo significativo en el rendimiento.
Esta parcela siempre debe usarse para bosques aleatorios para determinar el mejor punto de corte para los árboles.
Plot para ver la importancia de las variables
varImpPlot(rf_model$finalModel)
Me sorprende que oldBalanceOrig sea la información más importante. Amount y oldBalanceDest también son influyentes.
Prediccion con el set de entrenamiento
rf_train_pred <- predict(rf_model, train)
confusionMatrix(train$isFraud, rf_train_pred, positive = "Yes")
## Confusion Matrix and Statistics
##
## Reference
## Prediction No Yes
## No 6160 0
## Yes 0 6160
##
## Accuracy : 1
## 95% CI : (0.9997, 1)
## No Information Rate : 0.5
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 1
##
## Mcnemar's Test P-Value : NA
##
## Sensitivity : 1.0
## Specificity : 1.0
## Pos Pred Value : 1.0
## Neg Pred Value : 1.0
## Prevalence : 0.5
## Detection Rate : 0.5
## Detection Prevalence : 0.5
## Balanced Accuracy : 1.0
##
## 'Positive' Class : Yes
##
Accuracy : 1; Obviamente overfitting Sensitivity : 1.0 Specificity : 1.0
Prediccion con el set de Test
rf_test_pred <- predict(rf_model, test)
confusionMatrix(test$isFraud, rf_test_pred, positive = "Yes")
## Confusion Matrix and Statistics
##
## Reference
## Prediction No Yes
## No 1995 58
## Yes 29 2024
##
## Accuracy : 0.9788
## 95% CI : (0.9739, 0.983)
## No Information Rate : 0.5071
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.9576
##
## Mcnemar's Test P-Value : 0.002683
##
## Sensitivity : 0.9721
## Specificity : 0.9857
## Pos Pred Value : 0.9859
## Neg Pred Value : 0.9717
## Prevalence : 0.5071
## Detection Rate : 0.4929
## Detection Prevalence : 0.5000
## Balanced Accuracy : 0.9789
##
## 'Positive' Class : Yes
##
Accuracy : 0.9783; No está nada mal. Sensitivity : 0.9709 Specificity : 0.9901
Prediccion con el set de No fraude
start_time <- Sys.time()
rf_big_no_pred <- predict(rf_model, big_no_sample)
end_time <- Sys.time()
end_time - start_time
## Time difference of 5.360228 secs
confusionMatrix(big_no_sample$isFraud, rf_big_no_pred, positive = "Yes")
## Confusion Matrix and Statistics
##
## Reference
## Prediction No Yes
## No 96989 3011
## Yes 0 0
##
## Accuracy : 0.9699
## 95% CI : (0.9688, 0.9709)
## No Information Rate : 0.9699
## P-Value [Acc > NIR] : 0.5048
##
## Kappa : 0
##
## Mcnemar's Test P-Value : <2e-16
##
## Sensitivity : 0.00000
## Specificity : 1.00000
## Pos Pred Value : NaN
## Neg Pred Value : 0.96989
## Prevalence : 0.03011
## Detection Rate : 0.00000
## Detection Prevalence : 0.00000
## Balanced Accuracy : 0.50000
##
## 'Positive' Class : Yes
##
Accuracy : 0.97 Sensitivity : 0.00000 Specificity : 1.00000 False-positives: 3002 Run-time: 4.95 secs
Plot de la curva ROC
rf_probs <- predict(rf_model, test, type = "prob")
rf_ROC <- roc(response = test$isFraud,
predictor = rf_probs$Yes,
levels = levels(test$isFraud))
## Setting direction: controls < cases
plot(rf_ROC, col = "green")
Area bajo la curva
Comparamos modelos a través de la ROC CURVE
plot(rpart_ROC, col = "blue")
plot(rf_ROC, col = "green", add = TRUE)
Por cada modelo se vería así
sort(c(rpart = auc(rpart_ROC), rf = auc(rf_ROC)))
## rpart rf
## 0.9867197 0.9979305
Como de costumbre, la decisión de qué modelo utilizar no es sencilla. El arbol de decisión discrimina y predice muy bien en los datos de testeo, hay una diferencia mínima con el RF. En este caso podríamos sacrificar algo de accuracy a favor de ganar explicabilidad.