{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "8eb822ac-ff27-4475-9aa6-374e2b7e77e6",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os, json\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "from sklearn.model_selection import train_test_split, StratifiedKFold, GridSearchCV\n",
    "from sklearn.preprocessing import StandardScaler, OneHotEncoder\n",
    "from sklearn.compose import ColumnTransformer\n",
    "from sklearn.pipeline import Pipeline\n",
    "from sklearn.impute import SimpleImputer\n",
    "from sklearn.metrics import accuracy_score, f1_score, roc_auc_score, classification_report, confusion_matrix\n",
    "\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.svm import SVC\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "\n",
    "from sklearn.feature_selection import SelectKBest, f_classif, RFE\n",
    "\n",
    "RANDOM_STATE = 42\n",
    "CV_SPLITS = 5\n",
    "FULL_NAME = \"Atheer_Mohammed_AlShahrani\"\n",
    "\n",
    "RAW_CSV_PATH   = \"Breast_cancer_dataset.csv\"  \n",
    "CLEAN_CSV_PATH = f\"Breast_cancer_clean_{FULL_NAME}.csv\"\n",
    "\n",
    "REPORTS_DIR = f\"final_project_outputs_{FULL_NAME}\"\n",
    "os.makedirs(REPORTS_DIR, exist_ok=True)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "9f4fd296-c1b8-4692-881c-8bc8f16e364a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Target column: diagnosis\n",
      "Numeric features: 31  | Categorical features: 0\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>diagnosis</th>\n",
       "      <th>radius_mean</th>\n",
       "      <th>texture_mean</th>\n",
       "      <th>perimeter_mean</th>\n",
       "      <th>area_mean</th>\n",
       "      <th>smoothness_mean</th>\n",
       "      <th>compactness_mean</th>\n",
       "      <th>concavity_mean</th>\n",
       "      <th>concave_points_mean</th>\n",
       "      <th>symmetry_mean</th>\n",
       "      <th>...</th>\n",
       "      <th>radius_worst</th>\n",
       "      <th>texture_worst</th>\n",
       "      <th>perimeter_worst</th>\n",
       "      <th>area_worst</th>\n",
       "      <th>smoothness_worst</th>\n",
       "      <th>compactness_worst</th>\n",
       "      <th>concavity_worst</th>\n",
       "      <th>concave_points_worst</th>\n",
       "      <th>symmetry_worst</th>\n",
       "      <th>fractal_dimension_worst</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>1</td>\n",
       "      <td>17.99</td>\n",
       "      <td>10.38</td>\n",
       "      <td>122.80</td>\n",
       "      <td>1001.0</td>\n",
       "      <td>0.11840</td>\n",
       "      <td>0.27760</td>\n",
       "      <td>0.3001</td>\n",
       "      <td>0.14710</td>\n",
       "      <td>0.2419</td>\n",
       "      <td>...</td>\n",
       "      <td>25.38</td>\n",
       "      <td>17.33</td>\n",
       "      <td>184.60</td>\n",
       "      <td>2019.0</td>\n",
       "      <td>0.1622</td>\n",
       "      <td>0.6656</td>\n",
       "      <td>0.7119</td>\n",
       "      <td>0.2654</td>\n",
       "      <td>0.4601</td>\n",
       "      <td>0.11890</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>1</td>\n",
       "      <td>20.57</td>\n",
       "      <td>17.77</td>\n",
       "      <td>132.90</td>\n",
       "      <td>1326.0</td>\n",
       "      <td>0.08474</td>\n",
       "      <td>0.07864</td>\n",
       "      <td>0.0869</td>\n",
       "      <td>0.07017</td>\n",
       "      <td>0.1812</td>\n",
       "      <td>...</td>\n",
       "      <td>24.99</td>\n",
       "      <td>23.41</td>\n",
       "      <td>158.80</td>\n",
       "      <td>1956.0</td>\n",
       "      <td>0.1238</td>\n",
       "      <td>0.1866</td>\n",
       "      <td>0.2416</td>\n",
       "      <td>0.1860</td>\n",
       "      <td>0.2750</td>\n",
       "      <td>0.08902</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>1</td>\n",
       "      <td>19.69</td>\n",
       "      <td>21.25</td>\n",
       "      <td>130.00</td>\n",
       "      <td>1203.0</td>\n",
       "      <td>0.10960</td>\n",
       "      <td>0.15990</td>\n",
       "      <td>0.1974</td>\n",
       "      <td>0.12790</td>\n",
       "      <td>0.2069</td>\n",
       "      <td>...</td>\n",
       "      <td>23.57</td>\n",
       "      <td>25.53</td>\n",
       "      <td>152.50</td>\n",
       "      <td>1709.0</td>\n",
       "      <td>0.1444</td>\n",
       "      <td>0.4245</td>\n",
       "      <td>0.4504</td>\n",
       "      <td>0.2430</td>\n",
       "      <td>0.3613</td>\n",
       "      <td>0.08758</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>1</td>\n",
       "      <td>11.42</td>\n",
       "      <td>20.38</td>\n",
       "      <td>77.58</td>\n",
       "      <td>386.1</td>\n",
       "      <td>0.14250</td>\n",
       "      <td>0.28390</td>\n",
       "      <td>0.2414</td>\n",
       "      <td>0.10520</td>\n",
       "      <td>0.2597</td>\n",
       "      <td>...</td>\n",
       "      <td>14.91</td>\n",
       "      <td>26.50</td>\n",
       "      <td>98.87</td>\n",
       "      <td>567.7</td>\n",
       "      <td>0.2098</td>\n",
       "      <td>0.8663</td>\n",
       "      <td>0.6869</td>\n",
       "      <td>0.2575</td>\n",
       "      <td>0.6638</td>\n",
       "      <td>0.17300</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>1</td>\n",
       "      <td>20.29</td>\n",
       "      <td>14.34</td>\n",
       "      <td>135.10</td>\n",
       "      <td>1297.0</td>\n",
       "      <td>0.10030</td>\n",
       "      <td>0.13280</td>\n",
       "      <td>0.1980</td>\n",
       "      <td>0.10430</td>\n",
       "      <td>0.1809</td>\n",
       "      <td>...</td>\n",
       "      <td>22.54</td>\n",
       "      <td>16.67</td>\n",
       "      <td>152.20</td>\n",
       "      <td>1575.0</td>\n",
       "      <td>0.1374</td>\n",
       "      <td>0.2050</td>\n",
       "      <td>0.4000</td>\n",
       "      <td>0.1625</td>\n",
       "      <td>0.2364</td>\n",
       "      <td>0.07678</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>5 rows × 31 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "   diagnosis  radius_mean  texture_mean  perimeter_mean  area_mean  \\\n",
       "0          1        17.99         10.38          122.80     1001.0   \n",
       "1          1        20.57         17.77          132.90     1326.0   \n",
       "2          1        19.69         21.25          130.00     1203.0   \n",
       "3          1        11.42         20.38           77.58      386.1   \n",
       "4          1        20.29         14.34          135.10     1297.0   \n",
       "\n",
       "   smoothness_mean  compactness_mean  concavity_mean  concave_points_mean  \\\n",
       "0          0.11840           0.27760          0.3001              0.14710   \n",
       "1          0.08474           0.07864          0.0869              0.07017   \n",
       "2          0.10960           0.15990          0.1974              0.12790   \n",
       "3          0.14250           0.28390          0.2414              0.10520   \n",
       "4          0.10030           0.13280          0.1980              0.10430   \n",
       "\n",
       "   symmetry_mean  ...  radius_worst  texture_worst  perimeter_worst  \\\n",
       "0         0.2419  ...         25.38          17.33           184.60   \n",
       "1         0.1812  ...         24.99          23.41           158.80   \n",
       "2         0.2069  ...         23.57          25.53           152.50   \n",
       "3         0.2597  ...         14.91          26.50            98.87   \n",
       "4         0.1809  ...         22.54          16.67           152.20   \n",
       "\n",
       "   area_worst  smoothness_worst  compactness_worst  concavity_worst  \\\n",
       "0      2019.0            0.1622             0.6656           0.7119   \n",
       "1      1956.0            0.1238             0.1866           0.2416   \n",
       "2      1709.0            0.1444             0.4245           0.4504   \n",
       "3       567.7            0.2098             0.8663           0.6869   \n",
       "4      1575.0            0.1374             0.2050           0.4000   \n",
       "\n",
       "   concave_points_worst  symmetry_worst  fractal_dimension_worst  \n",
       "0                0.2654          0.4601                  0.11890  \n",
       "1                0.1860          0.2750                  0.08902  \n",
       "2                0.2430          0.3613                  0.08758  \n",
       "3                0.2575          0.6638                  0.17300  \n",
       "4                0.1625          0.2364                  0.07678  \n",
       "\n",
       "[5 rows x 31 columns]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def normalize_col(c: str) -> str:\n",
    "    return (\n",
    "        str(c).strip()\n",
    "        .replace(\"\\n\", \" \").replace(\"\\r\", \" \")\n",
    "        .replace(\"/\", \"_\").replace(\"\\\\\", \"_\")\n",
    "        .replace(\" \", \"_\").lower()\n",
    "    )\n",
    "\n",
    "def load_and_clean(path: str, target_guess=(\"diagnosis\",\"target\",\"class\",\"label\",\"outcome\")):\n",
    "    df = pd.read_csv(path)\n",
    "    df.columns = [normalize_col(c) for c in df.columns]\n",
    "\n",
    "    # Drop junk cols\n",
    "    junk_like = [c for c in df.columns if c.startswith(\"unnamed\")]\n",
    "    df = df.drop(columns=junk_like, errors=\"ignore\")\n",
    "\n",
    "    df = df.drop_duplicates()\n",
    "\n",
    "    # Find target\n",
    "    target_col = None\n",
    "    for c in target_guess:\n",
    "        if c in df.columns:\n",
    "            target_col = c\n",
    "            break\n",
    "    if target_col is None:\n",
    "        raise ValueError(\"Target column not found.\")\n",
    "\n",
    "    df = df.dropna(subset=[target_col]).copy()\n",
    "\n",
    "    # Map target if M/B\n",
    "    if df[target_col].dtype == object:\n",
    "        if set(df[target_col].unique()).issubset({\"M\",\"B\",\"m\",\"b\",\"Malignant\",\"Benign\"}):\n",
    "            df[target_col] = df[target_col].astype(str).str.upper().map(\n",
    "                {\"M\":1, \"B\":0, \"MALIGNANT\":1, \"BENIGN\":0}\n",
    "            )\n",
    "\n",
    "    # Feature types\n",
    "    numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()\n",
    "    cat_cols = [c for c in df.columns if c not in numeric_cols]\n",
    "\n",
    "    if target_col in numeric_cols: numeric_cols.remove(target_col)\n",
    "    if target_col in cat_cols: cat_cols.remove(target_col)\n",
    "\n",
    "    if numeric_cols:\n",
    "        df[numeric_cols] = SimpleImputer(strategy=\"median\").fit_transform(df[numeric_cols])\n",
    "    if cat_cols:\n",
    "        df[cat_cols] = SimpleImputer(strategy=\"most_frequent\").fit_transform(df[cat_cols])\n",
    "\n",
    "    # Drop id column if exists\n",
    "    for maybe_id in [\"id\",\"patient_id\",\"index\"]:\n",
    "        if maybe_id in df.columns:\n",
    "            df = df.drop(columns=[maybe_id])\n",
    "\n",
    "    return df, target_col, numeric_cols, cat_cols\n",
    "\n",
    "df, TARGET_COL, num_cols, cat_cols = load_and_clean(RAW_CSV_PATH)\n",
    "df.to_csv(CLEAN_CSV_PATH, index=False)\n",
    "print(\"Target column:\", TARGET_COL)\n",
    "print(\"Numeric features:\", len(num_cols), \" | Categorical features:\", len(cat_cols))\n",
    "display(df.head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "f419198f-f1ba-4d11-b84a-5bfec5c83696",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train size: (455, 30)  | Test size: (114, 30)\n",
      "Numeric features: 30  | Categorical features: 0\n"
     ]
    }
   ],
   "source": [
    "X = df.drop(columns=[TARGET_COL])\n",
    "y = df[TARGET_COL].astype(int)\n",
    "\n",
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "    X, y, test_size=0.2, stratify=y, random_state=RANDOM_STATE\n",
    ")\n",
    "\n",
    "num_cols = X_train.select_dtypes(include=[np.number]).columns.tolist()\n",
    "cat_cols = [c for c in X_train.columns if c not in num_cols]\n",
    "\n",
    "numeric_transformer = Pipeline(steps=[(\"scaler\", StandardScaler())])\n",
    "categorical_transformer = Pipeline(steps=[(\"onehot\", OneHotEncoder(handle_unknown=\"ignore\", sparse_output=False))]) if len(cat_cols) > 0 else \"drop\"\n",
    "\n",
    "preprocess = ColumnTransformer(\n",
    "    transformers=[\n",
    "        (\"num\", numeric_transformer, num_cols if len(num_cols)>0 else []),\n",
    "        (\"cat\", categorical_transformer, cat_cols if len(cat_cols)>0 else [])\n",
    "    ],\n",
    "    remainder=\"drop\"\n",
    ")\n",
    "print(\"Train size:\", X_train.shape, \" | Test size:\", X_test.shape)\n",
    "print(\"Numeric features:\", len(num_cols), \" | Categorical features:\", len(cat_cols))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "ebb9608b-5a04-4ca5-8834-ebc8dffa76f8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>model</th>\n",
       "      <th>best_params</th>\n",
       "      <th>accuracy</th>\n",
       "      <th>f1</th>\n",
       "      <th>roc_auc</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>logreg</td>\n",
       "      <td>{'clf__C': 1, 'clf__penalty': 'l2', 'clf__solv...</td>\n",
       "      <td>0.973684</td>\n",
       "      <td>0.963855</td>\n",
       "      <td>0.996032</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>svm</td>\n",
       "      <td>{'clf__C': 1, 'clf__gamma': 'scale', 'clf__ker...</td>\n",
       "      <td>0.973684</td>\n",
       "      <td>0.962963</td>\n",
       "      <td>0.994709</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>rf</td>\n",
       "      <td>{'clf__max_depth': 10, 'clf__min_samples_split...</td>\n",
       "      <td>0.973684</td>\n",
       "      <td>0.962963</td>\n",
       "      <td>0.992890</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>knn</td>\n",
       "      <td>{'clf__metric': 'euclidean', 'clf__n_neighbors...</td>\n",
       "      <td>0.938596</td>\n",
       "      <td>0.911392</td>\n",
       "      <td>0.981812</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "    model                                        best_params  accuracy  \\\n",
       "0  logreg  {'clf__C': 1, 'clf__penalty': 'l2', 'clf__solv...  0.973684   \n",
       "1     svm  {'clf__C': 1, 'clf__gamma': 'scale', 'clf__ker...  0.973684   \n",
       "2      rf  {'clf__max_depth': 10, 'clf__min_samples_split...  0.973684   \n",
       "3     knn  {'clf__metric': 'euclidean', 'clf__n_neighbors...  0.938596   \n",
       "\n",
       "         f1   roc_auc  \n",
       "0  0.963855  0.996032  \n",
       "1  0.962963  0.994709  \n",
       "2  0.962963  0.992890  \n",
       "3  0.911392  0.981812  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "\n",
    "models_and_grids = {\n",
    "    \"logreg\": (\n",
    "        LogisticRegression(max_iter=500, random_state=RANDOM_STATE),\n",
    "        {\"clf__C\":[0.01,0.1,1,10], \"clf__penalty\":[\"l2\"], \"clf__solver\":[\"liblinear\",\"lbfgs\"]}\n",
    "    ),\n",
    "    \"svm\": (\n",
    "        SVC(probability=True, random_state=RANDOM_STATE),\n",
    "        {\"clf__C\":[0.1,1,10], \"clf__kernel\":[\"rbf\",\"linear\"], \"clf__gamma\":[\"scale\",\"auto\"]}\n",
    "    ),\n",
    "    \"rf\": (\n",
    "        RandomForestClassifier(random_state=RANDOM_STATE),\n",
    "        {\"clf__n_estimators\":[100,300], \"clf__max_depth\":[None,5,10], \"clf__min_samples_split\":[2,5]}\n",
    "    ),\n",
    "    \"knn\": (\n",
    "        KNeighborsClassifier(),\n",
    "        {\"clf__n_neighbors\":[3,5,7,9], \"clf__weights\":[\"uniform\",\"distance\"], \"clf__metric\":[\"euclidean\",\"manhattan\"]}\n",
    "    )\n",
    "}\n",
    "\n",
    "cv = StratifiedKFold(n_splits=CV_SPLITS, shuffle=True, random_state=RANDOM_STATE)\n",
    "\n",
    "results = []\n",
    "best_estimators = {}\n",
    "\n",
    "for name, (model, grid) in models_and_grids.items():\n",
    "    pipe = Pipeline([(\"prep\", preprocess), (\"clf\", model)])\n",
    "    gs = GridSearchCV(pipe, grid, cv=cv, scoring=\"roc_auc\", n_jobs=-1, refit=True)\n",
    "    gs.fit(X_train, y_train)\n",
    "\n",
    "    y_pred = gs.predict(X_test)\n",
    "    y_proba = gs.predict_proba(X_test)[:,1]\n",
    "\n",
    "    results.append({\n",
    "        \"model\": name,\n",
    "        \"best_params\": gs.best_params_,\n",
    "        \"accuracy\": accuracy_score(y_test, y_pred),\n",
    "        \"f1\": f1_score(y_test, y_pred),\n",
    "        \"roc_auc\": roc_auc_score(y_test, y_proba)\n",
    "    })\n",
    "    best_estimators[name] = gs.best_estimator_\n",
    "\n",
    "baseline_results = pd.DataFrame(results).sort_values(\"roc_auc\", ascending=False)\n",
    "display(baseline_results)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "3743071f-360f-434b-a7d0-833d8c60641f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "SelectKBest features: ['concave_points_worst', 'perimeter_worst', 'radius_worst', 'concave_points_mean', 'perimeter_mean', 'area_worst', 'radius_mean', 'area_mean', 'concavity_mean', 'concavity_worst']\n",
      "RFE features: [np.str_('radius_mean'), np.str_('concavity_mean'), np.str_('texture_se'), np.str_('perimeter_se'), np.str_('radius_worst'), np.str_('smoothness_worst'), np.str_('compactness_worst'), np.str_('concavity_worst'), np.str_('concave_points_worst'), np.str_('symmetry_worst')]\n"
     ]
    }
   ],
   "source": [
    "skb = SelectKBest(score_func=f_classif, k=10)\n",
    "skb.fit(X_train[num_cols], y_train)\n",
    "skb_features = list(pd.Series(skb.scores_, index=num_cols).nlargest(10).index)\n",
    "\n",
    "\n",
    "rfe = RFE(\n",
    "    estimator=LogisticRegression(\n",
    "        max_iter=5000,      \n",
    "        solver=\"lbfgs\",\n",
    "        random_state=RANDOM_STATE\n",
    "    ),\n",
    "    n_features_to_select=10\n",
    ")\n",
    "rfe.fit(X_train[num_cols], y_train)\n",
    "rfe_features = list(np.array(num_cols)[rfe.support_])\n",
    "\n",
    "print(\"SelectKBest features:\", skb_features)\n",
    "print(\"RFE features:\", rfe_features)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "1e6ee5e2-48ba-4f0f-ae39-85b2968017e7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      " Results using SelectKBest features\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>model</th>\n",
       "      <th>accuracy</th>\n",
       "      <th>f1</th>\n",
       "      <th>roc_auc</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>logreg</td>\n",
       "      <td>0.982456</td>\n",
       "      <td>0.975610</td>\n",
       "      <td>0.998677</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>svm</td>\n",
       "      <td>0.973684</td>\n",
       "      <td>0.962963</td>\n",
       "      <td>0.998677</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>knn</td>\n",
       "      <td>0.956140</td>\n",
       "      <td>0.936709</td>\n",
       "      <td>0.996197</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>rf</td>\n",
       "      <td>0.964912</td>\n",
       "      <td>0.950000</td>\n",
       "      <td>0.992394</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "    model  accuracy        f1   roc_auc\n",
       "0  logreg  0.982456  0.975610  0.998677\n",
       "1     svm  0.973684  0.962963  0.998677\n",
       "3     knn  0.956140  0.936709  0.996197\n",
       "2      rf  0.964912  0.950000  0.992394"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      " Results using RFE features\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>model</th>\n",
       "      <th>accuracy</th>\n",
       "      <th>f1</th>\n",
       "      <th>roc_auc</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>logreg</td>\n",
       "      <td>0.982456</td>\n",
       "      <td>0.976190</td>\n",
       "      <td>0.997354</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>svm</td>\n",
       "      <td>0.964912</td>\n",
       "      <td>0.951220</td>\n",
       "      <td>0.996693</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>knn</td>\n",
       "      <td>0.947368</td>\n",
       "      <td>0.925000</td>\n",
       "      <td>0.995205</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>rf</td>\n",
       "      <td>0.956140</td>\n",
       "      <td>0.936709</td>\n",
       "      <td>0.992725</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "    model  accuracy        f1   roc_auc\n",
       "0  logreg  0.982456  0.976190  0.997354\n",
       "1     svm  0.964912  0.951220  0.996693\n",
       "3     knn  0.947368  0.925000  0.995205\n",
       "2      rf  0.956140  0.936709  0.992725"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import display\n",
    "\n",
    "def retrain_on_features(selected_features, tag):\n",
    "    selected_features = [str(f) for f in selected_features]\n",
    "    selected_features = [f for f in selected_features if f in X_train.columns]\n",
    "\n",
    "    prep = ColumnTransformer([\n",
    "        (\"num\", StandardScaler(), selected_features)\n",
    "    ], remainder=\"drop\")\n",
    "\n",
    "    res = []\n",
    "    for name,(model,grid) in models_and_grids.items():\n",
    "        pipe = Pipeline([(\"prep\", prep), (\"clf\", model)])\n",
    "        gs = GridSearchCV(pipe, grid, cv=cv, scoring=\"roc_auc\", n_jobs=-1, refit=True)\n",
    "        gs.fit(X_train[selected_features], y_train)\n",
    "        y_pred = gs.predict(X_test[selected_features])\n",
    "        y_proba = gs.predict_proba(X_test[selected_features])[:,1]\n",
    "        res.append({\n",
    "            \"model\": name,\n",
    "            \"accuracy\": accuracy_score(y_test, y_pred),\n",
    "            \"f1\": f1_score(y_test, y_pred),\n",
    "            \"roc_auc\": roc_auc_score(y_test, y_proba)\n",
    "        })\n",
    "\n",
    "    df_res = pd.DataFrame(res).sort_values(\"roc_auc\", ascending=False)\n",
    "    print(f\"\\n Results using {tag} features\")\n",
    "    display(df_res)   # <-- force table display\n",
    "    return df_res\n",
    "\n",
    "skb_results = retrain_on_features(skb_features, \"SelectKBest\")\n",
    "rfe_results = retrain_on_features(rfe_features, \"RFE\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "36827f2a-6c24-458b-8892-14fb9bc19f1a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Final Project Summary\n",
      "----------------------------------------\n",
      "Target column: diagnosis\n",
      "Train rows: 455 | Test rows: 114\n",
      "\n",
      " Baseline Best Model:\n",
      " model                                                     best_params  accuracy       f1  roc_auc\n",
      "logreg {'clf__C': 1, 'clf__penalty': 'l2', 'clf__solver': 'liblinear'}  0.973684 0.963855 0.996032\n",
      "\n",
      " SelectKBest Top Features:\n",
      "concave_points_worst, perimeter_worst, radius_worst, concave_points_mean, perimeter_mean, area_worst, radius_mean, area_mean, concavity_mean, concavity_worst\n",
      "\n",
      " RFE Top Features:\n",
      "radius_mean, concavity_mean, texture_se, perimeter_se, radius_worst, smoothness_worst, compactness_worst, concavity_worst, concave_points_worst, symmetry_worst\n",
      "----------------------------------------\n",
      " Project Complete\n"
     ]
    }
   ],
   "source": [
    "\n",
    "summary = {\n",
    "    \"target\": TARGET_COL,\n",
    "    \"baseline_best\": baseline_results.head(1).to_dict(orient=\"records\")[0],\n",
    "    \"selectkbest_features\": skb_features,\n",
    "    \"rfe_features\": rfe_features\n",
    "}\n",
    "print(\"Final Project Summary\")\n",
    "print(\"-\" * 40)\n",
    "print(f\"Target column: {TARGET_COL}\")\n",
    "print(f\"Train rows: {X_train.shape[0]} | Test rows: {X_test.shape[0]}\")\n",
    "print()\n",
    "print(\" Baseline Best Model:\")\n",
    "print(baseline_results.head(1).to_string(index=False))\n",
    "print()\n",
    "print(\" SelectKBest Top Features:\")\n",
    "print(\", \".join(skb_features))\n",
    "print()\n",
    "print(\" RFE Top Features:\")\n",
    "print(\", \".join(rfe_features))\n",
    "print(\"-\" * 40)\n",
    "print(\" Project Complete\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "de239aa3-2dbc-4966-b16f-d8ca10b94daa",
   "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.13.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
