{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "e3fc2e4f-cce6-4e15-a894-b47618a120fc",
   "metadata": {},
   "source": [
    "# Prediction of Under-Five Mortality Using Supervised Machine Learning Algorithms in 23 Sub-Saharan African Countries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7aa2833a-b7a0-4521-a394-244907ba58d5",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "\n",
    "This notebook reproduces the analysis described in the manuscript submitted to Scientific Reports.\n",
    "\n",
    "Author: Angwach Abrham Asnake et al.\n",
    "Python version: 3.12\n",
    "Random seed: 42\n",
    "\n",
    "This notebook includes:\n",
    "- Data preprocessing\n",
    "- Feature selection (Boruta)\n",
    "- Class balancing (SMOTE)\n",
    "- Model development\n",
    "- Hyperparameter tuning\n",
    "- Model evaluation\n",
    "- SHAP interpretability analysis\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21cc9f50-06cd-4da8-ae57-4e8380ccd6ae",
   "metadata": {},
   "source": [
    "# IMPORT LIBRARIES & VERSION CHECK"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "77f5b638-515e-4a68-8a30-b5740da72009",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "import shap\n",
    "\n",
    "from sklearn.model_selection import train_test_split, GridSearchCV\n",
    "from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, roc_curve\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.tree import DecisionTreeClassifier\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "from sklearn.svm import SVC\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "from sklearn.naive_bayes import GaussianNB\n",
    "from xgboost import XGBClassifier\n",
    "from imblearn.over_sampling import SMOTE\n",
    "from boruta import BorutaPy\n",
    "\n",
    "RANDOM_STATE = 42\n",
    "np.random.seed(RANDOM_STATE)\n",
    "\n",
    "print(\"Python:\", sys.version)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d3df4a0f-7a7f-41dc-a16c-e668c0bdbf98",
   "metadata": {},
   "outputs": [],
   "source": [
    "RANDOM_STATE = 42\n",
    "np.random.seed(RANDOM_STATE)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9773da24-681f-405f-b647-e83cd2ede6c2",
   "metadata": {},
   "source": [
    "# LOAD DATA"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "43327d16-5c2a-43d8-8849-0536ae0cf8ee",
   "metadata": {},
   "outputs": [],
   "source": [
    "# NOTE:\n",
    "# DHS data are not publicly redistributed due to data use agreements.\n",
    "# Researchers must request access from https://data.dhsprogram.com/\n",
    "\n",
    "df = pd.read_csv(\"DHS_combined_dataset.csv\")\n",
    "\n",
    "# Target variable\n",
    "target = \"under_five_mortality\"\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fc8e530b-3f5b-42e2-9b2d-3591b1026279",
   "metadata": {},
   "source": [
    "# PREPROCESSING "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b150fddf-60a2-48ae-b91b-ee1c0a8a750e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Normalize sampling weights\n",
    "df['weight'] = df['v005'] / 1000000\n",
    "\n",
    "# One-hot encoding\n",
    "df_encoded = pd.get_dummies(df, drop_first=True)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "de8622f5-2b66-442d-ba4a-ef11e2a247b8",
   "metadata": {},
   "source": [
    "# TRAIN–TEST SPLIT "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4cc24026-0c0a-47b7-919d-fd78c80b80da",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "X = df_encoded.drop(\"under_five_mortality\", axis=1)\n",
    "y = df_encoded[\"under_five_mortality\"]\n",
    "\n",
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "    X, y,\n",
    "    test_size=0.2,\n",
    "    random_state=RANDOM_STATE,\n",
    "    stratify=y\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f6994180-0e1c-436d-8f5d-38047e3fde45",
   "metadata": {},
   "source": [
    "# SMOTE (ONLY ON TRAINING SET)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f3c22fc4-3636-4d21-9004-2c8330e81f0e",
   "metadata": {},
   "outputs": [],
   "source": [
    "from imblearn.over_sampling import SMOTE\n",
    "\n",
    "smote = SMOTE(random_state=RANDOM_STATE)\n",
    "X_train_resampled, y_train_resampled = smote.fit_resample(X_train, y_train)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "090ed3e3-9aae-4c0b-834e-2365d7530d7a",
   "metadata": {},
   "source": [
    "# BORUTA FEATURE SELECTION (ON TRAINING DATA ONLY)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26337860-4cad-42c2-b5a9-2822a7e506e2",
   "metadata": {},
   "outputs": [],
   "source": [
    "rf_for_boruta = RandomForestClassifier(random_state=RANDOM_STATE)\n",
    "\n",
    "boruta = BorutaPy(\n",
    "    rf_for_boruta,\n",
    "    n_estimators='auto',\n",
    "    random_state=RANDOM_STATE\n",
    ")\n",
    "\n",
    "boruta.fit(X_train_resampled.values, y_train_resampled.values)\n",
    "\n",
    "selected_features = X_train.columns[boruta.support_]\n",
    "\n",
    "X_train_resampled = pd.DataFrame(X_train_resampled, columns=X_train.columns)[selected_features]\n",
    "X_test = X_test[selected_features]\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cc60a5e2-79d7-4f13-b85c-6ad4f5ac9859",
   "metadata": {},
   "source": [
    "# DEFINE SEVEN MODELS"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "116cf737-a37b-47ee-902c-c161cef56e21",
   "metadata": {},
   "outputs": [],
   "source": [
    "models = {\n",
    "    \"Logistic Regression\": LogisticRegression(max_iter=1000, random_state=RANDOM_STATE),\n",
    "    \"Decision Tree\": DecisionTreeClassifier(random_state=RANDOM_STATE),\n",
    "    \"Random Forest\": RandomForestClassifier(random_state=RANDOM_STATE),\n",
    "    \"SVM\": SVC(probability=True, random_state=RANDOM_STATE),\n",
    "    \"KNN\": KNeighborsClassifier(),\n",
    "    \"Naive Bayes\": GaussianNB(),\n",
    "    \"XGBoost\": XGBClassifier(eval_metric='logloss', random_state=RANDOM_STATE)\n",
    "}\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ff9d96b3-4f2d-4265-b3fb-0174d3f79989",
   "metadata": {},
   "source": [
    "# TRAIN & EVALUATE ALL MODELS (Default Parameters)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0de541d4-4542-4a6b-a2fb-18ecad6afdc5",
   "metadata": {},
   "outputs": [],
   "source": [
    "baseline_results = []\n",
    "\n",
    "for name, model in models.items():\n",
    "    model.fit(X_train_resampled, y_train_resampled)\n",
    "    \n",
    "    y_pred = model.predict(X_test)\n",
    "    y_prob = model.predict_proba(X_test)[:, 1]\n",
    "    \n",
    "    baseline_results.append({\n",
    "        \"Model\": name,\n",
    "        \"Accuracy\": accuracy_score(y_test, y_pred),\n",
    "        \"Precision\": precision_score(y_test, y_pred),\n",
    "        \"Recall\": recall_score(y_test, y_pred),\n",
    "        \"F1-score\": f1_score(y_test, y_pred),\n",
    "        \"AUC\": roc_auc_score(y_test, y_prob)\n",
    "    })\n",
    "\n",
    "baseline_df = pd.DataFrame(baseline_results)\n",
    "baseline_df\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4c90c3f1-fae2-4baf-8430-426fb4eacac6",
   "metadata": {},
   "source": [
    "# Hyperparameter Grids (For Each Model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f944c623-aff4-4479-b6e2-df9881a59d4c",
   "metadata": {},
   "outputs": [],
   "source": [
    "param_grids = {\n",
    "    \"Logistic Regression\": {\n",
    "        \"C\": [0.1, 1, 10]\n",
    "    },\n",
    "    \"Decision Tree\": {\n",
    "        \"max_depth\": [None, 10, 20]\n",
    "    },\n",
    "    \"Random Forest\": {\n",
    "        \"n_estimators\": [100, 200],\n",
    "        \"max_depth\": [None, 10, 20]\n",
    "    },\n",
    "    \"SVM\": {\n",
    "        \"C\": [0.1, 1],\n",
    "        \"kernel\": [\"rbf\"]\n",
    "    },\n",
    "    \"KNN\": {\n",
    "        \"n_neighbors\": [5, 7, 9]\n",
    "    },\n",
    "    \"Naive Bayes\": {},\n",
    "    \"XGBoost\": {\n",
    "        \"n_estimators\": [100, 200],\n",
    "        \"max_depth\": [3, 5]\n",
    "    }\n",
    "}\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "caa3d6d1-85a7-4dcb-bc4a-560a0321d7c6",
   "metadata": {},
   "source": [
    "# GridSearchCV for Each Model (Training Data Only)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0eba7c60-70f5-46b0-812f-d43285436f23",
   "metadata": {},
   "outputs": [],
   "source": [
    "tuned_results = []\n",
    "\n",
    "for name, model in models.items():\n",
    "    \n",
    "    grid = GridSearchCV(\n",
    "        model,\n",
    "        param_grids[name],\n",
    "        cv=10,\n",
    "        scoring=\"roc_auc\",\n",
    "        n_jobs=-1\n",
    "    )\n",
    "    \n",
    "    grid.fit(X_train_resampled, y_train_resampled)\n",
    "    \n",
    "    best_model = grid.best_estimator_\n",
    "    \n",
    "    y_pred = best_model.predict(X_test)\n",
    "    y_prob = best_model.predict_proba(X_test)[:, 1]\n",
    "    \n",
    "    tuned_results.append({\n",
    "        \"Model\": name,\n",
    "        \"Accuracy\": accuracy_score(y_test, y_pred),\n",
    "        \"Precision\": precision_score(y_test, y_pred),\n",
    "        \"Recall\": recall_score(y_test, y_pred),\n",
    "        \"F1-score\": f1_score(y_test, y_pred),\n",
    "        \"AUC\": roc_auc_score(y_test, y_prob)\n",
    "    })\n",
    "\n",
    "tuned_df = pd.DataFrame(tuned_results)\n",
    "tuned_df\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d5835c2-a0cd-4b66-b460-e61e6b677cde",
   "metadata": {},
   "source": [
    "# Compare Before vs After"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bc868b10-8a66-42b9-a1c7-db3c72cc9c1f",
   "metadata": {},
   "outputs": [],
   "source": [
    "comparison = baseline_df.merge(tuned_df, on=\"Model\", suffixes=(\"_Before\", \"_After\"))\n",
    "comparison\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2d1bc195-810d-43c0-8ecd-ce10e3e5c8c7",
   "metadata": {},
   "source": [
    "# SHAP for final model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a63347d4-1e6f-4197-9990-ecffd9b83da0",
   "metadata": {},
   "outputs": [],
   "source": [
    "import shap\n",
    "\n",
    "# Initialize JS visualization (optional)\n",
    "shap.initjs()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a233d072-f3f4-473d-9813-e88de8218ade",
   "metadata": {},
   "source": [
    "## Create SHAP Explainer (TreeExplainer for Random Forest)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "896888ee-38ae-4ee6-b559-0c6626f9a0b4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create SHAP explainer for the tuned Random Forest model\n",
    "explainer = shap.TreeExplainer(best_rf)\n",
    "\n",
    "# Compute SHAP values on test dataset\n",
    "shap_values = explainer.shap_values(X_test)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d886438-8338-4214-b917-cca2dfd75b81",
   "metadata": {},
   "source": [
    "## SHAP Beeswarm Plot (Global Interpretation)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ceb482c5-170e-4846-84ab-a51f17cdab48",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Beeswarm plot (for class 1 = death)\n",
    "shap.summary_plot(\n",
    "    shap_values[1], \n",
    "    X_test,\n",
    "    plot_type=\"dot\"\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "011481cc-9bfa-4843-b7be-1268c6c7e3c3",
   "metadata": {},
   "source": [
    "## SHAP Bar Plot (Mean Absolute Importance)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ef9ec6c5-7a46-4de0-9ff4-9e6930a33ff1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Bar plot of feature importance\n",
    "shap.summary_plot(\n",
    "    shap_values[1], \n",
    "    X_test,\n",
    "    plot_type=\"bar\"\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4be37124-952d-421b-9e74-9a558bfcb6da",
   "metadata": {},
   "source": [
    "## SHAP Waterfall Plot (Single High-Risk Case)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8d258ae1-db9c-45cd-9154-d60fc4ea2cce",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Identify high-risk case (highest predicted probability)\n",
    "y_prob_rf = best_rf.predict_proba(X_test)[:, 1]\n",
    "\n",
    "high_risk_index = np.argmax(y_prob_rf)\n",
    "\n",
    "# Create explanation for that case\n",
    "single_explanation = shap.Explanation(\n",
    "    values=shap_values[1][high_risk_index],\n",
    "    base_values=explainer.expected_value[1],\n",
    "    data=X_test.iloc[high_risk_index]\n",
    ")\n",
    "\n",
    "shap.waterfall_plot(single_explanation)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "392cf331-50b3-4c31-b8cb-a003809a0214",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4b14ead8-4cea-4bf8-b5c1-8d30a7db63af",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "068a811e-f0da-4095-b092-1cedf781d189",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4bc62fe7-17f7-4caf-8bd8-99f5a51a6531",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
