{ "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": 4, "id": "d6017ed0-6233-4888-85a7-05dec50a255b", "metadata": {}, "outputs": [], "source": [ "type_of_activity = \"sport\"" ] }, { "cell_type": "code", "execution_count": 7, "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/1_Temp/1_0_Modelling_Datasets/{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": 8, "id": "2831d546-b365-498b-8248-c618bd9c3057", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/tmp/ipykernel_427/290017524.py:8: DtypeWarning: Columns (10,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_427/290017524.py:12: DtypeWarning: Columns (10,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 222819\n", "mcp_contact_id 70845\n", "fidelity 0\n", " ... \n", "purchases_8_2021 0\n", "purchases_8_2022 0\n", "purchases_9_2021 0\n", "purchases_9_2022 0\n", "y_has_purchased 0\n", "Length: 87, dtype: int64" ] }, "execution_count": 8, "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": 9, "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": 10, "id": "c18195fc-ed40-4e39-a59e-c9ecc5a8e6c3", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Shape train : (224213, 87)\n", "Shape test : (96096, 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": 11, "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
05_4317407969908NaN6156473.011771FalseNaNTrue0...0.00.00.00.00.00.00.00.00.00.0
15_477635109121NaN6213652.021771FalseNaNTrue0...0.00.00.00.00.00.00.00.00.00.0
25_41163992929NaN6160271.041771FalseNaNTrue0...0.00.00.00.00.00.00.00.00.00.0
35_32662379862NaN6140109.011771FalseNaNTrue1...0.00.00.00.00.00.00.01.00.00.0
45_38391585421NaN6149409.021771FalseNaNTrue1...0.00.00.00.00.00.00.00.00.00.0
..................................................................
960919_9120576215NaN47280.001490FalseNaNTrue1...0.00.00.00.00.00.00.00.00.00.0
960929_369887815891NaN30764537.041490FalseNaNTrue0...0.00.00.00.00.00.00.00.00.01.0
960939_10075621NaNNaN01490FalseNaNTrue0...0.00.00.00.00.00.00.00.00.00.0
960949_1503712992NaN2213448.001490FalseNaNTrue1...0.00.00.00.00.00.00.00.00.00.0
960959_13537076215NaN2164740.001490FalseNaNTrue1...0.00.00.00.00.00.00.00.00.00.0
\n", "

96096 rows × 87 columns

\n", "
" ], "text/plain": [ " customer_id street_id structure_id mcp_contact_id fidelity \\\n", "0 5_4317407 969908 NaN 6156473.0 1 \n", "1 5_477635 109121 NaN 6213652.0 2 \n", "2 5_411639 92929 NaN 6160271.0 4 \n", "3 5_326623 79862 NaN 6140109.0 1 \n", "4 5_383915 85421 NaN 6149409.0 2 \n", "... ... ... ... ... ... \n", "96091 9_91205 76215 NaN 47280.0 0 \n", "96092 9_369887 815891 NaN 30764537.0 4 \n", "96093 9_1007562 1 NaN NaN 0 \n", "96094 9_15037 12992 NaN 2213448.0 0 \n", "96095 9_135370 76215 NaN 2164740.0 0 \n", "\n", " tenant_id is_partner deleted_at is_email_true opt_in ... \\\n", "0 1771 False NaN True 0 ... \n", "1 1771 False NaN True 0 ... \n", "2 1771 False NaN True 0 ... \n", "3 1771 False NaN True 1 ... \n", "4 1771 False NaN True 1 ... \n", "... ... ... ... ... ... ... \n", "96091 1490 False NaN True 1 ... \n", "96092 1490 False NaN True 0 ... \n", "96093 1490 False NaN True 0 ... \n", "96094 1490 False NaN True 1 ... \n", "96095 1490 False NaN True 1 ... \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", "96091 0.0 0.0 0.0 0.0 \n", "96092 0.0 0.0 0.0 0.0 \n", "96093 0.0 0.0 0.0 0.0 \n", "96094 0.0 0.0 0.0 0.0 \n", "96095 0.0 0.0 0.0 0.0 \n", "\n", " purchases_7_2022 purchases_8_2021 purchases_8_2022 purchases_9_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 1.0 \n", "4 0.0 0.0 0.0 0.0 \n", "... ... ... ... ... \n", "96091 0.0 0.0 0.0 0.0 \n", "96092 0.0 0.0 0.0 0.0 \n", "96093 0.0 0.0 0.0 0.0 \n", "96094 0.0 0.0 0.0 0.0 \n", "96095 0.0 0.0 0.0 0.0 \n", "\n", " purchases_9_2022 y_has_purchased \n", "0 0.0 0.0 \n", "1 0.0 0.0 \n", "2 0.0 0.0 \n", "3 0.0 0.0 \n", "4 0.0 0.0 \n", "... ... ... \n", "96091 0.0 0.0 \n", "96092 0.0 1.0 \n", "96093 0.0 0.0 \n", "96094 0.0 0.0 \n", "96095 0.0 0.0 \n", "\n", "[96096 rows x 87 columns]" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_test" ] }, { "cell_type": "code", "execution_count": 12, "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": 13, "id": "5261a803-05b8-41a0-968c-dc7bde48ddd3", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
GridSearchCV(cv=3, error_score='raise',\n",
       "             estimator=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',\n",
       "                                                                          'nb_purchases',\n",
       "                                                                          'total_amount',\n",
       "                                                                          'nb_suppliers',\n",
       "                                                                          'pu...\n",
       "       1.562500e-02, 3.125000e-02, 6.250000e-02, 1.250000e-01,\n",
       "       2.500000e-01, 5.000000e-01, 1.000000e+00, 2.000000e+00,\n",
       "       4.000000e+00, 8.000000e+00, 1.600000e+01, 3.200000e+01,\n",
       "       6.400000e+01]),\n",
       "                         'LogisticRegression_cv__class_weight': ['balanced',\n",
       "                                                                 {0.0: 0.5834990214856762,\n",
       "                                                                  1.0: 3.49404706249026}],\n",
       "                         'LogisticRegression_cv__penalty': ['l1', 'l2']},\n",
       "             scoring=make_scorer(recall_score, response_method='predict'))
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": [ "GridSearchCV(cv=3, error_score='raise',\n", " estimator=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',\n", " 'nb_purchases',\n", " 'total_amount',\n", " 'nb_suppliers',\n", " 'pu...\n", " 1.562500e-02, 3.125000e-02, 6.250000e-02, 1.250000e-01,\n", " 2.500000e-01, 5.000000e-01, 1.000000e+00, 2.000000e+00,\n", " 4.000000e+00, 8.000000e+00, 1.600000e+01, 3.200000e+01,\n", " 6.400000e+01]),\n", " 'LogisticRegression_cv__class_weight': ['balanced',\n", " {0.0: 0.5834990214856762,\n", " 1.0: 3.49404706249026}],\n", " 'LogisticRegression_cv__penalty': ['l1', 'l2']},\n", " scoring=make_scorer(recall_score, response_method='predict'))" ] }, "execution_count": 13, "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 = load_model(type_of_activity, \"LogisticRegression_cv\")\n", "model" ] }, { "cell_type": "markdown", "id": "006819e7-e9c5-48d9-85ee-aa43d5e4c9c2", "metadata": {}, "source": [ "## Quartile clustering" ] }, { "cell_type": "code", "execution_count": 14, "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": 15, "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_7_2022purchases_8_2021purchases_8_2022purchases_9_2021purchases_9_2022y_has_purchasedhas_purchasedhas_purchased_estimscorequartile
05_4317407969908NaN6156473.011771FalseNaNTrue0...0.00.00.00.00.00.00.00.00.4450192
15_477635109121NaN6213652.021771FalseNaNTrue0...0.00.00.00.00.00.00.00.00.3825862
25_41163992929NaN6160271.041771FalseNaNTrue0...0.00.00.00.00.00.00.01.00.9167474
35_32662379862NaN6140109.011771FalseNaNTrue1...0.00.00.01.00.00.00.00.00.0905341
45_38391585421NaN6149409.021771FalseNaNTrue1...0.00.00.00.00.00.00.00.00.3465712
55_233172141401NaN3324.011771FalseNaNTrue1...0.00.00.00.01.00.00.01.00.9246844
65_38999995759NaN6151025.011771FalseNaNTrue0...0.00.00.00.00.00.00.01.00.5690313
75_429221178897NaN4729841.011771FalseNaNTrue1...0.00.00.00.00.00.00.00.00.1256221
85_35355384189NaN6146995.011771FalseNaNTrue1...0.00.00.00.00.00.00.00.00.2294321
95_4012963491NaN6155457.011771FalseNaNTrue0...0.00.00.00.00.00.00.01.00.5039873
\n", "

10 rows × 91 columns

\n", "
" ], "text/plain": [ " customer_id street_id structure_id mcp_contact_id fidelity tenant_id \\\n", "0 5_4317407 969908 NaN 6156473.0 1 1771 \n", "1 5_477635 109121 NaN 6213652.0 2 1771 \n", "2 5_411639 92929 NaN 6160271.0 4 1771 \n", "3 5_326623 79862 NaN 6140109.0 1 1771 \n", "4 5_383915 85421 NaN 6149409.0 2 1771 \n", "5 5_233172 141401 NaN 3324.0 1 1771 \n", "6 5_389999 95759 NaN 6151025.0 1 1771 \n", "7 5_4292211 78897 NaN 4729841.0 1 1771 \n", "8 5_353553 84189 NaN 6146995.0 1 1771 \n", "9 5_401296 3491 NaN 6155457.0 1 1771 \n", "\n", " is_partner deleted_at is_email_true opt_in ... purchases_7_2022 \\\n", "0 False NaN True 0 ... 0.0 \n", "1 False NaN True 0 ... 0.0 \n", "2 False NaN True 0 ... 0.0 \n", "3 False NaN True 1 ... 0.0 \n", "4 False NaN True 1 ... 0.0 \n", "5 False NaN True 1 ... 0.0 \n", "6 False NaN True 0 ... 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_2021 purchases_8_2022 purchases_9_2021 purchases_9_2022 \\\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 1.0 0.0 \n", "4 0.0 0.0 0.0 0.0 \n", "5 0.0 0.0 0.0 1.0 \n", "6 0.0 0.0 0.0 0.0 \n", "7 0.0 0.0 0.0 0.0 \n", "8 0.0 0.0 0.0 0.0 \n", "9 0.0 0.0 0.0 0.0 \n", "\n", " y_has_purchased has_purchased has_purchased_estim score quartile \n", "0 0.0 0.0 0.0 0.445019 2 \n", "1 0.0 0.0 0.0 0.382586 2 \n", "2 0.0 0.0 1.0 0.916747 4 \n", "3 0.0 0.0 0.0 0.090534 1 \n", "4 0.0 0.0 0.0 0.346571 2 \n", "5 0.0 0.0 1.0 0.924684 4 \n", "6 0.0 0.0 1.0 0.569031 3 \n", "7 0.0 0.0 0.0 0.125622 1 \n", "8 0.0 0.0 0.0 0.229432 1 \n", "9 0.0 0.0 1.0 0.503987 3 \n", "\n", "[10 rows x 91 columns]" ] }, "execution_count": 15, "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": 16, "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": 17, "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": 18, "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": 19, "id": "2dff1def-02df-413e-afce-b4aeaf7752b6", "metadata": {}, "outputs": [], "source": [ "def odd_ratio(score) :\n", " return score / (1 - score)" ] }, { "cell_type": "code", "execution_count": 20, "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": 21, "id": "f17dc6ca-7a48-441b-8c04-11c47b8b9741", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.31861289893787315 0.14317973692973693\n" ] }, { "data": { "text/plain": [ "0.14310053386734936" ] }, "execution_count": 21, "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": 22, "id": "781b0d40-c954-4c54-830a-e709c8667328", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5.939748066330849" ] }, "execution_count": 22, "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": 23, "id": "248cb862-418e-4767-9933-70c4885ecf40", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "6.01952986090399" ] }, "execution_count": 23, "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": 24, "id": "fff6cbe6-7bb3-4732-9b81-b9ac5383bbcf", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "betâ test - betâ train = -0.013342440676233564\n" ] } ], "source": [ "print(\"betâ test - betâ train = \",np.log(bias_test_set/bias_train_set))" ] }, { "cell_type": "code", "execution_count": 25, "id": "f506870d-4a8a-4b2c-8f0b-e0789080b20c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "mean absolute erreur 0.0009061459618344602\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": 26, "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": 27, "id": "834d3723-2e72-4c65-9c62-e2d595c69461", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MSE for score : 0.11809894130837426\n", "MSE for ajusted score : 0.07434720017843571\n", "sum of y_has_purchased : 13759.0\n", "sum of adjusted scores : 13671.922997651252\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": 28, "id": "9f30a4dd-a9d8-405a-a7d5-5324ae88cf70", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MAE for score : 0.24742788848313355\n", "MAE for adjusted score : 0.14205672428104504\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": 30, "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": 31, "id": "b478d40d-9677-4204-87bd-16fb0bc1fe9a", "metadata": {}, "outputs": [ { "data": { "image/png": "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", "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": 32, "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 (%)
0110.201.941.19
1237.089.1210.62
2362.0722.0028.67
3490.3567.1663.09
\n", "
" ], "text/plain": [ " quartile score (%) score adjusted (%) has purchased (%)\n", "0 1 10.20 1.94 1.19\n", "1 2 37.08 9.12 10.62\n", "2 3 62.07 22.00 28.67\n", "3 4 90.35 67.16 63.09" ] }, "execution_count": 32, "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": 129, "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", " # project number of tickets : at least 1 ticket purchased if the customer purchased\n", " df_output.loc[:,\"nb_tickets_projected\"] = df_output.loc[:,nb_tickets].apply(lambda x : max(1, x /duration_ratio))\n", "\n", " # project amount : if the customer buys a ticket, we expect the amount to be at least the average price of tickets \n", " # for customers purchasing exactly one ticket\n", " if df_output.loc[df_output[nb_tickets]==1].shape[0] > 0 :\n", " avg_price = df_output.loc[df_output[nb_tickets]==1][total_amount].mean()\n", " else :\n", " avg_price = df_output[total_amount].mean()\n", " # df_output.loc[:,\"total_amount_projected\"] = df_output.loc[:,total_amount] / duration_ratio\n", " # df_output.loc[:,\"total_amount_projected\"] = df_output.loc[:,total_amount].apply(lambda x : max(avg_ticket_price, x/duration_ratio))\n", "\n", " # we compute the avg price of ticket for each customer\n", " df_output[\"avg_ticket_price\"] = df_output[total_amount]/df_output[nb_tickets]\n", "\n", " # correct negatives total amounts\n", " df_output.loc[:,\"total_amount_corrected\"] = np.where(df_output[total_amount] < 0, \n", " avg_price * df_output[nb_tickets],\n", " df_output[total_amount])\n", " \n", " df_output.loc[:,\"total_amount_projected\"] = np.where(\n", " # if no ticket bought in the past, we take the average price\n", " df_output[nb_tickets]==0, avg_price,\n", " # if avg prices of tickets are negative, we recompute the expected amount based on the avg price of a ticket\n", " # observed on the whole population\n", " np.where(X_test_segment[\"avg_ticket_price\"] < 0, avg_price * df_output.loc[:,\"nb_tickets_projected\"],\n", " # else, the amount projected is the average price of tickets bought by the customer * nb tickets projected\n", " df_output[\"avg_ticket_price\"] * df_output.loc[:,\"nb_tickets_projected\"])\n", " )\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": 120, "id": "87fb8e1c-3567-46df-9e98-197b7ca3becd", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([25., 92., 45., ..., 0., 0., 0.])" ] }, "execution_count": 120, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.where(X_test_segment[\"total_amount\"] < 0, avg_price * X_test_segment[\"nb_tickets\"],\n", " X_test_segment[\"total_amount\"]\n", ")" ] }, { "cell_type": "code", "execution_count": 121, "id": "dc0cdf9c-c55c-4085-80a6-c2131bb22ad4", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0 25.00\n", "1 92.00\n", "2 45.00\n", "3 10.00\n", "4 127.00\n", " ... \n", "96091 0.00\n", "96092 100.89\n", "96093 0.00\n", "96094 0.00\n", "96095 0.00\n", "Name: total_amount, Length: 96096, dtype: float64" ] }, "execution_count": 121, "metadata": {}, "output_type": "execute_result" } ], "source": [ " X_test_segment[\"total_amount\"]" ] }, { "cell_type": "code", "execution_count": 105, "id": "51455654-e6de-4608-8fbe-594d7fcd5b53", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(0, 98)" ] }, "execution_count": 105, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_test_segment.loc[X_test_segment[\"nb_tickets\"]==-1].shape[0°" ] }, { "cell_type": "code", "execution_count": 71, "id": "a0d08a46-93d0-425a-9a56-28cf8bfd93e9", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "count 4.410500e+04\n", "mean 4.640310e+02\n", "std 1.049793e+04\n", "min -2.064700e+04\n", "25% 3.000000e+01\n", "50% 6.900000e+01\n", "75% 1.339900e+02\n", "max 1.209751e+06\n", "Name: total_amount, dtype: float64" ] }, "execution_count": 71, "metadata": {}, "output_type": "execute_result" } ], "source": [ "duration_ratio = 17/12\n", "X_test_segment.loc[X_test_segment[\"nb_tickets\"]>0][\"total_amount\"].describe()" ] }, { "cell_type": "code", "execution_count": 74, "id": "dc7de319-6d22-44f0-9e58-492088b0dd5f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "count 96096.000000\n", "mean 183.851977\n", "std 5021.379770\n", "min 48.713098\n", "25% 48.713098\n", "50% 48.713098\n", "75% 48.713098\n", "max 853942.164706\n", "Name: total_amount, dtype: float64" ] }, "execution_count": 74, "metadata": {}, "output_type": "execute_result" } ], "source": [ "avg_price = X_test_segment.loc[X_test_segment[\"nb_tickets\"]==1][\"total_amount\"].mean()\n", "X_test_segment[\"total_amount\"].apply(lambda x : max(avg_price, x/duration_ratio)).describe()" ] }, { "cell_type": "code", "execution_count": 76, "id": "8aa50962-067b-493a-8766-258547da8bcd", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "count 96096.000000\n", "mean 150.335598\n", "std 5022.896337\n", "min -14574.352941\n", "25% 0.000000\n", "50% 0.000000\n", "75% 42.352941\n", "max 853942.164706\n", "Name: total_amount, dtype: float64" ] }, "execution_count": 76, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_test_segment[\"total_amount\"].apply(lambda x : x/duration_ratio).describe()" ] }, { "cell_type": "code", "execution_count": 57, "id": "f2f04205-7b8b-4978-9b4f-1c83034628fe", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0 1.411765\n", "1 1.411765\n", "2 2.117647\n", "3 0.705882\n", "4 5.647059\n", " ... \n", "96091 0.000000\n", "96092 1.411765\n", "96093 0.000000\n", "96094 0.000000\n", "96095 0.000000\n", "Name: nb_tickets, Length: 96096, dtype: float64" ] }, "execution_count": 57, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_test_segment.loc[:,\"nb_tickets\"]/duration_ratio" ] }, { "cell_type": "code", "execution_count": 81, "id": "140e09b9-f6b8-4075-b380-86851e1596f1", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "count 96096.000000\n", "mean 176.690937\n", "std 5022.166115\n", "min -14574.352941\n", "25% 48.713098\n", "50% 48.713098\n", "75% 48.713098\n", "max 853942.164706\n", "dtype: float64" ] }, "execution_count": 81, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pd.Series(np.where(X_test_segment[\"nb_tickets\"]==0, avg_price, X_test_segment[\"nb_tickets_projected\"] * X_test_segment[\"total_amount\"]/X_test_segment[\"nb_tickets\"])).describe()" ] }, { "cell_type": "code", "execution_count": 87, "id": "b2c8c7dd-9cd2-40b8-945f-0daf27b3b66b", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "count 162.000000\n", "mean 51.283951\n", "std 135.183724\n", "min 1.000000\n", "25% 2.000000\n", "50% 6.000000\n", "75% 31.500000\n", "max 1038.000000\n", "Name: nb_tickets, dtype: float64" ] }, "execution_count": 87, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_test_segment[X_test_segment[\"total_amount\"]<0][\"nb_tickets\"].describe()" ] }, { "cell_type": "code", "execution_count": 89, "id": "44ce62e3-fae6-4192-b8dd-386fd84fed22", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "count 44105.000000\n", "mean 35.661188\n", "std 71.477667\n", "min -216.368182\n", "25% 10.000000\n", "50% 25.000000\n", "75% 48.720000\n", "max 4000.000000\n", "Name: avg_ticket_price, dtype: float64" ] }, "execution_count": 89, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# code pr projet revenue\n", "\n", "X_test_segment[\"avg_ticket_price\"] = X_test_segment[\"total_amount\"]/X_test_segment[\"nb_tickets\"]\n", "X_test_segment[\"avg_ticket_price\"].describe()" ] }, { "cell_type": "code", "execution_count": 97, "id": "e1c0671a-2b5f-48bf-b964-6bee8a4223ac", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "count 96096.000000\n", "mean 180.394197\n", "std 5025.591726\n", "min 0.000000\n", "25% 48.713098\n", "50% 48.713098\n", "75% 48.713098\n", "max 853942.164706\n", "dtype: float64" ] }, "execution_count": 97, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pd.Series(\n", " np.where(X_test_segment[\"nb_tickets\"]==0, avg_price,\n", " \n", " np.where(X_test_segment[\"avg_ticket_price\"] < 0, avg_price * X_test_segment[\"nb_tickets\"] / duration_ratio,\n", " X_test_segment[\"avg_ticket_price\"] * X_test_segment[\"nb_tickets\"] / duration_ratio)\n", " )\n", ").describe()" ] }, { "cell_type": "code", "execution_count": 100, "id": "6c1e0649-3be1-4754-a86c-24b46a12d523", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "count 5058.000000\n", "mean 13.671807\n", "std 155.341970\n", "min 1.000000\n", "25% 1.000000\n", "50% 2.000000\n", "75% 4.000000\n", "max 8250.000000\n", "Name: nb_tickets, dtype: float64" ] }, "execution_count": 100, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_test_segment[X_test_segment[\"avg_ticket_price\"] == 0][\"nb_tickets\"].describe()" ] }, { "cell_type": "code", "execution_count": null, "id": "2a4d1b0a-fe16-49e7-9b61-d822d2ed062a", "metadata": {}, "outputs": [], "source": [ "df['colonne2'] = np.where(df['colonne1'] > seuil2, df['colonne2'] * 2, # Si colonne1 > seuil2\n", " np.where(df['colonne1'] > seuil1, df['colonne2'] + 1, df['colonne2'])) " ] }, { "cell_type": "code", "execution_count": null, "id": "fa87726a-dee2-4b15-af2d-b22583a9eb53", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": 132, "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...scorequartilescore_adjustednb_tickets_projectedtotal_amount_projectednb_tickets_expectedtotal_amount_expectedpace_purchaseavg_ticket_pricetotal_amount_corrected
05_4317407969908NaN6156473.011771FalseNaNTrue0...0.44501920.1175511.41176517.6470590.1659552.07443217.00000012.50025.00
15_477635109121NaN6213652.021771FalseNaNTrue0...0.38258620.0933331.41176564.9411760.1317656.0611818.50000046.00092.00
25_41163992929NaN6160271.041771FalseNaNTrue0...0.91674740.6465562.11764731.7647061.36917820.5376705.66666715.00045.00
35_32662379862NaN6140109.011771FalseNaNTrue1...0.09053410.0162681.00000010.0000000.0162680.16268317.00000010.00010.00
45_38391585421NaN6149409.021771FalseNaNTrue1...0.34657120.0809765.64705989.6470590.4572797.2592988.50000015.875127.00
..................................................................
960919_9120576215NaN47280.001490FalseNaNTrue1...0.01496610.0025181.00000048.7130980.0025180.122642NaNNaN0.00
960929_369887815891NaN30764537.041490FalseNaNTrue0...0.83425740.4553921.41176571.2164710.64290632.4313798.50000050.445100.89
960939_10075621NaNNaN01490FalseNaNTrue0...0.06288610.0110251.00000048.7130980.0110250.537071NaNNaN0.00
960949_1503712992NaN2213448.001490FalseNaNTrue1...0.06899810.0121621.00000048.7130980.0121620.592451NaNNaN0.00
960959_13537076215NaN2164740.001490FalseNaNTrue1...0.01848610.0031191.00000048.7130980.0031190.151938NaNNaN0.00
\n", "

96096 rows × 99 columns

\n", "
" ], "text/plain": [ " customer_id street_id structure_id mcp_contact_id fidelity \\\n", "0 5_4317407 969908 NaN 6156473.0 1 \n", "1 5_477635 109121 NaN 6213652.0 2 \n", "2 5_411639 92929 NaN 6160271.0 4 \n", "3 5_326623 79862 NaN 6140109.0 1 \n", "4 5_383915 85421 NaN 6149409.0 2 \n", "... ... ... ... ... ... \n", "96091 9_91205 76215 NaN 47280.0 0 \n", "96092 9_369887 815891 NaN 30764537.0 4 \n", "96093 9_1007562 1 NaN NaN 0 \n", "96094 9_15037 12992 NaN 2213448.0 0 \n", "96095 9_135370 76215 NaN 2164740.0 0 \n", "\n", " tenant_id is_partner deleted_at is_email_true opt_in ... \\\n", "0 1771 False NaN True 0 ... \n", "1 1771 False NaN True 0 ... \n", "2 1771 False NaN True 0 ... \n", "3 1771 False NaN True 1 ... \n", "4 1771 False NaN True 1 ... \n", "... ... ... ... ... ... ... \n", "96091 1490 False NaN True 1 ... \n", "96092 1490 False NaN True 0 ... \n", "96093 1490 False NaN True 0 ... \n", "96094 1490 False NaN True 1 ... \n", "96095 1490 False NaN True 1 ... \n", "\n", " score quartile score_adjusted nb_tickets_projected \\\n", "0 0.445019 2 0.117551 1.411765 \n", "1 0.382586 2 0.093333 1.411765 \n", "2 0.916747 4 0.646556 2.117647 \n", "3 0.090534 1 0.016268 1.000000 \n", "4 0.346571 2 0.080976 5.647059 \n", "... ... ... ... ... \n", "96091 0.014966 1 0.002518 1.000000 \n", "96092 0.834257 4 0.455392 1.411765 \n", "96093 0.062886 1 0.011025 1.000000 \n", "96094 0.068998 1 0.012162 1.000000 \n", "96095 0.018486 1 0.003119 1.000000 \n", "\n", " total_amount_projected nb_tickets_expected total_amount_expected \\\n", "0 17.647059 0.165955 2.074432 \n", "1 64.941176 0.131765 6.061181 \n", "2 31.764706 1.369178 20.537670 \n", "3 10.000000 0.016268 0.162683 \n", "4 89.647059 0.457279 7.259298 \n", "... ... ... ... \n", "96091 48.713098 0.002518 0.122642 \n", "96092 71.216471 0.642906 32.431379 \n", "96093 48.713098 0.011025 0.537071 \n", "96094 48.713098 0.012162 0.592451 \n", "96095 48.713098 0.003119 0.151938 \n", "\n", " pace_purchase avg_ticket_price total_amount_corrected \n", "0 17.000000 12.500 25.00 \n", "1 8.500000 46.000 92.00 \n", "2 5.666667 15.000 45.00 \n", "3 17.000000 10.000 10.00 \n", "4 8.500000 15.875 127.00 \n", "... ... ... ... \n", "96091 NaN NaN 0.00 \n", "96092 8.500000 50.445 100.89 \n", "96093 NaN NaN 0.00 \n", "96094 NaN NaN 0.00 \n", "96095 NaN NaN 0.00 \n", "\n", "[96096 rows x 99 columns]" ] }, "execution_count": 132, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# project nb tickets and CA\n", "\n", "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": 124, "id": "22222709-218e-43b5-815f-714dfb776230", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "count 9.609600e+04\n", "mean 2.182217e+02\n", "std 7.120650e+03\n", "min 0.000000e+00\n", "25% 0.000000e+00\n", "50% 0.000000e+00\n", "75% 6.100000e+01\n", "max 1.209751e+06\n", "Name: total_amount_corrected, dtype: float64" ] }, "execution_count": 124, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_test_segment[\"total_amount_corrected\"].describe()" ] }, { "cell_type": "code", "execution_count": 111, "id": "73404bdd-e2f2-40e0-8bde-224c460426c5", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "count 44105.000000\n", "mean 35.661188\n", "std 71.477667\n", "min -216.368182\n", "25% 10.000000\n", "50% 25.000000\n", "75% 48.720000\n", "max 4000.000000\n", "Name: avg_ticket_price, dtype: float64" ] }, "execution_count": 111, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_test_segment[\"avg_ticket_price\"].describe()" ] }, { "cell_type": "code", "execution_count": 113, "id": "f96536d3-fff7-4ccf-be3d-34e671852cd8", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.052634865134865136" ] }, "execution_count": 113, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(X_test_segment[\"total_amount_projected\"]==0).mean()" ] }, { "cell_type": "code", "execution_count": 115, "id": "884416e8-edec-4f6b-a40f-1a7c5d653160", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "count 96096.000000\n", "mean 4.442483\n", "std 64.952589\n", "min 1.000000\n", "25% 1.000000\n", "50% 1.000000\n", "75% 1.411765\n", "max 11472.000000\n", "Name: nb_tickets_projected, dtype: float64" ] }, "execution_count": 115, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_test_segment[\"nb_tickets_projected\"].describe()" ] }, { "cell_type": "code", "execution_count": 35, "id": "cb66a8ea-65f7-460f-b3fc-ba76a3b91faa", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "quartile\n", "1 15.330011\n", "2 15.314322\n", "3 14.031588\n", "4 8.562546\n", "Name: pace_purchase, dtype: float64" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_test_segment.groupby(\"quartile\")[\"pace_purchase\"].mean()" ] }, { "cell_type": "code", "execution_count": 128, "id": "8a4eec5c-8a4d-4a2b-9afb-1d49c77f78ea", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "count 162.000000\n", "mean 3112.018089\n", "std 8392.717823\n", "min 51.843098\n", "25% 161.889295\n", "50% 395.635139\n", "75% 2141.696184\n", "max 69988.895986\n", "dtype: float64" ] }, "execution_count": 128, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(X_test[((X_test[\"total_amount_corrected\"] - X_test[\"total_amount\"])>0)][\"total_amount_corrected\"]\n", " -X_test[((X_test[\"total_amount_corrected\"] - X_test[\"total_amount\"])>0)][\"total_amount\"]) .describe()" ] }, { "cell_type": "code", "execution_count": 118, "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_expected_CA[\"share_future_revenue_perct\"] = 100 * duration_ratio * df_expected_CA[total_amount_expected] / \\\n", " df[total_amount].sum()\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": 133, "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", " \n", " \n", " \n", " \n", " \n", "
quartilesizesize_perctnb_tickets_expectedtotal_amount_expectedrevenue_recovered_perctshare_future_revenue_perctpace_purchase
015412356.321480.3655345.2111.990.3715.33
121818118.924381.84130503.2611.650.8815.31
231111111.568827.97285945.5024.001.9314.03
341268113.20239758.6110313321.9185.7469.678.56
\n", "
" ], "text/plain": [ " quartile size size_perct nb_tickets_expected total_amount_expected \\\n", "0 1 54123 56.32 1480.36 55345.21 \n", "1 2 18181 18.92 4381.84 130503.26 \n", "2 3 11111 11.56 8827.97 285945.50 \n", "3 4 12681 13.20 239758.61 10313321.91 \n", "\n", " revenue_recovered_perct share_future_revenue_perct pace_purchase \n", "0 11.99 0.37 15.33 \n", "1 11.65 0.88 15.31 \n", "2 24.00 1.93 14.03 \n", "3 85.74 69.67 8.56 " ] }, "execution_count": 133, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"\"\"\n", "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 = 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_corrected\", pace_purchase=\"pace_purchase\"),2)\n", "X_test_expected_CA" ] }, { "cell_type": "code", "execution_count": null, "id": "dd25c898-9991-4cc4-8e69-160b61fea0c4", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": 116, "id": "63369c2a-a842-4b03-aa11-230287cb3b69", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "count 96096.000000\n", "mean 4.442483\n", "std 64.952589\n", "min 1.000000\n", "25% 1.000000\n", "50% 1.000000\n", "75% 1.411765\n", "max 11472.000000\n", "Name: nb_tickets_projected, dtype: float64\n" ] }, { "data": { "text/plain": [ "count 96096.000000\n", "mean 2.647860\n", "std 59.108910\n", "min 0.001335\n", "25% 0.015281\n", "50% 0.044399\n", "75% 0.230742\n", "max 11450.589975\n", "Name: nb_tickets_expected, dtype: float64" ] }, "execution_count": 116, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print(X_test_segment[\"nb_tickets_projected\"].describe())\n", "X_test_segment[\"nb_tickets_expected\"].describe()\n" ] }, { "cell_type": "code", "execution_count": 117, "id": "72af97dc-8558-4591-adcf-ad404c9cb3f2", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "quartile\n", "1 0.029070\n", "2 0.074526\n", "3 0.078737\n", "4 0.817668\n", "Name: total_amount, dtype: float64" ] }, "execution_count": 117, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# we can recover share future revenue by multipling the share of amount by quartile * revenue recovered\n", "X_test_segment.groupby(\"quartile\")[\"total_amount\"].sum()/X_test_segment[\"total_amount\"].sum()" ] }, { "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 }