The Behavioral Risk Factor Surveillance System (BRFSS) is a health-related telephone survey that is collected annually by the CDC. Each year, the survey collects responses from over 400,000 Americans on health-related risk behaviors, chronic health conditions, and the use of preventative services. It has been conducted every year since 1984. For this project, a csv of the dataset available on Kaggle for the year 2015 was used. This original dataset contains responses from 441,455 individuals and has 330 features. These features are either questions directly asked of participants, or calculated variables based on individual participant responses.

In [ ]:
# import models
from random_forest import RandomForest
from logistic_regression import LogisticReg
from gradient_boosting import GradientBoosting

from xg_boost import ModelXGBoost
from data_cleaning import DataCleaning
from sklearn.model_selection import train_test_split
import pandas as pd
import numpy as np
import sklearn.datasets
import matplotlib.pyplot as plt
import shap
from ipywidgets import FloatProgress
import matplotlib.pyplot as plt
import seaborn as sns

Read data¶

In [ ]:
data = pd.read_csv("data/diabetes.csv")

categorical_variables = ['GenHlth', 'Age', 'Education', 'Income']
data[categorical_variables] = data[categorical_variables].apply(lambda x: x.astype('category'))
In [ ]:
display(data.head(3))
Diabetes_binary HighBP HighChol CholCheck BMI Smoker Stroke HeartDiseaseorAttack PhysActivity Fruits ... AnyHealthcare NoDocbcCost GenHlth MentHlth PhysHlth DiffWalk Sex Age Education Income
0 0.0 1.0 0.0 1.0 26.0 0.0 0.0 0.0 1.0 0.0 ... 1.0 0.0 3.0 5.0 30.0 0.0 1.0 4.0 6.0 8.0
1 0.0 1.0 1.0 1.0 26.0 1.0 1.0 0.0 0.0 1.0 ... 1.0 0.0 3.0 0.0 0.0 0.0 1.0 12.0 6.0 8.0
2 0.0 0.0 0.0 1.0 26.0 0.0 0.0 0.0 1.0 1.0 ... 1.0 0.0 1.0 0.0 10.0 0.0 1.0 13.0 6.0 8.0

3 rows × 22 columns

Exploratory Data Analysis¶

In [ ]:
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 70692 entries, 0 to 70691
Data columns (total 22 columns):
 #   Column                Non-Null Count  Dtype   
---  ------                --------------  -----   
 0   Diabetes_binary       70692 non-null  float64 
 1   HighBP                70692 non-null  float64 
 2   HighChol              70692 non-null  float64 
 3   CholCheck             70692 non-null  float64 
 4   BMI                   70692 non-null  float64 
 5   Smoker                70692 non-null  float64 
 6   Stroke                70692 non-null  float64 
 7   HeartDiseaseorAttack  70692 non-null  float64 
 8   PhysActivity          70692 non-null  float64 
 9   Fruits                70692 non-null  float64 
 10  Veggies               70692 non-null  float64 
 11  HvyAlcoholConsump     70692 non-null  float64 
 12  AnyHealthcare         70692 non-null  float64 
 13  NoDocbcCost           70692 non-null  float64 
 14  GenHlth               70692 non-null  category
 15  MentHlth              70692 non-null  float64 
 16  PhysHlth              70692 non-null  float64 
 17  DiffWalk              70692 non-null  float64 
 18  Sex                   70692 non-null  float64 
 19  Age                   70692 non-null  category
 20  Education             70692 non-null  category
 21  Income                70692 non-null  category
dtypes: category(4), float64(18)
memory usage: 10.0 MB

Definition of Categorical Variables¶

Diabetes_binary (scale: 0-2)
0 = no diabetes
1 = prediabetes
2 = diabetes

Age (scale: 1-13)
1 = 18-24; 2 = 25-29; 3 = 30-34; 4 = 35-39; 5 = 40-44; 6 = 45-49; 7 = 50-55; 8 = 56-59; 9 = 60-64; 10 = 65-69; 11 = 70-74; 12 = 75-79; 13 = 80 or older

GenHlth (scale: 1-5) 1 = excellent
2 = very good
3 = good
4 = fair
5 = poor

Education (scale: 1-6)
1 = Never attended school or only kindergarten
2 = Grades 1 through 8 (Elementary)
3 = Grades 9 through 11 (Some high school)
4 = Grade 12 or GED (High school graduate)
5 = College 1 year to 3 years (Some college or technical school)
6 = College 4 years or more (College graduate)

Income (scale: 1-8) 1 = less than 10K
2 = less than 25K
3 = less than 35K
4 = less than 45K
5 = less than 55K
6 = less than 65K
7 = less than 75K
8 = $75K or more

Exploratory Data Analysis¶

Distribution of Age, Income, Education¶

In [ ]:
plt.figure(figsize=(10, 5))
plt.subplot(2,2,1)
sns.histplot(data['Age']).set(title='Distribution of Age')

plt.subplot(2,2,2)
sns.histplot(data['Income']).set(title='Distribution of Income')

plt.subplot(2,2,3)
sns.histplot(data['Education']).set(title='Distribution of Education')

plt.subplot(2,2,4)
sns.histplot(data['GenHlth']).set(title='GenHlth')

plt.subplots_adjust(left=0,
                    bottom=0, 
                    right=0.9, 
                    top=0.9,
                    wspace=0.5, 
                    hspace=0.5)
plt.show()
No description has been provided for this image

As shown on the histograms above, the distribution for Age and GenHlth are the closest to a normal distribution.

Boxplot of BMI¶

In [ ]:
sns.boxplot(data['BMI'])
Out[ ]:
<Axes: ylabel='BMI'>
No description has been provided for this image

As shown on the boxplot above, there are outliers in BMI

Remove Outliers¶

In [ ]:
obj = DataCleaning()
data = obj.replace_outliers_cols(df=data, cols=['BMI'])
2181 outliers were replaced for the column: BMI

Boxplot of BMI after replacing outliers with the upperbound or lowerbound values¶

In [ ]:
sns.boxplot(data['BMI'])
Out[ ]:
<Axes: ylabel='BMI'>
No description has been provided for this image

Check for missing values¶

In [ ]:
# Check for missing values in each column
print(f"The number of NaNs in each column: {data.isnull().sum()}")
The number of NaNs in each column: Diabetes_binary         0
HighBP                  0
HighChol                0
CholCheck               0
BMI                     0
Smoker                  0
Stroke                  0
HeartDiseaseorAttack    0
PhysActivity            0
Fruits                  0
Veggies                 0
HvyAlcoholConsump       0
AnyHealthcare           0
NoDocbcCost             0
GenHlth                 0
MentHlth                0
PhysHlth                0
DiffWalk                0
Sex                     0
Age                     0
Education               0
Income                  0
dtype: int64

Split data into training and testing¶

In [ ]:
# split data frames into features and target
X =  data.drop(columns='Diabetes_binary')
y = data[['Diabetes_binary']]
In [ ]:
# split to training and testing
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=100)
In [ ]:
score_tracker = {}

Random Forest¶

In [ ]:
rf = RandomForest()

# Train the model on the train set
model = rf.classifier(X=X_train, y=y_train)

# Training accuracy
prediction = rf.predict(model_object=model, X=X_train)
train_accuracy = rf.accuracy(prediction_object=prediction, y=y_train)
#print(f"Training Accuracy is: {train_accuracy}")

# Testing Accuracy
prediction = rf.predict(model_object=model, X=X_test)
test_accuracy = rf.accuracy(prediction_object=prediction, y=y_test)
#print(f"Testing Accuracy is: {test_accuracy}")


# Tune the model
tuner = rf.tune_classifier(model_object=model, X=X_train, y=y_train)
# Retrieve best parameters
best_params = rf.best_params(tuned_model_object=tuner)
# Retrieve best scores
best_score = rf.best_score(tuned_model_object=tuner)
print(f"Best params are === {best_params}")
#print(f"Best score is === {best_score}")

# Train model with the best hyper-parameters, returns model object, training prediction and testing prediction
tuned_model = rf.tuned_classifier(X=X_train, y=y_train, bestParams=tuner.best_params_)

# calculate accuracy for test set
prediction = rf.predict(model_object=tuned_model, X=X_test)
test_accuracy = rf.accuracy(prediction_object=prediction, y=y_test)
#print(f"Testing Accuracy after tuning: {test_accuracy}")

imp = rf.feature_importance(model_object=tuned_model)
sorted_imp = rf.sorted_feature_importance_indicies(model_object=tuned_model)
print(f"Index of Sorted Feature Importance === {sorted_imp}")

important_features = []
col_names = list(X_train.columns)

for x in sorted_imp:
    important_features.append(col_names[x])

#print(f"Sorted features importance === {important_features}")

most_important_feature = list(X_train.iloc[:, [sorted_imp[0]]].columns)[0]
print(f"Most important feature is === {most_important_feature}")

# cross-validation - AUC
cross_val_auc = rf.cross_validation_classifier(tuned_model, X_train, y_train)

print(f"Mean cross-validation AUC === {cross_val_auc.mean()}")

# append score to dictionary
score_tracker['Random Forest'] = cross_val_auc.mean()
Best params are === {'criterion': 'entropy', 'max_depth': 10, 'max_features': 6, 'min_samples_leaf': 2, 'min_samples_split': 2, 'n_estimators': 510}
Index of Sorted Feature Importance === [13  0  3 18  1 16 20 15  6 14 19 17 10  2  7  4  8  9  5 12 11]
Most important feature is === GenHlth
Mean cross-validation AUC === 0.8287197394531376

GBM¶

In [ ]:
gb = GradientBoosting()

# Train the model on the train set
model = gb.classifier(X=X_train, y=y_train)
# Training accuracy
prediction_train = gb.predict(model_object=model, X=X_train)
train_accuracy = gb.accuracy(prediction_object=prediction_train, y=y_train)
#print(f"Training Accuracy is: {train_accuracy}")

# Testing accuracy
prediction_test = gb.predict(model_object=model, X=X_test)
test_accuracy = gb.accuracy(prediction_object=prediction_test, y=y_test)
#print(f"Testing Accuracy is: {test_accuracy}")

#tune model
tuner = gb.tune_classifier(model_object=model, X=X_train, y=y_train)
# Retrieve best parameters
best_params = gb.best_params(tuned_model_object=tuner)
# Retrieve best scores
best_score = gb.best_score(tuned_model_object=tuner)

print(f"Best params are === {best_params}")
#print(f"Best score is === {best_score}")

tuned_model = gb.tuned_classifier(X=X_train, y=y_train, bestParams=best_params)

# Testing accuracy
prediction_test = gb.predict(model_object=tuned_model, X=X_test)
test_accuracy = gb.accuracy(prediction_object=prediction_test, y=y_test)
#print(f"Testing Accuracy after tuning is: {test_accuracy}")

# feature importance
imp = gb.feature_importance(model_object=tuned_model)
sorted_imp = gb.sorted_feature_importance_indicies(model_object=tuned_model)
#print(f"Index of Sorted Feature Importance === {sorted_imp}")

important_features = []
col_names = list(X_train.columns)

for x in sorted_imp:
    important_features.append(col_names[x])

print(f"Sorted features importance === {important_features}")


most_important_feature = list(X_train.iloc[:, [sorted_imp[0]]].columns)[0]
print(f"Most important feature is === {most_important_feature}")

# cross-validation - AUC
cross_val_auc = gb.cross_validation_classifier(tuned_model, X_train, y_train)

print(f"Mean cross-validation AUC === {cross_val_auc.mean()}")

# append score to dictionary
score_tracker['GBM'] = cross_val_auc.mean()
Best params are === {'criterion': 'squared_error', 'learning_rate': 0.001, 'max_depth': 90, 'max_features': 2, 'min_samples_leaf': 3, 'min_samples_split': 2, 'n_estimators': 360}
Sorted features importance === ['BMI', 'GenHlth', 'Age', 'HighBP', 'HighChol', 'Income', 'PhysHlth', 'DiffWalk', 'Education', 'MentHlth', 'HeartDiseaseorAttack', 'PhysActivity', 'Sex', 'Smoker', 'Fruits', 'Veggies', 'HvyAlcoholConsump', 'CholCheck', 'Stroke', 'NoDocbcCost', 'AnyHealthcare']
Most important feature is === BMI
Mean cross-validation AUC === 0.8263080767962554

Logistic Regression¶

In [ ]:
lr = LogisticReg()

#train the model on the training set
lr_model = lr.classifier(X=X_train, y=y_train)

# predict binary
prediction = lr.predict(model_object=lr_model, X=X_train)
# Accuracy training set
accuracy_train = lr.accuracy(model_object=lr_model, X=X_train, y=y_train)
#print(f"Training Accuracy is: {accuracy_train}")
# Accuracy testing set
accuracy_test = lr.accuracy(model_object=lr_model, X=X_test, y=y_test)
#print(f"Testing Accuracy is: {accuracy_test}")

# tune the model
tuner = lr.tune(model_object=lr_model, X=X_train, y=y_train)
# Retrieve best parameters
best_params = lr.best_params(tuned_model_object=tuner)
# Retrieve best scores
best_score = lr.best_score(tuned_model_object=tuner)
print(f"Best params are === {best_params}")
#print(f"Best score is === {best_score}")

tuned_model = lr.tuned_model(X=X_train, y=y_train, bestParams=tuner.best_params_)
# calculate accuracy for test set
prediction = lr.predict(model_object=tuned_model, X=X_test)
test_accuracy = lr.accuracy(model_object=tuned_model, X=X_test, y=y_test)
#print(f"Testing Accuracy after tuning: {test_accuracy}")

feature_importance, sorted_idx, pos = lr.feature_importance(model_object=tuned_model)

# cross-validation - AUC
cross_val_auc = lr.cross_validation(tuned_model, X_train, y_train)

print(f"Mean cross-validation AUC === {cross_val_auc.mean()}")

# append score to dictionary
score_tracker['Logistic Regression'] = cross_val_auc.mean()
Best params are === {'solver': 'newton-cg', 'max_iter': 100, 'C': 100}
Mean cross-validation AUC === 0.8271197031081078

XGBoost¶

In [ ]:
xg = ModelXGBoost()

# Train the model on the train set
model = xg.classifier(X=X_train, y=y_train)
# Training accuracy
prediction_train = xg.predict(model_object=model, X=X_train)
train_accuracy = xg.accuracy(prediction_object=prediction_train, y=y_train)
#print(f"Training Accuracy is: {train_accuracy}")

# Testing accuracy
prediction_test = xg.predict(model_object=model, X=X_test)
test_accuracy = xg.accuracy(prediction_object=prediction_test, y=y_test)
#print(f"Testing Accuracy is: {test_accuracy}")

#tune model
tuner = xg.tune_classifier(model_object=model, X=X_train, y=y_train)
# Retrieve best parameters
best_params = xg.best_params(tuned_model_object=tuner)
# Retrieve best scores
best_score = xg.best_score(tuned_model_object=tuner)

print(f"Best params are === {best_params}")
#print(f"Best score is === {best_score}")

tuned_model = xg.tuned_classifier(X=X_train, y=y_train, bestParams=best_params)

# Testing accuracy
prediction_test = xg.predict(model_object=tuned_model, X=X_test)
test_accuracy = xg.accuracy(prediction_object=prediction_test, y=y_test)
#print(f"Testing Accuracy after tuning is: {test_accuracy}")

# feature importance
imp = xg.feature_importance(model_object=tuned_model)
sorted_imp = xg.sorted_feature_importance_indicies(model_object=tuned_model)
#print(f"Index of Sorted Feature Importance === {sorted_imp}")

important_features = []
col_names = list(X_train.columns)

for x in sorted_imp:
    important_features.append(col_names[x])

#print(f"Sorted features importance === {important_features}")


most_important_feature = list(X_train.iloc[:, [sorted_imp[0]]].columns)[0]
print(f"Most important feature is === {most_important_feature}")

# cross-validation - AUC
cross_val_auc = xg.cross_validation_classifier(tuned_model, X_train, y_train)

print(f"Mean cross-validation AUC === {cross_val_auc.mean()}")

# append score to dictionary
score_tracker['XGBoost'] = cross_val_auc.mean()
Best params are === {'subsample': 0.8, 'reg_alpha': 0, 'n_estimators': 160, 'min_child_weight': 4, 'max_depth': 2, 'learning_rate': 0.05, 'gamma': 0.4, 'colsample_bytree': 0.7}
Most important feature is === HighBP
Mean cross-validation AUC === 0.8293038596929987

Best Performing model¶

In [ ]:
best_model = max(score_tracker.items(), key=lambda k: k[1])
print(f"The best model is: {best_model}")
The best model is: ('XGBoost', 0.8293038596929987)

The best performing model was XGBoost with a AUC of 0.8293

SHAP Values from XGBoost¶

In [ ]:
# shap values
shap_values, X = xg.shap_values(model_object=tuned_model, X=X_train)
shap.summary_plot(shap_values, X, plot_type="bar")
No description has been provided for this image

The plot above shows the plot of the SHAP values. This shows the most important features that the model used to make predictions. The most important features in determining a person has diabetes is GenHlth(General Health), BMI, HighBP (High blood pressure), Age, and HighChol (High Cholestrol)

Confusion Matrix for XGBoost model¶

In [ ]:
from sklearn.metrics import confusion_matrix
from sklearn import metrics

y_pred = prediction_test
y_true = y_test
confusion_matrix = confusion_matrix(y_true, y_pred)

cm_display = metrics.ConfusionMatrixDisplay(confusion_matrix = confusion_matrix, display_labels = [False, True])

cm_display.plot()
plt.show()
No description has been provided for this image