{ "cells": [ { "cell_type": "markdown", "id": "84b6e27e-4bda-4d38-8689-ec7fc0da1848", "metadata": {}, "source": [ "# Define segment and predict sales associated" ] }, { "cell_type": "markdown", "id": "ec059482-45d3-4ae6-99bc-9b4ced115db3", "metadata": {}, "source": [ "## Importations of packages " ] }, { "cell_type": "code", "execution_count": 1, "id": "9771bf29-d08e-4674-8c23-9a2672fbef8f", "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "from pandas import DataFrame\n", "import numpy as np\n", "import os\n", "import s3fs\n", "import re\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.ensemble import RandomForestClassifier\n", "from sklearn.metrics import accuracy_score, confusion_matrix, classification_report, recall_score\n", "from sklearn.utils import class_weight\n", "from sklearn.neighbors import KNeighborsClassifier\n", "from sklearn.pipeline import Pipeline\n", "from sklearn.compose import ColumnTransformer\n", "from sklearn.preprocessing import OneHotEncoder\n", "from sklearn.impute import SimpleImputer\n", "from sklearn.model_selection import GridSearchCV\n", "from sklearn.preprocessing import StandardScaler, MaxAbsScaler, MinMaxScaler\n", "from sklearn.metrics import make_scorer, f1_score, balanced_accuracy_score\n", "import seaborn as sns\n", "import matplotlib.pyplot as plt\n", "from sklearn.metrics import roc_curve, auc, precision_recall_curve, average_precision_score\n", "from sklearn.exceptions import ConvergenceWarning, DataConversionWarning\n", "from sklearn.naive_bayes import GaussianNB\n", "from scipy.optimize import fsolve\n", "import io\n", "\n", "import pickle\n", "import warnings" ] }, { "cell_type": "markdown", "id": "048fcd7c-800a-4a6b-b725-faf8410f924a", "metadata": {}, "source": [ "## load databases" ] }, { "cell_type": "code", "execution_count": 2, "id": "539ccbdf-f29f-4f04-99c1-8c88d0efe514", "metadata": {}, "outputs": [], "source": [ "# Create filesystem object\n", "S3_ENDPOINT_URL = \"https://\" + os.environ[\"AWS_S3_ENDPOINT\"]\n", "fs = s3fs.S3FileSystem(client_kwargs={'endpoint_url': S3_ENDPOINT_URL})" ] }, { "cell_type": "code", "execution_count": 75, "id": "d6017ed0-6233-4888-85a7-05dec50a255b", "metadata": {}, "outputs": [], "source": [ "type_of_activity = \"musique\"" ] }, { "cell_type": "code", "execution_count": 4, "id": "0c3a6ddc-9345-4a42-b6bf-a20a95de3028", "metadata": {}, "outputs": [], "source": [ "def load_train_test(type_of_activity):\n", " # BUCKET = f\"projet-bdc2324-team1/Generalization/{type_of_activity}\"\n", " BUCKET = f\"projet-bdc2324-team1/Generalization_v2/{type_of_activity}\"\n", " File_path_train = BUCKET + \"/Train_set.csv\"\n", " File_path_test = BUCKET + \"/Test_set.csv\"\n", " \n", " with fs.open( File_path_train, mode=\"rb\") as file_in:\n", " dataset_train = pd.read_csv(file_in, sep=\",\")\n", " # dataset_train['y_has_purchased'] = dataset_train['y_has_purchased'].fillna(0)\n", "\n", " with fs.open(File_path_test, mode=\"rb\") as file_in:\n", " dataset_test = pd.read_csv(file_in, sep=\",\")\n", " # dataset_test['y_has_purchased'] = dataset_test['y_has_purchased'].fillna(0)\n", " \n", " return dataset_train, dataset_test" ] }, { "cell_type": "code", "execution_count": 76, "id": "2831d546-b365-498b-8248-c618bd9c3057", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/tmp/ipykernel_552/3983721681.py:8: DtypeWarning: Columns (10,19,20,21,24) have mixed types. Specify dtype option on import or set low_memory=False.\n", " dataset_train = pd.read_csv(file_in, sep=\",\")\n", "/tmp/ipykernel_552/3983721681.py:12: DtypeWarning: Columns (19,20,21,24) have mixed types. Specify dtype option on import or set low_memory=False.\n", " dataset_test = pd.read_csv(file_in, sep=\",\")\n" ] }, { "data": { "text/plain": [ "customer_id 0\n", "street_id 0\n", "structure_id 327020\n", "mcp_contact_id 135470\n", "fidelity 0\n", " ... \n", "purchases_8_2021 113963\n", "purchases_8_2022 0\n", "purchases_9_2021 113963\n", "purchases_9_2022 0\n", "y_has_purchased 0\n", "Length: 87, dtype: int64" ] }, "execution_count": 76, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dataset_train, dataset_test = load_train_test(type_of_activity)\n", "dataset_train.isna().sum()" ] }, { "cell_type": "code", "execution_count": 17, "id": "b8827f7b-b304-4f51-9814-c7a98ed88cf0", "metadata": {}, "outputs": [], "source": [ "def features_target_split(dataset_train, dataset_test):\n", " \n", " features_l = ['nb_tickets', 'nb_purchases', 'total_amount', 'nb_suppliers', 'purchase_date_min', 'purchase_date_max', \n", " 'time_between_purchase', 'fidelity', 'is_email_true', 'opt_in', #'is_partner', 'nb_tickets_internet',, 'vente_internet_max'\n", " 'gender_female', 'gender_male', 'gender_other', 'nb_campaigns', 'nb_campaigns_opened']\n", "\n", " # we suppress fidelity, time between purchase, and gender other (colinearity issue)\n", " \"\"\"\n", " features_l = ['nb_tickets', 'nb_purchases', 'total_amount', 'nb_suppliers', 'vente_internet_max', \n", " 'purchase_date_min', 'purchase_date_max', 'nb_tickets_internet', 'is_email_true', \n", " 'opt_in', 'gender_female', 'gender_male', 'nb_campaigns', 'nb_campaigns_opened']\n", " \"\"\"\n", " \n", " X_train = dataset_train # [features_l]\n", " y_train = dataset_train[['y_has_purchased']]\n", "\n", " X_test = dataset_test # [features_l]\n", " y_test = dataset_test[['y_has_purchased']]\n", " return X_train, X_test, y_train, y_test" ] }, { "cell_type": "code", "execution_count": 77, "id": "c18195fc-ed40-4e39-a59e-c9ecc5a8e6c3", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Shape train : (354365, 87)\n", "Shape test : (151874, 87)\n" ] } ], "source": [ "X_train, X_test, y_train, y_test = features_target_split(dataset_train, dataset_test)\n", "print(\"Shape train : \", X_train.shape)\n", "print(\"Shape test : \", X_test.shape)" ] }, { "cell_type": "markdown", "id": "74eda066-5e01-43aa-b0cf-cc6d9bbf770e", "metadata": {}, "source": [ "## get results from the logit cross validated model" ] }, { "cell_type": "code", "execution_count": 78, "id": "7c81390e-598c-4f02-bd56-dd03b00dcb33", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
customer_idstreet_idstructure_idmcp_contact_idfidelitytenant_idis_partnerdeleted_atis_email_trueopt_in...purchases_5_2022purchases_6_2021purchases_6_2022purchases_7_2021purchases_7_2022purchases_8_2021purchases_8_2022purchases_9_2021purchases_9_2022y_has_purchased
010_699783139NaN186852.00875FalseNaNTrue0...0.00.00.00.00.00.00.00.00.00.0
110_38307862NaN17621.07875FalseNaNTrue0...0.00.00.00.00.00.00.00.00.00.0
210_5561011063NaN136909.00875FalseNaNTrue1...0.00.00.00.00.00.00.00.00.00.0
310_686663443226NaN186611.01875FalseNaNTrue1...0.00.00.00.00.00.01.00.00.00.0
410_91656316684NaN21559.02875FalseNaNTrue0...0.00.00.00.00.00.00.00.00.00.0
..................................................................
15186914_1843791718883224.0394849.01862FalseNaNTrue1...0.0NaN0.0NaN0.0NaN0.0NaN0.00.0
15187014_4630858741826NaN1555631.00862FalseNaNTrue1...0.0NaN0.0NaN0.0NaN0.0NaN0.00.0
15187114_4659926871477NaN1542180.00862FalseNaNTrue1...0.0NaN0.0NaN0.0NaN0.0NaN0.00.0
15187214_4881492917272NaNNaN1862FalseNaNTrue1...0.0NaN0.0NaN0.0NaN0.0NaN1.00.0
15187314_81242762NaN10077.02862FalseNaNTrue0...0.0NaN0.0NaN0.0NaN0.0NaN0.00.0
\n", "

151874 rows × 87 columns

\n", "
" ], "text/plain": [ " customer_id street_id structure_id mcp_contact_id fidelity \\\n", "0 10_699783 139 NaN 186852.0 0 \n", "1 10_38307 862 NaN 17621.0 7 \n", "2 10_556101 1063 NaN 136909.0 0 \n", "3 10_686663 443226 NaN 186611.0 1 \n", "4 10_91656 316684 NaN 21559.0 2 \n", "... ... ... ... ... ... \n", "151869 14_1843791 718883 224.0 394849.0 1 \n", "151870 14_4630858 741826 NaN 1555631.0 0 \n", "151871 14_4659926 871477 NaN 1542180.0 0 \n", "151872 14_4881492 917272 NaN NaN 1 \n", "151873 14_8124 2762 NaN 10077.0 2 \n", "\n", " tenant_id is_partner deleted_at is_email_true opt_in ... \\\n", "0 875 False NaN True 0 ... \n", "1 875 False NaN True 0 ... \n", "2 875 False NaN True 1 ... \n", "3 875 False NaN True 1 ... \n", "4 875 False NaN True 0 ... \n", "... ... ... ... ... ... ... \n", "151869 862 False NaN True 1 ... \n", "151870 862 False NaN True 1 ... \n", "151871 862 False NaN True 1 ... \n", "151872 862 False NaN True 1 ... \n", "151873 862 False NaN True 0 ... \n", "\n", " purchases_5_2022 purchases_6_2021 purchases_6_2022 purchases_7_2021 \\\n", "0 0.0 0.0 0.0 0.0 \n", "1 0.0 0.0 0.0 0.0 \n", "2 0.0 0.0 0.0 0.0 \n", "3 0.0 0.0 0.0 0.0 \n", "4 0.0 0.0 0.0 0.0 \n", "... ... ... ... ... \n", "151869 0.0 NaN 0.0 NaN \n", "151870 0.0 NaN 0.0 NaN \n", "151871 0.0 NaN 0.0 NaN \n", "151872 0.0 NaN 0.0 NaN \n", "151873 0.0 NaN 0.0 NaN \n", "\n", " purchases_7_2022 purchases_8_2021 purchases_8_2022 \\\n", "0 0.0 0.0 0.0 \n", "1 0.0 0.0 0.0 \n", "2 0.0 0.0 0.0 \n", "3 0.0 0.0 1.0 \n", "4 0.0 0.0 0.0 \n", "... ... ... ... \n", "151869 0.0 NaN 0.0 \n", "151870 0.0 NaN 0.0 \n", "151871 0.0 NaN 0.0 \n", "151872 0.0 NaN 0.0 \n", "151873 0.0 NaN 0.0 \n", "\n", " purchases_9_2021 purchases_9_2022 y_has_purchased \n", "0 0.0 0.0 0.0 \n", "1 0.0 0.0 0.0 \n", "2 0.0 0.0 0.0 \n", "3 0.0 0.0 0.0 \n", "4 0.0 0.0 0.0 \n", "... ... ... ... \n", "151869 NaN 0.0 0.0 \n", "151870 NaN 0.0 0.0 \n", "151871 NaN 0.0 0.0 \n", "151872 NaN 1.0 0.0 \n", "151873 NaN 0.0 0.0 \n", "\n", "[151874 rows x 87 columns]" ] }, "execution_count": 78, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_test" ] }, { "cell_type": "code", "execution_count": 20, "id": "c708f439-bb75-4688-bf4f-4c04e13deaae", "metadata": {}, "outputs": [], "source": [ "def load_model(type_of_activity, model):\n", " # BUCKET = f\"projet-bdc2324-team1/Output_model/{type_of_activity}/{model}/\"\n", " BUCKET = f\"projet-bdc2324-team1/basique/{type_of_activity}/{model}/\"\n", " filename = model + '.pkl'\n", " file_path = BUCKET + filename\n", " with fs.open(file_path, mode=\"rb\") as f:\n", " model_bytes = f.read()\n", "\n", " model = pickle.loads(model_bytes)\n", " return model" ] }, { "cell_type": "code", "execution_count": 92, "id": "5261a803-05b8-41a0-968c-dc7bde48ddd3", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Pipeline(steps=[('preprocessor',\n",
       "                 ColumnTransformer(transformers=[('num',\n",
       "                                                  Pipeline(steps=[('imputer',\n",
       "                                                                   SimpleImputer(fill_value=0,\n",
       "                                                                                 strategy='constant')),\n",
       "                                                                  ('scaler',\n",
       "                                                                   StandardScaler())]),\n",
       "                                                  ['nb_campaigns',\n",
       "                                                   'taux_ouverture_mail',\n",
       "                                                   'prop_purchases_internet',\n",
       "                                                   'nb_tickets', 'nb_purchases',\n",
       "                                                   'total_amount',\n",
       "                                                   'nb_suppliers',\n",
       "                                                   'purchases_10_2021',\n",
       "                                                   'purchases_10_2022',\n",
       "                                                   'purchases_...\n",
       "                                                   'categorie_age_40_50',\n",
       "                                                   'categorie_age_50_60',\n",
       "                                                   'categorie_age_60_70',\n",
       "                                                   'categorie_age_70_80',\n",
       "                                                   'categorie_age_plus_80',\n",
       "                                                   'categorie_age_inconnue',\n",
       "                                                   'country_fr',\n",
       "                                                   'is_profession_known',\n",
       "                                                   'is_zipcode_known',\n",
       "                                                   'opt_in'])])),\n",
       "                ('LogisticRegression_Benchmark',\n",
       "                 LogisticRegression(class_weight={0.0: 0.5480249666729557,\n",
       "                                                  1.0: 5.705625684291879},\n",
       "                                    max_iter=5000, n_jobs=-1, solver='saga'))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('preprocessor',\n", " ColumnTransformer(transformers=[('num',\n", " Pipeline(steps=[('imputer',\n", " SimpleImputer(fill_value=0,\n", " strategy='constant')),\n", " ('scaler',\n", " StandardScaler())]),\n", " ['nb_campaigns',\n", " 'taux_ouverture_mail',\n", " 'prop_purchases_internet',\n", " 'nb_tickets', 'nb_purchases',\n", " 'total_amount',\n", " 'nb_suppliers',\n", " 'purchases_10_2021',\n", " 'purchases_10_2022',\n", " 'purchases_...\n", " 'categorie_age_40_50',\n", " 'categorie_age_50_60',\n", " 'categorie_age_60_70',\n", " 'categorie_age_70_80',\n", " 'categorie_age_plus_80',\n", " 'categorie_age_inconnue',\n", " 'country_fr',\n", " 'is_profession_known',\n", " 'is_zipcode_known',\n", " 'opt_in'])])),\n", " ('LogisticRegression_Benchmark',\n", " LogisticRegression(class_weight={0.0: 0.5480249666729557,\n", " 1.0: 5.705625684291879},\n", " max_iter=5000, n_jobs=-1, solver='saga'))])" ] }, "execution_count": 92, "metadata": {}, "output_type": "execute_result" } ], "source": [ "model = load_model(type_of_activity, \"LogisticRegression_Benchmark\")\n", "# model = load_model(type_of_activity, \"randomF_cv\")\n", "model" ] }, { "cell_type": "markdown", "id": "006819e7-e9c5-48d9-85ee-aa43d5e4c9c2", "metadata": {}, "source": [ "## Quartile clustering" ] }, { "cell_type": "code", "execution_count": 93, "id": "018d8ff4-3436-4eec-8507-d1a265cbabf1", "metadata": {}, "outputs": [], "source": [ "y_pred = model.predict(X_test)\n", "y_pred_prob = model.predict_proba(X_test)[:, 1]" ] }, { "cell_type": "code", "execution_count": 94, "id": "846f53b9-73c2-4a8b-9d9e-f11bf59ce9ba", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
customer_idstreet_idstructure_idmcp_contact_idfidelitytenant_idis_partnerdeleted_atis_email_trueopt_in...purchases_8_2021purchases_8_2022purchases_9_2021purchases_9_2022y_has_purchasedhas_purchasedhas_purchased_estimscorequartilescore_adjusted
010_699783139NaN186852.00875FalseNaNTrue0...0.00.00.00.00.00.00.00.13118010.017574
110_38307862NaN17621.07875FalseNaNTrue0...0.00.00.00.00.00.00.00.32163520.042466
210_5561011063NaN136909.00875FalseNaNTrue1...0.00.00.00.00.00.00.00.00506810.000676
310_686663443226NaN186611.01875FalseNaNTrue1...0.01.00.00.00.00.00.00.16697910.018397
410_91656316684NaN21559.02875FalseNaNTrue0...0.00.00.00.00.00.00.00.16152310.018632
510_35956106204NaNNaN1875FalseNaNTrue0...0.00.00.00.00.00.00.00.09813910.010129
610_5600581063NaN161812.00875FalseNaNTrue1...0.00.00.00.00.00.00.00.00537710.000715
710_386035136421865.07660.04875FalseNaNTrue1...0.00.00.00.01.01.01.00.90669840.461388
810_5632941063NaN167549.00875FalseNaNTrue1...0.00.00.00.00.00.00.00.00739910.000974
910_548983268636NaN173318.01875FalseNaNTrue0...0.00.00.00.00.00.00.00.16352910.022102
\n", "

10 rows × 92 columns

\n", "
" ], "text/plain": [ " customer_id street_id structure_id mcp_contact_id fidelity tenant_id \\\n", "0 10_699783 139 NaN 186852.0 0 875 \n", "1 10_38307 862 NaN 17621.0 7 875 \n", "2 10_556101 1063 NaN 136909.0 0 875 \n", "3 10_686663 443226 NaN 186611.0 1 875 \n", "4 10_91656 316684 NaN 21559.0 2 875 \n", "5 10_35956 106204 NaN NaN 1 875 \n", "6 10_560058 1063 NaN 161812.0 0 875 \n", "7 10_38603 513642 1865.0 7660.0 4 875 \n", "8 10_563294 1063 NaN 167549.0 0 875 \n", "9 10_548983 268636 NaN 173318.0 1 875 \n", "\n", " is_partner deleted_at is_email_true opt_in ... purchases_8_2021 \\\n", "0 False NaN True 0 ... 0.0 \n", "1 False NaN True 0 ... 0.0 \n", "2 False NaN True 1 ... 0.0 \n", "3 False NaN True 1 ... 0.0 \n", "4 False NaN True 0 ... 0.0 \n", "5 False NaN True 0 ... 0.0 \n", "6 False NaN True 1 ... 0.0 \n", "7 False NaN True 1 ... 0.0 \n", "8 False NaN True 1 ... 0.0 \n", "9 False NaN True 0 ... 0.0 \n", "\n", " purchases_8_2022 purchases_9_2021 purchases_9_2022 y_has_purchased \\\n", "0 0.0 0.0 0.0 0.0 \n", "1 0.0 0.0 0.0 0.0 \n", "2 0.0 0.0 0.0 0.0 \n", "3 1.0 0.0 0.0 0.0 \n", "4 0.0 0.0 0.0 0.0 \n", "5 0.0 0.0 0.0 0.0 \n", "6 0.0 0.0 0.0 0.0 \n", "7 0.0 0.0 0.0 1.0 \n", "8 0.0 0.0 0.0 0.0 \n", "9 0.0 0.0 0.0 0.0 \n", "\n", " has_purchased has_purchased_estim score quartile score_adjusted \n", "0 0.0 0.0 0.131180 1 0.017574 \n", "1 0.0 0.0 0.321635 2 0.042466 \n", "2 0.0 0.0 0.005068 1 0.000676 \n", "3 0.0 0.0 0.166979 1 0.018397 \n", "4 0.0 0.0 0.161523 1 0.018632 \n", "5 0.0 0.0 0.098139 1 0.010129 \n", "6 0.0 0.0 0.005377 1 0.000715 \n", "7 1.0 1.0 0.906698 4 0.461388 \n", "8 0.0 0.0 0.007399 1 0.000974 \n", "9 0.0 0.0 0.163529 1 0.022102 \n", "\n", "[10 rows x 92 columns]" ] }, "execution_count": 94, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_test_segment = X_test\n", "\n", "X_test_segment[\"has_purchased\"] = y_test\n", "X_test_segment[\"has_purchased_estim\"] = y_pred\n", "X_test_segment[\"score\"] = y_pred_prob\n", "X_test_segment[\"quartile\"] = np.where(X_test['score']<0.25, '1',\n", " np.where(X_test['score']<0.5, '2',\n", " np.where(X_test['score']<0.75, '3', '4')))\n", "X_test_segment.head(10)" ] }, { "cell_type": "code", "execution_count": 24, "id": "fb592fe3-ea40-4e83-8fe9-c52b9ee42f2a", "metadata": {}, "outputs": [], "source": [ "def df_segment(df, y, model) :\n", "\n", " y_pred = model.predict(df)\n", " y_pred_prob = model.predict_proba(df)[:, 1]\n", "\n", " df_segment = df\n", "\n", " df_segment[\"has_purchased\"] = y\n", " df_segment[\"has_purchased_estim\"] = y_pred\n", " df_segment[\"score\"] = y_pred_prob\n", " df_segment[\"quartile\"] = np.where(df_segment['score']<0.25, '1',\n", " np.where(df_segment['score']<0.5, '2',\n", " np.where(df_segment['score']<0.75, '3', '4')))\n", "\n", " return df_segment" ] }, { "cell_type": "code", "execution_count": 88, "id": "968645d5-58cc-485a-bd8b-99f4cfc26fec", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/tmp/ipykernel_1080/2624515794.py:8: SettingWithCopyWarning: \n", "A value is trying to be set on a copy of a slice from a DataFrame.\n", "Try using .loc[row_indexer,col_indexer] = value instead\n", "\n", "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n", " df_segment[\"has_purchased\"] = y\n", "/tmp/ipykernel_1080/2624515794.py:9: SettingWithCopyWarning: \n", "A value is trying to be set on a copy of a slice from a DataFrame.\n", "Try using .loc[row_indexer,col_indexer] = value instead\n", "\n", "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n", " df_segment[\"has_purchased_estim\"] = y_pred\n", "/tmp/ipykernel_1080/2624515794.py:10: SettingWithCopyWarning: \n", "A value is trying to be set on a copy of a slice from a DataFrame.\n", "Try using .loc[row_indexer,col_indexer] = value instead\n", "\n", "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n", " df_segment[\"score\"] = y_pred_prob\n", "/tmp/ipykernel_1080/2624515794.py:11: SettingWithCopyWarning: \n", "A value is trying to be set on a copy of a slice from a DataFrame.\n", "Try using .loc[row_indexer,col_indexer] = value instead\n", "\n", "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n", " df_segment[\"quartile\"] = np.where(df_segment['score']<0.25, '1',\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
nb_ticketsnb_purchasestotal_amountnb_suppliersvente_internet_maxpurchase_date_minpurchase_date_maxtime_between_purchasenb_tickets_internetfidelity...opt_ingender_femalegender_malegender_othernb_campaignsnb_campaigns_openedhas_purchasedhas_purchased_estimscorequartile
04.01.0100.001.00.05.1771875.1771870.0000000.01...False1000.00.00.00.00.0060661
11.01.055.001.00.0426.265613426.2656130.0000000.02...True0100.00.01.00.00.2888472
217.01.080.001.00.0436.033437436.0334370.0000000.02...True1000.00.00.00.00.1032641
34.01.0120.001.00.05.1964125.1964120.0000000.01...False1000.00.00.00.00.0089281
434.02.0416.001.00.0478.693148115.631470363.0616780.04...False1000.00.01.01.00.9928094
..................................................................
960911.01.067.311.01.0278.442257278.4422570.0000001.02...False01015.05.01.00.00.3517622
960921.01.061.411.01.0189.207373189.2073730.0000001.01...False01012.09.00.01.00.5678143
960930.00.00.000.00.0550.000000550.000000-1.0000000.01...True10029.03.00.00.00.0046521
960941.01.079.431.01.0279.312905279.3129050.0000001.01...False01020.04.00.00.00.2930422
960950.00.00.000.00.0550.000000550.000000-1.0000000.02...False01031.04.00.01.00.7878524
\n", "

96096 rows × 21 columns

\n", "
" ], "text/plain": [ " nb_tickets nb_purchases total_amount nb_suppliers \\\n", "0 4.0 1.0 100.00 1.0 \n", "1 1.0 1.0 55.00 1.0 \n", "2 17.0 1.0 80.00 1.0 \n", "3 4.0 1.0 120.00 1.0 \n", "4 34.0 2.0 416.00 1.0 \n", "... ... ... ... ... \n", "96091 1.0 1.0 67.31 1.0 \n", "96092 1.0 1.0 61.41 1.0 \n", "96093 0.0 0.0 0.00 0.0 \n", "96094 1.0 1.0 79.43 1.0 \n", "96095 0.0 0.0 0.00 0.0 \n", "\n", " vente_internet_max purchase_date_min purchase_date_max \\\n", "0 0.0 5.177187 5.177187 \n", "1 0.0 426.265613 426.265613 \n", "2 0.0 436.033437 436.033437 \n", "3 0.0 5.196412 5.196412 \n", "4 0.0 478.693148 115.631470 \n", "... ... ... ... \n", "96091 1.0 278.442257 278.442257 \n", "96092 1.0 189.207373 189.207373 \n", "96093 0.0 550.000000 550.000000 \n", "96094 1.0 279.312905 279.312905 \n", "96095 0.0 550.000000 550.000000 \n", "\n", " time_between_purchase nb_tickets_internet fidelity ... opt_in \\\n", "0 0.000000 0.0 1 ... False \n", "1 0.000000 0.0 2 ... True \n", "2 0.000000 0.0 2 ... True \n", "3 0.000000 0.0 1 ... False \n", "4 363.061678 0.0 4 ... False \n", "... ... ... ... ... ... \n", "96091 0.000000 1.0 2 ... False \n", "96092 0.000000 1.0 1 ... False \n", "96093 -1.000000 0.0 1 ... True \n", "96094 0.000000 1.0 1 ... False \n", "96095 -1.000000 0.0 2 ... False \n", "\n", " gender_female gender_male gender_other nb_campaigns \\\n", "0 1 0 0 0.0 \n", "1 0 1 0 0.0 \n", "2 1 0 0 0.0 \n", "3 1 0 0 0.0 \n", "4 1 0 0 0.0 \n", "... ... ... ... ... \n", "96091 0 1 0 15.0 \n", "96092 0 1 0 12.0 \n", "96093 1 0 0 29.0 \n", "96094 0 1 0 20.0 \n", "96095 0 1 0 31.0 \n", "\n", " nb_campaigns_opened has_purchased has_purchased_estim score \\\n", "0 0.0 0.0 0.0 0.006066 \n", "1 0.0 1.0 0.0 0.288847 \n", "2 0.0 0.0 0.0 0.103264 \n", "3 0.0 0.0 0.0 0.008928 \n", "4 0.0 1.0 1.0 0.992809 \n", "... ... ... ... ... \n", "96091 5.0 1.0 0.0 0.351762 \n", "96092 9.0 0.0 1.0 0.567814 \n", "96093 3.0 0.0 0.0 0.004652 \n", "96094 4.0 0.0 0.0 0.293042 \n", "96095 4.0 0.0 1.0 0.787852 \n", "\n", " quartile \n", "0 1 \n", "1 2 \n", "2 1 \n", "3 1 \n", "4 4 \n", "... ... \n", "96091 2 \n", "96092 3 \n", "96093 1 \n", "96094 2 \n", "96095 4 \n", "\n", "[96096 rows x 21 columns]" ] }, "execution_count": 88, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_segment(X_test, y_test, model)" ] }, { "cell_type": "markdown", "id": "ad16b8ab-7e01-404b-971e-866e9b9d5aa4", "metadata": {}, "source": [ "## definition of functions to compute the bias of scores and adjust it \n", "\n", "Le biais est calculé de la façon suivante. \n", "En notant $\\hat{p(x_i)}$ le score calculé (estimé par la modélisation) et $p(x_i)$ le vrai score (sans biais), et $\\beta$ le logarithme du biais, on a : \\\n", "$\\ln{\\frac{\\hat{p(x_i)}}{1-\\hat{p(x_i)}}} = \\beta + \\ln{\\frac{p(x_i)}{1-p(x_i)}}$ \\\n", "$ \\frac{\\hat{p(x_i)}}{1-\\hat{p(x_i)}} = \\exp(\\beta) . \\frac{p(x_i)}{1-p(x_i)} $ , soit : \\\n", "$p(x_i) = {\\frac{\\frac{\\hat{p(x_i)}}{1-\\hat{p(x_i)}}}{B+\\frac{\\hat{p(x_i)}}{1-\\hat{p(x_i)}}}}$ \\\n", "Ce qu'on appelle biais et qu'on estime dans le code par la suite est : $B=\\exp(\\beta) $. Les probabilités ne sont donc pas biaisées si $B=1$. Il y a surestimation si $B>1$. \n", "\n", "On cherche le B qui permette d'ajuster les probabilités de telle sorte que la somme des scores soit égale à la somme des y_has_purchased. Cela revient à résoudre : \n", "\n", "\\begin{equation}\n", "\\sum_{i}{\\frac{\\frac{\\hat{p(x_i)}}{1-\\hat{p(x_i)}}}{B+\\frac{\\hat{p(x_i)}}{1-\\hat{p(x_i)}}}} = \\sum_{i}{Y_i}\n", "\\end{equation}\n", "\n", "C'est ce que fait la fonction find_bias. \n", "\n", "Note sur les notations : \\\n", "$\\hat{p(x_i)}$ correspond à ce qu'on appelle le score et $p(x_i)$ à ce qu'on appellera le score adjusted" ] }, { "cell_type": "code", "execution_count": 25, "id": "f0379536-a6c5-4b16-bde5-d0319ec1b140", "metadata": {}, "outputs": [], "source": [ "# compute adjusted score from odd ratios (cf formula above)\n", "def adjusted_score(odd_ratio, bias) :\n", " adjusted_score = odd_ratio/(bias+odd_ratio)\n", " return adjusted_score" ] }, { "cell_type": "code", "execution_count": 26, "id": "32a0dfd0-f49d-4785-a56f-706d381bfe41", "metadata": {}, "outputs": [], "source": [ "# when the score is 1 we cannot compute the odd ratio, so we adjust scores equal to 1\n", "# we set the second best score instead\n", "\n", "def adjust_score_1(score) :\n", " second_best_score = np.array([element for element in score if element !=1]).max()\n", " new_score = np.array([element if element!=1 else second_best_score for element in score]) \n", " return new_score" ] }, { "cell_type": "code", "execution_count": 27, "id": "2dff1def-02df-413e-afce-b4aeaf7752b6", "metadata": {}, "outputs": [], "source": [ "def odd_ratio(score) :\n", " return score / (1 - score)" ] }, { "cell_type": "code", "execution_count": 28, "id": "683d71fc-7442-4028-869c-49c57592d6e9", "metadata": {}, "outputs": [], "source": [ "# definition of a function that automatically detects the bias\n", "\n", "def find_bias(odd_ratios, y_objective, initial_guess=6) :\n", " \"\"\"\n", " results = minimize(lambda bias : (sum([adjusted_score(element, bias) for element in list(odd_ratios)]) - y_objective)**2 ,\n", " initial_guess , method = \"BFGS\")\n", "\n", " estimated_bias = results.x[0]\n", " \"\"\"\n", "\n", " # faster method\n", " bias_estimated = fsolve(lambda bias : sum([adjusted_score(element, bias) for element in list(odd_ratios)]) - y_objective, x0=6)\n", " \n", " return bias_estimated[0]" ] }, { "cell_type": "code", "execution_count": 95, "id": "f17dc6ca-7a48-441b-8c04-11c47b8b9741", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.3000275047453295 0.08797424180570736\n" ] }, { "data": { "text/plain": [ "0.08763280798047211" ] }, "execution_count": 95, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print(X_test_segment[\"score\"].mean(), y_test[\"y_has_purchased\"].mean())\n", "y_train[\"y_has_purchased\"].mean()" ] }, { "cell_type": "code", "execution_count": 96, "id": "781b0d40-c954-4c54-830a-e709c8667328", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10.698758485840244" ] }, "execution_count": 96, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# computation with the function defined\n", "\n", "bias_test_set = find_bias(odd_ratios = odd_ratio(adjust_score_1(X_test_segment[\"score\"])), \n", " y_objective = y_test[\"y_has_purchased\"].sum(),\n", " initial_guess=6)\n", "bias_test_set" ] }, { "cell_type": "code", "execution_count": 97, "id": "248cb862-418e-4767-9933-70c4885ecf40", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10.688693734338177" ] }, "execution_count": 97, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# comparison with bias of the train set\n", "X_train_score = model.predict_proba(X_train)[:, 1]\n", "\n", "bias_train_set = find_bias(odd_ratios = odd_ratio(adjust_score_1(X_train_score)), \n", " y_objective = y_train[\"y_has_purchased\"].sum(),\n", " initial_guess=10)\n", "bias_train_set" ] }, { "cell_type": "code", "execution_count": 98, "id": "fff6cbe6-7bb3-4732-9b81-b9ac5383bbcf", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "betâ test - betâ train = 0.00094118290869078\n" ] } ], "source": [ "print(\"betâ test - betâ train = \",np.log(bias_test_set/bias_train_set))" ] }, { "cell_type": "code", "execution_count": 99, "id": "f506870d-4a8a-4b2c-8f0b-e0789080b20c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "mean absolute erreur 4.674943825828751e-05\n" ] } ], "source": [ "# impact of considering a bias computed on train set instead of test set - totally neglectable\n", "\n", "score_adjusted_test = adjusted_score(odd_ratio(adjust_score_1(X_test_segment[\"score\"])), bias = bias_test_set)\n", "score_adjusted_train = adjusted_score(odd_ratio(adjust_score_1(X_test_segment[\"score\"])), bias = bias_train_set)\n", "\n", "print(\"mean absolute erreur\",abs(score_adjusted_test-score_adjusted_train).mean())" ] }, { "cell_type": "code", "execution_count": 100, "id": "8213d0e4-063b-49fa-90b7-677fc34f4c01", "metadata": {}, "outputs": [], "source": [ "# adjust scores accordingly \n", "\n", "# X_test_segment[\"score_adjusted\"] = adjusted_score(odd_ratio(adjust_score_1(X_test_segment[\"score\"])), bias = bias_test_set)\n", "\n", "# actually, we are not supposed to have X_test, so the biais is estimated on X_train\n", "# X_test_segment[\"score_adjusted\"] = adjusted_score(odd_ratio(adjust_score_1(X_test_segment[\"score\"])), bias = bias_train_set)\n", "X_test_segment[\"score_adjusted\"] = score_adjusted_train" ] }, { "cell_type": "code", "execution_count": 101, "id": "834d3723-2e72-4c65-9c62-e2d595c69461", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MSE for score : 0.12309116071575532\n", "MSE for ajusted score : 0.05482346713233594\n", "sum of y_has_purchased : 13361.0\n", "sum of adjusted scores : 13368.100024185826\n" ] } ], "source": [ "# check \n", "\n", "MSE_score = ((X_test_segment[\"score\"]-X_test_segment[\"has_purchased\"])**2).mean()\n", "MSE_ajusted_score = ((X_test_segment[\"score_adjusted\"]-X_test_segment[\"has_purchased\"])**2).mean()\n", "print(f\"MSE for score : {MSE_score}\")\n", "print(f\"MSE for ajusted score : {MSE_ajusted_score}\")\n", "\n", "print(\"sum of y_has_purchased :\",y_test[\"y_has_purchased\"].sum())\n", "print(\"sum of adjusted scores :\", X_test_segment[\"score_adjusted\"].sum())" ] }, { "cell_type": "code", "execution_count": 102, "id": "9f30a4dd-a9d8-405a-a7d5-5324ae88cf70", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MAE for score : 0.25695361997840177\n", "MAE for adjusted score : 0.10450649550597542\n" ] } ], "source": [ "# mean absolute error - divided by 2 with out method\n", "\n", "MAE_score = abs(X_test_segment[\"score\"]-X_test_segment[\"has_purchased\"]).mean()\n", "MAE_ajusted_score = abs(X_test_segment[\"score_adjusted\"]-X_test_segment[\"has_purchased\"]).mean()\n", "print(f\"MAE for score : {MAE_score}\")\n", "print(f\"MAE for adjusted score : {MAE_ajusted_score}\")" ] }, { "cell_type": "code", "execution_count": 37, "id": "6f9396db-e213-408c-a596-eaeec3bc79f3", "metadata": {}, "outputs": [], "source": [ "# visualization\n", "\n", "# histogramme des probas et des probas ajustées\n", "\n", "def plot_hist_scores(df, score, score_adjusted, type_of_activity) :\n", "\n", " plt.figure()\n", " plt.hist(df[score], label = \"score\", alpha=0.6)\n", " plt.hist(df[score_adjusted], label=\"adjusted score\", alpha=0.6)\n", " plt.legend()\n", " plt.xlabel(\"probability of a future purchase\")\n", " plt.ylabel(\"count\")\n", " plt.title(f\"Comparison between score and adjusted score for {type_of_activity} companies\")\n", " # plt.show()" ] }, { "cell_type": "code", "execution_count": 64, "id": "def64c16-f4dd-493c-909c-d886d7f53947", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'projet-bdc2324-team1/Output_expected_CA/sport/hist_score_adjustedsport.png'" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "PATH + file_name + type_of_activity + \".png\"" ] }, { "cell_type": "code", "execution_count": 103, "id": "b478d40d-9677-4204-87bd-16fb0bc1fe9a", "metadata": {}, "outputs": [ { "data": { "image/png": "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J+jq6iIoKKjC+7tgwQLcvHkTr732GkxNTWXzxowZg+zsbIwePfq56/niiy/w7rvvwtPTEx06dMAff/yB33//HYaGhmXOxdTUFJs3b8bPP/8MOzs7fPHFF1i2bFm59wkAli9fDhsbG3Tv3h0eHh6YPn069PT0pPkaGhpYvXo1vvnmG1hZWeGtt94CAIwePRojR47EiBEj4OLigqZNm6Jnz57P3V6bNm0QHh6OGzduoFu3bmjfvj3mzp0LS0vLUpd7/fXX8c4772DAgAFwdXWFvb09vv76a2l+RfNp0aIFQkNDcf78eXTq1AlOTk749ddfoaFRtsfpluV4zMvLw8SJE9GqVSv069cPtra2Uu5WVlY4ceIE8vLy4ObmBnt7e0yZMgVKpRJqavwoJCqkEGX94hERVbsePXqgXbt21fZLILXd9u3bMWXKFNy5c4cPTK4CAQEB2L17N399hegVxl8+IaJa7/Hjx4iPj0dgYCDGjRvHopCIqIpw/JyIar0lS5agXbt2MDc3x5w5c2o6HSKilxYvJRMRERERAI4YEhEREVEBFoZEREREBICFIREREREVYGFIRERERABYGBIRERFRARaGRERERASAhSERERERFWBhSEREREQAWBgSERERUYH/AxTvctmi4E3PAAAAAElFTkSuQmCC", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_hist_scores(X_test_segment, score = \"score\", score_adjusted = \"score_adjusted\", type_of_activity = type_of_activity)" ] }, { "cell_type": "code", "execution_count": 40, "id": "add631d7-0757-45a5-bb5b-f7f4b4baa961", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "projet-bdc2324-team1/Output_expected_CA/sport/\n" ] } ], "source": [ "# define path so save graphics\n", "\n", "# define type of activity \n", "type_of_activity = \"sport\"\n", "PATH = f\"projet-bdc2324-team1/Output_expected_CA/{type_of_activity}/\"\n", "print(PATH)" ] }, { "cell_type": "code", "execution_count": 68, "id": "3a5b5bd9-e033-4436-8c56-bf5fb61df87f", "metadata": {}, "outputs": [], "source": [ "# export png \n", "\n", "# plot adjusted scores and save (to be tested)\n", "plot_hist_scores(X_test_segment, score = \"score\", score_adjusted = \"score_adjusted\", type_of_activity = type_of_activity)\n", "\n", "image_buffer = io.BytesIO()\n", "plt.savefig(image_buffer, format='png')\n", "image_buffer.seek(0)\n", "file_name = \"hist_score_adjusted_\"\n", "FILE_PATH_OUT_S3 = PATH + file_name + type_of_activity + \".png\"\n", "with fs.open(FILE_PATH_OUT_S3, 'wb') as s3_file:\n", " s3_file.write(image_buffer.read())\n", "plt.close()" ] }, { "cell_type": "markdown", "id": "e6fae260-fab8-4f51-90dc-9b6d7314c77b", "metadata": {}, "source": [ "## Compute number of tickets and CA by segment with the recalibrated score" ] }, { "cell_type": "code", "execution_count": 104, "id": "90c4c2b5-0ede-4001-889f-749cfbd9df04", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
quartilescore (%)score adjusted (%)has purchased (%)
018.800.941.02
1236.165.174.70
2361.0613.3314.62
3489.8653.7453.19
\n", "
" ], "text/plain": [ " quartile score (%) score adjusted (%) has purchased (%)\n", "0 1 8.80 0.94 1.02\n", "1 2 36.16 5.17 4.70\n", "2 3 61.06 13.33 14.62\n", "3 4 89.86 53.74 53.19" ] }, "execution_count": 104, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_test_table_adjusted_scores = (100 * X_test_segment.groupby(\"quartile\")[[\"score\",\"score_adjusted\", \"has_purchased\"]].mean()).round(2).reset_index()\n", "X_test_table_adjusted_scores = X_test_table_adjusted_scores.rename(columns = {col : f\"{col.replace('_', ' ')} (%)\" for col in X_test_table_adjusted_scores.columns if col in [\"score\",\"score_adjusted\", \"has_purchased\"]})\n", "X_test_table_adjusted_scores" ] }, { "cell_type": "code", "execution_count": 162, "id": "d0b8740c-cf48-4a3e-83cb-23d95059f62f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'\\\\begin{tabular}{lrrr}\\n\\\\toprule\\nquartile & score (%) & score adjusted (%) & has purchased (%) \\\\\\\\\\n\\\\midrule\\n1 & 13.250000 & 2.510000 & 1.570000 \\\\\\\\\\n2 & 33.890000 & 8.000000 & 9.850000 \\\\\\\\\\n3 & 63.060000 & 22.580000 & 21.470000 \\\\\\\\\\n4 & 90.520000 & 66.200000 & 65.010000 \\\\\\\\\\n\\\\bottomrule\\n\\\\end{tabular}\\n'" ] }, "execution_count": 162, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_test_table_adjusted_scores.to_latex(index=False)" ] }, { "cell_type": "code", "execution_count": 43, "id": "d6a04d3e-c454-43e4-ae4c-0746e928575b", "metadata": {}, "outputs": [], "source": [ "# comparison between score and adjusted score - export csv associated\n", "\n", "file_name = \"table_adjusted_score_\"\n", "FILE_PATH_OUT_S3 = PATH + file_name + type_of_activity + \".csv\"\n", "with fs.open(FILE_PATH_OUT_S3, 'w') as file_out:\n", " X_test_table_adjusted_scores.to_csv(file_out, index = False)" ] }, { "cell_type": "code", "execution_count": 40, "id": "a974589f-7952-4db2-bebf-7b69c6b09372", "metadata": {}, "outputs": [], "source": [ "def project_tickets_CA (df, nb_purchases, nb_tickets, total_amount, score_adjusted, duration_ref, duration_projection) :\n", " \n", " duration_ratio = duration_ref/duration_projection\n", "\n", " df_output = df\n", "\n", " df_output.loc[:,\"nb_tickets_projected\"] = df_output.loc[:,nb_tickets] / duration_ratio\n", " df_output.loc[:,\"total_amount_projected\"] = df_output.loc[:,total_amount] / duration_ratio\n", " \n", " df_output.loc[:,\"nb_tickets_expected\"] = df_output.loc[:,score_adjusted] * df_output.loc[:,\"nb_tickets_projected\"]\n", " df_output.loc[:,\"total_amount_expected\"] = df_output.loc[:,score_adjusted] * df_output.loc[:,\"total_amount_projected\"]\n", "\n", " df_output.loc[:,\"pace_purchase\"] = (duration_ref/df_output.loc[:,nb_purchases]).apply(lambda x : np.nan if x==np.inf else x)\n", " \n", " return df_output\n" ] }, { "cell_type": "code", "execution_count": 41, "id": "dd8a52e1-d06e-4790-8687-8e58e3e6b84e", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
customer_idstreet_idstructure_idmcp_contact_idfidelitytenant_idis_partnerdeleted_atis_email_trueopt_in...has_purchasedhas_purchased_estimscorequartilescore_adjustednb_tickets_projectedtotal_amount_projectednb_tickets_expectedtotal_amount_expectedpace_purchase
01_81918114NaN834.001311FalseNaNTrue1...0.00.00.40854620.0270660.0000000.0000000.0000000.000000NaN
11_147922NaN251178.001311FalseNaNTrue1...0.00.00.02704610.0011180.0000000.0000000.0000000.000000NaN
21_304662NaN2355.001311FalseNaNTrue1...0.00.00.18085110.0088130.0000000.0000000.0000000.000000NaN
31_4189820244203714.097973.001311FalseNaNTrue1...0.00.00.22087210.0112880.0000000.0000000.0000000.000000NaN
41_587462NaN82026.011311FalseNaNTrue1...0.00.00.10095110.0045020.0000000.0000000.0000000.000000NaN
..................................................................
1861154_24295103884NaN96913.001342FalseNaNTrue1...0.00.00.46664420.0340370.0000000.0000000.0000000.000000NaN
1861164_4444343315NaN234734.001342FalseNaNTrue0...0.00.00.42764120.0292110.0000000.0000000.0000000.000000NaN
1861174_33439472NaNNaN11342FalseNaNTrue0...0.00.00.46846420.0342780.70588220.4705880.0241960.70168617.0
1861184_4775246460NaN89791.001342FalseNaNTrue1...0.00.00.36010020.0221610.0000000.0000000.0000000.000000NaN
1861194_3544934592NaN119197.001342FalseNaNTrue1...0.01.00.72890730.0977050.0000000.0000000.0000000.000000NaN
\n", "

186120 rows × 97 columns

\n", "
" ], "text/plain": [ " customer_id street_id structure_id mcp_contact_id fidelity \\\n", "0 1_8191 8114 NaN 834.0 0 \n", "1 1_14792 2 NaN 251178.0 0 \n", "2 1_30466 2 NaN 2355.0 0 \n", "3 1_41898 20244 203714.0 97973.0 0 \n", "4 1_58746 2 NaN 82026.0 1 \n", "... ... ... ... ... ... \n", "186115 4_24295 103884 NaN 96913.0 0 \n", "186116 4_44443 43315 NaN 234734.0 0 \n", "186117 4_3343947 2 NaN NaN 1 \n", "186118 4_47752 46460 NaN 89791.0 0 \n", "186119 4_35449 34592 NaN 119197.0 0 \n", "\n", " tenant_id is_partner deleted_at is_email_true opt_in ... \\\n", "0 1311 False NaN True 1 ... \n", "1 1311 False NaN True 1 ... \n", "2 1311 False NaN True 1 ... \n", "3 1311 False NaN True 1 ... \n", "4 1311 False NaN True 1 ... \n", "... ... ... ... ... ... ... \n", "186115 1342 False NaN True 1 ... \n", "186116 1342 False NaN True 0 ... \n", "186117 1342 False NaN True 0 ... \n", "186118 1342 False NaN True 1 ... \n", "186119 1342 False NaN True 1 ... \n", "\n", " has_purchased has_purchased_estim score quartile score_adjusted \\\n", "0 0.0 0.0 0.408546 2 0.027066 \n", "1 0.0 0.0 0.027046 1 0.001118 \n", "2 0.0 0.0 0.180851 1 0.008813 \n", "3 0.0 0.0 0.220872 1 0.011288 \n", "4 0.0 0.0 0.100951 1 0.004502 \n", "... ... ... ... ... ... \n", "186115 0.0 0.0 0.466644 2 0.034037 \n", "186116 0.0 0.0 0.427641 2 0.029211 \n", "186117 0.0 0.0 0.468464 2 0.034278 \n", "186118 0.0 0.0 0.360100 2 0.022161 \n", "186119 0.0 1.0 0.728907 3 0.097705 \n", "\n", " nb_tickets_projected total_amount_projected nb_tickets_expected \\\n", "0 0.000000 0.000000 0.000000 \n", "1 0.000000 0.000000 0.000000 \n", "2 0.000000 0.000000 0.000000 \n", "3 0.000000 0.000000 0.000000 \n", "4 0.000000 0.000000 0.000000 \n", "... ... ... ... \n", "186115 0.000000 0.000000 0.000000 \n", "186116 0.000000 0.000000 0.000000 \n", "186117 0.705882 20.470588 0.024196 \n", "186118 0.000000 0.000000 0.000000 \n", "186119 0.000000 0.000000 0.000000 \n", "\n", " total_amount_expected pace_purchase \n", "0 0.000000 NaN \n", "1 0.000000 NaN \n", "2 0.000000 NaN \n", "3 0.000000 NaN \n", "4 0.000000 NaN \n", "... ... ... \n", "186115 0.000000 NaN \n", "186116 0.000000 NaN \n", "186117 0.701686 17.0 \n", "186118 0.000000 NaN \n", "186119 0.000000 NaN \n", "\n", "[186120 rows x 97 columns]" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_test_segment = project_tickets_CA (X_test_segment, \"nb_purchases\", \"nb_tickets\", \"total_amount\", \"score_adjusted\", \n", " duration_ref=17, duration_projection=12)\n", "X_test_segment" ] }, { "cell_type": "code", "execution_count": 42, "id": "cb66a8ea-65f7-460f-b3fc-ba76a3b91faa", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "quartile\n", "1 16.722853\n", "2 16.568788\n", "3 15.765899\n", "4 13.263500\n", "Name: pace_purchase, dtype: float64" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_test_segment.groupby(\"quartile\")[\"pace_purchase\"].mean()" ] }, { "cell_type": "code", "execution_count": 43, "id": "f58f9151-2f91-45df-abb7-1ddcf0652adc", "metadata": {}, "outputs": [], "source": [ "# generalization with a function\n", "\n", "def summary_expected_CA(df, segment, nb_tickets_expected, total_amount_expected, total_amount, pace_purchase,\n", " duration_ref=17, duration_projection=12) :\n", " \n", " # compute nb tickets estimated and total amount expected\n", " df_expected_CA = df.groupby(segment)[[nb_tickets_expected, total_amount_expected]].sum().reset_index()\n", " \n", " # number of customers by segment\n", " df_expected_CA.insert(1, \"size\", df.groupby(segment).size().values)\n", " \n", " # size in percent of all customers\n", " df_expected_CA.insert(2, \"size_perct\", 100 * df_expected_CA[\"size\"]/df_expected_CA[\"size\"].sum())\n", " \n", " # compute share of CA recovered\n", " duration_ratio=duration_ref/duration_projection\n", " \n", " df_expected_CA[\"revenue_recovered_perct\"] = 100 * duration_ratio * df_expected_CA[total_amount_expected] / \\\n", " df.groupby(segment)[total_amount].sum().values\n", "\n", " df_drop_null_pace = df.dropna(subset=[pace_purchase])\n", " df_expected_CA[\"pace_purchase\"] = df_drop_null_pace.groupby(segment)[pace_purchase].mean().values\n", " \n", " return df_expected_CA" ] }, { "cell_type": "code", "execution_count": 44, "id": "c8df6c80-43e8-4f00-9cd3-eb9022744313", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
quartilesizesize_perctnb_tickets_expectedtotal_amount_expectedrevenue_recovered_perctpace_purchase
018162243.85263.123258.540.8816.72
126081132.671984.5627052.822.4716.57
232891315.533476.6343945.796.3415.77
34147747.9458598.68523568.9360.0313.26
\n", "
" ], "text/plain": [ " quartile size size_perct nb_tickets_expected total_amount_expected \\\n", "0 1 81622 43.85 263.12 3258.54 \n", "1 2 60811 32.67 1984.56 27052.82 \n", "2 3 28913 15.53 3476.63 43945.79 \n", "3 4 14774 7.94 58598.68 523568.93 \n", "\n", " revenue_recovered_perct pace_purchase \n", "0 0.88 16.72 \n", "1 2.47 16.57 \n", "2 6.34 15.77 \n", "3 60.03 13.26 " ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_test_expected_CA = round(summary_expected_CA(df=X_test_segment, segment=\"quartile\", \n", " nb_tickets_expected=\"nb_tickets_expected\", total_amount_expected=\"total_amount_expected\", \n", " total_amount=\"total_amount\", pace_purchase=\"pace_purchase\"),2)\n", "\n", "X_test_expected_CA" ] }, { "cell_type": "code", "execution_count": 64, "id": "ac706ed7-defa-4df1-82e1-06f12fc1b6ad", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'\\\\begin{tabular}{lrrrrrr}\\n\\\\toprule\\nquartile & size & size (%) & nb tickets expected & total amount expected & revenue recovered (%) & pace purchase \\\\\\\\\\n\\\\midrule\\n1 & 53626 & 35.310000 & 398.260000 & 13949.330000 & 2.350000 & 16.480000 \\\\\\\\\\n2 & 55974 & 36.860000 & 3113.770000 & 101639.450000 & 6.240000 & 16.470000 \\\\\\\\\\n3 & 30435 & 20.040000 & 6214.350000 & 208267.220000 & 14.270000 & 15.710000 \\\\\\\\\\n4 & 11839 & 7.800000 & 72929.460000 & 1835702.430000 & 75.380000 & 11.480000 \\\\\\\\\\n\\\\bottomrule\\n\\\\end{tabular}\\n'" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Création du dictionnaire de mapping pour les noms de colonnes\n", "mapping_dict = {col: col.replace(\"perct\", \"(%)\").replace(\"_\", \" \") for col in X_test_expected_CA.columns}\n", "\n", "X_test_expected_CA.rename(columns=mapping_dict).to_latex(index=False)" ] }, { "cell_type": "code", "execution_count": 122, "id": "771da0cf-c49f-4e7e-b52f-ebcfb0fb2df3", "metadata": {}, "outputs": [], "source": [ "# export summary table to the MinIO storage\n", "\n", "file_name = \"table_expected_CA_\"\n", "FILE_PATH_OUT_S3 = PATH + file_name + type_of_activity + \".csv\"\n", "with fs.open(FILE_PATH_OUT_S3, 'w') as file_out:\n", " X_test_expected_CA.to_csv(file_out, index = False)" ] }, { "cell_type": "code", "execution_count": 53, "id": "c805dc10-4d07-4f7d-a677-5461a92845d7", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'projet-bdc2324-team1/Output_expected_CA/musique/table_expected_CA_musique.csv'" ] }, "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ "PATH = f\"projet-bdc2324-team1/Output_expected_CA/{type_of_activity}/\"\n", "file_name = \"table_expected_CA_\"\n", "FILE_PATH_OUT_S3 = PATH + file_name + type_of_activity + \".csv\"\n", "FILE_PATH_OUT_S3" ] }, { "cell_type": "markdown", "id": "e35ccfff-1845-41f0-9bde-f09b09b67877", "metadata": {}, "source": [ "## Test : vizu tables saved" ] }, { "cell_type": "code", "execution_count": 66, "id": "4e9e88e4-ea10-41f4-9bf1-20b55269a20d", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
quartilescore (%)score adjusted (%)has purchased (%)
0113.252.511.57
1233.898.009.85
2363.0622.5821.47
3490.5266.2065.01
\n", "
" ], "text/plain": [ " quartile score (%) score adjusted (%) has purchased (%)\n", "0 1 13.25 2.51 1.57\n", "1 2 33.89 8.00 9.85\n", "2 3 63.06 22.58 21.47\n", "3 4 90.52 66.20 65.01" ] }, "execution_count": 66, "metadata": {}, "output_type": "execute_result" } ], "source": [ "path = 'projet-bdc2324-team1/Output_expected_CA/sport/table_adjusted_scoresport.csv'\n", "\n", "with fs.open( path, mode=\"rb\") as file_in:\n", " df = pd.read_csv(file_in, sep=\",\")\n", "df" ] }, { "cell_type": "markdown", "id": "9c471bdd-25c2-420a-a8a1-3add9f003cbc", "metadata": {}, "source": [ "## Just to try, same computation with score instead of score adjusted\n", "\n", "seems overestimated : if only 14% of customers come back, how can we recover 22% of the revenue from the segment that is least likely to buy ?? ..." ] }, { "cell_type": "code", "execution_count": 80, "id": "53684a24-1809-465f-8e21-b9295e34582a", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
quartilesizesize_perctnb_tickets_expectedtotal_amount_expectedperct_revenue_recovered
013741038.93419.769245.0821.71
122951730.7211549.06296522.0239.24
232013720.9629997.85954751.9163.34
3490329.40244655.8210736011.9597.72
\n", "
" ], "text/plain": [ " quartile size size_perct nb_tickets_expected total_amount_expected \\\n", "0 1 37410 38.93 419.76 9245.08 \n", "1 2 29517 30.72 11549.06 296522.02 \n", "2 3 20137 20.96 29997.85 954751.91 \n", "3 4 9032 9.40 244655.82 10736011.95 \n", "\n", " perct_revenue_recovered \n", "0 21.71 \n", "1 39.24 \n", "2 63.34 \n", "3 97.72 " ] }, "execution_count": 80, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_test_segment_bis = project_tickets_CA (X_test_segment, \"nb_tickets\", \"total_amount\", \"score\", duration_ref=1.5, duration_projection=1)\n", "\n", "X_test_expected_CA_bis = round(summary_expected_CA(df=X_test_segment_bis, segment=\"quartile\", nb_tickets_expected=\"nb_tickets_expected\", \n", " total_amount_expected=\"total_amount_expected\", total_amount=\"total_amount\"),2)\n", "\n", "X_test_expected_CA_bis" ] }, { "cell_type": "code", "execution_count": 81, "id": "7dc66d1e-da03-4513-96e4-d9a43ac0a2c8", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "overall share of revenue recovered : 90.26 %\n" ] } ], "source": [ "print(\"overall share of revenue recovered : \", round(100 * duration_ratio * X_test_expected_CA_bis[\"total_amount_expected\"].sum() / \\\n", "X_test_segment_bis[\"total_amount\"].sum(),2), \"%\")" ] }, { "cell_type": "markdown", "id": "673f2969-7b9a-44c1-abf5-5679fca877ce", "metadata": {}, "source": [ "## Last pieces of analysis" ] }, { "cell_type": "code", "execution_count": 161, "id": "2365bb13-0f3f-49d5-bf91-52c92abebcee", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "overall share of revenue recovered : 77.64%\n" ] } ], "source": [ "# global revenue recovered\n", "global_revenue_recovered = round(100 * duration_ratio * X_test_expected_CA[\"total_amount_expected\"].sum() / \\\n", "X_test_segment[\"total_amount\"].sum(),2)\n", "print(f\"overall share of revenue recovered : {global_revenue_recovered}%\")" ] }, { "cell_type": "code", "execution_count": 163, "id": "16b17f35-57dd-459a-8989-129143dc0952", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0 0.018093\n", "1 0.721519\n", "2 3.336101\n", "3 95.924287\n", "Name: total_amount_expected, dtype: float64" ] }, "execution_count": 163, "metadata": {}, "output_type": "execute_result" } ], "source": [ "100 * X_test_expected_CA[\"total_amount_expected\"]/X_test_expected_CA[\"total_amount_expected\"].sum()" ] }, { "cell_type": "code", "execution_count": 166, "id": "dee4a200-eefe-4377-8e80-59ad33edd3c0", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "quartile\n", "1 0.320407\n", "2 5.685020\n", "3 11.339715\n", "4 82.654858\n", "Name: total_amount, dtype: float64" ] }, "execution_count": 166, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# le segment 4 représente 83% du CA actuel et 96% du CA lié aux anciens clients pour l'année prochaine\n", "100 * X_test_segment.groupby(\"quartile\")[\"total_amount\"].sum()/X_test_segment[\"total_amount\"].sum()" ] }, { "cell_type": "code", "execution_count": 177, "id": "c1e6f020-ef18-40b4-bfc1-19f98cb2796e", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "count 96096.000000\n", "mean 207.475735\n", "std 4720.046248\n", "min -48831.800000\n", "25% 0.000000\n", "50% 0.000000\n", "75% 60.000000\n", "max 624890.000000\n", "Name: total_amount, dtype: float64" ] }, "execution_count": 177, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_test_segment[\"total_amount\"].describe() # total amount négatif ???\n" ] }, { "cell_type": "code", "execution_count": 184, "id": "d301a50e-7c68-40f0-9245-a4eea64c387b", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0 -4.883180e+04\n", "1 -6.483180e+04\n", "2 -7.683860e+04\n", "3 -8.683860e+04\n", "4 -9.683860e+04\n", " ... \n", "96091 1.802247e+07\n", "96092 1.839238e+07\n", "96093 1.877219e+07\n", "96094 1.931270e+07\n", "96095 1.993759e+07\n", "Name: total_amount, Length: 96096, dtype: float64" ] }, "execution_count": 184, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.cumsum(X_test_segment[\"total_amount\"].sort_values()).reset_index()[\"total_amount\"]" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.6" } }, "nbformat": 4, "nbformat_minor": 5 }