{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"# The 2nd place solution of the [RetailHero Uplift Modelling contest ](https://retailhero.ai/c/uplift_modeling/overview) \n",
"\n",
"### To open Notebook for read, use [nbviewer](https://nbviewer.jupyter.org/github/kirrlix1994/Retail_hero/blob/master/Retail_hero_contest_2nd_place_solution.ipynb)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Summary of the solution:\n",
"###### Main points to get good results in this competition:\n",
"-\tUse few features, pre-select features. My best submission used only 6 ones!\n",
"\n",
"\n",
"-\tMake NOT very complex estimators for fitting. I used mainly different gradient boosting realizations (Catboost / Xgboost / Lightgbm), best results gave models with trees of death 1-2.\n",
"\n",
"\n",
"-\tUse Class transformation approach for uplift modelling.\n",
"\n",
"\n",
"- Try to make more accurate local validation and do not rely only on public validation score. For testing hypotheses I used to make N (N = 30, 50) train - test splits and calculate test scores mean and standard deviation for making decisions."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Additional info:\n",
" \n",
" Package with different uplift model realizations: \n",
" [uplift modelling package](https://github.com/maks-sh/scikit-uplift/)\n",
"\n",
" Tutorial about uplift and approaches (in Russian): \n",
" [uplift modeling approaches article](https://habr.com/ru/company/ru_mts/blog/485980/)\n",
" \n",
" Additional code with my feature engenearing, experimemts and validation: \n",
" [retail hero research](/research)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### code is on py37, requirements:\n",
"- numpy==1.16.1\n",
"- pandas==0.24.1\n",
"- scikit-learn==0.21.3\n",
"- scikit-uplift==0.0.3\n",
"- xgboost==0.90\n",
"- matplotlib==3.1.1"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"import gc\n",
"import pandas as pd\n",
"import numpy as np\n",
"from datetime import datetime\n",
"\n",
"from xgboost import XGBClassifier\n",
"from sklift.models import ClassTransformation\n",
"\n",
"from sklift.metrics import uplift_at_k\n",
"import matplotlib.pyplot as plt\n",
"\n",
"%matplotlib inline"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### LOAD INITIAL DATA"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"df_clients = pd.read_csv(\n",
" 'data/clients.csv', \n",
" index_col='client_id', \n",
" parse_dates=['first_issue_date', 'first_redeem_date']\n",
")\n",
"\n",
"df_purchases = pd.read_csv(\n",
" 'data/purchases.csv'\n",
" index_col='client_id', \n",
" parse_dates=['transaction_datetime']\n",
")\n",
"\n",
"df_dtrain = pd.read_csv('data/uplift_train.csv', index_col='client_id')\n",
"df_test = pd.read_csv('data/uplift_test.csv', index_col='client_id')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### MAKE FEATURES FOR TRAIN AND TEST"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"###### Final second place submission used only 6 features: "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"'first_redeem_date' - make date as an integer feature"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"df_clients['first_redeem_date'] =\\\n",
" df_clients['first_redeem_date']\\\n",
" .fillna(datetime(2019, 3, 19, 0, 0))\n",
"\n",
"df_clients.loc[:, 'first_redeem_date'] =\\\n",
" ((df_clients['first_redeem_date'] - pd.Timestamp(\"1970-01-01\")) // pd.Timedelta('1d'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Aggregate df_purchases data"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# two -level aggregation: \n",
"\n",
"# 1. aggregate to (client, transaction) take one row only \n",
"# ('express_points_spent', 'purchase_sum' - same for one transaction)\n",
"\n",
"# 2. aggregate all transactions to client.\n",
"\n",
"\n",
"df_purch = df_purchases\\\n",
" .groupby(['client_id','transaction_id'])[['express_points_spent', 'purchase_sum']]\\\n",
" .last()\\\n",
" .groupby('client_id')\\\n",
" .agg({\n",
" 'express_points_spent': ['mean', 'sum'], \n",
" 'purchase_sum': ['sum']\n",
" })\n",
" \n",
"# set readable column names:\n",
"df_purch.columns =\\\n",
" ['express_spent_mean', 'express_points_spent_sum', 'purchase_sum__sum']"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"#'regular_points_received_sum_last_m'\n",
"\n",
"reg_points_last_m = df_purchases[\n",
" df_purchases['transaction_datetime'] > '2019-02-18'\n",
" ]\\\n",
" .groupby(['client_id', 'transaction_id'])['regular_points_received']\\\n",
" .last()\\\n",
" .groupby('client_id')\\\n",
" .sum()\n",
" \n",
"reg_points_last_m = pd.DataFrame({\n",
" 'regular_points_received_sum_last_m': reg_points_last_m \n",
"})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Make 'transaction_datetime' from purchases data frame as integer. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Join clients df and purchases df and calculate difference between 'transaction_datetime' and ''first_redeem_date' (date_diff)."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"df_purchases.loc[:,'purch_day'] =\\\n",
" ((df_purchases['transaction_datetime'] - pd.Timestamp(\"1970-01-01\")) // pd.Timedelta('1d'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Calculate features from purchases data for all transactions after redeem date:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# 'after_redeem_sum'\n",
"\n",
"df_purch_joined = pd.merge(\n",
" df_purchases[\n",
" ['client_id', 'purch_day', 'transaction_id', 'purchase_sum']\n",
" ],\n",
" df_clients\\\n",
" .reset_index()[\n",
" ['client_id', 'first_redeem_date']\n",
" ], on='client_id', how='left')\n",
"\n",
"df_purch_joined = df_purch_joined\\\n",
" .assign(date_diff=\\\n",
" df_purch_joined['first_redeem_date'] - df_purch_joined['purch_day']\n",
" )\n",
"\n",
"df_purch_agg = df_purch_joined[\n",
" df_purch_joined['date_diff'] <= 0\n",
" ]\\\n",
" .groupby(\n",
" ['client_id', 'transaction_id']\n",
" )\\\n",
" .last()\\\n",
" .groupby('client_id')['purchase_sum']\\\n",
" .sum()\n",
" \n",
"after_redeem_sum = pd.DataFrame(data={\n",
" 'after_redeem_sum': df_purch_agg\n",
"})\n",
"\n",
"del df_purch_joined, df_purch_agg\n",
"gc.collect();"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"'purch_delta' - difference in days between last and first transaction "
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"df_purch_delta_agg = df_purchases\\\n",
" .groupby('client_id')\\\n",
" .agg({\n",
" 'purch_day': ['max', 'min']\n",
" })\n",
"\n",
"df_purch_delta = pd.DataFrame(\n",
" data=df_purch_delta_agg['purch_day']['max'] -\\\n",
" df_purch_delta_agg['purch_day']['min'] + 1,\n",
" columns=['purch_delta']\n",
")\n",
"\n",
"del df_purch_delta_agg\n",
"gc.collect();"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"------"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Concat all data together:"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"df_feats = pd.concat([\n",
" df_clients[['first_redeem_date']], \n",
" df_purch, \n",
" df_purch_delta, \n",
" reg_points_last_m, \n",
" after_redeem_sum\n",
"], axis=1, sort=False)\n",
"\n",
"df_feats = df_feats\\\n",
" .assign(\n",
" avg_spent_perday=\\\n",
" df_feats['purchase_sum__sum'] / df_feats['purch_delta'],\n",
" after_redeem_sum_perday =\\\n",
" df_feats['after_redeem_sum'] / df_feats['purch_delta']\n",
" )\\\n",
" .drop([\n",
" 'purch_delta', 'purchase_sum__sum', 'after_redeem_sum'\n",
" ], axis=1\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(400162, 6)\n"
]
},
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" first_redeem_date | \n",
" express_spent_mean | \n",
" express_points_spent_sum | \n",
" regular_points_received_sum_last_m | \n",
" avg_spent_perday | \n",
" after_redeem_sum_perday | \n",
"
\n",
" \n",
" \n",
" \n",
" 000012768d | \n",
" 17535 | \n",
" 0.0 | \n",
" 0.0 | \n",
" 10.0 | \n",
" 26.951923 | \n",
" 26.951923 | \n",
"
\n",
" \n",
" 000036f903 | \n",
" 17279 | \n",
" 0.0 | \n",
" 0.0 | \n",
" 13.7 | \n",
" 89.136364 | \n",
" 89.136364 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" first_redeem_date express_spent_mean express_points_spent_sum \\\n",
"000012768d 17535 0.0 0.0 \n",
"000036f903 17279 0.0 0.0 \n",
"\n",
" regular_points_received_sum_last_m avg_spent_perday \\\n",
"000012768d 10.0 26.951923 \n",
"000036f903 13.7 89.136364 \n",
"\n",
" after_redeem_sum_perday \n",
"000012768d 26.951923 \n",
"000036f903 89.136364 "
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# train and test features\n",
"print(df_feats.shape)\n",
"df_feats.head(2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Make train and test data: join clints ids and treatment and target for train."
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Train df shape: (200039, 8)\n",
"Test df shape: (200123, 6)\n"
]
}
],
"source": [
"# train data:\n",
"df_train_feats = df_train\\\n",
" .join(\n",
" df_feats, \n",
" how='left'\n",
" )\n",
" \n",
"df_test_feats = df_test\\\n",
" .join(\n",
" df_feats, \n",
" how='left'\n",
" )\n",
" \n",
"print(f'Train df shape: {df_train_feats.shape}')\n",
"print(f'Test df shape: {df_test_feats.shape}')"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# save final train / test data \n",
"df_train_feats.to_csv('data/retail_hero_final_model_train_data.csv')\n",
"df_test_feats.to_csv('data/retail_hero_final_model_test_data.csv')"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"-----"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### SET MODEL\n",
"Use uplift class tranformation approach for uplift prediction with XGBoost as an estimator."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"xgb_est_params = {\n",
" 'max_depth':2,\n",
" 'learning_rate': 0.2, \n",
" 'n_estimators': 100,\n",
" 'nthread':40,\n",
" 'n_gpus':0,\n",
" 'seed':42\n",
"}\n",
"\n",
"estimator = XGBClassifier(\n",
" **xgb_est_params\n",
")\n",
"\n",
"uplift_model_cl_tr = ClassTransformation(\n",
" estimator=estimator\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"-----"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### FIT MODEL ON ALL TRAIN DATA"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true,
"scrolled": true
},
"outputs": [],
"source": [
"uplift_model_cl_tr.fit(\n",
" X=df_train_feats\\\n",
" .drop(\n",
" ['treatment_flg', 'target'], \n",
" axis=1\n",
" ),\n",
" y=df_train_feats['target'],\n",
" treatment=df_train_feats['treatment_flg']\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"--------"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Make predictions on whole train and test sets.\n",
"\n",
"#### Calculate uplift@30% metric on train data.\n",
"\n",
"###### **NOTE: In this contest uplift@30% metric calculates as follows: \n",
"All test data are sorted in descending order by 'predicted uplift'. Then calculates target share / conversion in top 30% of treatment group and conversion in top 30% of control group SEPARATELY. Metric is then the difference between two conversions.\n",
"
\n",
"\n",
" Usually uplift@30% metric calculates differently. Firstly selects top 30% of ALL test data and only then calculates conversion in treatment and control group and they are subtracted."
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# predicts\n",
"uplift_tr = uplift_model_cl_tr.predict(\n",
" df_train_feats\\\n",
" .drop(['treatment_flg', 'target'], axis=1),\n",
")\n",
"\n",
"uplift_ts = uplift_model_cl_tr.predict(\n",
" df_test_feats\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Uplift@30% on train data: 0.105\n"
]
}
],
"source": [
"# score on train:\n",
"df_train_scores = df_train_feats[['treatment_flg', 'target']]\\\n",
" .assign(uplift_score=uplift_tr)\n",
" \n",
"train_score = uplift_at_k(\n",
" y_true=df_train_scores['target'],\n",
" uplift=df_train_scores['uplift_score'],\n",
" treatment=df_train_scores['treatment_flg']\n",
")\n",
"\n",
"print(f'Uplift@30% on train data: {train_score:.3f}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Make submit:"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Submit data shape: (200123, 1)\n",
"\n",
"client_id,uplift\n",
"000048b7a6,0.03721845\n",
"000073194a,0.019023776\n",
"00007c7133,0.032182574\n",
"00007f9014,0.016914487\n"
]
}
],
"source": [
"df_submit = df_test_feats\\\n",
" .assign(uplift=uplift_ts)[['uplift']]\n",
"\n",
"print(f'Submit data shape: {df_submit.shape}\\n')\n",
"df_submit.head(2)\n",
"\n",
"df_submit.to_csv('submissions/retail_hero_2nd_place_submit.csv')\n",
"\n",
"!head -5 submissions/retail_hero_2nd_place_submit.csv"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"-----"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"------"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### FEATURE IMPORTANCE"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"There is the area of research, how to calculate feature importance in uplift models. \n",
"\n",
"Here feature importance is just vanilla information gain in XGBoost estimator, used in Class transformation approach.\n"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" feature | \n",
" value | \n",
"
\n",
" \n",
" \n",
" \n",
" 4 | \n",
" first_redeem_date | \n",
" 64.531791 | \n",
"
\n",
" \n",
" 2 | \n",
" express_points_spent_sum | \n",
" 14.171003 | \n",
"
\n",
" \n",
" 3 | \n",
" express_spent_mean | \n",
" 14.151961 | \n",
"
\n",
" \n",
" 5 | \n",
" regular_points_received_sum_last_m | \n",
" 7.514503 | \n",
"
\n",
" \n",
" 1 | \n",
" avg_spent_perday | \n",
" 5.645311 | \n",
"
\n",
" \n",
" 0 | \n",
" after_redeem_sum_perday | \n",
" 4.776374 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" feature value\n",
"4 first_redeem_date 64.531791\n",
"2 express_points_spent_sum 14.171003\n",
"3 express_spent_mean 14.151961\n",
"5 regular_points_received_sum_last_m 7.514503 \n",
"1 avg_spent_perday 5.645311 \n",
"0 after_redeem_sum_perday 4.776374 "
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_feat_imp = pd.DataFrame([\n",
" uplift_model_cl_tr\\\n",
" .estimator\\\n",
" .get_booster()\\\n",
" .get_score(importance_type='gain')\n",
" ]\n",
").T.reset_index()\n",
"\n",
"df_feat_imp.columns =\\\n",
" [\"feature\", \"value\"]\n",
" \n",
"df_feat_imp\\\n",
" .sort_values('value', ascending=False)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"------"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### PLOT ULIFT PREDICTIONS FOR TRAIN / TEST DATA"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"