diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e2a7d61 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/Anomaly Detection/dataset \ No newline at end of file diff --git a/Anomaly Detection/README.md b/Anomaly Detection/README.md new file mode 100644 index 0000000..dc4f2d9 --- /dev/null +++ b/Anomaly Detection/README.md @@ -0,0 +1,79 @@ +# Anomaly Detection in Credit Card Fraud Transactions + +This project demonstrates a simple but strong anomaly detection workflow using the Credit Card Fraud Detection dataset. + +## Overview + +Credit card fraud detection is a highly imbalanced anomaly detection problem. Most transactions are normal, while fraudulent transactions are rare. This notebook compares three popular unsupervised anomaly detection algorithms: + +- Isolation Forest +- Local Outlier Factor +- One-Class SVM + +The notebook includes: + +- Data loading +- Class distribution analysis +- Feature scaling +- Model training +- Prediction conversion from anomaly labels to binary labels +- Evaluation using classification report, confusion matrix, ROC-AUC, and PR-AUC +- A summary table comparing all models + +## Dataset + +Use the Credit Card Fraud Detection dataset from Kaggle: + +https://www.kaggle.com/datasets/mlg-ulb/creditcardfraud + +Download `creditcard.csv` and place it in this project folder before running the notebook. + +Expected structure: + +```text +Anomaly Detection/ +├── README.md +├── anomaly_detection.ipynb +|── dataset/ +└──── creditcard.csv +``` + +## How to Run + +Install the required packages: + +```bash +pip install pandas numpy matplotlib scikit-learn +``` + +Then open the notebook: + +```bash +jupyter notebook anomaly_detection.ipynb +``` + +## Models Used + +### Isolation Forest +Isolation Forest detects anomalies by randomly isolating observations. Anomalies are expected to require fewer random splits. + +### Local Outlier Factor +Local Outlier Factor detects anomalies by comparing the local density of a sample with the density of nearby samples. + +### One-Class SVM +One-Class SVM learns a boundary around normal samples and treats points outside the boundary as anomalies. + +## Evaluation Metrics + +Because the dataset is highly imbalanced, accuracy alone is not reliable. This project reports: + +- Precision +- Recall +- F1-score +- ROC-AUC +- PR-AUC +- Confusion matrix + +## Notes + +The models are trained in an unsupervised way by removing the label column during training. The labels are only used for evaluation. diff --git a/Anomaly Detection/anomaly_detection.ipynb b/Anomaly Detection/anomaly_detection.ipynb new file mode 100644 index 0000000..0f03026 --- /dev/null +++ b/Anomaly Detection/anomaly_detection.ipynb @@ -0,0 +1,911 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7488e70f", + "metadata": {}, + "source": [ + "# Anomaly Detection in Credit Card Fraud Transactions\n", + "\n", + "This notebook presents a simple but strong anomaly detection workflow for credit card fraud detection.\n", + "\n", + "We compare three unsupervised anomaly detection methods:\n", + "\n", + "- Isolation Forest\n", + "- Local Outlier Factor\n", + "- One-Class SVM\n", + "\n", + "The dataset is highly imbalanced, so we evaluate models using precision, recall, F1-score, ROC-AUC, PR-AUC, and confusion matrices instead of relying only on accuracy.\n" + ] + }, + { + "cell_type": "markdown", + "id": "b272eb73", + "metadata": {}, + "source": [ + "## 1. Import Libraries" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "1ef5ceea", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import warnings\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from sklearn.model_selection import train_test_split\n", + "from sklearn.preprocessing import StandardScaler\n", + "from sklearn.ensemble import IsolationForest\n", + "from sklearn.neighbors import LocalOutlierFactor\n", + "from sklearn.svm import OneClassSVM\n", + "from sklearn.metrics import (\n", + " classification_report,\n", + " confusion_matrix,\n", + " roc_auc_score,\n", + " average_precision_score,\n", + " precision_score,\n", + " recall_score,\n", + " f1_score,\n", + ")\n", + "\n", + "RANDOM_STATE = 42\n", + "np.random.seed(RANDOM_STATE)\n" + ] + }, + { + "cell_type": "markdown", + "id": "60fccad2", + "metadata": {}, + "source": [ + "## 2. Load Dataset\n", + "\n", + "Download the dataset from Kaggle and place `creditcard.csv` in the same folder as this notebook:\n", + "\n", + "https://www.kaggle.com/datasets/mlg-ulb/creditcardfraud\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "82b0966b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dataset shape: (284807, 31)\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
TimeV1V2V3V4V5V6V7V8V9...V21V22V23V24V25V26V27V28AmountClass
00.0-1.359807-0.0727812.5363471.378155-0.3383210.4623880.2395990.0986980.363787...-0.0183070.277838-0.1104740.0669280.128539-0.1891150.133558-0.021053149.620
10.01.1918570.2661510.1664800.4481540.060018-0.082361-0.0788030.085102-0.255425...-0.225775-0.6386720.101288-0.3398460.1671700.125895-0.0089830.0147242.690
21.0-1.358354-1.3401631.7732090.379780-0.5031981.8004990.7914610.247676-1.514654...0.2479980.7716790.909412-0.689281-0.327642-0.139097-0.055353-0.059752378.660
31.0-0.966272-0.1852261.792993-0.863291-0.0103091.2472030.2376090.377436-1.387024...-0.1083000.005274-0.190321-1.1755750.647376-0.2219290.0627230.061458123.500
42.0-1.1582330.8777371.5487180.403034-0.4071930.0959210.592941-0.2705330.817739...-0.0094310.798278-0.1374580.141267-0.2060100.5022920.2194220.21515369.990
\n", + "

5 rows × 31 columns

\n", + "
" + ], + "text/plain": [ + " Time V1 V2 V3 V4 V5 V6 V7 \\\n", + "0 0.0 -1.359807 -0.072781 2.536347 1.378155 -0.338321 0.462388 0.239599 \n", + "1 0.0 1.191857 0.266151 0.166480 0.448154 0.060018 -0.082361 -0.078803 \n", + "2 1.0 -1.358354 -1.340163 1.773209 0.379780 -0.503198 1.800499 0.791461 \n", + "3 1.0 -0.966272 -0.185226 1.792993 -0.863291 -0.010309 1.247203 0.237609 \n", + "4 2.0 -1.158233 0.877737 1.548718 0.403034 -0.407193 0.095921 0.592941 \n", + "\n", + " V8 V9 ... V21 V22 V23 V24 V25 \\\n", + "0 0.098698 0.363787 ... -0.018307 0.277838 -0.110474 0.066928 0.128539 \n", + "1 0.085102 -0.255425 ... -0.225775 -0.638672 0.101288 -0.339846 0.167170 \n", + "2 0.247676 -1.514654 ... 0.247998 0.771679 0.909412 -0.689281 -0.327642 \n", + "3 0.377436 -1.387024 ... -0.108300 0.005274 -0.190321 -1.175575 0.647376 \n", + "4 -0.270533 0.817739 ... -0.009431 0.798278 -0.137458 0.141267 -0.206010 \n", + "\n", + " V26 V27 V28 Amount Class \n", + "0 -0.189115 0.133558 -0.021053 149.62 0 \n", + "1 0.125895 -0.008983 0.014724 2.69 0 \n", + "2 -0.139097 -0.055353 -0.059752 378.66 0 \n", + "3 -0.221929 0.062723 0.061458 123.50 0 \n", + "4 0.502292 0.219422 0.215153 69.99 0 \n", + "\n", + "[5 rows x 31 columns]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "DATA_PATH = \"dataset/creditcard.csv\"\n", + "\n", + "if not os.path.exists(DATA_PATH):\n", + " raise FileNotFoundError(\n", + " \"creditcard.csv was not found. Download it from Kaggle and place it in this folder.\"\n", + " )\n", + "\n", + "df = pd.read_csv(DATA_PATH)\n", + "print(\"Dataset shape:\", df.shape)\n", + "df.head()\n" + ] + }, + { + "cell_type": "markdown", + "id": "24e01823", + "metadata": {}, + "source": [ + "## 3. Basic Data Analysis" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "577b6f83", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Missing values: 0\n", + "Duplicate rows: 1081\n", + "Columns: ['Time', 'V1', 'V2', 'V3', 'V4', 'V5', 'V6', 'V7', 'V8', 'V9', 'V10', 'V11', 'V12', 'V13', 'V14', 'V15', 'V16', 'V17', 'V18', 'V19', 'V20', 'V21', 'V22', 'V23', 'V24', 'V25', 'V26', 'V27', 'V28', 'Amount', 'Class']\n" + ] + } + ], + "source": [ + "print(\"Missing values:\", df.isnull().sum().sum())\n", + "print(\"Duplicate rows:\", df.duplicated().sum())\n", + "print(\"Columns:\", list(df.columns))\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "f52b83fa", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
ClassCountPercentage
0Normal28431599.827251
1Fraud4920.172749
\n", + "
" + ], + "text/plain": [ + " Class Count Percentage\n", + "0 Normal 284315 99.827251\n", + "1 Fraud 492 0.172749" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "class_counts = df[\"Class\"].value_counts().sort_index()\n", + "class_ratio = df[\"Class\"].value_counts(normalize=True).sort_index() * 100\n", + "\n", + "summary = pd.DataFrame({\n", + " \"Class\": [\"Normal\", \"Fraud\"],\n", + " \"Count\": class_counts.values,\n", + " \"Percentage\": class_ratio.values,\n", + "})\n", + "summary\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "8d34cd81", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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9vXXX8f6OjOmAABAqhpzo72etA3CkiVL3BYFs2bNsgkTJrgVi7WzNwAAQKpqudGMKG10eeONN1qaNGmsWLFiVq9ePbcMsqaDN27cOHFqCgAAkBgtN8eOHbN8+fK5x9qBW91Uol24f/755/heDgAAIHnDjda3+e2339zjihUr2ujRo+3vv/+2t99+2woWLBjZ2gEAACR2t5TG3OzYscM97tOnjzVo0MAmTZpkGTJksPHjx8f3cgAAAMkbbu6///7g4xtuuMG2bNniFvArWrSo5cmTJ7K1AwAASOxuqegyZszoBhanTZs2oZcCAABInqngY8aMCa5po32mKlWqZEWKFLFvv/024TUCAABIynDz6aefWoUKFdzj6dOnB7ulFHqef/75hNQFAAAg6cPN3r17rUCBAu7xzJkz7Z577rGSJUtahw4dbM2aNQmvEQAAQFKGm/z589u6detcl5RWJ65bt25wrynG3QAAgFQ3W0r7SrVq1cqtaRMVFeVWJ5alS5faddddlxh1BAAASLxw07dvXytbtqxt27bNdUlptpSo1aZXr17xvRwAAEDyhhu5++67Y5xr165dJOoDAACQ9OFm3rx57ti9e7edO3cu7LWxY8cmrEYAAABJGW769etn/fv3typVqgTH3QAAAKTacKMNMrWHVNu2bROnRgAAAEk5Ffz06dNWvXr1hNwTAAAg5YSbRx55xCZPnpw4tQEAAEjqbqmTJ0/aO++8Y998842VL1/e0qdPH/b68OHDE1onAACApAs3q1evtooVK7rHa9euDXuNwcUAACDVhZsFCxYkTk0AAACSY8wNAACA7xbxW758uX3yySe2detWN3sq1GeffRapugEAACR+y82UKVOsRo0abmfwadOm2ZkzZ9zj+fPnW/bs2eNfAwAAgOQMNwMHDrQRI0bYV199ZRkyZLD//e9/tn79erdTeNGiRSNZNwAAgMQPN3/++ac1btzYPdaO4MeOHXOzpJ566ik3RRwAACBVhZtcuXLZkSNH3OPChQsHp4MfPHjQjh8/HvkaAgAAJOaA4ltvvdXmzp1r5cqVc11RTz75pBtvo3N16tSJ7+UAAACSN9y88cYbbpVi6d27t1uheOHChdayZUt74YUXIls7AACAxAw3//77r02fPt0aNGjgnqdJk8Z69OjhDgAAgFQ35iZdunT23//+106dOpV4NQIAAEjKAcU333yzrVy50iLh+++/tyZNmlihQoXcjKvPP/887PX27du786FH1apVw8ooaHXp0sXy5MljWbJksaZNm9r27dvDyhw4cMDatm3r1uHRoccaAB1KCxKqLrqGrtW1a9cYCxSuWbPGatasaZkyZXKDqfv372+BQCAi3wsAAJBMY246d+5s3bt3dwGicuXKLgyE0k7hcaVp5BUqVLCHHnrI7rrrrljLNGzY0MaNGxd8rrV1QnXr1s11lWlxwdy5c7u63XnnnbZixQpLmzatK9OmTRtX31mzZrnnnTp1cgFH75OzZ8+66e158+Z144f27dtn7dq1c8Hl9ddfd2UOHz5s9erVs9q1a7sVmjdu3OjCl75+3RMAAKQMUYE4Nj08/PDDNnLkSMuRI0fMi0RFuSCgfxUULqkiUVFuxePmzZsHzyk8qIUleouO59ChQy6QTJw40Vq3bu3O/fPPP1akSBGbOXOmGxukBQbLlCljS5Ysca1OosfVqlWzDRs2WKlSpezrr792gWjbtm2uFUkUlnT/3bt3W7Zs2WzUqFFuAPWuXbvc+j4yePBgF34UnOK6I7pCklqPVHddN1KK95oRsWsBKd2Wwf9vrS0Al5fDcfwMjXO31IQJE9wsqc2bN8c4Nm3aFPw30r799lvLly+flSxZ0jp27OjChketM9r+oX79+sFzCidly5a1RYsWueeLFy923wgv2Ii6tnQutIze4wUbUTBSl5fu4ZVRl5QXbLwyClNbtmw5b/11Df0wQg8AAJACuqW8Bp5ixYpZUmnUqJHdc8897p4KT5pqfvvtt7vAoZCxc+dO102VM2fOsPflz5/fvSb6V+EoOp0LLaP3hNI1de3QMsWLF49xH++1EiVKxPo1DBo0yPr165eg7wMAAEikMTdx7XqJFK+rSdSyUqVKFRd0ZsyY4dbVOR+vi+xC9Y5EGS/wXej7oq6sp59+OvhcLTfqNgMAACkg3Khr6GIBZ//+/ZZYChYs6MLN77//7p4XKFDAzWjSbKjQ1ht1XVWvXj1YRuNkotuzZ0+w5UVlli5dGva6rqkur9AyXitO6H0keqtPKLUwhXZlAQCAFBRu1L2isSrJRbOYNOhXIUc0W0srJGvrB20FITt27HD7XQ0ZMsQ918BhDTxatmyZ3XTTTe6cgozOeQFIZV5++WX3Xu/ac+bMcaFE9/DKPPfccy5MeTO2VEbjdKJ3VwEAgFQSbu69995Yx69cqqNHj9off/wRfK5xNatWrXKbc+ro27evmyKuwKFBuwoXWoOmRYsWrryCVocOHdxUbE0D13ueeeYZt+9V3bp1XZnSpUu76eQajDx69OjgVHDNjtJMKdGAZM2o0vTwoUOHutYnXUfv8UZjazq5wp1mUKkeaj0aOHCgvfjii0neXQcAACIQbhLjA/ynn35y68Z4vLEpWmNGU6+1aN7777/vpoMr4KjsRx99ZFmzZg2+Z8SIEW7lZLXcnDhxwm3eOX78+OAaNzJp0iS3KJ83q0oL/WmPLI/KahyP1vCpUaOGW6RPYWbYsGHBMgpSaiF6/PHH3dgfdYOpvqHjaQAAQCpa50b7SJ1v5hHijnVugIRjnRvg8nQ4juvcxLnl5ty5c5GqGwAAQMrZWwoAACAlI9wAAABfIdwAAIDLL9xUqlTJLWon/fv3t+PHjyd2vQAAABIv3Ghn7WPHjrnHWutF69MAAACkRHGaLVWxYkV76KGH7JZbbnH7KWn9lyuvvDLWslrUDgAAIEWHGy2K16dPH/vqq6/cYn5ff/21WzgvOr1GuAEAACk+3GibgilTpgQX85s3bx6L+QEAgNS/t5SwmB8AAPBVuJE///zTRo4c6QYaqytKm1M++eSTdvXVV0e+hgAAAIm5zs3s2bPdDtrLli2z8uXLW9myZW3p0qV2/fXXu40lAQAAUlXLTa9eveypp56ywYMHxzjfs2dPq1evXiTrBwAAkLgtN+qK6tChQ4zzDz/8sK1bty6+lwMAAEjecJM3b15btWpVjPM6ly9fvkjVCwAAIGm6pTp27GidOnWyTZs2WfXq1d2A4oULF9orr7xi3bt3v7RaAAAAJFe4eeGFFyxr1qz26quvWu/evd25QoUKWd++fa1r166RqhcAAEDShBu11GhAsY4jR464cwo7AAAAqXadGw+hBgAApPoBxQAAACkZ4QYAAPgK4QYAAFy+4ebMmTNWu3Zt27hxY+LVCAAAIKnCTfr06W3t2rVuxhQAAIAvuqUefPBBGzNmTOLUBgAAIKmngp8+fdree+89twN4lSpVLEuWLGGvDx8+PKF1AgAASLpwo26pSpUqucfRx97QXQUAAFJduFmwYEHi1AQAACA5p4L/8ccfNnv2bDtx4oR7HggEIlEfAACApA03+/btszp16ljJkiXtjjvusB07drjzjzzyCLuCAwCA1BdutGGmpoRv3brVMmfOHDzfunVrmzVrVqTrBwAAkLhjbubMmeO6o6666qqw89dee6399ddf8b0cAABA8rbcHDt2LKzFxrN3717LmDFjpOoFAACQNOHmtttus/fffz9s+ve5c+ds6NChbmsGAACAVNUtpRBTq1Yt++mnn9yCfj169LBff/3V9u/fbz/++GPi1BIAACCxWm7KlCljq1evtptuusnq1avnuqlatmxpK1eutKuvvjq+lwMAAEjelhspUKCA9evXL7I1AQAASK5wc+DAAbd55vr1692Ym9KlS9tDDz1kuXLlikSdAAAAkq5b6rvvvrMSJUrYa6+95kKOxtrosc7pNQAAgFTVcvP4449bq1atbNSoUZY2bVp37uzZs9a5c2f3mjbWBAAASDUtN3/++afbZsELNqLHTz/9tHsNAAAgOcU73FSqVMmNtYlO5ypWrBiva33//ffWpEkTK1SokBu78/nnn4e9rs04+/bt617PlCmTm4KuaeehTp06ZV26dLE8efJYlixZrGnTprZ9+/awMuo+a9u2rWXPnt0denzw4MGwMtpOQnXRNXStrl27uqnuodasWWM1a9Z0dSlcuLD179+fDUMBAEiN3VKa+u3Rh/6TTz7pdgWvWrWqO7dkyRJ78803bfDgwfG6uaaRV6hQwQ1Gvuuuu2K8PmTIEBs+fLiNHz/ebdQ5YMAAN/38t99+s6xZs7oy3bp1s+nTp9uUKVMsd+7crlXpzjvvtBUrVgRbl9q0aeMCj7f3VadOnVzA0fu8brXGjRtb3rx5beHChW5z0Hbt2rng8vrrr7syhw8fdvfWQoXLly+3jRs3Wvv27V0Y0j0BAEDKEBXQJ/hFpEmTxrWsXKyoyigoXFJFoqJs2rRp1rx5c/dc91KLjcJLz549g600+fPnt1deecUeffRRO3TokAskEydOdBt3yj///GNFihSxmTNnWoMGDVyLktbmUQC7+eabXRk9rlatmm3YsMFKlSplX3/9tQtE27Ztc/cUhSWFl927d1u2bNncGKPevXvbrl27gttMKMwp/Cg4qf5xoZCk1iPVXdeNlOK9ZkTsWkBKt2Vw4+SuAoBkENfP0Dh1S23evNk2bdrk/r3QoTKRouvt3LnT6tevHzynUKFuoUWLFrnnap05c+ZMWBmFk7JlywbLLF682H0jvGAjanHSudAyeo8XbETBSGFK9/DK6N6h+2epjMLUli1bzvt16Br6YYQeAAAgmbulihUrZklNwUbUUhNKz73dx1UmQ4YMljNnzhhlvPfr33z58sW4vs6Flol+H11T1w4tU7x48Rj38V7TVPjYDBo0iAUPAQBI6Yv4/f33324fKXXZaNPMUBqTE0nRu3vUXXWxLqDoZWIrH4kyXjfdheqjrizNJPOo5UbdZgAAIIWEm3Hjxtljjz3mWjU0gDf6h3+kwo22ePBaRQoWLBg8r0DltZiojGY0aTZUaOuNylSvXj1YRuNkotuzZ0/YdZYuXRr2uq6pLq/QMl4rTuh9JHqrTyh1Y4V2ZQEAgBQ2FfzFF190hwbzaKxJYo25UTePAsXcuXOD5xRktAqyF1wqV65s6dOnDyuzY8cOt5CgV0YDh1XXZcuWBcsoyOhcaBm9R+/1zJkzx4US3cMro6nrodPDVUbjdKJ3VwEAgFQUbo4fP2733nuvm0GVUEePHrVVq1a5QxSQ9FhrzqgVSDOlBg4c6GZRKXxo9lLmzJnd1G7RoOAOHTq4qdjz5s1zO5M/8MADVq5cOatbt64ro32vGjZsaB07dnSzpHTosWZHaaaUaECyZlRperiuoWs988wzrpw3Glv3VNhRHVQX1Ul1U5dTXGdKAQCAxBfvhKIw8cknn0Tk5j/99JPdcMMN7hAFBT1Wy5D06NHDBRxt7VClShU31ketJd4aNzJixAg3fVxbQtSoUcOFH61fE7qC8qRJk1zgUYjRUb58eTd93KOyM2bMsCuuuMJdQ9fSNYcNGxYsoyClFiJN+1ZdVCfVN3Q8DQAASCXr3ITSOjZq9Thx4oQLDOoWCqVF93B+rHMDJBzr3ACXp8NxXOcm3gOK1RUze/bsYJfOxWYcAQAAJKV4hxu1zIwdO9aNPQEAAEj1Y240qFbjUgAAAHwRbrRppreZJAAAQKrvltJ6MfPnz7evvvrKrr/++hgDij/77LNI1g8AACBxw02OHDmsZcuW8X0bAABAyt1+AQAAIKVK+DLDAAAAqbnlRns+XWg9m0juLwUAAJDo4UbbIYTSztnaj2nWrFn27LPPxrsCAAAAyRpuNBU8Nm+++abbKwoAAMAXY24aNWpkU6dOjdTlAAAAkjfcfPrpp5YrV65IXQ4AACBpuqVuuOGGsAHF2lR8586dtmfPHnvrrbcurRYAAADJFW6aN28e9jxNmjSWN29eq1Wrll133XWRqhcAAEDShJs+ffpc2p0AAACSAIv4AQCAy7PlRt1PF1q8T/T6v//+G4l6AQAAJG64mTZt2nlfW7Rokb3++utucDEAAECqCDfNmjWLcW7Dhg3Wu3dvmz59ut1///320ksvRbp+AAAAiT/m5p9//rGOHTta+fLlXTfUqlWrbMKECVa0aNFLuRwAAEDyhJtDhw5Zz5497ZprrrFff/3V5s2b51ptypYtG7kaAQAAJEW31JAhQ+yVV16xAgUK2IcffhhrNxUAAEByiwrEcRSwZktlypTJ6tata2nTpj1vuc8++yyS9fOdw4cPW/bs2V0rWLZs2SJ23eK9ZkTsWkBKt2Vw4+SuAoAU/Bka55abBx988KJTwQEAAJJbnMPN+PHjE7cmAAAAEcAKxQAAwFcINwAAwFcINwAAwFcINwAAwFcINwAAwFcINwAAwFcINwAAwFcINwAAwFcINwAAwFcINwAAwFcINwAAwFcINwAAwFcINwAAwFcINwAAwFdSdLjp27evRUVFhR0FChQIvh4IBFyZQoUKWaZMmaxWrVr266+/hl3j1KlT1qVLF8uTJ49lyZLFmjZtatu3bw8rc+DAAWvbtq1lz57dHXp88ODBsDJbt261Jk2auGvoWl27drXTp08n8ncAAAD4KtzI9ddfbzt27Agea9asCb42ZMgQGz58uL3xxhu2fPlyF3zq1atnR44cCZbp1q2bTZs2zaZMmWILFy60o0eP2p133mlnz54NlmnTpo2tWrXKZs2a5Q49VsDxqGzjxo3t2LFj7hq61tSpU6179+5J+J0AAABxkc5SuHTp0oW11oS22owcOdKef/55a9mypTs3YcIEy58/v02ePNkeffRRO3TokI0ZM8YmTpxodevWdWU++OADK1KkiH3zzTfWoEEDW79+vQs0S5YssZtvvtmVeffdd61atWr222+/WalSpWzOnDm2bt0627Ztm2slkldffdXat29vL7/8smXLli1JvycAACAVt9z8/vvvLlCUKFHC7r33Xtu0aZM7v3nzZtu5c6fVr18/WDZjxoxWs2ZNW7RokXu+YsUKO3PmTFgZXats2bLBMosXL3ZdUV6wkapVq7pzoWX0Hi/YiIKRurx0jwtRmcOHD4cdAADgMg03Chzvv/++zZ4927WmKMxUr17d9u3b5x6LWmpC6bn3mv7NkCGD5cyZ84Jl8uXLF+PeOhdaJvp9dE1d2ytzPoMGDQqO5dGhViMAAHCZhptGjRrZXXfdZeXKlXPdSjNmzAh2P3k0yDh6d1X0c9FFLxNb+UspE5vevXu77jHvUNcWAAC4TMNNdJqppKCjripvHE70lpPdu3cHW1lURjOaNBvqQmV27doV41579uwJKxP9Prqmuryit+hEp64yjckJPQAAQOJJVeFG41c0ALhgwYJuDI5Cx9y5c4OvK8h89913rutKKleubOnTpw8roxlXa9euDZbRwGG1qCxbtixYZunSpe5caBm9R+/1aJCxgovuAQAAUo4UPVvqmWeecWvLFC1a1LW2DBgwwA3IbdeunesO0jTvgQMH2rXXXusOPc6cObOb2i0a49KhQwc3ZTt37tyWK1cud02vm0tKly5tDRs2tI4dO9ro0aPduU6dOrnp4popJRqQXKZMGTc9fOjQobZ//353Hb2HlhgAAFKWFB1utNjefffdZ3v37rW8efO6WUyasl2sWDH3eo8ePezEiRPWuXNn102kAchqUcmaNWvwGiNGjHDTyVu1auXK1qlTx8aPH29p06YNlpk0aZJblM+bVaWF/rR2jkdlNd5H96lRo4ZbMFABatiwYUn6/QAAABcXFdCoWCQZtTypRUndXpFs9Sne6/8NtgYuB1sGN07uKgBIwZ+hqWrMDQAAwMUQbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8QbgAAgK8Qbi7BW2+9ZSVKlLArrrjCKleubD/88EPkfzIAAOCSEG7i6aOPPrJu3brZ888/bytXrrRbb73VGjVqZFu3br20nwAAAIgowk08DR8+3Dp06GCPPPKIlS5d2kaOHGlFihSxUaNGRfYnAwAALkm6S3vb5en06dO2YsUK69WrV9j5+vXr26JFi2J9z6lTp9zhOXTokPv38OHDEa3buVPHI3o9ICWL9O9PUirbZ3ZyVwFIEmv7NUi03/1AIHDBcoSbeNi7d6+dPXvW8ufPH3Zez3fu3BnrewYNGmT9+vWLcV6tPQAuTfaRfOeAy/n39MiRI5Y9e/bzvk64uQRRUVFhz5Ugo5/z9O7d255++ung83Pnztn+/fstd+7c530PUgf9BaGQum3bNsuWLVtyVwdALPg99Rd93irYFCpU6ILlCDfxkCdPHkubNm2MVprdu3fHaM3xZMyY0R2hcuTIEZ/bIoVTsCHcACkbv6f+caEWGw8DiuMhQ4YMbur33Llzw87refXq1eP/EwIAABFHy008qYupbdu2VqVKFatWrZq98847bhr4Y489FvmfDgAAiDfCTTy1bt3a9u3bZ/3797cdO3ZY2bJlbebMmVasWLH4f/eRqqm7sU+fPjG6HQGkHPyeXp6iAhebTwUAAJCKMOYGAAD4CuEGAAD4CuEGAAD4CuEGSGG+/fZbt8DjwYMHk7sqAMysffv21rx5c74XqQjhBr7/j5KCwuDBg8POf/7556wQDaSS39/oxx9//JHcVUMKR7iB711xxRX2yiuv2IEDByK6iSqAxNewYUO37EboUaJECX4fcUGEG/he3bp1rUCBAm4T0/OZOnWqXX/99W5NjOLFi9urr74a9rrODRgwwP0lqaW/O3bsaOPHj3dbaXz11VdWqlQpy5w5s91999127NgxmzBhgntPzpw5rUuXLm7DVc8HH3zgFoHMmjWrq1ebNm3cFh4AYtLvpH5PQo86derYE0884RZV1bY49erVc2WHDx9u5cqVsyxZsrh93zp37mxHjx4NXqtv375WsWLFsOuPHDnS/a569Luq6+p3W3sA9ujR46I7UCPlIdzA97Qf2MCBA+3111+37du3x3h9xYoV1qpVK7v33nttzZo17j+AL7zwggsvoYYOHeoWbVR5vS7Hjx+31157zaZMmWKzZs1y42VatmzpFnbUMXHiRLeK9aeffhrW6vPSSy/ZL7/84rrHNm/e7EITgLjTHxDp0qWzH3/80UaPHu3OpUmTxv0+rl271r0+f/58F07iQ3/YjB071saMGWMLFy50Gx1PmzaNH01qo0X8AL9q165doFmzZu5x1apVAw8//LB7PG3aNP0p5h63adMmUK9evbD3Pfvss4EyZcoEnxcrVizQvHnzsDLjxo1z1/jjjz+C5x599NFA5syZA0eOHAmea9CggTt/PsuWLXPX8d6zYMEC9/zAgQMJ/OqB1P/7mzZt2kCWLFmCx9133x2oWbNmoGLFihd9/8cffxzInTt38HmfPn0CFSpUCCszYsQI9/vtKViwYGDw4MHB52fOnAlcddVVwf+OIHWg5QaXDY270V9z69atCzu/fv16q1GjRtg5Pf/999/DupPUlRSduqKuvvrq4HPtDq8m7iuvvDLsXGi308qVK61Zs2Zuyw51TdWqVcud1x5lAMLVrl3bVq1aFTzUMnO+38cFCxa4LqrChQu7360HH3zQbZejruK4OHTokBvTo30DPWodiu1eSNkIN7hs3HbbbdagQQN77rnnws6rP10zMKKfi079+NGlT58+7LmuE9u5c+fOucf6j2z9+vVd+NHYm+XLlwebvBmkDFisv3fXXHNN8ChYsGCsv49//fWX3XHHHa7rWGPo1H385ptvutfOnDkT7LaK/rvtvQZ/YeNMXFY0JVwDCkuWLBk8V6ZMGde3HmrRokWujMbrRNKGDRts7969rh4a8Cg//fRTRO8BXI70e/Tvv/+6MTMKMfLxxx+HlcmbN6/t3Lkz7A8atQZ5NFlA4WnJkiXujyHRNRWUKlWqlKRfDxKGlhtcVjST4v7773eDiz3du3e3efPmuUG+GzdudF1Xb7zxhj3zzDMRv3/RokUtQ4YM7v6bNm2yL7/80t0XQMKoe1hBxPvd0mD+t99+O6yMuoD37NljQ4YMsT///NO17Hz99ddhZZ588kn3x4daVPXHiGZcsaBm6kO4wWVHYSK0aVp/kekvPM14UpP2iy++aP3790+UGUz6y1GzsD755BPXYqT/iA4bNizi9wEuN2qR1VRwja3T7/GkSZNiLP9QunRpe+utt1yoqVChgi1btizGHzH6Y0djdfT7r7E3GrvTokWLJP5qkFBRGlWc4KsAAACkELTcAAAAXyHcAAAAXyHcAAAAXyHcAAAAXyHcAAAAXyHcAAAAXyHcAAAAXyHcAAAAXyHcAEA0W7ZscXsPhe47BCD1INwAiBN92F/oSIztKpKC6t28efOwc9rUdMeOHW4Z/8RSvHjxC34/tQ8SgEvDruAA4kQf9p6PPvrI7cH122+/Bc9lypQprPyZM2csffr0qfK7q93gCxQokKj3WL58uZ09eza4C/1dd93lvp/ZsmVz57TBKoBLQ8sNgDjRh713ZM+e3bUueM9PnjxpOXLkcBuQqsXhiiuusA8++MD27dtn9913n1111VWWOXNmtyv7hx9+GHZdle/atav16NHDcuXK5a7Xt2/fsDJ6rh3VM2bMaIUKFXLlPbpPlSpV3AaHem+bNm1s9+7dYe//9ddfrXHjxi44qNytt97qdoXWdbUL/BdffBFsMfn2229j7Zb67rvv7KabbnJ1KFiwoPXq1cvtQh2fryP6Jqre90/lJV++fMGvQeExlL6Xuvf8+fODLT/aBFZlr7zySvd9Cd3tXg4dOmSdOnVy19XXfvvtt9svv/wSp583kJoRbgBETM+ePd0H/Pr1661BgwYu9FSuXNm++uorW7t2rfugbdu2rS1dujTsfQoYWbJkceeHDBnidmWfO3eue+3TTz+1ESNG2OjRo+3333+3zz//3IUkz+nTp92HvD609drmzZvDusj+/vtvu+2221zgUjBYsWKFPfzwwy6YaEfoVq1aWcOGDV3LlI7q1avH+Lp0jTvuuMNuvPFGd59Ro0bZmDFjbMCAAXH+OuLjkUcescmTJ9upU6eC57TLtQJM7dq1g+eGDh1q5cuXt59//tl69+5tTz31VPB+2hNZgW7nzp02c+ZM93VXqlTJ6tSpY/v37493nYBURbuCA0B8jBs3LpA9e/bg882bNwf0n5ORI0de9L133HFHoHv37sHnNWvWDNxyyy1hZW688cZAz5493eNXX301ULJkycDp06fjVLdly5a5uhw5csQ97927d6BEiRLnfX+7du0CzZo1CzvnfT0rV650z5977rlAqVKlAufOnQuWefPNNwNXXnll4OzZs3H6Oi5kwYIF7n4HDhxwz0+ePBnIlStX4KOPPgqWqVixYqBv377B58WKFQs0bNgw7DqtW7cONGrUyD2eN29eIFu2bO5aoa6++urA6NGjL1onIDWj5QZAxKh7KJTGlLz88suudSF37tyu+2TOnDm2devWsHJ6PZS6fbyupXvuucdOnDhh//nPf6xjx442bdq0sO6glStXWrNmzaxYsWKuy8kbiOvdQ11L6oZKyPgftURVq1bNdVV5atSoYUePHrXt27fH6euID3U/PfDAAzZ27Njg16AWo+iDtlWn6M9VV1FLjernfd+9Qy1b6pID/IwBxQAiRl0yoV599VXXpTRy5EjXlaTXu3Xr5rqSQkUPHgoR586dC85c0kBbdbd888031rlzZ9cdozEwuk79+vXdobE3GseiUKMuMe8e0Qc6Xwp18YQGG++cV9e4fB2X0jVVsWJFF54UctSdpAB3MV59dF+FK40hik7jowA/I9wASDQ//PCDa1VRK4T3gatxM6VLl47XdRRQmjZt6o7HH3/crrvuOluzZo0LGHv37rXBgwe7ECQ//fRT2HvVmqKxMOebvaVZSd6spfMpU6aMTZ06NSzkaIaTWooKFy5siUFhUC1h7777rht/E32wsCxZsiTGc31vRONrNN4mXbp0bvAxcDmhWwpAornmmmtci4uCgLpLHn30UfeBGx/jx493g3c1IHnTpk02ceJEF3bUiqEZVAon+uDXa19++aUbXBzqiSeesMOHD9u9997rgo/Cla7hTWPXB//q1avdcwUlhaDo1Fq0bds269Kli23YsMHNrurTp489/fTTliZN4v1nVK03Cm4KXy1atIjx+o8//ugGLm/cuNHefPNN++STT+zJJ590r9WtW9d1U2kNn9mzZ7sZYPo5/N///V+MAAj4DeEGQKJ54YUXXAuCuok0FkbTnKMvmHcx6kJR64XGuKgVZt68eTZ9+nQ3lkTdUAo/+lBX64qCwLBhw8Ler3KaJaXxJzVr1nSzt3Q9rxVH43hKlSrlWkl0PQWG6NQ6oxlHy5YtswoVKthjjz1mHTp0cEEhMWkavVpeNN1bs72i6969uxtbc8MNN7hQp25Afa9FLUyqs2aKaXZYyZIlXcBTyMmfP3+i1htIblEaVZzclQAAxKTWIrUsacE/hcRQOq/xSzoAhGPMDQCkMOoa05o7WiiwatWqMYINgAujWwoAUhh1jWlMkbqc3n777eSuDpDq0C0FAAB8hZYbAADgK4QbAADgK4QbAADgK4QbAADgK4QbAADgK4QbAADgK4QbAADgK4QbAABgfvL/AeK+tUYmBhC8AAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(6, 4))\n", + "plt.bar([\"Normal\", \"Fraud\"], class_counts.values)\n", + "plt.title(\"Class Distribution\")\n", + "plt.xlabel(\"Transaction Type\")\n", + "plt.ylabel(\"Number of Transactions\")\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "id": "d4fe0f01", + "metadata": {}, + "source": [ + "## 4. Prepare Features and Labels\n", + "\n", + "The models are trained without using the `Class` label. The label is used only for evaluation.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "1e5eb677", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Feature matrix: (284807, 30)\n", + "Labels: (284807,)\n" + ] + } + ], + "source": [ + "X = df.drop(columns=[\"Class\"])\n", + "y = df[\"Class\"].astype(int)\n", + "\n", + "print(\"Feature matrix:\", X.shape)\n", + "print(\"Labels:\", y.shape)\n" + ] + }, + { + "cell_type": "markdown", + "id": "04c66b7a", + "metadata": {}, + "source": [ + "## 5. Train-Test Split and Scaling\n", + "\n", + "We use stratified splitting to preserve the fraud ratio in both train and test sets.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "dabf3d56", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train shape: (199364, 30)\n", + "Test shape: (85443, 30)\n", + "Fraud ratio in train set: 0.0017254870488152324\n" + ] + } + ], + "source": [ + "X_train, X_test, y_train, y_test = train_test_split(\n", + " X,\n", + " y,\n", + " test_size=0.30,\n", + " random_state=RANDOM_STATE,\n", + " stratify=y,\n", + ")\n", + "\n", + "scaler = StandardScaler()\n", + "X_train_scaled = scaler.fit_transform(X_train)\n", + "X_test_scaled = scaler.transform(X_test)\n", + "\n", + "fraud_ratio = y_train.mean()\n", + "print(\"Train shape:\", X_train_scaled.shape)\n", + "print(\"Test shape:\", X_test_scaled.shape)\n", + "print(\"Fraud ratio in train set:\", fraud_ratio)\n" + ] + }, + { + "cell_type": "markdown", + "id": "fc12f29f", + "metadata": {}, + "source": [ + "## 6. Helper Function for Evaluation\n", + "\n", + "Scikit-learn anomaly detectors usually return:\n", + "\n", + "- `1` for normal samples\n", + "- `-1` for anomalies\n", + "\n", + "We convert this format into binary labels:\n", + "\n", + "- `0` = normal\n", + "- `1` = fraud/anomaly\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "b80b563c", + "metadata": {}, + "outputs": [], + "source": [ + "def convert_anomaly_labels(predictions):\n", + " \"\"\"Convert sklearn anomaly labels from {1, -1} to {0, 1}.\"\"\"\n", + " return np.where(predictions == -1, 1, 0)\n", + "\n", + "\n", + "def evaluate_model(model_name, y_true, y_pred, anomaly_score=None):\n", + " \"\"\"Evaluate an anomaly detection model and return key metrics.\"\"\"\n", + " print(f\"================ {model_name} ================\")\n", + " print(\"Confusion Matrix:\")\n", + " print(confusion_matrix(y_true, y_pred))\n", + " print(\"Classification Report:\")\n", + " print(classification_report(y_true, y_pred, digits=4))\n", + "\n", + " result = {\n", + " \"Model\": model_name,\n", + " \"Precision\": precision_score(y_true, y_pred, zero_division=0),\n", + " \"Recall\": recall_score(y_true, y_pred, zero_division=0),\n", + " \"F1-score\": f1_score(y_true, y_pred, zero_division=0),\n", + " }\n", + "\n", + " if anomaly_score is not None:\n", + " result[\"ROC-AUC\"] = roc_auc_score(y_true, anomaly_score)\n", + " result[\"PR-AUC\"] = average_precision_score(y_true, anomaly_score)\n", + " else:\n", + " result[\"ROC-AUC\"] = np.nan\n", + " result[\"PR-AUC\"] = np.nan\n", + "\n", + " return result\n" + ] + }, + { + "cell_type": "markdown", + "id": "d80add0c", + "metadata": {}, + "source": [ + "## 7. Model 1: Isolation Forest\n", + "\n", + "Isolation Forest is a strong baseline for anomaly detection. It isolates anomalies using random partitions.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "c582ca3c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================ Isolation Forest ================\n", + "Confusion Matrix:\n", + "[[85188 107]\n", + " [ 113 35]]\n", + "Classification Report:\n", + " precision recall f1-score support\n", + "\n", + " 0 0.9987 0.9987 0.9987 85295\n", + " 1 0.2465 0.2365 0.2414 148\n", + "\n", + " accuracy 0.9974 85443\n", + " macro avg 0.6226 0.6176 0.6200 85443\n", + "weighted avg 0.9974 0.9974 0.9974 85443\n", + "\n" + ] + } + ], + "source": [ + "iso_forest = IsolationForest(\n", + " n_estimators=200,\n", + " contamination=fraud_ratio,\n", + " random_state=RANDOM_STATE,\n", + " n_jobs=-1,\n", + ")\n", + "\n", + "iso_forest.fit(X_train_scaled)\n", + "\n", + "iso_pred_raw = iso_forest.predict(X_test_scaled)\n", + "iso_pred = convert_anomaly_labels(iso_pred_raw)\n", + "\n", + "# Higher score means more anomalous.\n", + "iso_score = -iso_forest.decision_function(X_test_scaled)\n", + "\n", + "results = []\n", + "results.append(evaluate_model(\"Isolation Forest\", y_test, iso_pred, iso_score))\n" + ] + }, + { + "cell_type": "markdown", + "id": "ff979e59", + "metadata": {}, + "source": [ + "## 8. Model 2: Local Outlier Factor\n", + "\n", + "Local Outlier Factor compares the local density of each point with the density of its neighbors.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "552adfcb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================ Local Outlier Factor ================\n", + "Confusion Matrix:\n", + "[[85158 137]\n", + " [ 148 0]]\n", + "Classification Report:\n", + " precision recall f1-score support\n", + "\n", + " 0 0.9983 0.9984 0.9983 85295\n", + " 1 0.0000 0.0000 0.0000 148\n", + "\n", + " accuracy 0.9967 85443\n", + " macro avg 0.4991 0.4992 0.4992 85443\n", + "weighted avg 0.9965 0.9967 0.9966 85443\n", + "\n" + ] + } + ], + "source": [ + "lof = LocalOutlierFactor(\n", + " n_neighbors=20,\n", + " contamination=fraud_ratio,\n", + " novelty=True,\n", + " n_jobs=-1,\n", + ")\n", + "\n", + "lof.fit(X_train_scaled)\n", + "\n", + "lof_pred_raw = lof.predict(X_test_scaled)\n", + "lof_pred = convert_anomaly_labels(lof_pred_raw)\n", + "\n", + "# Higher score means more anomalous.\n", + "lof_score = -lof.decision_function(X_test_scaled)\n", + "\n", + "results.append(evaluate_model(\"Local Outlier Factor\", y_test, lof_pred, lof_score))\n" + ] + }, + { + "cell_type": "markdown", + "id": "9691ad5a", + "metadata": {}, + "source": [ + "## 9. Model 3: One-Class SVM\n", + "\n", + "One-Class SVM can be useful, but it is computationally expensive on large datasets. To keep the notebook beginner-friendly and fast, we train it on a subset of normal training samples.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "dbc0c3dd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================ One-Class SVM ================\n", + "Confusion Matrix:\n", + "[[82728 2567]\n", + " [ 28 120]]\n", + "Classification Report:\n", + " precision recall f1-score support\n", + "\n", + " 0 0.9997 0.9699 0.9846 85295\n", + " 1 0.0447 0.8108 0.0847 148\n", + "\n", + " accuracy 0.9696 85443\n", + " macro avg 0.5222 0.8904 0.5346 85443\n", + "weighted avg 0.9980 0.9696 0.9830 85443\n", + "\n" + ] + } + ], + "source": [ + "normal_train_indices = np.where(y_train.values == 0)[0]\n", + "\n", + "# Limit training samples for faster execution.\n", + "max_ocsvm_samples = min(20000, len(normal_train_indices))\n", + "selected_indices = np.random.choice(\n", + " normal_train_indices,\n", + " size=max_ocsvm_samples,\n", + " replace=False,\n", + ")\n", + "\n", + "X_train_ocsvm = X_train_scaled[selected_indices]\n", + "\n", + "ocsvm = OneClassSVM(\n", + " kernel=\"rbf\",\n", + " gamma=\"scale\",\n", + " nu=float(fraud_ratio),\n", + ")\n", + "\n", + "ocsvm.fit(X_train_ocsvm)\n", + "\n", + "ocsvm_pred_raw = ocsvm.predict(X_test_scaled)\n", + "ocsvm_pred = convert_anomaly_labels(ocsvm_pred_raw)\n", + "\n", + "# Higher score means more anomalous.\n", + "ocsvm_score = -ocsvm.decision_function(X_test_scaled)\n", + "\n", + "results.append(evaluate_model(\"One-Class SVM\", y_test, ocsvm_pred, ocsvm_score))\n" + ] + }, + { + "cell_type": "markdown", + "id": "adec8721", + "metadata": {}, + "source": [ + "## 10. Compare All Models" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "66b1aa71", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
ModelPrecisionRecallF1-scoreROC-AUCPR-AUC
2One-Class SVM0.0446590.8108110.0846560.9362660.252087
0Isolation Forest0.2464790.2364860.2413790.9460390.137427
1Local Outlier Factor0.0000000.0000000.0000000.5376890.002871
\n", + "
" + ], + "text/plain": [ + " Model Precision Recall F1-score ROC-AUC PR-AUC\n", + "2 One-Class SVM 0.044659 0.810811 0.084656 0.936266 0.252087\n", + "0 Isolation Forest 0.246479 0.236486 0.241379 0.946039 0.137427\n", + "1 Local Outlier Factor 0.000000 0.000000 0.000000 0.537689 0.002871" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results_df = pd.DataFrame(results)\n", + "results_df = results_df.sort_values(by=\"PR-AUC\", ascending=False)\n", + "results_df\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "05e9f2a3", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAArwAAAG0CAYAAADQAfSzAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAUblJREFUeJzt3Qm4TeX7//H7mDWYMpYxZUpkzBClQlJfRaWEFJUopDKUEt8yNEgKpW9IZagQigwNJJqEFJVEZMiUsczrf32e/2/ta+999uHIcfY567xf17Ude+211x7XXvd6nvu5nwTP8zwDAAAAAipTvJ8AAAAAcDoR8AIAACDQCHgBAAAQaAS8AAAACDQCXgAAAAQaAS8AAAACjYAXAAAAgUbACwAAgEAj4AUAAECgEfACATN27FhLSEhwl88++yzR7Zpc8YILLnC3X3HFFSn62Nrmk08+edL3W7dunbuvnnty/Pnnn9arVy+7+OKL7ayzzrIcOXLYhRdeaF27drXVq1dbRvmM9b4Fnb6jKf09PZnH9vclXXLmzGmVK1e2oUOH2rFjx0LrtWvXLmK9bNmyWenSpe3hhx+2PXv2nNRjHj582AoXLuy2895778VcR4+n731SdJvWifbbb7/Z/fffb2XKlHGv5YwzzrCLLrrI+vTpYxs3bjyp5wmkN1ni/QQAnB5nn322vf7664mChfnz59uaNWvc7enR119/bdddd50L3HXwrl27tgswfv75Z3vrrbesZs2a9tdff1mQNW3a1BYvXmxFihSxoBsxYkRcH//888+3t99+2/1/69at9sorr9iDDz5omzdvtsGDB4fWUwD5ySefuP/v2rXLBavPP/+8ff/99zZnzpxkP94HH3zgTuhE++9NN92UIq9D27311lstf/78br+pUqWKC6pXrFhho0ePtg8//NCWLl2aIo8FpEUEvEBAtWzZ0h2ohw8fbrly5Qot10FUQeLJtjylBXrOzZo1cy26ixYtsqJFi4ZuU2B/7733JtkqFgT//POPe+0FChRwl4ygQoUKcX18BbK1atUKXW/SpImVK1fOXn75ZXvqqacsa9asbnmmTJki1rvmmmtci+rcuXNt7dq1VqpUqWQ9nvZPncBdfvnlLlD+448/Ir7n/4YeX8GuWnY//fRTy507d+i2K6+80rp06WJTp049pccA0jpSGoCAuu2229zf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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "metric_to_plot = \"PR-AUC\"\n", + "\n", + "plt.figure(figsize=(8, 4))\n", + "plt.bar(results_df[\"Model\"], results_df[metric_to_plot])\n", + "plt.title(f\"Model Comparison using {metric_to_plot}\")\n", + "plt.xlabel(\"Model\")\n", + "plt.ylabel(metric_to_plot)\n", + "plt.xticks(rotation=20)\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "id": "5deca4fb", + "metadata": {}, + "source": [ + "## 11. Conclusion\n", + "\n", + "This notebook introduced a complete anomaly detection workflow for a highly imbalanced fraud detection problem.\n", + "\n", + "Key points:\n", + "\n", + "- Accuracy is not enough for fraud detection because the dataset is highly imbalanced.\n", + "- Precision, recall, F1-score, ROC-AUC, and PR-AUC provide a better evaluation.\n", + "- Isolation Forest is usually a strong and efficient first baseline.\n", + "- Local Outlier Factor is useful for density-based anomaly detection.\n", + "- One-Class SVM can work well but may be slower on large datasets.\n", + "\n", + "Possible improvements:\n", + "\n", + "- Add threshold tuning using validation data.\n", + "- Compare with supervised models such as Random Forest or XGBoost.\n", + "- Add explainability methods to understand why a transaction is flagged as anomalous.\n", + "- Test deep learning methods such as Autoencoders or VAE-based anomaly detection.\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.10.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}