1 Introduction

Alzheimer’s disease is a progressive brain disorder that affects memory, thinking, behavior, and the ability to complete everyday activities. Because Alzheimer’s disease affects several areas of cognitive and functional health, classification methods can be useful for exploring which patient characteristics are most strongly associated with a recorded diagnosis.

The goal of this project is to use machine-learning methods to classify whether a patient is diagnosed with Alzheimer’s disease. I compare three approaches: logistic regression, k-nearest neighbors (kNN), and random forest. Using multiple models makes it possible to compare an interpretable statistical classifier with a distance-based method and a nonlinear tree-based ensemble method.

2 Data Source

The data come from Rabie El Kharoua’s Alzheimer’s Disease Dataset on Kaggle:

https://www.kaggle.com/datasets/rabieelkharoua/alzheimers-disease-dataset

Kaggle citation:

El Kharoua, R. (2024). Alzheimer’s Disease Dataset. Kaggle. https://doi.org/10.34740/KAGGLE/DSV/8668279

The dataset is licensed under CC BY 4.0.

3 Load and Inspect the Data

The CSV file should be saved in the same folder as this R Markdown file.

dat <- read.csv("alzheimers_disease_data.csv")

dim(dat)
## [1] 2149   35
head(dat)
##   PatientID Age Gender Ethnicity EducationLevel      BMI Smoking
## 1      4751  73      0         0              2 22.92775       0
## 2      4752  89      0         0              0 26.82768       0
## 3      4753  73      0         3              1 17.79588       0
## 4      4754  74      1         0              1 33.80082       1
## 5      4755  89      0         0              0 20.71697       0
## 6      4756  86      1         1              1 30.62689       0
##   AlcoholConsumption PhysicalActivity DietQuality SleepQuality
## 1          13.297218        6.3271125   1.3472143     9.025679
## 2           4.542524        7.6198845   0.5187671     7.151293
## 3          19.555085        7.8449878   1.8263347     9.673574
## 4          12.209266        8.4280014   7.4356041     8.392554
## 5          18.454356        6.3104607   0.7954975     5.597238
## 6           4.140144        0.2110616   1.5849220     7.261953
##   FamilyHistoryAlzheimers CardiovascularDisease Diabetes Depression HeadInjury
## 1                       0                     0        1          1          0
## 2                       0                     0        0          0          0
## 3                       1                     0        0          0          0
## 4                       0                     0        0          0          0
## 5                       0                     0        0          0          0
## 6                       0                     0        1          0          0
##   Hypertension SystolicBP DiastolicBP CholesterolTotal CholesterolLDL
## 1            0        142          72         242.3668       56.15090
## 2            0        115          64         231.1626      193.40800
## 3            0         99         116         284.1819      153.32276
## 4            0        118         115         159.5822       65.36664
## 5            0         94         117         237.6022       92.86970
## 6            0        168          62         280.7125      198.33463
##   CholesterolHDL CholesterolTriglycerides      MMSE FunctionalAssessment
## 1       33.68256                162.18914 21.463532             6.518877
## 2       79.02848                294.63091 20.613267             7.118696
## 3       69.77229                 83.63832  7.356249             5.895077
## 4       68.45749                277.57736 13.991127             8.965106
## 5       56.87430                291.19878 13.517609             6.045039
## 6       79.08050                263.94365 27.517529             5.510144
##   MemoryComplaints BehavioralProblems        ADL Confusion Disorientation
## 1                0                  0 1.72588346         0              0
## 2                0                  0 2.59242413         0              0
## 3                0                  0 7.11954774         0              1
## 4                0                  1 6.48122586         0              0
## 5                0                  0 0.01469122         0              0
## 6                0                  0 9.01568628         1              0
##   PersonalityChanges DifficultyCompletingTasks Forgetfulness Diagnosis
## 1                  0                         1             0         0
## 2                  0                         0             1         0
## 3                  0                         1             0         0
## 4                  0                         0             0         0
## 5                  1                         1             0         0
## 6                  0                         0             0         0
##   DoctorInCharge
## 1      XXXConfid
## 2      XXXConfid
## 3      XXXConfid
## 4      XXXConfid
## 5      XXXConfid
## 6      XXXConfid
str(dat)
## 'data.frame':    2149 obs. of  35 variables:
##  $ PatientID                : int  4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 ...
##  $ Age                      : int  73 89 73 74 89 86 68 75 72 87 ...
##  $ Gender                   : int  0 0 0 1 0 1 0 0 1 0 ...
##  $ Ethnicity                : int  0 0 3 0 0 1 3 0 1 0 ...
##  $ EducationLevel           : int  2 0 1 1 0 1 2 1 0 0 ...
##  $ BMI                      : num  22.9 26.8 17.8 33.8 20.7 ...
##  $ Smoking                  : int  0 0 0 1 0 0 1 0 0 1 ...
##  $ AlcoholConsumption       : num  13.3 4.54 19.56 12.21 18.45 ...
##  $ PhysicalActivity         : num  6.33 7.62 7.84 8.43 6.31 ...
##  $ DietQuality              : num  1.347 0.519 1.826 7.436 0.795 ...
##  $ SleepQuality             : num  9.03 7.15 9.67 8.39 5.6 ...
##  $ FamilyHistoryAlzheimers  : int  0 0 1 0 0 0 0 0 0 0 ...
##  $ CardiovascularDisease    : int  0 0 0 0 0 0 0 0 0 1 ...
##  $ Diabetes                 : int  1 0 0 0 0 1 0 0 0 0 ...
##  $ Depression               : int  1 0 0 0 0 0 0 0 0 0 ...
##  $ HeadInjury               : int  0 0 0 0 0 0 1 0 0 0 ...
##  $ Hypertension             : int  0 0 0 0 0 0 0 0 1 0 ...
##  $ SystolicBP               : int  142 115 99 118 94 168 143 117 117 130 ...
##  $ DiastolicBP              : int  72 64 116 115 117 62 88 63 119 78 ...
##  $ CholesterolTotal         : num  242 231 284 160 238 ...
##  $ CholesterolLDL           : num  56.2 193.4 153.3 65.4 92.9 ...
##  $ CholesterolHDL           : num  33.7 79 69.8 68.5 56.9 ...
##  $ CholesterolTriglycerides : num  162.2 294.6 83.6 277.6 291.2 ...
##  $ MMSE                     : num  21.46 20.61 7.36 13.99 13.52 ...
##  $ FunctionalAssessment     : num  6.52 7.12 5.9 8.97 6.05 ...
##  $ MemoryComplaints         : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ BehavioralProblems       : int  0 0 0 1 0 0 0 0 1 1 ...
##  $ ADL                      : num  1.7259 2.5924 7.1195 6.4812 0.0147 ...
##  $ Confusion                : int  0 0 0 0 0 1 0 1 0 0 ...
##  $ Disorientation           : int  0 0 1 0 0 0 0 0 0 0 ...
##  $ PersonalityChanges       : int  0 0 0 0 1 0 0 0 1 0 ...
##  $ DifficultyCompletingTasks: int  1 0 1 0 1 0 0 0 0 0 ...
##  $ Forgetfulness            : int  0 1 0 0 0 0 1 1 0 0 ...
##  $ Diagnosis                : int  0 0 0 0 0 0 0 1 0 0 ...
##  $ DoctorInCharge           : chr  "XXXConfid" "XXXConfid" "XXXConfid" "XXXConfid" ...

The dataset contains 2149 observations and 35 variables, which exceeds the minimum project requirement of 500 observations.

sum(is.na(dat))
## [1] 0
colSums(is.na(dat))
##                 PatientID                       Age                    Gender 
##                         0                         0                         0 
##                 Ethnicity            EducationLevel                       BMI 
##                         0                         0                         0 
##                   Smoking        AlcoholConsumption          PhysicalActivity 
##                         0                         0                         0 
##               DietQuality              SleepQuality   FamilyHistoryAlzheimers 
##                         0                         0                         0 
##     CardiovascularDisease                  Diabetes                Depression 
##                         0                         0                         0 
##                HeadInjury              Hypertension                SystolicBP 
##                         0                         0                         0 
##               DiastolicBP          CholesterolTotal            CholesterolLDL 
##                         0                         0                         0 
##            CholesterolHDL  CholesterolTriglycerides                      MMSE 
##                         0                         0                         0 
##      FunctionalAssessment          MemoryComplaints        BehavioralProblems 
##                         0                         0                         0 
##                       ADL                 Confusion            Disorientation 
##                         0                         0                         0 
##        PersonalityChanges DifficultyCompletingTasks             Forgetfulness 
##                         0                         0                         0 
##                 Diagnosis            DoctorInCharge 
##                         0                         0

There are 0 missing values in the dataset.

4 Variable Description

The response variable is Diagnosis, where 0 indicates no Alzheimer’s diagnosis and 1 indicates an Alzheimer’s diagnosis. The predictors cover several groups:

Group Variables
Demographics Age, Gender, Ethnicity, EducationLevel
Lifestyle BMI, Smoking, AlcoholConsumption, PhysicalActivity, DietQuality, SleepQuality
Medical history FamilyHistoryAlzheimers, CardiovascularDisease, Diabetes, Depression, HeadInjury, Hypertension
Clinical measurements SystolicBP, DiastolicBP, CholesterolTotal, CholesterolLDL, CholesterolHDL, CholesterolTriglycerides
Cognitive/functional measures MMSE, FunctionalAssessment, ADL
Symptoms MemoryComplaints, BehavioralProblems, Confusion, Disorientation, PersonalityChanges, DifficultyCompletingTasks, Forgetfulness

PatientID is only an identifier and should not be used for prediction. DoctorInCharge contains the same confidential placeholder value for every observation, so it also has no predictive information. Both are removed before modeling.

5 Data Preparation

# Remove identifier and constant confidential field
model_dat <- dat %>%
  select(-PatientID, -DoctorInCharge)

# Convert categorical variables to factors
factor_vars <- c(
  "Gender", "Ethnicity", "EducationLevel", "Smoking",
  "FamilyHistoryAlzheimers", "CardiovascularDisease", "Diabetes",
  "Depression", "HeadInjury", "Hypertension", "MemoryComplaints",
  "BehavioralProblems", "Confusion", "Disorientation",
  "PersonalityChanges", "DifficultyCompletingTasks", "Forgetfulness",
  "Diagnosis"
)

model_dat[factor_vars] <- lapply(model_dat[factor_vars], factor)
levels(model_dat$Diagnosis) <- c("No", "Yes")

table(model_dat$Diagnosis)
## 
##   No  Yes 
## 1389  760
prop.table(table(model_dat$Diagnosis))
## 
##        No       Yes 
## 0.6463471 0.3536529
ggplot(model_dat, aes(x = Diagnosis, fill = Diagnosis)) +
  geom_bar(show.legend = FALSE) +
  labs(title = "Alzheimer's Diagnosis Distribution",
       x = "Diagnosis", y = "Number of Patients") +
  theme_minimal()

The data are somewhat imbalanced, with more patients without Alzheimer’s disease than with Alzheimer’s disease. Because accuracy alone can hide poor performance on the smaller class, I also evaluate sensitivity, specificity, precision, F1 score, and ROC AUC.

6 Exploratory Data Analysis

6.1 Age

ggplot(model_dat, aes(x = Age, fill = Diagnosis)) +
  geom_histogram(position = "identity", alpha = 0.55, bins = 25) +
  facet_wrap(~Diagnosis, ncol = 1) +
  labs(title = "Age Distribution by Diagnosis",
       x = "Age", y = "Count") +
  theme_minimal() +
  theme(legend.position = "none")

model_dat %>%
  group_by(Diagnosis) %>%
  summarise(mean_age = mean(Age), sd_age = sd(Age))
## # A tibble: 2 × 3
##   Diagnosis mean_age sd_age
##   <fct>        <dbl>  <dbl>
## 1 No            74.9   8.90
## 2 Yes           74.8   9.15

6.2 MMSE

MMSE is a cognitive assessment score. Lower values in this dataset tend to appear more often among patients diagnosed with Alzheimer’s disease.

ggplot(model_dat, aes(x = Diagnosis, y = MMSE, fill = Diagnosis)) +
  geom_boxplot(show.legend = FALSE) +
  labs(title = "MMSE by Alzheimer's Diagnosis",
       x = "Diagnosis", y = "MMSE Score") +
  theme_minimal()

6.3 Functional Assessment

ggplot(model_dat, aes(x = Diagnosis, y = FunctionalAssessment, fill = Diagnosis)) +
  geom_boxplot(show.legend = FALSE) +
  labs(title = "Functional Assessment by Diagnosis",
       x = "Diagnosis", y = "Functional Assessment") +
  theme_minimal()

6.4 Activities of Daily Living (ADL)

ggplot(model_dat, aes(x = Diagnosis, y = ADL, fill = Diagnosis)) +
  geom_boxplot(show.legend = FALSE) +
  labs(title = "ADL Score by Diagnosis",
       x = "Diagnosis", y = "ADL Score") +
  theme_minimal()

6.5 Memory Complaints

memory_tab <- prop.table(table(model_dat$MemoryComplaints,
                               model_dat$Diagnosis), margin = 1)
memory_tab
##    
##            No       Yes
##   0 0.7215041 0.2784959
##   1 0.3601790 0.6398210
memory_df <- model_dat %>%
  count(MemoryComplaints, Diagnosis) %>%
  group_by(MemoryComplaints) %>%
  mutate(prop = n / sum(n))

ggplot(memory_df, aes(x = MemoryComplaints, y = prop, fill = Diagnosis)) +
  geom_col(position = "fill") +
  scale_y_continuous(labels = scales::percent) +
  labs(title = "Diagnosis by Memory Complaint Status",
       x = "Memory Complaints (0 = No, 1 = Yes)", y = "Proportion") +
  theme_minimal()

6.6 Numeric Correlations

numeric_dat <- dat %>%
  select(where(is.numeric)) %>%
  select(-PatientID)

cors <- cor(numeric_dat, use = "complete.obs")
diag_cor <- sort(cors[, "Diagnosis"], decreasing = TRUE)
diag_cor
##                 Diagnosis          MemoryComplaints        BehavioralProblems 
##              1.0000000000              0.3067423937              0.2243504008 
##            CholesterolHDL              Hypertension     CardiovascularDisease 
##              0.0425840413              0.0350800344              0.0314902464 
##                       BMI  CholesterolTriglycerides DifficultyCompletingTasks 
##              0.0263428130              0.0226718770              0.0090685818 
##               DietQuality          CholesterolTotal          PhysicalActivity 
##              0.0085057713              0.0063944630              0.0059450425 
##               DiastolicBP             Forgetfulness                   Smoking 
##              0.0052926798             -0.0003542898             -0.0048652352 
##                       Age                Depression        AlcoholConsumption 
##             -0.0054883776             -0.0058929070             -0.0076179684 
##                 Ethnicity                SystolicBP                 Confusion 
##             -0.0147822954             -0.0156152313             -0.0191857197 
##        PersonalityChanges                    Gender                HeadInjury 
##             -0.0206274625             -0.0209747103             -0.0214114257 
##            Disorientation                  Diabetes            CholesterolLDL 
##             -0.0246481569             -0.0315076042             -0.0319758259 
##   FamilyHistoryAlzheimers            EducationLevel              SleepQuality 
##             -0.0328997475             -0.0439658496             -0.0565480860 
##                      MMSE                       ADL      FunctionalAssessment 
##             -0.2371256071             -0.3323459163             -0.3648983066

The EDA helps identify relationships worth examining, but the final comparison is based on performance on data that are not used to fit the models.

7 Train/Test Split

I use an 80/20 split and stratify by diagnosis so that both sets have approximately the same class proportions.

set.seed(402)
train_index <- createDataPartition(model_dat$Diagnosis, p = 0.80,
                                   list = FALSE)
train <- model_dat[train_index, ]
test  <- model_dat[-train_index, ]

nrow(train)
## [1] 1720
nrow(test)
## [1] 429
prop.table(table(train$Diagnosis))
## 
##        No       Yes 
## 0.6465116 0.3534884
prop.table(table(test$Diagnosis))
## 
##        No       Yes 
## 0.6456876 0.3543124

For tuning, I use 5-fold cross-validation on the training data. The test set remains untouched until the final evaluation.

ctrl <- trainControl(
  method = "cv",
  number = 5,
  classProbs = TRUE,
  summaryFunction = twoClassSummary,
  savePredictions = "final"
)

8 Method 1: Logistic Regression

Logistic regression models the log-odds of a binary outcome as a linear combination of predictor variables. For a predictor vector \(x\), the model estimates

\[ P(Y=1|x) = \frac{1}{1 + e^{-(\beta_0 + \beta_1x_1 + \cdots + \beta_px_p)}}. \]

It is a useful baseline because its fitted coefficients provide a relatively interpretable relationship between predictors and the probability of diagnosis.

set.seed(402)
log_fit <- train(
  Diagnosis ~ ., data = train,
  method = "glm",
  family = binomial,
  metric = "ROC",
  trControl = ctrl
)

log_fit
## Generalized Linear Model 
## 
## 1720 samples
##   32 predictor
##    2 classes: 'No', 'Yes' 
## 
## No pre-processing
## Resampling: Cross-Validated (5 fold) 
## Summary of sample sizes: 1376, 1377, 1375, 1376, 1376 
## Resampling results:
## 
##   ROC        Sens       Spec     
##   0.9016589  0.8956773  0.7137515

9 Method 2: k-Nearest Neighbors

Because distance is sensitive to the scale of the predictors, numerical variables are centered and scaled before fitting the model. I tune \(k\) by cross-validation rather than choosing it arbitrarily.

set.seed(402)
knn_grid <- expand.grid(k = seq(3, 31, by = 2))

knn_fit <- train(
  Diagnosis ~ ., data = train,
  method = "knn",
  preProcess = c("center", "scale"),
  tuneGrid = knn_grid,
  metric = "ROC",
  trControl = ctrl
)

knn_fit
## k-Nearest Neighbors 
## 
## 1720 samples
##   32 predictor
##    2 classes: 'No', 'Yes' 
## 
## Pre-processing: centered (36), scaled (36) 
## Resampling: Cross-Validated (5 fold) 
## Summary of sample sizes: 1376, 1377, 1375, 1376, 1376 
## Resampling results across tuning parameters:
## 
##   k   ROC        Sens       Spec     
##    3  0.7055190  0.8300368  0.4588132
##    5  0.7391052  0.8615319  0.4341553
##    7  0.7566747  0.8840060  0.4259585
##    9  0.7751024  0.9091666  0.4357811
##   11  0.7879937  0.9190563  0.4176941
##   13  0.8022890  0.9298307  0.3996071
##   15  0.8131224  0.9361290  0.3978729
##   17  0.8172196  0.9469317  0.3995936
##   19  0.8217751  0.9505272  0.3929820
##   21  0.8302718  0.9523290  0.3946349
##   23  0.8360825  0.9541389  0.3814524
##   25  0.8394439  0.9577304  0.3798130
##   27  0.8413103  0.9568376  0.3568080
##   29  0.8448800  0.9622268  0.3485707
##   31  0.8469953  0.9622268  0.3304837
## 
## ROC was used to select the optimal model using the largest value.
## The final value used for the model was k = 31.
knn_fit$bestTune
##     k
## 15 31
plot(knn_fit)

10 Method 3: Random Forest

Random forest is an ensemble method that builds many decision trees from bootstrap samples of the training data. At each split, only a random subset of predictors is considered. The final classification is based on the combined votes of the trees.

set.seed(402)
rf_fit <- train(
  Diagnosis ~ ., data = train,
  method = "rf",
  ntree = 500,
  tuneLength = 5,
  metric = "ROC",
  trControl = ctrl,
  importance = TRUE
)

rf_fit
## Random Forest 
## 
## 1720 samples
##   32 predictor
##    2 classes: 'No', 'Yes' 
## 
## No pre-processing
## Resampling: Cross-Validated (5 fold) 
## Summary of sample sizes: 1376, 1377, 1375, 1376, 1376 
## Resampling results across tuning parameters:
## 
##   mtry  ROC        Sens       Spec     
##    2    0.9468177  0.9847008  0.5689608
##   10    0.9494733  0.9757120  0.8947839
##   19    0.9466195  0.9757120  0.9062864
##   27    0.9476011  0.9748111  0.9095651
##   36    0.9470043  0.9712156  0.9079122
## 
## ROC was used to select the optimal model using the largest value.
## The final value used for the model was mtry = 10.
plot(rf_fit)

11 Test-Set Evaluation

get_metrics <- function(model, newdata, actual, model_name) {
  pred <- predict(model, newdata = newdata)
  prob <- predict(model, newdata = newdata, type = "prob")[, "Yes"]
  cm <- confusionMatrix(pred, actual, positive = "Yes")
  roc_obj <- roc(actual, prob, levels = c("No", "Yes"), quiet = TRUE)

  tibble(
    Model = model_name,
    Accuracy = unname(cm$overall["Accuracy"]),
    Sensitivity = unname(cm$byClass["Sensitivity"]),
    Specificity = unname(cm$byClass["Specificity"]),
    Precision = unname(cm$byClass["Pos Pred Value"]),
    F1 = unname(cm$byClass["F1"]),
    AUC = as.numeric(auc(roc_obj))
  )
}

log_metrics <- get_metrics(log_fit, test, test$Diagnosis,
                           "Logistic Regression")
knn_metrics <- get_metrics(knn_fit, test, test$Diagnosis,
                           "k-Nearest Neighbors")
rf_metrics  <- get_metrics(rf_fit, test, test$Diagnosis,
                           "Random Forest")

results <- bind_rows(log_metrics, knn_metrics, rf_metrics)
results
## # A tibble: 3 × 7
##   Model               Accuracy Sensitivity Specificity Precision    F1   AUC
##   <chr>                  <dbl>       <dbl>       <dbl>     <dbl> <dbl> <dbl>
## 1 Logistic Regression    0.818       0.75        0.856     0.740 0.745 0.878
## 2 k-Nearest Neighbors    0.753       0.336       0.982     0.911 0.490 0.831
## 3 Random Forest          0.946       0.908       0.968     0.939 0.923 0.952
kable(results %>% mutate(across(where(is.numeric), ~round(.x, 3))),
      caption = "Test-set model comparison")
Test-set model comparison
Model Accuracy Sensitivity Specificity Precision F1 AUC
Logistic Regression 0.818 0.750 0.856 0.740 0.745 0.878
k-Nearest Neighbors 0.753 0.336 0.982 0.911 0.490 0.831
Random Forest 0.946 0.908 0.968 0.939 0.923 0.952

11.1 Confusion Matrices

log_pred <- predict(log_fit, test)
knn_pred <- predict(knn_fit, test)
rf_pred  <- predict(rf_fit, test)

confusionMatrix(log_pred, test$Diagnosis, positive = "Yes")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  No Yes
##        No  237  38
##        Yes  40 114
##                                           
##                Accuracy : 0.8182          
##                  95% CI : (0.7784, 0.8535)
##     No Information Rate : 0.6457          
##     P-Value [Acc > NIR] : 2.677e-15       
##                                           
##                   Kappa : 0.6038          
##                                           
##  Mcnemar's Test P-Value : 0.9099          
##                                           
##             Sensitivity : 0.7500          
##             Specificity : 0.8556          
##          Pos Pred Value : 0.7403          
##          Neg Pred Value : 0.8618          
##              Prevalence : 0.3543          
##          Detection Rate : 0.2657          
##    Detection Prevalence : 0.3590          
##       Balanced Accuracy : 0.8028          
##                                           
##        'Positive' Class : Yes             
## 
confusionMatrix(knn_pred, test$Diagnosis, positive = "Yes")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  No Yes
##        No  272 101
##        Yes   5  51
##                                          
##                Accuracy : 0.7529         
##                  95% CI : (0.7093, 0.793)
##     No Information Rate : 0.6457         
##     P-Value [Acc > NIR] : 1.186e-06      
##                                          
##                   Kappa : 0.3702         
##                                          
##  Mcnemar's Test P-Value : < 2.2e-16      
##                                          
##             Sensitivity : 0.3355         
##             Specificity : 0.9819         
##          Pos Pred Value : 0.9107         
##          Neg Pred Value : 0.7292         
##              Prevalence : 0.3543         
##          Detection Rate : 0.1189         
##    Detection Prevalence : 0.1305         
##       Balanced Accuracy : 0.6587         
##                                          
##        'Positive' Class : Yes            
## 
confusionMatrix(rf_pred, test$Diagnosis, positive = "Yes")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  No Yes
##        No  268  14
##        Yes   9 138
##                                           
##                Accuracy : 0.9464          
##                  95% CI : (0.9206, 0.9657)
##     No Information Rate : 0.6457          
##     P-Value [Acc > NIR] : <2e-16          
##                                           
##                   Kappa : 0.8819          
##                                           
##  Mcnemar's Test P-Value : 0.4042          
##                                           
##             Sensitivity : 0.9079          
##             Specificity : 0.9675          
##          Pos Pred Value : 0.9388          
##          Neg Pred Value : 0.9504          
##              Prevalence : 0.3543          
##          Detection Rate : 0.3217          
##    Detection Prevalence : 0.3427          
##       Balanced Accuracy : 0.9377          
##                                           
##        'Positive' Class : Yes             
## 

11.2 ROC Curves

log_prob <- predict(log_fit, test, type = "prob")[, "Yes"]
knn_prob <- predict(knn_fit, test, type = "prob")[, "Yes"]
rf_prob  <- predict(rf_fit, test, type = "prob")[, "Yes"]

roc_log <- roc(test$Diagnosis, log_prob, levels = c("No", "Yes"), quiet = TRUE)
roc_knn <- roc(test$Diagnosis, knn_prob, levels = c("No", "Yes"), quiet = TRUE)
roc_rf  <- roc(test$Diagnosis, rf_prob,  levels = c("No", "Yes"), quiet = TRUE)

plot(roc_log, main = "ROC Curves for the Three Models")
plot(roc_knn, add = TRUE)
plot(roc_rf, add = TRUE)
legend("bottomright",
       legend = c(
         paste0("Logistic AUC = ", round(auc(roc_log), 3)),
         paste0("kNN AUC = ", round(auc(roc_knn), 3)),
         paste0("Random Forest AUC = ", round(auc(roc_rf), 3))
       ),
       lty = 1)

12 Random Forest Variable Importance

rf_importance <- varImp(rf_fit, scale = FALSE)
rf_importance
## rf variable importance
## 
##   only 20 most important variables shown (out of 36)
## 
##                      Importance
## FunctionalAssessment   98.02305
## ADL                    93.26751
## MMSE                   73.60310
## MemoryComplaints1      68.68533
## BehavioralProblems1    59.83792
## EducationLevel2         1.72307
## SleepQuality            1.57461
## Age                     1.12344
## Hypertension1           1.04471
## Smoking1                0.99311
## Ethnicity2              0.93059
## Forgetfulness1          0.72047
## Depression1             0.60879
## Disorientation1         0.45707
## CholesterolHDL          0.28298
## HeadInjury1             0.26057
## Diabetes1               0.21991
## Confusion1              0.14980
## AlcoholConsumption      0.08391
## Gender1                -0.07110
plot(rf_importance, top = 15,
     main = "Top Random Forest Predictors")

The variable-importance plot shows which predictors contributed most strongly to classification in the random forest. Importance should not be interpreted as proof that a variable causes Alzheimer’s disease. It only describes how useful a variable was for prediction in this dataset.

13 Model Comparison and Discussion

Based on the held-out test set, Random Forest provides the strongest overall performance. Its accuracy is approximately 0.946 and its ROC AUC is approximately 0.952. The model comparison is more informative than accuracy alone because sensitivity measures the ability to identify diagnosed cases while specificity measures the ability to identify patients without the diagnosis.

The exploratory analysis and random forest importance results also suggest that cognitive and functional measures are especially useful for separating the two diagnosis groups. In particular, measures such as MMSE, functional assessment, ADL, memory complaints, and behavioral problems can contribute substantial predictive information. This pattern is reasonable within the structure of the dataset because Alzheimer’s disease is characterized by changes in cognition, memory, and everyday functioning.

14 Limitations

There are several important limitations:

  1. This is a prediction exercise, not a diagnostic tool.
  2. Association is not causation.

15 Conclusion

This project compared logistic regression, k-nearest neighbors, and random forest for classifying Alzheimer’s disease diagnosis using demographic, lifestyle, medical, cognitive, functional, and symptom variables.

On the held-out test set, Random Forest achieved the strongest overall performance according to the combination of accuracy and ROC AUC. The result illustrates the value of comparing several learners rather than relying on a single model. At the same time, the synthetic nature of the dataset means that the project should be understood as a demonstration of data-science methodology, not as evidence that the fitted model is appropriate for clinical use.

16 References

El Kharoua, R. (2024). Alzheimer’s Disease Dataset. Kaggle. https://doi.org/10.34740/KAGGLE/DSV/8668279