{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "fb3e5bec",
   "metadata": {},
   "source": [
    "# AI ML Fellowship | Module 7 Asynchronous Unit 3\n",
    "\n",
    "Practical Data Handling for Training\n",
    "\n",
    "# Skillable Lab: The data checkup lab"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1156e930",
   "metadata": {},
   "source": [
    "## Objective\n",
    "\n",
    "In this lab, you will design robust data splitting and cross-validation strategies to prevent leakage, implement techniques for handling imbalanced datasets, and develop data quality protocols to improve model reliability.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "47ebf455",
   "metadata": {},
   "source": [
    "## Scenario\n",
    "\n",
    "You are a data scientist at MediCareAI, a healthcare analytics firm building a model to predict 30-day readmissions. The dataset contains admissions from 2022–2024 with demographic, clinical, and discharge information. It is intentionally **imbalanced** (~8% positives) and includes **missing values**, **outliers**, and **duplicate rows** to simulate real-world data challenges.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cfbe296d",
   "metadata": {},
   "source": [
    "## Dataset Information\n",
    "\n",
    "- File: `MediCareAI_readmissions.csv` (provided with this lab).\n",
    "- Each row = one admission. Target label: `readmitted` (1 = readmitted within 30 days, 0 = not).\n",
    "- Time component: `admission_date` supports **temporal splits** to avoid leakage.\n",
    "- Known issues to address: class imbalance, missing values (BMI, glucose, BP), extreme outliers (very high glucose/BP), and duplicates.\n",
    "\n",
    "In your environment (e.g., Colab), upload the dataset CSV.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "098afc57",
   "metadata": {},
   "source": [
    "## Skillable Lab: The data checkup lab\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b22900b0",
   "metadata": {},
   "source": [
    "## Step 1: Load the dataset\n",
    "\n",
    "Load the CSV and perform a quick sanity check (shape, dtypes, preview). Ensure `admission_date` is parsed as a date.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c113229e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Imports\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "# Optional: install extra libs if needed in your environment\n",
    "# !pip install scikit-learn imbalanced-learn\n",
    "\n",
    "# Load dataset (adjust path if needed)\n",
    "csv_path = '/mnt/data/MediCareAI_readmissions.csv'  # replace with your path if running elsewhere\n",
    "df = pd.read_csv(csv_path)\n",
    "\n",
    "# Parse dates\n",
    "df['admission_date'] = pd.to_datetime(df['admission_date'])\n",
    "\n",
    "# Basic checks\n",
    "print(\"Shape:\", df.shape)\n",
    "print(\"\\nDtypes:\")\n",
    "print(df.dtypes)\n",
    "print(\"\\nPreview:\")\n",
    "display(df.head())\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "72a788de",
   "metadata": {},
   "source": [
    "## Step 2: Explore class imbalance and temporal coverage\n",
    "\n",
    "Visualize the class distribution for `readmitted` and the admissions volume over time. Use these insights to motivate your splitting strategy.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5f3bd6fe",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# Class distribution\n",
    "class_counts = df['readmitted'].value_counts().sort_index()\n",
    "plt.figure()\n",
    "class_counts.plot(kind='bar')\n",
    "plt.title('Class distribution: readmitted')\n",
    "plt.xlabel('Class (0 = not readmitted, 1 = readmitted)')\n",
    "plt.ylabel('Count')\n",
    "plt.show()\n",
    "\n",
    "# Admissions per month (temporal coverage)\n",
    "df_month = df.set_index('admission_date').resample('MS').size().rename('admissions')\n",
    "plt.figure()\n",
    "plt.plot(df_month.index, df_month.values)\n",
    "plt.title('Admissions over time (monthly)')\n",
    "plt.xlabel('Month')\n",
    "plt.ylabel('Admissions')\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "print(\"Positive rate (~should be around 8%):\", round(df['readmitted'].mean()*100, 2), \"%\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b79b5ba2",
   "metadata": {},
   "source": [
    "## Step 3: Implement a leakage-safe data splitting and cross-validation strategy\n",
    "\n",
    "Design a strategy that respects time and class balance. For example:\n",
    "- **Temporal holdout**: Train on earlier dates and test on the most recent period.\n",
    "- **Within-train CV**: Use stratified k-fold cross-validation **inside the training set** for tuning.\n",
    "- Verify that **no records in the test set occur before any in the training set** (temporal leakage check).\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d2b1fc4c",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import StratifiedKFold\n",
    "# Choose a cutoff date for temporal split (example: last 6 months for test)\n",
    "cutoff_date = df['admission_date'].max() - pd.DateOffset(months=6)\n",
    "\n",
    "train_df = df[df['admission_date'] < cutoff_date].copy()\n",
    "test_df  = df[df['admission_date'] >= cutoff_date].copy()\n",
    "\n",
    "print(\"Train shape:\", train_df.shape, \"| Test shape:\", test_df.shape)\n",
    "print(\"Train positive rate:\", round(train_df['readmitted'].mean()*100, 2), \"%\")\n",
    "print(\"Test positive rate:\", round(test_df['readmitted'].mean()*100, 2), \"%\")\n",
    "\n",
    "# Leakage check: latest train date should be < earliest test date\n",
    "print(\"Max train date:\", train_df['admission_date'].max())\n",
    "print(\"Min test date:\", test_df['admission_date'].min())\n",
    "\n",
    "# Set up stratified k-fold inside training partition for future model tuning\n",
    "skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\n",
    "y_train = train_df['readmitted'].values\n",
    "\n",
    "# Example: generate CV indices (do not train models here; just verify splits)\n",
    "fold_sizes = []\n",
    "for fold, (tr_idx, val_idx) in enumerate(skf.split(train_df, y_train), start=1):\n",
    "    fold_sizes.append((len(tr_idx), len(val_idx)))\n",
    "print(\"CV fold (train, val) sizes:\", fold_sizes)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "def4cb28",
   "metadata": {},
   "source": [
    "## Step 4: Handle class imbalance\n",
    "\n",
    "Choose and implement **one** approach below (you can try more than one):\n",
    "- **Class weights** in your estimator.\n",
    "- **Synthetic oversampling** (e.g., SMOTE) on the training set only.\n",
    "- **Undersampling** the majority class (use sparingly).\n",
    "\n",
    "Show the before/after class counts for the **training set** to confirm the effect.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1b1a5546",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Before: class counts in training set\n",
    "print(\"Before balancing (train):\")\n",
    "print(train_df['readmitted'].value_counts())\n",
    "\n",
    "# Option A: Class weights example (logistic regression shown as placeholder)\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "\n",
    "X_cols = [c for c in train_df.columns if c not in ['readmitted','admission_date','patient_id']]\n",
    "X_train = train_df[X_cols]\n",
    "y_train = train_df['readmitted']\n",
    "\n",
    "# Simple numeric-only subset for quick demo (handles categorical by dropping; refine as needed)\n",
    "X_num = X_train.select_dtypes(include=['int64','float64']).fillna(X_train.select_dtypes(include=['int64','float64']).median())\n",
    "\n",
    "model = LogisticRegression(max_iter=1000, class_weight='balanced', n_jobs=None)\n",
    "model.fit(X_num, y_train)\n",
    "\n",
    "print(\"\\nFitted a baseline LogisticRegression with class_weight='balanced'.\")\n",
    "\n",
    "# Option B: SMOTE (uncomment if imbalanced-learn is available)\n",
    "# from imblearn.over_sampling import SMOTE\n",
    "# smote = SMOTE(random_state=42)\n",
    "# X_bal, y_bal = smote.fit_resample(X_num, y_train)\n",
    "# print(\"After SMOTE class counts:\", np.bincount(y_bal))\n",
    "\n",
    "# TODO: If you apply SMOTE or undersampling, ensure operations are confined to the training data only.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "25de6b66",
   "metadata": {},
   "source": [
    "## Step 5: Develop data quality protocols\n",
    "\n",
    "Implement a minimal, repeatable pipeline for the **training set** (and apply the same transformations to validation/test without fitting again):\n",
    "- Handle **missing values** (e.g., SimpleImputer).\n",
    "- Detect and **cap outliers** using domain thresholds or robust quantiles.\n",
    "- **Remove duplicates** as appropriate.\n",
    "- Validate the final schema and basic statistics.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "391a77b3",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.impute import SimpleImputer\n",
    "\n",
    "# Identify numeric columns\n",
    "num_cols = train_df.select_dtypes(include=['int64','float64']).columns.tolist()\n",
    "\n",
    "# 1) Missing values: simple median imputation (fit on train)\n",
    "imputer = SimpleImputer(strategy='median')\n",
    "train_num_imputed = pd.DataFrame(imputer.fit_transform(train_df[num_cols]), columns=num_cols, index=train_df.index)\n",
    "\n",
    "# 2) Outlier capping on selected clinical vars (fit caps on train only)\n",
    "train_clean = train_df.copy()\n",
    "for col, (low_q, high_q) in {\n",
    "    'avg_glucose_level': (0.01, 0.99),\n",
    "    'blood_pressure_systolic': (0.01, 0.99),\n",
    "    'bmi': (0.01, 0.99),\n",
    "}.items():\n",
    "    if col in train_clean.columns:\n",
    "        lo, hi = train_clean[col].quantile([low_q, high_q])\n",
    "        train_clean[col] = train_clean[col].clip(lo, hi)\n",
    "\n",
    "# 3) Remove exact duplicate rows (optional)\n",
    "before = train_clean.shape[0]\n",
    "train_clean = train_clean.drop_duplicates()\n",
    "after = train_clean.shape[0]\n",
    "\n",
    "print(f\"Removed {before - after} duplicate rows from training data.\")\n",
    "\n",
    "# 4) Validate schema and stats\n",
    "print(\"\\nTraining data summary (post-cleaning):\")\n",
    "print(train_clean.describe(include='all').T[['count','mean','std','min','max']].head(12))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3b1ef15d",
   "metadata": {},
   "source": [
    "## Step 6: Reflect on your implementation\n",
    "\n",
    "In 3–5 sentences, explain how your splitting strategy prevents leakage, how your imbalance method improves learning, and how your data quality protocol enhances reliability. Mention at least one trade-off you considered.\n"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
