{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Generating counterfactuals for multi-class classification and regression models\n",
    "This notebook will demonstrate how the DiCE library can be used for multiclass classification and regression for scikit-learn models.\n",
    "You can use any method (\"random\", \"kdtree\", \"genetic\"), just specific it in the method argument in the initialization step. The rest of the code is completely identical.\n",
    "For demonstration, we will be using the genetic algorithm for CFs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import dice_ml\n",
    "from dice_ml import Dice\n",
    "\n",
    "from sklearn.datasets import load_iris, load_boston\n",
    "from sklearn.pipeline import Pipeline\n",
    "from sklearn.preprocessing import StandardScaler, OneHotEncoder\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.compose import ColumnTransformer\n",
    "from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor\n",
    "\n",
    "import pandas as pd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%load_ext autoreload\n",
    "%autoreload 2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We will use sklearn's internal datasets to demonstrate DiCE's features in this notebook"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Multiclass Classification"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For multiclass classification, we will use sklearn's Iris dataset. This data set consists of 3 different types of irises’ (Setosa, Versicolour, and Virginica) petal and sepal length. More information at https://scikit-learn.org/stable/datasets/toy_dataset.html#iris-plants-dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_iris = load_iris(as_frame=True).frame\n",
    "df_iris.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_iris.info()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "outcome_name = \"target\"\n",
    "continuous_features_iris = df_iris.drop(outcome_name, axis=1).columns.tolist()\n",
    "target = df_iris[outcome_name]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Split data into train and test\n",
    "datasetX = df_iris.drop(outcome_name, axis=1)\n",
    "x_train, x_test, y_train, y_test = train_test_split(datasetX,\n",
    "                                                    target,\n",
    "                                                    test_size=0.2,\n",
    "                                                    random_state=0,\n",
    "                                                    stratify=target)\n",
    "\n",
    "categorical_features = x_train.columns.difference(continuous_features_iris)\n",
    "\n",
    "# We create the preprocessing pipelines for both numeric and categorical data.\n",
    "numeric_transformer = Pipeline(steps=[\n",
    "    ('scaler', StandardScaler())])\n",
    "\n",
    "categorical_transformer = Pipeline(steps=[\n",
    "    ('onehot', OneHotEncoder(handle_unknown='ignore'))])\n",
    "\n",
    "transformations = ColumnTransformer(\n",
    "    transformers=[\n",
    "        ('num', numeric_transformer, continuous_features_iris),\n",
    "        ('cat', categorical_transformer, categorical_features)])\n",
    "\n",
    "# Append classifier to preprocessing pipeline.\n",
    "# Now we have a full prediction pipeline.\n",
    "clf_iris = Pipeline(steps=[('preprocessor', transformations),\n",
    "                           ('classifier', RandomForestClassifier())])\n",
    "model_iris = clf_iris.fit(x_train, y_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "d_iris = dice_ml.Data(dataframe=df_iris,\n",
    "                      continuous_features=continuous_features_iris,\n",
    "                      outcome_name=outcome_name)\n",
    "\n",
    "# We provide the type of model as a parameter (model_type)\n",
    "m_iris = dice_ml.Model(model=model_iris, backend=\"sklearn\", model_type='classifier')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "exp_genetic_iris = Dice(d_iris, m_iris, method=\"genetic\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we can see below, all the target values will lie in the desired class"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Single input\n",
    "query_instances_iris = x_train[2:3]\n",
    "genetic_iris = exp_genetic_iris.generate_counterfactuals(query_instances_iris, total_CFs=7, desired_class=2)\n",
    "genetic_iris.visualize_as_dataframe()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Multiple queries can be given as input at once\n",
    "query_instances_iris = x_train[17:19]\n",
    "genetic_iris = exp_genetic_iris.generate_counterfactuals(query_instances_iris, total_CFs=7, desired_class=2)\n",
    "genetic_iris.visualize_as_dataframe(show_only_changes=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Regression"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For regression, we will use sklearn's boston dataset. This dataset contains boston house-prices. More information at https://scikit-learn.org/stable/datasets/toy_dataset.html#boston-house-prices-dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "boston_data = load_boston()\n",
    "df_boston = pd.DataFrame(boston_data.data, columns=boston_data.feature_names)\n",
    "df_boston[outcome_name] = pd.Series(boston_data.target)\n",
    "df_boston.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_boston.info()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "continuous_features_boston = df_boston.drop(outcome_name, axis=1).columns.tolist()\n",
    "target = df_boston[outcome_name]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Split data into train and test\n",
    "datasetX = df_boston.drop(outcome_name, axis=1)\n",
    "x_train, x_test, y_train, y_test = train_test_split(datasetX,\n",
    "                                                    target,\n",
    "                                                    test_size=0.2,\n",
    "                                                    random_state=0)\n",
    "\n",
    "categorical_features = x_train.columns.difference(continuous_features_boston)\n",
    "\n",
    "# We create the preprocessing pipelines for both numeric and categorical data.\n",
    "numeric_transformer = Pipeline(steps=[\n",
    "    ('scaler', StandardScaler())])\n",
    "\n",
    "categorical_transformer = Pipeline(steps=[\n",
    "    ('onehot', OneHotEncoder(handle_unknown='ignore'))])\n",
    "\n",
    "transformations = ColumnTransformer(\n",
    "    transformers=[\n",
    "        ('num', numeric_transformer, continuous_features_boston),\n",
    "        ('cat', categorical_transformer, categorical_features)])\n",
    "\n",
    "# Append classifier to preprocessing pipeline.\n",
    "# Now we have a full prediction pipeline.\n",
    "regr_boston = Pipeline(steps=[('preprocessor', transformations),\n",
    "                              ('regressor', RandomForestRegressor())])\n",
    "model_boston = regr_boston.fit(x_train, y_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "d_boston = dice_ml.Data(dataframe=df_boston, continuous_features=continuous_features_boston, outcome_name=outcome_name)\n",
    "# We provide the type of model as a parameter (model_type)\n",
    "m_boston = dice_ml.Model(model=model_boston, backend=\"sklearn\", model_type='regressor')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "exp_genetic_boston = Dice(d_boston, m_boston, method=\"genetic\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we can see below, all the target values will lie in the desired range"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Multiple queries can be given as input at once\n",
    "query_instances_boston = x_train[2:3]\n",
    "genetic_boston = exp_genetic_boston.generate_counterfactuals(query_instances_boston,\n",
    "                                                             total_CFs=2,\n",
    "                                                             desired_range=[30, 45])\n",
    "genetic_boston.visualize_as_dataframe(show_only_changes=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Multiple queries can be given as input at once\n",
    "query_instances_boston = x_train[17:19]\n",
    "genetic_boston = exp_genetic_boston.generate_counterfactuals(query_instances_boston, total_CFs=4, desired_range=[40, 50])\n",
    "genetic_boston.visualize_as_dataframe(show_only_changes=True)"
   ]
  }
 ],
 "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.6.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
