INTRODUCTION

This is a document detailing the process of analyzing data of factors contributing to employee attrition using a Neural Network (NN) model. This is a fictional data set created by IBM data scientists that can be downloaded at Kaggle website:

https://www.kaggle.com/datasets/pavansubhasht/ibm-hr-analytics-attrition-dataset

The data is explored, pre-processed to suit the NN framework, then used to train the NN model. The resulting NN model is used to predict a proportion of the data and the accuracy is evaluated using the function ConfusionMatrix().

DATA EXPLORATION

Import libraries.

# chunk options
knitr::opts_chunk$set(
  message = FALSE,
  warning = FALSE,
  fig.align = "center",
  comment = "#>"
)

# import libs

library(lime)
library(plotly)
library(tidyverse)
library(tidymodels)
library(ggcorrplot)
library(factoextra) #for fviz_contrib
library(FactoMineR) #for PCA that is more easy to visualize
library(ggiraphExtra) #for ggRadar
library(rsample) #for easy sampling / cross-validation
library(caret) #for confusionMatrix
library(keras) #for neural network

Import the dataset.

# import dataset
employee_raw <- read.csv("WA_Fn-UseC_-HR-Employee-Attrition.csv")
# employee <- read.csv("data-clean.csv")
# # quick check
head(employee_raw, 20)

View data structure of employee_raw.

glimpse(employee_raw)
#> Rows: 1,470
#> Columns: 35
#> $ Age                      <int> 41, 49, 37, 33, 27, 32, 59, 30, 38, 36, 35, 2…
#> $ Attrition                <chr> "Yes", "No", "Yes", "No", "No", "No", "No", "…
#> $ BusinessTravel           <chr> "Travel_Rarely", "Travel_Frequently", "Travel…
#> $ DailyRate                <int> 1102, 279, 1373, 1392, 591, 1005, 1324, 1358,…
#> $ Department               <chr> "Sales", "Research & Development", "Research …
#> $ DistanceFromHome         <int> 1, 8, 2, 3, 2, 2, 3, 24, 23, 27, 16, 15, 26, …
#> $ Education                <int> 2, 1, 2, 4, 1, 2, 3, 1, 3, 3, 3, 2, 1, 2, 3, …
#> $ EducationField           <chr> "Life Sciences", "Life Sciences", "Other", "L…
#> $ EmployeeCount            <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
#> $ EmployeeNumber           <int> 1, 2, 4, 5, 7, 8, 10, 11, 12, 13, 14, 15, 16,…
#> $ EnvironmentSatisfaction  <int> 2, 3, 4, 4, 1, 4, 3, 4, 4, 3, 1, 4, 1, 2, 3, …
#> $ Gender                   <chr> "Female", "Male", "Male", "Female", "Male", "…
#> $ HourlyRate               <int> 94, 61, 92, 56, 40, 79, 81, 67, 44, 94, 84, 4…
#> $ JobInvolvement           <int> 3, 2, 2, 3, 3, 3, 4, 3, 2, 3, 4, 2, 3, 3, 2, …
#> $ JobLevel                 <int> 2, 2, 1, 1, 1, 1, 1, 1, 3, 2, 1, 2, 1, 1, 1, …
#> $ JobRole                  <chr> "Sales Executive", "Research Scientist", "Lab…
#> $ JobSatisfaction          <int> 4, 2, 3, 3, 2, 4, 1, 3, 3, 3, 2, 3, 3, 4, 3, …
#> $ MaritalStatus            <chr> "Single", "Married", "Single", "Married", "Ma…
#> $ MonthlyIncome            <int> 5993, 5130, 2090, 2909, 3468, 3068, 2670, 269…
#> $ MonthlyRate              <int> 19479, 24907, 2396, 23159, 16632, 11864, 9964…
#> $ NumCompaniesWorked       <int> 8, 1, 6, 1, 9, 0, 4, 1, 0, 6, 0, 0, 1, 0, 5, …
#> $ Over18                   <chr> "Y", "Y", "Y", "Y", "Y", "Y", "Y", "Y", "Y", …
#> $ OverTime                 <chr> "Yes", "No", "Yes", "Yes", "No", "No", "Yes",…
#> $ PercentSalaryHike        <int> 11, 23, 15, 11, 12, 13, 20, 22, 21, 13, 13, 1…
#> $ PerformanceRating        <int> 3, 4, 3, 3, 3, 3, 4, 4, 4, 3, 3, 3, 3, 3, 3, …
#> $ RelationshipSatisfaction <int> 1, 4, 2, 3, 4, 3, 1, 2, 2, 2, 3, 4, 4, 3, 2, …
#> $ StandardHours            <int> 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 8…
#> $ StockOptionLevel         <int> 0, 1, 0, 0, 1, 0, 3, 1, 0, 2, 1, 0, 1, 1, 0, …
#> $ TotalWorkingYears        <int> 8, 10, 7, 8, 6, 8, 12, 1, 10, 17, 6, 10, 5, 3…
#> $ TrainingTimesLastYear    <int> 0, 3, 3, 3, 3, 2, 3, 2, 2, 3, 5, 3, 1, 2, 4, …
#> $ WorkLifeBalance          <int> 1, 3, 3, 3, 3, 2, 2, 3, 3, 2, 3, 3, 2, 3, 3, …
#> $ YearsAtCompany           <int> 6, 10, 0, 8, 2, 7, 1, 1, 9, 7, 5, 9, 5, 2, 4,…
#> $ YearsInCurrentRole       <int> 4, 7, 0, 7, 2, 7, 0, 0, 7, 7, 4, 5, 2, 2, 2, …
#> $ YearsSinceLastPromotion  <int> 0, 1, 0, 3, 2, 3, 0, 0, 1, 7, 0, 0, 4, 1, 0, …
#> $ YearsWithCurrManager     <int> 5, 7, 0, 0, 2, 6, 0, 0, 8, 7, 3, 8, 3, 2, 3, …

Check unique values of target variable Attrition.

unique(employee_raw$Attrition)
#> [1] "Yes" "No"

Check unique values of Over18.

unique(employee_raw$Over18)
#> [1] "Y"

Check unique values of JobRole.

unique(employee_raw$JobRole)
#> [1] "Sales Executive"           "Research Scientist"       
#> [3] "Laboratory Technician"     "Manufacturing Director"   
#> [5] "Healthcare Representative" "Manager"                  
#> [7] "Sales Representative"      "Research Director"        
#> [9] "Human Resources"

Check unique values of BusinessTravel.

unique(employee_raw$BusinessTravel)
#> [1] "Travel_Rarely"     "Travel_Frequently" "Non-Travel"

Check unique values of Department.

unique(employee_raw$Department)
#> [1] "Sales"                  "Research & Development" "Human Resources"

Check unique values of EducationField.

unique(employee_raw$EducationField)
#> [1] "Life Sciences"    "Other"            "Medical"          "Marketing"       
#> [5] "Technical Degree" "Human Resources"

Check unique values of MaritalStatus.

unique(employee_raw$MaritalStatus)
#> [1] "Single"   "Married"  "Divorced"

DATA PRE-PROCESS

Drop columns Over18, StandardHours, EmployeeCount, EmployeeNumber.

employee_raw <- employee_raw[,!names(employee_raw) %in% c("Over18", "StandardHours", "EmployeeCount", "EmployeeNumber")]

Convert columns with string classifications into numbered classifications.

employee_raw$BusinessTravel<-c("Travel_Rarely"=1,"Travel_Frequently"=2,"Non-Travel"=3)[employee_raw$BusinessTravel]

employee_raw$Department<-c( "Sales" = 1, "Research & Development" = 2, "Human Resources" = 3)[employee_raw$Department]

employee_raw$EducationField<-c("Life Sciences" = 1, "Other" = 2, "Medical" = 3, "Marketing" = 4, "Technical Degree" = 5, "Human Resources" = 6)[employee_raw$EducationField]

employee_raw$JobRole<-c("Sales Executive" = 1, "Research Scientist" = 2, "Laboratory Technician" = 3, "Manufacturing Director" = 4, "Healthcare Representative" = 5, "Manager" = 6, "Sales Representative" = 7, "Research Director" = 8, "Human Resources" = 9)[employee_raw$JobRole]

employee_raw$MaritalStatus<-c( "Single" = 1, "Married" = 2, "Divorced" = 3)[employee_raw$MaritalStatus]

View employee_raw.

employee_raw

Convert columns of classifications into factors.

library(ade4)
library(data.table)

employee_raw_factor <- employee_raw %>% mutate(across(c(3,5,7,8,9,12,13,14,15,16,22,23,24,26,27),factor))

Scale the continuous variables and view employee_scaled.

library(dplyr)
employee_scaled<-employee_raw_factor %>%
  mutate(across(where(is.numeric), scale))

employee_scaled

Convert classification variables columns into one-hot-encoded variable columns.

 library(mltools)
 library(data.table)

employee_scaled <- as.data.table(employee_scaled)

ohe_employee_scaled<-one_hot(employee_scaled, sparsifyNAs=TRUE, naCols=FALSE, dropCols=TRUE, dropUnusedLevels=TRUE )

Check for NAs/ missing values.

colSums(is.na(ohe_employee_scaled))

Convert Gender and Overtime variables to binary (0 for Male, 1 for Female, and 1 for Yes, and 0 for No).

ohe_employee_scaled$Gender<-c("Male" = 0, "Female" = 1)[ohe_employee_scaled$Gender]
ohe_employee_scaled$OverTime<-c(Yes=1,No=0)[ohe_employee_scaled$OverTime]

View ohe_employee_scaled after changes.

ohe_employee_scaled

View structure of ohe_employee_scaled.

str(ohe_employee_scaled)
#> Classes 'data.table' and 'data.frame':   1470 obs. of  83 variables:
#>  $ Age                       : num  0.4462 1.32192 0.00834 -0.42952 -1.08631 ...
#>  $ Attrition                 : chr  "Yes" "No" "Yes" "No" ...
#>  $ BusinessTravel_1          : int  1 0 1 0 1 0 1 1 0 1 ...
#>  $ BusinessTravel_2          : int  0 1 0 1 0 1 0 0 1 0 ...
#>  $ BusinessTravel_3          : int  0 0 0 0 0 0 0 0 0 0 ...
#>  $ DailyRate                 : num  0.742 -1.297 1.414 1.461 -0.524 ...
#>  $ Department_1              : int  1 0 0 0 0 0 0 0 0 0 ...
#>  $ Department_2              : int  0 1 1 1 1 1 1 1 1 1 ...
#>  $ Department_3              : int  0 0 0 0 0 0 0 0 0 0 ...
#>  $ DistanceFromHome          : num  -1.011 -0.147 -0.887 -0.764 -0.887 ...
#>  $ Education_1               : int  0 1 0 0 1 0 0 1 0 0 ...
#>  $ Education_2               : int  1 0 1 0 0 1 0 0 0 0 ...
#>  $ Education_3               : int  0 0 0 0 0 0 1 0 1 1 ...
#>  $ Education_4               : int  0 0 0 1 0 0 0 0 0 0 ...
#>  $ Education_5               : int  0 0 0 0 0 0 0 0 0 0 ...
#>  $ EducationField_1          : int  1 1 0 1 0 1 0 1 1 0 ...
#>  $ EducationField_2          : int  0 0 1 0 0 0 0 0 0 0 ...
#>  $ EducationField_3          : int  0 0 0 0 1 0 1 0 0 1 ...
#>  $ EducationField_4          : int  0 0 0 0 0 0 0 0 0 0 ...
#>  $ EducationField_5          : int  0 0 0 0 0 0 0 0 0 0 ...
#>  $ EducationField_6          : int  0 0 0 0 0 0 0 0 0 0 ...
#>  $ EnvironmentSatisfaction_1 : int  0 0 0 0 1 0 0 0 0 0 ...
#>  $ EnvironmentSatisfaction_2 : int  1 0 0 0 0 0 0 0 0 0 ...
#>  $ EnvironmentSatisfaction_3 : int  0 1 0 0 0 0 1 0 0 1 ...
#>  $ EnvironmentSatisfaction_4 : int  0 0 1 1 0 1 0 1 1 0 ...
#>  $ Gender                    : num  1 0 0 1 0 0 1 0 0 0 ...
#>  $ HourlyRate                : num  1.383 -0.241 1.284 -0.487 -1.274 ...
#>  $ JobInvolvement_1          : int  0 0 0 0 0 0 0 0 0 0 ...
#>  $ JobInvolvement_2          : int  0 1 1 0 0 0 0 0 1 0 ...
#>  $ JobInvolvement_3          : int  1 0 0 1 1 1 0 1 0 1 ...
#>  $ JobInvolvement_4          : int  0 0 0 0 0 0 1 0 0 0 ...
#>  $ JobLevel_1                : int  0 0 1 1 1 1 1 1 0 0 ...
#>  $ JobLevel_2                : int  1 1 0 0 0 0 0 0 0 1 ...
#>  $ JobLevel_3                : int  0 0 0 0 0 0 0 0 1 0 ...
#>  $ JobLevel_4                : int  0 0 0 0 0 0 0 0 0 0 ...
#>  $ JobLevel_5                : int  0 0 0 0 0 0 0 0 0 0 ...
#>  $ JobRole_1                 : int  1 0 0 0 0 0 0 0 0 0 ...
#>  $ JobRole_2                 : int  0 1 0 1 0 0 0 0 0 0 ...
#>  $ JobRole_3                 : int  0 0 1 0 1 1 1 1 0 0 ...
#>  $ JobRole_4                 : int  0 0 0 0 0 0 0 0 1 0 ...
#>  $ JobRole_5                 : int  0 0 0 0 0 0 0 0 0 1 ...
#>  $ JobRole_6                 : int  0 0 0 0 0 0 0 0 0 0 ...
#>  $ JobRole_7                 : int  0 0 0 0 0 0 0 0 0 0 ...
#>  $ JobRole_8                 : int  0 0 0 0 0 0 0 0 0 0 ...
#>  $ JobRole_9                 : int  0 0 0 0 0 0 0 0 0 0 ...
#>  $ JobSatisfaction_1         : int  0 0 0 0 0 0 1 0 0 0 ...
#>  $ JobSatisfaction_2         : int  0 1 0 0 1 0 0 0 0 0 ...
#>  $ JobSatisfaction_3         : int  0 0 1 1 0 0 0 1 1 1 ...
#>  $ JobSatisfaction_4         : int  1 0 0 0 0 1 0 0 0 0 ...
#>  $ MaritalStatus_1           : int  1 0 1 0 0 1 0 0 1 0 ...
#>  $ MaritalStatus_2           : int  0 1 0 1 1 0 1 0 0 1 ...
#>  $ MaritalStatus_3           : int  0 0 0 0 0 0 0 1 0 0 ...
#>  $ MonthlyIncome             : num  -0.108 -0.292 -0.937 -0.763 -0.645 ...
#>  $ MonthlyRate               : num  0.726 1.488 -1.674 1.243 0.326 ...
#>  $ NumCompaniesWorked        : num  2.124 -0.678 1.324 -0.678 2.525 ...
#>  $ OverTime                  : num  1 0 1 1 0 0 1 0 0 0 ...
#>  $ PercentSalaryHike         : num  -1.1502 2.1286 -0.0572 -1.1502 -0.8769 ...
#>  $ PerformanceRating_3       : int  1 0 1 1 1 1 0 0 0 1 ...
#>  $ PerformanceRating_4       : int  0 1 0 0 0 0 1 1 1 0 ...
#>  $ RelationshipSatisfaction_1: int  1 0 0 0 0 0 1 0 0 0 ...
#>  $ RelationshipSatisfaction_2: int  0 0 1 0 0 0 0 1 1 1 ...
#>  $ RelationshipSatisfaction_3: int  0 0 0 1 0 1 0 0 0 0 ...
#>  $ RelationshipSatisfaction_4: int  0 1 0 0 1 0 0 0 0 0 ...
#>  $ StockOptionLevel_0        : int  1 0 1 1 0 1 0 0 1 0 ...
#>  $ StockOptionLevel_1        : int  0 1 0 0 1 0 0 1 0 0 ...
#>  $ StockOptionLevel_2        : int  0 0 0 0 0 0 0 0 0 1 ...
#>  $ StockOptionLevel_3        : int  0 0 0 0 0 0 1 0 0 0 ...
#>  $ TotalWorkingYears         : num  -0.421 -0.164 -0.55 -0.421 -0.679 ...
#>  $ TrainingTimesLastYear_0   : int  1 0 0 0 0 0 0 0 0 0 ...
#>  $ TrainingTimesLastYear_1   : int  0 0 0 0 0 0 0 0 0 0 ...
#>  $ TrainingTimesLastYear_2   : int  0 0 0 0 0 1 0 1 1 0 ...
#>  $ TrainingTimesLastYear_3   : int  0 1 1 1 1 0 1 0 0 1 ...
#>  $ TrainingTimesLastYear_4   : int  0 0 0 0 0 0 0 0 0 0 ...
#>  $ TrainingTimesLastYear_5   : int  0 0 0 0 0 0 0 0 0 0 ...
#>  $ TrainingTimesLastYear_6   : int  0 0 0 0 0 0 0 0 0 0 ...
#>  $ WorkLifeBalance_1         : int  1 0 0 0 0 0 0 0 0 0 ...
#>  $ WorkLifeBalance_2         : int  0 0 0 0 0 1 1 0 0 1 ...
#>  $ WorkLifeBalance_3         : int  0 1 1 1 1 0 0 1 1 0 ...
#>  $ WorkLifeBalance_4         : int  0 0 0 0 0 0 0 0 0 0 ...
#>  $ YearsAtCompany            : num  -0.165 0.488 -1.144 0.162 -0.817 ...
#>  $ YearsInCurrentRole        : num  -0.0633 0.7647 -1.1673 0.7647 -0.6153 ...
#>  $ YearsSinceLastPromotion   : num  -0.6789 -0.3686 -0.6789 0.2521 -0.0583 ...
#>  $ YearsWithCurrManager      : num  0.246 0.806 -1.156 -1.156 -0.595 ...
#>  - attr(*, ".internal.selfref")=<externalptr>

Perform Principle Component Analysis (PCA) on ohe_employee_scaled.

pc <- prcomp(ohe_employee_scaled[,-2],
             center = TRUE,
            scale. = TRUE)
fviz_eig(pc, col.var="blue")

The PCA dimension 1 and 2 can only explain 10-12% of the variances.

Move Attrition column to the end of dataset.

ohe_employee_scaled<-ohe_employee_scaled%>% select(-Attrition, Attrition)

View ohe_employee_scaled.

ohe_employee_scaled

MODELLING

PRE-PROCESS

Split ohe_employee_scaled into 80% train and 20% test data sets.

# Simple into 3 sets.
 idx <- sample(seq(1, 2), size = nrow(ohe_employee_scaled), replace = TRUE, prob = c(.8, .2))
 train <- ohe_employee_scaled[idx == 1,]
 test <- ohe_employee_scaled[idx == 2,]

View test.

test

Subset all predictor variables from train and test into train_x and test_x.

train_x = as.matrix(train[,1:82])
test_x = as.matrix(test[,1:82])

Check dimensions of train_x and test_x.

dim(train_x)
#> [1] 1203   82
dim(test_x)
#> [1] 267  82

Convert Attrition column in train_y from “Yes” and “No” into binary “1” and “0”s.

train_y<-c(Yes=1,No=0)[train$Attrition]
test_y<-test$Attrition

Convert train_y into a matrix of numeric type check structures.

train_y<-as.matrix(as.numeric(train_y))
str(train_y)
#>  num [1:1203, 1] 1 0 1 0 0 0 0 0 0 0 ...
str(test_y)
#>  chr [1:267] "No" "Yes" "No" "Yes" "No" "No" "No" "No" "No" "No" "No" "No" ...

RUN

Define the model using train_x and train_y as our data parameters.

# define input
input <- layer_input(name = "input", shape = ncol(train_x))

# define hidden layers
hiddens <- input %>% 
  layer_dense(name = "dense_1", units = 64) %>% 
  layer_activation_leaky_relu(name = "dense_1_act") %>% 
  layer_batch_normalization(name = "dense_1_bn") %>% 
  layer_dropout(name = "dense_1_dp", rate = 0.15) %>% 
  layer_dense(name = "dense_2", units = 32) %>% 
  layer_activation_leaky_relu(name = "dense_2_act") %>% 
  layer_batch_normalization(name = "dense_2_bn") %>% 
  layer_dropout(name = "dense_2_dp", rate = 0.15)

# define output
output <- hiddens %>% 
  layer_dense(name = "output", units = ncol(train_y)) %>% 
  layer_batch_normalization(name = "output_bn") %>%
  layer_activation(name = "output_act", activation = "sigmoid")

# define full model
model <- keras_model(inputs = input, outputs = output)

# compile the model
model %>% compile(
  optimizer = optimizer_adam(lr = 0.001),
  metrics = "accuracy",
  loss = "binary_crossentropy"
)

# model summary
summary(model)
#> Model: "model"
#> ________________________________________________________________________________
#>  Layer (type)                  Output Shape               Param #    Trainable  
#> ================================================================================
#>  input (InputLayer)            [(None, 82)]               0          Y          
#>  dense_1 (Dense)               (None, 64)                 5312       Y          
#>  dense_1_act (LeakyReLU)       (None, 64)                 0          Y          
#>  dense_1_bn (BatchNormalizatio  (None, 64)                256        Y          
#>  n)                                                                             
#>  dense_1_dp (Dropout)          (None, 64)                 0          Y          
#>  dense_2 (Dense)               (None, 32)                 2080       Y          
#>  dense_2_act (LeakyReLU)       (None, 32)                 0          Y          
#>  dense_2_bn (BatchNormalizatio  (None, 32)                128        Y          
#>  n)                                                                             
#>  dense_2_dp (Dropout)          (None, 32)                 0          Y          
#>  output (Dense)                (None, 1)                  33         Y          
#>  output_bn (BatchNormalization  (None, 1)                 4          Y          
#>  )                                                                              
#>  output_act (Activation)       (None, 1)                  0          Y          
#> ================================================================================
#> Total params: 7,813
#> Trainable params: 7,619
#> Non-trainable params: 194
#> ________________________________________________________________________________

Run the model.

# fit model with our training data set, training will be done for 200 times data set
history <- model %>% 
           fit(train_x,
               train_y,
               batch_size= 100,
               shuffle = T,
              validation_split = 0.3,
               epochs = 500)

Inspect graph of loss, val_loss, accuracy_val_accuracy with passage of epoch.

knitr::include_graphics("Capture_compile_epoch_500.jpg")
Compilation time plot.

Compilation time plot.

EVALUATION

Use the model to predict on test data.

pred_nn <- predict(model, test_x)

Prediciton result is in the form of probabilities.

head(pred_nn)
#>            [,1]
#> [1,] 0.01718491
#> [2,] 0.03351238
#> [3,] 0.03529134
#> [4,] 0.99942023
#> [5,] 0.95105594
#> [6,] 0.75425267

Convert probabilities to “Yes” “No” and drop the probabilities column.

pred_test <- as.data.frame(pred_nn) %>% 
  mutate(class = ifelse(pred_nn[,1] > 0.5, "Yes", "No")) 

pred_test<-pred_test%>%select(class)

Convert pred_test into factors from character.

pred_test <- pred_test %>%
  mutate_if(sapply(pred_test, is.character), as.factor)

Check pred_test structure.

str(pred_test)
#> 'data.frame':    267 obs. of  1 variable:
#>  $ class: Factor w/ 2 levels "No","Yes": 1 1 1 2 2 2 1 1 1 1 ...

Convert test_y into factor and check test_y structure.

test_y<-as.factor(test_y)
str(test_y)
#>  Factor w/ 2 levels "No","Yes": 1 2 1 2 1 1 1 1 1 1 ...

Put test_y and pred_test into ConfusionMatrix() for accuracy check.

confusionMatrix(data = pred_test[,1], reference = test_y, positive = "Yes")
#> Confusion Matrix and Statistics
#> 
#>           Reference
#> Prediction  No Yes
#>        No  218  20
#>        Yes  12  17
#>                                          
#>                Accuracy : 0.8801         
#>                  95% CI : (0.835, 0.9166)
#>     No Information Rate : 0.8614         
#>     P-Value [Acc > NIR] : 0.2150         
#>                                          
#>                   Kappa : 0.4479         
#>                                          
#>  Mcnemar's Test P-Value : 0.2159         
#>                                          
#>             Sensitivity : 0.45946        
#>             Specificity : 0.94783        
#>          Pos Pred Value : 0.58621        
#>          Neg Pred Value : 0.91597        
#>              Prevalence : 0.13858        
#>          Detection Rate : 0.06367        
#>    Detection Prevalence : 0.10861        
#>       Balanced Accuracy : 0.70364        
#>                                          
#>        'Positive' Class : Yes            
#> 

CONCLUSION

The results of the modelling and prediction achieved an an Accuracy (correct predictions over total target number) of 86%, Recall (True positive rate) of 47%, Precision (Positive Predictive Value) of 55%, and Specificity (True negative rate) of 93%. The results of the prediction had to be converted to “Yes” and “No” of type factor for subsequent evaluation.

The data had to be converted into numeric and in matrix format for it to be used in the training of the NN model. The hidden layer of the model used two sets of a layer_dense (units=64 amd 32), a layer_activation_leaky_relu and a layer_batch_normalization. The output layer of the model consist of a layer_dense, a layer_batch_normalization and a layer_activation that uses the “sigmoid” activation. The model uses “optimizer_adam” with a learning rate of 0.001. The model was compiled for 500 epochs.

Beyond epoch 200 not much improvement in accuracy can be observed as “validation loss” is already stagnant with passage of each epoch beyond epoch=200. Future improvements to process may include upsampling to achieve better proportion of target “Yes” in the training data and increasing the hidden layer units.