library(paws)
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(ggplot2)
library(car)
## Loading required package: carData
## 
## Attaching package: 'car'
## The following object is masked from 'package:dplyr':
## 
##     recode
library(caret)
## Loading required package: lattice
library(forcats)
library(pROC)
## Type 'citation("pROC")' for a citation.
## 
## Attaching package: 'pROC'
## The following objects are masked from 'package:stats':
## 
##     cov, smooth, var
library(corrplot)
## corrplot 0.95 loaded
library(tidymodels)
## ── Attaching packages ────────────────────────────────────── tidymodels 1.5.0 ──
## ✔ broom        1.0.13     ✔ rsample      1.3.2 
## ✔ dials        1.4.4      ✔ tailor       0.1.0 
## ✔ infer        1.1.0      ✔ tidyr        1.3.2 
## ✔ modeldata    1.5.1      ✔ tune         2.1.0 
## ✔ parsnip      1.6.0      ✔ workflows    1.3.0 
## ✔ purrr        1.2.2      ✔ workflowsets 1.1.1 
## ✔ recipes      1.3.3      ✔ yardstick    1.4.0
## ── Conflicts ───────────────────────────────────────── tidymodels_conflicts() ──
## ✖ rsample::calibration()   masks caret::calibration()
## ✖ purrr::discard()         masks scales::discard()
## ✖ dplyr::filter()          masks stats::filter()
## ✖ dplyr::lag()             masks stats::lag()
## ✖ purrr::lift()            masks caret::lift()
## ✖ yardstick::precision()   masks caret::precision()
## ✖ yardstick::recall()      masks caret::recall()
## ✖ car::recode()            masks dplyr::recode()
## ✖ yardstick::sensitivity() masks caret::sensitivity()
## ✖ purrr::some()            masks car::some()
## ✖ yardstick::specificity() masks caret::specificity()
## ✖ recipes::step()          masks stats::step()
## ✖ parsnip::translate()     masks paws::translate()
library(kknn)
## 
## Attaching package: 'kknn'
## The following object is masked from 'package:caret':
## 
##     contr.dummy
library(ranger)
library(xgboost)
library(reticulate)

Data from from UC-Irvine Machine Learning Repository at: https://archive.ics.uci.edu/dataset/697/predict+students+dropout+and+academic+success

s3 <- paws::s3()

s3Object <- s3$get_object(
  Bucket = "wgu-ml-demo",
  Key = "raw/dropout-ml-data.csv" # Red in data from AWS bucket
)

rawData <- read.csv(
  text = rawToChar(s3Object$Body),
  sep = ";",
  stringsAsFactors = TRUE
)

rawData <- rawData %>%
  mutate(across(
    c(
      Marital.status, Application.mode, Application.order, Course,
      Daytime.evening.attendance., Previous.qualification, Nacionality,
      Mother.s.qualification, Father.s.qualification, Mother.s.occupation,
      Father.s.occupation, Displaced, Educational.special.needs, Debtor,
      Tuition.fees.up.to.date, Gender, Scholarship.holder, International,
      Target
    ),
    as.factor
  ))

head(rawData)
##   Marital.status Application.mode Application.order Course
## 1              1               17                 5    171
## 2              1               15                 1   9254
## 3              1                1                 5   9070
## 4              1               17                 2   9773
## 5              2               39                 1   8014
## 6              2               39                 1   9991
##   Daytime.evening.attendance. Previous.qualification
## 1                           1                      1
## 2                           1                      1
## 3                           1                      1
## 4                           1                      1
## 5                           0                      1
## 6                           0                     19
##   Previous.qualification..grade. Nacionality Mother.s.qualification
## 1                          122.0           1                     19
## 2                          160.0           1                      1
## 3                          122.0           1                     37
## 4                          122.0           1                     38
## 5                          100.0           1                     37
## 6                          133.1           1                     37
##   Father.s.qualification Mother.s.occupation Father.s.occupation
## 1                     12                   5                   9
## 2                      3                   3                   3
## 3                     37                   9                   9
## 4                     37                   5                   3
## 5                     38                   9                   9
## 6                     37                   9                   7
##   Admission.grade Displaced Educational.special.needs Debtor
## 1           127.3         1                         0      0
## 2           142.5         1                         0      0
## 3           124.8         1                         0      0
## 4           119.6         1                         0      0
## 5           141.5         0                         0      0
## 6           114.8         0                         0      1
##   Tuition.fees.up.to.date Gender Scholarship.holder Age.at.enrollment
## 1                       1      1                  0                20
## 2                       0      1                  0                19
## 3                       0      1                  0                19
## 4                       1      0                  0                20
## 5                       1      0                  0                45
## 6                       1      1                  0                50
##   International Curricular.units.1st.sem..credited.
## 1             0                                   0
## 2             0                                   0
## 3             0                                   0
## 4             0                                   0
## 5             0                                   0
## 6             0                                   0
##   Curricular.units.1st.sem..enrolled. Curricular.units.1st.sem..evaluations.
## 1                                   0                                      0
## 2                                   6                                      6
## 3                                   6                                      0
## 4                                   6                                      8
## 5                                   6                                      9
## 6                                   5                                     10
##   Curricular.units.1st.sem..approved. Curricular.units.1st.sem..grade.
## 1                                   0                          0.00000
## 2                                   6                         14.00000
## 3                                   0                          0.00000
## 4                                   6                         13.42857
## 5                                   5                         12.33333
## 6                                   5                         11.85714
##   Curricular.units.1st.sem..without.evaluations.
## 1                                              0
## 2                                              0
## 3                                              0
## 4                                              0
## 5                                              0
## 6                                              0
##   Curricular.units.2nd.sem..credited. Curricular.units.2nd.sem..enrolled.
## 1                                   0                                   0
## 2                                   0                                   6
## 3                                   0                                   6
## 4                                   0                                   6
## 5                                   0                                   6
## 6                                   0                                   5
##   Curricular.units.2nd.sem..evaluations. Curricular.units.2nd.sem..approved.
## 1                                      0                                   0
## 2                                      6                                   6
## 3                                      0                                   0
## 4                                     10                                   5
## 5                                      6                                   6
## 6                                     17                                   5
##   Curricular.units.2nd.sem..grade.
## 1                          0.00000
## 2                         13.66667
## 3                          0.00000
## 4                         12.40000
## 5                         13.00000
## 6                         11.50000
##   Curricular.units.2nd.sem..without.evaluations. Unemployment.rate
## 1                                              0              10.8
## 2                                              0              13.9
## 3                                              0              10.8
## 4                                              0               9.4
## 5                                              0              13.9
## 6                                              5              16.2
##   Inflation.rate   GDP   Target
## 1            1.4  1.74  Dropout
## 2           -0.3  0.79 Graduate
## 3            1.4  1.74  Dropout
## 4           -0.8 -3.12 Graduate
## 5           -0.3  0.79 Graduate
## 6            0.3 -0.92 Graduate

The dataset is organized such that each row represents a single student. It documents information about the student’s demographics (sex, age), family background (parents’ education and occupation), financial status (scholarship status, tuition payment, debtor status), academic path (how they applied, degree program, prior qualifications), and actual performance across two semesters. The final column, Target, records the outcome: whether the student ultimately graduated, dropped out, or was enrolled at the time of observation. This dataset helps identify students at risk quickly (Semesters 1 and 2) rather than after a theoretical “tipping point”. Unrealistically, this dataset is flawlessly organized with 0 missing data. Real-world data would require more cleaning and/or data imputation.

table(rawData$Target)
## 
##  Dropout Enrolled Graduate 
##     1421      794     2209
prop.table(table(rawData$Target)) * 100
## 
##  Dropout Enrolled Graduate 
## 32.12025 17.94756 49.93219
ggplot(rawData, aes(x = Target)) +
  geom_bar(fill = "#003057") +
  theme_minimal() +
  labs(
    title = "Student Outcome Distribution",
    x = "Outcome",
    y = "Count"
  )

modelData <- rawData |>
  filter(Target != "Enrolled") |>
  droplevels() # Enrolled students are not a final outcome and add noise to model

modelData$Target <- relevel(modelData$Target, ref = "Graduate") # The goal is to "flag" those at risk for drop out. For this reason, I am setting the reference as Graduate

str(modelData)
## 'data.frame':    3630 obs. of  37 variables:
##  $ Marital.status                                : Factor w/ 6 levels "1","2","3","4",..: 1 1 1 1 2 2 1 1 1 1 ...
##  $ Application.mode                              : Factor w/ 18 levels "1","2","5","7",..: 8 6 1 8 12 12 1 9 1 1 ...
##  $ Application.order                             : Factor w/ 7 levels "0","1","2","3",..: 6 2 6 3 2 2 2 5 4 2 ...
##  $ Course                                        : Factor w/ 17 levels "33","171","8014",..: 2 11 5 15 3 17 12 11 10 10 ...
##  $ Daytime.evening.attendance.                   : Factor w/ 2 levels "0","1": 2 2 2 2 1 1 2 2 2 2 ...
##  $ Previous.qualification                        : Factor w/ 17 levels "1","2","3","4",..: 1 1 1 1 1 12 1 1 1 1 ...
##  $ Previous.qualification..grade.                : num  122 160 122 122 100 ...
##  $ Nacionality                                   : Factor w/ 19 levels "1","2","6","11",..: 1 1 1 1 1 1 1 1 14 1 ...
##  $ Mother.s.qualification                        : Factor w/ 29 levels "1","2","3","4",..: 13 1 22 23 22 22 13 22 1 1 ...
##  $ Father.s.qualification                        : Factor w/ 34 levels "1","2","3","4",..: 10 3 27 27 28 27 28 27 1 14 ...
##  $ Mother.s.occupation                           : Factor w/ 29 levels "0","1","2","3",..: 6 4 10 6 10 10 8 10 10 5 ...
##  $ Father.s.occupation                           : Factor w/ 42 levels "0","1","2","3",..: 10 4 10 4 10 8 11 10 10 8 ...
##  $ Admission.grade                               : num  127 142 125 120 142 ...
##  $ Displaced                                     : Factor w/ 2 levels "0","1": 2 2 2 2 1 1 2 2 1 2 ...
##  $ Educational.special.needs                     : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
##  $ Debtor                                        : Factor w/ 2 levels "0","1": 1 1 1 1 1 2 1 1 1 2 ...
##  $ Tuition.fees.up.to.date                       : Factor w/ 2 levels "0","1": 2 1 1 2 2 2 2 1 2 1 ...
##  $ Gender                                        : Factor w/ 2 levels "0","1": 2 2 2 1 1 2 1 2 1 1 ...
##  $ Scholarship.holder                            : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 2 1 2 1 ...
##  $ Age.at.enrollment                             : int  20 19 19 20 45 50 18 22 21 18 ...
##  $ International                                 : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 2 1 ...
##  $ Curricular.units.1st.sem..credited.           : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ Curricular.units.1st.sem..enrolled.           : int  0 6 6 6 6 5 7 5 6 6 ...
##  $ Curricular.units.1st.sem..evaluations.        : int  0 6 0 8 9 10 9 5 8 9 ...
##  $ Curricular.units.1st.sem..approved.           : int  0 6 0 6 5 5 7 0 6 5 ...
##  $ Curricular.units.1st.sem..grade.              : num  0 14 0 13.4 12.3 ...
##  $ Curricular.units.1st.sem..without.evaluations.: int  0 0 0 0 0 0 0 0 0 0 ...
##  $ Curricular.units.2nd.sem..credited.           : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ Curricular.units.2nd.sem..enrolled.           : int  0 6 6 6 6 5 8 5 6 6 ...
##  $ Curricular.units.2nd.sem..evaluations.        : int  0 6 0 10 6 17 8 5 7 14 ...
##  $ Curricular.units.2nd.sem..approved.           : int  0 6 0 5 6 5 8 0 6 2 ...
##  $ Curricular.units.2nd.sem..grade.              : num  0 13.7 0 12.4 13 ...
##  $ Curricular.units.2nd.sem..without.evaluations.: int  0 0 0 0 0 5 0 0 0 0 ...
##  $ Unemployment.rate                             : num  10.8 13.9 10.8 9.4 13.9 16.2 15.5 15.5 16.2 8.9 ...
##  $ Inflation.rate                                : num  1.4 -0.3 1.4 -0.8 -0.3 0.3 2.8 2.8 0.3 1.4 ...
##  $ GDP                                           : num  1.74 0.79 1.74 -3.12 0.79 -0.92 -4.06 -4.06 -0.92 3.51 ...
##  $ Target                                        : Factor w/ 2 levels "Graduate","Dropout": 2 1 2 1 1 1 1 2 1 2 ...
table(modelData$Target)
## 
## Graduate  Dropout 
##     2209     1421
prop.table(table(modelData$Target)) * 100
## 
## Graduate  Dropout 
## 60.85399 39.14601
numericCols <- modelData |>
  select(where(is.numeric))
corrMatrix <- cor(numericCols)

corrplot(corrMatrix,
  method = "color",
  type = "lower",
  tl.cex = .7,
  tl.col = "black"
) # Notice high correlation in columns beginning with "curricular". These have to do with early semester success

# Logistic Regression
set.seed(8247)

data_split <- initial_split(modelData, prop = .8, strata = Target) # Split data with 80% in traininng data. Note Target is given to strata. To quote the documentation, "This can help ensure that the resamples have equivalent proportions as the original data set."
train_lr <- training(data_split)
test_lr <- testing(data_split)

fullLogistic <- glm(Target ~ .,
  data = train_lr,
  family = binomial
) # Starting with the fully saturated model
summary(fullLogistic) # High cardinality categorical variables lead to sparse, unstable, and in the worst case, perfectly separated data. The simplest solution is to remove some of these variables (eg. Mother's and Father's qualifications have a negligible effect and introduce dozens of levels). However, I will opt to group sparse factors together to avoid missing a real signal. Another solution is regularization, but given this is supposed to be a basic model, I will skip that for now
## 
## Call:
## glm(formula = Target ~ ., family = binomial, data = train_lr)
## 
## Coefficients: (8 not defined because of singularities)
##                                                  Estimate Std. Error z value
## (Intercept)                                     2.732e+00  6.754e+03   0.000
## Marital.status2                                 7.771e-02  4.198e-01   0.185
## Marital.status3                                -1.716e+01  6.523e+03  -0.003
## Marital.status4                                -1.293e+00  7.871e-01  -1.642
## Marital.status5                                -8.814e-01  1.101e+00  -0.800
## Marital.status6                                -5.021e-01  4.268e+00  -0.118
## Application.mode2                              -1.552e+01  6.523e+03  -0.002
## Application.mode5                               2.166e+00  1.553e+00   1.394
## Application.mode7                              -7.903e-02  8.932e-01  -0.088
## Application.mode10                              4.206e+00  1.718e+00   2.448
## Application.mode15                             -7.806e-01  1.297e+00  -0.602
## Application.mode16                             -7.340e-01  8.274e-01  -0.887
## Application.mode17                              4.266e-01  2.281e-01   1.871
## Application.mode18                              1.050e-01  5.707e-01   0.184
## Application.mode26                              1.726e+01  6.523e+03   0.003
## Application.mode27                              2.141e+01  6.523e+03   0.003
## Application.mode39                              6.832e-01  3.820e-01   1.788
## Application.mode42                             -9.168e-02  6.756e-01  -0.136
## Application.mode43                             -1.797e-01  4.170e-01  -0.431
## Application.mode44                              2.308e-01  1.614e+00   0.143
## Application.mode51                              6.766e-01  7.160e-01   0.945
## Application.mode53                             -1.555e+01  6.523e+03  -0.002
## Application.mode57                             -1.594e+01  6.523e+03  -0.002
## Application.order1                              1.236e+01  6.523e+03   0.002
## Application.order2                              1.202e+01  6.523e+03   0.002
## Application.order3                              1.248e+01  6.523e+03   0.002
## Application.order4                              1.233e+01  6.523e+03   0.002
## Application.order5                              1.208e+01  6.523e+03   0.002
## Application.order6                              1.239e+01  6.523e+03   0.002
## Course171                                      -1.468e+01  1.751e+03  -0.008
## Course8014                                     -1.497e+01  1.751e+03  -0.009
## Course9003                                     -1.501e+01  1.751e+03  -0.009
## Course9070                                     -1.374e+01  1.751e+03  -0.008
## Course9085                                     -1.428e+01  1.751e+03  -0.008
## Course9119                                     -1.239e+01  1.751e+03  -0.007
## Course9130                                     -1.314e+01  1.751e+03  -0.008
## Course9147                                     -1.378e+01  1.751e+03  -0.008
## Course9238                                     -1.460e+01  1.751e+03  -0.008
## Course9254                                     -1.433e+01  1.751e+03  -0.008
## Course9500                                     -1.363e+01  1.751e+03  -0.008
## Course9556                                     -1.298e+01  1.751e+03  -0.007
## Course9670                                     -1.367e+01  1.751e+03  -0.008
## Course9773                                     -1.335e+01  1.751e+03  -0.008
## Course9853                                     -1.193e+01  1.751e+03  -0.007
## Course9991                                     -1.339e+01  1.751e+03  -0.008
## Daytime.evening.attendance.1                           NA         NA      NA
## Previous.qualification2                         1.407e+00  1.130e+00   1.245
## Previous.qualification3                         8.910e-01  7.626e-01   1.168
## Previous.qualification4                         2.762e+00  1.457e+00   1.895
## Previous.qualification5                         5.497e+00  6.523e+03   0.001
## Previous.qualification6                         1.300e+00  1.159e+00   1.122
## Previous.qualification9                         1.187e+01  1.791e+03   0.007
## Previous.qualification10                       -1.899e+00  1.875e+01  -0.101
## Previous.qualification12                       -6.699e-01  1.241e+00  -0.540
## Previous.qualification14                        1.060e+01  6.523e+03   0.002
## Previous.qualification15                       -5.102e+00  4.977e+00  -1.025
## Previous.qualification19                       -1.887e-03  5.364e-01  -0.004
## Previous.qualification38                       -1.912e+00  2.710e+00  -0.705
## Previous.qualification39                       -3.590e-01  1.597e+00  -0.225
## Previous.qualification40                        2.132e-01  1.020e+00   0.209
## Previous.qualification42                        1.520e+01  6.523e+03   0.002
## Previous.qualification43                       -5.179e+00  1.342e+01  -0.386
## Previous.qualification..grade.                  9.135e-04  8.093e-03   0.113
## Nacionality2                                   -1.438e+01  4.601e+03  -0.003
## Nacionality6                                   -2.927e+00  3.186e+00  -0.919
## Nacionality11                                  -1.581e+01  4.522e+03  -0.003
## Nacionality13                                  -1.268e+01  6.523e+03  -0.002
## Nacionality14                                  -1.576e+01  6.523e+03  -0.002
## Nacionality17                                   1.381e+01  6.523e+03   0.002
## Nacionality21                                   1.717e+01  6.523e+03   0.003
## Nacionality22                                  -6.410e+00  5.200e+00  -1.233
## Nacionality24                                  -6.531e+00  1.844e+00  -3.541
## Nacionality25                                  -8.684e+00  6.523e+03  -0.001
## Nacionality26                                  -2.071e+01  2.184e+03  -0.009
## Nacionality41                                   2.985e-01  8.227e-01   0.363
## Nacionality62                                  -1.408e+01  6.523e+03  -0.002
## Nacionality100                                  1.057e+01  6.523e+03   0.002
## Nacionality101                                  1.910e+01  6.523e+03   0.003
## Nacionality103                                 -1.561e+01  6.523e+03  -0.002
## Nacionality105                                  1.247e+01  6.523e+03   0.002
## Nacionality109                                  2.204e+01  6.523e+03   0.003
## Mother.s.qualification2                        -3.402e-01  6.775e-01  -0.502
## Mother.s.qualification3                         7.634e-02  3.916e-01   0.195
## Mother.s.qualification4                        -1.076e+00  1.080e+00  -0.996
## Mother.s.qualification5                         1.979e-01  1.208e+00   0.164
## Mother.s.qualification6                        -6.395e-01  5.691e+03   0.000
## Mother.s.qualification9                         8.797e-01  2.345e+00   0.375
## Mother.s.qualification10                       -2.133e+00  8.494e+00  -0.251
## Mother.s.qualification11                        1.721e+01  2.486e+03   0.007
## Mother.s.qualification12                        7.863e-01  7.522e-01   1.045
## Mother.s.qualification18                        1.679e+01  6.523e+03   0.003
## Mother.s.qualification19                        1.613e-01  2.615e-01   0.617
## Mother.s.qualification22                       -2.452e+01  9.224e+03  -0.003
## Mother.s.qualification26                       -2.862e+01  9.224e+03  -0.003
## Mother.s.qualification27                        1.561e+01  6.523e+03   0.002
## Mother.s.qualification29                       -1.663e+01  3.772e+03  -0.004
## Mother.s.qualification30                        7.033e-01  5.410e+00   0.130
## Mother.s.qualification34                        1.288e+00  1.266e+00   1.017
## Mother.s.qualification35                       -1.620e+01  6.523e+03  -0.002
## Mother.s.qualification36                       -9.252e-01  2.382e+00  -0.388
## Mother.s.qualification37                        3.189e-01  3.260e-01   0.978
## Mother.s.qualification38                       -1.031e-01  3.322e-01  -0.310
## Mother.s.qualification39                        2.852e+00  1.730e+00   1.649
## Mother.s.qualification40                        2.432e+00  1.514e+00   1.606
## Mother.s.qualification41                       -8.454e-01  1.510e+00  -0.560
## Mother.s.qualification42                        5.758e+00  2.872e+03   0.002
## Mother.s.qualification43                        3.553e+01  1.598e+04   0.002
## Father.s.qualification2                         1.477e+00  6.438e-01   2.294
## Father.s.qualification3                        -4.371e-01  4.397e-01  -0.994
## Father.s.qualification4                         3.364e-01  8.533e-01   0.394
## Father.s.qualification5                        -5.933e-02  1.555e+00  -0.038
## Father.s.qualification6                         1.156e+01  5.275e+03   0.002
## Father.s.qualification9                        -2.406e+00  4.375e+00  -0.550
## Father.s.qualification11                       -1.653e+01  2.486e+03  -0.007
## Father.s.qualification12                       -1.172e+00  8.356e-01  -1.403
## Father.s.qualification14                       -6.710e-01  2.321e+00  -0.289
## Father.s.qualification18                               NA         NA      NA
## Father.s.qualification19                       -4.655e-01  2.629e-01  -1.771
## Father.s.qualification20                        1.153e+01  6.523e+03   0.002
## Father.s.qualification22                        1.368e+01  4.101e+03   0.003
## Father.s.qualification25                        1.632e+01  6.523e+03   0.003
## Father.s.qualification26                       -3.784e+00  1.947e+01  -0.194
## Father.s.qualification27                        1.236e+01  8.656e+03   0.001
## Father.s.qualification29                        1.475e+01  2.877e+03   0.005
## Father.s.qualification30                        1.080e+01  6.523e+03   0.002
## Father.s.qualification34                        1.329e-01  1.333e+00   0.100
## Father.s.qualification35                        3.244e+01  7.750e+03   0.004
## Father.s.qualification36                       -2.214e-01  1.896e+00  -0.117
## Father.s.qualification37                       -3.876e-01  3.122e-01  -1.241
## Father.s.qualification38                       -3.066e-01  3.077e-01  -0.996
## Father.s.qualification39                        2.252e+00  1.173e+00   1.921
## Father.s.qualification40                        1.198e+00  1.599e+00   0.750
## Father.s.qualification41                        6.394e+00  6.523e+03   0.001
## Father.s.qualification42                       -1.892e+01  6.523e+03  -0.003
## Father.s.qualification43                       -2.024e+01  4.214e+03  -0.005
## Mother.s.occupation1                           -2.585e-01  1.042e+00  -0.248
## Mother.s.occupation2                           -3.089e-01  1.005e+00  -0.308
## Mother.s.occupation3                           -5.151e-01  9.621e-01  -0.535
## Mother.s.occupation4                           -4.436e-01  9.187e-01  -0.483
## Mother.s.occupation5                           -3.817e-01  9.238e-01  -0.413
## Mother.s.occupation6                           -5.863e-01  1.167e+00  -0.502
## Mother.s.occupation7                           -7.640e-02  9.641e-01  -0.079
## Mother.s.occupation8                           -1.670e-01  1.250e+00  -0.134
## Mother.s.occupation9                           -4.765e-01  9.172e-01  -0.520
## Mother.s.occupation10                           1.021e+01  6.523e+03   0.002
## Mother.s.occupation90                           5.543e-03  1.370e+00   0.004
## Mother.s.occupation99                           3.071e-01  1.066e+01   0.029
## Mother.s.occupation123                          2.076e+01  1.130e+04   0.002
## Mother.s.occupation125                         -2.345e+01  7.127e+03  -0.003
## Mother.s.occupation132                         -1.582e+01  1.130e+04  -0.001
## Mother.s.occupation134                         -3.172e+01  1.171e+04  -0.003
## Mother.s.occupation141                         -1.396e+01  8.432e+03  -0.002
## Mother.s.occupation143                         -1.398e+01  6.523e+03  -0.002
## Mother.s.occupation144                          4.383e+01  1.510e+04   0.003
## Mother.s.occupation151                         -1.409e+01  1.047e+04  -0.001
## Mother.s.occupation152                         -1.335e+01  1.030e+04  -0.001
## Mother.s.occupation153                         -1.396e+01  6.523e+03  -0.002
## Mother.s.occupation171                          1.035e+01  1.341e+04   0.001
## Mother.s.occupation175                         -1.519e+01  6.523e+03  -0.002
## Mother.s.occupation191                         -1.556e+01  6.523e+03  -0.002
## Mother.s.occupation193                          4.275e+01  1.341e+04   0.003
## Mother.s.occupation194                          1.237e+01  9.728e+03   0.001
## Father.s.occupation1                            1.081e+00  1.036e+00   1.043
## Father.s.occupation2                            1.304e+00  1.048e+00   1.244
## Father.s.occupation3                            7.355e-01  9.695e-01   0.759
## Father.s.occupation4                            1.624e+00  9.728e-01   1.669
## Father.s.occupation5                            6.420e-01  9.657e-01   0.665
## Father.s.occupation6                            2.651e-01  1.050e+00   0.252
## Father.s.occupation7                            1.015e+00  9.570e-01   1.060
## Father.s.occupation8                            9.889e-01  9.850e-01   1.004
## Father.s.occupation9                            1.374e+00  9.527e-01   1.442
## Father.s.occupation10                           1.024e+00  9.901e-01   1.034
## Father.s.occupation90                           1.433e+00  1.428e+00   1.004
## Father.s.occupation99                          -8.431e-01  1.060e+01  -0.080
## Father.s.occupation101                         -5.989e+01  1.491e+04  -0.004
## Father.s.occupation102                          6.492e-01  9.224e+03   0.000
## Father.s.occupation103                         -7.044e-01  9.224e+03   0.000
## Father.s.occupation112                          4.746e+01  1.341e+04   0.004
## Father.s.occupation122                         -3.486e+01  1.305e+04  -0.003
## Father.s.occupation124                                 NA         NA      NA
## Father.s.occupation131                         -2.901e+01  1.171e+04  -0.002
## Father.s.occupation135                         -3.622e+01  1.305e+04  -0.003
## Father.s.occupation141                         -6.047e+01  1.645e+04  -0.004
## Father.s.occupation143                          1.446e+00  9.224e+03   0.000
## Father.s.occupation144                         -6.056e+01  1.504e+04  -0.004
## Father.s.occupation151                         -3.858e-01  1.057e+04   0.000
## Father.s.occupation153                                 NA         NA      NA
## Father.s.occupation154                         -2.571e-01  9.224e+03   0.000
## Father.s.occupation163                         -2.955e+01  1.171e+04  -0.003
## Father.s.occupation171                          5.224e-01  7.972e+03   0.000
## Father.s.occupation172                                 NA         NA      NA
## Father.s.occupation174                         -1.712e-01  9.224e+03   0.000
## Father.s.occupation175                         -1.175e+00  8.320e+03   0.000
## Father.s.occupation181                         -5.735e+01  1.428e+04  -0.004
## Father.s.occupation182                         -9.920e-01  9.224e+03   0.000
## Father.s.occupation183                                 NA         NA      NA
## Father.s.occupation192                         -2.666e-01  8.012e+03   0.000
## Father.s.occupation193                         -1.123e-02  7.218e+03   0.000
## Father.s.occupation194                         -2.952e+01  1.171e+04  -0.003
## Father.s.occupation195                                 NA         NA      NA
## Admission.grade                                -3.382e-03  7.660e-03  -0.441
## Displaced1                                      4.094e-01  2.050e-01   1.997
## Educational.special.needs1                     -3.568e-01  6.616e-01  -0.539
## Debtor1                                         1.720e+00  3.285e-01   5.236
## Tuition.fees.up.to.date1                       -3.045e+00  3.949e-01  -7.711
## Gender1                                         5.838e-01  1.888e-01   3.092
## Scholarship.holder1                            -7.552e-01  2.143e-01  -3.524
## Age.at.enrollment                               1.854e-02  2.061e-02   0.900
## International1                                         NA         NA      NA
## Curricular.units.1st.sem..credited.             2.249e-01  1.459e-01   1.541
## Curricular.units.1st.sem..enrolled.             2.834e-01  2.110e-01   1.343
## Curricular.units.1st.sem..evaluations.          5.540e-02  5.316e-02   1.042
## Curricular.units.1st.sem..approved.            -6.740e-01  1.109e-01  -6.077
## Curricular.units.1st.sem..grade.                1.226e-01  6.834e-02   1.795
## Curricular.units.1st.sem..without.evaluations. -3.599e-01  2.400e-01  -1.499
## Curricular.units.2nd.sem..credited.            -3.750e-02  1.496e-01  -0.251
## Curricular.units.2nd.sem..enrolled.             8.542e-01  2.090e-01   4.087
## Curricular.units.2nd.sem..evaluations.          2.833e-02  4.830e-02   0.587
## Curricular.units.2nd.sem..approved.            -1.015e+00  9.904e-02 -10.244
## Curricular.units.2nd.sem..grade.               -2.048e-01  7.245e-02  -2.827
## Curricular.units.2nd.sem..without.evaluations. -2.923e-01  1.955e-01  -1.495
## Unemployment.rate                               1.022e-01  3.835e-02   2.664
## Inflation.rate                                 -1.040e-01  6.289e-02  -1.654
## GDP                                             3.857e-02  4.356e-02   0.885
##                                                Pr(>|z|)    
## (Intercept)                                    0.999677    
## Marital.status2                                0.853144    
## Marital.status3                                0.997901    
## Marital.status4                                0.100540    
## Marital.status5                                0.423551    
## Marital.status6                                0.906350    
## Application.mode2                              0.998102    
## Application.mode5                              0.163242    
## Application.mode7                              0.929492    
## Application.mode10                             0.014345 *  
## Application.mode15                             0.547438    
## Application.mode16                             0.375041    
## Application.mode17                             0.061372 .  
## Application.mode18                             0.853967    
## Application.mode26                             0.997889    
## Application.mode27                             0.997381    
## Application.mode39                             0.073725 .  
## Application.mode42                             0.892052    
## Application.mode43                             0.666506    
## Application.mode44                             0.886280    
## Application.mode51                             0.344668    
## Application.mode53                             0.998097    
## Application.mode57                             0.998050    
## Application.order1                             0.998488    
## Application.order2                             0.998529    
## Application.order3                             0.998473    
## Application.order4                             0.998492    
## Application.order5                             0.998522    
## Application.order6                             0.998484    
## Course171                                      0.993310    
## Course8014                                     0.993178    
## Course9003                                     0.993163    
## Course9070                                     0.993738    
## Course9085                                     0.993493    
## Course9119                                     0.994353    
## Course9130                                     0.994013    
## Course9147                                     0.993723    
## Course9238                                     0.993347    
## Course9254                                     0.993469    
## Course9500                                     0.993788    
## Course9556                                     0.994087    
## Course9670                                     0.993771    
## Course9773                                     0.993917    
## Course9853                                     0.994564    
## Course9991                                     0.993897    
## Daytime.evening.attendance.1                         NA    
## Previous.qualification2                        0.213094    
## Previous.qualification3                        0.242644    
## Previous.qualification4                        0.058044 .  
## Previous.qualification5                        0.999328    
## Previous.qualification6                        0.261662    
## Previous.qualification9                        0.994711    
## Previous.qualification10                       0.919338    
## Previous.qualification12                       0.589474    
## Previous.qualification14                       0.998703    
## Previous.qualification15                       0.305380    
## Previous.qualification19                       0.997192    
## Previous.qualification38                       0.480608    
## Previous.qualification39                       0.822163    
## Previous.qualification40                       0.834487    
## Previous.qualification42                       0.998141    
## Previous.qualification43                       0.699553    
## Previous.qualification..grade.                 0.910133    
## Nacionality2                                   0.997505    
## Nacionality6                                   0.358195    
## Nacionality11                                  0.997211    
## Nacionality13                                  0.998449    
## Nacionality14                                  0.998072    
## Nacionality17                                  0.998311    
## Nacionality21                                  0.997899    
## Nacionality22                                  0.217741    
## Nacionality24                                  0.000398 ***
## Nacionality25                                  0.998938    
## Nacionality26                                  0.992433    
## Nacionality41                                  0.716729    
## Nacionality62                                  0.998277    
## Nacionality100                                 0.998707    
## Nacionality101                                 0.997663    
## Nacionality103                                 0.998090    
## Nacionality105                                 0.998475    
## Nacionality109                                 0.997304    
## Mother.s.qualification2                        0.615582    
## Mother.s.qualification3                        0.845434    
## Mother.s.qualification4                        0.319055    
## Mother.s.qualification5                        0.869931    
## Mother.s.qualification6                        0.999910    
## Mother.s.qualification9                        0.707508    
## Mother.s.qualification10                       0.801749    
## Mother.s.qualification11                       0.994475    
## Mother.s.qualification12                       0.295896    
## Mother.s.qualification18                       0.997947    
## Mother.s.qualification19                       0.537395    
## Mother.s.qualification22                       0.997879    
## Mother.s.qualification26                       0.997525    
## Mother.s.qualification27                       0.998090    
## Mother.s.qualification29                       0.996481    
## Mother.s.qualification30                       0.896563    
## Mother.s.qualification34                       0.309173    
## Mother.s.qualification35                       0.998019    
## Mother.s.qualification36                       0.697677    
## Mother.s.qualification37                       0.327843    
## Mother.s.qualification38                       0.756342    
## Mother.s.qualification39                       0.099190 .  
## Mother.s.qualification40                       0.108243    
## Mother.s.qualification41                       0.575622    
## Mother.s.qualification42                       0.998400    
## Mother.s.qualification43                       0.998226    
## Father.s.qualification2                        0.021767 *  
## Father.s.qualification3                        0.320146    
## Father.s.qualification4                        0.693401    
## Father.s.qualification5                        0.969561    
## Father.s.qualification6                        0.998252    
## Father.s.qualification9                        0.582404    
## Father.s.qualification11                       0.994694    
## Father.s.qualification12                       0.160661    
## Father.s.qualification14                       0.772494    
## Father.s.qualification18                             NA    
## Father.s.qualification19                       0.076604 .  
## Father.s.qualification20                       0.998590    
## Father.s.qualification22                       0.997338    
## Father.s.qualification25                       0.998004    
## Father.s.qualification26                       0.845890    
## Father.s.qualification27                       0.998860    
## Father.s.qualification29                       0.995908    
## Father.s.qualification30                       0.998679    
## Father.s.qualification34                       0.920574    
## Father.s.qualification35                       0.996660    
## Father.s.qualification36                       0.907007    
## Father.s.qualification37                       0.214468    
## Father.s.qualification38                       0.319045    
## Father.s.qualification39                       0.054788 .  
## Father.s.qualification40                       0.453537    
## Father.s.qualification41                       0.999218    
## Father.s.qualification42                       0.997685    
## Father.s.qualification43                       0.996168    
## Mother.s.occupation1                           0.804139    
## Mother.s.occupation2                           0.758427    
## Mother.s.occupation3                           0.592383    
## Mother.s.occupation4                           0.629212    
## Mother.s.occupation5                           0.679456    
## Mother.s.occupation6                           0.615475    
## Mother.s.occupation7                           0.936834    
## Mother.s.occupation8                           0.893678    
## Mother.s.occupation9                           0.603360    
## Mother.s.occupation10                          0.998751    
## Mother.s.occupation90                          0.996771    
## Mother.s.occupation99                          0.977021    
## Mother.s.occupation123                         0.998534    
## Mother.s.occupation125                         0.997374    
## Mother.s.occupation132                         0.998883    
## Mother.s.occupation134                         0.997839    
## Mother.s.occupation141                         0.998679    
## Mother.s.occupation143                         0.998290    
## Mother.s.occupation144                         0.997684    
## Mother.s.occupation151                         0.998926    
## Mother.s.occupation152                         0.998966    
## Mother.s.occupation153                         0.998293    
## Mother.s.occupation171                         0.999384    
## Mother.s.occupation175                         0.998141    
## Mother.s.occupation191                         0.998097    
## Mother.s.occupation193                         0.997455    
## Mother.s.occupation194                         0.998986    
## Father.s.occupation1                           0.296834    
## Father.s.occupation2                           0.213574    
## Father.s.occupation3                           0.448076    
## Father.s.occupation4                           0.095057 .  
## Father.s.occupation5                           0.506180    
## Father.s.occupation6                           0.800754    
## Father.s.occupation7                           0.288971    
## Father.s.occupation8                           0.315401    
## Father.s.occupation9                           0.149311    
## Father.s.occupation10                          0.300935    
## Father.s.occupation90                          0.315501    
## Father.s.occupation99                          0.936578    
## Father.s.occupation101                         0.996795    
## Father.s.occupation102                         0.999944    
## Father.s.occupation103                         0.999939    
## Father.s.occupation112                         0.997175    
## Father.s.occupation122                         0.997868    
## Father.s.occupation124                               NA    
## Father.s.occupation131                         0.998023    
## Father.s.occupation135                         0.997784    
## Father.s.occupation141                         0.997067    
## Father.s.occupation143                         0.999875    
## Father.s.occupation144                         0.996787    
## Father.s.occupation151                         0.999971    
## Father.s.occupation153                               NA    
## Father.s.occupation154                         0.999978    
## Father.s.occupation163                         0.997987    
## Father.s.occupation171                         0.999948    
## Father.s.occupation172                               NA    
## Father.s.occupation174                         0.999985    
## Father.s.occupation175                         0.999887    
## Father.s.occupation181                         0.996796    
## Father.s.occupation182                         0.999914    
## Father.s.occupation183                               NA    
## Father.s.occupation192                         0.999973    
## Father.s.occupation193                         0.999999    
## Father.s.occupation194                         0.997989    
## Father.s.occupation195                               NA    
## Admission.grade                                0.658889    
## Displaced1                                     0.045870 *  
## Educational.special.needs1                     0.589746    
## Debtor1                                        1.64e-07 ***
## Tuition.fees.up.to.date1                       1.25e-14 ***
## Gender1                                        0.001986 ** 
## Scholarship.holder1                            0.000425 ***
## Age.at.enrollment                              0.368277    
## International1                                       NA    
## Curricular.units.1st.sem..credited.            0.123201    
## Curricular.units.1st.sem..enrolled.            0.179344    
## Curricular.units.1st.sem..evaluations.         0.297296    
## Curricular.units.1st.sem..approved.            1.22e-09 ***
## Curricular.units.1st.sem..grade.               0.072724 .  
## Curricular.units.1st.sem..without.evaluations. 0.133786    
## Curricular.units.2nd.sem..credited.            0.802100    
## Curricular.units.2nd.sem..enrolled.            4.36e-05 ***
## Curricular.units.2nd.sem..evaluations.         0.557464    
## Curricular.units.2nd.sem..approved.             < 2e-16 ***
## Curricular.units.2nd.sem..grade.               0.004702 ** 
## Curricular.units.2nd.sem..without.evaluations. 0.134904    
## Unemployment.rate                              0.007713 ** 
## Inflation.rate                                 0.098051 .  
## GDP                                            0.375958    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 3886.2  on 2902  degrees of freedom
## Residual deviance: 1105.3  on 2687  degrees of freedom
## AIC: 1537.3
## 
## Number of Fisher Scoring iterations: 17
train_simplified <- train_lr |>
  mutate(
    Marital.status = fct_lump_prop(Marital.status, prop = .05),
    Application.mode = fct_lump_prop(Application.mode, prop = .05),
    Course = fct_lump_prop(Course, prop = .05),
    Previous.qualification = fct_lump_prop(Previous.qualification, prop = .05),
    Nacionality = fct_lump_prop(Nacionality, prop = .05),
    Mother.s.occupation = fct_lump_prop(Mother.s.occupation, prop = .05),
    Father.s.occupation = fct_lump_prop(Father.s.occupation, prop = .05),
    Mother.s.qualification = fct_lump_prop(Mother.s.qualification, prop = .05),
    Father.s.qualification = fct_lump_prop(Father.s.qualification, prop = .05)
  ) # This helps with sparse data. Categories with few datapoins (<.05) are regarded as Other rather than taking up degrees of freedom

lump_levels <- list(
  Marital.status = levels(train_simplified$Marital.status),
  Application.mode = levels(train_simplified$Application.mode),
  Course = levels(train_simplified$Course),
  Previous.qualification = levels(train_simplified$Previous.qualification),
  Nacionality = levels(train_simplified$Nacionality),
  Mother.s.occupation = levels(train_simplified$Mother.s.occupation),
  Father.s.occupation = levels(train_simplified$Father.s.occupation),
  Mother.s.qualification = levels(train_simplified$Mother.s.qualification),
  Father.s.qualification = levels(train_simplified$Father.s.qualification)
)

simplifiedLogistic <- glm(Target ~ .,
  data = train_simplified,
  family = binomial
)
summary(simplifiedLogistic) # Notably, Marital.status, Application.order, and anything about parent occupation/qualification yield no significance. Too further simplify, I will remove these entirely (Although, I could see an argument to be made to retain marital status. Gut instinct tells me it could have a larger and more persistent effect on academic success than the model currently identifies.) Also, alias(simplifiedLogistic) reveals that Daytime.evening.attendance and International are redundant from oher columns, this makes estimates of variance unreliable (or in tthis case, not able to be estimatetd at all). Removing.
## 
## Call:
## glm(formula = Target ~ ., family = binomial, data = train_simplified)
## 
## Coefficients: (2 not defined because of singularities)
##                                                  Estimate Std. Error z value
## (Intercept)                                    -6.894e+00  3.247e+02  -0.021
## Marital.status2                                 1.291e-01  3.684e-01   0.351
## Marital.statusOther                            -5.909e-01  5.885e-01  -1.004
## Application.mode17                              4.751e-01  2.111e-01   2.251
## Application.mode39                              3.938e-01  3.301e-01   1.193
## Application.mode43                             -2.284e-01  3.728e-01  -0.613
## Application.modeOther                          -1.417e-01  2.879e-01  -0.492
## Application.order1                              7.523e+00  3.247e+02   0.023
## Application.order2                              7.345e+00  3.247e+02   0.023
## Application.order3                              7.781e+00  3.247e+02   0.024
## Application.order4                              7.567e+00  3.247e+02   0.023
## Application.order5                              7.342e+00  3.247e+02   0.023
## Application.order6                              7.522e+00  3.247e+02   0.023
## Course9085                                      7.930e-01  5.389e-01   1.471
## Course9147                                      1.137e+00  4.958e-01   2.294
## Course9238                                      2.464e-01  5.266e-01   0.468
## Course9254                                      6.691e-01  5.288e-01   1.265
## Course9500                                      1.233e+00  5.069e-01   2.433
## Course9670                                      1.144e+00  5.043e-01   2.269
## Course9773                                      1.470e+00  4.977e-01   2.954
## Course9991                                      1.717e+00  5.117e-01   3.355
## CourseOther                                     1.626e+00  4.530e-01   3.589
## Daytime.evening.attendance.1                           NA         NA      NA
## Previous.qualificationOther                     1.817e-01  2.716e-01   0.669
## Previous.qualification..grade.                  5.964e-04  7.190e-03   0.083
## NacionalityOther                               -9.902e-01  5.672e-01  -1.746
## Mother.s.qualification3                         2.666e-01  3.405e-01   0.783
## Mother.s.qualification19                        1.450e-01  2.416e-01   0.600
## Mother.s.qualification37                        1.866e-01  2.967e-01   0.629
## Mother.s.qualification38                       -1.313e-01  3.049e-01  -0.431
## Mother.s.qualificationOther                     3.257e-01  3.369e-01   0.967
## Father.s.qualification3                        -2.087e-01  3.641e-01  -0.573
## Father.s.qualification19                       -3.437e-01  2.430e-01  -1.414
## Father.s.qualification37                       -2.988e-01  2.842e-01  -1.051
## Father.s.qualification38                       -1.983e-01  2.832e-01  -0.700
## Father.s.qualificationOther                     3.693e-01  3.396e-01   1.087
## Mother.s.occupation3                           -1.302e-01  4.106e-01  -0.317
## Mother.s.occupation4                           -1.263e-01  3.894e-01  -0.324
## Mother.s.occupation5                           -9.243e-02  4.264e-01  -0.217
## Mother.s.occupation7                            1.907e-01  4.994e-01   0.382
## Mother.s.occupation9                           -1.646e-01  4.029e-01  -0.409
## Mother.s.occupationOther                       -2.493e-01  4.100e-01  -0.608
## Father.s.occupation4                            6.096e-01  3.494e-01   1.745
## Father.s.occupation5                           -7.120e-02  3.505e-01  -0.203
## Father.s.occupation6                           -5.761e-01  4.772e-01  -1.207
## Father.s.occupation7                            2.678e-01  3.292e-01   0.814
## Father.s.occupation8                            1.179e-01  3.898e-01   0.302
## Father.s.occupation9                            5.119e-01  3.138e-01   1.632
## Father.s.occupation10                           2.474e-02  4.038e-01   0.061
## Father.s.occupationOther                        7.922e-02  3.550e-01   0.223
## Admission.grade                                -1.052e-02  6.724e-03  -1.565
## Displaced1                                      3.393e-01  1.858e-01   1.827
## Educational.special.needs1                     -5.356e-02  5.891e-01  -0.091
## Debtor1                                         1.235e+00  2.809e-01   4.397
## Tuition.fees.up.to.date1                       -2.781e+00  3.301e-01  -8.424
## Gender1                                         2.825e-01  1.658e-01   1.704
## Scholarship.holder1                            -7.045e-01  1.980e-01  -3.557
## Age.at.enrollment                               2.611e-02  1.807e-02   1.445
## International1                                         NA         NA      NA
## Curricular.units.1st.sem..credited.             2.550e-01  1.262e-01   2.021
## Curricular.units.1st.sem..enrolled.             2.847e-01  1.772e-01   1.607
## Curricular.units.1st.sem..evaluations.          5.046e-02  4.669e-02   1.081
## Curricular.units.1st.sem..approved.            -6.770e-01  9.886e-02  -6.848
## Curricular.units.1st.sem..grade.                1.415e-01  5.588e-02   2.533
## Curricular.units.1st.sem..without.evaluations. -3.252e-01  2.092e-01  -1.554
## Curricular.units.2nd.sem..credited.            -1.640e-02  1.336e-01  -0.123
## Curricular.units.2nd.sem..enrolled.             8.753e-01  1.693e-01   5.171
## Curricular.units.2nd.sem..evaluations.         -5.691e-05  4.348e-02  -0.001
## Curricular.units.2nd.sem..approved.            -1.028e+00  8.786e-02 -11.706
## Curricular.units.2nd.sem..grade.               -1.173e-01  5.569e-02  -2.106
## Curricular.units.2nd.sem..without.evaluations. -2.462e-01  1.748e-01  -1.409
## Unemployment.rate                               6.228e-02  3.348e-02   1.860
## Inflation.rate                                 -6.024e-02  5.728e-02  -1.052
## GDP                                             1.835e-02  3.892e-02   0.471
##                                                Pr(>|z|)    
## (Intercept)                                    0.983063    
## Marital.status2                                0.725931    
## Marital.statusOther                            0.315272    
## Application.mode17                             0.024408 *  
## Application.mode39                             0.232817    
## Application.mode43                             0.540126    
## Application.modeOther                          0.622505    
## Application.order1                             0.981518    
## Application.order2                             0.981956    
## Application.order3                             0.980885    
## Application.order4                             0.981411    
## Application.order5                             0.981962    
## Application.order6                             0.981520    
## Course9085                                     0.141165    
## Course9147                                     0.021778 *  
## Course9238                                     0.639828    
## Course9254                                     0.205789    
## Course9500                                     0.014963 *  
## Course9670                                     0.023252 *  
## Course9773                                     0.003139 ** 
## Course9991                                     0.000792 ***
## CourseOther                                    0.000333 ***
## Daytime.evening.attendance.1                         NA    
## Previous.qualificationOther                    0.503615    
## Previous.qualification..grade.                 0.933895    
## NacionalityOther                               0.080845 .  
## Mother.s.qualification3                        0.433663    
## Mother.s.qualification19                       0.548396    
## Mother.s.qualification37                       0.529445    
## Mother.s.qualification38                       0.666748    
## Mother.s.qualificationOther                    0.333716    
## Father.s.qualification3                        0.566586    
## Father.s.qualification19                       0.157337    
## Father.s.qualification37                       0.293031    
## Father.s.qualification38                       0.483825    
## Father.s.qualificationOther                    0.276826    
## Mother.s.occupation3                           0.751155    
## Mother.s.occupation4                           0.745754    
## Mother.s.occupation5                           0.828386    
## Mother.s.occupation7                           0.702549    
## Mother.s.occupation9                           0.682788    
## Mother.s.occupationOther                       0.543096    
## Father.s.occupation4                           0.081057 .  
## Father.s.occupation5                           0.839014    
## Father.s.occupation6                           0.227367    
## Father.s.occupation7                           0.415907    
## Father.s.occupation8                           0.762287    
## Father.s.occupation9                           0.102766    
## Father.s.occupation10                          0.951152    
## Father.s.occupationOther                       0.823428    
## Admission.grade                                0.117660    
## Displaced1                                     0.067752 .  
## Educational.special.needs1                     0.927561    
## Debtor1                                        1.10e-05 ***
## Tuition.fees.up.to.date1                        < 2e-16 ***
## Gender1                                        0.088328 .  
## Scholarship.holder1                            0.000374 ***
## Age.at.enrollment                              0.148469    
## International1                                       NA    
## Curricular.units.1st.sem..credited.            0.043318 *  
## Curricular.units.1st.sem..enrolled.            0.108104    
## Curricular.units.1st.sem..evaluations.         0.279846    
## Curricular.units.1st.sem..approved.            7.49e-12 ***
## Curricular.units.1st.sem..grade.               0.011321 *  
## Curricular.units.1st.sem..without.evaluations. 0.120162    
## Curricular.units.2nd.sem..credited.            0.902277    
## Curricular.units.2nd.sem..enrolled.            2.32e-07 ***
## Curricular.units.2nd.sem..evaluations.         0.998956    
## Curricular.units.2nd.sem..approved.             < 2e-16 ***
## Curricular.units.2nd.sem..grade.               0.035165 *  
## Curricular.units.2nd.sem..without.evaluations. 0.158922    
## Unemployment.rate                              0.062847 .  
## Inflation.rate                                 0.292918    
## GDP                                            0.637361    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 3886.2  on 2902  degrees of freedom
## Residual deviance: 1245.1  on 2831  degrees of freedom
## AIC: 1389.1
## 
## Number of Fisher Scoring iterations: 11
train_simplified <- train_simplified |>
  select(
    -c(
      Marital.status,
      Application.order,
      Daytime.evening.attendance.,
      International
    ),
    -matches("(?i)mother"),
    -matches("(?i)father")
  )

simplifiedLogistic <- glm(Target ~ .,
  data = train_simplified,
  family = binomial
)
summary(simplifiedLogistic) # The AIC has decreased between. The fully saturated and this simplified version. Put simply, AIC = “how good your model fits the data” + “how complicated your model is”. Noise has been removed. Now to test if the model is predicting reasonably well.
## 
## Call:
## glm(formula = Target ~ ., family = binomial, data = train_simplified)
## 
## Coefficients:
##                                                  Estimate Std. Error z value
## (Intercept)                                     0.8526454  1.1488067   0.742
## Application.mode17                              0.4188391  0.2018028   2.075
## Application.mode39                              0.3574525  0.3199969   1.117
## Application.mode43                             -0.1359885  0.3579053  -0.380
## Application.modeOther                          -0.1461884  0.2776736  -0.526
## Course9085                                      0.7089113  0.5157969   1.374
## Course9147                                      1.0696243  0.4752356   2.251
## Course9238                                      0.1847771  0.5159368   0.358
## Course9254                                      0.5873473  0.5066059   1.159
## Course9500                                      1.2231052  0.4911947   2.490
## Course9670                                      1.1358454  0.4863071   2.336
## Course9773                                      1.3690876  0.4842612   2.827
## Course9991                                      1.6355578  0.4905435   3.334
## CourseOther                                     1.5482948  0.4334950   3.572
## Previous.qualificationOther                     0.1213098  0.2627736   0.462
## Previous.qualification..grade.                 -0.0004876  0.0068812  -0.071
## NacionalityOther                               -0.8062682  0.5416090  -1.489
## Admission.grade                                -0.0093100  0.0064592  -1.441
## Displaced1                                      0.3541893  0.1768298   2.003
## Educational.special.needs1                      0.0414567  0.5869996   0.071
## Debtor1                                         1.1942206  0.2720851   4.389
## Tuition.fees.up.to.date1                       -2.7733999  0.3243995  -8.549
## Gender1                                         0.3480420  0.1612680   2.158
## Scholarship.holder1                            -0.7764812  0.1886097  -4.117
## Age.at.enrollment                               0.0263483  0.0146191   1.802
## Curricular.units.1st.sem..credited.             0.2499171  0.1243220   2.010
## Curricular.units.1st.sem..enrolled.             0.3042771  0.1751229   1.738
## Curricular.units.1st.sem..evaluations.          0.0423247  0.0449229   0.942
## Curricular.units.1st.sem..approved.            -0.6805951  0.0963783  -7.062
## Curricular.units.1st.sem..grade.                0.1495343  0.0551541   2.711
## Curricular.units.1st.sem..without.evaluations. -0.2748761  0.2065566  -1.331
## Curricular.units.2nd.sem..credited.             0.0140129  0.1305765   0.107
## Curricular.units.2nd.sem..enrolled.             0.8158481  0.1670733   4.883
## Curricular.units.2nd.sem..evaluations.          0.0005362  0.0423913   0.013
## Curricular.units.2nd.sem..approved.            -1.0086864  0.0848207 -11.892
## Curricular.units.2nd.sem..grade.               -0.1111705  0.0548564  -2.027
## Curricular.units.2nd.sem..without.evaluations. -0.2293201  0.1724447  -1.330
## Unemployment.rate                               0.0500493  0.0308251   1.624
## Inflation.rate                                 -0.0438319  0.0548049  -0.800
## GDP                                             0.0101367  0.0371851   0.273
##                                                Pr(>|z|)    
## (Intercept)                                    0.457966    
## Application.mode17                             0.037941 *  
## Application.mode39                             0.263973    
## Application.mode43                             0.703978    
## Application.modeOther                          0.598558    
## Course9085                                     0.169317    
## Course9147                                     0.024403 *  
## Course9238                                     0.720239    
## Course9254                                     0.246302    
## Course9500                                     0.012772 *  
## Course9670                                     0.019509 *  
## Course9773                                     0.004696 ** 
## Course9991                                     0.000856 ***
## CourseOther                                    0.000355 ***
## Previous.qualificationOther                    0.644331    
## Previous.qualification..grade.                 0.943513    
## NacionalityOther                               0.136579    
## Admission.grade                                0.149483    
## Displaced1                                     0.045178 *  
## Educational.special.needs1                     0.943696    
## Debtor1                                        1.14e-05 ***
## Tuition.fees.up.to.date1                        < 2e-16 ***
## Gender1                                        0.030915 *  
## Scholarship.holder1                            3.84e-05 ***
## Age.at.enrollment                              0.071494 .  
## Curricular.units.1st.sem..credited.            0.044406 *  
## Curricular.units.1st.sem..enrolled.            0.082298 .  
## Curricular.units.1st.sem..evaluations.         0.346109    
## Curricular.units.1st.sem..approved.            1.64e-12 ***
## Curricular.units.1st.sem..grade.               0.006704 ** 
## Curricular.units.1st.sem..without.evaluations. 0.183270    
## Curricular.units.2nd.sem..credited.            0.914538    
## Curricular.units.2nd.sem..enrolled.            1.04e-06 ***
## Curricular.units.2nd.sem..evaluations.         0.989908    
## Curricular.units.2nd.sem..approved.             < 2e-16 ***
## Curricular.units.2nd.sem..grade.               0.042706 *  
## Curricular.units.2nd.sem..without.evaluations. 0.183578    
## Unemployment.rate                              0.104450    
## Inflation.rate                                 0.423838    
## GDP                                            0.785161    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 3886.2  on 2902  degrees of freedom
## Residual deviance: 1274.3  on 2863  degrees of freedom
## AIC: 1354.3
## 
## Number of Fisher Scoring iterations: 8
importance <- varImp(simplifiedLogistic, scale = TRUE)
importance <- importance[order(-importance$Overall), , drop = FALSE]
importance$Variable <- rownames(importance)

ggplot(
  importance[1:10, ],
  aes(
    x = reorder(Variable, Overall),
    y = Overall
  )
) +
  geom_col(fill = "#003057") + # WGU COLOR!
  coord_flip() +
  theme_minimal() +
  labs(
    title = "Top 10 Most Important Variables",
    x = NULL,
    y = "Importance (scaled 0-100)"
  ) # It is also important to identify WHICH variables are most useful for predicting the Target. This model suggests that the number of "curricular units successfully completed in the second semester" is the strongest predictor of Dropout. Students who complete fewer units in this period are substantially more likely to drop out, holding all other variables constant. This may indicate that academic performance in the second semester is critical for student persistence, and could represent a valuable time for intervention. However, this result reflects an association rather than a causal effect. A controlled study or experimental intervention would be required to determine whether improving second-semester performance directly reduces dropout rates.

test_simplified <- test_lr |>
  mutate(
    Marital.status = fct_other(Marital.status, keep = lump_levels$Marital.status),
    Application.mode = fct_other(Application.mode, keep = lump_levels$Application.mode),
    Course = fct_other(Course, keep = lump_levels$Course),
    Previous.qualification = fct_other(Previous.qualification, keep = lump_levels$Previous.qualification),
    Nacionality = fct_other(Nacionality, keep = lump_levels$Nacionality),
    Mother.s.occupation = fct_other(Mother.s.occupation, keep = lump_levels$Mother.s.occupation),
    Father.s.occupation = fct_other(Father.s.occupation, keep = lump_levels$Father.s.occupation),
    Mother.s.qualification = fct_other(Mother.s.qualification, keep = lump_levels$Mother.s.qualification),
    Father.s.qualification = fct_other(Father.s.qualification, keep = lump_levels$Father.s.qualification)
  ) |>
  select(
    -c(
      Marital.status,
      Application.order,
      Daytime.evening.attendance.,
      International
    ),
    -matches("(?i)mother"),
    -matches("(?i)father")
  ) # In order to run this logistic regression, the same changes made to the training data need to be made on the test data. In tidymodels, this would be saved as a recipe and applied to test data

predProbs <- predict(simplifiedLogistic,
  newdata = test_simplified,
  type = "response"
) # This is my actual predictions for each student!

predClass <- ifelse(predProbs > .5,
  "Dropout",
  "Graduate"
) |>
  factor(levels = levels(test_simplified$Target)) # Convert completion prediction to class labels (.5 threshold)

confusionMatrix(predClass,
  test_simplified$Target,
  positive = "Dropout"
) # Now for Model Evaluation. I predicted Dropout or Graduate for each student, but was the model accurate?
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction Graduate Dropout
##   Graduate      426      46
##   Dropout        16     239
##                                         
##                Accuracy : 0.9147        
##                  95% CI : (0.892, 0.934)
##     No Information Rate : 0.608         
##     P-Value [Acc > NIR] : < 2.2e-16     
##                                         
##                   Kappa : 0.8177        
##                                         
##  Mcnemar's Test P-Value : 0.0002305     
##                                         
##             Sensitivity : 0.8386        
##             Specificity : 0.9638        
##          Pos Pred Value : 0.9373        
##          Neg Pred Value : 0.9025        
##              Prevalence : 0.3920        
##          Detection Rate : 0.3287        
##    Detection Prevalence : 0.3508        
##       Balanced Accuracy : 0.9012        
##                                         
##        'Positive' Class : Dropout       
## 
# ROC (Receiver Operating Characteristic) curve for Model Evaluation
rocObj <- roc(
  response = test_simplified$Target,
  predictor = predProbs,
  levels = c("Graduate", "Dropout")
)
## Setting direction: controls < cases
plot(rocObj,
  main = "ROC Curve for Baseline Logistic Regression",
  col = "#003057",
  lwd = 2,
  print.thres = seq(.2, .8, by = .2),
  print.thres.pch = 19,
  print.thres.col = "#97E152",
  print.thres.cex = .8,
  print.thres.adj = c(-.1, 1.5),
  print.auc = TRUE,
  print.auc.x = .4,
  print.auc.y = .15
)

Of all the numbers in this output, the most important one is likely Sensitivity: .8386. The positive case is Dropout, meaning I am interested in flagging students at risk of dropping out and, therefore, might benefit from an intervention of some kind. In this case, the model correctly flagged 84% of those who ended up dropping out, allowing allocation of resources toward intervening on their behalf. However, the model misses 16% of students who may have benefited from intervention but received none. These correctly flagged dropouts come at a cost: 4% of students flagged for intervention were actually going to graduate without it. Depending on the cost of these interventions (fiscal, time, and opportunity costs), this rate maybe be deemed acceptable. This is called Specificity. If the threshold to flag a “Dropout” is lowered, more flasely flagged “Dropouts” must be accepted. This balance is a trade-off that cannot be avoided and is shown in the ROC curve.

Previously identified was high-correlation in early semester performance. One way to handle this is to perform Principal Component Analysis (PCA). This reduces higher-dimensional data into lower dimensions based on its most important (or principal) variables (or components). This is Unsupervised Learning (there is no “correct”, labeled grouping to evaluate against) and will reduce correlation and perhaps improve my model.

# PCA Logistic Regression (Early semester performance compression)
set.seed(8247)

train_pca <- train_simplified # One option would be to start back at the full model. For convenience, and because previous removals appear valid regardless of model, I am going to continue with the simplified model landed on in the previous logistic model.
test_pca <- test_simplified

pca_recipe <- recipe(Target ~ ., data = train_pca) |>
  step_pca(matches("1st|2nd"), num_comp = 5) # The recipe, prep, bake preprocessing can be unintuitive. This will be used often throughout. Think of it as plan, calculate, apply

pca_prep <- prep(pca_recipe, training = train_pca)

train_pca_baked <- bake(pca_prep, new_data = NULL)
test_pca_baked <- bake(pca_prep, new_data = test_pca)

pcaLogistic <- glm(Target ~ .,
  data = train_pca_baked,
  family = binomial
)
summary(pcaLogistic) # Early semeseter performance coluumns have been replaced with PC1-5
## 
## Call:
## glm(formula = Target ~ ., family = binomial, data = train_pca_baked)
## 
## Coefficients:
##                                 Estimate Std. Error z value Pr(>|z|)    
## (Intercept)                     1.172978   1.046482   1.121 0.262340    
## Application.mode17              0.412180   0.184588   2.233 0.025550 *  
## Application.mode39              0.086719   0.279275   0.311 0.756168    
## Application.mode43              0.453635   0.304964   1.488 0.136882    
## Application.modeOther           0.323475   0.236664   1.367 0.171685    
## Course9085                      0.961429   0.450297   2.135 0.032753 *  
## Course9147                      1.587206   0.415504   3.820 0.000133 ***
## Course9238                      0.097234   0.453212   0.215 0.830122    
## Course9254                      0.742077   0.441166   1.682 0.092553 .  
## Course9500                      0.088125   0.423568   0.208 0.835186    
## Course9670                      1.514696   0.433907   3.491 0.000482 ***
## Course9773                      1.112284   0.431227   2.579 0.009899 ** 
## Course9991                      1.773639   0.421536   4.208 2.58e-05 ***
## CourseOther                     1.459106   0.378105   3.859 0.000114 ***
## Previous.qualificationOther     0.338767   0.225474   1.502 0.132977    
## Previous.qualification..grade.  0.002086   0.006184   0.337 0.735847    
## NacionalityOther               -1.228408   0.480491  -2.557 0.010571 *  
## Admission.grade                -0.011330   0.005860  -1.933 0.053209 .  
## Displaced1                      0.326191   0.156371   2.086 0.036978 *  
## Educational.special.needs1      0.333767   0.546078   0.611 0.541062    
## Debtor1                         1.146415   0.241982   4.738 2.16e-06 ***
## Tuition.fees.up.to.date1       -2.905069   0.299393  -9.703  < 2e-16 ***
## Gender1                         0.604228   0.142217   4.249 2.15e-05 ***
## Scholarship.holder1            -0.957927   0.175358  -5.463 4.69e-08 ***
## Age.at.enrollment               0.048667   0.012677   3.839 0.000124 ***
## Unemployment.rate              -0.005998   0.027048  -0.222 0.824502    
## Inflation.rate                  0.004263   0.048360   0.088 0.929750    
## GDP                             0.003909   0.032491   0.120 0.904243    
## PC1                             0.036455   0.010042   3.630 0.000283 ***
## PC2                            -0.316721   0.022796 -13.894  < 2e-16 ***
## PC3                             0.527300   0.034101  15.463  < 2e-16 ***
## PC4                            -0.686069   0.075920  -9.037  < 2e-16 ***
## PC5                             0.103682   0.049679   2.087 0.036885 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 3886.2  on 2902  degrees of freedom
## Residual deviance: 1524.3  on 2870  degrees of freedom
## AIC: 1590.3
## 
## Number of Fisher Scoring iterations: 7
# Predictiioins as probabilities
predProbs_pca <- predict(pcaLogistic,
  newdata = test_pca_baked,
  type = "response"
) # This is my actual predictions for each student!

# Convert to class labels (.5 threshold)
predClass_pca <- ifelse(predProbs_pca > .5,
  "Dropout",
  "Graduate"
) |>
  factor(levels = levels(test_pca_baked$Target))

# Confusion matrix
confusionMatrix(predClass_pca,
  test_pca_baked$Target,
  positive = "Dropout"
)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction Graduate Dropout
##   Graduate      410      57
##   Dropout        32     228
##                                           
##                Accuracy : 0.8776          
##                  95% CI : (0.8515, 0.9005)
##     No Information Rate : 0.608           
##     P-Value [Acc > NIR] : < 2e-16         
##                                           
##                   Kappa : 0.7391          
##                                           
##  Mcnemar's Test P-Value : 0.01096         
##                                           
##             Sensitivity : 0.8000          
##             Specificity : 0.9276          
##          Pos Pred Value : 0.8769          
##          Neg Pred Value : 0.8779          
##              Prevalence : 0.3920          
##          Detection Rate : 0.3136          
##    Detection Prevalence : 0.3576          
##       Balanced Accuracy : 0.8638          
##                                           
##        'Positive' Class : Dropout         
## 
# ROC curve
rocObj_pca <- roc(
  response = test_pca_baked$Target,
  predictor = predProbs_pca,
  levels = c("Graduate", "Dropout")
)
## Setting direction: controls < cases
plot(rocObj_pca,
  main = "ROC Curve for Logistic Regression with PCA",
  col = "#003057",
  lwd = 2,
  print.thres = seq(.2, .8, by = .2),
  print.thres.pch = 19,
  print.thres.col = "#97E152",
  print.thres.cex = .8,
  print.thres.adj = c(-.1, 1.5),
  print.auc = TRUE,
  print.auc.x = .4,
  print.auc.y = .15
)

The baseline logistic regression outperformed the PCA-based model on every metric that matters for this use case. AIC increased from 1354 to 1590. Notably, the PCA model is simpler, so a higher AIC despite fewer predictors indicates real information loss, not just added complexity. More critically, Sensitivity dropped from .8386 to .8000, meaning the PCA model misses more at-risk students. Since catching Dropouts is the primary objective, the baseline model is the stronger choice. PCA improved multicollinearity and produced a more compact representation, but at an unacceptable cost to sensitivity. Note: While PCA produces more reliable standard errors. That means in some instances (ie causal inferences) one might prefer PCA even though it has lower predictive power.

I can apply this same basic framework to other models. Here is a K-Nearest Neighbors. This maps out data. on a high-dimension cartesian coordinate plan and groups new data points based on closest, known points (Think “You are the average of the 5 people wiith whom you spend the most time”). However, this requires only numeric values be used. It also is a good idea to normalize data; otherwise, variables with larger values would always have a greater influence on which observations are considered neighbors (imagine plotting income and height— income would dominate the distance calculation because its values are much larger).

# K-Nearest Neighbors
set.seed(8247)

train_knn <- train_simplified |>
  select(where(is.numeric), Target) # Only numeric columns
test_knn <- test_simplified |>
  select(where(is.numeric), Target)

knn_recipe <- recipe(Target ~ ., data = train_knn) |>
  step_normalize(all_numeric_predictors()) # Normalize

knn_spec <- nearest_neighbor(neighbors = tune()) |>
  set_engine("kknn") |> # Which package should be used
  set_mode("classification") # Regression or classification

knn_wf <- workflow() |>
  add_recipe(knn_recipe) |>
  add_model(knn_spec) # Workflow is container for recipe and model

folds <- vfold_cv(train_knn, v = 5) # 5-fold cross validation

knnTune <- tune_grid(
  knn_wf,
  resamples = folds,
  grid = tibble(neighbors = seq(3, 25, by = 1)),
  metrics = metric_set(accuracy)
) # Finds optimal hyperparameters (In KNN, number of neighbors)

autoplot(knnTune)

knn_best <- select_best(knnTune, metric = "accuracy") # RMSE, MAE, Precision, Recall
knn_best
## # A tibble: 1 × 2
##   neighbors .config         
##       <dbl> <chr>           
## 1        23 pre0_mod21_post0
final_knn <- finalize_workflow(knn_wf, knn_best) |>
  fit(train_knn) # Notice KNN has no "training" phase. It is a lazy learner

predClass_knn <- predict(final_knn, test_knn, type = "class") |>
  bind_cols(test_knn) # This is my actual predictions for each student!

confusionMatrix(predClass_knn$.pred_class, test_knn$Target, positive = "Dropout")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction Graduate Dropout
##   Graduate      423      85
##   Dropout        19     200
##                                           
##                Accuracy : 0.8569          
##                  95% CI : (0.8294, 0.8816)
##     No Information Rate : 0.608           
##     P-Value [Acc > NIR] : < 2.2e-16       
##                                           
##                   Kappa : 0.687           
##                                           
##  Mcnemar's Test P-Value : 1.844e-10       
##                                           
##             Sensitivity : 0.7018          
##             Specificity : 0.9570          
##          Pos Pred Value : 0.9132          
##          Neg Pred Value : 0.8327          
##              Prevalence : 0.3920          
##          Detection Rate : 0.2751          
##    Detection Prevalence : 0.3012          
##       Balanced Accuracy : 0.8294          
##                                           
##        'Positive' Class : Dropout         
## 
predProb_knn <- predict(final_knn, test_knn, type = "prob") |>
  bind_cols(test_knn) # Probabilities needed for ROC, not just predicted class

rocObj_knn <- roc(
  response = test_knn$Target,
  predictor = predProb_knn$.pred_Dropout,
  levels = c("Graduate", "Dropout")
)
## Setting direction: controls < cases
plot(rocObj_knn,
  main = "ROC Curve for KNN",
  col = "#003057",
  lwd = 2,
  print.thres = seq(.2, .8, by = .2),
  print.thres.pch = 19,
  print.thres.col = "#97E152",
  print.thres.cex = .8,
  print.thres.adj = c(-.1, 1.5),
  print.auc = TRUE,
  print.auc.x = .4,
  print.auc.y = .15
)

KNN did worse than our basic model. Sensitivity decreases substaintially (From .8386 all the way down to .7018). Oddly enough, Specificity increased (From .9276 to .9570). It appears this model does a slightly better job identifying Graduates. If that was my output of interest, I might be interested in keeping this model. But knowing the goals of his study, I will continue to use the baseline as the preferred model.

Another relatively simple model is the Random Forest. It consists of many decision trees (like a forest!) that are built independently of one another. For classification, each tree casts one vote, and the majority vote determines the final prediction. The “random” component comes from each tree being built using a random subset of the predictors (and typically a random sample of the data). This differs from a single optimal decision tree, which uses all predictors.

# Random Forest
set.seed(8247)

train_rf <- train_simplified |>
  select(Target, everything())
test_rf <- test_simplified |>
  select(Target, everything())

rf_spec <- rand_forest(mtry = tune(), trees = tune()) |> # Predictor subset size, Number of trees in forest
  set_engine("ranger") |>
  set_mode("classification")

rf_wf <- workflow() |>
  add_formula(Target ~ .) |>
  add_model(rf_spec)

folds <- vfold_cv(train_rf, v = 5)

rfTune <- tune_grid(
  rf_wf,
  resamples = folds,
  grid = 30, # Combinations of hyperparameters to try
  metrics = metric_set(accuracy)
) # Finds optimal hyperparameter (Specified in rf_spec)
## i Creating pre-processing data to finalize 1 unknown parameter: "mtry"
autoplot(rfTune)

rf_best <- select_best(rfTune, metric = "accuracy")
rf_best
## # A tibble: 1 × 3
##    mtry trees .config         
##   <int> <int> <chr>           
## 1    17   690 pre0_mod19_post0
final_rf <- finalize_workflow(rf_wf, rf_best) |>
  fit(train_rf)

predClass_rf <- predict(final_rf, test_rf, type = "class") |>
  bind_cols(test_rf) # This is my actual predictions for each student!

confusionMatrix(predClass_rf$.pred_class, test_rf$Target, positive = "Dropout")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction Graduate Dropout
##   Graduate      423      47
##   Dropout        19     238
##                                           
##                Accuracy : 0.9092          
##                  95% CI : (0.8859, 0.9291)
##     No Information Rate : 0.608           
##     P-Value [Acc > NIR] : < 2.2e-16       
##                                           
##                   Kappa : 0.8062          
##                                           
##  Mcnemar's Test P-Value : 0.000889        
##                                           
##             Sensitivity : 0.8351          
##             Specificity : 0.9570          
##          Pos Pred Value : 0.9261          
##          Neg Pred Value : 0.9000          
##              Prevalence : 0.3920          
##          Detection Rate : 0.3274          
##    Detection Prevalence : 0.3535          
##       Balanced Accuracy : 0.8961          
##                                           
##        'Positive' Class : Dropout         
## 
predProb_rf <- predict(final_rf, test_rf, type = "prob") |>
  bind_cols(test_rf)

rocObj_rf <- roc(
  response = test_rf$Target,
  predictor = predProb_rf$.pred_Dropout,
  levels = c("Graduate", "Dropout")
)
## Setting direction: controls < cases
plot(rocObj_rf,
  main = "ROC Curve for Random Forest",
  col = "#003057",
  lwd = 2,
  print.thres = seq(.2, .8, by = .2),
  print.thres.pch = 19,
  print.thres.col = "#97E152",
  print.thres.cex = .8,
  print.thres.adj = c(-.1, 1.5),
  print.auc = TRUE,
  print.auc.x = .4,
  print.auc.y = .15
)

Random Forest also did slightly worse than my basic model. Sensitivity decreased (From .8386 to .8351). Specificity again increased (From .9276 to .9570). Again, I will continue to use the baseline as the preferred model. (Note: The Random Forest algorithm also handles multicolinearity because it does no rely on coefficient estimates).

XGBoost is similar in that it builds an ensemble of decision trees, but unlike Random Forest it builds them sequentially rather than independently. Each tree is trained to correct the error from the previous trees, gradually improving performance. And as opposed to Random Forest’s majority vote, XGBoost predicts by summing the outputs of all trees, with each tree’s contribution scaled by a learning rate.

# XGBoost
set.seed(8247)

train_xgb <- train_simplified
test_xgb <- test_simplified

xgb_spec <- boost_tree(
  trees = tune(), # Number of boosting rounds
  tree_depth = tune(), # Number of levels
  learn_rate = tune(), # Learning rate after rounds
  loss_reduction = tune(), # Split improvement to justify inclusion
  sample_size = tune(), # Sample size from training data
  mtry = tune() # Predictor subset size
) |>
  set_engine("xgboost") |>
  set_mode("classification")

xgb_wf <- workflow() |>
  add_formula(Target ~ .) |>
  add_model(xgb_spec)

folds <- vfold_cv(train_xgb, v = 5)

xgbTune <- tune_grid(
  xgb_wf,
  resamples = folds,
  grid = 30,
  metrics = metric_set(accuracy)
) # Finds optimal hyperparameters (see xgb_spec above)
## i Creating pre-processing data to finalize 1 unknown parameter: "mtry"
autoplot(xgbTune)

xgb_best <- select_best(xgbTune, metric = "accuracy")
xgb_best
## # A tibble: 1 × 7
##    mtry trees tree_depth learn_rate loss_reduction sample_size .config         
##   <int> <int>      <int>      <dbl>          <dbl>       <dbl> <chr>           
## 1    12  1172         13     0.0788   0.0000000597       0.983 pre0_mod08_post0
final_xgb <- finalize_workflow(xgb_wf, xgb_best) |>
  fit(train_xgb)

predClass_xgb <- predict(final_xgb, test_xgb, type = "class") |>
  bind_cols(test_xgb) # This is my actual predictions for each student!

confusionMatrix(predClass_xgb$.pred_class, test_xgb$Target, positive = "Dropout")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction Graduate Dropout
##   Graduate      412      50
##   Dropout        30     235
##                                           
##                Accuracy : 0.89            
##                  95% CI : (0.8649, 0.9118)
##     No Information Rate : 0.608           
##     P-Value [Acc > NIR] : < 2e-16         
##                                           
##                   Kappa : 0.7662          
##                                           
##  Mcnemar's Test P-Value : 0.03365         
##                                           
##             Sensitivity : 0.8246          
##             Specificity : 0.9321          
##          Pos Pred Value : 0.8868          
##          Neg Pred Value : 0.8918          
##              Prevalence : 0.3920          
##          Detection Rate : 0.3232          
##    Detection Prevalence : 0.3645          
##       Balanced Accuracy : 0.8783          
##                                           
##        'Positive' Class : Dropout         
## 
predProb_xgb <- predict(final_xgb, test_xgb, type = "prob") |>
  bind_cols(test_xgb)

rocObj_xgb <- roc(
  response = test_xgb$Target,
  predictor = predProb_xgb$.pred_Dropout,
  levels = c("Graduate", "Dropout")
)
## Setting direction: controls < cases
plot(rocObj_xgb,
  main = "ROC Curve for XGBoost",
  col = "#003057",
  lwd = 2,
  print.thres = seq(.2, .8, by = .2),
  print.thres.pch = 19,
  print.thres.col = "#97E152",
  print.thres.cex = .8,
  print.thres.adj = c(-.1, 1.5),
  print.auc = TRUE,
  print.auc.x = .4,
  print.auc.y = .15
)

XGBoost also did slightly worse than our basic model. Sensitivity decreased (From .8386 to .8246). Specificity again increased (From .9276 to .9321). Again, I will continue to use the baseline as the preferred model.

To demonstrate breadth, I apply a Neural Network using Tensorflow as a final comparison. Books could (and have) been written on how these models work. In short, a neural network mimics how the brain processes information by passing data through layers to recognize complex patterns. While R has packages for fitting neural networks, I’ll use TensorFlow through Python, called from R using reticulate, to build the model.

use_virtualenv("~/tf_env", required = TRUE)
# TensorFlow Neural Network

import random

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.metrics import confusion_matrix, classification_report, roc_curve, auc

import tensorflow as tf
from tensorflow import keras

SEED = 8287
random.seed(SEED)
np.random.seed(SEED)
tf.random.set_seed(SEED)

# Grab training and test data froom R
train_nn = r.train_simplified.copy()
test_nn = r.test_simplified.copy()

X_train = train_nn.drop(columns=["Target"])
y_train = (train_nn["Target"] == "Dropout").astype(int)
X_test = test_nn.drop(columns=["Target"])
y_test = (test_nn["Target"] == "Dropout").astype(int)

# Preprocess data
numeric = X_train.select_dtypes(include=["number"]).columns
categorical = X_train.select_dtypes(exclude=["number"]).columns

preprocess_recipe = ColumnTransformer(
    transformers=[
        ("numbers", StandardScaler(), numeric), # Standardize numbers
        ("categories", OneHotEncoder(handle_unknown="ignore"), categorical) # NN-readable categorical data
    ]
)

X_train = preprocess_recipe.fit_transform(X_train)
X_test = preprocess_recipe.transform(X_test)

# Neural network design
model = keras.Sequential([
    keras.layers.Input(shape=(X_train.shape[1],)),
    keras.layers.Dense(64, activation="relu"), # 64 nodes, "firing" of neuron if >0
    keras.layers.Dropout(.3), # Randomly disable 30% of neurons during training
    keras.layers.Dense(1, activation="sigmoid") # Binary prediction
]) # I spent a while tweaking the design. I tried deeper architectures and found they overfit on this dataset of only ~3k observations. One hidden layer with outperformed them

model.compile(
    optimizer="adam", # Or stochastic gradient descent. Method for weight updating
    loss="binary_crossentropy", # Only Dropout or Graduate. What is minimized
    metrics=["accuracy"]
)

early_stop = keras.callbacks.EarlyStopping(
    monitor="val_loss",
    patience=5,
    restore_best_weights=True
) # This was overtraining a lot, so I put a fail-safe to prevent that

# Model training
history = model.fit(
    X_train,
    y_train,
    epochs=100,
    batch_size=32,
    validation_split=.2,
    verbose=0,
    callbacks=[early_stop]
)

# Predict probabilities
predProb_nn = model.predict(X_test).flatten() # This is my actual predictions for each student!
##  1/23 ━━━━━━━━━━━━━━━━━━━━ 1s 53ms/step23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step
# Probabilities to classes
predClass_nn = np.where(
    predProb_nn > .5,
    "Dropout",
    "Graduate"
)

# Confusion matrix
confusionMatrix = confusion_matrix(
    test_nn["Target"],
    predClass_nn,
    labels=["Dropout", "Graduate"]
)

print(confusionMatrix)
## [[243  42]
##  [ 31 411]]
print(
    classification_report(
        test_nn["Target"],
        predClass_nn,
        target_names=["Dropout", "Graduate"]
    )
)
##               precision    recall  f1-score   support
## 
##      Dropout       0.89      0.85      0.87       285
##     Graduate       0.91      0.93      0.92       442
## 
##     accuracy                           0.90       727
##    macro avg       0.90      0.89      0.89       727
## weighted avg       0.90      0.90      0.90       727
true_negative, false_positive, false_negative, true_positive = confusion_matrix(
    y_test,
    predProb_nn > .5
).ravel()

sensitivity = true_positive / (true_positive + false_negative) # AKA Recall

print(f"\nSensitivity: {sensitivity:.4f}")
## 
## Sensitivity: 0.8526
# ROC Curve
false_positive_rate, true_positive_rate, thresholds = roc_curve(y_test, predProb_nn)
roc_auc = auc(false_positive_rate, true_positive_rate)

plt.figure(figsize=(6, 6))
plt.plot(
    false_positive_rate,
    true_positive_rate,
    color="#003057",
    linewidth=2,
    label=f"AUC = {roc_auc:.3f}"
)
plt.plot([0, 1], [0, 1], "k--")
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.title("ROC Curve for TensorFlow Neural Network")
plt.legend(loc="lower right")
plt.show()

The Neural Network beat the baseline model! Sensitivity increased from .8386 to .8526. The improvement is slight, but it is what was being optimized during his study. Note 1: While this model may be optimal now, it would be important to monitor for continuous improvement. While this is the best model now, new students, legislation, socioeconomic, or known factors may chance optimal predictions. One way to combat this is a retraining trigger if performance degrades beyond a defined threshold (eg. If sensitiviy drops below .8000). Note 2: Neural networks coefficients are highly unintuitive. It almost impossible to determine which factor contribute most to Dropout potential. While it may do the best job predicting Graduate/Dropout, it cannot explain why that prediction is made. This is in opposition to the logistic model which easily explained “early semester performance has the largest impact”. If this was a study for WHICH factors have the largest impact, I would opt for something like logistic regression even though the neural network predicts better.

I will use this model to predict which currently enrolled students might be given some type of intervention.

# Using only currently enrolled students
enrolled_simplified <- rawData |>
  filter(Target == "Enrolled") |>
  droplevels() |>
  mutate(
    Marital.status = fct_other(Marital.status, keep = lump_levels$Marital.status),
    Application.mode = fct_other(Application.mode, keep = lump_levels$Application.mode),
    Course = fct_other(Course, keep = lump_levels$Course),
    Previous.qualification = fct_other(Previous.qualification, keep = lump_levels$Previous.qualification),
    Nacionality = fct_other(Nacionality, keep = lump_levels$Nacionality),
    Mother.s.occupation = fct_other(Mother.s.occupation, keep = lump_levels$Mother.s.occupation),
    Father.s.occupation = fct_other(Father.s.occupation, keep = lump_levels$Father.s.occupation),
    Mother.s.qualification = fct_other(Mother.s.qualification, keep = lump_levels$Mother.s.qualification),
    Father.s.qualification = fct_other(Father.s.qualification, keep = lump_levels$Father.s.qualification)
  ) |>
  select(
    -c(
      Marital.status,
      Application.order,
      Daytime.evening.attendance.,
      International
    ),
    -matches("(?i)mother"),
    -matches("(?i)father")
  )
enrolled_nn = r.enrolled_simplified.copy()
X_enrolled = enrolled_nn.drop(columns=["Target"])

# Preprocess data in same way for model
numeric = X_enrolled.select_dtypes(include=["number"]).columns
categorical = X_enrolled.select_dtypes(exclude=["number"]).columns
X_enrolled = preprocess_recipe.transform(X_enrolled)

enrolledProb_nn = model.predict(X_enrolled).flatten() # This is my actual predictions for each student! This time for students where the "correct" answer is not known
##  1/25 ━━━━━━━━━━━━━━━━━━━━ 0s 22ms/step25/25 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step
enrolledClass_nn = np.where(
    enrolledProb_nn > .5,
    "Dropout",
    "Graduate"
)
enrolledProb_nn <- py$enrolledProb_nn
enrolledClass_nn <- py$enrolledClass_nn

enrolled_results <- data.frame(
  Student_id = 1:length(enrolledProb_nn),
  Dropout_Probability = enrolledProb_nn,
  Graduate_Dropout_Prediction = enrolledClass_nn
)

# Which students will drop out in order of model confidence
dropouts <- enrolled_results |>
  filter(Graduate_Dropout_Prediction == "Dropout")

nrow(dropouts)
## [1] 404
dropouts |>
  arrange(desc(Dropout_Probability)) |>
  head(25)
##    Student_id Dropout_Probability Graduate_Dropout_Prediction
## 1          30           0.9999714                     Dropout
## 2         157           0.9999596                     Dropout
## 3         391           0.9999090                     Dropout
## 4         163           0.9998428                     Dropout
## 5         674           0.9997957                     Dropout
## 6         554           0.9997841                     Dropout
## 7          20           0.9995615                     Dropout
## 8         482           0.9994577                     Dropout
## 9         498           0.9994521                     Dropout
## 10        265           0.9992898                     Dropout
## 11        713           0.9992771                     Dropout
## 12        488           0.9992689                     Dropout
## 13         15           0.9991385                     Dropout
## 14        168           0.9988894                     Dropout
## 15        326           0.9987278                     Dropout
## 16        753           0.9986851                     Dropout
## 17         44           0.9983725                     Dropout
## 18        268           0.9983531                     Dropout
## 19         59           0.9979700                     Dropout
## 20        772           0.9978831                     Dropout
## 21        699           0.9978712                     Dropout
## 22        649           0.9977934                     Dropout
## 23        510           0.9977838                     Dropout
## 24        415           0.9976854                     Dropout
## 25        490           0.9976150                     Dropout

The DECISION part of a DECISION SCIENTIST: I recommend intervention on behalf of these 404 students who are predicted to Dropout. If resources are scare, I recommend intervention in the indicated order (most needed first). While our final model is not able to indicate exactly what factor is most important- and therefore what intervention might be most helpful- our baseline model suggests early semester performance plays a significant role. Perhaps a “no-cost class retake” or an “integrated course” that reviews 100 level classes while moving onto to 200 level material. If these students are and/or feel behind, our models suggest that catching them up may make the difference in graduating or dropping out.