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.
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.
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.
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.
# 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.
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
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()
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()
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()
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()
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.
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"
)
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
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)
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)
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")
| 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 |
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
##
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)
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.
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.
There are several important limitations:
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.
El Kharoua, R. (2024). Alzheimer’s Disease Dataset. Kaggle. https://doi.org/10.34740/KAGGLE/DSV/8668279