{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Explainable AI Projektaufgabe - Adult Census Income Datensatz"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Abhängigkeiten installieren"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-20T12:18:22.499947Z",
     "start_time": "2025-03-20T12:17:19.734970Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: scikit-learn in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (1.6.1)\n",
      "Requirement already satisfied: matplotlib in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (3.10.1)\n",
      "Requirement already satisfied: seaborn in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (0.13.2)\n",
      "Requirement already satisfied: pandas in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (2.2.3)\n",
      "Requirement already satisfied: numpy in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (2.0.2)\n",
      "Requirement already satisfied: lime in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (0.2.0.1)\n",
      "Requirement already satisfied: scipy>=1.6.0 in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from scikit-learn) (1.15.2)\n",
      "Requirement already satisfied: threadpoolctl>=3.1.0 in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from scikit-learn) (3.5.0)\n",
      "Requirement already satisfied: joblib>=1.2.0 in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from scikit-learn) (1.4.2)\n",
      "Requirement already satisfied: fonttools>=4.22.0 in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from matplotlib) (4.56.0)\n",
      "Requirement already satisfied: kiwisolver>=1.3.1 in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from matplotlib) (1.4.8)\n",
      "Requirement already satisfied: pillow>=8 in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from matplotlib) (11.1.0)\n",
      "Requirement already satisfied: contourpy>=1.0.1 in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from matplotlib) (1.3.1)\n",
      "Requirement already satisfied: pyparsing>=2.3.1 in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from matplotlib) (3.2.1)\n",
      "Requirement already satisfied: python-dateutil>=2.7 in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from matplotlib) (2.9.0.post0)\n",
      "Requirement already satisfied: cycler>=0.10 in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from matplotlib) (0.12.1)\n",
      "Requirement already satisfied: packaging>=20.0 in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from matplotlib) (24.2)\n",
      "Requirement already satisfied: pytz>=2020.1 in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from pandas) (2025.1)\n",
      "Requirement already satisfied: tzdata>=2022.7 in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from pandas) (2025.1)\n",
      "Requirement already satisfied: tqdm in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from lime) (4.67.1)\n",
      "Requirement already satisfied: scikit-image>=0.12 in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from lime) (0.25.2)\n",
      "Requirement already satisfied: six>=1.5 in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from python-dateutil>=2.7->matplotlib) (1.17.0)\n",
      "Requirement already satisfied: imageio!=2.35.0,>=2.33 in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from scikit-image>=0.12->lime) (2.37.0)\n",
      "Requirement already satisfied: lazy-loader>=0.4 in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from scikit-image>=0.12->lime) (0.4)\n",
      "Requirement already satisfied: networkx>=3.0 in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from scikit-image>=0.12->lime) (3.4.2)\n",
      "Requirement already satisfied: tifffile>=2022.8.12 in /Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages (from scikit-image>=0.12->lime) (2025.2.18)\n",
      "\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.0.1\u001b[0m\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n",
      "Note: you may need to restart the kernel to use updated packages.\n"
     ]
    }
   ],
   "source": [
    "%pip install scikit-learn matplotlib seaborn pandas numpy lime"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Daten einlesen und erkunden"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Einlesen der Daten aus einer csv Datei"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-20T12:18:27.087445Z",
     "start_time": "2025-03-20T12:18:23.163620Z"
    }
   },
   "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>age</th>\n",
       "      <th>workclass</th>\n",
       "      <th>fnlwgt</th>\n",
       "      <th>education</th>\n",
       "      <th>education.num</th>\n",
       "      <th>marital.status</th>\n",
       "      <th>occupation</th>\n",
       "      <th>relationship</th>\n",
       "      <th>race</th>\n",
       "      <th>sex</th>\n",
       "      <th>capital.gain</th>\n",
       "      <th>capital.loss</th>\n",
       "      <th>hours.per.week</th>\n",
       "      <th>native.country</th>\n",
       "      <th>income</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>90</td>\n",
       "      <td>?</td>\n",
       "      <td>77053</td>\n",
       "      <td>HS-grad</td>\n",
       "      <td>9</td>\n",
       "      <td>Widowed</td>\n",
       "      <td>?</td>\n",
       "      <td>Not-in-family</td>\n",
       "      <td>White</td>\n",
       "      <td>Female</td>\n",
       "      <td>0</td>\n",
       "      <td>4356</td>\n",
       "      <td>40</td>\n",
       "      <td>United-States</td>\n",
       "      <td>&lt;=50K</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>82</td>\n",
       "      <td>Private</td>\n",
       "      <td>132870</td>\n",
       "      <td>HS-grad</td>\n",
       "      <td>9</td>\n",
       "      <td>Widowed</td>\n",
       "      <td>Exec-managerial</td>\n",
       "      <td>Not-in-family</td>\n",
       "      <td>White</td>\n",
       "      <td>Female</td>\n",
       "      <td>0</td>\n",
       "      <td>4356</td>\n",
       "      <td>18</td>\n",
       "      <td>United-States</td>\n",
       "      <td>&lt;=50K</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>66</td>\n",
       "      <td>?</td>\n",
       "      <td>186061</td>\n",
       "      <td>Some-college</td>\n",
       "      <td>10</td>\n",
       "      <td>Widowed</td>\n",
       "      <td>?</td>\n",
       "      <td>Unmarried</td>\n",
       "      <td>Black</td>\n",
       "      <td>Female</td>\n",
       "      <td>0</td>\n",
       "      <td>4356</td>\n",
       "      <td>40</td>\n",
       "      <td>United-States</td>\n",
       "      <td>&lt;=50K</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>54</td>\n",
       "      <td>Private</td>\n",
       "      <td>140359</td>\n",
       "      <td>7th-8th</td>\n",
       "      <td>4</td>\n",
       "      <td>Divorced</td>\n",
       "      <td>Machine-op-inspct</td>\n",
       "      <td>Unmarried</td>\n",
       "      <td>White</td>\n",
       "      <td>Female</td>\n",
       "      <td>0</td>\n",
       "      <td>3900</td>\n",
       "      <td>40</td>\n",
       "      <td>United-States</td>\n",
       "      <td>&lt;=50K</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>41</td>\n",
       "      <td>Private</td>\n",
       "      <td>264663</td>\n",
       "      <td>Some-college</td>\n",
       "      <td>10</td>\n",
       "      <td>Separated</td>\n",
       "      <td>Prof-specialty</td>\n",
       "      <td>Own-child</td>\n",
       "      <td>White</td>\n",
       "      <td>Female</td>\n",
       "      <td>0</td>\n",
       "      <td>3900</td>\n",
       "      <td>40</td>\n",
       "      <td>United-States</td>\n",
       "      <td>&lt;=50K</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>34</td>\n",
       "      <td>Private</td>\n",
       "      <td>216864</td>\n",
       "      <td>HS-grad</td>\n",
       "      <td>9</td>\n",
       "      <td>Divorced</td>\n",
       "      <td>Other-service</td>\n",
       "      <td>Unmarried</td>\n",
       "      <td>White</td>\n",
       "      <td>Female</td>\n",
       "      <td>0</td>\n",
       "      <td>3770</td>\n",
       "      <td>45</td>\n",
       "      <td>United-States</td>\n",
       "      <td>&lt;=50K</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>38</td>\n",
       "      <td>Private</td>\n",
       "      <td>150601</td>\n",
       "      <td>10th</td>\n",
       "      <td>6</td>\n",
       "      <td>Separated</td>\n",
       "      <td>Adm-clerical</td>\n",
       "      <td>Unmarried</td>\n",
       "      <td>White</td>\n",
       "      <td>Male</td>\n",
       "      <td>0</td>\n",
       "      <td>3770</td>\n",
       "      <td>40</td>\n",
       "      <td>United-States</td>\n",
       "      <td>&lt;=50K</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>74</td>\n",
       "      <td>State-gov</td>\n",
       "      <td>88638</td>\n",
       "      <td>Doctorate</td>\n",
       "      <td>16</td>\n",
       "      <td>Never-married</td>\n",
       "      <td>Prof-specialty</td>\n",
       "      <td>Other-relative</td>\n",
       "      <td>White</td>\n",
       "      <td>Female</td>\n",
       "      <td>0</td>\n",
       "      <td>3683</td>\n",
       "      <td>20</td>\n",
       "      <td>United-States</td>\n",
       "      <td>&gt;50K</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>68</td>\n",
       "      <td>Federal-gov</td>\n",
       "      <td>422013</td>\n",
       "      <td>HS-grad</td>\n",
       "      <td>9</td>\n",
       "      <td>Divorced</td>\n",
       "      <td>Prof-specialty</td>\n",
       "      <td>Not-in-family</td>\n",
       "      <td>White</td>\n",
       "      <td>Female</td>\n",
       "      <td>0</td>\n",
       "      <td>3683</td>\n",
       "      <td>40</td>\n",
       "      <td>United-States</td>\n",
       "      <td>&lt;=50K</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>41</td>\n",
       "      <td>Private</td>\n",
       "      <td>70037</td>\n",
       "      <td>Some-college</td>\n",
       "      <td>10</td>\n",
       "      <td>Never-married</td>\n",
       "      <td>Craft-repair</td>\n",
       "      <td>Unmarried</td>\n",
       "      <td>White</td>\n",
       "      <td>Male</td>\n",
       "      <td>0</td>\n",
       "      <td>3004</td>\n",
       "      <td>60</td>\n",
       "      <td>?</td>\n",
       "      <td>&gt;50K</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   age    workclass  fnlwgt     education  education.num marital.status  \\\n",
       "0   90            ?   77053       HS-grad              9        Widowed   \n",
       "1   82      Private  132870       HS-grad              9        Widowed   \n",
       "2   66            ?  186061  Some-college             10        Widowed   \n",
       "3   54      Private  140359       7th-8th              4       Divorced   \n",
       "4   41      Private  264663  Some-college             10      Separated   \n",
       "5   34      Private  216864       HS-grad              9       Divorced   \n",
       "6   38      Private  150601          10th              6      Separated   \n",
       "7   74    State-gov   88638     Doctorate             16  Never-married   \n",
       "8   68  Federal-gov  422013       HS-grad              9       Divorced   \n",
       "9   41      Private   70037  Some-college             10  Never-married   \n",
       "\n",
       "          occupation    relationship   race     sex  capital.gain  \\\n",
       "0                  ?   Not-in-family  White  Female             0   \n",
       "1    Exec-managerial   Not-in-family  White  Female             0   \n",
       "2                  ?       Unmarried  Black  Female             0   \n",
       "3  Machine-op-inspct       Unmarried  White  Female             0   \n",
       "4     Prof-specialty       Own-child  White  Female             0   \n",
       "5      Other-service       Unmarried  White  Female             0   \n",
       "6       Adm-clerical       Unmarried  White    Male             0   \n",
       "7     Prof-specialty  Other-relative  White  Female             0   \n",
       "8     Prof-specialty   Not-in-family  White  Female             0   \n",
       "9       Craft-repair       Unmarried  White    Male             0   \n",
       "\n",
       "   capital.loss  hours.per.week native.country income  \n",
       "0          4356              40  United-States  <=50K  \n",
       "1          4356              18  United-States  <=50K  \n",
       "2          4356              40  United-States  <=50K  \n",
       "3          3900              40  United-States  <=50K  \n",
       "4          3900              40  United-States  <=50K  \n",
       "5          3770              45  United-States  <=50K  \n",
       "6          3770              40  United-States  <=50K  \n",
       "7          3683              20  United-States   >50K  \n",
       "8          3683              40  United-States  <=50K  \n",
       "9          3004              60              ?   >50K  "
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "from sklearn.model_selection import train_test_split, GridSearchCV\n",
    "from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix, classification_report\n",
    "from sklearn.preprocessing import LabelEncoder\n",
    "from sklearn.tree import plot_tree\n",
    "\n",
    "# Daten einlesen\n",
    "df = pd.read_csv(\"./sample_data/adult_census_income/adult.csv\")\n",
    "\n",
    "# Anzeigen der ersten Zeilen des Datensatzes\n",
    "df.head(10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Ausgabe verschiedenster Informationen über den Datensatz"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-20T12:18:33.125325Z",
     "start_time": "2025-03-20T12:18:33.112378Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Datensatzgröße: (32561, 15)\n"
     ]
    }
   ],
   "source": [
    "# Informationen über den Datensatz\n",
    "print(\"Datensatzgröße:\", df.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-20T12:18:33.306881Z",
     "start_time": "2025-03-20T12:18:33.232246Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Datentypen:\n",
      "<class 'pandas.core.frame.DataFrame'>\n",
      "RangeIndex: 32561 entries, 0 to 32560\n",
      "Data columns (total 15 columns):\n",
      " #   Column          Non-Null Count  Dtype \n",
      "---  ------          --------------  ----- \n",
      " 0   age             32561 non-null  int64 \n",
      " 1   workclass       32561 non-null  object\n",
      " 2   fnlwgt          32561 non-null  int64 \n",
      " 3   education       32561 non-null  object\n",
      " 4   education.num   32561 non-null  int64 \n",
      " 5   marital.status  32561 non-null  object\n",
      " 6   occupation      32561 non-null  object\n",
      " 7   relationship    32561 non-null  object\n",
      " 8   race            32561 non-null  object\n",
      " 9   sex             32561 non-null  object\n",
      " 10  capital.gain    32561 non-null  int64 \n",
      " 11  capital.loss    32561 non-null  int64 \n",
      " 12  hours.per.week  32561 non-null  int64 \n",
      " 13  native.country  32561 non-null  object\n",
      " 14  income          32561 non-null  object\n",
      "dtypes: int64(6), object(9)\n",
      "memory usage: 3.7+ MB\n"
     ]
    }
   ],
   "source": [
    "print(\"\\nDatentypen:\")\n",
    "#print(df.dtypes)\n",
    "df.info()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-20T12:18:34.030250Z",
     "start_time": "2025-03-20T12:18:33.974088Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Fehlende Werte:\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "age               0\n",
       "workclass         0\n",
       "fnlwgt            0\n",
       "education         0\n",
       "education.num     0\n",
       "marital.status    0\n",
       "occupation        0\n",
       "relationship      0\n",
       "race              0\n",
       "sex               0\n",
       "capital.gain      0\n",
       "capital.loss      0\n",
       "hours.per.week    0\n",
       "native.country    0\n",
       "income            0\n",
       "dtype: int64"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print(\"\\nFehlende Werte:\")\n",
    "df.isnull().sum()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-20T12:18:34.431804Z",
     "start_time": "2025-03-20T12:18:34.372676Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Statistische Zusammenfassung:\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>age</th>\n",
       "      <th>fnlwgt</th>\n",
       "      <th>education.num</th>\n",
       "      <th>capital.gain</th>\n",
       "      <th>capital.loss</th>\n",
       "      <th>hours.per.week</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>count</th>\n",
       "      <td>32561.000000</td>\n",
       "      <td>3.256100e+04</td>\n",
       "      <td>32561.000000</td>\n",
       "      <td>32561.000000</td>\n",
       "      <td>32561.000000</td>\n",
       "      <td>32561.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>mean</th>\n",
       "      <td>38.581647</td>\n",
       "      <td>1.897784e+05</td>\n",
       "      <td>10.080679</td>\n",
       "      <td>1077.648844</td>\n",
       "      <td>87.303830</td>\n",
       "      <td>40.437456</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>std</th>\n",
       "      <td>13.640433</td>\n",
       "      <td>1.055500e+05</td>\n",
       "      <td>2.572720</td>\n",
       "      <td>7385.292085</td>\n",
       "      <td>402.960219</td>\n",
       "      <td>12.347429</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>min</th>\n",
       "      <td>17.000000</td>\n",
       "      <td>1.228500e+04</td>\n",
       "      <td>1.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>1.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>25%</th>\n",
       "      <td>28.000000</td>\n",
       "      <td>1.178270e+05</td>\n",
       "      <td>9.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>40.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>50%</th>\n",
       "      <td>37.000000</td>\n",
       "      <td>1.783560e+05</td>\n",
       "      <td>10.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>40.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>75%</th>\n",
       "      <td>48.000000</td>\n",
       "      <td>2.370510e+05</td>\n",
       "      <td>12.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>45.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>max</th>\n",
       "      <td>90.000000</td>\n",
       "      <td>1.484705e+06</td>\n",
       "      <td>16.000000</td>\n",
       "      <td>99999.000000</td>\n",
       "      <td>4356.000000</td>\n",
       "      <td>99.000000</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                age        fnlwgt  education.num  capital.gain  capital.loss  \\\n",
       "count  32561.000000  3.256100e+04   32561.000000  32561.000000  32561.000000   \n",
       "mean      38.581647  1.897784e+05      10.080679   1077.648844     87.303830   \n",
       "std       13.640433  1.055500e+05       2.572720   7385.292085    402.960219   \n",
       "min       17.000000  1.228500e+04       1.000000      0.000000      0.000000   \n",
       "25%       28.000000  1.178270e+05       9.000000      0.000000      0.000000   \n",
       "50%       37.000000  1.783560e+05      10.000000      0.000000      0.000000   \n",
       "75%       48.000000  2.370510e+05      12.000000      0.000000      0.000000   \n",
       "max       90.000000  1.484705e+06      16.000000  99999.000000   4356.000000   \n",
       "\n",
       "       hours.per.week  \n",
       "count    32561.000000  \n",
       "mean        40.437456  \n",
       "std         12.347429  \n",
       "min          1.000000  \n",
       "25%         40.000000  \n",
       "50%         40.000000  \n",
       "75%         45.000000  \n",
       "max         99.000000  "
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print(\"\\nStatistische Zusammenfassung:\")\n",
    "df.describe()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-20T12:18:35.616458Z",
     "start_time": "2025-03-20T12:18:34.680587Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 21140 characters]",
      "text/plain": [
       "<Figure size 800x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Prozentuale Verteilung der Einkommensklassen:\n",
      "income\n",
      "<=50K    75.919044\n",
      ">50K     24.080956\n",
      "Name: proportion, dtype: float64\n"
     ]
    }
   ],
   "source": [
    "# Überprüfen der Verteilung der Zielklasse\n",
    "plt.figure(figsize=(8, 6))\n",
    "sns.countplot(x='income', data=df)\n",
    "plt.title('Verteilung der Einkommensklassen')\n",
    "plt.xlabel('Einkommen')\n",
    "plt.ylabel('Anzahl')\n",
    "plt.savefig('output/Verteilung_Einkommensklassen.png', dpi=300)\n",
    "plt.show()\n",
    "\n",
    "# Prozentuale Verteilung berechnen\n",
    "income_counts = df['income'].value_counts(normalize=True) * 100\n",
    "print(\"Prozentuale Verteilung der Einkommensklassen:\")\n",
    "print(income_counts)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-20T12:18:35.950107Z",
     "start_time": "2025-03-20T12:18:35.872971Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Spalte 'workclass' hat 1836 Einträge mit '?'\n",
      "Spalte 'occupation' hat 1843 Einträge mit '?'\n",
      "Spalte 'native.country' hat 583 Einträge mit '?'\n"
     ]
    }
   ],
   "source": [
    "# Überprüfen auf fehlende Werte oder '?'\n",
    "for col in df.columns:\n",
    "    missing_count = df[df[col] == '?'].shape[0]\n",
    "    if missing_count > 0:\n",
    "        print(f\"Spalte '{col}' hat {missing_count} Einträge mit '?'\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Datenvorverarbeitung"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Leere Werte durch häufigsten Wert der Spalte ersetzen\n",
    "\n",
    "So stellen wir sicher, dass die Daten vollständig sind und die Daten so nah als möglich an den realen Daten liegen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-20T12:18:36.357635Z",
     "start_time": "2025-03-20T12:18:36.201432Z"
    }
   },
   "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>age</th>\n",
       "      <th>workclass</th>\n",
       "      <th>fnlwgt</th>\n",
       "      <th>education</th>\n",
       "      <th>education.num</th>\n",
       "      <th>marital.status</th>\n",
       "      <th>occupation</th>\n",
       "      <th>relationship</th>\n",
       "      <th>race</th>\n",
       "      <th>sex</th>\n",
       "      <th>capital.gain</th>\n",
       "      <th>capital.loss</th>\n",
       "      <th>hours.per.week</th>\n",
       "      <th>native.country</th>\n",
       "      <th>income</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>90</td>\n",
       "      <td>Private</td>\n",
       "      <td>77053</td>\n",
       "      <td>HS-grad</td>\n",
       "      <td>9</td>\n",
       "      <td>Widowed</td>\n",
       "      <td>Prof-specialty</td>\n",
       "      <td>Not-in-family</td>\n",
       "      <td>White</td>\n",
       "      <td>Female</td>\n",
       "      <td>0</td>\n",
       "      <td>4356</td>\n",
       "      <td>40</td>\n",
       "      <td>United-States</td>\n",
       "      <td>&lt;=50K</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>82</td>\n",
       "      <td>Private</td>\n",
       "      <td>132870</td>\n",
       "      <td>HS-grad</td>\n",
       "      <td>9</td>\n",
       "      <td>Widowed</td>\n",
       "      <td>Exec-managerial</td>\n",
       "      <td>Not-in-family</td>\n",
       "      <td>White</td>\n",
       "      <td>Female</td>\n",
       "      <td>0</td>\n",
       "      <td>4356</td>\n",
       "      <td>18</td>\n",
       "      <td>United-States</td>\n",
       "      <td>&lt;=50K</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>66</td>\n",
       "      <td>Private</td>\n",
       "      <td>186061</td>\n",
       "      <td>Some-college</td>\n",
       "      <td>10</td>\n",
       "      <td>Widowed</td>\n",
       "      <td>Prof-specialty</td>\n",
       "      <td>Unmarried</td>\n",
       "      <td>Black</td>\n",
       "      <td>Female</td>\n",
       "      <td>0</td>\n",
       "      <td>4356</td>\n",
       "      <td>40</td>\n",
       "      <td>United-States</td>\n",
       "      <td>&lt;=50K</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>54</td>\n",
       "      <td>Private</td>\n",
       "      <td>140359</td>\n",
       "      <td>7th-8th</td>\n",
       "      <td>4</td>\n",
       "      <td>Divorced</td>\n",
       "      <td>Machine-op-inspct</td>\n",
       "      <td>Unmarried</td>\n",
       "      <td>White</td>\n",
       "      <td>Female</td>\n",
       "      <td>0</td>\n",
       "      <td>3900</td>\n",
       "      <td>40</td>\n",
       "      <td>United-States</td>\n",
       "      <td>&lt;=50K</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>41</td>\n",
       "      <td>Private</td>\n",
       "      <td>264663</td>\n",
       "      <td>Some-college</td>\n",
       "      <td>10</td>\n",
       "      <td>Separated</td>\n",
       "      <td>Prof-specialty</td>\n",
       "      <td>Own-child</td>\n",
       "      <td>White</td>\n",
       "      <td>Female</td>\n",
       "      <td>0</td>\n",
       "      <td>3900</td>\n",
       "      <td>40</td>\n",
       "      <td>United-States</td>\n",
       "      <td>&lt;=50K</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>34</td>\n",
       "      <td>Private</td>\n",
       "      <td>216864</td>\n",
       "      <td>HS-grad</td>\n",
       "      <td>9</td>\n",
       "      <td>Divorced</td>\n",
       "      <td>Other-service</td>\n",
       "      <td>Unmarried</td>\n",
       "      <td>White</td>\n",
       "      <td>Female</td>\n",
       "      <td>0</td>\n",
       "      <td>3770</td>\n",
       "      <td>45</td>\n",
       "      <td>United-States</td>\n",
       "      <td>&lt;=50K</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>38</td>\n",
       "      <td>Private</td>\n",
       "      <td>150601</td>\n",
       "      <td>10th</td>\n",
       "      <td>6</td>\n",
       "      <td>Separated</td>\n",
       "      <td>Adm-clerical</td>\n",
       "      <td>Unmarried</td>\n",
       "      <td>White</td>\n",
       "      <td>Male</td>\n",
       "      <td>0</td>\n",
       "      <td>3770</td>\n",
       "      <td>40</td>\n",
       "      <td>United-States</td>\n",
       "      <td>&lt;=50K</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>74</td>\n",
       "      <td>State-gov</td>\n",
       "      <td>88638</td>\n",
       "      <td>Doctorate</td>\n",
       "      <td>16</td>\n",
       "      <td>Never-married</td>\n",
       "      <td>Prof-specialty</td>\n",
       "      <td>Other-relative</td>\n",
       "      <td>White</td>\n",
       "      <td>Female</td>\n",
       "      <td>0</td>\n",
       "      <td>3683</td>\n",
       "      <td>20</td>\n",
       "      <td>United-States</td>\n",
       "      <td>&gt;50K</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>68</td>\n",
       "      <td>Federal-gov</td>\n",
       "      <td>422013</td>\n",
       "      <td>HS-grad</td>\n",
       "      <td>9</td>\n",
       "      <td>Divorced</td>\n",
       "      <td>Prof-specialty</td>\n",
       "      <td>Not-in-family</td>\n",
       "      <td>White</td>\n",
       "      <td>Female</td>\n",
       "      <td>0</td>\n",
       "      <td>3683</td>\n",
       "      <td>40</td>\n",
       "      <td>United-States</td>\n",
       "      <td>&lt;=50K</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>41</td>\n",
       "      <td>Private</td>\n",
       "      <td>70037</td>\n",
       "      <td>Some-college</td>\n",
       "      <td>10</td>\n",
       "      <td>Never-married</td>\n",
       "      <td>Craft-repair</td>\n",
       "      <td>Unmarried</td>\n",
       "      <td>White</td>\n",
       "      <td>Male</td>\n",
       "      <td>0</td>\n",
       "      <td>3004</td>\n",
       "      <td>60</td>\n",
       "      <td>United-States</td>\n",
       "      <td>&gt;50K</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   age    workclass  fnlwgt     education  education.num marital.status  \\\n",
       "0   90      Private   77053       HS-grad              9        Widowed   \n",
       "1   82      Private  132870       HS-grad              9        Widowed   \n",
       "2   66      Private  186061  Some-college             10        Widowed   \n",
       "3   54      Private  140359       7th-8th              4       Divorced   \n",
       "4   41      Private  264663  Some-college             10      Separated   \n",
       "5   34      Private  216864       HS-grad              9       Divorced   \n",
       "6   38      Private  150601          10th              6      Separated   \n",
       "7   74    State-gov   88638     Doctorate             16  Never-married   \n",
       "8   68  Federal-gov  422013       HS-grad              9       Divorced   \n",
       "9   41      Private   70037  Some-college             10  Never-married   \n",
       "\n",
       "          occupation    relationship   race     sex  capital.gain  \\\n",
       "0     Prof-specialty   Not-in-family  White  Female             0   \n",
       "1    Exec-managerial   Not-in-family  White  Female             0   \n",
       "2     Prof-specialty       Unmarried  Black  Female             0   \n",
       "3  Machine-op-inspct       Unmarried  White  Female             0   \n",
       "4     Prof-specialty       Own-child  White  Female             0   \n",
       "5      Other-service       Unmarried  White  Female             0   \n",
       "6       Adm-clerical       Unmarried  White    Male             0   \n",
       "7     Prof-specialty  Other-relative  White  Female             0   \n",
       "8     Prof-specialty   Not-in-family  White  Female             0   \n",
       "9       Craft-repair       Unmarried  White    Male             0   \n",
       "\n",
       "   capital.loss  hours.per.week native.country income  \n",
       "0          4356              40  United-States  <=50K  \n",
       "1          4356              18  United-States  <=50K  \n",
       "2          4356              40  United-States  <=50K  \n",
       "3          3900              40  United-States  <=50K  \n",
       "4          3900              40  United-States  <=50K  \n",
       "5          3770              45  United-States  <=50K  \n",
       "6          3770              40  United-States  <=50K  \n",
       "7          3683              20  United-States   >50K  \n",
       "8          3683              40  United-States  <=50K  \n",
       "9          3004              60  United-States   >50K  "
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Ersetzen von '?' durch NaN und dann durch den häufigsten Wert\n",
    "df_clean = df.copy()\n",
    "\n",
    "for col in df_clean.columns:\n",
    "    if df_clean[col].dtype == 'object':\n",
    "        # Ersetze '?' durch NaN\n",
    "        df_clean[col] = df_clean[col].replace('?', np.nan)\n",
    "        \n",
    "        # Ersetze NaN durch den häufigsten Wert\n",
    "        most_frequent = df_clean[col].mode()[0]\n",
    "        df_clean[col] = df_clean[col].fillna(most_frequent)\n",
    "\n",
    "df_clean.head(10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Umwandlung von Kategorischen Daten in Numerische\n",
    "\n",
    "**Label Encoding**\n",
    "- Wandelt Kategorien in Zahlen um => Jede Kategorie bekommt eindeutige Zahl\n",
    "- Income => *('>50K', '≤50K') in 0 und 1*\n",
    "\n",
    " \n",
    "**One-Hot Encoding**\n",
    "- Kategorisch geordnete Daten in Binärformat darstellen\n",
    "- Für jede Kategorie werden neue Spalten erstellt, wobei 1 bedeutet, dass die Kategorie vorhanden ist, und 0 bedeutet, dass sie nicht vorhanden ist\n",
    "- Z.B. education => *education_Bachelors  education_Masters  education_PhD*\n",
    "\n",
    "\n",
    "**Zweck:**\n",
    "- ML-Algorithmen arbeiten mit mathematischen Operationen\n",
    "- Können nur mit Zahlen rechnen, nicht mit Text\n",
    "- Benötigen numerische Eingaben für Berechnungen"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-20T12:18:36.714586Z",
     "start_time": "2025-03-20T12:18:36.582665Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Kategorische Spalten: ['workclass', 'education', 'marital.status', 'occupation', 'relationship', 'race', 'sex', 'native.country', 'income']\n",
      "\n",
      "Label Encoding für 'income':\n",
      "<=50K -> 0\n",
      ">50K -> 1\n",
      "\n",
      "Neue Spalten durch One-Hot Encoding:\n",
      "['age', 'fnlwgt', 'education.num', 'capital.gain', 'capital.loss', 'hours.per.week', 'income', 'income_encoded', 'workclass_Federal-gov', 'workclass_Local-gov']\n",
      "\n",
      "Datensatz nach Vorverarbeitung: (32561, 107)\n"
     ]
    }
   ],
   "source": [
    "# Kategorische Variablen in numerische umwandeln\n",
    "categorical_cols = df_clean.select_dtypes(include=['object']).columns\n",
    "print(\"Kategorische Spalten:\", categorical_cols.tolist())\n",
    "\n",
    "# Label Encoding für die Zielvariable\n",
    "label_encoder = LabelEncoder()\n",
    "df_clean['income_encoded'] = label_encoder.fit_transform(df_clean['income'])\n",
    "print(\"\\nLabel Encoding für 'income':\")\n",
    "for i, label in enumerate(label_encoder.classes_):\n",
    "    print(f\"{label} -> {i}\")\n",
    "\n",
    "# One-Hot Encoding für kategorische Variablen (außer der Zielvariable)\n",
    "categorical_cols = categorical_cols.drop('income')\n",
    "df_encoded = pd.get_dummies(df_clean, columns=categorical_cols, drop_first=False)\n",
    "\n",
    "\n",
    "print(\"\\nNeue Spalten durch One-Hot Encoding:\")\n",
    "print(df_encoded.columns[:10].tolist())\n",
    "\n",
    "print(\"\\nDatensatz nach Vorverarbeitung:\", df_encoded.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Daten aufteilen und Modell trainieren"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Datensatz aufteilen in Test und Trainingsdaten\n",
    "\n",
    "**In den Testdaten wird das Ergebnis *(income, income_encoded)* entfernt**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-20T12:18:37.018743Z",
     "start_time": "2025-03-20T12:18:36.962517Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Trainingsdaten: 26048 Beispiele\n",
      "Testdaten: 6513 Beispiele\n",
      "Features: 105 Merkmale\n"
     ]
    }
   ],
   "source": [
    "# Features und Zielwerte definieren\n",
    "X = df_encoded.drop(['income', 'income_encoded'], axis=1)\n",
    "y = df_encoded['income_encoded']\n",
    "\n",
    "\n",
    "# Daten in Trainings- und Testdaten aufteilen\n",
    "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)\n",
    "\n",
    "print(f\"Trainingsdaten: {X_train.shape[0]} Beispiele\")\n",
    "print(f\"Testdaten: {X_test.shape[0]} Beispiele\")\n",
    "print(f\"Features: {X_train.shape[1]} Merkmale\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Instanz des Random Forest Classifier erstellen\n",
    "\n",
    "1. Trainieren mit Trainingsdaten\n",
    "2. Vorhersagen für Testdaten\n",
    "3. Ausgabe der Modellgüte durch Vergleich von Testdaten und Vorhersagen"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-20T12:18:45.888131Z",
     "start_time": "2025-03-20T12:18:37.243261Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Accuracy: 0.8531\n",
      "Precision: 0.7313\n",
      "Recall: 0.6161\n",
      "F1 Score: 0.6687\n"
     ]
    }
   ],
   "source": [
    "# Random Forest Modell trainieren\n",
    "rf_model = RandomForestClassifier(n_estimators=100, random_state=42)\n",
    "rf_model.fit(X_train, y_train)\n",
    "\n",
    "# Vorhersagen für die Testdaten\n",
    "y_pred = rf_model.predict(X_test)\n",
    "\n",
    "# Modellgüte evaluieren\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 = f1_score(y_test, y_pred)\n",
    "\n",
    "print(f\"Accuracy: {accuracy:.4f}\")\n",
    "print(f\"Precision: {precision:.4f}\")\n",
    "print(f\"Recall: {recall:.4f}\")\n",
    "print(f\"F1 Score: {f1:.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Berechnung und Darstellung der Konfusionsmatrix"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 21032 characters]",
      "text/plain": [
       "<Figure size 800x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Klassifikationsbericht:\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "       <=50K       0.88      0.93      0.91      4945\n",
      "        >50K       0.73      0.62      0.67      1568\n",
      "\n",
      "    accuracy                           0.85      6513\n",
      "   macro avg       0.81      0.77      0.79      6513\n",
      "weighted avg       0.85      0.85      0.85      6513\n",
      "\n"
     ]
    }
   ],
   "source": [
    "\n",
    "# Confusion Matrix\n",
    "cm = confusion_matrix(y_test, y_pred)\n",
    "plt.figure(figsize=(8, 6))\n",
    "sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', cbar=False,\n",
    "            xticklabels=['<=50K', '>50K'],\n",
    "            yticklabels=['<=50K', '>50K'])\n",
    "plt.xlabel('Vorhergesagt')\n",
    "plt.ylabel('Tatsächlich')\n",
    "plt.title('Confusion Matrix')\n",
    "plt.savefig('output/Confusions_Matrix.png', dpi=300)\n",
    "plt.show()\n",
    "\n",
    "# Klassifikationsbericht\n",
    "print(\"\\nKlassifikationsbericht:\")\n",
    "print(classification_report(y_test, y_pred, target_names=['<=50K', '>50K']))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Ausgabe eines Entscheidungsbaums des Random Forest zur Veranschaulichung"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-20T12:18:52.630675Z",
     "start_time": "2025-03-20T12:18:46.231962Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 279440 characters]",
      "text/plain": [
       "<Figure size 2500x1200 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Visualisierung eines einzelnen Entscheidungsbaums aus dem Random Forest\n",
    "plt.figure(figsize=(25, 12))\n",
    "tree_to_plot = rf_model.estimators_[0]  # Ersten Baum aus dem Forest auswählen\n",
    "plot_tree(tree_to_plot, \n",
    "          feature_names=X_train.columns,\n",
    "          class_names=['<=50K', '>50K'],\n",
    "          filled=True,\n",
    "          rounded=True,\n",
    "          fontsize=10,\n",
    "          max_depth=3)\n",
    "plt.savefig('output/Random_Forest_Tree_Example.png', dpi=300)\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Random Forest Feature Importance"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Visualisierung Feature Importance vom Random Forest"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-20T12:18:54.946339Z",
     "start_time": "2025-03-20T12:18:52.770982Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 56676 characters]",
      "text/plain": [
       "<Figure size 1200x800 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Feature Importance\n",
    "feature_importance = pd.DataFrame({\n",
    "    'Feature': X_train.columns,\n",
    "    'Importance': rf_model.feature_importances_\n",
    "}).sort_values('Importance', ascending=False)\n",
    "\n",
    "plt.figure(figsize=(12, 8))\n",
    "sns.barplot(x='Importance', y='Feature', data=feature_importance.head(15))\n",
    "plt.title('Feature Importance')\n",
    "plt.tight_layout()\n",
    "plt.savefig('output/Feature_Importance.png', dpi=300)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Hyperparameter-Tuning"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Evaluation optimaler Hyperparameter\n",
    "\n",
    "1. Durch ausprobieren verschiedener Parameter aus *param_grid*\n",
    "2. Random Forest mit besten Parametern in *best_rf_model* speichern"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-20T12:21:30.012732Z",
     "start_time": "2025-03-20T12:18:54.965340Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Beste Parameter:\n",
      "{'max_depth': None, 'min_samples_leaf': 2, 'min_samples_split': 2, 'n_estimators': 100}\n",
      "\n",
      "Bestes Modell:\n",
      "Accuracy: 0.8595\n",
      "Precision: 0.7585\n",
      "Recall: 0.6110\n",
      "F1 Score: 0.6768\n"
     ]
    }
   ],
   "source": [
    "# Hyperparameter-Grid definieren\n",
    "param_grid = {\n",
    "    'n_estimators': [50, 100],\n",
    "    'max_depth': [None, 10, 20],\n",
    "    'min_samples_split': [2, 5],\n",
    "    'min_samples_leaf': [1, 2]\n",
    "}\n",
    "\n",
    "# GridSearchCV\n",
    "grid_search = GridSearchCV(RandomForestClassifier(random_state=42), param_grid, cv=3, scoring='accuracy')\n",
    "grid_search.fit(X_train, y_train)\n",
    "\n",
    "# Beste Parameter\n",
    "print(\"Beste Parameter:\")\n",
    "print(grid_search.best_params_)\n",
    "\n",
    "# Bestes Modell\n",
    "best_rf_model = grid_search.best_estimator_\n",
    "\n",
    "# Vorhersagen mit dem besten Modell\n",
    "y_pred_best = best_rf_model.predict(X_test)\n",
    "\n",
    "# Modellgüte evaluieren\n",
    "accuracy_best = accuracy_score(y_test, y_pred_best)\n",
    "precision_best = precision_score(y_test, y_pred_best)\n",
    "recall_best = recall_score(y_test, y_pred_best)\n",
    "f1_best = f1_score(y_test, y_pred_best)\n",
    "\n",
    "print(f\"\\nBestes Modell:\")\n",
    "print(f\"Accuracy: {accuracy_best:.4f}\")\n",
    "print(f\"Precision: {precision_best:.4f}\")\n",
    "print(f\"Recall: {recall_best:.4f}\")\n",
    "print(f\"F1 Score: {f1_best:.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7. LIME für Erklärbarkeit"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Initialisierung des LimeExplainer \n",
    "\n",
    "1. Initialisieren des LimeExplainer mit Trainingsdaten\n",
    "2. Auswahl eines zufälligen Datenbeispiels"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-20T12:21:34.627276Z",
     "start_time": "2025-03-20T12:21:30.211876Z"
    }
   },
   "outputs": [],
   "source": [
    "# LIME für Erklärbarkeit\n",
    "from lime import lime_tabular\n",
    "import random\n",
    "\n",
    "# Erstelle einen LIME-Erklärer\n",
    "lime_explainer = lime_tabular.LimeTabularExplainer(\n",
    "    X_train.values,\n",
    "    feature_names=X_train.columns,\n",
    "    class_names=['<=50K', '>50K'],\n",
    "    mode='classification',\n",
    "    random_state=42\n",
    ")\n",
    "\n",
    "# Wähle ein zufälliges Beispiel aus den Testdaten\n",
    "random_idx = random.randint(0, len(X_test) - 1)\n",
    "instance_df = X_test.iloc[random_idx:random_idx+1]\n",
    "instance = instance_df.values[0]  # Für LIME benötigen wir das Array\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### LIME-Erklärung erstellen für Datenbeispiel\n",
    "\n",
    "1. Berechnung der LIME-Erklärung\n",
    "2. Ausgabe der Resultierenden Feature Importance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-20T12:21:35.766182Z",
     "start_time": "2025-03-20T12:21:34.636343Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/Users/nick.starzmann/.pyenv/versions/3.10.13/lib/python3.10/site-packages/sklearn/utils/validation.py:2739: UserWarning: X does not have valid feature names, but RandomForestClassifier was fitted with feature names\n",
      "  warnings.warn(\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 93228 characters]",
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "LIME Feature-Wichtigkeiten Analyse:\n",
      "--------------------------------------------------\n",
      "Random Forest Vorhersage für diese Instanz: 0.2749\n",
      "\n",
      "Feature-Einflüsse:\n",
      "capital.gain > 0.00: +0.4438\n",
      "native.country_Greece <= 0.00: +0.1138\n",
      "education.num > 12.00: +0.1044\n",
      "marital.status_Married-civ-spouse <= 0.00: -0.0963\n",
      "capital.loss <= 0.00: -0.0891\n",
      "native.country_Thailand <= 0.00: -0.0723\n",
      "native.country_Haiti <= 0.00: -0.0699\n",
      "relationship_Husband <= 0.00: -0.0684\n",
      "native.country_Iran <= 0.00: -0.0679\n",
      "relationship_Wife <= 0.00: -0.0665\n",
      "native.country_Poland <= 0.00: -0.0627\n",
      "education_Prof-school <= 0.00: -0.0619\n",
      "occupation_Exec-managerial <= 0.00: -0.0614\n",
      "education_Doctorate <= 0.00: -0.0614\n",
      "native.country_El-Salvador <= 0.00: -0.0571\n",
      "native.country_Italy <= 0.00: +0.0560\n",
      "native.country_Cambodia <= 0.00: -0.0537\n",
      "education_Masters <= 0.00: -0.0524\n",
      "marital.status_Married-AF-spouse <= 0.00: +0.0376\n",
      "native.country_Ecuador <= 0.00: -0.0372\n"
     ]
    }
   ],
   "source": [
    "# Erkläre die Vorhersage mit LIME\n",
    "\n",
    "def analyze_lime_feature_importance(instance, rf_model, lime_explainer, num_features=5):\n",
    "    \"\"\"\n",
    "    Analysiert die Feature-Wichtigkeiten der LIME-Erklärung.\n",
    "    \"\"\"\n",
    "    # LIME-Erklärung generieren\n",
    "    exp = lime_explainer.explain_instance(\n",
    "        instance,\n",
    "        rf_model.predict_proba,\n",
    "        num_features=num_features\n",
    "    )\n",
    "    \n",
    "    # Random Forest Vorhersage (nur zur Information)\n",
    "    feature_names = rf_model.feature_names_in_\n",
    "    instance_df = pd.DataFrame([instance], columns=feature_names)\n",
    "    rf_prediction = rf_model.predict_proba(instance_df)[0, 1]\n",
    "    \n",
    "    # Feature-Wichtigkeiten aus LIME extrahieren\n",
    "    feature_importance = exp.as_list()\n",
    "    \n",
    "    # Visualisierung der Feature-Wichtigkeiten\n",
    "    plt.figure(figsize=(10, 6))\n",
    "    \n",
    "    # Features und ihre Wichtigkeiten trennen\n",
    "    features, importances = zip(*feature_importance)\n",
    "    \n",
    "    # Balkendiagramm erstellen\n",
    "    colors = ['red' if imp < 0 else 'green' for imp in importances]\n",
    "    y_pos = np.arange(len(features))\n",
    "    \n",
    "    plt.barh(y_pos, importances, color=colors)\n",
    "    plt.yticks(y_pos, features)\n",
    "    \n",
    "    plt.xlabel('Feature-Einfluss')\n",
    "    plt.title('LIME Feature-Wichtigkeiten')\n",
    "    \n",
    "    # Vertikale Linie bei 0 für bessere Lesbarkeit\n",
    "    plt.axvline(x=0, color='black', linestyle='-', alpha=0.3)\n",
    "    \n",
    "    plt.grid(alpha=0.3)\n",
    "    plt.tight_layout()\n",
    "    plt.savefig('output/lime_feature_importance.png', dpi=300)\n",
    "    plt.show()\n",
    "    \n",
    "    # Ausgabe der Ergebnisse\n",
    "    print(\"\\nLIME Feature-Wichtigkeiten Analyse:\")\n",
    "    print(\"-\" * 50)\n",
    "    print(f\"Random Forest Vorhersage für diese Instanz: {rf_prediction:.4f}\")\n",
    "    print(\"\\nFeature-Einflüsse:\")\n",
    "    for feature, importance in feature_importance:\n",
    "        print(f\"{feature}: {importance:+.4f}\")\n",
    "    \n",
    "    return {\n",
    "        'rf_prediction': rf_prediction,\n",
    "        'feature_importance': dict(feature_importance)\n",
    "    }\n",
    "\n",
    "# Analysiere wie gut LIME die RF-Vorhersage erklärt\n",
    "importance_analysis = analyze_lime_feature_importance(\n",
    "    instance=instance,\n",
    "    rf_model=best_rf_model,\n",
    "    lime_explainer=lime_explainer,\n",
    "    num_features=20\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Visualisierung des Einfluss einzelner Features auf die Vorhersage\n",
    "\n",
    "Berechnung der Feature Importance für Veränderte Werte um nachzuvollziehen, wie diese die Vorhersage beeinflussen.\n",
    "\n",
    "Es fehlt:\n",
    "\n",
    "- Zufälligkeit der Störungen/Perturbationen/Variationen,\n",
    "- Gewichtung nach Nähe zum ursprünglichen Wert,\n",
    "- Lineare Modellanpassung,\n",
    "- Perbutation mehrerer Features zeitgleich"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 46288 characters]",
      "text/plain": [
       "<Figure size 800x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 42880 characters]",
      "text/plain": [
       "<Figure size 800x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# Wählen wir zwei Features zur Variation:\n",
    "less_important_feature_education = \"education.num\"\n",
    "less_important_feature_fnlwgt = \"fnlwgt\" \n",
    "\n",
    "# Festlegen derRange für die Variation der zwei Features\n",
    "education_range = np.linspace(instance_df[less_important_feature_education].values[0] - 10, instance_df[less_important_feature_education].values[0] + 10, 50)\n",
    "fnlwgt_range = np.linspace(instance_df[less_important_feature_fnlwgt].values[0] - 100000, instance_df[less_important_feature_fnlwgt].values[0] + 100000, 50)\n",
    "\n",
    "# Erstellen von Instanzen für LIME\n",
    "instances_education = pd.DataFrame([instance] * len(education_range), columns=X_train.columns)\n",
    "instances_fnlwgt = pd.DataFrame([instance] * len(fnlwgt_range), columns=X_train.columns)\n",
    "\n",
    "# Ändern der Feature-Werte in den Instanzen\n",
    "instances_education[less_important_feature_education] = education_range\n",
    "instances_fnlwgt[less_important_feature_fnlwgt] = fnlwgt_range\n",
    "\n",
    "# Vorhersagen mit dem Modell (Wahrscheinlichkeiten)\n",
    "instances_education[\"prediction\"] = best_rf_model.predict_proba(instances_education)[:, 1]\n",
    "instances_fnlwgt[\"prediction\"] = best_rf_model.predict_proba(instances_fnlwgt)[:, 1]\n",
    "\n",
    "# Bestimmen der y-Achsen-Grenzen (min/max für alle Vorhersagen)\n",
    "y_min = min(instances_education[\"prediction\"].min(), instances_fnlwgt[\"prediction\"].min())\n",
    "y_max = max(instances_education[\"prediction\"].max(), instances_fnlwgt[\"prediction\"].max())\n",
    "\n",
    "# Visualisierung der Variation von 'education-num' (moderater Einfluss)\n",
    "plt.figure(figsize=(8,5))\n",
    "plt.plot(education_range, instances_education[\"prediction\"], label=\"Moderater Einfluss auf die Vorhersage\", color='green')\n",
    "plt.axvline(instance_df[less_important_feature_education].values[0], color=\"red\", linestyle=\"dashed\", label=\"Originalwert\")\n",
    "plt.xlabel(\"Bildungsniveau (education-num)\")\n",
    "plt.ylabel(\"Vorhersage (0 = <=50K, 1 = >50K)\")\n",
    "plt.title(f\"Einfluss von {less_important_feature_education} auf die Vorhersage\")\n",
    "plt.ylim([y_min, y_max])  # Einheitliche y-Achse\n",
    "plt.legend()\n",
    "plt.show()\n",
    "\n",
    "# Visualisierung der Variation von 'fnlwgt' (wenig Einfluss)\n",
    "plt.figure(figsize=(8,5))\n",
    "plt.plot(fnlwgt_range, instances_fnlwgt[\"prediction\"], label=\"Wenig Einfluss auf die Vorhersage\", color='orange')\n",
    "plt.axvline(instance_df[less_important_feature_fnlwgt].values[0], color=\"red\", linestyle=\"dashed\", label=\"Originalwert\")\n",
    "plt.xlabel(\"Finales Gewicht (fnlwgt)\")\n",
    "plt.ylabel(\"Vorhersage (0 = <=50K, 1 = >50K)\")\n",
    "plt.title(f\"Einfluss von {less_important_feature_fnlwgt} auf die Vorhersage\")\n",
    "plt.ylim([y_min, y_max])  # Einheitliche y-Achse\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Approximation der LIME-Funktionswiese \"von Hand\"\n",
    "\n",
    "Manuelle Perbutation und lineare Regression zur Berechnung des Einflusses der Features\n",
    "\n",
    "Ergänzt:\n",
    "- Zufälligkeit der Störungen/Perturbationen/Variationen,\n",
    "- Gewichtung nach Nähe zum ursprünglichen Wert,\n",
    "- Lineare Modellanpassung,\n",
    "- Perbutation mehrerer Features zeitgleich"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 44172 characters]",
      "text/plain": [
       "<Figure size 800x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.linear_model import LinearRegression\n",
    "from sklearn.metrics.pairwise import euclidean_distances\n",
    "\n",
    "# Anzahl der zu erzeugenden Perturbationen\n",
    "num_samples = 500  \n",
    "\n",
    "# Die Originalinstanz, die wir erklären wollen\n",
    "original_instance = instance_df.iloc[0].copy()\n",
    "\n",
    "# Erstelle perturbierte Instanzen durch zufällige Variation aller Features\n",
    "perturbed_instances = pd.DataFrame(\n",
    "    np.random.normal(loc=original_instance, scale=1.0, size=(num_samples, len(original_instance))),\n",
    "    columns=original_instance.index\n",
    ")\n",
    "\n",
    "# Vorhersagen für die perturbierten Instanzen mit dem Random-Forest-Modell\n",
    "perturbed_instances[\"prediction\"] = best_rf_model.predict_proba(perturbed_instances)[:, 1]\n",
    "\n",
    "# Berechnung der Gewichte nach Distanz zur Originalinstanz\n",
    "distances = euclidean_distances(perturbed_instances.drop(columns=[\"prediction\"]), [original_instance])\n",
    "kernel_width = np.sqrt(len(original_instance))  # Kernel-Bandbreite\n",
    "weights = np.exp(- (distances ** 2) / (2 * (kernel_width ** 2)))\n",
    "\n",
    "# Gewichtete lokale lineare Regression zum Erklären der Vorhersage\n",
    "lin_reg = LinearRegression()\n",
    "lin_reg.fit(perturbed_instances.drop(columns=[\"prediction\"]), perturbed_instances[\"prediction\"], sample_weight=weights.flatten())\n",
    "\n",
    "# Anzeige der Feature-Wichtigkeiten\n",
    "feature_importances = pd.Series(lin_reg.coef_, index=original_instance.index).sort_values(key=abs, ascending=False)\n",
    "\n",
    "# Visualisierung der wichtigsten Features\n",
    "plt.figure(figsize=(8, 5))\n",
    "feature_importances[:10].plot(kind=\"barh\", color=\"skyblue\")\n",
    "plt.xlabel(\"Einfluss auf die Vorhersage\")\n",
    "plt.title(\"Erklärungsmodell (Nachbildung von LIME)\")\n",
    "plt.gca().invert_yaxis()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Berechnung Konsistenz LIME Erklärungen\n",
    "\n",
    "1. Berechnung von 100 Erklärungen auf *gleichem Datenpunkt*\n",
    "2. Varianz der resultierenden Feature Importance Werte berechnen"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Varianz der Feature-Wichtigkeiten:\n",
      "capital.gain > 0.00: 0.000075\n",
      "education.num > 12.00: 0.000010\n",
      "capital.loss <= 0.00: 0.000015\n",
      "marital.status_Married-civ-spouse <= 0.00: 0.000009\n",
      "education_Preschool <= 0.00: 0.000000\n",
      "education_Doctorate <= 0.00: 0.000012\n",
      "relationship_Wife <= 0.00: 0.000009\n",
      "relationship_Husband <= 0.00: 0.000003\n",
      "education_Prof-school <= 0.00: 0.000071\n",
      "native.country_Hong <= 0.00: 0.003236\n",
      "marital.status_Married-AF-spouse <= 0.00: 0.000000\n",
      "native.country_Ecuador <= 0.00: 0.002583\n",
      "education_Masters <= 0.00: 0.000009\n",
      "occupation_Exec-managerial <= 0.00: 0.000006\n",
      "native.country_South <= 0.00: 0.002072\n",
      "native.country_El-Salvador <= 0.00: 0.001839\n",
      "native.country_Laos <= 0.00: 0.001499\n",
      "native.country_Peru <= 0.00: 0.001286\n",
      "native.country_Thailand <= 0.00: 0.000615\n",
      "native.country_Yugoslavia <= 0.00: 0.000134\n"
     ]
    }
   ],
   "source": [
    "def predict_fn(x):\n",
    "    # Konvertiere das NumPy-Array zurück in ein DataFrame mit den richtigen Feature-Namen\n",
    "    df = pd.DataFrame(x, columns=X_train.columns)\n",
    "    return best_rf_model.predict_proba(df)\n",
    "\n",
    "# Speichere mehrere Erklärungen\n",
    "explanations = []\n",
    "for i in range(100):\n",
    "    exp = lime_explainer.explain_instance(\n",
    "        instance, \n",
    "        predict_fn,\n",
    "        num_features=20\n",
    "    )\n",
    "    explanations.append(exp.as_list())\n",
    "\n",
    "# Berechne die Varianz der Feature-Wichtigkeiten\n",
    "feature_variances = {}\n",
    "for i in range(len(explanations[0])):\n",
    "    feature = explanations[0][i][0]\n",
    "    values = [exp[i][1] for exp in explanations if len(exp) > i and exp[i][0] == feature]\n",
    "    feature_variances[feature] = np.var(values)\n",
    "\n",
    "print(\"Varianz der Feature-Wichtigkeiten:\")\n",
    "for feature, variance in feature_variances.items():\n",
    "    print(f\"{feature}: {variance:.6f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Ausgabe der 5 Features mit höchster Varianz als Diagramm"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 63140 characters]",
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Erstelle nur das farbcodierte Stabilitätsdiagramm für die Top-5 Features\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "from matplotlib.patches import Patch\n",
    "\n",
    "# Anzahl der anzuzeigenden Features (Top-N mit höchster Varianz)\n",
    "num_features_to_show = 5  # Top-5 Features\n",
    "\n",
    "# Sortiere die Features nach Varianz (absteigend)\n",
    "sorted_features = sorted(feature_variances.items(), key=lambda x: x[1], reverse=True)\n",
    "# Beschränke auf die Top-N Features\n",
    "sorted_features = sorted_features[:num_features_to_show]\n",
    "features = [item[0] for item in sorted_features]\n",
    "variances = [item[1] for item in sorted_features]\n",
    "\n",
    "# Definiere Schwellenwerte für die Farbkodierung\n",
    "low_threshold = 0.0001\n",
    "medium_threshold = 0.001\n",
    "\n",
    "# Farbkodierung basierend auf Varianzwerten\n",
    "colors = []\n",
    "for v in variances:\n",
    "    if v < low_threshold:\n",
    "        colors.append('green')  # Niedrige Varianz - sehr stabil\n",
    "    elif v < medium_threshold:\n",
    "        colors.append('orange')  # Mittlere Varianz - mäßig stabil\n",
    "    else:\n",
    "        colors.append('red')    # Hohe Varianz - instabil\n",
    "\n",
    "# Erstelle das Balkendiagramm mit Farbkodierung\n",
    "plt.figure(figsize=(10, 6))\n",
    "bars = plt.barh(features, variances, color=colors)\n",
    "\n",
    "# Füge Werte am Ende der Balken hinzu\n",
    "for i, v in enumerate(variances):\n",
    "    plt.text(v + 0.00001, i, f\"{v:.6f}\", va='center')\n",
    "\n",
    "# Füge eine Legende hinzu\n",
    "legend_elements = [\n",
    "    Patch(facecolor='green', label='Sehr stabil (< 0.0001)'),\n",
    "    Patch(facecolor='orange', label='Mäßig stabil (< 0.001)'),\n",
    "    Patch(facecolor='red', label='Instabil (≥ 0.001)')\n",
    "]\n",
    "plt.legend(handles=legend_elements, loc='lower right')\n",
    "\n",
    "plt.xlabel('Varianz')\n",
    "plt.title('Stabilität der Top-5 LIME-Features')\n",
    "plt.grid(axis='x', linestyle='--', alpha=0.7)\n",
    "plt.tight_layout()\n",
    "\n",
    "# Speichere die Abbildung\n",
    "plt.savefig('output/Lime_Varianz_Features.png', dpi=300)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8. Regelbasierte Erklärungen"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Initialisierung DecisionTreeClassifier\n",
    "\n",
    "**Vorghehen**\n",
    "1. DecisionTreeClassifier mit **Trainingsdaten** und **Vorhersagen von RF** erstellen (**Da Globales Verfahren ganze Daten und Vorhersagen**)\n",
    "2. Genauigkeit berechnen\n",
    "\n",
    "**Ziel: Random Forest nachbilden**\n",
    "- Random Forest nachbilden\n",
    "- Je näher, desto besser kann der RF vom Surrogate-Modell erklärt werden\n",
    "\n",
    "**Genauigkeit**\n",
    "- Wie nah sind die Vorhersagen des Surrogate-Modell an denen des Random Forest\n",
    "- Berechnung: Mittelwert über alle Vorhersagen, ob sie gleich sind"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Genauigkeit des Surrogate-Modells: 0.9070\n"
     ]
    }
   ],
   "source": [
    "from sklearn.tree import DecisionTreeClassifier, export_text\n",
    "\n",
    "# Random Forest-Vorhersagen verwenden als Ziel\n",
    "rf_predictions = rf_model.predict(X_train)\n",
    "\n",
    "# Einfachen Entscheidungsbaum auf die Vorhersagen des Random Forests trainieren\n",
    "surrogate_tree = DecisionTreeClassifier(max_depth=5)\n",
    "surrogate_tree.fit(X_train, rf_predictions)\n",
    "\n",
    "# Evaluieren, wie gut der Baum den Random Forest approximiert\n",
    "surrogate_predictions = surrogate_tree.predict(X_test)\n",
    "rf_test_predictions = rf_model.predict(X_test)\n",
    "surrogate_accuracy = np.mean(surrogate_predictions == rf_test_predictions)\n",
    "print(f\"Genauigkeit des Surrogate-Modells: {surrogate_accuracy:.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Ausgabe eines generierten DecisionTree"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "|--- marital.status_Married-civ-spouse <= 0.50\n",
      "|   |--- capital.gain <= 7073.50\n",
      "|   |   |--- education.num <= 12.50\n",
      "|   |   |   |--- capital.loss <= 2218.50\n",
      "|   |   |   |   |--- hours.per.week <= 40.50\n",
      "|   |   |   |   |   |--- Einkommen ≤ 50K\n",
      "|   |   |   |   |--- hours.per.week >  40.50\n",
      "|   |   |   |   |   |--- Einkommen ≤ 50K\n",
      "|   |   |   |--- capital.loss >  2218.50\n",
      "|   |   |   |   |--- fnlwgt <= 125450.50\n",
      "|   |   |   |   |   |--- Einkommen > 50K\n",
      "|   |   |   |   |--- fnlwgt >  125450.50\n",
      "|   |   |   |   |   |--- Einkommen ≤ 50K\n",
      "|   |   |--- education.num >  12.50\n",
      "|   |   |   |--- hours.per.week <= 43.50\n",
      "|   |   |   |   |--- capital.loss <= 2365.50\n",
      "|   |   |   |   |   |--- Einkommen ≤ 50K\n",
      "|   |   |   |   |--- capital.loss >  2365.50\n",
      "|   |   |   |   |   |--- Einkommen > 50K\n",
      "|   |   |   |--- hours.per.week >  43.50\n",
      "|   |   |   |   |--- age <= 27.50\n",
      "|   |   |   |   |   |--- Einkommen ≤ 50K\n",
      "|   |   |   |   |--- age >  27.50\n",
      "|   |   |   |   |   |--- Einkommen ≤ 50K\n",
      "|   |--- capital.gain >  7073.50\n",
      "|   |   |--- age <= 21.00\n",
      "|   |   |   |--- Einkommen ≤ 50K\n",
      "|   |   |--- age >  21.00\n",
      "|   |   |   |--- capital.gain <= 8296.00\n",
      "|   |   |   |   |--- capital.gain <= 7436.50\n",
      "|   |   |   |   |   |--- Einkommen > 50K\n",
      "|   |   |   |   |--- capital.gain >  7436.50\n",
      "|   |   |   |   |   |--- Einkommen ≤ 50K\n",
      "|   |   |   |--- capital.gain >  8296.00\n",
      "|   |   |   |   |--- capital.gain <= 30961.50\n",
      "|   |   |   |   |   |--- Einkommen > 50K\n",
      "|   |   |   |   |--- capital.gain >  30961.50\n",
      "|   |   |   |   |   |--- Einkommen > 50K\n",
      "|--- marital.status_Married-civ-spouse >  0.50\n",
      "|   |--- education.num <= 12.50\n",
      "|   |   |--- capital.gain <= 5095.50\n",
      "|   |   |   |--- education.num <= 7.50\n",
      "|   |   |   |   |--- capital.loss <= 1791.50\n",
      "|   |   |   |   |   |--- Einkommen ≤ 50K\n",
      "|   |   |   |   |--- capital.loss >  1791.50\n",
      "|   |   |   |   |   |--- Einkommen ≤ 50K\n",
      "|   |   |   |--- education.num >  7.50\n",
      "|   |   |   |   |--- capital.loss <= 1782.50\n",
      "|   |   |   |   |   |--- Einkommen ≤ 50K\n",
      "|   |   |   |   |--- capital.loss >  1782.50\n",
      "|   |   |   |   |   |--- Einkommen > 50K\n",
      "|   |   |--- capital.gain >  5095.50\n",
      "|   |   |   |--- age <= 60.50\n",
      "|   |   |   |   |--- education.num <= 1.50\n",
      "|   |   |   |   |   |--- Einkommen ≤ 50K\n",
      "|   |   |   |   |--- education.num >  1.50\n",
      "|   |   |   |   |   |--- Einkommen > 50K\n",
      "|   |   |   |--- age >  60.50\n",
      "|   |   |   |   |--- hours.per.week <= 8.00\n",
      "|   |   |   |   |   |--- Einkommen ≤ 50K\n",
      "|   |   |   |   |--- hours.per.week >  8.00\n",
      "|   |   |   |   |   |--- Einkommen > 50K\n",
      "|   |--- education.num >  12.50\n",
      "|   |   |--- capital.gain <= 5095.50\n",
      "|   |   |   |--- capital.loss <= 1794.00\n",
      "|   |   |   |   |--- hours.per.week <= 34.50\n",
      "|   |   |   |   |   |--- Einkommen ≤ 50K\n",
      "|   |   |   |   |--- hours.per.week >  34.50\n",
      "|   |   |   |   |   |--- Einkommen > 50K\n",
      "|   |   |   |--- capital.loss >  1794.00\n",
      "|   |   |   |   |--- native.country_Iran <= 0.50\n",
      "|   |   |   |   |   |--- Einkommen > 50K\n",
      "|   |   |   |   |--- native.country_Iran >  0.50\n",
      "|   |   |   |   |   |--- Einkommen ≤ 50K\n",
      "|   |   |--- capital.gain >  5095.50\n",
      "|   |   |   |--- age <= 85.00\n",
      "|   |   |   |   |--- occupation_Farming-fishing <= 0.50\n",
      "|   |   |   |   |   |--- Einkommen > 50K\n",
      "|   |   |   |   |--- occupation_Farming-fishing >  0.50\n",
      "|   |   |   |   |   |--- Einkommen > 50K\n",
      "|   |   |   |--- age >  85.00\n",
      "|   |   |   |   |--- workclass_Self-emp-not-inc <= 0.50\n",
      "|   |   |   |   |   |--- Einkommen > 50K\n",
      "|   |   |   |   |--- workclass_Self-emp-not-inc >  0.50\n",
      "|   |   |   |   |   |--- Einkommen ≤ 50K\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Formatierte Regeln anzeigen\n",
    "def format_rules(tree_rules):\n",
    "    \"\"\"Formatiert die Baumregeln mit menschenlesbaren Klassennamen\"\"\"\n",
    "    # Ersetze 'class: 0' durch 'Einkommen ≤ 50K'\n",
    "    formatted_rules = tree_rules.replace('class: 0', 'Einkommen ≤ 50K')\n",
    "    # Ersetze 'class: 1' durch 'Einkommen > 50K'\n",
    "    formatted_rules = formatted_rules.replace('class: 1', 'Einkommen > 50K')\n",
    "    return formatted_rules\n",
    "\n",
    "# Regeln aus dem Surrogate-Baum extrahieren\n",
    "tree_rules = export_text(surrogate_tree, feature_names=X_train.columns.tolist())\n",
    "#print(tree_rules)\n",
    "print(format_rules(tree_rules))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Extraktion einer \"WENN, DANN\" - Regel aus DecisionTrees"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Einzelne Regel aus dem Surrogate-Modell:\n",
      "WENN marital.status_Married-civ-spouse ≤ 0.50 UND capital.gain ≤ 7073.50 UND education.num ≤ 12.50 UND capital.loss > 2218.50 UND fnlwgt ≤ 125450.50 DANN Einkommen > 50K\n"
     ]
    }
   ],
   "source": [
    "from sklearn.tree import _tree\n",
    "\n",
    "def extract_single_rule(tree, feature_names, class_to_extract=1):\n",
    "    \"\"\"\n",
    "    Extrahiert eine einzelne Regel aus einem Decision Tree für eine bestimmte Klasse.\n",
    "    \n",
    "    Parameters:\n",
    "    -----------\n",
    "    tree : DecisionTreeClassifier\n",
    "        Der trainierte Entscheidungsbaum\n",
    "    feature_names : list\n",
    "        Liste der Feature-Namen\n",
    "    class_to_extract : int, default=1\n",
    "        Die Klasse, für die eine Regel extrahiert werden soll (0=≤50K, 1=>50K)\n",
    "    \n",
    "    Returns:\n",
    "    --------\n",
    "    rule : str\n",
    "        Eine lesbare Regel als String\n",
    "    \"\"\"\n",
    "    tree_ = tree.tree_\n",
    "    \n",
    "    # Funktion zum rekursiven Extrahieren einer Regel\n",
    "    def tree_to_rule(node, depth, conditions):\n",
    "        # Wenn wir einen Blattknoten erreicht haben\n",
    "        if tree_.children_left[node] == _tree.TREE_LEAF:\n",
    "            # Prüfe, ob dieser Blattknoten die gewünschte Klasse vorhersagt\n",
    "            if np.argmax(tree_.value[node][0]) == class_to_extract:\n",
    "                # Formatiere die Bedingungen als Regel\n",
    "                if conditions:\n",
    "                    rule = \" UND \".join(conditions)\n",
    "                    return rule\n",
    "                else:\n",
    "                    return \"Keine Bedingungen (Wurzelklasse)\"\n",
    "            return None\n",
    "            \n",
    "        # Feature und Schwellenwert am aktuellen Knoten\n",
    "        feature = feature_names[tree_.feature[node]]\n",
    "        threshold = tree_.threshold[node]\n",
    "        \n",
    "        # Linkspfad (≤)\n",
    "        left_conditions = conditions + [f\"{feature} ≤ {threshold:.2f}\"]\n",
    "        left_rule = tree_to_rule(tree_.children_left[node], depth + 1, left_conditions)\n",
    "        if left_rule is not None:\n",
    "            return left_rule\n",
    "            \n",
    "        # Rechtspfad (>)\n",
    "        right_conditions = conditions + [f\"{feature} > {threshold:.2f}\"]\n",
    "        right_rule = tree_to_rule(tree_.children_right[node], depth + 1, right_conditions)\n",
    "        if right_rule is not None:\n",
    "            return right_rule\n",
    "            \n",
    "        # Keine passende Regel gefunden\n",
    "        return None\n",
    "    \n",
    "    # Starte die Suche vom Wurzelknoten\n",
    "    rule = tree_to_rule(0, 1, [])\n",
    "    \n",
    "    # Formatiere die Ausgabe\n",
    "    class_name = \"Einkommen > 50K\" if class_to_extract == 1 else \"Einkommen ≤ 50K\"\n",
    "    if rule:\n",
    "        return f\"WENN {rule} DANN {class_name}\"\n",
    "    else:\n",
    "        return f\"Keine Regel für {class_name} gefunden.\"\n",
    "\n",
    "# Anwendung für die Extraktion einer Regel für hohes Einkommen (Klasse 1)\n",
    "single_rule = extract_single_rule(surrogate_tree, X_train.columns.tolist(), class_to_extract=1)\n",
    "print(\"Einzelne Regel aus dem Surrogate-Modell:\")\n",
    "print(single_rule)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Berechnung der Genauigkeit des Surrogate-Modells mit unterschiedlichen Baumtiefen\n",
    "\n",
    "**Vorgehen (Ähnlich zu Hyper-Parameter Tuning)**\n",
    "- Vorhersagen erstellen mit unterschieldichen Baumtiefen\n",
    "- Vergleich der Vorhersagen von RF und Surrogate-Modell nach Gleichheit\n",
    "\n",
    "**Ziel: Beste Baumtiefe finden**\n",
    "- Mit der Random Forest am besten nachgebbildet wird"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 61840 characters]",
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Optimale Baumtiefe: 10 mit einer Genauigkeit von 0.9194\n"
     ]
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "def plot_surrogate_accuracy_vs_depth_test_only(rf_model, X_train, X_test, max_depths=range(1, 16)):\n",
    "    \"\"\"\n",
    "    Visualisiert die Genauigkeit des Surrogate-Modells für verschiedene Baumtiefen,\n",
    "    fokussiert nur auf die Testdaten.\n",
    "    \"\"\"\n",
    "    # Random Forest-Vorhersagen (nur einmal berechnen)\n",
    "    rf_train_predictions = rf_model.predict(X_train)\n",
    "    rf_test_predictions = rf_model.predict(X_test)\n",
    "    \n",
    "    # Ergebnisse für verschiedene Baumtiefen\n",
    "    test_accuracies = []\n",
    "    \n",
    "    for depth in max_depths:\n",
    "        # Surrogate-Baum mit aktueller Tiefe trainieren\n",
    "        surrogate_tree = DecisionTreeClassifier(max_depth=depth, random_state=42)\n",
    "        surrogate_tree.fit(X_train, rf_train_predictions)\n",
    "        \n",
    "        # Vorhersagen\n",
    "        surrogate_test_pred = surrogate_tree.predict(X_test)\n",
    "        \n",
    "        # Genauigkeit berechnen\n",
    "        test_acc = np.mean(surrogate_test_pred == rf_test_predictions)\n",
    "        test_accuracies.append(test_acc)\n",
    "    \n",
    "    # Visualisierung\n",
    "    plt.figure(figsize=(10, 6))\n",
    "    plt.plot(max_depths, test_accuracies, 'o-', color='#ED7D31', linewidth=2)\n",
    "    \n",
    "    # Finde die beste Tiefe\n",
    "    best_depth = max_depths[np.argmax(test_accuracies)]\n",
    "    best_acc = max(test_accuracies)\n",
    "    \n",
    "    # Markiere den besten Punkt\n",
    "    plt.scatter([best_depth], [best_acc], s=100, c='red', zorder=5)\n",
    "    plt.annotate(f'Optimale Tiefe: {best_depth}\\nGenauigkeit: {best_acc:.4f}',\n",
    "                xy=(best_depth, best_acc), xytext=(best_depth+1, best_acc-0.05),\n",
    "                arrowprops=dict(facecolor='black', shrink=0.05, width=1.5))\n",
    "    \n",
    "    # Beschriftungen und Layout\n",
    "    plt.grid(alpha=0.3)\n",
    "    plt.title('Surrogate-Modell-Genauigkeit bei verschiedenen Baumtiefen', fontsize=14)\n",
    "    plt.xlabel('Maximale Baumtiefe', fontsize=12)\n",
    "    plt.ylabel('Genauigkeit auf Testdaten', fontsize=12)\n",
    "    \n",
    "    # Füge Werte über den Punkten hinzu\n",
    "    for i, acc in enumerate(test_accuracies):\n",
    "        plt.text(max_depths[i], acc + 0.01, f'{acc:.3f}', ha='center')\n",
    "    \n",
    "    # Y-Achse anpassen (je nach Daten)\n",
    "    y_min = max(0, min(test_accuracies) - 0.05)\n",
    "    plt.ylim(y_min, 1.05)\n",
    "    \n",
    "    # Verbesserte visuelle Elemente\n",
    "    plt.fill_between(max_depths, test_accuracies, y_min, alpha=0.1, color='#ED7D31')\n",
    "    \n",
    "    plt.tight_layout()\n",
    "    plt.savefig('output/surrogate_accuracy.png', dpi=300)\n",
    "    plt.show()\n",
    "    \n",
    "    return best_depth, best_acc\n",
    "\n",
    "# Aufruf der Funktion\n",
    "best_depth, best_accuracy = plot_surrogate_accuracy_vs_depth_test_only(rf_model, X_train, X_test)\n",
    "print(f\"Optimale Baumtiefe: {best_depth} mit einer Genauigkeit von {best_accuracy:.4f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-20T12:21:48.896773Z",
     "start_time": "2025-03-20T12:21:48.874715Z"
    }
   },
   "outputs": [],
   "source": [
    "# Für Quarto Präsentationen\n",
    "import extract_cells\n",
    "extract_cells.extractToFiles(\"Explainable_AI_Adult_Census_Income.ipynb\")"
   ]
  }
 ],
 "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.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
