diff --git a/prediction-service/README.md b/prediction-service/README.md index acde50e..7f0561a 100644 --- a/prediction-service/README.md +++ b/prediction-service/README.md @@ -45,34 +45,34 @@ curl -X 'POST' \ -H 'Content-Type: application/json' \ -d '{ "customer": { - "customer_id": 4444, - "age_range": "46-55", - "marital_status": "Married", - "family_size": 5, - "no_of_children": 3, - "income_bracket": 3, - "gender": "M", - "mean_discount_used": -1.83, - "total_discount_used": -7548.79, - "total_unique_items_bought": 2040, - "total_quantity_bought": 4314, - "mean_quantity_bought": 1.04, - "mean_selling_price_paid": 61.27, - "total_coupons_redeemed": 220, - "total_price_paid": 253295.6 + "customer_id": 54, + "age": "old", + "credit": 1, + "gender": "F", + "mean_product_price": 13.63, + "unique_coupons_used": 369, + "mean_discount_used": 11.93, + "unique_items_bought": 1934, + "total_items_bought": 42265 }, "coupons": [ { - "coupon_id": 1111, - "item_selling_price": 70.88, - "coupon_discount": -35.62, - "item_category": "Men" + "coupon_id": 1, + "mean_item_selling_price": 7.06, + "coupon_discount": 50, + "category": "", + "how_many_products": 2, + "coupon_type": "buy_more", + "days_valid": 24 }, { - "coupon_id": 2323, - "item_selling_price": 75.51, - "coupon_discount": -26.71, - "item_category": "Sport" + "coupon_id": 2, + "mean_item_selling_price": 7.06, + "coupon_discount": 3.78, + "category": "", + "how_many_products": 2, + "coupon_type": "buy_all", + "days_valid": 20 } ] }' @@ -83,14 +83,14 @@ Example response: ``` [ { - "coupon_id": 1111, - "customer_id": 4444, - "prediction": 0.85 + "coupon_id": 2, + "customer_id": 54, + "prediction": 0.0409353164 }, { - "coupon_id": 2323, - "customer_id": 4444, - "prediction": 0.8 + "coupon_id": 1, + "customer_id": 54, + "prediction": 0.0311633506 } ] ``` diff --git a/prediction-service/app/encoder.py b/prediction-service/app/encoder.py index 6dfbcc7..a30d6b3 100644 --- a/prediction-service/app/encoder.py +++ b/prediction-service/app/encoder.py @@ -1,14 +1,13 @@ import pandas -# from .model import PredictionInput from app.model import PredictionInput class DataEncoder: - _age_range = {'18-25': 0, '26-35': 1, '36-45': 2, '46-55': 3, '56-70': 4, '70+': 5} - _marital_status = {'Married': 0, 'Single': 1} - _gender = {'F': 0, 'M': 1} + _gender = ['F', 'M'] + _age = ['young', 'mid', 'old'] _categories = ['Boys', 'Girls', 'Men', 'Sports', 'Women'] + _coupon_types = ['biy_all', 'boy_more', 'department', 'just_discount'] @classmethod def encode(cls, input: PredictionInput) -> pandas.DataFrame: @@ -16,29 +15,37 @@ def encode(cls, input: PredictionInput) -> pandas.DataFrame: for coupon in input.coupons: row = { 'customer_id': input.customer.customer_id, - 'age_range': cls._age_range[input.customer.age_range], - 'marital_status': cls._marital_status[input.customer.marital_status], - 'family_size': input.customer.family_size, - 'no_of_children': input.customer.no_of_children, - 'income_bracket': input.customer.income_bracket, - 'gender': cls._gender[input.customer.gender], - 'mean_discount_per_cust': input.customer.mean_discount_used, - 'unique_items_per_cust': input.customer.total_unique_items_bought, - 'mean_quantity_per_cust': input.customer.mean_quantity_bought, - 'mean_selling_price_per_cust': input.customer.mean_selling_price_paid, - 'total_discount_per_cust': input.customer.total_discount_used, - 'total_coupons_used_per_cust': input.customer.total_coupons_redeemed, - 'total_quantity_per_cust': input.customer.total_quantity_bought, - 'total_selling_price_per_cust': input.customer.total_price_paid, + 'cust_credit': input.customer.credit, + 'cust_mean_product_price': input.customer.mean_product_price, + 'cust_unique_coupons_used': input.customer.unique_coupons_used, + 'cust_mean_discount': input.customer.mean_discount_used, + 'cust_unique_products_bought': input.customer.unique_items_bought, + 'cust_total_products_bougth': input.customer.total_items_bought, 'coupon_id': coupon.coupon_id, 'coupon_discount': coupon.coupon_discount, - 'item_selling_price': coupon.item_selling_price + 'coupon_how_many': coupon.how_many_products, + 'coupon_days_valid': coupon.days_valid, + 'coupon_mean_prod_price': coupon.mean_item_selling_price } - row.update(cls._encode_category(coupon.item_category)) + # row.update(cls._encode_category(coupon.item_category)) + row.update(cls._encode_age(input.customer.age)) + row.update(cls._encode_gender(input.customer.gender)) + row.update(cls._encode_coupon_type(coupon.coupon_type)) rows.append(row) - return pandas.DataFrame(rows) @classmethod def _encode_category(cls, category): return {f'category_{c}': 1 if category == c else 0 for c in cls._categories} + + @classmethod + def _encode_age(cls, age): + return {f'cust_age_{a}': 1 if age == a else 0 for a in cls._age} + + @classmethod + def _encode_gender(cls, gender): + return {f'cust_gender_{g}': 1 if gender == g else 0 for g in cls._gender} + + @classmethod + def _encode_coupon_type(cls, coupon_type): + return {f'coupon_type_{t}': 1 if coupon_type == t else 0 for t in cls._coupon_types} diff --git a/prediction-service/app/main.py b/prediction-service/app/main.py index 712137d..bbdd62e 100644 --- a/prediction-service/app/main.py +++ b/prediction-service/app/main.py @@ -15,11 +15,6 @@ ) -@app.get('/') -async def root(): - return {'message': 'Hello World'} - - @app.post('/score', response_model=List[PredictionOutput]) def score_coupon( input_data: PredictionInput, diff --git a/prediction-service/app/model.py b/prediction-service/app/model.py index f0a3db7..30f5330 100644 --- a/prediction-service/app/model.py +++ b/prediction-service/app/model.py @@ -5,28 +5,24 @@ class Coupon(BaseModel): coupon_id: int - item_selling_price: float + mean_item_selling_price: float coupon_discount: float - item_category: str # TODO Enum + category: str # TODO this is not used (for now) + how_many_products: int + coupon_type: str + days_valid: int class Customer(BaseModel): customer_id: int - age_range: str # TODO Enum - marital_status: str # TODO Enum - family_size: int - no_of_children: int - income_bracket: int + age: str # TODO Enum + credit: int gender: str # TODO Enum + mean_product_price: float + unique_coupons_used: int mean_discount_used: float - total_discount_used: float - total_unique_items_bought: int - total_quantity_bought: int - mean_quantity_bought: float - mean_selling_price_paid: float - total_coupons_redeemed: int - total_price_paid: float - + unique_items_bought: int + total_items_bought: int class PredictionInput(BaseModel): customer: Customer diff --git a/prediction-service/app/model_store/pickled_model_gbm_no_balancing b/prediction-service/app/model_store/pickled_model_gbm_no_balancing new file mode 100644 index 0000000..3ff584e Binary files /dev/null and b/prediction-service/app/model_store/pickled_model_gbm_no_balancing differ diff --git a/prediction-service/app/scorer.py b/prediction-service/app/scorer.py index bf20246..8047c00 100644 --- a/prediction-service/app/scorer.py +++ b/prediction-service/app/scorer.py @@ -14,11 +14,11 @@ def score(self, input_df: pandas.DataFrame) -> pandas.DataFrame: input_df.drop(['customer_id', 'coupon_id'], axis=1, inplace=True) probs = self._model.predict_proba(input_df)[:, 1] output_df['prediction'] = probs.round(decimals=10) - output_df.sort_values(by='prediction', ascending=False) + output_df.sort_values(by='prediction', ascending=False, inplace=True) return output_df def get_scorer(): - model_path = 'app/model_store/scikit_classifier' + model_path = 'app/model_store/pickled_model_gbm_no_balancing' with open(model_path, 'rb') as f: return Scorer(pickle.load(f)) diff --git a/training-with-artificial-data/01_data_prep_v1.ipynb b/training-with-artificial-data/01_data_prep_v1.ipynb new file mode 100644 index 0000000..be32e41 --- /dev/null +++ b/training-with-artificial-data/01_data_prep_v1.ipynb @@ -0,0 +1,3523 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "antique-place", + "metadata": {}, + "source": [ + "# Prepare data for training dataframe\n", + "\n", + "The aim is to produce a training table where each `order_id` is mapped to all coupons available in the departments from which products were selected in that order on the date the order was made, along with information (`True`/`False`) if such available coupon was used in that order.\n", + "\n", + "The steps are as follows:\n", + "1. Create `coupon_date_department` table which maps coupons to applicable departments, to coupon validity dates\n", + "2. Create `order_date_department` table which maps orders to departments from which products were ordered, to order dates.\n", + "3. Create `order_coupons_available` by merging the tables above on date and department - this will create a table which maps orders and coupons available at that order date, but limited by departments (i.e. rows are selected only if order contained products from a department for which there was a coupon)\n", + "4. Create `order_coupons_used` - by dropping columns from `order_details`, leaving only a mapping between order and coupons used in that order.\n", + "5. Cobine 3 and 4 to create a dataset with coupons and orders mapped, with information whether coupon was used or not.\n", + "6. Add details about customers (such as age, gender, etc., and also some statistics about customer purchases)\n", + "7. Add details about coupons (type, discount, mean product pric, etc.)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "seven-middle", + "metadata": {}, + "outputs": [], + "source": [ + "import datetime\n", + "import os\n", + "\n", + "from IPython.display import Image\n", + "import numpy as np\n", + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "encouraging-subscription", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAwYAAAIeCAYAAAABNBiWAAAKmXRFWHRteGZpbGUAJTNDbXhmaWxlJTIwaG9zdCUzRCUyMmFwcC5kaWFncmFtcy5uZXQlMjIlMjBtb2RpZmllZCUzRCUyMjIwMjEtMDQtMDhUMDklM0E0NyUzQTM1LjI4M1olMjIlMjBhZ2VudCUzRCUyMjUuMCUyMChXaW5kb3dzJTIwTlQlMjAxMC4wJTNCJTIwV2luNjQlM0IlMjB4NjQpJTIwQXBwbGVXZWJLaXQlMkY1MzcuMzYlMjAoS0hUTUwlMkMlMjBsaWtlJTIwR2Vja28pJTIwQ2hyb21lJTJGODkuMC40Mzg5LjExNCUyMFNhZmFyaSUyRjUzNy4zNiUyMiUyMGV0YWclM0QlMjJoMS1ZdEpGb0ctaEZXUG5ZNDJhLSUyMiUyMHZlcnNpb24lM0QlMjIxMy4xMC42JTIyJTIwdHlwZSUzRCUyMmRldmljZSUyMiUzRSUzQ2RpYWdyYW0lMjBuYW1lJTNEJTIyUGFnZS0xJTIyJTIwaWQlM0QlMjJlZmE3YTBhMS1iZjliLWEzMGUtZTZkZi05NGE3NzkxYzA5ZTklMjIlM0U3VnR0YzlvNEVQNDFUSm9QeVJnYkhQZ1lDRWx2THVuMW11dGNlMThZWVF1alZyWmNXU2JRWDM5NnhhOGs1QVZNRWpLZEdxOVcwa3BhN1Q2N2tsdk9NRnhjVVJEUGJvZ1BjY3UyJTJGRVhMdVdqWmRydmQ2JTJGS0hvQ3dWeFhVMElhREkxMHdaNFJiOWhwcG9hV3FLZkpnVUdCa2htS0c0U1BSSUZFR1BGV2lBVW5KWFpKc1NYT3cxQmdHc0VHNDlnS3ZVZjVIUFpvcmFzOTJNJTJGaEdpWUdaNmJydDlWVElCM3MlMkJBa2pUUyUyRmJWc1p5ciUyRlZIRUlURnQ2b01rTSUyQk9RdVIzSkdMV2RJQ1dIcVY3Z1lRaXptMWt5YnFuZTVwblFsTjRVUjI2U0NyZGRsRG5BS2pjZ3U1blVIc1JDUExmV1V1TDlTSWRNZ0JEUkFVY3M1NTZWV3ZPRCUyRmM2SWNtYUNmTUJLcnNrNnVqTUVGT3dFWUJicWV4NFdEdEZqc1E0OVF3QkRSUEh3R0ljVW9nbG5YJTJGRmVnbjFMQWhGRVNCWWJxcFFraklhU0pLZWFETG5Ma0N1SXliVWJMbENjTkg4TXBVNFU5VVZpV25NJTJCNmJYMUFFVHZtejYlMkJmJTJGdmo3NiUyQmclMkJvYlloUWdSQ0tJVGdVM084Njc0REtCWlY5RTdFdWxwa0tocm9EbzV1amxyMmtQOCUyQnVqeHFkUzkyTHBjd0JxdDEyVzNYOFV4TlJTUHJBWHlmd2lScHFuc1BzV1ZUZmNja1lRQVB1ZU5xYXVVOTdpUVliV3dDUEFwOXhLNVJpTmo5TTJBWE9yZm5rRExFSGVXNU11Y1gwdUFQdEhHJTJGVUowT0NPZWFZdW5YcG9qN0cyY3dKUkhUYnI1dDYlMkZkTEVDSXNBTUpIaU9kUXRDb01NUXV4WU5MMiUyRlNjY0VreW83TnV4TE1keFhWRml2S1pnRXoza21DNDZvJTJGWmxWM2RSclZ6eGo5cGxpbkhCUlk2ayUyRmVVVjVFNUZyWk11N1dqWHJhR04zVDUxTzRweWwwR0Z0cXU1Wm5tWTBPdWRPbG9Fb0JGS3NHbyUyRjg5TDhoM2JVOVU3YlolMkYlMkZkJTJGZlhuSnhCJTJGdmsyJTJGb0t2SjJYSVluYlNkVG1XeG9NOVJqWDRsbE0xSVFDS0FSeGwxSUtFS0ZPMEs3Y2w0cm9sWVZ6bSUyRlB5QmpTNzEySUdXa3VFaHdnZGkzM08lMkZ2b3FuVHJuNjdXT2lXNWN2U3ZBakYlMkY1WXhpdGZ2JTJCYktzbW53ejlmaVdwZXhjd0R0T0dIMzVEU241aDl5QWFHbktMcVd5U1ZuVVhJZ0pXSXVNTkNraEtmWGdmVFBiMW12R3V3Z2d1NDl6bmVwUWlEbTJtUmRsZVhrVk1JSyUyQlJqUTN5Um5HbUVSNUdEY3A4NzVyQk1lV01hekJVRDg0JTJGaDM3S0pGJTJCeGVDcFNib2NoNFJDOCUyQjVEdmdGWUNJc2NBT05HY0ZjbVRWTmUwTXhYemdXQ1VEaTBhSkxFcWlHeEpjVSUyRks0WlVxTG9DaTNMYXM5cTdsWHBHN3NhaE1Ic2JTTTE1WmJ1U082YkVUejBtRUYlMkZJMVlVJTJGSm1Jd0U1SUtIeVZpYkxGWEUwRUREWTV2RmVERklESTBhZCUyRkhQbUI1dEx4MjFHd20ySlFsRVZZelVVWlI3dDRFUlI1YzhhZ1dzNGd4MSUyQlUlMkJESnRIVEM4eWFMNktDSmNHTFpUSHc2bVBvdUQ0VVZQd0ZpRmhCZiUyRlZZSVcxa05EdXVCdGd3azY3RGhOMnRvb0ozU1l4WVlZRFYlMkJqdTBaaXclMkZSaE02R0dRSk1qYk5odzgyeGdPV3ZWcXN5czRlUFlHNENDSzVyeEZJdVpqSHdHaGRxbmpQUUNHdjFMQXJUekx3NEtEOFg3WWVMdE9yMkM4T3oxcnMzRCUyQmJKdVcyJTJCNDNhcmxiVDRqbVY2WTZWMnZ6YUQ2c3h2SFdTeHZ1M3FhR3U5MSUyRnB1SFdWVDhUSklNTGd4TmNwNkJxam1PZFdybSUyRlVvTktUdDFHU2FGV1FqMEhIVmhONnRqVE1rYlAwckhkWll3MjFyU0dJWUpqdnl1QXVKY3FvTEoyemFIRTNodEFpWVJ5M3JFUEdVRDRrRHBjSjRLYXBid2dPejUxcXdITHpXRGtzWndLMkpnY2hDTE9CZkE0cGtobW96NXdmQTBlU0VCcTFnbWNFbXB5ZENiZHMxdnhSZGI0Q1pLRHFkcjB6UW11ZXMwVWtEJTJCSWtBaUdNVnNlSXFiTklpYkhWREhwTHJjYU1kbTFCNkRtN0hRTFhxeiUyRlZyell3WDJ0M2J6NmlsZUQlMkZrUGpqR0pHJTJGbUF6TnNpeWRJc213OXdRelNkWjZ2TGolMkZhMVpETWM1aEQ3YkNYM001ZGE5dnk5aEJIM1ZUaU03MUQyNGpiMjc4dXB4VHhHUTVxNzVKZHdPTlhiQmN3NGpYNkxyWnU1MndNU2pLSmFicWlFUjZzT2t2YnhqY3dBc1pjRFNMeUdXYnMyeFVPMkp2cnMxeUxLNnJQcWElMkZaVU93YlhiMmslMkJ2WmRyTzM0VXBwdzRldWpNenFVamFUSnJ0c09jZnNlZUxkN3RYSjJUNVBXJTJGWDdQbXo3VzE1dTdMbHI4RkVmRzVYamwwaXYzb041cDBGSGlZSmxBODgxQmR1TDM2VVc5SVVwNXpaVXNKV3ptNnJSOExkQnhwYWN3ak1aeGNzYzJ5eFlFald5N3RTMjFMT0xsTk8xZUxMbmpEYlRrVjdDMG1jbXZoYmFUZVB0Q0hIcm1BaWk2eWlLaHVUcEQyVEFKcEZ5eFVpMzVmaHV1cEJmeVdxRzJ1dFByNTh3RmJjdHlG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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Image('data_diagram.png')" + ] + }, + { + "cell_type": "markdown", + "id": "radical-mining", + "metadata": {}, + "source": [ + "#### Step 0. Read and prepare tables" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "worth-situation", + "metadata": {}, + "outputs": [], + "source": [ + "data_dir = 'data_0408_0'" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "recorded-intellectual", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "RangeIndex: 1033 entries, 0 to 1032\n", + "Data columns (total 7 columns):\n", + " # Column Non-Null Count Dtype \n", + "--- ------ -------------- ----- \n", + " 0 id 1033 non-null int64 \n", + " 1 type 1033 non-null object\n", + " 2 department 62 non-null object\n", + " 3 discount 1033 non-null int64 \n", + " 4 how_many 1033 non-null int64 \n", + " 5 start_date 1033 non-null object\n", + " 6 end_date 1033 non-null object\n", + "dtypes: int64(3), object(4)\n", + "memory usage: 56.6+ KB\n" + ] + } + ], + "source": [ + "coupons = pd.read_csv(os.path.join(data_dir, 'coupons.csv'))\n", + "coupons.info()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "racial-complaint", + "metadata": {}, + "outputs": [], + "source": [ + "coupons.rename(columns={'id': 'coupon_id'}, inplace=True)\n", + "coupons.start_date = pd.to_datetime(coupons.start_date, format='%Y-%m-%d')\n", + "coupons.end_date = pd.to_datetime(coupons.end_date, format='%Y-%m-%d')" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "prescription-transsexual", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "RangeIndex: 1812 entries, 0 to 1811\n", + "Data columns (total 2 columns):\n", + " # Column Non-Null Count Dtype\n", + "--- ------ -------------- -----\n", + " 0 coupon_id 1812 non-null int64\n", + " 1 product_id 1812 non-null int64\n", + "dtypes: int64(2)\n", + "memory usage: 28.4 KB\n" + ] + } + ], + "source": [ + "coupon_product = pd.read_csv(os.path.join(data_dir, 'coupon_product.csv'))\n", + "coupon_product.info()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "martial-failure", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "RangeIndex: 3000 entries, 0 to 2999\n", + "Data columns (total 8 columns):\n", + " # Column Non-Null Count Dtype \n", + "--- ------ -------------- ----- \n", + " 0 id 3000 non-null int64 \n", + " 1 name 3000 non-null object \n", + " 2 category 3000 non-null object \n", + " 3 sizes 3000 non-null object \n", + " 4 vendor 3000 non-null object \n", + " 5 description 3000 non-null object \n", + " 6 buy_price 3000 non-null float64\n", + " 7 department 3000 non-null object \n", + "dtypes: float64(1), int64(1), object(6)\n", + "memory usage: 187.6+ KB\n" + ] + } + ], + "source": [ + "products = pd.read_csv(os.path.join(data_dir, 'products.csv'))\n", + "products.info()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "miniature-pension", + "metadata": {}, + "outputs": [], + "source": [ + "products.rename(columns={'id': 'product_id'}, inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "weekly-heater", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "RangeIndex: 308313 entries, 0 to 308312\n", + "Data columns (total 3 columns):\n", + " # Column Non-Null Count Dtype \n", + "--- ------ -------------- ----- \n", + " 0 id 308313 non-null int64 \n", + " 1 customer_id 308313 non-null int64 \n", + " 2 order_date 308313 non-null object\n", + "dtypes: int64(2), object(1)\n", + "memory usage: 7.1+ MB\n" + ] + } + ], + "source": [ + "orders = pd.read_csv(os.path.join(data_dir, 'orders.csv'))\n", + "orders.info()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "pursuant-scholarship", + "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", + "
idcustomer_idorder_date
1669381669392122011-08-15 16:24:53
1835391835402742011-10-14 10:57:56
2740102740111582012-08-30 11:15:48
46785467867402010-06-15 15:19:19
1532071532086602011-06-28 09:54:40
\n", + "
" + ], + "text/plain": [ + " id customer_id order_date\n", + "166938 166939 212 2011-08-15 16:24:53\n", + "183539 183540 274 2011-10-14 10:57:56\n", + "274010 274011 158 2012-08-30 11:15:48\n", + "46785 46786 740 2010-06-15 15:19:19\n", + "153207 153208 660 2011-06-28 09:54:40" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "orders.sample(5)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "intense-mistake", + "metadata": {}, + "outputs": [], + "source": [ + "orders.rename(columns={'id': 'order_id', 'order_date': 'date'}, inplace=True)\n", + "orders.date = pd.to_datetime(orders.date, format='%Y-%m-%d').dt.date" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "unexpected-desperate", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "RangeIndex: 2297759 entries, 0 to 2297758\n", + "Data columns (total 7 columns):\n", + " # Column Dtype \n", + "--- ------ ----- \n", + " 0 id int64 \n", + " 1 order_id int64 \n", + " 2 product_id int64 \n", + " 3 quantity_ordered int64 \n", + " 4 original_price float64\n", + " 5 buy_price float64\n", + " 6 coupon_id float64\n", + "dtypes: float64(3), int64(4)\n", + "memory usage: 122.7 MB\n" + ] + } + ], + "source": [ + "order_details = pd.read_csv(os.path.join(data_dir, 'order_details.csv'))\n", + "order_details.info()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "literary-asbestos", + "metadata": {}, + "outputs": [], + "source": [ + "order_details.drop(['id'], axis=1, inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "western-clear", + "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", + "
order_idproduct_idquantity_orderedoriginal_pricebuy_pricecoupon_id
1789646240717264247.637.6300NaN
94069912518785923.092.9355417.0
7630671008901877481.9945.0945338.0
6728688842689873.603.6000NaN
1142583153580247738.124.1412514.0
\n", + "
" + ], + "text/plain": [ + " order_id product_id quantity_ordered original_price buy_price \\\n", + "1789646 240717 2642 4 7.63 7.6300 \n", + "940699 125187 859 2 3.09 2.9355 \n", + "763067 100890 1877 4 81.99 45.0945 \n", + "672868 88426 898 7 3.60 3.6000 \n", + "1142583 153580 2477 3 8.12 4.1412 \n", + "\n", + " coupon_id \n", + "1789646 NaN \n", + "940699 417.0 \n", + "763067 338.0 \n", + "672868 NaN \n", + "1142583 514.0 " + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "order_details.sample(5)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "substantial-boards", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "RangeIndex: 1000 entries, 0 to 999\n", + "Data columns (total 11 columns):\n", + " # Column Non-Null Count Dtype \n", + "--- ------ -------------- ----- \n", + " 0 id 1000 non-null int64 \n", + " 1 name 1000 non-null object\n", + " 2 gender 1000 non-null object\n", + " 3 age 1000 non-null int64 \n", + " 4 phone 1000 non-null object\n", + " 5 address 1000 non-null object\n", + " 6 city 1000 non-null object\n", + " 7 state 1000 non-null object\n", + " 8 postalCode 1000 non-null int64 \n", + " 9 country 1000 non-null object\n", + " 10 creditLimit 1000 non-null int64 \n", + "dtypes: int64(4), object(7)\n", + "memory usage: 86.1+ KB\n" + ] + } + ], + "source": [ + "customers = pd.read_csv(os.path.join(data_dir, 'customers.csv'))\n", + "customers.info()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "colonial-enforcement", + "metadata": {}, + "outputs": [], + "source": [ + "customers.rename(columns={'id': 'customer_id'}, inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "identical-financing", + "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", + "
customer_idnamegenderagephoneaddresscitystatepostalCodecountrycreditLimit
510511Travarious WolffM37(359)461-8875138 Davis RoadWatertownMassachusetts2272US5483
757758Jhade StrackeF56(282)145-163847 Central TrailAbsarakaNorth Dakota58002US4845
858859Treydan MDM19(793)753-7311119 View CourtSavannaOklahoma74565US9810
7475Evelynn DVMF66(945)774-6546160 Cherry CourtChewelahWashington99109US9202
815816Saedie Sr.F36(188)487-2981323 4th TrailPlattsburghNew York12901US1452
\n", + "
" + ], + "text/plain": [ + " customer_id name gender age phone \\\n", + "510 511 Travarious Wolff M 37 (359)461-8875 \n", + "757 758 Jhade Stracke F 56 (282)145-1638 \n", + "858 859 Treydan MD M 19 (793)753-7311 \n", + "74 75 Evelynn DVM F 66 (945)774-6546 \n", + "815 816 Saedie Sr. F 36 (188)487-2981 \n", + "\n", + " address city state postalCode country \\\n", + "510 138 Davis Road Watertown Massachusetts 2272 US \n", + "757 47 Central Trail Absaraka North Dakota 58002 US \n", + "858 119 View Court Savanna Oklahoma 74565 US \n", + "74 160 Cherry Court Chewelah Washington 99109 US \n", + "815 323 4th Trail Plattsburgh New York 12901 US \n", + "\n", + " creditLimit \n", + "510 5483 \n", + "757 4845 \n", + "858 9810 \n", + "74 9202 \n", + "815 1452 " + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "customers.sample(5)" + ] + }, + { + "cell_type": "markdown", + "id": "crazy-festival", + "metadata": {}, + "source": [ + "#### Step 1. Create `coupon_date_department` table\n", + "\n", + "1. Create `coupon_department` mapping coupons to departments for which they are valid\n", + "2. Create `coupon_dates` mapping coupons to all dates on which they were valid\n", + "3. Merge the two into `coupon_department_dates`" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "italian-apple", + "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", + "
coupon_iddepartment
01Girls
12Boys
23Boys
34Men
45Sport
.........
15981031Boys
15991031Women
16001031Girls
16011032Men
16021033Women
\n", + "

1603 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " coupon_id department\n", + "0 1 Girls\n", + "1 2 Boys\n", + "2 3 Boys\n", + "3 4 Men\n", + "4 5 Sport\n", + "... ... ...\n", + "1598 1031 Boys\n", + "1599 1031 Women\n", + "1600 1031 Girls\n", + "1601 1032 Men\n", + "1602 1033 Women\n", + "\n", + "[1603 rows x 2 columns]" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Step 1.1 Map coupons to departments\n", + "coupon_department = pd.merge(coupon_product, products[['product_id', 'department']], on='product_id')\\\n", + " .drop(['product_id'], axis=1).drop_duplicates()\n", + "\n", + "# Coupon_product does not include coupons valid for all products in a department, add this info here\n", + "department_coupons = coupons.loc[coupons.type == 'department'][['coupon_id', 'department']]\n", + "coupon_department = coupon_department.append(department_coupons).sort_values(by='coupon_id').reset_index(drop=True)\n", + "coupon_department" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "exact-exclusion", + "metadata": {}, + "outputs": [], + "source": [ + "# Validate that all coupons are present\n", + "assert len(coupon_department.coupon_id.unique()) == max(coupon_department.coupon_id)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "handled-maximum", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Start: 2010-01-01, end: 2013-01-23. 1119 days\n" + ] + } + ], + "source": [ + "# Step 1.2 Map coupons to dates on which they were valid\n", + "coupon_dates = coupons.drop(['type', 'department', 'discount', 'how_many'], axis=1)\n", + "# Get the earliest and latest date in the dataset\n", + "start = coupon_dates.start_date.min()\n", + "end = coupon_dates.end_date.max()\n", + "days = (end-start).days + 1\n", + "print(f'Start: {start.date()}, end: {end.date()}. {days} days')" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "connected-swiss", + "metadata": {}, + "outputs": [], + "source": [ + "# Create a dataframe with row for each day from the earliest to the latest date in the set\n", + "all_dates = pd.DataFrame(pd.date_range(start=start, end=end, freq='D'), columns=['date'])\n", + "assert days == len(all_dates)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "future-external", + "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", + "
coupon_iddate
012010-01-01
112010-01-02
212010-01-03
312010-01-04
412010-01-05
.........
115591410332013-01-11
115591510332013-01-12
115591610332013-01-13
115591710332013-01-14
115591810332013-01-15
\n", + "

15590 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " coupon_id date\n", + "0 1 2010-01-01\n", + "1 1 2010-01-02\n", + "2 1 2010-01-03\n", + "3 1 2010-01-04\n", + "4 1 2010-01-05\n", + "... ... ...\n", + "1155914 1033 2013-01-11\n", + "1155915 1033 2013-01-12\n", + "1155916 1033 2013-01-13\n", + "1155917 1033 2013-01-14\n", + "1155918 1033 2013-01-15\n", + "\n", + "[15590 rows x 2 columns]" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Step 1.2.1: Perform a cross join of `all_dates` and `coupon_dates` - which contains info on validity periods\n", + "coupon_dates['key'] = 1\n", + "all_dates['key'] = 1\n", + "coupon_dates = pd.merge(coupon_dates, all_dates, on='key').drop('key', axis=1)\n", + "\n", + "# Step 1.2.2 Drop rows where a date does not fall within the validity period of a coupon\n", + "coupon_dates = coupon_dates[(coupon_dates['date'] >= coupon_dates['start_date']) & \\\n", + " (coupon_dates['date'] <= coupon_dates['end_date'])]\n", + "coupon_dates.drop(['start_date', 'end_date'], axis=1, inplace=True)\n", + "coupon_dates" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "activated-entrance", + "metadata": {}, + "outputs": [], + "source": [ + "# Validate coupon_dates is consistent with the original data in terms of coupon validity dates\n", + "coupons['days_valid'] = (coupons.end_date - coupons.start_date).dt.days + 1\n", + "df = pd.merge(coupons, coupon_dates.groupby(by='coupon_id').count().rename(columns={'date': 'days_valid'}), on='coupon_id')\n", + "assert 0 == len(df.loc[df.days_valid_x != df.days_valid_y])\n", + "coupons.drop(['days_valid'], axis=1, inplace=True)\n", + "\n", + "# Validate no coupon has been lost\n", + "assert len(coupons) == len(coupon_dates.coupon_id.unique())" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "charitable-certificate", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "coupon_id 14.0\n", + "dtype: float64" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "coupon_dates.groupby('date').count().median()" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "ignored-window", + "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", + "
coupon_iddatedepartment
012010-01-01Girls
112010-01-02Girls
212010-01-03Girls
312010-01-04Girls
412010-01-05Girls
............
2439510332013-01-11Women
2439610332013-01-12Women
2439710332013-01-13Women
2439810332013-01-14Women
2439910332013-01-15Women
\n", + "

24400 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " coupon_id date department\n", + "0 1 2010-01-01 Girls\n", + "1 1 2010-01-02 Girls\n", + "2 1 2010-01-03 Girls\n", + "3 1 2010-01-04 Girls\n", + "4 1 2010-01-05 Girls\n", + "... ... ... ...\n", + "24395 1033 2013-01-11 Women\n", + "24396 1033 2013-01-12 Women\n", + "24397 1033 2013-01-13 Women\n", + "24398 1033 2013-01-14 Women\n", + "24399 1033 2013-01-15 Women\n", + "\n", + "[24400 rows x 3 columns]" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Step 1.3 Merge coupon_department and coupon_dates\n", + "coupon_date_department = pd.merge(coupon_dates, coupon_department, on='coupon_id', how='left')\n", + "coupon_date_department.date = coupon_date_department.date.dt.date\n", + "coupon_date_department" + ] + }, + { + "cell_type": "markdown", + "id": "confidential-driver", + "metadata": {}, + "source": [ + "#### Step 2. Create `order_date_department` table\n", + "\n", + "1. Map `order_id` and product `department` -> `order_department`\n", + "2. Add dates by joining with `orders` -> `order_date_department`" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "fitted-difference", + "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", + "
order_iddepartment
01Boys
11Sport
21Girls
32Girls
42Boys
.........
2297745308311Women
2297748308312Women
2297749308313Boys
2297751308313Sport
2297753308313Men
\n", + "

1096057 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " order_id department\n", + "0 1 Boys\n", + "1 1 Sport\n", + "2 1 Girls\n", + "3 2 Girls\n", + "4 2 Boys\n", + "... ... ...\n", + "2297745 308311 Women\n", + "2297748 308312 Women\n", + "2297749 308313 Boys\n", + "2297751 308313 Sport\n", + "2297753 308313 Men\n", + "\n", + "[1096057 rows x 2 columns]" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "order_department = pd.merge(order_details[['order_id', 'product_id']], products[['product_id', 'department']],\n", + " on='product_id', how='left').drop(['product_id'], axis=1).drop_duplicates()\n", + "order_department" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "mature-feedback", + "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", + "
order_iddepartmentdate
01Boys2010-01-01
11Sport2010-01-01
21Girls2010-01-01
32Girls2010-01-01
42Boys2010-01-01
............
1096052308311Women2012-12-30
1096053308312Women2012-12-30
1096054308313Boys2012-12-30
1096055308313Sport2012-12-30
1096056308313Men2012-12-30
\n", + "

1096057 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " order_id department date\n", + "0 1 Boys 2010-01-01\n", + "1 1 Sport 2010-01-01\n", + "2 1 Girls 2010-01-01\n", + "3 2 Girls 2010-01-01\n", + "4 2 Boys 2010-01-01\n", + "... ... ... ...\n", + "1096052 308311 Women 2012-12-30\n", + "1096053 308312 Women 2012-12-30\n", + "1096054 308313 Boys 2012-12-30\n", + "1096055 308313 Sport 2012-12-30\n", + "1096056 308313 Men 2012-12-30\n", + "\n", + "[1096057 rows x 3 columns]" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "order_date_department = pd.merge(order_department, orders[['order_id', 'date']], on='order_id', how='right')\n", + "order_date_department" + ] + }, + { + "cell_type": "markdown", + "id": "dynamic-jumping", + "metadata": {}, + "source": [ + "#### Step 3. Create `order_coupons_available`" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "adjacent-mexican", + "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", + "
order_idcoupon_id
012.0
113.0
216.0
3112.0
4113.0
.........
48667883083131015.0
48667893083131019.0
48667913083131028.0
48667943083131029.0
48667953083131032.0
\n", + "

3374643 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " order_id coupon_id\n", + "0 1 2.0\n", + "1 1 3.0\n", + "2 1 6.0\n", + "3 1 12.0\n", + "4 1 13.0\n", + "... ... ...\n", + "4866788 308313 1015.0\n", + "4866789 308313 1019.0\n", + "4866791 308313 1028.0\n", + "4866794 308313 1029.0\n", + "4866795 308313 1032.0\n", + "\n", + "[3374643 rows x 2 columns]" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "order_coupons_available = pd.merge(order_date_department, coupon_date_department, on=['date', 'department'], how='left')\\\n", + " .drop(['department', 'date'], axis=1).drop_duplicates()\n", + "order_coupons_available" + ] + }, + { + "cell_type": "markdown", + "id": "neural-spouse", + "metadata": {}, + "source": [ + "#### Step 4. Create `order_coupons_used`" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "corresponding-evans", + "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", + "
order_idcoupon_id
8214.0
1138.0
4472.0
58911.0
59913.0
.........
22977363083101020.0
22977403083111020.0
22977443083111018.0
22977493083131020.0
22977503083131021.0
\n", + "

341463 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " order_id coupon_id\n", + "8 2 14.0\n", + "11 3 8.0\n", + "44 7 2.0\n", + "58 9 11.0\n", + "59 9 13.0\n", + "... ... ...\n", + "2297736 308310 1020.0\n", + "2297740 308311 1020.0\n", + "2297744 308311 1018.0\n", + "2297749 308313 1020.0\n", + "2297750 308313 1021.0\n", + "\n", + "[341463 rows x 2 columns]" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "order_coupon_used = order_details[['order_id', 'coupon_id']].dropna().drop_duplicates()\n", + "order_coupon_used" + ] + }, + { + "cell_type": "markdown", + "id": "solar-mounting", + "metadata": {}, + "source": [ + "#### Step 5. Combine `order_coupons_available` and `order_coupons_used` to get final info on which coupons were used and which, although they were available, were ignored" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "worthy-austria", + "metadata": {}, + "outputs": [], + "source": [ + "order_coupon_used.set_index(['order_id', 'coupon_id'], inplace=True)\n", + "order_coupons_available.set_index(['order_id', 'coupon_id'], inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "tough-proportion", + "metadata": {}, + "outputs": [], + "source": [ + "order_coupons_available['coupon_used'] = order_coupons_available.index.isin(order_coupon_used.index)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "capital-twelve", + "metadata": {}, + "outputs": [], + "source": [ + "order_coupons = order_coupons_available.reset_index().dropna().reset_index(drop=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "portuguese-alias", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False 0.89864\n", + "True 0.10136\n", + "Name: coupon_used, dtype: float64" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "order_coupons.coupon_used.value_counts(normalize=True)" + ] + }, + { + "cell_type": "markdown", + "id": "informal-launch", + "metadata": {}, + "source": [ + "## Prepare customer data\n", + "- From `customers` table, get:\n", + " - gender\n", + " - age bracket: young < 30, medium >= 30 & < 60, old >= 60\n", + " - credit bracket: '0' < 3000, '1' >= 3000 & < 6000, '2' >= 6000\n", + "- From `order_details` get:\n", + " - number of unique products bought\n", + " - mean price paid\n", + " - total quantity bought\n", + " - total coupons used\n", + " - mean discount used" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "billion-ceramic", + "metadata": {}, + "outputs": [], + "source": [ + "customer_demo = customers.drop(['name', 'phone', 'address', 'city', 'state', 'postalCode', 'country'], axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "difficult-mouth", + "metadata": {}, + "outputs": [], + "source": [ + "customer_demo['age_bracket'] = None\n", + "customer_demo.loc[customer_demo.age < 30, 'age_bracket'] = 'young'\n", + "customer_demo.loc[(customer_demo.age >= 30) & (customer_demo.age < 60), 'age_bracket'] = 'mid'\n", + "customer_demo.loc[(customer_demo.age >= 60), 'age_bracket'] = 'old'\n", + "customer_demo.age = customer_demo.age_bracket\n", + "customer_demo.drop(['age_bracket'], axis=1, inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "enormous-finnish", + "metadata": {}, + "outputs": [], + "source": [ + "customer_demo['credit'] = None\n", + "customer_demo.loc[customer_demo.creditLimit < 3000, 'credit'] = 0\n", + "customer_demo.loc[(customer_demo.creditLimit >= 3000) & (customer_demo.creditLimit < 6000), 'credit'] = 1\n", + "customer_demo.loc[(customer_demo.creditLimit >= 6000), 'credit'] = 2\n", + "customer_demo.drop(['creditLimit'], axis=1, inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "going-blowing", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "RangeIndex: 1000 entries, 0 to 999\n", + "Data columns (total 4 columns):\n", + " # Column Non-Null Count Dtype \n", + "--- ------ -------------- ----- \n", + " 0 customer_id 1000 non-null int64 \n", + " 1 gender 1000 non-null object\n", + " 2 age 1000 non-null object\n", + " 3 credit 1000 non-null object\n", + "dtypes: int64(1), object(3)\n", + "memory usage: 31.4+ KB\n" + ] + } + ], + "source": [ + "customer_demo.info()" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "ideal-depression", + "metadata": {}, + "outputs": [], + "source": [ + "customer_demo.rename(\n", + " columns={'age': 'cust_age', 'credit': 'cust_credit', 'gender': 'cust_gender'},\n", + " inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "confused-hampton", + "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", + "
product_idquantity_orderedbuy_pricecoupon_idcustomer_iddiscount
905646118186.4400NaN6920.0
21924432659212.0400NaN6750.0
1130421187033.3099514.069849.0
3053522293204.9800NaN3510.0
1712637130027.6096771.053642.0
\n", + "
" + ], + "text/plain": [ + " product_id quantity_ordered buy_price coupon_id customer_id \\\n", + "905646 1181 8 6.4400 NaN 692 \n", + "2192443 2659 2 12.0400 NaN 675 \n", + "1130421 1870 3 3.3099 514.0 698 \n", + "305352 2293 20 4.9800 NaN 351 \n", + "1712637 1300 2 7.6096 771.0 536 \n", + "\n", + " discount \n", + "905646 0.0 \n", + "2192443 0.0 \n", + "1130421 49.0 \n", + "305352 0.0 \n", + "1712637 42.0 " + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cust_orders = pd.merge(order_details, orders[['order_id', 'customer_id']], on='order_id', how='left')\\\n", + " .drop(['order_id'], axis=1)\n", + "cust_orders['discount'] = 100 * ((cust_orders.original_price - cust_orders.buy_price) / cust_orders.original_price)\n", + "cust_orders.drop(['original_price'], axis=1, inplace=True)\n", + "cust_orders.sample(5)" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "floppy-conflict", + "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", + "
cust_mean_product_pricecust_unique_coupons_usedcust_mean_discountcust_unique_products_boughtcust_total_products_bougth
customer_id
115.4743.010.792962381
28.91132.011.798518470
310.1675.012.594864671
423.0218.09.721291155
511.81157.011.8598111171
..................
9969.52377.012.60219441630
9979.90106.011.266486550
99810.1270.010.664784511
99910.89208.09.79139819659
100011.09284.012.88165324460
\n", + "

1000 rows × 5 columns

\n", + "
" + ], + "text/plain": [ + " cust_mean_product_price cust_unique_coupons_used \\\n", + "customer_id \n", + "1 15.47 43.0 \n", + "2 8.91 132.0 \n", + "3 10.16 75.0 \n", + "4 23.02 18.0 \n", + "5 11.81 157.0 \n", + "... ... ... \n", + "996 9.52 377.0 \n", + "997 9.90 106.0 \n", + "998 10.12 70.0 \n", + "999 10.89 208.0 \n", + "1000 11.09 284.0 \n", + "\n", + " cust_mean_discount cust_unique_products_bought \\\n", + "customer_id \n", + "1 10.79 296 \n", + "2 11.79 851 \n", + "3 12.59 486 \n", + "4 9.72 129 \n", + "5 11.85 981 \n", + "... ... ... \n", + "996 12.60 2194 \n", + "997 11.26 648 \n", + "998 10.66 478 \n", + "999 9.79 1398 \n", + "1000 12.88 1653 \n", + "\n", + " cust_total_products_bougth \n", + "customer_id \n", + "1 2381 \n", + "2 8470 \n", + "3 4671 \n", + "4 1155 \n", + "5 11171 \n", + "... ... \n", + "996 41630 \n", + "997 6550 \n", + "998 4511 \n", + "999 19659 \n", + "1000 24460 \n", + "\n", + "[1000 rows x 5 columns]" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cust_stats = pd.pivot_table(cust_orders,\n", + " values=['product_id', 'quantity_ordered', 'buy_price', 'coupon_id', 'discount'],\n", + " index='customer_id',\n", + " aggfunc={\n", + " 'product_id': lambda x: len(set(x)),\n", + " 'quantity_ordered': sum,\n", + " 'buy_price': lambda x: np.round(np.mean(x), decimals=2),\n", + " 'coupon_id': lambda x: int(x.nunique()),\n", + " 'discount': lambda x: np.round(np.mean(x), decimals=2)\n", + " })\n", + "cust_stats.rename(columns={\n", + " 'product_id': 'cust_unique_products_bought',\n", + " 'quantity_ordered': 'cust_total_products_bougth',\n", + " 'buy_price': 'cust_mean_product_price',\n", + " 'coupon_id': 'cust_unique_coupons_used',\n", + " 'discount': 'cust_mean_discount'\n", + "}, inplace=True)\n", + "cust_stats" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "painted-arbor", + "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", + "
customer_idcust_gendercust_agecust_creditcust_mean_product_pricecust_unique_coupons_usedcust_mean_discountcust_unique_products_boughtcust_total_products_bougth
01Fyoung115.4743.010.792962381
12Fyoung18.91132.011.798518470
23Fold010.1675.012.594864671
34Fold223.0218.09.721291155
45Mmid111.81157.011.8598111171
..............................
995996Fmid09.52377.012.60219441630
996997Myoung29.90106.011.266486550
997998Mmid110.1270.010.664784511
998999Mmid210.89208.09.79139819659
9991000Fmid111.09284.012.88165324460
\n", + "

1000 rows × 9 columns

\n", + "
" + ], + "text/plain": [ + " customer_id cust_gender cust_age cust_credit cust_mean_product_price \\\n", + "0 1 F young 1 15.47 \n", + "1 2 F young 1 8.91 \n", + "2 3 F old 0 10.16 \n", + "3 4 F old 2 23.02 \n", + "4 5 M mid 1 11.81 \n", + ".. ... ... ... ... ... \n", + "995 996 F mid 0 9.52 \n", + "996 997 M young 2 9.90 \n", + "997 998 M mid 1 10.12 \n", + "998 999 M mid 2 10.89 \n", + "999 1000 F mid 1 11.09 \n", + "\n", + " cust_unique_coupons_used cust_mean_discount \\\n", + "0 43.0 10.79 \n", + "1 132.0 11.79 \n", + "2 75.0 12.59 \n", + "3 18.0 9.72 \n", + "4 157.0 11.85 \n", + ".. ... ... \n", + "995 377.0 12.60 \n", + "996 106.0 11.26 \n", + "997 70.0 10.66 \n", + "998 208.0 9.79 \n", + "999 284.0 12.88 \n", + "\n", + " cust_unique_products_bought cust_total_products_bougth \n", + "0 296 2381 \n", + "1 851 8470 \n", + "2 486 4671 \n", + "3 129 1155 \n", + "4 981 11171 \n", + ".. ... ... \n", + "995 2194 41630 \n", + "996 648 6550 \n", + "997 478 4511 \n", + "998 1398 19659 \n", + "999 1653 24460 \n", + "\n", + "[1000 rows x 9 columns]" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "customer_data = pd.merge(customer_demo, cust_stats, on='customer_id', how='left')\n", + "customer_data" + ] + }, + { + "cell_type": "markdown", + "id": "interpreted-murder", + "metadata": {}, + "source": [ + "## Prepare coupon data\n", + "- From `coupons` table, take:\n", + " - type\n", + " - days_valid (end_date - start_date)\n", + " - discount\n", + " - how_many (change to `1` where `how_many == 1`)\n", + "- From merging `products` and `coupon_product`, `coupons`, take:\n", + " - mean_product_price" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "vocal-milton", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "RangeIndex: 1033 entries, 0 to 1032\n", + "Data columns (total 5 columns):\n", + " # Column Non-Null Count Dtype \n", + "--- ------ -------------- ----- \n", + " 0 coupon_id 1033 non-null int64 \n", + " 1 type 1033 non-null object\n", + " 2 discount 1033 non-null int64 \n", + " 3 how_many 1033 non-null int64 \n", + " 4 days_valid 1033 non-null int64 \n", + "dtypes: int64(4), object(1)\n", + "memory usage: 40.5+ KB\n" + ] + } + ], + "source": [ + "coupon_info = coupons.drop(['department'], axis=1)\n", + "coupon_info['days_valid'] = (coupon_info.end_date - coupon_info.start_date).dt.days + 1\n", + "coupon_info.drop(['start_date', 'end_date'], axis=1, inplace=True)\n", + "coupon_info.loc[coupon_info.how_many == -1, 'how_many'] = 1\n", + "coupon_info.info()\n", + "coupon_info.rename(columns={'type': 'coupon_type', 'how_many': 'coupon_how_many', \n", + " 'days_valid': 'coupon_days_valid', 'discount': 'coupon_discount'},\n", + " inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "later-technical", + "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", + "
coupon_mean_prod_price
coupon_id
12.18
29.62
34.25
48.02
510.27
......
102911.44
103018.22
10317.11
10326.41
10332.76
\n", + "

1033 rows × 1 columns

\n", + "
" + ], + "text/plain": [ + " coupon_mean_prod_price\n", + "coupon_id \n", + "1 2.18\n", + "2 9.62\n", + "3 4.25\n", + "4 8.02\n", + "5 10.27\n", + "... ...\n", + "1029 11.44\n", + "1030 18.22\n", + "1031 7.11\n", + "1032 6.41\n", + "1033 2.76\n", + "\n", + "[1033 rows x 1 columns]" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "coupon_price = pd.merge(coupon_product, products[['product_id', 'buy_price']], on='product_id')\\\n", + " .drop(['product_id'], axis=1).drop_duplicates()\n", + "\n", + "# Coupon_product does not include coupons valid for all products in a department, add those product prices here\n", + "department_coupons = coupons.loc[coupons.type == 'department'][['coupon_id', 'department']]\\\n", + " .merge(products[['department', 'buy_price']], on='department', how='left')\\\n", + " .drop(['department'], axis=1)\n", + "\n", + "coupon_price = coupon_price.append(department_coupons).reset_index(drop=True)\n", + "\n", + "coupon_price = pd.pivot_table(coupon_price, index='coupon_id', values=['buy_price'],\n", + " aggfunc={\n", + " 'buy_price': lambda x: np.round(np.mean(x), decimals=2)\n", + " })\n", + "coupon_price.rename(columns={'buy_price': 'coupon_mean_prod_price'}, inplace=True)\n", + "coupon_price" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "chronic-statement", + "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", + "
coupon_idcoupon_typecoupon_discountcoupon_how_manycoupon_days_validcoupon_mean_prod_price
01buy_more22272.18
12just_discount17169.62
23just_discount301254.25
34buy_more473188.02
45buy_all353110.27
.....................
10281029buy_all6452711.44
10291030buy_more1442718.22
10301031buy_all51337.11
10311032just_discount101226.41
10321033buy_more284172.76
\n", + "

1033 rows × 6 columns

\n", + "
" + ], + "text/plain": [ + " coupon_id coupon_type coupon_discount coupon_how_many \\\n", + "0 1 buy_more 22 2 \n", + "1 2 just_discount 17 1 \n", + "2 3 just_discount 30 1 \n", + "3 4 buy_more 47 3 \n", + "4 5 buy_all 35 3 \n", + "... ... ... ... ... \n", + "1028 1029 buy_all 64 5 \n", + "1029 1030 buy_more 14 4 \n", + "1030 1031 buy_all 51 3 \n", + "1031 1032 just_discount 10 1 \n", + "1032 1033 buy_more 28 4 \n", + "\n", + " coupon_days_valid coupon_mean_prod_price \n", + "0 7 2.18 \n", + "1 6 9.62 \n", + "2 25 4.25 \n", + "3 18 8.02 \n", + "4 1 10.27 \n", + "... ... ... \n", + "1028 27 11.44 \n", + "1029 27 18.22 \n", + "1030 3 7.11 \n", + "1031 22 6.41 \n", + "1032 17 2.76 \n", + "\n", + "[1033 rows x 6 columns]" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "coupon_data = pd.merge(coupon_info, coupon_price, on='coupon_id', how='left')\n", + "coupon_data" + ] + }, + { + "cell_type": "markdown", + "id": "touched-complaint", + "metadata": {}, + "source": [ + "## Merge everyghing into one dataframe\n", + "\n", + "1. Add `customer_id` to `order_coupons` (from `orders`) -> `final`\n", + "2. Merge `final` with `customer_data`\n", + "3. Merge `final` with `coupon_data`" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "therapeutic-swift", + "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", + "
order_idcoupon_idcoupon_usedcustomer_idcust_gendercust_agecust_creditcust_mean_product_pricecust_unique_coupons_usedcust_mean_discountcust_unique_products_boughtcust_total_products_bougthcoupon_typecoupon_discountcoupon_how_manycoupon_days_validcoupon_mean_prod_price
012.0False51Mmid210.17282.011.94176429456just_discount17169.62
113.0False51Mmid210.17282.011.94176429456just_discount301254.25
216.0False51Mmid210.17282.011.94176429456buy_all413512.51
3112.0False51Mmid210.17282.011.94176429456just_discount51132.01
4113.0False51Mmid210.17282.011.94176429456buy_all453143.92
......................................................
33688093083131015.0False985Myoung212.0365.011.814404420just_discount1912513.01
33688103083131019.0False985Myoung212.0365.011.814404420just_discount61224.67
33688113083131028.0False985Myoung212.0365.011.814404420buy_all613239.24
33688123083131029.0False985Myoung212.0365.011.814404420buy_all6452711.44
33688133083131032.0False985Myoung212.0365.011.814404420just_discount101226.41
\n", + "

3368814 rows × 17 columns

\n", + "
" + ], + "text/plain": [ + " order_id coupon_id coupon_used customer_id cust_gender cust_age \\\n", + "0 1 2.0 False 51 M mid \n", + "1 1 3.0 False 51 M mid \n", + "2 1 6.0 False 51 M mid \n", + "3 1 12.0 False 51 M mid \n", + "4 1 13.0 False 51 M mid \n", + "... ... ... ... ... ... ... \n", + "3368809 308313 1015.0 False 985 M young \n", + "3368810 308313 1019.0 False 985 M young \n", + "3368811 308313 1028.0 False 985 M young \n", + "3368812 308313 1029.0 False 985 M young \n", + "3368813 308313 1032.0 False 985 M young \n", + "\n", + " cust_credit cust_mean_product_price cust_unique_coupons_used \\\n", + "0 2 10.17 282.0 \n", + "1 2 10.17 282.0 \n", + "2 2 10.17 282.0 \n", + "3 2 10.17 282.0 \n", + "4 2 10.17 282.0 \n", + "... ... ... ... \n", + "3368809 2 12.03 65.0 \n", + "3368810 2 12.03 65.0 \n", + "3368811 2 12.03 65.0 \n", + "3368812 2 12.03 65.0 \n", + "3368813 2 12.03 65.0 \n", + "\n", + " cust_mean_discount cust_unique_products_bought \\\n", + "0 11.94 1764 \n", + "1 11.94 1764 \n", + "2 11.94 1764 \n", + "3 11.94 1764 \n", + "4 11.94 1764 \n", + "... ... ... \n", + "3368809 11.81 440 \n", + "3368810 11.81 440 \n", + "3368811 11.81 440 \n", + "3368812 11.81 440 \n", + "3368813 11.81 440 \n", + "\n", + " cust_total_products_bougth coupon_type coupon_discount \\\n", + "0 29456 just_discount 17 \n", + "1 29456 just_discount 30 \n", + "2 29456 buy_all 41 \n", + "3 29456 just_discount 5 \n", + "4 29456 buy_all 45 \n", + "... ... ... ... \n", + "3368809 4420 just_discount 19 \n", + "3368810 4420 just_discount 6 \n", + "3368811 4420 buy_all 61 \n", + "3368812 4420 buy_all 64 \n", + "3368813 4420 just_discount 10 \n", + "\n", + " coupon_how_many coupon_days_valid coupon_mean_prod_price \n", + "0 1 6 9.62 \n", + "1 1 25 4.25 \n", + "2 3 5 12.51 \n", + "3 1 13 2.01 \n", + "4 3 14 3.92 \n", + "... ... ... ... \n", + "3368809 1 25 13.01 \n", + "3368810 1 22 4.67 \n", + "3368811 3 23 9.24 \n", + "3368812 5 27 11.44 \n", + "3368813 1 22 6.41 \n", + "\n", + "[3368814 rows x 17 columns]" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "final = pd.merge(order_coupons, orders[['order_id', 'customer_id']], on='order_id', how='left')\n", + "final = pd.merge(final, customer_data, on='customer_id', how='left')\n", + "final = pd.merge(final, coupon_data, on='coupon_id', how='left')\n", + "final" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "theoretical-crack", + "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", + "
order_idcoupon_idcoupon_usedcustomer_idcust_gendercust_agecust_creditcust_mean_product_pricecust_unique_coupons_usedcust_mean_discountcust_unique_products_boughtcust_total_products_bougthcoupon_typecoupon_discountcoupon_how_manycoupon_days_validcoupon_mean_prod_price
19214.0True54Fold113.63369.011.93193442265buy_more502247.06
2138.0True62Fold212.03510.012.53234677144buy_all442203.78
7072.0True167Fold010.65220.013.90121815034just_discount17169.62
94913.0True180Fmid210.81305.012.79182231331buy_all453143.92
10197.0True180Fmid210.81305.012.79182231331just_discount251410.00
......................................................
33687813083101020.0True792Fmid210.69269.013.64169226774department3213014.57
33687873083111020.0True858Fmid111.18395.012.48210343475department3213014.57
33687933083111018.0True858Fmid111.18395.012.48210343475department512817.83
33688043083131020.0True985Myoung212.0365.011.814404420department3213014.57
33688053083131021.0True985Myoung212.0365.011.814404420buy_all584184.74
\n", + "

341463 rows × 17 columns

\n", + "
" + ], + "text/plain": [ + " order_id coupon_id coupon_used customer_id cust_gender cust_age \\\n", + "19 2 14.0 True 54 F old \n", + "21 3 8.0 True 62 F old \n", + "70 7 2.0 True 167 F old \n", + "94 9 13.0 True 180 F mid \n", + "101 9 7.0 True 180 F mid \n", + "... ... ... ... ... ... ... \n", + "3368781 308310 1020.0 True 792 F mid \n", + "3368787 308311 1020.0 True 858 F mid \n", + "3368793 308311 1018.0 True 858 F mid \n", + "3368804 308313 1020.0 True 985 M young \n", + "3368805 308313 1021.0 True 985 M young \n", + "\n", + " cust_credit cust_mean_product_price cust_unique_coupons_used \\\n", + "19 1 13.63 369.0 \n", + "21 2 12.03 510.0 \n", + "70 0 10.65 220.0 \n", + "94 2 10.81 305.0 \n", + "101 2 10.81 305.0 \n", + "... ... ... ... \n", + "3368781 2 10.69 269.0 \n", + "3368787 1 11.18 395.0 \n", + "3368793 1 11.18 395.0 \n", + "3368804 2 12.03 65.0 \n", + "3368805 2 12.03 65.0 \n", + "\n", + " cust_mean_discount cust_unique_products_bought \\\n", + "19 11.93 1934 \n", + "21 12.53 2346 \n", + "70 13.90 1218 \n", + "94 12.79 1822 \n", + "101 12.79 1822 \n", + "... ... ... \n", + "3368781 13.64 1692 \n", + "3368787 12.48 2103 \n", + "3368793 12.48 2103 \n", + "3368804 11.81 440 \n", + "3368805 11.81 440 \n", + "\n", + " cust_total_products_bougth coupon_type coupon_discount \\\n", + "19 42265 buy_more 50 \n", + "21 77144 buy_all 44 \n", + "70 15034 just_discount 17 \n", + "94 31331 buy_all 45 \n", + "101 31331 just_discount 25 \n", + "... ... ... ... \n", + "3368781 26774 department 32 \n", + "3368787 43475 department 32 \n", + "3368793 43475 department 5 \n", + "3368804 4420 department 32 \n", + "3368805 4420 buy_all 58 \n", + "\n", + " coupon_how_many coupon_days_valid coupon_mean_prod_price \n", + "19 2 24 7.06 \n", + "21 2 20 3.78 \n", + "70 1 6 9.62 \n", + "94 3 14 3.92 \n", + "101 1 4 10.00 \n", + "... ... ... ... \n", + "3368781 1 30 14.57 \n", + "3368787 1 30 14.57 \n", + "3368793 1 28 17.83 \n", + "3368804 1 30 14.57 \n", + "3368805 4 18 4.74 \n", + "\n", + "[341463 rows x 17 columns]" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "final.loc[final['coupon_used'] == True]" + ] + }, + { + "cell_type": "markdown", + "id": "beautiful-latest", + "metadata": {}, + "source": [ + "## Droping ids, encoding" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "decimal-guard", + "metadata": {}, + "outputs": [], + "source": [ + "train = final.drop(['order_id', 'coupon_id', 'customer_id'], axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "capable-brother", + "metadata": {}, + "outputs": [], + "source": [ + "train.coupon_used = train.coupon_used.astype(int)\n", + "train = pd.get_dummies(train, columns=['cust_gender', 'cust_age', 'coupon_type'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "pharmaceutical-trial", + "metadata": {}, + "outputs": [], + "source": [ + "train.columns" + ] + }, + { + "cell_type": "markdown", + "id": "original-decimal", + "metadata": {}, + "source": [ + "## Save as csv" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "consistent-arrangement", + "metadata": {}, + "outputs": [], + "source": [ + "train.to_csv(os.path.join(data_dir, 'train.csv'), index=False)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/training-with-artificial-data/02_training_automl.ipynb b/training-with-artificial-data/02_training_automl.ipynb new file mode 100644 index 0000000..232c5bc --- /dev/null +++ b/training-with-artificial-data/02_training_automl.ipynb @@ -0,0 +1,2473 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "id": "sized-waters", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "import h2o\n", + "from h2o.automl import H2OAutoML" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "handy-terry", + "metadata": {}, + "outputs": [], + "source": [ + "h2o.init()" + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "id": "ceramic-association", + "metadata": {}, + "outputs": [], + "source": [ + "data_dir = 'data_0408_0'\n", + "train_file_path = os.path.join(data_dir, 'train.csv')" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "electronic-gibraltar", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Parse progress: |█████████████████████████████████████████████████████████| 100%\n", + "Rows:3368814\n", + "Cols:20\n", + "\n", + "\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", + "
coupon_used cust_credit cust_mean_product_price cust_unique_coupons_used cust_mean_discount cust_unique_products_bought cust_total_products_bougth coupon_discount coupon_how_many coupon_days_valid coupon_mean_prod_price cust_gender_F cust_gender_M cust_age_mid cust_age_old cust_age_young coupon_type_buy_all coupon_type_buy_more coupon_type_department coupon_type_just_discount
type int int real int real int int int int int real int int int int int int int int int
mins 0.0 0.0 6.53 0.0 0.0 5.0 44.0 5.0 1.0 1.0 1.02 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
mean 0.101360003847051211.126982967893151311.23579317231524 288.1248774791359 12.157121176770222 1637.607053402177 31270.10393895302 29.9120337305651062.653063362952065220.144611723888584 11.517169638929307 0.6130243462536074 0.386975653746392640.38354447588973450.40985818748081670.206597336629448820.39924644103236334 0.253279047166154 0.05399466993428548 0.29347984186719717
maxs 1.0 2.0 23.02 562.0 17.94 2662.0 102076.0 70.0 5.0 30.0 525.05 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
sigma 0.301804871573062350.80084641062264311.2856230666254118 120.79882235111731 1.000653355630264 575.9128426350499 19794.72723022965 16.5526688663137381.50065768904400277.2175978222313555 24.35405441156409 0.487058074126761940.487058074126761940.48624909366352650.49180740685138170.4048640830990501 0.48974359776536064 0.43488944293214415 0.22600721385690276 0.45535643822669347
zeros 3027351 893572 0 11 11 0 0 0 0 0 0 1303649 2065165 2076724 1988078 2672826 2023827 2515564 3186916 2380135
missing0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0.0 2.0 10.17 282.0 11.94 1764.0 29456.0 17.0 1.0 6.0 9.62 0.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 1.0
1 0.0 2.0 10.17 282.0 11.94 1764.0 29456.0 30.0 1.0 25.0 4.25 0.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 1.0
2 0.0 2.0 10.17 282.0 11.94 1764.0 29456.0 41.0 3.0 5.0 12.51 0.0 1.0 1.0 0.0 0.0 1.0 0.0 0.0 0.0
3 0.0 2.0 10.17 282.0 11.94 1764.0 29456.0 5.0 1.0 13.0 2.01 0.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 1.0
4 0.0 2.0 10.17 282.0 11.94 1764.0 29456.0 45.0 3.0 14.0 3.92 0.0 1.0 1.0 0.0 0.0 1.0 0.0 0.0 0.0
5 0.0 2.0 10.17 282.0 11.94 1764.0 29456.0 35.0 3.0 1.0 10.27 0.0 1.0 1.0 0.0 0.0 1.0 0.0 0.0 0.0
6 0.0 2.0 10.17 282.0 11.94 1764.0 29456.0 22.0 2.0 7.0 2.18 0.0 1.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0
7 0.0 2.0 10.17 282.0 11.94 1764.0 29456.0 44.0 2.0 20.0 3.78 0.0 1.0 1.0 0.0 0.0 1.0 0.0 0.0 0.0
8 0.0 1.0 13.63 369.0 11.93 1934.0 42265.0 22.0 2.0 7.0 2.18 1.0 0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0
9 0.0 1.0 13.63 369.0 11.93 1934.0 42265.0 44.0 2.0 20.0 3.78 1.0 0.0 0.0 1.0 0.0 1.0 0.0 0.0 0.0
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "train_h2o = h2o.H2OFrame(train)\n", + "train_h2o.describe()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "6b3f3df2", + "metadata": {}, + "outputs": [], + "source": [ + "y = 'coupon_used'\n", + "x = train_h2o.columns\n", + "x.remove(y)\n", + "train_h2o['coupon_used'] = train_h2o['coupon_used'].asfactor()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "43852a5f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AutoML progress: |████████████████████████████████████████████████████████| 100%\n" + ] + } + ], + "source": [ + "aml = H2OAutoML(max_models=10, seed=1, balance_classes=True)\n", + "aml.train(x=x, y=y, training_frame=train_h2o)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "716251d3", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
model_id auc logloss aucpr mean_per_class_error rmse mse
StackedEnsemble_AllModels_AutoML_20210408_164243 0.803702 0.2419510.502149 0.3100920.2546710.0648575
StackedEnsemble_BestOfFamily_AutoML_20210408_1642430.802811 0.2422330.501732 0.31046 0.2547230.0648838
GBM_4_AutoML_20210408_164243 0.802158 0.2426060.499533 0.3108090.2549270.064988
XGBoost_3_AutoML_20210408_164243 0.800627 0.2426780.501184 0.3105920.2547580.0649015
XGBoost_1_AutoML_20210408_164243 0.799724 0.2431670.497031 0.3112580.2551590.0651059
GBM_5_AutoML_20210408_164243 0.798054 0.2439140.493746 0.3111240.2554780.0652692
GBM_3_AutoML_20210408_164243 0.79799 0.2436940.497595 0.3102380.2551190.0650856
GBM_2_AutoML_20210408_164243 0.794188 0.2445140.495236 0.3105570.2553120.0651843
GBM_1_AutoML_20210408_164243 0.790586 0.2452790.4932 0.3111570.2555170.0652888
XGBoost_2_AutoML_20210408_164243 0.785739 0.2504210.479367 0.3160640.2581710.066652
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "aml.leaderboard" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "71abc828", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model Details\n", + "=============\n", + "H2OGradientBoostingEstimator : Gradient Boosting Machine\n", + "Model Key: GBM_4_AutoML_20210408_164243\n", + "\n", + "\n", + "Model Summary: \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", + "
number_of_treesnumber_of_internal_treesmodel_size_in_bytesmin_depthmax_depthmean_depthmin_leavesmax_leavesmean_leaves
070.070.0578602.00.010.07.5714291.0994.0654.6
\n", + "
" + ], + "text/plain": [ + " number_of_trees number_of_internal_trees model_size_in_bytes \\\n", + "0 70.0 70.0 578602.0 \n", + "\n", + " min_depth max_depth mean_depth min_leaves max_leaves mean_leaves \n", + "0 0.0 10.0 7.571429 1.0 994.0 654.6 " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "ModelMetricsBinomial: gbm\n", + "** Reported on train data. **\n", + "\n", + "MSE: 0.2711710163001577\n", + "RMSE: 0.5207408341009544\n", + "LogLoss: 0.8622691624238035\n", + "Mean Per-Class Error: 0.2690251558453727\n", + "AUC: 0.8156996013643698\n", + "AUCPR: 0.8394483144715442\n", + "Gini: 0.6313992027287396\n", + "\n", + "Confusion Matrix (Act/Pred) for max f1 @ threshold = 0.05733060682487723: \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", + "
01ErrorRate
001729723.01297628.00.4286(1297628.0/3027351.0)
11513782.02513447.00.1697(513782.0/3027229.0)
2Total2243505.03811075.00.2992(1811410.0/6054580.0)
\n", + "
" + ], + "text/plain": [ + " 0 1 Error Rate\n", + "0 0 1729723.0 1297628.0 0.4286 (1297628.0/3027351.0)\n", + "1 1 513782.0 2513447.0 0.1697 (513782.0/3027229.0)\n", + "2 Total 2243505.0 3811075.0 0.2992 (1811410.0/6054580.0)" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Maximum Metrics: Maximum metrics at their respective thresholds\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", + "
metricthresholdvalueidx
0max f10.0573317.351083e-01336.0
1max f20.0325488.452362e-01374.0
2max f0point50.1335127.645287e-01255.0
3max accuracy0.0953497.309765e-01292.0
4max precision0.8803081.000000e+000.0
5max recall0.0077371.000000e+00398.0
6max specificity0.8803081.000000e+000.0
7max absolute_mcc0.1392634.796984e-01250.0
8max min_per_class_accuracy0.0785577.252985e-01310.0
9max mean_per_class_accuracy0.0953497.309748e-01292.0
10max tns0.8803083.027351e+060.0
11max fns0.8803083.027017e+060.0
12max fps0.0062373.027351e+06399.0
13max tps0.0077373.027229e+06398.0
14max tnr0.8803081.000000e+000.0
15max fnr0.8803089.999300e-010.0
16max fpr0.0062371.000000e+00399.0
17max tpr0.0077371.000000e+00398.0
\n", + "
" + ], + "text/plain": [ + " metric threshold value idx\n", + "0 max f1 0.057331 7.351083e-01 336.0\n", + "1 max f2 0.032548 8.452362e-01 374.0\n", + "2 max f0point5 0.133512 7.645287e-01 255.0\n", + "3 max accuracy 0.095349 7.309765e-01 292.0\n", + "4 max precision 0.880308 1.000000e+00 0.0\n", + "5 max recall 0.007737 1.000000e+00 398.0\n", + "6 max specificity 0.880308 1.000000e+00 0.0\n", + "7 max absolute_mcc 0.139263 4.796984e-01 250.0\n", + "8 max min_per_class_accuracy 0.078557 7.252985e-01 310.0\n", + "9 max mean_per_class_accuracy 0.095349 7.309748e-01 292.0\n", + "10 max tns 0.880308 3.027351e+06 0.0\n", + "11 max fns 0.880308 3.027017e+06 0.0\n", + "12 max fps 0.006237 3.027351e+06 399.0\n", + "13 max tps 0.007737 3.027229e+06 398.0\n", + "14 max tnr 0.880308 1.000000e+00 0.0\n", + "15 max fnr 0.880308 9.999300e-01 0.0\n", + "16 max fpr 0.006237 1.000000e+00 399.0\n", + "17 max tpr 0.007737 1.000000e+00 398.0" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Gains/Lift Table: Avg response rate: 50.00 %, avg score: 21.13 %\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", + " \n", + "
groupcumulative_data_fractionlower_thresholdliftcumulative_liftresponse_ratescorecumulative_response_ratecumulative_scorecapture_ratecumulative_capture_rategaincumulative_gainkolmogorov_smirnov
010.0100020.8375401.9815451.9815450.9907530.8468320.9907530.8468320.0198190.01981998.15449698.1544960.019634
120.0200090.8287691.9751191.9783310.9875400.8327170.9891460.8397720.0197660.03958597.51190297.8331060.039151
230.0300060.8227021.9693741.9753470.9846670.8257490.9876540.8351010.0196860.05927296.93736697.5346950.058531
340.0400020.8171281.9670281.9732680.9834940.8198330.9866140.8312850.0196630.07893596.70279497.3268060.077864
450.0500000.8122581.9633981.9712950.9816790.8146300.9856270.8279550.0196300.09856596.33983997.1294540.097127
560.1000080.7855881.9563631.9638280.9781620.7995910.9818940.8137720.0978330.19639895.63634296.3828410.192776
670.1500110.7511691.9424741.9567100.9712170.7695430.9783350.7990290.0971310.29352994.24736895.6710190.287029
780.2000010.3465161.8622311.9330950.9310970.6418980.9665280.7597540.0930930.38662286.22306793.3095090.373233
890.3000030.1267281.3295601.7319160.6647670.1642620.8659410.5612560.1329580.51957932.95602973.1916270.439145
9100.4000000.0981881.1106461.5766030.5553120.1110480.7882850.4487070.1110620.63064111.06456957.6602730.461273
10110.5000010.0785220.9590231.4530850.4795020.0880370.7265280.3765720.0959040.726545-4.09772045.3085440.453078
11120.6000000.0612710.8173251.3471270.4086540.0696580.6735500.3254210.0817320.808276-18.26754434.7126550.416544
12130.7000010.0490130.7054261.2554550.3527060.0546390.6277150.2867370.0705430.878819-29.45740525.5454530.357630
13140.8000000.0407720.5770731.1706570.2885310.0447640.5853170.2564910.0577070.936526-42.29271917.0657400.273046
14150.9000000.0324510.4393791.0894040.2196850.0367790.5446910.2320780.0439380.980464-56.0620568.9404420.160925
15161.0000000.0025560.1953601.0000000.0976780.0241910.4999900.2112900.0195361.000000-80.4639820.0000000.000000
\n", + "
" + ], + "text/plain": [ + " group cumulative_data_fraction lower_threshold lift \\\n", + "0 1 0.010002 0.837540 1.981545 \n", + "1 2 0.020009 0.828769 1.975119 \n", + "2 3 0.030006 0.822702 1.969374 \n", + "3 4 0.040002 0.817128 1.967028 \n", + "4 5 0.050000 0.812258 1.963398 \n", + "5 6 0.100008 0.785588 1.956363 \n", + "6 7 0.150011 0.751169 1.942474 \n", + "7 8 0.200001 0.346516 1.862231 \n", + "8 9 0.300003 0.126728 1.329560 \n", + "9 10 0.400000 0.098188 1.110646 \n", + "10 11 0.500001 0.078522 0.959023 \n", + "11 12 0.600000 0.061271 0.817325 \n", + "12 13 0.700001 0.049013 0.705426 \n", + "13 14 0.800000 0.040772 0.577073 \n", + "14 15 0.900000 0.032451 0.439379 \n", + "15 16 1.000000 0.002556 0.195360 \n", + "\n", + " cumulative_lift response_rate score cumulative_response_rate \\\n", + "0 1.981545 0.990753 0.846832 0.990753 \n", + "1 1.978331 0.987540 0.832717 0.989146 \n", + "2 1.975347 0.984667 0.825749 0.987654 \n", + "3 1.973268 0.983494 0.819833 0.986614 \n", + "4 1.971295 0.981679 0.814630 0.985627 \n", + "5 1.963828 0.978162 0.799591 0.981894 \n", + "6 1.956710 0.971217 0.769543 0.978335 \n", + "7 1.933095 0.931097 0.641898 0.966528 \n", + "8 1.731916 0.664767 0.164262 0.865941 \n", + "9 1.576603 0.555312 0.111048 0.788285 \n", + "10 1.453085 0.479502 0.088037 0.726528 \n", + "11 1.347127 0.408654 0.069658 0.673550 \n", + "12 1.255455 0.352706 0.054639 0.627715 \n", + "13 1.170657 0.288531 0.044764 0.585317 \n", + "14 1.089404 0.219685 0.036779 0.544691 \n", + "15 1.000000 0.097678 0.024191 0.499990 \n", + "\n", + " cumulative_score capture_rate cumulative_capture_rate gain \\\n", + "0 0.846832 0.019819 0.019819 98.154496 \n", + "1 0.839772 0.019766 0.039585 97.511902 \n", + "2 0.835101 0.019686 0.059272 96.937366 \n", + "3 0.831285 0.019663 0.078935 96.702794 \n", + "4 0.827955 0.019630 0.098565 96.339839 \n", + "5 0.813772 0.097833 0.196398 95.636342 \n", + "6 0.799029 0.097131 0.293529 94.247368 \n", + "7 0.759754 0.093093 0.386622 86.223067 \n", + "8 0.561256 0.132958 0.519579 32.956029 \n", + "9 0.448707 0.111062 0.630641 11.064569 \n", + "10 0.376572 0.095904 0.726545 -4.097720 \n", + "11 0.325421 0.081732 0.808276 -18.267544 \n", + "12 0.286737 0.070543 0.878819 -29.457405 \n", + "13 0.256491 0.057707 0.936526 -42.292719 \n", + "14 0.232078 0.043938 0.980464 -56.062056 \n", + "15 0.211290 0.019536 1.000000 -80.463982 \n", + "\n", + " cumulative_gain kolmogorov_smirnov \n", + "0 98.154496 0.019634 \n", + "1 97.833106 0.039151 \n", + "2 97.534695 0.058531 \n", + "3 97.326806 0.077864 \n", + "4 97.129454 0.097127 \n", + "5 96.382841 0.192776 \n", + "6 95.671019 0.287029 \n", + "7 93.309509 0.373233 \n", + "8 73.191627 0.439145 \n", + "9 57.660273 0.461273 \n", + "10 45.308544 0.453078 \n", + "11 34.712655 0.416544 \n", + "12 25.545453 0.357630 \n", + "13 17.065740 0.273046 \n", + "14 8.940442 0.160925 \n", + "15 0.000000 0.000000 " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "ModelMetricsBinomial: gbm\n", + "** Reported on cross-validation data. **\n", + "\n", + "MSE: 0.06498795354987083\n", + "RMSE: 0.2549273495525163\n", + "LogLoss: 0.24260618450943383\n", + "Mean Per-Class Error: 0.2770904651860334\n", + "AUC: 0.8021582436506262\n", + "AUCPR: 0.4995332242622148\n", + "Gini: 0.6043164873012523\n", + "\n", + "Confusion Matrix (Act/Pred) for max f1 @ threshold = 0.22919100477943258: \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", + "
01ErrorRate
002977495.049856.00.0165(49856.0/3027351.0)
11206636.0134827.00.6051(206636.0/341463.0)
2Total3184131.0184683.00.0761(256492.0/3368814.0)
\n", + "
" + ], + "text/plain": [ + " 0 1 Error Rate\n", + "0 0 2977495.0 49856.0 0.0165 (49856.0/3027351.0)\n", + "1 1 206636.0 134827.0 0.6051 (206636.0/341463.0)\n", + "2 Total 3184131.0 184683.0 0.0761 (256492.0/3368814.0)" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Maximum Metrics: Maximum metrics at their respective thresholds\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", + "
metricthresholdvalueidx
0max f10.2291915.125079e-01167.0
1max f20.0987985.028615e-01259.0
2max f0point50.5411646.446633e-01104.0
3max accuracy0.5337399.262245e-01105.0
4max precision0.8837009.166667e-010.0
5max recall0.0071461.000000e+00398.0
6max specificity0.8837009.999993e-010.0
7max absolute_mcc0.4774525.089233e-01115.0
8max min_per_class_accuracy0.0773827.161830e-01287.0
9max mean_per_class_accuracy0.0987987.229095e-01259.0
10max tns0.8837003.027349e+060.0
11max fns0.8837003.414410e+050.0
12max fps0.0057753.027351e+06399.0
13max tps0.0071463.414630e+05398.0
14max tnr0.8837009.999993e-010.0
15max fnr0.8837009.999356e-010.0
16max fpr0.0057751.000000e+00399.0
17max tpr0.0071461.000000e+00398.0
\n", + "
" + ], + "text/plain": [ + " metric threshold value idx\n", + "0 max f1 0.229191 5.125079e-01 167.0\n", + "1 max f2 0.098798 5.028615e-01 259.0\n", + "2 max f0point5 0.541164 6.446633e-01 104.0\n", + "3 max accuracy 0.533739 9.262245e-01 105.0\n", + "4 max precision 0.883700 9.166667e-01 0.0\n", + "5 max recall 0.007146 1.000000e+00 398.0\n", + "6 max specificity 0.883700 9.999993e-01 0.0\n", + "7 max absolute_mcc 0.477452 5.089233e-01 115.0\n", + "8 max min_per_class_accuracy 0.077382 7.161830e-01 287.0\n", + "9 max mean_per_class_accuracy 0.098798 7.229095e-01 259.0\n", + "10 max tns 0.883700 3.027349e+06 0.0\n", + "11 max fns 0.883700 3.414410e+05 0.0\n", + "12 max fps 0.005775 3.027351e+06 399.0\n", + "13 max tps 0.007146 3.414630e+05 398.0\n", + "14 max tnr 0.883700 9.999993e-01 0.0\n", + "15 max fnr 0.883700 9.999356e-01 0.0\n", + "16 max fpr 0.005775 1.000000e+00 399.0\n", + "17 max tpr 0.007146 1.000000e+00 398.0" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Gains/Lift Table: Avg response rate: 10.14 %, avg score: 9.87 %\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", + " \n", + "
groupcumulative_data_fractionlower_thresholdliftcumulative_liftresponse_ratescorecumulative_response_ratecumulative_scorecapture_ratecumulative_capture_rategaincumulative_gainkolmogorov_smirnov
010.0100010.8160498.4215318.4215310.8536060.8306720.8536060.8306720.0842200.084220742.153097742.1530970.082591
120.0200000.7940098.1715868.2965640.8282720.8051670.8409400.8179200.0817130.165933717.158645729.6564270.162393
230.0300000.7682837.8940658.1623970.8001420.7815340.8273410.8057910.0789430.244876689.406521716.2396590.239112
340.0400000.7289447.6048888.0230250.7708310.7511440.8132140.7921300.0760460.320922660.488801702.3024620.312608
450.0500000.3768585.8452867.5874690.5924780.5882070.7690660.7513440.0584540.379376484.528645658.7469230.366527
560.1000000.1401831.9345084.7610050.1960820.1777260.4825760.4645370.0967250.47610193.450753376.1005160.418523
670.1500000.1163311.3356113.6192120.1353780.1270540.3668430.3520430.0667800.54288233.561146261.9211780.437196
780.2000010.1016351.1455263.0007840.1161100.1085540.3041590.2911700.0572770.60015914.552575200.0783850.445293
890.3000000.0816090.9363602.3126470.0949090.0910480.2344100.2244630.0936350.693794-6.364005131.2647310.438211
9100.4000000.0656730.7652351.9257940.0775640.0733850.1951980.1866940.0765240.770318-23.47647292.5793730.412087
10110.5000000.0536590.6113101.6628970.0619620.0592440.1685510.1612040.0611310.831449-38.86903566.2896600.368833
11120.6000010.0459130.5029511.4695720.0509790.0495400.1489560.1425930.0502950.881744-49.70489246.9571680.313522
12130.7000000.0400440.4172081.3192350.0422880.0428990.1337180.1283510.0417200.923465-58.27922431.9235060.248670
13140.8000000.0345430.3541821.1986040.0359000.0373200.1214900.1169720.0354180.958883-64.58177219.8603600.176804
14150.9000000.0277480.2842771.0970120.0288140.0314040.1111930.1074650.0284280.987310-71.5722899.7011870.097159
15161.0000000.0017960.1268951.0000000.0128620.0198230.1013600.0987000.0126901.000000-87.3105070.0000000.000000
\n", + "
" + ], + "text/plain": [ + " group cumulative_data_fraction lower_threshold lift \\\n", + "0 1 0.010001 0.816049 8.421531 \n", + "1 2 0.020000 0.794009 8.171586 \n", + "2 3 0.030000 0.768283 7.894065 \n", + "3 4 0.040000 0.728944 7.604888 \n", + "4 5 0.050000 0.376858 5.845286 \n", + "5 6 0.100000 0.140183 1.934508 \n", + "6 7 0.150000 0.116331 1.335611 \n", + "7 8 0.200001 0.101635 1.145526 \n", + "8 9 0.300000 0.081609 0.936360 \n", + "9 10 0.400000 0.065673 0.765235 \n", + "10 11 0.500000 0.053659 0.611310 \n", + "11 12 0.600001 0.045913 0.502951 \n", + "12 13 0.700000 0.040044 0.417208 \n", + "13 14 0.800000 0.034543 0.354182 \n", + "14 15 0.900000 0.027748 0.284277 \n", + "15 16 1.000000 0.001796 0.126895 \n", + "\n", + " cumulative_lift response_rate score cumulative_response_rate \\\n", + "0 8.421531 0.853606 0.830672 0.853606 \n", + "1 8.296564 0.828272 0.805167 0.840940 \n", + "2 8.162397 0.800142 0.781534 0.827341 \n", + "3 8.023025 0.770831 0.751144 0.813214 \n", + "4 7.587469 0.592478 0.588207 0.769066 \n", + "5 4.761005 0.196082 0.177726 0.482576 \n", + "6 3.619212 0.135378 0.127054 0.366843 \n", + "7 3.000784 0.116110 0.108554 0.304159 \n", + "8 2.312647 0.094909 0.091048 0.234410 \n", + "9 1.925794 0.077564 0.073385 0.195198 \n", + "10 1.662897 0.061962 0.059244 0.168551 \n", + "11 1.469572 0.050979 0.049540 0.148956 \n", + "12 1.319235 0.042288 0.042899 0.133718 \n", + "13 1.198604 0.035900 0.037320 0.121490 \n", + "14 1.097012 0.028814 0.031404 0.111193 \n", + "15 1.000000 0.012862 0.019823 0.101360 \n", + "\n", + " cumulative_score capture_rate cumulative_capture_rate gain \\\n", + "0 0.830672 0.084220 0.084220 742.153097 \n", + "1 0.817920 0.081713 0.165933 717.158645 \n", + "2 0.805791 0.078943 0.244876 689.406521 \n", + "3 0.792130 0.076046 0.320922 660.488801 \n", + "4 0.751344 0.058454 0.379376 484.528645 \n", + "5 0.464537 0.096725 0.476101 93.450753 \n", + "6 0.352043 0.066780 0.542882 33.561146 \n", + "7 0.291170 0.057277 0.600159 14.552575 \n", + "8 0.224463 0.093635 0.693794 -6.364005 \n", + "9 0.186694 0.076524 0.770318 -23.476472 \n", + "10 0.161204 0.061131 0.831449 -38.869035 \n", + "11 0.142593 0.050295 0.881744 -49.704892 \n", + "12 0.128351 0.041720 0.923465 -58.279224 \n", + "13 0.116972 0.035418 0.958883 -64.581772 \n", + "14 0.107465 0.028428 0.987310 -71.572289 \n", + "15 0.098700 0.012690 1.000000 -87.310507 \n", + "\n", + " cumulative_gain kolmogorov_smirnov \n", + "0 742.153097 0.082591 \n", + "1 729.656427 0.162393 \n", + "2 716.239659 0.239112 \n", + "3 702.302462 0.312608 \n", + "4 658.746923 0.366527 \n", + "5 376.100516 0.418523 \n", + "6 261.921178 0.437196 \n", + "7 200.078385 0.445293 \n", + "8 131.264731 0.438211 \n", + "9 92.579373 0.412087 \n", + "10 66.289660 0.368833 \n", + "11 46.957168 0.313522 \n", + "12 31.923506 0.248670 \n", + "13 19.860360 0.176804 \n", + "14 9.701187 0.097159 \n", + "15 0.000000 0.000000 " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Cross-Validation Metrics Summary: \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", + "
meansdcv_1_validcv_2_validcv_3_validcv_4_validcv_5_valid
0accuracy0.92394396.1908754E-40.924405160.923516150.92312280.92406080.9246143
1auc0.80216380.00126022890.80095090.801374730.801443930.80338930.80366004
2aucpr0.49957460.00225483490.497096720.497974250.499651670.50022880.5029216
3err0.0760561456.1908754E-40.075594830.076483870.0768771840.075939160.07538567
4err_count51243.8417.138750933.051532.051797.051165.050792.0
5f0point50.62473430.00319626880.6245320.62352060.621065860.624730170.6298228
6f10.512628140.00209274420.513278250.509490.51236570.51270040.51530653
7f20.434640560.00225528240.43566850.430719260.436047820.434740480.43602678
8lift_top_group8.4214730.061159628.4660858.3333628.3956738.42348.488844
9logloss0.242606180.00107185470.241791750.2438980.243649570.241941530.24175005
10max_per_class_error0.60538060.00241940210.60422650.60952730.6033410.60527360.6045347
11mcc0.50209180.00259712010.50252980.49981160.49980530.50217680.50613576
12mean_per_class_accuracy0.68913370.0011231190.689690770.687177360.68973180.689195750.68987286
13mean_per_class_error0.31086630.0011231190.310309260.312822640.31026820.310804250.3101271
14mse0.064987953.5334285E-40.0646779460.065434390.065302590.0648028550.06472199
15pr_auc0.49957460.00225483490.497096720.497974250.499651670.50022880.5029216
16precision0.731376950.0057410210.730020640.73287150.72337710.73125410.7393614
17r20.286522750.00201352310.28588090.283921120.285945450.287595060.28927112
18recall0.39461940.00241940210.395773470.39047270.396659050.394726430.39546534
19rmse0.25492666.9268135E-40.25431860.255801470.25554370.254564050.25440517
\n", + "
" + ], + "text/plain": [ + " mean sd cv_1_valid \\\n", + "0 accuracy 0.9239439 6.1908754E-4 0.92440516 \n", + "1 auc 0.8021638 0.0012602289 0.8009509 \n", + "2 aucpr 0.4995746 0.0022548349 0.49709672 \n", + "3 err 0.076056145 6.1908754E-4 0.07559483 \n", + "4 err_count 51243.8 417.1387 50933.0 \n", + "5 f0point5 0.6247343 0.0031962688 0.624532 \n", + "6 f1 0.51262814 0.0020927442 0.51327825 \n", + "7 f2 0.43464056 0.0022552824 0.4356685 \n", + "8 lift_top_group 8.421473 0.06115962 8.466085 \n", + "9 logloss 0.24260618 0.0010718547 0.24179175 \n", + "10 max_per_class_error 0.6053806 0.0024194021 0.6042265 \n", + "11 mcc 0.5020918 0.0025971201 0.5025298 \n", + "12 mean_per_class_accuracy 0.6891337 0.001123119 0.68969077 \n", + "13 mean_per_class_error 0.3108663 0.001123119 0.31030926 \n", + "14 mse 0.06498795 3.5334285E-4 0.064677946 \n", + "15 pr_auc 0.4995746 0.0022548349 0.49709672 \n", + "16 precision 0.73137695 0.005741021 0.73002064 \n", + "17 r2 0.28652275 0.0020135231 0.2858809 \n", + "18 recall 0.3946194 0.0024194021 0.39577347 \n", + "19 rmse 0.2549266 6.9268135E-4 0.2543186 \n", + "\n", + " cv_2_valid cv_3_valid cv_4_valid cv_5_valid \n", + "0 0.92351615 0.9231228 0.9240608 0.9246143 \n", + "1 0.80137473 0.80144393 0.8033893 0.80366004 \n", + "2 0.49797425 0.49965167 0.5002288 0.5029216 \n", + "3 0.07648387 0.076877184 0.07593916 0.07538567 \n", + "4 51532.0 51797.0 51165.0 50792.0 \n", + "5 0.6235206 0.62106586 0.62473017 0.6298228 \n", + "6 0.50949 0.5123657 0.5127004 0.51530653 \n", + "7 0.43071926 0.43604782 0.43474048 0.43602678 \n", + "8 8.333362 8.395673 8.4234 8.488844 \n", + "9 0.243898 0.24364957 0.24194153 0.24175005 \n", + "10 0.6095273 0.603341 0.6052736 0.6045347 \n", + "11 0.4998116 0.4998053 0.5021768 0.50613576 \n", + "12 0.68717736 0.6897318 0.68919575 0.68987286 \n", + "13 0.31282264 0.3102682 0.31080425 0.3101271 \n", + "14 0.06543439 0.06530259 0.064802855 0.06472199 \n", + "15 0.49797425 0.49965167 0.5002288 0.5029216 \n", + "16 0.7328715 0.7233771 0.7312541 0.7393614 \n", + "17 0.28392112 0.28594545 0.28759506 0.28927112 \n", + "18 0.3904727 0.39665905 0.39472643 0.39546534 \n", + "19 0.25580147 0.2555437 0.25456405 0.25440517 " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "See the whole table with table.as_data_frame()\n", + "\n", + "Scoring History: \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", + "
timestampdurationnumber_of_treestraining_rmsetraining_loglosstraining_auctraining_pr_auctraining_lifttraining_classification_error
02021-04-08 17:33:344 min 10.858 sec0.00.6394571.1979530.5000000.4999901.0000000.500010
12021-04-08 17:33:394 min 15.279 sec5.00.6168371.0917850.7922650.8243121.9768490.322680
22021-04-08 17:33:434 min 19.044 sec10.00.5933161.0240190.7993460.8277511.9698230.313274
32021-04-08 17:33:464 min 22.648 sec15.00.5698780.9712720.8027950.8302881.9720700.311855
42021-04-08 17:33:504 min 26.494 sec20.00.5517650.9349230.8048660.8318981.9737810.310356
52021-04-08 17:33:544 min 30.256 sec25.00.5394840.9100680.8065980.8333371.9760330.304712
62021-04-08 17:33:584 min 34.008 sec30.00.5321100.8931910.8089930.8348411.9768180.303763
72021-04-08 17:34:014 min 37.697 sec35.00.5270830.8810220.8108230.8360761.9796310.304047
82021-04-08 17:34:054 min 41.397 sec40.00.5241190.8732960.8120400.8369471.9804300.299840
92021-04-08 17:34:094 min 45.047 sec45.00.5223470.8680300.8134480.8379181.9804890.298310
102021-04-08 17:34:124 min 48.648 sec50.00.5210980.8635130.8152600.8391071.9817740.301199
112021-04-08 17:34:144 min 50.656 sec55.00.5207410.8622690.8157000.8394481.9815450.299180
122021-04-08 17:34:154 min 51.297 sec60.00.5207410.8622690.8157000.8394481.9815450.299180
132021-04-08 17:34:164 min 51.956 sec65.00.5207410.8622690.8157000.8394481.9815450.299180
142021-04-08 17:34:164 min 52.613 sec70.00.5207410.8622690.8157000.8394481.9815450.299180
\n", + "
" + ], + "text/plain": [ + " timestamp duration number_of_trees training_rmse \\\n", + "0 2021-04-08 17:33:34 4 min 10.858 sec 0.0 0.639457 \n", + "1 2021-04-08 17:33:39 4 min 15.279 sec 5.0 0.616837 \n", + "2 2021-04-08 17:33:43 4 min 19.044 sec 10.0 0.593316 \n", + "3 2021-04-08 17:33:46 4 min 22.648 sec 15.0 0.569878 \n", + "4 2021-04-08 17:33:50 4 min 26.494 sec 20.0 0.551765 \n", + "5 2021-04-08 17:33:54 4 min 30.256 sec 25.0 0.539484 \n", + "6 2021-04-08 17:33:58 4 min 34.008 sec 30.0 0.532110 \n", + "7 2021-04-08 17:34:01 4 min 37.697 sec 35.0 0.527083 \n", + "8 2021-04-08 17:34:05 4 min 41.397 sec 40.0 0.524119 \n", + "9 2021-04-08 17:34:09 4 min 45.047 sec 45.0 0.522347 \n", + "10 2021-04-08 17:34:12 4 min 48.648 sec 50.0 0.521098 \n", + "11 2021-04-08 17:34:14 4 min 50.656 sec 55.0 0.520741 \n", + "12 2021-04-08 17:34:15 4 min 51.297 sec 60.0 0.520741 \n", + "13 2021-04-08 17:34:16 4 min 51.956 sec 65.0 0.520741 \n", + "14 2021-04-08 17:34:16 4 min 52.613 sec 70.0 0.520741 \n", + "\n", + " training_logloss training_auc training_pr_auc training_lift \\\n", + "0 1.197953 0.500000 0.499990 1.000000 \n", + "1 1.091785 0.792265 0.824312 1.976849 \n", + "2 1.024019 0.799346 0.827751 1.969823 \n", + "3 0.971272 0.802795 0.830288 1.972070 \n", + "4 0.934923 0.804866 0.831898 1.973781 \n", + "5 0.910068 0.806598 0.833337 1.976033 \n", + "6 0.893191 0.808993 0.834841 1.976818 \n", + "7 0.881022 0.810823 0.836076 1.979631 \n", + "8 0.873296 0.812040 0.836947 1.980430 \n", + "9 0.868030 0.813448 0.837918 1.980489 \n", + "10 0.863513 0.815260 0.839107 1.981774 \n", + "11 0.862269 0.815700 0.839448 1.981545 \n", + "12 0.862269 0.815700 0.839448 1.981545 \n", + "13 0.862269 0.815700 0.839448 1.981545 \n", + "14 0.862269 0.815700 0.839448 1.981545 \n", + "\n", + " training_classification_error \n", + "0 0.500010 \n", + "1 0.322680 \n", + "2 0.313274 \n", + "3 0.311855 \n", + "4 0.310356 \n", + "5 0.304712 \n", + "6 0.303763 \n", + "7 0.304047 \n", + "8 0.299840 \n", + "9 0.298310 \n", + "10 0.301199 \n", + "11 0.299180 \n", + "12 0.299180 \n", + "13 0.299180 \n", + "14 0.299180 " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Variable Importances: \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", + "
variablerelative_importancescaled_importancepercentage
0coupon_type_department1.227769e+061.0000000.534741
1coupon_mean_prod_price2.428440e+050.1977930.105768
2coupon_how_many1.721953e+050.1402510.074998
3coupon_type_just_discount1.354846e+050.1103500.059009
4coupon_type_buy_all1.111026e+050.0904920.048390
5coupon_discount1.098288e+050.0894540.047835
6coupon_type_buy_more8.621582e+040.0702220.037550
7coupon_days_valid7.937698e+040.0646510.034572
8cust_unique_coupons_used2.874513e+040.0234120.012520
9cust_mean_discount2.412111e+040.0196460.010506
10cust_total_products_bougth2.372456e+040.0193230.010333
11cust_mean_product_price1.860420e+040.0151530.008103
12cust_unique_products_bought1.549414e+040.0126200.006748
13cust_credit4.810139e+030.0039180.002095
14cust_age_mid4.308483e+030.0035090.001877
15cust_age_old3.579277e+030.0029150.001559
16cust_gender_F3.050989e+030.0024850.001329
17cust_age_young2.436828e+030.0019850.001061
18cust_gender_M2.314412e+030.0018850.001008
\n", + "
" + ], + "text/plain": [ + " variable relative_importance scaled_importance \\\n", + "0 coupon_type_department 1.227769e+06 1.000000 \n", + "1 coupon_mean_prod_price 2.428440e+05 0.197793 \n", + "2 coupon_how_many 1.721953e+05 0.140251 \n", + "3 coupon_type_just_discount 1.354846e+05 0.110350 \n", + "4 coupon_type_buy_all 1.111026e+05 0.090492 \n", + "5 coupon_discount 1.098288e+05 0.089454 \n", + "6 coupon_type_buy_more 8.621582e+04 0.070222 \n", + "7 coupon_days_valid 7.937698e+04 0.064651 \n", + "8 cust_unique_coupons_used 2.874513e+04 0.023412 \n", + "9 cust_mean_discount 2.412111e+04 0.019646 \n", + "10 cust_total_products_bougth 2.372456e+04 0.019323 \n", + "11 cust_mean_product_price 1.860420e+04 0.015153 \n", + "12 cust_unique_products_bought 1.549414e+04 0.012620 \n", + "13 cust_credit 4.810139e+03 0.003918 \n", + "14 cust_age_mid 4.308483e+03 0.003509 \n", + "15 cust_age_old 3.579277e+03 0.002915 \n", + "16 cust_gender_F 3.050989e+03 0.002485 \n", + "17 cust_age_young 2.436828e+03 0.001985 \n", + "18 cust_gender_M 2.314412e+03 0.001885 \n", + "\n", + " percentage \n", + "0 0.534741 \n", + "1 0.105768 \n", + "2 0.074998 \n", + "3 0.059009 \n", + "4 0.048390 \n", + "5 0.047835 \n", + "6 0.037550 \n", + "7 0.034572 \n", + "8 0.012520 \n", + "9 0.010506 \n", + "10 0.010333 \n", + "11 0.008103 \n", + "12 0.006748 \n", + "13 0.002095 \n", + "14 0.001877 \n", + "15 0.001559 \n", + "16 0.001329 \n", + "17 0.001061 \n", + "18 0.001008 " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "h2o.get_model('GBM_4_AutoML_20210408_164243')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/training-with-artificial-data/03_training.ipynb b/training-with-artificial-data/03_training.ipynb new file mode 100644 index 0000000..547aa17 --- /dev/null +++ b/training-with-artificial-data/03_training.ipynb @@ -0,0 +1,643 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "unusual-blade", + "metadata": {}, + "source": [ + "# Training with scikit-learn" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "impressive-poison", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import pickle\n", + "\n", + "from imblearn.combine import SMOTETomek\n", + "from imblearn.over_sampling import SMOTE\n", + "from matplotlib import pyplot\n", + "import pandas as pd\n", + "from sklearn.ensemble import GradientBoostingClassifier\n", + "from sklearn.metrics import classification_report, auc, precision_recall_curve, f1_score\n", + "from sklearn.model_selection import train_test_split" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "determined-radius", + "metadata": {}, + "outputs": [], + "source": [ + "data_dir = 'data_0408_0'\n", + "train_file_path = os.path.join(data_dir, 'train.csv')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "technical-mapping", + "metadata": {}, + "outputs": [], + "source": [ + "train = pd.read_csv(train_file_path)\n", + "train.info()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "federal-character", + "metadata": {}, + "outputs": [], + "source": [ + "train.sample(10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "natural-argentina", + "metadata": {}, + "outputs": [], + "source": [ + "train.coupon_used.value_counts(normalize=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "congressional-hartford", + "metadata": {}, + "outputs": [], + "source": [ + "y_train = train['coupon_used']\n", + "X_train = train.drop(['coupon_used'], axis=1)\n", + "X_train, X_test, y_train, y_test = train_test_split(X_train, y_train, test_size=0.2, random_state=1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "published-retrieval", + "metadata": {}, + "outputs": [], + "source": [ + "X_all = X_train.append(X_test)\n", + "y_all = y_train.append(y_test)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "incredible-secretariat", + "metadata": {}, + "outputs": [], + "source": [ + "def plot_pr_curve(probs, preds, y, legend=''):\n", + " precision, recall, _ = precision_recall_curve(y, probs)\n", + " f1_, auc_ = f1_score(y, preds), auc(recall, precision)\n", + " # summarize scores\n", + " print(f'{legend}:\\nf1={round(f1_, 3)} auc={round(auc_, 3)}')\n", + " # plot the precision-recall curves\n", + " no_skill = len(y[y==1]) / len(y)\n", + " pyplot.plot([0, 1], [no_skill, no_skill], linestyle='--', label='No Skill')\n", + " pyplot.plot(recall, precision, marker='.', label='GBM')\n", + " # axis labels\n", + " pyplot.xlabel('Recall')\n", + " pyplot.ylabel('Precision')\n", + " # show the legend\n", + " pyplot.legend()\n", + " # show the plot\n", + " pyplot.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cleared-national", + "metadata": {}, + "outputs": [], + "source": [ + "gbm_params = {\n", + " 'n_estimators': 70,\n", + " 'max_depth': 10,\n", + " 'max_leaf_nodes': 994\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "exclusive-enlargement", + "metadata": {}, + "source": [ + "## 1. Training with no balancing" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "modern-fleece", + "metadata": {}, + "outputs": [], + "source": [ + "gbm = GradientBoostingClassifier(**gbm_params)\n", + "gbm.fit(X_train, y_train)" + ] + }, + { + "cell_type": "markdown", + "id": "documented-county", + "metadata": {}, + "source": [ + "#### 1.1. Evaluating on the test dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "oriental-backing", + "metadata": {}, + "outputs": [], + "source": [ + "probs = gbm.predict_proba(X_test)[:, 1]\n", + "preds = gbm.predict(X_test)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "alleged-modern", + "metadata": {}, + "outputs": [], + "source": [ + "plot_pr_curve(probs, preds, y=y_test, legend='GBM trained on an unbalanced dataset, evaluated on the test dataset')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "executed-seattle", + "metadata": {}, + "outputs": [], + "source": [ + "pd.crosstab(y_test, preds, rownames=['Actual'], colnames=['Predicted'], margins=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "extraordinary-fisher", + "metadata": {}, + "outputs": [], + "source": [ + "print(classification_report(y_test, preds))" + ] + }, + { + "cell_type": "markdown", + "id": "regulated-imperial", + "metadata": {}, + "source": [ + "#### 1.2 Evaluating on the whole dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "declared-tyler", + "metadata": {}, + "outputs": [], + "source": [ + "probs = gbm.predict_proba(X_all)[:, 1]\n", + "preds = gbm.predict(X_all)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "married-garlic", + "metadata": {}, + "outputs": [], + "source": [ + "plot_pr_curve(probs, preds, y=y_all, legend='GBM trained on an unbalanced dataset, evaluated on the whole dataset')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "funky-integral", + "metadata": {}, + "outputs": [], + "source": [ + "pd.crosstab(y_all, preds, rownames=['Actual'], colnames=['Predicted'], margins=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "separated-sugar", + "metadata": {}, + "outputs": [], + "source": [ + "print(classification_report(y_all, preds))" + ] + }, + { + "cell_type": "markdown", + "id": "hollow-complex", + "metadata": {}, + "source": [ + "#### 1.3. Pickle the model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "sensitive-reggae", + "metadata": {}, + "outputs": [], + "source": [ + "with open(os.path.join(data_dir, 'pickled_model_gbm_no_balancing'), 'wb') as f:\n", + " pickle.dump(gbm, f)" + ] + }, + { + "cell_type": "markdown", + "id": "intense-injury", + "metadata": {}, + "source": [ + "## 2. Training with balancing - SMOTE + Tomek" + ] + }, + { + "cell_type": "markdown", + "id": "right-three", + "metadata": {}, + "source": [ + "#### 2.1. Balancing" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "sharp-routine", + "metadata": {}, + "outputs": [], + "source": [ + "# NOTE! This takes very long\n", + "smt = SMOTETomek()\n", + "X_train_smt, y_train_smt = smt.fit_resample(X_train, y_train)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "recognized-stone", + "metadata": {}, + "outputs": [], + "source": [ + "y_train_smt.coupon_used.value_counts(normalize=True)" + ] + }, + { + "cell_type": "markdown", + "id": "emotional-valley", + "metadata": {}, + "source": [ + "#### 2.2. Training" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "applicable-taiwan", + "metadata": {}, + "outputs": [], + "source": [ + "gbm_smt = GradientBoostingClassifier(**gbm_params)\n", + "gbm_smt.fit(X_train_smt, y_train_smt)" + ] + }, + { + "cell_type": "markdown", + "id": "progressive-phenomenon", + "metadata": {}, + "source": [ + "#### 2.3. Evaluating on the test dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "swedish-soldier", + "metadata": {}, + "outputs": [], + "source": [ + "probs = gbm_smt.predict_proba(X_test)[:, 1]\n", + "preds = gbm_smt.predict(X_test)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "acceptable-bradford", + "metadata": {}, + "outputs": [], + "source": [ + "plot_pr_curve(probs, preds, y=y_test,\n", + " legend='GBM trained on a balanced dataset (SMOTE+Tomek), evaluated on the test dataset')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "lyric-scroll", + "metadata": {}, + "outputs": [], + "source": [ + "pd.crosstab(y_test, preds, rownames=['Actual'], colnames=['Predicted'], margins=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "lesbian-hybrid", + "metadata": {}, + "outputs": [], + "source": [ + "print(classification_report(y_test, preds))" + ] + }, + { + "cell_type": "markdown", + "id": "deadly-medline", + "metadata": {}, + "source": [ + "#### 2.4. Evaluating on the entire dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "tested-tower", + "metadata": {}, + "outputs": [], + "source": [ + "probs = gbm_smt.predict_proba(X_all)[:, 1]\n", + "preds = gbm_smt.predict(X_all)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "japanese-complaint", + "metadata": {}, + "outputs": [], + "source": [ + "plot_pr_curve(probs, preds, y=y_all,\n", + " legend='GBM trained on a balanced dataset (SMOTE+Tomek), evaluated on the whole dataset')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "middle-history", + "metadata": {}, + "outputs": [], + "source": [ + "pd.crosstab(y_all, preds, rownames=['Actual'], colnames=['Predicted'], margins=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "removable-causing", + "metadata": {}, + "outputs": [], + "source": [ + "print(classification_report(y_all, preds))" + ] + }, + { + "cell_type": "markdown", + "id": "loved-present", + "metadata": {}, + "source": [ + "#### 2.5. Pickle the model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "vocal-twist", + "metadata": {}, + "outputs": [], + "source": [ + "with open(os.path.join(data_dir, 'pickled_model_gbm_smote_tomek'), 'wb') as f:\n", + " pickle.dump(gbm_smt, f)" + ] + }, + { + "cell_type": "markdown", + "id": "uniform-malaysia", + "metadata": {}, + "source": [ + "## 3. Training with balancing (SMOTE)" + ] + }, + { + "cell_type": "markdown", + "id": "republican-foundation", + "metadata": {}, + "source": [ + "#### 3.1. Balancing" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "congressional-suffering", + "metadata": {}, + "outputs": [], + "source": [ + "smote = SMOTE(sampling_strategy=0.5)\n", + "X_train_sm, y_train_sm = smote.fit_resample(X_train, y_train)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "framed-objective", + "metadata": {}, + "outputs": [], + "source": [ + "y_train_sm.value_counts(normalize=True)" + ] + }, + { + "cell_type": "markdown", + "id": "voluntary-orlando", + "metadata": {}, + "source": [ + "#### 3.2 Training" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "lightweight-controversy", + "metadata": {}, + "outputs": [], + "source": [ + "gbm_sm = GradientBoostingClassifier(**gbm_params)\n", + "gbm_sm.fit(X_train_sm, y_train_sm)" + ] + }, + { + "cell_type": "markdown", + "id": "equal-words", + "metadata": {}, + "source": [ + "#### 3.3 Evaluating on the test dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "miniature-contribution", + "metadata": {}, + "outputs": [], + "source": [ + "probs = gbm_sm.predict_proba(X_test)[:, 1]\n", + "preds = gbm_sm.predict(X_test)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "improved-discretion", + "metadata": {}, + "outputs": [], + "source": [ + "plot_pr_curve(probs, preds, y=y_test,\n", + " legend='GBM trained on a balanced dataset (SMOTE), evaluated on the test dataset')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "lesbian-charity", + "metadata": {}, + "outputs": [], + "source": [ + "pd.crosstab(y_test, preds, rownames=['Actual'], colnames=['Predicted'], margins=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "remarkable-disclaimer", + "metadata": {}, + "outputs": [], + "source": [ + "print(classification_report(y_test, preds))" + ] + }, + { + "cell_type": "markdown", + "id": "gothic-wales", + "metadata": {}, + "source": [ + "#### 3.4 Evaluating on the entire dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ambient-carol", + "metadata": {}, + "outputs": [], + "source": [ + "probs = gbm_sm.predict_proba(X_all)[:, 1]\n", + "preds = gbm_sm.predict(X_all)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fatal-terrace", + "metadata": {}, + "outputs": [], + "source": [ + "plot_pr_curve(probs, preds, y=y_all,\n", + " legend='GBM trained on a balanced dataset (SMOTE), evaluated on the whole dataset')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "incoming-raleigh", + "metadata": {}, + "outputs": [], + "source": [ + "pd.crosstab(y_all, preds, rownames=['Actual'], colnames=['Predicted'], margins=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "executive-festival", + "metadata": {}, + "outputs": [], + "source": [ + "print(classification_report(y_all, preds))" + ] + }, + { + "cell_type": "markdown", + "id": "preliminary-large", + "metadata": {}, + "source": [ + "#### 3.5. Pickle the model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "enabling-cyprus", + "metadata": {}, + "outputs": [], + "source": [ + "with open(os.path.join(data_dir, 'pickled_model_gbm_smote'), 'wb') as f:\n", + " pickle.dump(gbm_sm, f)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/training-with-artificial-data/99_data_prep_v0.ipynb b/training-with-artificial-data/99_data_prep_v0.ipynb new file mode 100644 index 0000000..f97e7d5 --- /dev/null +++ b/training-with-artificial-data/99_data_prep_v0.ipynb @@ -0,0 +1,1103 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "abstract-belize", + "metadata": {}, + "source": [ + "# Prepare data for training dataframe\n", + "\n", + "NOTE: This notebook is not finished. The work was aborted when it turned out that the resulting dataset is highly imbalanced. For adjusted and finished version, see the other data_prep notebook.\n", + "\n", + "The aim is to produce a table where each `order_id` is mapped to all coupons available on the date the order was made, along with information (`True`/`False`) if the coupon was used in that order.\n", + "\n", + "The steps are as follows:\n", + "1. Based on the `coupons` table, create a `coupon_dates` dataframe which maps a date to all coupons available on that date.\n", + "2. Merge `coupon_dates` with `orders` in order to create a dataframe mapping `order_id` to all coupons available on the date that order was made -> `order_coupons_available`\n", + "3. From the `order_details` table, select only `order_id` - `coupon_id` pairs, resulting in `order_coupons_used` - a dataframe mapping an order to the coupons used in that order, if any.\n", + "4. Combine `order_coupons_available` and `order_coupons_used`, add column `coupon_used` which stores `True` for coupons used in an order, and `False` for coupons not used in an order." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "coral-advertiser", + "metadata": {}, + "outputs": [], + "source": [ + "import datetime\n", + "import os\n", + "\n", + "from IPython.display import Image\n", + "import numpy as np\n", + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "orange-ministry", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAwYAAAIeCAYAAAABNBiWAAAKmXRFWHRteGZpbGUAJTNDbXhmaWxlJTIwaG9zdCUzRCUyMmFwcC5kaWFncmFtcy5uZXQlMjIlMjBtb2RpZmllZCUzRCUyMjIwMjEtMDQtMDhUMDklM0E0NyUzQTM1LjI4M1olMjIlMjBhZ2VudCUzRCUyMjUuMCUyMChXaW5kb3dzJTIwTlQlMjAxMC4wJTNCJTIwV2luNjQlM0IlMjB4NjQpJTIwQXBwbGVXZWJLaXQlMkY1MzcuMzYlMjAoS0hUTUwlMkMlMjBsaWtlJTIwR2Vja28pJTIwQ2hyb21lJTJGODkuMC40Mzg5LjExNCUyMFNhZmFyaSUyRjUzNy4zNiUyMiUyMGV0YWclM0QlMjJoMS1ZdEpGb0ctaEZXUG5ZNDJhLSUyMiUyMHZlcnNpb24lM0QlMjIxMy4xMC42JTIyJTIwdHlwZSUzRCUyMmRldmljZSUyMiUzRSUzQ2RpYWdyYW0lMjBuYW1lJTNEJTIyUGFnZS0xJTIyJTIwaWQlM0QlMjJlZmE3YTBhMS1iZjliLWEzMGUtZTZkZi05NGE3NzkxYzA5ZTklMjIlM0U3VnR0YzlvNEVQNDFUSm9QeVJnYkhQZ1lDRWx2THVuMW11dGNlMThZWVF1alZyWmNXU2JRWDM5NnhhOGs1QVZNRWpLZEdxOVcwa3BhN1Q2N2tsdk9NRnhjVVJEUGJvZ1BjY3UyJTJGRVhMdVdqWmRydmQ2JTJGS0hvQ3dWeFhVMElhREkxMHdaNFJiOWhwcG9hV3FLZkpnVUdCa2htS0c0U1BSSUZFR1BGV2lBVW5KWFpKc1NYT3cxQmdHc0VHNDlnS3ZVZjVIUFpvcmFzOTJNJTJGaEdpWUdaNmJydDlWVElCM3MlMkJBa2pUUyUyRmJWc1p5ciUyRlZIRUlURnQ2b01rTSUyQk9RdVIzSkdMV2RJQ1dIcVY3Z1lRaXptMWt5YnFuZTVwblFsTjRVUjI2U0NyZGRsRG5BS2pjZ3U1blVIc1JDUExmV1V1TDlTSWRNZ0JEUkFVY3M1NTZWV3ZPRCUyRmM2SWNtYUNmTUJLcnNrNnVqTUVGT3dFWUJicWV4NFdEdEZqc1E0OVF3QkRSUEh3R0ljVW9nbG5YJTJGRmVnbjFMQWhGRVNCWWJxcFFraklhU0pLZWFETG5Ma0N1SXliVWJMbENjTkg4TXBVNFU5VVZpV25NJTJCNmJYMUFFVHZtejYlMkJmJTJGdmo3NiUyQmclMkJvYlloUWdSQ0tJVGdVM084Njc0REtCWlY5RTdFdWxwa0tocm9EbzV1amxyMmtQOCUyQnVqeHFkUzkyTHBjd0JxdDEyVzNYOFV4TlJTUHJBWHlmd2lScHFuc1BzV1ZUZmNja1lRQVB1ZU5xYXVVOTdpUVliV3dDUEFwOXhLNVJpTmo5TTJBWE9yZm5rRExFSGVXNU11Y1gwdUFQdEhHJTJGVUowT0NPZWFZdW5YcG9qN0cyY3dKUkhUYnI1dDYlMkZkTEVDSXNBTUpIaU9kUXRDb01NUXV4WU5MMiUyRlNjY0VreW83TnV4TE1keFhWRml2S1pnRXoza21DNDZvJTJGWmxWM2RSclZ6eGo5cGxpbkhCUlk2ayUyRmVVVjVFNUZyWk11N1dqWHJhR04zVDUxTzRweWwwR0Z0cXU1Wm5tWTBPdWRPbG9Fb0JGS3NHbyUyRjg5TDhoM2JVOVU3YlolMkYlMkZkJTJGZlhuSnhCJTJGdmsyJTJGb0t2SjJYSVluYlNkVG1XeG9NOVJqWDRsbE0xSVFDS0FSeGwxSUtFS0ZPMEs3Y2w0cm9sWVZ6bSUyRlB5QmpTNzEySUdXa3VFaHdnZGkzM08lMkZ2b3FuVHJuNjdXT2lXNWN2U3ZBakYlMkY1WXhpdGZ2JTJCYktzbW53ejlmaVdwZXhjd0R0T0dIMzVEU241aDl5QWFHbktMcVd5U1ZuVVhJZ0pXSXVNTkNraEtmWGdmVFBiMW12R3V3Z2d1NDl6bmVwUWlEbTJtUmRsZVhrVk1JSyUyQlJqUTN5Um5HbUVSNUdEY3A4NzVyQk1lV01hekJVRDg0JTJGaDM3S0pGJTJCeGVDcFNib2NoNFJDOCUyQjVEdmdGWUNJc2NBT05HY0ZjbVRWTmUwTXhYemdXQ1VEaTBhSkxFcWlHeEpjVSUyRks0WlVxTG9DaTNMYXM5cTdsWHBHN3NhaE1Ic2JTTTE1WmJ1U082YkVUejBtRUYlMkZJMVlVJTJGSm1Jd0U1SUtIeVZpYkxGWEUwRUREWTV2RmVERklESTBhZCUyRkhQbUI1dEx4MjFHd20ySlFsRVZZelVVWlI3dDRFUlI1YzhhZ1dzNGd4MSUyQlUlMkJESnRIVEM4eWFMNktDSmNHTFpUSHc2bVBvdUQ0VVZQd0ZpRmhCZiUyRlZZSVcxa05EdXVCdGd3azY3RGhOMnRvb0ozU1l4WVlZRFYlMkJqdTBaaXclMkZSaE02R0dRSk1qYk5odzgyeGdPV3ZWcXN5czRlUFlHNENDSzVyeEZJdVpqSHdHaGRxbmpQUUNHdjFMQXJUekx3NEtEOFg3WWVMdE9yMkM4T3oxcnMzRCUyQmJKdVcyJTJCNDNhcmxiVDRqbVY2WTZWMnZ6YUQ2c3h2SFdTeHZ1M3FhR3U5MSUyRnB1SFdWVDhUSklNTGd4TmNwNkJxam1PZFdybSUyRlVvTktUdDFHU2FGV1FqMEhIVmhONnRqVE1rYlAwckhkWll3MjFyU0dJWUpqdnl1QXVKY3FvTEoyemFIRTNodEFpWVJ5M3JFUEdVRDRrRHBjSjRLYXBid2dPejUxcXdITHpXRGtzWndLMkpnY2hDTE9CZkE0cGtobW96NXdmQTBlU0VCcTFnbWNFbXB5ZENiZHMxdnhSZGI0Q1pLRHFkcjB6UW11ZXMwVWtEJTJCSWtBaUdNVnNlSXFiTklpYkhWREhwTHJjYU1kbTFCNkRtN0hRTFhxeiUyRlZyell3WDJ0M2J6NmlsZUQlMkZrUGpqR0pHJTJGbUF6TnNpeWRJc213OXdRelNkWjZ2TGolMkZhMVpETWM1aEQ3YkNYM001ZGE5dnk5aEJIM1ZUaU03MUQyNGpiMjc4dXB4VHhHUTVxNzVKZHdPTlhiQmN3NGpYNkxyWnU1MndNU2pLSmFicWlFUjZzT2t2YnhqY3dBc1pjRFNMeUdXYnMyeFVPMkp2cnMxeUxLNnJQcWElMkZaVU93YlhiMmslMkJ2WmRyTzM0VXBwdzRldWpNenFVamFUSnJ0c09jZnNlZUxkN3RYSjJUNVBXJTJGWDdQbXo3VzE1dTdMbHI4RkVmRzVYamwwaXYzb041cDBGSGlZSmxBODgxQmR1TDM2VVc5SVVwNXpaVXNKV3ptNnJSOExkQnhwYWN3ak1aeGNzYzJ5eFlFald5N3RTMjFMT0xsTk8xZUxMbmpEYlRrVjdDMG1jbXZoYmFUZVB0Q0hIcm1BaWk2eWlLaHVUcEQyVEFKcEZ5eFVpMzVmaHV1cEJmeVdxRzJ1dFByNTh3RmJjdHlGZlBtVGxyOWxubjJvQnNtOXJuZEglMkYlM0MlMkZkaWFncmFtJTNFJTNDJTJGbXhmaWxlJTNFezAxywAAIABJREFUeF7svXv8VlWVP74dU0JEB9EcrzHq10SZVETNlEFowvD2GZgJLwxf6IVRpMJP0EZUpinyUgqmaRhGaYwW1g/DazoTXpBSQ5DCQEYdisQL4g2UQZ3h+1pb1+P6rM/Z5+zznPM85+xz3s8/8Hmec/blvddee7/3WnutbcwR47d2DB5g8AECRSCw4IElZuuS7xdRdaY6txnwJYN5kwlCvAwEgAAQAAIxCIS6PmJQw0ZgGyIGK+ZfHXYv0PpgEeg34rxgiQHmTbBih4YDASAABEqPQKjrY+mBRQNjEQAxgIAUigApvhfv/oZtw+67715oW9JUThYDEIM0iOFZIAAEgAAQSINAqOtjmj7i2fIhAGJQvjGpVYtI8S36zmjzP//zP2bQoEHB9B3EIJihQkOBABAAAkEiEOr6GCTYaHQDARADCEOhCJDie3DGmbYNIAaFDgUqBwJAAAgAgRIhEOr6WCII0ZQmEAAxaAI0vJIfAqEqPlgM8pMBlAQEgAAQAAJdEQh1fcRYho0AiEHM+P33lnfMrNvuM2M7hpheO/UIe6RL2vpQFR+IQUkFCs0CAkAACFQEgVDXx4rAX9tugBjEDP3Vc+80v33qWXP9RV8EMWjRFAlV8YEYtEggUCwQAAJAAAhYBEJdHzF8YSNQKWKw5KlnzNhp19sR2XO3XcwN08abPXfrZb72vXn2u69/5TSzecu75uzLbjRHHrK/OW/0KfZ7IgBzbl9o/3/SwP72uXsXLzPTrvtpp7L223v3yGc/2m17c/vCx8ysefebMacOMpfPud2+N3XccPO71X80dy9aaj554McbBOO1N9+ybaDf6HPT9LPNgEMOMM/9+SXz5emzzcAj+ppFT6w0hx/Ux7aFrBa6fVRnFT6hKj4QgypIH/ogEWD9s279q530Ev3h+o2/n3DaUDN8yNGGdTDptH4H7Gt178a3NtvyHl66sqGXSZf6lPv5zx5jHliywurKccOHNHR2XFsxqkCgKgiEuj5WBf+69qMyxIAXissmntFYkN7fnI8wl8+Z7yQGciHbZeeedmPOi5y2GMi/qUBJMIgYEJGYfs7pZtixh9sFkQgBLZBc7rDjDjMTRp7QiagQASFCQSSGPlQ/EwLa/Me1rwpCG6riAzGogvShD4wAH1Z8fugxdoMvdd1rb27qpBf5EETqrDhisGzVGqvfeu20o9WZ++ze2x54rFv/WmK5rAulntSHPXRwAssuZLmKCIS6PlZxLOrUp8oQA1qsfnb/b7q4/dA9gTiLgbQy8Mk9C0AcEaBn5AK5fPWaxgafLQu8WEkS0TH4qE6LoSQ0mpjQe3Htq4Kghqr4QAyqIH3oAyNAeuaia39iN/B8ms+/ST1HvzGJIKur1mdRFgMqh4iAtKxSPVpnRpVLhylk2Y0qlw5e2MJbFQsqJBIISARCXR8ximEjUBli4LoPkEQM+HdaZPjDBCHu1KxZYjCwf9+Gu5MUHbI0HHpgn06kgX6Pa1/Yovd+60NVfCAGVZA+9MG1+ZfIaN2aFzFY8MDjnU76fcslt0t5YEJtBUGALFcRgVDXxyqORZ36VBlioE+1eBCTiIEcbHYH4vsANy1Y2Fi46Dl9NyEPi4GsX/vrakHU7atCpKRQFR+IQZ3UZPX7Kk/kaeOt9SK7O7bbYhDloiTbJ+9r0eEKuUHhAwSqgkCo62NV8K9rPypDDPSmWp5yyQ3+s2tfsCf2fJFNbu6TfFeT7hjIxdP1rM8dA14MtVVCt68K5vNQFR+IQV1VZjX7LU/ryXUn7h6B/I3vDXAwBw7kIC8f812r/ffZI/UdgyhiwOVwnXGkppqjhV7VBYFQ18e6jE9V+1kZYkADFBWViE64ZAQLMjmvfWmDMyoRRzOi96L8+2UEIxklQ1ss4khEUlQiSQyoX7JO2b4qCGWoig/EoArShz5EWSyTohJpHcSWTCqLdCJFUJPEgL7fsUd3M++XiztFZ6PvpW6W5cZFO5IR3LitUhdjVIFAVRAIdX2sCv517UeliEFdBzHkfoeq+EAMQpY6tL0dCGg3zipYONuBG+oAAoxAqOsjRjBsBCpBDO5dtNRccPXcsEciZeuvPG+0GTawf8q3yvd4qIoPxKB8soQWNY8AzcO8P0t+8q1OEeEGnPHPeVfRqbxtt/0Ls/xnM1paBwoHAu1EINT1sZ0Yoa78EagEMcgfFpTYLgRCVXwgBu2SENQDBIAAEKgnAqGuj/Ucrer0GsSgOmMZZE9CVXwgBkGKGxoNBIAAEAgGgVDXx2AARkMjEQAxgGAUikCoig/EoFCxQeVAAAgAgcojEOr6WPmBqXgHQQwqPsBl716oig/EoOyShfYBASAABMJGINT1MWzU0XoQA8hAoQiEqvhADAoVG1QOBIAAEKg8AqGuj5UfmIp3EMSg4gNc9u6FqvhADMouWWgfEAACQCBsBEJdH8NGHa0HMYAMFIpAqIoPxKBQsUHlQAAIAIHKIxDq+lj5gal4B7eZPXv21or3Ed0rKQIHH3ywOW7Sj82DM860LRw0aFBJW9q1WTfeeGMwbUVDgQAQAAJAICwEQl4fw0IardUIbPPII49sJQHEBwgUgcAun70wSGKwePFig3lThMSgTiAABIBAPRAIdX2sx+hUt5dwJaru2AbRs1BNpXAlCkK80EggAASAQLAIhLo+Bgs4Gm4RADGAIBSKQKiKD8SgULFB5UAACACByiMQ6vpY+YGpeAdBDCo+wGXvXqiKD8Sg7JKF9gEBIAAEwkYg1PUxbNTRehADyEChCISq+EAMChUbVA4EgAAQqDwCoa6PlR+YincQxKDiA1z27oWq+EAMyi5ZaB8QAAJAIGwEQl0fw0YdrQcxgAwUikCoig/EoFCxQeVAAAgAgcojEOr6WPmBqXgHQQwqPsBl716oig/EoOyShfYBASAABMJGINT1MWzU0XoQA8hAoQiEqvhADAoVG1QOBIAAEKg8AqGuj5UfmIp3MJEYPPfnl8xVNy0wl04cZXrt1KMTHLcvfMw8uny1+fpXTjMf7bZ9F6iunnunGdi/r/3+Z/f/xvkc/f7am2+Zi6+9xZw/tsPsuVsvc+XNd5hRJw40++29e5dyqU0Xf/dWc+m5Z3b6nerrs9fHzPAhRxv6/2+fetZcf9EXG+2WfaFCuT6ug9pw9mU3mt+t/qOt85MHfrzL+z71zrl9YZc2Tz/ndNsufDojEKriSyIGmDed5x3mDWY+EAACQCAdAqGuj+l6iafLhkAiMYhrcBwxWPLUM2bR0pXmvNGnePVZEgPaqNPG6pZ7FpkLxpzahXT4EgPaoMsNeRwxoN++PH22mXDa0MYGnvo3a9795oZp4y0B8a2XOuzbby9wKvxQqIoviRhg3mDeVHjaomtAAAi0AYFQ18c2QIMqWohAIjHQJ5+04R877Xqz5267mIFH9DWb3trcxRLw31ve6XTiT++QxWDquBHm8jnzzV/t+peGT9XHDR9iJow8wXzte/PM3YuW2nJ5I84WhwGHHNAJAt8N+qbNW8wfnl3bsCzEEQOqK2pDL7/3rRfEwF9iQ1V8ScQA8+bD+YR54z8f8CQQAAJAgBEIdX3ECIaNQCpi8Nqbm+yp+mUTzzD9DtjXbubpo12J9Gm/Jgb8zopn/mQuuvYnlgj02mnHLq49LquD70aD3Iros+b5l+0JvosYkOsS9eXzQ48xmoRQG2bOvcu6FFH/fVwiQAz8J0Woii8NMcC8wbzxnxF4EggAASDwPgKhro8Yv7ARSEUMnl37Qqe7Arzh18RAb+g1MfjUoQdadx3pPhRFDFzuRGmIwfED+tl7A5NHn2x22bln474EDRvfMaC6+Zko6wTfsfAlBvqOgbSChC0u+bc+VMWXhhhg3vgRA8yb/OcXSgQCQCBcBEJdH8NFHC0nBFIRgweXrOh02dhFDMg3n0/pqRJNDPhkvh3EgAgI1/9/TzneXP/Te+1FakkMYDEobjKEqvjSEAPMGz9iAEtbcfMQNQMBIFA+BEJdH8uHJFqUBoFUxCCvk8+sxEBfVKYO070Gcgdia4SMUMS/7diju3nh5Ve7EAO6WKzvGNz36yfNoCMONrNuu8/iSa5IvvVig+MvgqEqvjTEAPPmw4hjHAEsar5i3vjPGzwJBIBA9REIdX2s/shUu4epiAFBwS43We4Y+BID1x0D3lTQJWaO/kPP8n0F3uhz6FJqN0cd2rVXT3tfQFoMOOIQRyUaduzh5qEn/mCmXHVzp5ClvvVig+M/aUJVfGmIAebN+6Q9ab5i3vjPGzwJBIBA9REIdX2s/shUu4epiAHlMeCoRATL1HHDzXPPv9wlpGhSVKIoYsDuPMtWrUmMSiQtBBTJiD7aj19aDHgIycWJoiNFEQN6RucxOO1zx9qoS7JNTA7i6o3KY0DRlxDCtOtkClXxpSEGmDe7Nyx6mDfVXlDQOyAABPJDINT1MT8EUFIRCCQSg2YblTaPga4nLo9Bs23Ce+VDIFTFl0QMmkUa86ZZ5PAeEAACQKBaCIS6PlZrFOrXm5YRA4LSlYcgCWZtcUh6Hr+Hi0Coiq9VxADzJlxZRsuBABAAAnkiEOr6mCcGKKv9CLSUGLS/O6gxNARCVXytJAahjSHaCwSAABAAAvkjEOr6mD8SKLGdCIAYtBNt1NUFgVAVH4gBhBkIAAEgAARaiUCo62MrMUHZrUcAxKD1GKOGGARCVXwgBhBrIAAEgAAQaCUCoa6PrcQEZbcegaCIgc+F5KhoRAxj1oudrR+O+tUQquJLIgZROS9aOboUcYs+lNCvqA/NPYrIddLA/kZmQ6e2Tbvup/b7Iw7ezzz/8qtmwsgTbAhTjlCWts2kCzgjOUV98v1QWx5dvtpMHTfCXD5nftP1+9bn81xSokddhuy7zpHhU1/Zn9EJMqPa63qGI8tRpnudxT7vfrdzjsv+psEn6zzLAzMprzctWGgojPihB/ax83f8P37WfPXqfzMcRpzkedHSlYjg9wHwoa6PecgNyigOgWCIge+F5DhiQDA3eyG6uCGqds2hKr4yEQMfwtxqKYqbn2k3NT5tzUoMJHHxqa+Vz4AYdEY3zcZXh4AGMXgfS8aw7MTg0omjbHuvmDPfXDhuhCGSjzX6w/kQ6vrYSn2JsluPQCIx4AW4Z4/uhmOQ3zT97MZpDJ8GUlM5lwDnJPjkgR83N9/xkFm3/lUz/ZzTzZrnX7YnivQ95RIgJaDzAsiyZff1ab/Mp8DlPbhkhT2ZpA/Vt8/uvc2MH99pNry+yRx+UB97irlu/WvmlnsWdcm90HqoUUMUAqEqvjhiIGWa5sQ5Z3zOLF62qnGKzhv5c08fZk+t9TzhU39OykfzR84ZjaNeSF3v0Zz54e0L7esPL13ZKfeH7zyMeo6THUbpB7lJoU0ctWHtSxsMJRFki8EuO/fsdPpPz1C+EZqv9KHnuGya18cP6GcTLf5u9R9jcWGcWFfQWAw8oq/NTaItBmztoHdkzhGZ20SOQRzG3PaPdtu+ywaNkrxxnhOqhzdu1D9q3zX//AXzy8XLzNiOIVY/Rn2oTXTySu+ueOZPFivK6j7vl4sbeHTvtl0ni4wkHwseeNye2rKc+VibeExkPXQqT0klWb9zeVI3s/WIx/GNt942jyxdZUjPu+SGZSTO+iWfkTJJ9ZF8scXANU403jt2724eWLLCypFrzKU8UFkXf/dWOyRbjTF79O5l/v3R5Y1xu2nBA13mcpo1z7XWUn1jp11v66V2Duzf1/bRBx+eZ1E6Rs4zKasdg4/qNB9JduTG3bWS6TxAjGmUxYDmMMvw5i3vdiq/DAcdZVmtQ10fy4If2tEcAl7EgDMCkyIihfriK6/bRZsWpZlz72ps8uk3+vBiR//n50ix0aLOiupThx5oFRu/w5sGmb1YdklufrQJVy5s0mKgsyFTeb6Wh+bgxFtpEQhV8aWxGPTaaUdz8bW3mPPHdhjKss3yynOBFnkiyq+9ucluPC4990xD73CWcXKJkPOOFnH+6EWbF2dy0YmarzQP5aaMsxGnmYdy/vN85cOAKNegqBNg3szR83HE4N7Fy+yBAukH2VfCyseViDeGl008o7ERJewkMaC/JRG58uY7zKgTBxrdJ+4HbdppbFwYxxEDqRMZOy0faeYQb8JJt0p9qk+Kpc589Y2Nkf0l2XR9uB4pOyy35P7B6wCNC60XEm+SMW6PzH7tK3NJeOg1iWV8/332cM6hWbfdZ+595EmbTFPONSYrvD4xGaA5SR/uG81JiSnLSpY1j8ufcNrQLnNXzoMkPOTvPM+idIyUgygSy3PZxwWX63HhxnOVXYkkqdE6DGv0hyMY6vqYRkbxbPkQ8CIGvFmhhUOfMsguafOlVBJyEefNO5/88emO3CxI/1CtKOJ8OzUxkMSF2wpTZXkEMVTFl4YY0LxhmaONh9548jyhUWH5JWuXlF2X64w+XdPPyY0NbQRkmXqj6zMP5b0AuRmQFgDt250XMZBS6+tKpPUV/+0iBpJ0xWEu9ZnGOI4Y8FindR+K27BHycm0L48035l7Z+MOha6PT4BpI+9jQSXc9CEQWx3i7jzwezPOH2vbI/svia9L9ydpKv2e/Jvedc0h2qDSh12R4u4sMKmn5+VaGEUMsqx5RJBda21WYuDSMVGySpjowzayUqS5syGxIdzSEAPWg2nrTJKVEH8PdX0MEWu0+UMEvIiBXATjzPxUrDSP84mDXmA1MSBTrvzw6Rd/F0UEpIlYXnbUxEAqPhCD8ol+qIovLTHgUzcy0/NGjEZDX8CVxIDdB3jU2FVPnuxGbXzlZsh1UixPCPkE3Gceys2cJDKtIgbUTunmw66GvsSALxvznYIoYkAbHukSyfpHb4Z5HPT3PhjHneBntRhIHce4xBEDSVTpJJk+SZfWtZxJPSvHgtw56XI34x3XHnYHi9P9SRpL3ynQxMA1h6LcqdgyJV2hqH6ed3KDS25eUcQgy5rHF3LJ757Kl5hnJQbSkid1jIsY8IHDWSM+Y74z966G/3/cePjg5mMxADH4EOVQ18ekeYvfy41AJmJAykouAq4LT3HEQLpYuKBKMi3K0x4Qg3ILnG5dqIovLTFgc/kB++xhdt2lp92IafO7/JssBlGkVuOX1mIQtREgYuA7D9thMdCb+agDAr1Jc0m9j8VAWydddx+4jiSrjMRYu1fyBq1VFgN9Qi83qvo0+t7FT9oukdtUnBsRPeNLDHSUJFd78orok2QxcM0h6cbEG1H6l0kyW89cJ99ZiIFrrmm5ypMYsMUgTsdITHjN3W+vj5mXX30jMUqQJmiwGOSzDoe6PubTe5RSFAK5EQO6QEQnQEcesn+XUIQuYqDvGEh/YG22lO4/ury4OwZ6YUgiGUUNRF3rDVXxpSUGvPlgv2baiPEiTb/xxXjXHQOScZJlvrQvN8vyYmDSHQPXCaHcFCTNw1bcMdB+6YwJ+YKzy0ozdwzkhoX9x6ls6UpEp+Z8Wiz1g77nwYTlvNEnm8lX3ey8YyDvDrh0Yp7EgH3q+S4K9U/79FPbZ8273/rUk+wxLkRAfSI0+RKDpDsG+uSa2kquK3Eyl6QbJZGke2+uOwZyDtHJ9W+fetbOJ/qwJUzfS5C4aTLajMUgbs3zvWuThIf8PU7HkGthlKxK9yoK5uEKCCLr0cTAhZuPxQBr9IfIhro+ppFRPFs+BDIRAyYDHB1kzKnHm4WP/b5LxI84YqCjnGg3IoZMX4CSpn/pSsTfc1QiTQwQ8aBcQhiq4ksiBizXy1ataWzG9OaKn5GRauQiLN3lotyIeCTTRCVyEQPfeeiKXhTnI550x0C68lA/x5w6yEaKoQ2r1DHUX8aHNyL0nSZLWsKli8PUccPNc8+/bDgiFG1UdXQcqYPkGPhEJWICyNHXBg/oZzZt3tzlsCRqUyllhcrxcZdimeJoQVIXyrZPOvMks2zVc40L8NxOGZ0oTjP4EgN2gWEXHh2VSBIDH5mTFmBX+3RUIsJi2LGHWZ941xyicjdt3mIWPbGyS1QlubaQvJAsRl2Sl3OcoklRVKIkVyJpKZSRtuj7OIsBEx4ZPclnDLmNMiqR1DHspke/s6wyMfCRPzkmPrj5EAOs0SAG5dql1K81icSgLJDkdYqAi8dlGdH321FVYhCFst7kNHvhUpdd9oXUhxiUSyrL0xq6j3Licf2doUubbWlerjzN1u/7Hsn2f/7pBXPCpw/zfcXrOR/C4VVQwQ+1Ch/qlk8+iTy6r6MSYY0GMchDrlBG8wgEQwz4BM0ngoYLDp+wa81DiTebQaAOxECb8znyTV7EgBdx+jfpEmkzY5T1HT5J5NNjyiVCrkOcW0RGAkpblzwR1u/qDMxpyy76edow3fPIUnsHIM8PY8ZhMVm30phQToIy4Xjfr580/2ffPRLvQKTFpyrEoJX4sKsV59OQQQA03j7uRlFjxLKIzMfREhzq+ph2PuL5ciEQFDEoF3RoTR4IhKr4klyJ8sAGZQABIAAEgEB9EQh1fazviFWj5yAG1RjHYHsRquIDMQhW5NBwIAAEgEAQCIS6PgYBLhrpRADEAMJRKAKhKj4Qg0LFBpUDASAABCqPQKjrY+UHpuIdLJwYuJIINYN70ZeWZHQMGT1Cfk8RTyg0IkUDoYQ2Ub7WOqLN8tVrGuEUXbjoXA4UFUV+KNrLtyePNrN/dr+NTEIJfugZag8lp+JsvEnxzJsZl7h3QlV8aYlBnnKeNAZpo4kklRf3e5KvtgwlHFVO3CXYvAIOZOkf3gUCQAAIFIVAqOtjUXih3nwQqAwxKMPFYh1dgYdIXzLlzRQRA4pbv3PPHcxXx/5944Idbey+fdMvzBsb3zaXnnumaYYYUN0cdo7boTdhctNWVGSbUBUfiMH7UhVHDHxkKik6jk8Z+ahClAIEgAAQKBcCoa6P5UIRrUmLgBcxkHHA6SScExxRNBH5m45ZLeOzyxN0GUmEvpfRD+LKe+Ott80jS1d1SbgSdbKYtV0yZrkGNSq2OT1DSXIo5rWOOa+JAW3IKbEQJ7Q5/KD9GtlwqRz6/ZVXNzbijlMiGkrCFBdxhvrLz+isnpKgUMIoyu5JkSb0aW4RFpdQFZ8PMWhWzmXMcRlTX8o0jSlHAqHvZ/z4TrPh9U3mkP33Ni+88rqhuOdUzlfHdpjZP/93QzJ2za13W9m8bOIZZubcu6ysuuallH8q/4cfWKAeXrqyId9EWCkBEn2i8o9oeZJRTaheTsJF8dypXRwLXs/zIuQyrSLF80AACACBvBEIdX3MGweU114EEomBK3OoztS65269zNe+N88QGeAFn7pCz9EmRWdYpEQwnAWSiQFlzeTMr1HlUdn6FJzq0KeKMosmJy/ybdfk0SfbxDi0GZEESJ66EwGQ7efnKBlTVLp7V1hKdvnoGHKUTQxHWNGH3HooQQ9txsjtJ617j4sYaNHSxKAIq0uoii+JGERlIPaVcyJ4lLRLzgcaO54bJA862yvPL/pNuhJxJloOTUmywdmXZZk6y6+Uf5lNVs4nmosui4G2nskEWSzjFIaT6uU5I+e8nOdFyGV71TBqAwJAAAh0RSDU9RFjGTYCicRAZ7yUf9+7eJl5dPlqu6Fl6wFlVp06boS5fM5886lDD7Sbf+kuQHBdddMCc+nEUfbUWm5iHlyywqs8DbneOES1mU5IZ5w/1nxn7p2R7aJTeXqGs6i6/LT19/Q3b9jkJkdu5pOIwQVf+Htz5Y9+YUkAfShXw1kjPmOm3/CzpomBvmMQFWdaE4Mi3DZCVXxJxCBKTlju08i5a+MtZZw27i7ZleSCCQXdcaGNt5yXcfL/7NoXOpWv77NEZc/VsqTnpCTamhiw3uBnipDLsNU6Wg8EgEAVEAh1fawC9nXuQyIxkCeTcvNPZICIAbsSMIjkgsAbcE4P77sBoQ2TT3lRJ9+82aHfdJt5kzbtyyMtMXC1a+y06zsVrV2C6Ed9iVT2rVliQCSJUsUP7N/XugNRX8jdJ8r64COszVoMitiAhar4koiBlhNNgH3lXBIDnWCIXfeIGBAhZ4KuLQaSiMtNvZ6XLvkn0iDL9yEGUUSAE52RDLPrkWwDWwx4foIY+Mx2PAMEgEBVEQh1fazqeNSlX4nEIMliIDfkDJo+IU9jMfApL6vFwEUM5ObHJQCtsBgQMaBT2XsXP2k2vbXZEpf999kDxKDEszCJGCRZDOLknE/MeR7R33QnRVoFtMUgD2Lgkn+tA3yIQRzJlPpByjmIQYkFHk0DAkCg7QiAGLQdclRojEkkBr53DMhNgf2S2ZUoagPOiz9vfuidKN/ruPL0yKW9YxDVLu1jTZsf2iixaxHXGeU7nvWOARED9gXn1PBUX7stBkX4coeq+JKIgdzUx92liZJzGnt9h0e6+nTvtp29z8PP5WExiJN/Iq1pLQb6joEkEzJYQNQdA20xKEIusToAASAABIpGINT1sWjcUH82BBKJARXP0VDItWbMqYNsNBN5r4BdEDiSCW9cojbgfDmS4vevW/+qmXTmSeaZtS+YC8eNsHcOZOQVV3m6y2mjEvm0K8qNiOuNikpEbXeFXky6Y0DEgDHjC9auspLixlMbm3UlKiL6S6iKL4kY0DhIOUkj5zKaF98NkbkwSDanjDnFLFj4uL2rozfuTF6pDRyViO/0uFyJ9LyU8h9nMWD3oKSoRLL91C5+Xubs4KhEmhgUIZfZ1CreBgJAAAhkR4DWx6M/styMHDnSTJ48ObbAjRs3mv/6r/8yf/3Xf2169uyZvXKUUFsEvIiBRMd309luRMt8qugiBs1gRJvN//zTC+aETx/WzOud3kEeg+Yh9CEGaUvPU07S1t2K5/O4s5JHGa3oG8oEAkAACLQaASIGFwzZ2dx2223m4IMPtuTg7/7u77pU+9WvftXMmDHD9OvXz6xYscJMmTLFfPvb325181B+RRFIJAby1JMw4AuPdBG5bJ+ynizqzMdx+QiSML3v10+a/7OVY+0yAAAgAElEQVTvHqlDmOpy+SIrMh8nIR79O4iBH25JmY/jSkHmYz+M8RQQAALVREBa1P/zP//TzJw50/Tp06cTQZg+fbp57LHHzM0332x69+5tNmzYYMaMGWOOPvpoM23atGoCg161FIFEYtDS2lF47RGositR7QcXAAABIAAEgEDTCND6OOcrR9v3BwwYYP+dP3++mTt3rtlzzz3NsGHDzHXXXWd+9atfmb59+zbqWblypfnMZz5j7rvvvqbrLvrFbbbZptAmkPWlrh8Qg7qOfEn6DWJQkoFAM4AAEAACQKBUCND6uP/rv7Jt6tGjR6NtmzZtMuvWrTPbbrutefvtt817771n/uIv/qLx+//+7/+aj3zkI+Zv/uZvGt9t3bq1VH3zbUwR7Sb8nnrqKd8mVu45EIPKDWlYHQIxCGu80FogAASAABBoDwJ6ffyP//gP6060Zs0a60501llnmU9/+tPm/PPPNyNGjGg0iqwKV111lfn1r3/dnoZWrBayVhRBSMoCYymIAV8wPPf0YTZjso5KosHSCZwoU/AFY0612ZfxCQuBuhADmfBMRxEqcsSSolzxHYFDD+zTKWO5q82yvLQBAeTzodwvcEUPa/eYyrtgUVnOuT1llUMZLavd2KE+IFBWBHh9fOKJJwyRAkkIuM333HOPjVp09dVX23sFdN/gvPPOsxeWTzzxxLJ2rdTtAjE4YvzWFfOvLmyQmtkA6ORRWS44FtZxVGwRqCMxoNC2ZfnEEYNmIgLp8tIEBJDPNlN3EZiWhRj4kjCdkbsIzKLqBDEoy0igHWVCgMOVvvjii+aSSy6xFoKoz8MPP2zvGjz99NPmE5/4hDnnnHPM3/7t35apK0G1BcTAgxjI3AI0uvJEiuOYU9zzgUf0NTt272bOG32K0XHLXadY+pSQEjeRxWCXnXvaE8qePbqbuxcttUJFZVCm1LMvu9HmUuA8B/TbFXPmN3IhBCWBNW9slYmBPMUdN3xII5GftBhQcjKdB4SJA0eOknPONa80WdaZkX94+0IraQ8vXWk4R8Hy1WvMtOt+ar9PykMgy6f2+5bnu8HXBwRRG13uO837m+94yOZBke125Rehsmb8+E6z4fVN5vCD+jSSx3EuFRlpTeo6+T2NxabNW8yiJ1baeuk3TuRI+okx5QSOrLNcZZM8cGJEsnS66tXqIeo5KUNRUeN85HDd+tcM46FzuLCO13Ii5ZPHoRk5pOR9PAcIF1o/8AECQOD9gzMKV3rSSSeZQYMGAZI2IQBikEAMSNFf/N1bzaXnnmlDZNIi8ejy1V0ys3Lm1CMP2d8qdpnvgBazi679iblh2vguYTblKaGM407EgBaqCacNNZw5lhdSWsSINHDSJpKVNCeTbZItVOOBQFWJQVSGbM7wzcTgvNEnm2/c8DNz/tiOxtwiyEje5Qmq3GzdtGBhY0NJG0KeV/SenBOaGNDGi4h1vwP2tVmTOZGey2KgMxdrYuBbnq9FUBOIqPnM+mHtSxtsRnLKFs66SWdu5izslIhR4kQ6TJ/yMwbkLsXl8QZf4iQztJNuumziGfagQmYol3pPZo3nA43Jo09ujAGNmc5wreuVU4g3+FSvHkedhI7f85FDnalekjJZ7uYt7zb6SqSSM8Nz1nZqFx/osG72lUNYDDyUJR6pHQKhro+hDxSIgYfFQA6yVPT3Ll5m1jz/cuOEh5X72I4h9lSfFsEBhxzQsB7ouwN606CJgSQkst4oYgB3ojCnYqiKLymPgT451Rtr2lRpYsAj6Ep0pr/nvz916IFG3wHQG7KZc++ym2myRshNmIsY6I26bn+a8nxIu5y/mpRoXKi/nAuE27/P7r2NbFNce12baHnowaf4NE60eZ912/th/6Q1lPSZJgZyFsrxou+5LF026VE+bNG/yXtTut0+9wV85FATA9kHl3xEuYzRex2Dj4olqC65ATEIU3+j1a1FINT1sbWotL50EAMPYiBNxjQkbK6WiyV9r4kBufvIj3ZX0Cd3mhi4TkBBDFo/MdpVQ6iKL4kYaF/uKGLAp8XapYVOZiWx1qe/TLjpe3naHWcxkJtSH2KgN6Gu9tPGNak8H2Lgc78gijBJYsDuKIwXu8SQq4ruv9yI8/PSZYa/Y3dFstT02etjlpDIdmhioBNCUjlkqSErh2vzT8SAXbp0vfI+iiYucWPC5fjKIcucdNHs3m07a12SRIzKlYSUCRrLQBIxcMkhiEG7NC7qCQmBUNfHkDCOaiuIQQIx0AuLr8VAmtddQpJkMQAxCH16Jbc/VMWXRAx8TmqJGMgTYd4cTRh5gt2QRVnY5PdxFgO5iSRXmrTEIMlikKa8JGKgLQQu65/ekMq/yWIg2yQlL+qkPerZuM2pPCF3EQN2A+KNdBqLgbS8umZNqywGWg5lPXT4w4QozpLALlSaGPjKIYhBsq7EE/VDINT1MfSRAjFIQQz4BIkGXfvGxt0xkL6x5FqkF5iB/ft2cTmK81XFHYPQp92H7Q9V8SURA72JpY2TvmMwtmOw+e6t9zTuysgNsfRVl/d8FjzwuPOOAfu9s/85z9NmiEHSHQNfYuBzx8A3TCljGqd/2JpCWLIPvA4Pq+9NyU2tdF+U9xTkBtmXGPBFYRk0IemOAd2BkPVK4tjMHQMfOdQubXI85MZeWrLIAuK6Y9CMHIIYVEenoyf5IRDq+pgfAsWUBGKQQAxkFBQyzU8Zc4pZsPDxTpsZMoOTyX3wgH5m0+bNkVGJoqKe0JAnRSWKusTGCxS9T37T9EFUomImUNZaQ1V8ScSAcJFuJZPOPMk8s/YFGzlLblSlG4mMKOOKPhQX7UtGCBtz6iAbuYsv38a5cND8TRuVyLc8n6hEPvcLCE/uO10InvNBlCUZ7UziLSPrRN0pcEUBkt+zGxG587gsBkzClq1aY4Mr6Ag71G4+ceeyqW1yfHRUIlmvnl9x7XZZTHzk0Dc6lpSTqKhE1N5m5JD7hahEWTUq3q8SAqGuj6GPAYiBxx0Dn0GO8jv1fe/Km+8wo04c2CVikc/7vBDRv+zv6vsenisegVAVnw8xKB7dbC3w2dQn1ZDkRpT0vvzddSk7TRlleVZahMrSJrQDCACBciEQ6vpYLhTTtwbEIAMxkKdXBH2zpz1ZNiBZ3k0vLngjbwRCVXx1IAZZSbe0BuYhNyETA30pOSrfQB4YoQwgAASqg0Co62PoIwBikIEYhD74aH/xCISq+OpCDIqXELQACAABIFBPBEJdH0MfLRADEIPQZTjo9oeq+EAMghY7NB4IAAEgUHoEQl0fSw9sQgNBDEAMQpfhoNsfquIDMQha7NB4IAAEgEDpEQh1fSw9sCAGsQhsYzIQA1cG0XYJRdLFRh1LXrYLdxPaNUrx9YSq+JKIQZzslQP51rWC59a5pw8zl8+Z3yUfg65ZYvXam5vMLfcsMheMObVTjgdXa3U41CidULSeah3SrS9Z57FJW2OZwpDGzUlXhmff/rZzvsfJs28uDt9+le05CqPMyR+pba5IXO1ud6vkPNT1sd34510fLAaBEgOfi41JytqVSClvIUN5bgRCVXwgBtFj6pO3II4YUGjQNPNSEnwKYxwVthjEoHkNBGLgh13SWuNXit9TIAY3Gpn93Q+11j4FYtBafNtdOohBAjFghXf4QfuZa26920TFB9+xR3cz75eLbS4DyitAi7sr3jqX17NHd3P3oqV2vGUsclecbikYrozJXB7F2j5+QD97skCx3KldXx3bYb71o1/YYmjQkf+g3VMtur6qE4OoeUNIRMk55TTgDLjyZIySAvpsbvkZOR9pAb3o2p+Ydetf7ZSrgGPNU1t4TnPmXpkngCON6RNV1+Y9KS+Jnvf777NHp3madl7K+lxWwChcWE9R/3U8/mHHHt4p87TeHEedLkfpycsmnmFmzr3L6iAZsU1Hc2P9R9//8IP8DA8vXdkYF2qjzAKvk8/xzIrrJ7X56T+uM48sXdWQA1ceAhlBidqtE/NxpmS9GZIyRTqYslGPnXa9bR6Vwxm9pZ7WIaZ9cdQbcTk/qD7KEB63HsjxZxnYsXt388CSFV3GS+LhyjERt0665jvlr3Dlx6A5zzqA5IeiWG18a7M5f2yHzZVBJ+V63aX8KBLv80af0lC6emwId9f4S00d1z5Xzo+4XCuybNk/lhHZZn5Wlkc4UHI90mv0YYsBJT2lxHqk5+QehZ6J6nvUvCdMdOQzauPF197SwD1qfurcJVF9aHa1DXV9bLa/ZXkPxMCDGNCEG3bcYTZxmU5xT4qIFjZO9EObCnpOZu+k5Dm0OaEEQPSh8iacNtTmHZDP0eTmzKNyk6Inmt4AyAVKLprklsALKv2fM3LK7MtJ7khlEdSqtiNUxedjMYiTc50dluZNx+CjGm40NBe+fdMv7Bw54dOHeZ2iy0y7PB9pEaVNEG0aaJOq/8/Ju0i+eONG/+fEaDxveTNC39PHlXtEzie5yFIm8zg85MaXF23OiB4n+7K+OLLi0lMyQ7LUEYQbfQh/1nlf+vxQQ3opqu+8eZR67d5HnmzoPNZrVKbMruyjT3lcPj/0GJsh3mUt5fHnJGQyV4LOpuzqNxM1qov1sw8xkBtzstzwZmr56jUNsuvS0ySD/EmDo5QZWb8m2GxFkuuBrJPljcer1047NtxVGA8+oU7KSh21TvKmlYhi1DrJCfD0plSOH2E3a979nZLoRa27vq5ErvGXayPjEtU+woXHmLJ1u7K207iwDqHn9Gb/U4ceaOVMZyPX5OTFV15v6CSey0wMpo4b0cllUc6POLmMytzN48NzTRODOD3Chzp5rtWhro95YlBEWSAGHsRALmR6ovBmg10AaHLohUwmPzv0wD6dFkat0B9dvtoqAM4GGuVDqBdGlzLUvsuyHyxsadwWihDQqtcZquLzIQZS3qScE1GWcs0n0pedO8p8+0e3N06nNryxyfzh2bVmbMcQc91P701MAhh3sh3n6sDzh+ctL9ZyrtNmSW6wou4BuCx5tMgSMXDhQRsnTQx85qU8BOjebTsnWdG4SH2hT/95M0akhMfo///VY2bXv+xp3v7vLYb0V1Tf9cZGb4LlJkrOaS0XUfqUD2SYqLgOM3Q/5ZjftGChrZYPWVz9JnIqx0KWITN2k36Ow1HqV94w+bhb+OJI5fsQA4l10h0DiQ+3leRAjomrDN1uvU5GzXci6TQuPhtvn3WX5cS1QZVj7hp/fQjnshhoYsA4a0unb+4R2b8oAsEbdVmeixhoUsH4xn3P817vXfLAPcsaHur6mKXPZXgXxMCDGGgztjStSYXHypQ2MnxBiE8gWMHQwhqn0Kdd99NOchFluo1aYKRZlE3zmhjozQdV5LMBKYOgVrUNoSo+H2IQJ+eSAOsNHG1Glq1cYz592CfM/b950vz9kKPNDbfdZy4cN8K66bk+2t1ILuqyDtpES1cLKk+6ekSdltFizRtSeZou26IXd20xcOHRLDHwuV9A7dO4uIgQ6wPaWJEOIyL05ZEnmF8sfMwMPeYw8+snnzaH9+1jFi1d2dhgc//1hjGOGEhdRe9zsjNNGGUZ3NezRnzGfGfuXZGyoPup5UpvQJkAyn7HbYRdxEATSikTWldH6em4zbsLxzhiwC46cz5wy4paD6IsBnIDKYkBu+dwO7WrCn2vx1/OBbKaxM33KGKgiXQc0ZAY+VgM2FUuavx9iQGt69LFimWYrEXswivHla1Y8jvtUheFaxzRYGJAB4myXt4zsJ6T/aR35EElu7JpneCyGETtd5IIWZa1O9T1MUufy/AuiIEHMZAnffJkRLoXyBOkJItB2pMeLSguUzo9JxWoXDxcZmQQg2KnYaiKLwsxcFkM2M3n6TXrzMuvvmE3pz+//zdmh+7b27+TfFd9icGDS1Z02qj4LIpEDKj8exc/aQVm1IkDjTzZ4wVXutm0mhjIuRunE1zEgF0e5YZQum/c+PP/MPvuuav507pXzD8OPcae7m7avMUMO/Yw69LTzIaWdKY8gY6zJMlNHltj9tvrY05Z0BYD+bc8maZ252Ex0K5KUSezro2q64TYl2BpYkD1yM03j41rPYgiBvSOtKjQ39JyRGuc66MtBnHrpGtc5CZYn8jnSQzYRcwl97KPUk70Jj2KAJLOclnH5PO6rCwWA/Yw4PLlnJp1230Ni4yrXzwf6F+9d/HZ74AYFLuHaEXtIAYexED6BksFHHfCFXfHIO7kUJKQOH9Oac7XyivKp9RFDHDHoBXTyr/MOhKDOJ9jWojobsFeH+ttQ3bSHPvB7b8yZw3/TJfNaBRhlidaLouBJAZ80nbkIft3WRT1Ys2LOV0q1YsxtyXujkEai0HSvPQJUxq1UdAuMHG+1vTbfb9+0t7xoM0UEYVn1r4QeVrvu6GVxIBPNKmdfKfDdSJJz1B7yKIqgzVIGZB3TIi4xG3cXf1mH2s+ZaUy5B0D9hdnP3ySG333jGWK/PLJusSuLS49re8YSDmJsxjoezqMo9wMuu6cRRED7ieVwxZvfcdA4hZ3N0Kuk0l3DKL85/X4UXnyjkEzJ9cS/zi51xvoqPaRRUOPE73HpINJFlsV6H6FJNOaGMj+6QMHvefQdwzOG32y+cYNP7MumHyAwVY9+a6Wy7g7BnxXMg/c/VfDrk+Guj5m6XMZ3gUx8CAGpAA4moh07Yk7iUuKSnTpxFHWLUKXIc2LrggQelOgoxvwwsnfk6BRVKLZP/93w/XSd67oHmUQzLq0IVTFl8ViwPdn2EWBzfD0vTZzx/lFN0sMeOPDEbvGnHq8WfjY742+xBd1iqdPmqPawIuyr8WAF2wqyzcqke/9AiozTk/xaSG7nUiXhzh/d91vX2LAQRUoYg65TkwZc4pZsPBxq5fifPipviRZoH7KqClSrqLGTbr16H5zhJdJZ57UiQzxO6SbBw/oZzZt3tzplF3jyPqcXNXYxZTkjj5RBMcXR77wSkSJcBxz6iAbTUi7lch65HqQFJXIhUeUu4scG45Cptcuua7JcZFr12mfO9ZsemtzI++H/I3GYdmq5zpFJYqKDiXx1hZGJpbycnqU3EvZjmsfl0fPR+mwuOhTkujS/6eOG27Hj114ZBt0VCKKxkRWO/owQaIDFNanGnuXnPvKf1bcs6zloa6PWfpchndBDDyJgdxQl2Hg4lwHfNsHNyJfpFr3XKiKL4kYtA6x4kp2mfv1Iu6KWOTbcszLaKTi/MejCJAv3niu/AhoN7HytxgtzAOBUNfHPPpeZBkgBoESAxKaJHeDOMGSFxfjfEeLFM461B2q4iuCGMjLflo25IldK+SG6+ZwnK2aW5iX0chKlx7XBXRtGWmFHKDM9iCgLe4uS0V7WoNaikIg1PWxKLyarXfGjBnmkksuMVdccYWZNGmSzXW1detWc80115gLL7zQfPOb3zRTpkxptvjg3tvGZMh8HFxv0eDSIRCq4iuCGJRu8NAgIAAEgAAQaBkCoa6PLQOkRQVv3LjR9O7d23zkIx8xO+ywg9mwYYP9++233zbvvfee/btnz54tqr18xYIYlG9MatWiUBUfiEGtxBSdBQJAAAi0HYFQ18e2A5VDhVOnTjUzZ84077zzTqO07bff3kyePNlcfvnlOdQQThEgBuGMVSVbGqriAzGopDiiU0AACACB0iAQ6vpYGgBTNISsBrvttpvZsmVL461u3bqZ9evX18paQJ3PlRjExZLWCc9SjFemR5MuMsZdqNTRjzI1BC9HIhCq4muWGPhc4I0Cqtn3fC8tJkUbihPfpLs+cdF00t4p8AlT6pPYsKrTMcs4+mAisdVRlPj9qARSSWW3ut1Uf6vlwrX+6b63o69JeOP3MBAIdX0MA92urZRWg7paCypPDHw2HUkbLp8yQp0EZWh3qIoPxOB96fGJDpYUZjOJvEs59cl23OoNYBnmTVFtSBpLalczxKAd/Wm1XIAYtGMU61VHqOtjqKMkrQZ1tRZ4EwOdulzGn+bfKGrCwCP62jjMFF+ZPl/73jxDsYwpYgoluqGEN5zq3Va+zTY2bjklH+O42TIGsY7MwPGXXd9HncxQ5kpOrCLjFlNcbc5yyDHFr/nnL5ibFjxg3njrbfPI0lWNONtJJ6KhToIytDtUxedDDJLk7YZp4w0niuLY7iSXFH+cY+jTGG01xuzRu5f590eX25jt9J5OAqQ3zzyfqDxO3ETRbGTscS5r+eo1NnEWfXiOueKuy3r06X3UvDx+QD+bLIpzJlA+kW/96Be2GJ7/9H9OSuiKuMP1SiISRdpl3HXSJZy/hOPLa/x8+kl1y4hQMgKU6315KhyVzyFte6g8yrq86ImVZt36VzvFjZdjIuvV8qfj2utNvjwkkfkWqHzuMyXr4sRW2mLA9f1t/762SZ/99KE24ZVLNuVYcLsPPbCPLZ/z5tC7cr1JOw5cd6vkwmf9k/jdu3hZl7nmiqdfBv2MNhSLQKjrY7GoZaudrAYUpYiiENXtbgEjl+hKFJXkh1PPR2V0pIKJGFD2SVfGRM5YSRt2nQGRlCS/R0qUs2bKhEaUuTXqe7mp0MnLZCg/aiPHWqeNGadw58WQsx5GbUayiRze1giEqviSiEEaeeMss3KuEU56nrCcxpECnk+UKIizkMrMtTPn3mXJOM0VV2Zc2Q7XnODNssxArrPUujKQy36xPPiSb/mcy9KgT4Zl5napX0h/cab1uH5qq6LcxOrsu6w74ogBvTPsuMO6ZAxOypDL48gHKTqbLGHJ9VJ2ak7+JPWdlB1JWEgXS5kl/U0fIhNSR8uMt5IYkK7WmWR1aNuo+jgZnsaU35XjRYnw5ByQ7/iMY95yIbP6csZouf5F4ccZjfvs9TE7P10ZkmWWYKwa9UUg1PUx5BEjq8EXvvAF86Mf/ah2dwu8iYEeYLl4ULZBmZqdf9MZVPWJGStxTl8uNytSeUsCINuRlOgnatPiivEddUrGGzWuE+5ErZvmoSq+NMRA5smIc12TvxHicp4kubxJWeUTXdr8x7l+yHkkN7LaJcI1d7QbkWte6g2Z7Be328edyDfbcVx9si+0meVDDs5GLfWZPBjw+V7e57hpwULDm784/SfH9dU3NnbRp6wbqTzeaOoNtpydUcQgLk+LixzKMl0ZrCUxICLB/ZUERVoMfNpNFgO9PsjstlHj4CuvrZYL1zzR/eYxGnbs4daqLtcb3D9o3VoTYsllWR9pvcOn2ghsXfL9RgcTLQas5Dl1Ov3NZmXXwnre6JPN5Ktutq5DdPLhWlho46LdlKh8bWLmuqVJWZpf5fdxi7l0o2CXiShioNOygxi0bkKURfGl7WESMaDykuSNifHYadc3qmfZpy/kBt+XGOjLxnIz1L3bdg33Pq6QXZc0MWDXIn5OuvjJzTxb7vi7qHnp49vtQwx87hfwoQBjR6frEkdNDHz7KQmE7L/8XvYzjhi4xpVcutKWp0+WXePI+k7LOePx/40+2Uy/4Wfm/LEd1k1NuuzwO6RnoywGfBDks8HVLj3SekXEgl2JLp04ylq14oicHAefcYyTwzzkQpbB1h9y+5L4SYsBEwNyZZUfnpNpdRKerx4CZVkffda76qFfnx6RnKUiBnqjkYfFwLVQx51suTZGru/jNvOSqOy/zx5dXIlADNo3Icqi+NL2OI2idMkb3y9gAq0tBs0QA20h0BY4ufGMsxjoDX8UPnEXj1190Rt1ubmj/2ufdFmvJA9xdafZAPr2M4vFQLviyBNx6baVZDGIskDEEQPGLu6knsepY8hR5vEVz5gLxpxqX5Mn2XlZDFykhF1Pk4iBtlDHEdQoeW21XGiLOROlJIuBXm/S6iI8X10EyrI+plnvqjsa1e1ZJmLAJ44EDylz8vnkMKTax1JaE0ih06moPnGiEyF9x4AWf/ZVladu0o3A9X3cHQO5CZKXJqPuGGhF7RN5pboi09qelUXxpe1lkqL0kTdNDOidWfPut5eL6dMMMdARYegEmX3TyTWPiQHP3SMP2d/6kcuTZn2vSPp5S/KuybcsQ85XSQZcxCDpjoFPmFIeQ98NoLxjQKfkcf2Um3l2v+kYfFTjHgjrP3nHIO6OFfvQu3zMo8pLQwwICyY9SWGX2cqjgzvwxpatui6LAa0FpOPZ7YnvQOg7BvoQR+pVffk4ymIQNV7UTxoHPT6MvZZXH0uSr1zItUuuf9qCIvGLu2PAc5LvB6XVSXi+egiUZX1MWu+qh3y9eqTlLNGVSEYaITeHKWNOMQsWPm6k4mZXiKnjhpvnnn+506kTRyXasUd3M+zYwzqZonkjL83W0o1Imp1pmNhlyPW9Hkq52UiKcLRs1RrDUYk0MUjatNRLhPLtbVkUX9peJSlKH3kjAiAjAtH8oeg9JH/SZYPmCZdHcpomKtGkM08yz6x9wVw4boTtoowQNObU483Cx35vST5HS4mKShTlRkRl6Q1n0nyldzhKEOsP+k4HCogaC9/7BVwe9TOqPu0HLl0ZXf2kclzRh1zfSyxO+9yxNlqbHFeOuqPrdJXnuszsshhoNxWXKxH37aJrf9JJrmQ7yLWFPvpE3xWViPo0eEA/s+suPbtYgFx4+xADvgfC640rOpRrHHlMWiUXcv3jwzCqS+LHF47J9YnHRLrfwY0orSau9vNlWR+T1rtqj0L1e5eaGIQMSR53A/IoI2QMW932sii+tP2EonwfsTysaT73C9KOT1mfj7sMXtY2o11AAAgUg0BZ1kesd8WMf7tqrRUxIFCzbDqSTPDtGrQq11MWxZcW46IUpbZEyHb75DhI20+f57NY1OpGvEEMfCQKzwABIEAIlGV9LGq9q7oUaDfDZvtL+wKKDDe2Y4gN2pD2UztikBYgPN9eBMqi+NL2GooyLWJ4HggAASAABNIgUJb1EetdmlHzfzYvYiDvEoIY+OOPJ0uKQFkUX1p4oCjTIobngQAQAAJAIA0CZVkfm13v5J0ibdGWd2ui7gvxnVIdlIMSRA48oq8zC3xSuey9oZAAACAASURBVHQX6OY7HrIZ5OPuXvE4cR/o/g+Hzue7QHw/lttz+EF97J09Ha5YhtSXYcy5TPpdB/DRAUKoPVF4ynuKzXoNwGKQZlbi2ZYjUBbFl7ajzSrKtPXgeSAABIAAEKgnAmVZH5tZ75Iyg3O0PBpZChbBEfL0KXoUMeANOF/ylxf5k8qVuXv42bhTdkkMKIIfb+w5ShsRFW4PBUjgfsvIczLaID3Pme+ZxPgQA188C7UY+CQtSprKPv7FcZkg87j4mNRG/N5aBMqi+NL2shlFmbYOHcrX932dCTbqPd+Eaa53r5gz30Y6cimhuOzkaeet1BPUnitvvsOMOnGgTcTFH1mfjjzki1uIz7XjzoLEU2c4lqdqMoN9Vixd4+mqv5Xjr0MAu/qWZSxYxs89fZi5fM58G8FKR5zywbSZzMlSX7jw9ambnsly38i3jjo9V5b1sZn1Toae16HkJRGg8ZSbf8rnwuHtaQ5EEQPedPPcpDI4sS4TDFe5TCJkuXIt0fKliQqvy1QPh6yWoZl1ufJ5iuzGJEEmOPUhBi48ed75kBzX3GmJxaAZheh7sTdJ0UERha0my6L40qLYjKJMW0dZiYHPnIsjBmk3EDKAgOswoZUbw7TjVrXnfYiWToSZFQOX/LjWg1aOf6uJge9a6INp0noZVYbPQYJP3fSMT+hh37LwXNiXj11+7/pEPS9iMLZjsJn0rR8Z1yY9jnDkSQx0v/MiBnH3CAq5YxCVxp4TFX31C8PNRd+9xcZfp/jRf3PAvuag/fZqxK92RQXSp4ZR8a0pIROnuieWt8/uvc2MH99pNry+qWG6IV+uW+5ZZHMnxGVOhpIpJwJVJgYyP4eMrU6y/sPbF9oBeXjpSiP9AmXUIfK7XPvSBsOZkeNGkOcPlUX+jhQ3XyYhpPlJHzKjThh5gs1sSzlGZN3SN9Ple6k35lE5SFgBc30D+/fNNG8lEYmyNuiY+1QfJUmk3CnzfrnY6qXrL/piw7rh009erKT+4azMUe/rwxG5meaMvWnaw+VxvgNqj/RTZVmQ9XICShpX+kSNod446g015wiQ9bksBnLsSa6iTqyScImaB1Hyw5mno060WzX+PM5/27+vxfOznz7Urms6Xwf1naKByBwhJG96PKLGj8qVMi2zJNNvLj3hkk8eX85jwZaHqORyNNZaXzC+/D4l7NN+1bJufn/H7t1sokT+jf6Ny2JeztWofK0qy/rYzEGY60RebpSlzPBJeqstBlF3F/IkBkkWA1pTuA3SGpF0xyDOwtF2YqDTufNCQiaUqCySMukNTbMosz99Lxd7rbQkmZALGYGoE/HkedpSPrVQ/RaVRfGlRTpJUerTfpLjqEy4vhlz49wKXL6HOgOrzGgsM36TUnRl4NX1yrmpdYPc4OgNZ7PzVp9AuqwVURtcaZ7ljMS+/ZSbYboEdvG1t5jzx3bYhHScmZ0z/F428YwuCek0MaBNmPSDJXljf1VXeeSLyqdfUn5c2XzpIIWzHbtObmW7qA1EEDnxmsweLHFiYkNEkzeOxw/oZzfCnKU37nRQZvCOwsU1TtwXH1emvMdfmu1lJmfeMHNWaDmnCE/ZV8KExzlq7WKdI2VaEwN2qdB6wiWfCx543CaiiyMGVC/JFsmt3oxoYkDPcmZrnsP0HcsKZ2+X7hs+LsJp9W1dny/L+pi03kWNj7YMSB1x04KFjYMEele6Fsn3WI45sSfL7q69etrDHtpvat2adMegWWLAF6Q5Gai8YyCtFM3eMeCs6Nx+7pu+6CzXBH5W4tmWOwYuNyHXHQMaOPY/JoUadZoflTGVF17N3DQxiPJj9XFtqKtiKXu/y6L40uKUpCi1a4WcLzThpRzzpoZP8vmUT2+8XW3Urh4u1w9JwCUx2HO3XnaDyJsdJu60wdCnfq5NTBSBkBu7ZuetJBtxbgp6YxiHr28/Xf2X3/PmTx6UkGLWG2DZHikLpNBd5cmNumtMZVmSGLhkJW4jK9+JsnhIYnDogX06bYJ91okkXOQYulyDfO8Y5D3+LhcdOackMeDNDlv7XHNZr4WaGET1gwilqz0+FgM6kWULDGeT1hYZTYBkP4kc89ymfmq3L7gTpV1N3M+XZX1MWu/i1ia2QsZFJdIZv9laxxapRU+stJnZmRh8/rPHmAeWrLCeKq536Vn5m74rkPaOQVQ0oyi3KKo3ypLOe1tt4SSLHJMV+R57DEjSLd+VeMrvXZbJOKlMfcfA5TvqIgak/HnzQG4Q9NGbi6iLjxoQWoRIaWliIBVa1IlLflMSJbUDgbIovrR9TVKUcqJy2TyR9cLMCyu7IyRtJnRbtY9w1KmsbkMUMWAXFH5WK9yozU2UmyHpAB+fbx9C73O/QG9O9CZaEy/ffkoCQXVE+ZpHWVCjNsBSb7HunPblkeY7c+/sRMh8ynNZDFj3sutH1AIhx5D0s9zgSRcp6i+fkEVZDMit00V25IlVkiuRxCVPYhBVrnShk3PIJecuAqnnNs/rKGLALnxcn3bv0muhJgZx/dDyKQl9nMWANvaPLl9tLQFJxCDKFYmsEvSRbiBSjkAM0q4m1SUG+SHxfkmujXje9cjyNKFoZV1FlZ2aGPicBPF9g0snjrJ+vATkvYuftH3U0UN4gXW5GOlFHsSgKFFpT71VJgZRJJZQTdq45mUx4CgNTDSSLAY+kVDiNvOyX2RujXIFkZvaJGKgT1Nd95XSEgPffmaxGGhXHLmJloctcRYDlwtOHDHgWRsXdYo34PQs3ccga48+AGqVxcDlokR9ahcxaGb8eR1iF6qoOaWJgcsKLjVrksXARZzSWgykpSiNxSCKGMBi0J61kWopy/qYdBCmEbn/N0+ayVfenDtQd1x7oXWDY3cawqeVHzoc6Rh8pBn/je/bU315B6uV9WYpe+YFY8zQYw5LVURqYqB9pVmx0w3w7956jyEyoIkBv0OnSnwqoVspNwWafMTdMdCbLdwxSDX+pXu4LIovLTBJijJq3rAvubyHE7Uh4tM8jtGcZBqUdUmfYU0MpOk07o4BJ1Zh/3GJjZybURcaFy1d2fCdjyMGPvNW+ipTG3wPE1zEi336Gd+kfurnaDNIp+xxdwKi/Lb1OErf8yhf9qQ7Cy5iIElG3Kktn7qxny4f5jB54UuzhDn7lye5msTdMXD5s0tLRDuIQZrxl0RJ3jHQxEDOKUkM2HpD31G98h6QdruLu2MQZ1GJkk+af/KOgbxbw5c7+V4AyXPSHYMoYkB9wh2DtCtGc8+XZX1MWu+a6x3eKgsCqYkBNVy6+XCED0kG6Bm6PEIfjv7hOtFgIKTvMH1HCpYjgMgsePw9RyXSxAAXncoiWs21oyyKL23rfRSly88wbuOqoxJRJJthxx6WGNNcujeQL+Rzz79sI3XxRSnqH31Prg202POGgC91kf+jdCXR7hWMj55vsl4deYlOV6gcjhIkDwl85q3v/QJqG7cjqj7tA+3TTypTPiddQFzfs64i15Ixpw6yWPPGWkaXkfrNVU+cC46LGLAeZvcVF6FkGeNNI70n5Y7aP2XMKWbBwsftwY8kstLHX8r3pDNPMs+sfSEyt0UcLq6Nr2s84/IouOQt6/iTXA8e0M/suktP6xYr1yo5p/bfZ49O66BPlCiWXSbUPq5E7MITJYdy3dXjs2zVc/YCvYyfznqB9YW+fOyKasQYMDabNm9GVKK0i4jH82VZH33WO4/u4JGSItAUMUjbF5/kST4nhj71Jrkj+JSBZ4pDoCyKLy0CdVaUecy5PMpIO2ZFPe+6OFxUe1BvuRDIay0solf63g3uF+Q7CmVZH+u83uU7ouUsreXEIM3lEJ9TwzgYtdWhnJCjVXEIlEXxpR2ldipKefKn26lPn9P2o5nnsy7+dZu3IAbNSFm93sm6FrYTLX35WloX60T424F5WdbHdq537cAVdXRGoOXEAIADgTQIlEXxpWkzPQtFmRYxPA8EgAAQAAJpECjL+oj1Ls2ohfcsiEF4Y1bpFpdF8aUFGYoyLWJ4HggAASAABNIgUJb1EetdmlEL71kQg/DGrNItLoviSwsyFGVaxPA8EAACQAAIpEGgLOsj1rs0oxbes4UTg6RoRQRpVCIl7deoE8X4DIUOIenzDj/DYRp1xk9XGTrBE0edSFNnHZ4ti+JLizUUZVrE8DwQAAJAAAikQaAs6+ONN74fdRKf6iFw8MEHm+Mm/dg8OONM27lBgwaZbcwR47eumH9123rbDDEgUnDRtT+xKbEp1FpUBlKfDjRLDJq5GBYVIo+TCfm0tS7PlEXxpcUbxCAtYngeCAABIAAE0iBQlvVx8eLFhjaQ+FQTgV0+e2E6YsDxtA8/aD9zza13G07/Tht0+siTfBkhJSq++YNLVjRyFfCJv4wJzWXvuVsv87XvzesUb12nf4+K881Z6WQ7dFx4SlDEWSuj8jNQYhr9cSVjo/jeMj45t58yS3JbOGJDM+SimiLYuVdlUXxpsQYxSIsYngcCQAAIAIE0CJRlfcR6l2bUwns2tSsRb56HHXdYI6MpZ1xct/41m55aZvukpDljO4YYmQ4+LpMxZ9uUmSInjDyhQQx22blnI8sikxENu8wqyYmbOHkPbepffOX1RqIh2rBT4h9ORsMkQT4nEwjp0IySkBAx4PJ0vdpiEHKs6laKeVkUX9o+QlGmRQzPAwEgAASAQBoEyrI+Yr1LM2rhPdsUMeD057Qxl8nL6GRcZq7kNPKXnTvKfPtHtzeyLEqY4lyJeDOticFVNy2wGTijTvPZahHVjhnnjzXfmXuntTxQGnqdVVKSEm2B4Dbrk35NDGQZkgxoYkDlIcZz1wlTFsWXdipDUaZFDM8DASAABIBAGgTKsj5ivUszauE92xQxkBtzSQyWr15j2HpAp+xy0/zam5usNWHd+leNdO2RxEC6+TCU5HqT1mJAm/Codnz1C8PNRd+9peE6pIkBu/tw3dpNKop0aGIgCQmIQfoJURbFl7blUJRpEcPzQAAIAAEgkAaBsqyPWO/SjFp4zzZFDKTFgDbG/LfLYnD9RV/sdLovN8ySGOgNfZTFgF109B0D6eJDLj3NWAzkO66hTLIYgBhkmwRlUXxpewFFmRYxPA8EgAAQAAJpECjL+oj1Ls2ohfdsU8SATv4nnDbUDB9ytJGbedcdg47BRxlpZXDdMZBlbd7yrjn7shvNkYfs38liQC5ArqhEfI8g7o6BrGPFM39q3AnQdwzoOdrka1KTdMfAlxjgjkH0ZCmL4ks7laEo0yKG54EAEAACQCANAmVZH7HepRm18J5tihjQJr9nj+7m7kVLzScP/HinzbMrKpGMNiRdifh7ikp0/IB+lgz8bvUfbbljTj3eLHzs92bquBHm8jnzG3cDCGadx4Cj/fAQuNqhoxLt2KO7GXbsYfbOgYxKFOVGxGXHRSVyEQNuD6ISxU+Ssii+tFMZijItYngeCAABIAAE0iBQlvUR612aUQvv2aaJQdzl3/BgSNfiPEKN4uIxLAbppA5PAwEgAASAQJ0RADGo8+i3r+8gBk1iLd2h0hZB1gNkPgYxSCs3eB4IAAEgAATqiwCIQWvH3hWNkmrVd2B1S/iwl75Puq8qg/ZQdM+yHRSnJgatHRaUXncEyqL40o4DTKtpEcPzQAAIAAEgkAaBsqyPdVzv4ohB2sNeTQz03dU0MtGKZ0EMWoEqymwagbIovrQdqKOiTIsRngcCQAAIAIHmESjL+uiz3tFmV94Z5UAu8i6nvKNKm+uoO5ocrp6evfmOh2zIe7qTSsFv6BNX3g9vX2ifeXjpShN3b5RHRFsM+G4ovTvwiL5m01ubbXJcmfRWB5KR/Zh1231m0+YtZtETKxuh+vnOLN3RlW3K4oXSvERFvwlikDeiKC8TAmVRfGk74aMo05aJ54EAEAACQAAIMAJlWR+T1juZI4oCu3Do+bEdQyxZoCSztLEnF5oXX3ndbrYpSmQcMVj70gYb6IZyYnGI/F477RhbHuWmumn62YbD3HPkSpdEReXeumziGY336T1NDPSdU00MfvvUs412U0RPKo+iYF587S2dkv7mcXc1r5lSWmKgTS1ZOpzkvxXnV1amwcqCQSjvlkXxpcUrSVGmLQ/PAwEgAASAABCQCJRlfUxa71x7Kv19XB4sncdK5q7i/FeHHtinUyh8Xd7MuXc1ombK/Fk+xCAqH1bU3QHtRqSJAdV13uhTjCRLUcSgTO5ElScGPr5fccSABrVMJp6qq8myKL60OCcpyrTl4XkgAASAABAAAiESA9p3yU0590F/Lw+AdYLcqAS3ZH2gDxODfXbv3aken/Jok+5DDB5cssI8unx1w0KgXZ24DE04NDHos9fHrHWk8sQgb98xMu/M+cAXjOL8s08Z+2Bd889fMDcteMC88dbb5pGlq8yF4/7e/H71nxoD5jrV175fMocBDWpU7oSvju0w3/rRL+yYb7PNNpZt0ueKOfPNheNGdMrgDJWVPwIhE4P80UCJQAAIAAEgAAQ+RODBGWfaPwYNGlQYLEkHYXlYDGjzTx/eD7LFgPdx9HeSxcCVV8qHGORlMagFMWiF7xgNEvuYXXTtT8wN08Yb8h1jH6w9d+tlvva9eYb9w7SbketEXxMGyeyk2YZ81jgzM/2f/cCYnTJDHdi/r02Ehk/rEAiVGDAiDz30UOvAQclAoCIIvP322+aKK64wF154odlhhx0q0it0Awi0D4EyEwM+PJ48+mS7Z+KIPueNPtlMvupm5x0Duf+juwhHHrJ/gxjwPnHd+te87xhkIQZUH7WB+sB3FLgN8vJx0h0DX2JQJrf11K5EeTBBl++Y3PBHEQPtY0YbdRqwK2++w4w6caCheLDyo92IXD5m+sIJX2yR5cGdqD0KD8SgPTijFiBQJAKzZ882t912mxk5cqQZP358kU1B3UAgSATKTAwIUFe0INf3fABL3iMUgWjwgH5m0+bNDWIgPUvoQjEf0qaNcuTrStRrpx6GoxJR26aOG26ee/5lc8GYU1NFJYoiBkw0lq1aYw/Caa9Zpj1mamLQSt+xJGJAN9lZGHjT3zH4KHPLPYu6DBYNZBQRIPMUuy2xcGliwNYDEgz+lGnQgtRino0GMfAECo8BgUARIGtBR0eHeffdd812221nFixYAKtBoGOJZheHQNmJQV7IaC+VvMrNsxyfu6xx9ZXp4jG1MzUxyMNioH3HeMOfhhgwkAfss4fZdZeejZi2cRYD+Zusi76XrkQgBnlOmXRlhU4M0vUWTwOB+iEwdepUM3PmTPPOO++Y7bff3kyePNlcfvnl9QMCPQYCgSKQdMcgz27lSQz0PVPZTp88B3H9Sop+2ap388Say0pNDFrhO9YMMaAO0EDc+8iTDVOMBkj7bPFNdrohHnfHIIoYZBn0VgxcVcsEMajqyKJfQMCYjRs3mt12281s2bKlAUe3bt3M+vXrTc+ePQEREAACASDQTmIQAByVa2JqYkAI5O07FkUM+MIx+WBxVCLpSkTtcIWP4lHSUYlkNCV6hl2J+Hv6jqISzf75v5tLJ45qRCAqm5mnclIoOgRiUOXRRd/qjoC0FjAWsBrUXSrQ/9AQADEIbcTStbcpYpCuitY9LS0Arlqy+n5Rubhf0Lox1CWDGLQPa9QEBNqJAFkLevfubbbddlvTo0cPs2HDBvs33Tl477337N+wGrRzRFAXEGgOARCD5nAL5a0giQH7iRHIOj11FPBZ3IDKFEIqFKHK0k4Qgyzo4V0gUF4EZsyYYS655BIbpnTSpEk2T8zWrVvNNddcY8OWfvOb3zRTpkwpbwfQMiAABCwCRRAD7caediiSEtlyeS5PlKT6ffaKcYfZeRxip8XE9XyQxCCvzqOc8iEAYlC+MUGLgEArEGBi0IqyUSYQAAKtQ6DKxMCFWhwx0G7rrjKSvFyyHGLnOdogBnmiibIyIwBikBlCFAAEgkAAxCCIYUIjgUAXBJKIAZ/OH37QfuaaW+82MuIPnYzP+PGdZsPrm8zhB/VpJLcdO+16W89JA/s3PEFkFCH6fu1LG2zCsV127tmIJMn5BmQyM3kPlt7jxGq/W/1HmyPh+ou+2LhDqjsnLQb0GyXXvXvRUtsurl8nutWn/TL/Adf34JIVZtp1P7XVTT/ndLPP7r274EDJ21zh99sphiAG7UQbdSUiAGKQCBEeAAKVQADEoBLDiE7UEAEfYvDl6bPNsOMOM5RQjDMfk+v3imf+ZDjDMSX24k38ZRPPaGQYpmRm9B6doL/4yuudyAMFjYkjBpu3vGsuvvYWc/7YDps4jE/pDz2wTycy4Ro2SQxm3XZfZP2aGMiTfhkKXycukxYDqkfiQO3xtTy0WuRKQQxYaKaOG2EunzPfpsvef589ugwugaGz1vn6jUkg9cBzZroosPnisa9Q6YFftHRllza3elBDLh/EIOTRQ9uBgD8CIAb+WOFJIFAmBHyIwcXfvdVceu6ZdnMuN8uvvrHRzJx7V+PUXvv0cxLdGeePNd+Ze6fdD9JGXOYziCMGRDyk9YBx890rcnvkflTXL4lBVPRLSUzkuOn9ocSBnyuDO1GpiIG8SKxZFydFi0tn3czEifP58rlMouvU5ZVhkJvBpah3QAyKQh71AoH2IgBi0F68URsQyAsBH2Ig80FpYiA37tKa8NFu21sLAr371S8MNxd99xbrOpSGGNy7eJl5dPnqLoFp0hIDdj+Kql8SA71XJYy1KxPvbTUxiCIwZdgzNkUMZD4A9p967c1NhhgifUjhkw8XfUfmpHXrX+3i18U+WOR7NvCIvmbTW5sNM7SOwUeaBQ/81vp1sW/aggcet2XHWQyeXfuCZYo79uhu5v1ysa2TBpXMNdQG8uui5GbMCI84eD/zje//3JbLv2l2N7B/XyuUUqionh/evtA++vDSlY02Ll+9ppMPGdXVDLnIa/KGWA6IQYijhjYDgfQIgBikxwxvAIEyIOBDDKTFgPZB/DdZDOSGOA+LgXZVKtJioMeH2rbm+ZcbrlHsoeKKfhQkMdDpqbnTHYOPsiSA/MRoI61vcEtfMbpgwc/2O2Bfe7mDPlldiWjDThdYyAeNy6XLIkRS6Dc22zCBIBZHPmRRrkQ6qZkmBroe6RMnyyuLz1gZlIlPG0AMfFDCM0AgfARADMIfQ/Sgngj4EAPa4004bag9jI3buMfdMdDv8b6LXImi9pC0p6P9pSQl7G1Ce1RpxXCNnNywS+sDuShx/XF3DLRlQubBSrIYlGW/mNpi4DLHSEZIPmXsJ8a3v/XGOoox5kEMpM+WHARX/S5ioE/69fuyHhcjZMErAwMMRX2BGIQyUmgnEMiGAIhBNvzwNhAoCgEfYkCb8J49ulvPDxkJKOqkXEbxiYtKRN4gw449zB4+076LovyQV8mYUwcZijjELjtR5dGl5LMvu9FC1mxUIlm/xF5HJeK20TOyP/w9RyXSlo2yeJikJgZ6w8/gaMIgB4afYbcgcrmRPmBRlz2auXwsLQHkq5aFGGjhjSM2IAb5qScQg/ywRElAoMwIgBiUeXTQNiDgRsCXGFw6cZQzLGiV8M3rpL8sh8ipiUGcxUCaaVz+UyQMUT5lxJzysBhIBpaFGCRZDPTlmSgfMlgM0k99EIP0mOENIBAiAiAGIY4a2gwEkjMf+170LQJLeTFY1y9P99O2Letpf9CZj/XdAfYBG9sx2Hz31nsMM8So52gzTSYc+pBJhy4G533HIC9ikHTHwJcY5MUk0wppqM+DGIQ6cmg3EEiHAIhBOrzwNBAoCwJJFoOytBPtaA6B1BYDqkYyLhmVSF/skM/JzHdsNeBMd1PHDTfPPf+yOff0YY08BkwYlq1aY26YNt5QVKI5H0QC4q6OGz7EyAslzbgS0eUS8lNLG5XIRQykDxmiEqUXShCD9JjhDSAQIgIgBiGOGtoMBJItBsAobASaIgZhd9m/9VlNQ1RTWXzG/Htd7JMgBsXij9qBQLsQADFoF9KoBwjkiwAsBvniWbbSQAwSRkSGmko7eGXyGUvb9qKeBzEoCnnUCwTaiwCIQXvxRm1AIC8EQAzyQrKc5YAYlHNcatsqEIPaDj06XjMEQAxqNuDobmUQADGozFBGdgTEoNrjG1zvQAyCGzI0GAg0hQCIQVOw4SUgUDgCIAaFD0FLGwBi0FJ4UXhaBEAM0iKG54FAmAiAGIQ5bmg1EAAxqLYMgBhUe3yD6x2IQXBDhgYDgaYQADFoCja8BAQKRwDEoPAhaGkDQAxaCi8KT4sAiEFaxPA8EAgTARCDMMcNrQYCIAbVlgEQg2qPb3C9AzEIbsjQYCDQFAIgBk3BhpeAQOEIgBgUPgQtbQCIQUvhReFpEQAxSIsYngcCYSIAYhDmuKHVQADEoNoyAGJQ7fENrncgBsENGRoMBJpCAMSgKdjwEhAoHAEiBvhUG4EHZ5xpOzho0CCzjTli/NYV86+udo/Ru9IiAGJQ2qFBw4BArgiAGOQKJwoDAm1H4KGHHmp7ne2u8O233zZXXHGFufDCC80OO+zQ7uoLrw/EoPAhQANADCADQKAeCIAY1GOc0cvqIlAHYjB79mxz2223mZEjR5rx48dXdzAdPQMxqN2Ql6/DIAblGxO0CAi0AgEQg1agijKBQPsQqDoxIGtBR0eHeffdd812221nFixYUDurAYhB++YTanIgAGIA0QAC9UAAxKAe44xeAoFQEZg6daqZOXOmeeedd8z2229vJk+ebC6//PJQu9N0u3HHoGno8GIeCIAY5IEiygAC5UcAxKD8Y4QWAoG6IrBx40az2267mS1btjQg6Natm1m/fr3p2bNnrWABMajVcJevsyAG5RsTtAgItAIBEINWoIoygQAQyAMBaS3g8upqNQAxyEOiUEbTCIAYNA0dXgQCQSEAYhDUcKGxQKA2CJC1oHfv3mbbbbc1PXr0MBs2bLB/052D9957z/5dJ6sBiEFtRL+cHQUxKOe4oFVAIG8EYZONhgAAIABJREFUQAzyRhTlAQEgkAcCM2bMMJdccokNUzpp0iTDuuqaa66xYUu/+c1vmilTpuRRVRBlgBgEMUzVbSSIQXXHFj0DAhIBEAPIAxAAAiEgUHddZYlBCAOFNlYXAZlxr7q9RM+AQL0RqPtiW+/RR++BQDgI1F1XbbN161ZLDKoen7YVIln3DHl5Y0rxc/EBAkCgmgjUfbGt5qiiV0CgegjUXVeBGGSQ6bpnyMsAXeSrIAZ5I4rygEB5EKj7YluekUBLgAAQiEOg7roKxKDJ+YEMeU0CF/MaiEH+mKJEIFAWBOq+2JZlHNAOIAAE4hGou65qEAMISjoEkCEvHV54GggAgXojUPfFtt6jj94DgXAQqLuuAjFoQlaRIa8J0PAKEAACtUag7ottrQcfnQcCASFQd10FYtCEsCJDXhOg4RUgAARqjUDdF9taDz46DwQCQqDuugrEIKWwIkNeSsDwOBAAAkDAmEbSIIABBIAAECgzAiAGH4QrLfMglaltyJBXptFAW4AAEAgFgbovtqGME9oJBOqOQN11FSwGGWdA3QUoI3x4HQgAgZogAF1Zk4FGN4FA4AjUXVeBGGQU4LoLUEb48DoQAAI1QQC6siYDjW4CgcARqLuuAjHIKMB1F6CM8OF1IAAEaoIAdGVNBhrdBAKBI1B3XQVikFGA6y5AGeHD60AACNQEAejKmgw0ugkEAkeg7roKxCCjANddgDLCh9eBABCoCQLQlTUZaHQTCASOQN11FYhBRgGuuwBlhA+vAwEgUBMEoCtrMtDoJhAIHIG66yoQg4wCXHcByggfXgcCQKAmCEBX1mSg0U0gEDgCdddVIAYZBbjuApQRPrwOBIBATRCArqzJQKObQCBwBOquq0AMMgpw3QUoI3x4HQgAgZogAF1Zk4FGN4FA4AjUXVeBGGQU4LoLUEb48DoQAAI1QQC6siYDjW4CgcARqLuuAjHIKMB1F6CM8OF1IAAEaoIAdGVNBhrdBAKBI1B3XQVikFGA6y5AGeHD60AACNQEAejKmgw0ugkEAkeg7roKxCCjANddgDLCh9eBABCoCQLQlTUZaHQTCASOQN11FYhBRgGuuwBlhA+vAwEgUBMEoCtrMtDoJhAIHIG66yoQg4wCXHcByggfXgcCQKAmCEBX1mSg0U0gEDgCdddVIAYZBbjuApQRPrwOBIBATRCArqzJQKObQCBwBOquq0AMMgpw3QUoI3x4HQgAgZogAF1Zk4FGN4FAoAj84Ac/MDNnzjQrV640ffv2NZMnTzZnnXVWoL1pvtkgBs1jZ9/EYpcRQLwOBIBALRCArqzFMKOTQCBIBIgUXHPNNWbWrFnmuOOOM4888oiZMGGCmTRpUu3IAYhBRhHGYpcRQLwOBIBALRCArqzFMKOTQCBIBA4++GAze/ZsSwr4Q+Rg/Pjx5g9/+EOQfWq20SAGzSL3wXtY7DICiNeBABCoBQLQlbUYZnQSCASJgEs/1VFvgRhkFOE6Ck1GyPA6EAACNUQAurKGg44uA4FAEIDF4MOBAjHIKLRY7DICiNeBABCoBQLQlbUYZnQSCASJAO4YgBjkJrhY7HKDEgUBASBQYQSgKys8uOgaEKgAAohK9P4gwmJQAWFGF4AAEAACZUcAxKDsI4T2AQEg8PTTT5uDDjrIrFq1ynziE5+oJSAgBrUcdnQaCAABINBeBEAM2os3agMCQCA9AlOmTLG5DCiHwYwZM9IXUIE3QAzUIBJb/Nd//Vdz3XXXmd69exc2xIsXLzZz5841V199tenevXujHbfccot57rnnzLRp0wprGyoGAkAACKRFAMQgLWJ4HggAgXYi8Pzzz5u99967UeWf//xns9dee7WzCaWoC8RADQNtyKdPn25oA14kMXBJB4hBKeYNGgEEgIAnAvDb9QQKjwEBIFAoAmQtoA9bDOj/dbQaVIoY0Gn/aaedZpYvX26+9KUv2dP2pUuXNjb6O+ywgznvvPPM6NGjzbHHHmu//5d/+RcrCN/4xjfMV77yFTNq1Chz33332b/pVJ424v/0T/9kn/m3f/s3+zt9R3W8+eab5vvf/759dr/99rPPnXDCCQ1SId/l8oh4kDWCPhQey3XyLy0G1AdKunHooYeaoUOHmp49e8JiUKj6QOVAAAj4IIBIHz4o4RkgAASKRoCtBWQlIKuB/LduVoPKEIPNmzfbTf/AgQPt5p02/bRZ79OnTyQx2HXXXRsuQySQ55xzjv37lVdeaTxPl0/YekDPULm0kV+zZo258sorzbx586wsExm54IILzOc+97nGM/Q9uwLR/5mQ0P/PPvts+27cxRYmBlTfuHHjbL39+/e35ZCQwpWoaDWC+oEAEEhCALHBkxDC70AACJQBAbYWkIWA3R7ld2VoY7vaUBlisGHDhsbmXm64pWuQtBjwJptP/HmjLZ//5S9/2cmfn8kGDc6iRYusReLtt99u1Lvvvvs2CACRB7Y08GCS1WDIkCFerkpMDEaOHGm+/e1vd7JC4I5Bu6YH6gECQCALAsgmmgU9vAsEgEA7EJDWAjp4Zb2lv29HW8pQR22JAbkS0YcIBbsPPfLII/Y7thJkJQZRG3jfOwwgBmWYHmgDEAACWRCAxSALengXCACBdiCgLQPyQKOOVoPKEIMoVyISKDpxnzhxorn22mutfJHbz/XXX2/IlWjOnDnm61//uv1euvowMYhzJUqyGFCZ7DLElgRyc5KuTXGXm+FK1A51gDqAABBoJQK4Y9BKdFE2ECgOgW0GfKm4ylFzJgS2Lvl+7PuVIQb69J8vAbP7ELkMnX766RYMuk9AFoOoy8F8gfkf/uEfYi8fJxEDV/lpLQZ8gRqXjzPNA7wMBIBAQQggKlFBwKNaINBCBIgYrJh/dQtrQNGtQKDfiPNMrYhBK0BEmUAACAABIJANAWQTzYYf3gYCZUMAxKBsI+LXHhADP5wKe4qsB2QJ0B8KS5oUtaiwRqNiIAAEgEBKBJBNNCVgeBwIlBwBEIOSD5CjeUQMHpxxpv110KBBkU9VypUozGFCq4EAEAAC1UUA2USrO7boWX0RADEIc+xBDNowbq5wfG2oGlUAASAABEqPALKJln6I0EAgkBoBEIPUkJXiBRCDNgwDiEEbQEYVQAAIBIkAsokGOWxoNBBIRKCKxOD2hY+ZWfPuNzdMG2/223v3RAxcDzz355fMrx79vfniP/5d02W06kUQg1YhK8oFMWgDyKgCCACBIBFANtEghw2NBgKJCIAYREP02ptvmbMvu9Ececj+5rzRpyTi2O4HQAzagDiIQRtARhVAAAgEhwCyiQY3ZGgwEPBGoAhi8N9b3jFf+948s/GtzbadDy9dafbcbZfGCf+Sp54xY6ddb8YNH2Lm3L7Q/kub86vn3mn/ps9JA/ubr3/lNPPRbtvbv/k3KmfgEX3NoidW2vLo8+Xps82E04aa4UOONlz2TdPPNgMOOcBwW+5etNQ+S3VNGHmCbR9/x3Xdu3iZmXbdT+1znzzw4+b6i75oeu3UwxvrPB8EMcgTTUdZIAZtABlVAAEgEBwCyCYa3JChwUDAG4EiicGyVWvs5r3XTjva0/l9du9tN/srnvlTgxjwaT1t/H/71LN2M04feZrPm/3p55xuhh17uN3Uc9lJxIDKffGV1zvVS6Rh/3326FQHuRUxwTh+QL/CrQkgBt4i3vyDgwcPNg888EDzBeBNIFBxBJAhs+IDjO4BgZQIJCVYSlkcHi8AgSKJAXWXT/3lvYBX39hoiQGf6ke59cjnFzzweIM00Am+/C2OGPDm//NDj7HWBPnRdTIxWLf+VUMERD/f7qEDMWgD4rAYtAFkVBE0AkUsIEEDhsYDgQoj4JNgqcLdr0zXitDr7L7jSwzkaT1vyPMgBrvs3LOTm1EcMaDfpCsT/V0kQQAxaMMUBDFoA8ioImgEilhAggYMjQcCFUYAxKAag1uEXk9LDHwsBvc+8mTjjkJai0HUBeO4y8fsuiTvRbRbGkAM2oA4iEEbQEYVQSNQxAISNGBoPBCoMAI+G5MKd78yXStCr8sLv9KfX98xYFciPq1v5o4B31/gzT+f+nPZ8u7Ca29uct4j0JeW5XtFXED2mX/IfJxxmoIYZAQQr1cegSIWkMqDig4CgUAR8NmYBNq1WjW7CL0uLQY79uhu5v1ycacoP3oTzgMiXXk4UhH/RlYCihikoxJRHgP+jZ7lSEdxUYnkhWeKgsQRiB5csqIRlYjKksSl3ULjM/9ADDKOCohBRgDxeuURKGIBqTyo6CAQCBQBn41JoF2rVbOL0OtRrkS1Aj2HzvrMPxCDjECDGGQEEK9XHoEiFpDKg9rCDrYrQU+aLKN0Erj2pQ2xET30poFDFxZ5OtfCYQq2aJ+NSbCdq1HDi9Dreo4POOOfg0V82HH9zZWTR7e9/T7zD8Qg47CAGGQEEK9XHoEiFpDKg9rCDpaNGERFFvHpvsutwOddPNM6BHw2Jq2rHSXnhQD0el5Itrccn/kHYpBxTEAMMgKI1yuPABaQYobYle0zKjtox+Cj7OU5irVN2TrpdF5G3JC+tpzNk3ols5A+s/alRnQPV49dWUbJn5fbRe+yby79nxIS/W71H22RUZlM+dnu3baz7aGPTHbEFgPZh6KzjxYjEeWo1WdjUo6WohVxCECvhykfPvMPxCDj2IIYZAQQr1ceASwg7R9in2yfvMlm8zyRAcoO+uzaFzplD5Un7zKr54SRJ3TKFEqb+7iPb5ZRzkDKG/x161/rFDOcyvnZ/b+xm3/+bdhxhxluTxQxkHHHy5B9tP0SUZ4afTYm5WktWuJCAHo9TNnwmX8gBk2O7Q9+8AMzc+ZMs3LlStO3b18zefJkc9ZZZzVZGl4DAtVFAAtIe8c2KXa3zg6qXXX0+zq8Hv394iuvm6njRpjL58xvnNB/tNv2sR3V5cg7BstXrzGz5t3fKZ44bf6JqMhQgDJrqI404kMMypJ9tL0SUa7afDYm5WoxWhOFAPR6mHLhM/9ADJoYWyIF11xzjZk1a5Y57rjjzCOPPGImTJhgJk2aBHLQBJ54pdoIYAFp7/gmZftMQwx4s333oqWdOkGuODPOH2u+M/fOXIjBggceNxTeT344CRB9R25OE04bai8fs0sQWTzGdgyxrkbk9hRHDAYcckCpso+2VyLKVZvPxqRcLUZrQAyqIwM+8w/EoInxPvjgg83s2bMtKeAPkYPx48ebP/zhD02UiFeAQHURADFo79g2azEgdxyKw51kMeDepA0dSCf8riyj2mIgEZNER7sZbd7yrjcx4DLLkH20vRJRrtp8NiblajFaA2JQHRnwmX8gBk2Mt+teAe4bNAEmXqk8AiAG7R9inzsGOlFPs3cMqHfk75/kSpTXHQPZN04cRNaDOIsBtXHstOsbiYWKzj7afokoT40+G5PytBYtcSEAvR6mbPjMPxCDJsYWFoMmQMMrtUUAC0gxQ+/K9hkVxpOtBBT9J21UIl9iQM/FZRmNikrUa6cehi0T5M5EbRvbMdhM+taPbAQlcjfq/Zc7mn3/atcudx50HgMZlYjagvwGxcilz8akmJah1jQI3HjjjWkex7MlQID2rsdN+rF5cMaZtjWDBg2KbBWIQRODhTsGTYCGV2qLAIhBbYceHQcCXRAAMaiGUCxevNjQRhOfsBDY5bMXghi0asgQlahVyKLcqiEAYtDeEZ30rR+aXz32+/ZWGmhtQ4851My8YGygrQ+z2SAGYY6bbjX0epjj6DP/YDHIMLZPP/20Oeigg8yqVavMJz7xiQwl4VUgUF0EsIBUd2zRMyCQFgGfjUnaMvF8+xGAXm8/5nnU6DP/QAwyID1lyhSby4ByGMyYMSNDSXgVCFQXASwg1R1b9AwIpEXAZ2OStkw8334EoNfbj3keNfrMPxCDJpF+/vnnzd577914+89//rPZa6+9miwNrwGB6iKABaS6Y4ueAYG0CPhsTNKWiefbjwD0evsxz6NGn/kHYtAk0mQtoA9bDOj/sBo0CSZeqzQCWEAqPbzoHBBIhYDPxiRVgXi4EASg1wuBPXOlPvMPxKAJmNlaQFYCshrIf2E1aAJQvFJpBLCAVHp40TkgkAoBn41JqgLxcCEIQK8XAnvmSn3mH4hBEzCztYAsBJzUTH7XRJF4BQhUFgEsIJUdWnQMCKRGwGdjkrpQvNB2BKDX2w55LhX6zD8Qg5RQS2sBWQeYGOjvUxaLx4FAZRHAAlLZoUXHgEBqBHw2JqkLxQttRwB6ve2Q51Khz/wDMUgJtbYMMDGgYmA1SAkmHq8FAsiQWYthRieBQCICvplXEwvCA4UjkEQMnvvzS+aqmxaYSyeOMpTBXH4oC/mjy1ebr3/lNPPRbtt36QtlbR/Yv6/9/mf3/8b5HP1OWdsvvvYWc/7YDrPnbr3MlTffYUadONDst/fuXcqlNl383VvNpeee2el3qq/PXh8zw4ccbej/v33qWXP9RV9stFv2hQrl+rgOmTmefv/kgR/v8r5PvXNuX9ilzdPPOd22K69PaYgBCVAlPv/zrjEv/c6Y3Q4yZrsPBP2FJ4zZ44j3u/fuW8asX2XM7p80ZtvtKtHlrUu+X4l+oBPFIYAMmcVhj5qBQNkQ8Mm8WrY2oz1dEUgiBnGYxRGDJU89YxYtXWnOG32KF+ySGNBGnTbxt9yzyFww5tQupMOXGNAGXW7I44gB/fbl6bPNhNOGNjbw1L9Z8+43N0wbbwmIb73UYd9+e4ET8VCpiMGK+Vc32w+8VxACJEAgBgWBX6FqsywgFYIBXQECQMAY47MxAVDlRyBJr2uLAW34x0673uy52y5m4BF9zaa3NnexBPz3lnc6nfjTO2QxmDpuhLl8znzzV7v+peFT9XHDh5gJI08wX/vePHP3oqW2XN6Is8VhwCEHdALSd4O+afMW84dn1zYsC3HEgOqK2tDL733rBTEov9zXvoUgBrUXgVwASFpAcqkEhQABIBAEAiAGQQxTYiOT9LrcTL/25iZ7qn7ZxDNMvwP2tZt5+mhXIn3ar4kBv7PimT+Zi679iSUCvXbasYtrj8vq4LtBJ7ci+qx5/mV7gu8iBuS6RH35/NBjjCYh1IaZc++yLkXUfx9XIhCDRLHDA0UjAAVe9AhUo/6kBaQavUQvgAAQ8EEA64oPSuV/Jkmvy830s2tf6HRXgDf8mhjoDb0mBp869EDrriPdh6KIgcudKA0xOH5AP3P2ZTeayaNPNrvs3LNxX4JGhu8YUN38TJR1gu9Y+BIDfcdAWkHykgif+deWy8dJApRXh1FOvgj4CFC+NaK0KiKA+V/FUUWfgEBzCGBdaQ63sr2VpNclMXhwyYpOl41dxIB88/mUnvqriQGfzLeDGBAB4fr/7ynHm+t/eq+9SC2JASwGGaQySYAyFI1XW4gAFHgLwa1R0Zj/NRpsdBUIJCCAdaUaIpKk11thMchKDPRFZRoJutdA7kBsjZARivi3HXt0Ny+8/GoXYkAXi/Udg/t+/aQZdMTBZtZt99mBJlck33r5+VZKiM/8g8WgiRHg0FRHHrJ/y2+QN9G83F7xEaDcKkNBlUUgaQGpbMfRMSAABLoggHWlGkKRpNe1Xz673GS5Y+BLDFx3DHijT5eYOfoPPcv3FXijz6FLaaQ46tCuvXra+wLSYsARhzgq0bBjDzcPPfEHM+WqmzuFLPWtF8Qg4LkBYhDw4KHpbUcgaQFpe4NQIRAAAoUhAGJQGPS5Vpyk111RiagRU8cNN889/3KXkKJJUYmiiAG78yxbtSYxKhHVzZt0imREH+3HLy0GDBi5OFF0pChiQM/oPAanfe5YG3VJtsmn3qg8BhR9Kc8Qpj7zr9YWAxIAHoiTBvZv3JDnsFo0IPQ7/dsx+Ch7q37d+lcNPbv2pQ1GWgxIcKZd91MrR1wW/Z9MVBvf2my/f2btS1Zwl69e03hWJ8LIdeZmLMxHgDJWgddrgEDSAlIDCNBFIAAEPkAA60o1RKFVej1tHgONZlweg2ogn60XPvOvtsRAZrcjmMnMxRt9SQyIqTHTIzJAjJFu2FM8XmZy/PxN0882+++zR6MsjrErWaNMhsG33svqkuQjQNlEFG/XAYGkBQQZMjtn2PQJa9eODJl1kE30sf0IYF1pP+atqDFJr2ep05WHIKlMbXFIer6Ov/vMv1oSgyhXIJmp7tU3NtqNP230KQSVzmyn39cptOnvF195vZGUg4SPw3JxWWR5yDvVdd5C7iNAedeJ8qqHQJYFBBky35cHad52JdSpnuSgR1VEAOtKNUY1i16vBgJh9sJn/tWSGCSlsE5DDGTmPSkm5CI04/yx5jsfZMWT8XqlCxO9U1aC4CNAYU4NtLqdCCQtIMiQ+WHmTN842zR+efqdtlMeUFe9EcC6Uo3xT9Lr1ehl9XrhM/9qSQyatRgMO+6wRugp6XqkLQYsSuyCJC0GUsxkinBO5V0mMfQRoDK1F20pJwJJCwgyZJYzQ2Y5pQmtCh0BrCuhj+D77U/S69XoZfV64TP/akkMaKh97hiwK1HWOwaSGMj7COSm5CIVZRBHHwEqQzvRhnIjkLSAtCLeNTJkllsm0Lr6IoB1pRpjn6TXy9RLnwvJUdGIuA9ZL0SXCQuf+VdbYsDkgC/xyZBQevNOz8pwVGmjEmmLgYxgRL8xASmT8FBbfASobG1Ge8qHQNICggyZsBiUT2rRolYhgHWlVci2t9wkvd7e1rhr872QHEcMeL84sH9fe+805I/P/Ks1MQh5cNvRdh8Bakc7UEfYCCQtIK2wGPgmwnGdJPlmquREOFXMkBm21KH1ZUUA60pZRyZdu3z1es8e3Q3nDJCHoPKAlHMJcE4CuqN58x0P2fDwdAdzzfMv29DxMry7zgvgOmDVp/188Eu95fIeXLKiEUKe6ttn995mxo/vNBte32QOP6iPDR6zbv1r5pZ7FnXJvZAOteKf9pl/tSQGk6+8ydz/m+XFj5BowdBjDjUzLxhbqjb5CFCpGozGlBIB3wXk0omjbPuRIXOeScrMSTjh8nEpxR2NSkAA60o1RMRHr3NG4OFDjrZu0xStkTbZK575k5k59y4b/r3XTj3sb/ThYC70f36OIkTSZp2yClNeKHYTldHZdPZiibAMfaoPfIic0Ifbxwc9UeX5Wh7KPro+86+WxKDsA1eW9vkIUFnainaUFwGfBeSqmxYYIga0SMgTHWTIjM7M2Y4MmeWVKLQsZASwroQ8eh+23Uevy5wspNcpe7CM0Mil0QadrAJMDHjzryPWsbsP54CaPPpk69rD1gO2FHO5ejMfZQnmZ6UrEbVVEhf5TOjuRD7zD8SgGnO0Jb3wEaCWVIxCK4VA0gLSbGezXgjzuZDWbNvwHhAAAtEIYF2phmQk6fWoMNRMDAgBOv1nFyP6m+55MjHgDX4SMfjd6j92AlOHfo8iAjKXFN0XZaKiiUEUiWk28VqZRtxn/oEYlGnEStYWHwEqWZPRnBIikLSAZGlys4q6KmbhLNjhXSBQBAJYV4pAPf86k/R6HDG4d/Ey8+jy1Y1NubYY+BCDi6+9xZw/tsPst/fuzs4l6Xmul9wyQQw+hBHEIP/5UpkSocArM5SFdiRpASm0cagcCACBtiKAdaWtcLessiS97ksMNm95194rO/KQ/b0tBnwngDpHm3q2Alw28YwuUYPk4ZFuU9wdA20xSCIZLQM654J95h+IQc6gV6k4HwGqUn/Rl9YgkLSAtKZWlAoEgEAZEcC6UsZRSd+mJL0eRwyYDJArEEUGGnPq8WbhY783U8eNMJfPmW+SLAZEDHRUIu1GxD3SLqcyGpJ0JeLvOSqRJgZVcT31mX8gBunnQ23e8BGg2oCBjjaNQNIC0nTBeBEIAIHgEMC6EtyQRTY4FL2e10l/s26rZRttn/kHYlC2UStRe3wEqETNRVNKikArFxA65ZG+qlkhcEXO4ASHHAVD18OnSeeePqzTiZerPfI07bU3N1UiPnZW7PF+PRDAulKNcW6lXs8boayn/VkDXeTdnyzl+cy/thCDG2+8MUs/8G4BCBx88MHmuEk/Ng/OONPWPmjQoAJagSqrgEArF5C8iYEL7zhi0MyJVJyvaxXGHH0AAi4EfDYmQK/8CLRSr5e/9+G20Gf+tYUYLF682NBGE5+wENjlsxeCGIQ1ZKVsrc8CIkPIyeyWdFKjM1BSchxKekPZMgce0ddsemuzjW5BHxkCjzNhUtkUT5s+22yzTSOpThRY0mIgyyNf1LUvbTBRFgN5miTjae+yc09D+Rl05s/999nDXrZj/1pK8kOfK+bMNxeOG2FzOeADBKqKgM/GpKp9r1K/fPR6lfpblb74zL+2EAMIUJgi5SNAYfYMrW4nAknzn0/j6cJZVIbMi679iblh2ngblk5Gn+h3wL6WCNCHiMGs2+6z/6coFTJzJX1HGTijIlZoHCQxoPJkpk4iI0w25HvS91QTA1fmz3XrX7OkgZO6UXlV8WFtp2yhrvAQwLoS3phFtThJr1ejl9Xrhc/8AzGo3rjn1iMfAcqtMhRUWQSSFhDtVsMn/Jeee6Z59Y2NnTJQ6jsA/Pd5o082k6+6uXGirzfoMgNnHNBcno6O4ZtZM65e2fYoYiBD51VWGNCx2iOAdaUaIpCk15vppV4LmimjFe/43FGQeRCiDpwWLV1pD62K/vjMPxCDokepxPX7CFCJm4+mlQSBpAVEp5+X2SqJGMiwcfpOgSYGUZkwDz2wT5fTeRc0PkRjwCEHNF7XmTWjXInYKgBiUBKBRDMKRQDrSqHw51Z5kl5vpqIyEgPfO2RxxICwKItF2Gf+gRg0I701ecdHgGoCBbqZAYGkBSTJYiCJQZzF4Bs3/CwyE2aaxSZvi4F0FwIxyCBEeLUyCGBdqcZQJul16iW7icr7VHSHinQhuWbyh1w0o+5eUcQ2csdct/5Vm++A7mPxHSzOO8B3zXbs3s2eyOv8Bq67Zn9zwL7moP1xSC+jAAAgAElEQVT2su6r9HFZbHVEItl2btODS1aYadf91JbDeRD03TiyEt9yzyJzwZhTzUe7bV+YEPjMPxCDwoan/BX7CFD5e4EWFo1A0gKSdMdAEgMZHSjujoG8i8CXgKU/f5LFgO4s3Lt4WSMUKl94TnvHIA0xKMuJUtHygvqrjQDWlWqMb5Je1+6XtPFe8/zLpmPwUTYYBLmK0r0xaQWWLpaEEgVp4IAPpB/5zhc9x2X02mnHRuZkIgbyOdLbfEeNypN3zXSgiStvvsOMOnGgbZP8SL2sLcRxmZPl3Tgqz9fy0Grp8Jl/IAatHoWAy/cRoIC7h6a3CYGkBYSaEReVSGeglCc2U8cNN889/7I9haGPjErEmTCbsRjoKEcUlWjHHt3NsGMPM9KViOpMikoU5UrEmT/pfUQlapMgoppSIIB1pRTDkLkRSXrdV++6LKnPrn2h0/0yWd7/a+9cgO+qqvu/MxTSGIJCQETEZoDJgNDyENT+IQ1J/6JBIP/gGBSGP2GiaGqFIaBNwLTTIoSOJBgeDY1NJSAIqPAPCAidAdIQ6wMJtCCRAQabAkJIABNIw6P5zzp03a67f+exz73n3vP63BlH8rv37Mdn77P3/u691tqyQy8iQ232VXTMmfmJaA7Qm5NVnHzskIlOTEqtIJFFvkaCS7pLxl/M+8LAFxAT9n5vdALhm8fq76qw+RPy/iEM+n49mptASAdqbu2pWVEEsiaQovIpK50idoJwPC6r9ch32ASYV4ZNfDD5ZY3rSYtjKY0skJffem+nYLLxIpsx9sRAhIE1N5Ifi9mQRKhbed/Po2d9YTBr+tSuUwbNSxbscb5mulCXUNTyUbMiLVicELCbWFpuMQ2yPgZJF2UiDExfzOpAg+m2pNovAQbwfgnyvBCo0vtvB3W/dewgn7flQqJWJKXZz7N5y8nvIVA2AeaVslugmPyzxvWkEwNfMKSdGPinxVpyPSHIe2Lgh4iWvO9a83CUbJwZUdamjy0HwiBnv+q1A0k2WTebqgKT3yZ1ojj19/49dnVJNmXyexsy0dqc2caX//7FY091OcTYl0HSueDy67scIq0zjnzvO9SE5mvVttZPTSdyNk/izxnAiyLZ7nSy3v9206H2EGgXAeaVZrR31rju3xava7npU450V97442jdNGb0jl130aT5GMjzssaT58T0pxcfA18YaBn32XN8dGIR5xRsd/nTbqzPEgZZImNYvSLk/au8KVGaMPC9xbPA+sdCaTt1eRbodkGeJgx0t3LOycd2ecIvvemerguc4mKu+4JE6jromLghHSiLOd9DIGsCgRAEINAeAswrzWjrkHE9zndMxcAdqx+KTIPOPf0Et/Len0eXPcpHHI7lowJAoxKpGZFu1GpUItlcnXLEwW7L1q2ZUYl8YSD5ZIUZ9deZmq88a0+Z9e8alcjfqK7KyXDI+1cJYeCrMHUu1DBUW17bOkLN+erLDzP4vt3f07Fhmz1jqlOnFO2MepNqks1XqDDYsnWb+9VT6zse9mnCQPKKW9Dbv4fmizBoxuDahlqETCBt4EAdIQAB50IWJnCqPoGqjOvWwdj3EciimOZMrM8WtdNfBf8CqVPI+1c5YaBxay8+63POD0doj3l89eULAwEgR0M2XJWEtfJNe5JOHUIX6OLUIh/1kE8SBmK6ZL3lbYe1Nnf2iCzNhAlhkPXK831VCJQxgfjH2HlZ9BJRw45PWfnr+PWVz05zC5ff0omikVROW56kCBp568jvIVAGgZCFSRnlIs98BMoY17WE/j0Isvmb14IizoIjbfzt5w6CvNYt+Voi369D3r/KCQPxRE+70Mh2DHvFtC8MJDyVqEerCOOEQdLxTh5hcMwRB3c84W3MdCmrChGNtasxeW1T+pN+iCmR72PgH7Pl6yrxvw7pQEXkQxrNJlDGBJK1MM8iHioMktJJy7+XHag029asuvA9BKpEgHmlSq3Re1nKGNd7Ly1PKoGQ969ywkDi0/70kSc6pkNJYZ98r/SkG0uHIQw0bq0Imv97wjHuqhvv6tjLqTDgxIAXs60EsiYQXfQedsC+bskNd3RC0smJmbzX/g2SetmY8LQ2nvbGS/m7hKATIe5fcOaPKX74uXNOO97NvXSF82/rjGs//5IcvUfB5p/33oNxY8c4MXmUT9KNoPKdxuDWm0Db2r+od70IhCxM6lWjdpY2a1xvJ5Xq1zrk/aucMCjqxEAvuOhVGMTZnvm2bNZpRb+TS5Cef3HTCGEgixzfx+DunzzsJn/4Q27pzXdHvUmOwkLz1d8PshuGdKBB5k/azSCQNYHownza0YdG74ANOGBNAeUd0t9aU0PxJ4q78VJiYMvCOk0YyEVj1rxQ3+m4mNdZwkDeY72Zs9ebksXRToMT+Dd9+o5zVbFZbUYvpRbDIsC8MizSg80na1wfbO6k3iuBkPevcsJAKqvXYPfjYxAqDJJsv3Shr4sOKZf81l5z7Xuz66Jl913HdW4ztYsOa9M27ajD3Kpf/sqde+mKrpClofmWLQw2b97sli1b5s4999xe+yfPNZzAs88+6xYvXuwWr9riHr3lssTa+mZ7VhxvenVz1+2X/m6/+ucsOm+W+9Z1t4+48VLGgTRhIAv4uDDHoaZESSeV+h7rOKSV982I7O+knNaMMCm+t54QcClaw1+ghlZPFiYz93vFzZw50336059uaC3rX61Fixa5M888040bNy62Mr0Kg9CxtQiCWZsnaWWpShShIjjYNGopDGTSs44l82fPcE8/+6L76ukndsWYzYpKFCcM1Jxn7bpnOuFB0zqONU0QsL4df1yYKxtrV57Jusfg5E8e5STqki1TSL5x9xj04oCT1uniOpAIgosvvtjJoLHDDju4rVu3Ft1vSa/mBDqCYPFiN3fu3CBhYHfDfWFgF+5++GId2L92xgx3/hXXR6ZDYrrjL7ht+nbBfdeatV2mi4o+dPLStNT8KC5/a0rknwiGltPG90YY1PwFaXnxVRjcfPPN0fgg/9t7771HUPmHf/iHaGPh8ccfdwceeGD0u89//vMtpze86o8ZM8a9/fbb0ebf+eefP0Ig9CoMhlWDEIffrHG+iZsvtREGvXaUkIZPS7upirBXnv5ztgMdfvjhkSC47LLL3Pbt2yNRsHDhQnf22WcXlR3p1JyALwh0ws+aQPwTA/tvOTFIC0bQy4mBb6pUpRODJAGDMKj5y0HxOwR0XnnppZfcT37yk2jx7wsEEQVLlixxS5cudUcffbR74IEH3Jw5c6L5BnEwnM4k/OfNm+f+67/+y40aNcqdc845XQIha1yXUsrmqW5i6salXYxfs/LezvdaKzH/lM0Uu0Fsfcns3/0LYjWNpJNZ9d2SuwY0aIz6kn1t1nT3t9/5f1ESUl+5R0E+TfPlarww0I436fADo46U59NLZJA86Tfht9KB7rzw/7jvfve77oc//GEkCN54442oauPHj3cysPOBwK9//evIrCxugo8G2SO+mGlKZG3r0xbuaT4G/nPWx0DSjwuBLAtua76jfkDTp3zExV2G47d20ulDrz4GeYRB1jE5PRMCVSTgL0z8DQUxX5kxY0Y0pogo0I+IA/nuV7/6VRWr1cgy7b777m7jxo1R3XbaaacugbDLlPNSx3U/MMM3V9zmTj1uUpRW3NiaFJ1RLT3ErHvW9KldVhhJO/r+pq8NViOntrrYl9DPWhYbKt+uJ5s2zrZCGDTybaxIpaQD/cnYddGuzltvvVWRUlGM2hH48JmZwkAGZ43GY3eB4qKSJe0k+VGJJBDAtKMOjTYN9FZKMQc8/cTJUcQhuedE7h6IS0+cku0NnEmRf9KiEtn8bZvZk85QUyK/PE3cyapdv6bAPRGQecX9clnms7IR5X9kJ5dPuQR23HFHd+KJJ7ofPjM+WBj4d1D5wsA/NfZNRq3J5t9c/X133qzpzt7zFLdhY8PZ+1Es9fchoeKbZk6EMCj3/al97pwY1L4Jh1KBIk4MQnbnh1KZIWRSxGll0yarIWAni4oQ4MSgIg0RUIx+Tgwked2Qkf8W8x0J7e7b9cfd+WKf02LqhpHu7D+3YVNXuGpbnTghYM2a1FzJFwZx81DTxlqEQUDH5yfJBPAxoHfkIdCPj0FVhYG948BnYe1e83CS3/bj39TPs3nLye8hUDQBfAyKJjqY9MTHYP78+ZEDcq8+BlqypJPRMaN3dHL3ix+9LWmH369p0u/S/E9tAAhJz5oSIQzeIVyJcKVFdussL/Mi8wpNK61M/pGZn6bat8nf45wk7e9thxe7PLXpSztyS6sDUYlCW5jfWQJ5oxJBDwIQaA8BohLVo637jUpkF+32lFQX4wu+NDMKMf2xQyZGJwn245sW6Z0us6ZPcVfccGd0T5SYd4b6GNgIkmk+BnHCAB+DAfXXLOfDIrOtojBIq1+aMMgbdckPhdjvzmLakRP3GBTZa5uZVug9Bs2sPbWCAATiCHCPQT36Rb/3GPhh131TIg0xLf5e9qO/S4o+ZM2Mkk5tfXNNNVfSvNSUSP8u+UtUomU/+KeO6JC/WRHRlBvma2VKZBvO2pIlRemQRpMjqLTwUxJuytqjxTk1ioPgTT9eE10yJjHI5QIzsV3Tzin5xDknav6vvva6e+ChddENq0mRkXyxoumJI+SkDx8Y3WOgjpD6gmTd0yAe+jYM2JyZn+jwsPct9KN2QzpQPYY4SlkmgWFuDJRZT/KGAASyCTCvZDOqwy+qPq7n3ViNY940/wKpY8j7VwlTIv+WUD2C8kMG+qEBn3n2RXfOaSd0qTobfkogSGQRsV+Toyo9jpJFuA0nqDcsr39hYxS79qn1z3duW5X0NJyhDZulC3F7M3LSy+w7uCSFTvQ996+/c3XnYjf/hlXJS+uhtzHvusvOIy5U6+flCOlAdRjAKGO5BKo0gSQ5m9lxY5g7Q3GOd0W2lo6tccf1/o6YxBSfsPd7Rxzr2/L4/OwYVWS5Sau5BJhXmtG2VRrXk4j2szHar8VFVVs55P2rhDBIMv+J22lXO3u5sVSFQdrEZU8c/IuTFl/3o0gIyELA2qDZfO9/8NGum1H9BXrShJtUJhEdaRc26XP+gj4pX2s+FCcM+uncIR2oqp2fclWHQJUmkNCxZlj0yhYGeSdOn18Td9SG1fZtzYd5pRktX6VxvRlEh1OLkPevEsJAby/VRbriSRMGsrueFX7K7vzL4t8uov0bVdOEwYIrb+xqMTE7WnTerMhxxvemj2vaEKHhmxL53va+MNB8EQbDeZnIpXcCIROINdebP3uGW7N2XWTr6Qtp/6IaORFUu1H/Zk29F0FKLqZ+++2zV3SCaG+6FJtSa+sq7/Yf7v9Bd8C+e3d2zkMXv0n3K8jY8uvfPBeZHIqJ4rSjDuuY/YmNrJxUihmjmCLaKEi+6eOia293G1/Z4g47YELntFAucZOPb2urY+OfHH5g9P3H/9chsQ5+dsdfx8BDJk6IInVk8WvqzaC993SeDCEQsjAJSYfflEsgZFwvt4TkHkcg5P2rhDAI3cVLctQNDT/lnxjYnfs0YRB3MuGbP6V1QVu/ok4MEAa89HUhkDWB+Lvm8i7+4rGnOmZ99j1VYaCmfHpiZ99t4WJvUrYmhHLTcVx4OmtKZN9RSSskupcu6O3tympmaPPXDY3fvvRK1+LeChcVCb7po5oMSpQxW19r4iimlcJImFkfqzknHztCGPiCxwqDEH5qcpX31KEu/ZZyDoZAyMJkMDmTapEEssb1IvMireIIhLx/lRAG/sJABYCEpjr7b7/j7GQreGR3fenNd3fsYZPCT8lv03wMQoSB9TGQCVkn6/mzT3ILl9+S+8RAyySTv/o2aJ3y+BiECgN8DIp7oUipNwJZE4h/YpgmpJPiVvubA+oXJO+s9U0KEQZSy0uW3+LmzT4pCl4QYkfv39Bs6yS2+/KRRbu/oWD/Lb+x5o0+B/td0s2g/tgoadpND9uC/oLeCoMQfioMQk9Ueus9PNU0AiELk6bVuYn1yRrXB1nnIk0ws8YvP9qjrVcRl1UOklNc2iHvXyWEgRQ+6QhdQ1NJpJ3TT5wcmQHI5Ld125sdswB5Pi78VEhUIjXhSToxkMkvLmxW0sUccQ2RFJVIfitmE08/+2LHyVifz4pKFCcMdOdw7bpn3NULzoyuDO9nNy+kAw27U5Nf/QhkTSD+ojpUGNj3UqhoNC7576RoZiHCQH2OJh1+YGTmIx8/zrbfCv5C3dbBOvX6E5ovDNQ0SNPXOvmmj0k3g6qJo/V9ihMGcSeevimRxgpPElYIg/q9i1UoMfNKFVqh/zJkjev955CcQlHCIMQHM00Y6No1ZPNokDzypB3y/lVGGOSpWFt+289ufxEdNqQDtaUtqGfvBLImkDwnBrJ4lc+s6VOjjQE1u0kyJ1Rhr6eDocJAynTXmoejvE49blIkstM+WScGGu0n68Qg6RJDP/20m0F9IdDLiUGIsEIY9P5OtPlJ5pVmtH7WuO6PO3bMSvOl+sdb3zlh/eeHHu9s9sj4a+9F6Mc3y1pmxJ2apoWBX/IXZ7hrVt7n/DD1/WzADrs3hLx/CIMCWsW/yMMmae8U6CWrXjtcEUdcIR2olzrxTLsIZE0guvtjwwpbHwMbjlfEwJEH7TdCGMiks/Sme6KTMvmELGytX4EfrlTLtM+e40fcMRLXelk+BjYMqD1dsGGT1TlaxY71FfB9k6yPgTVx1BDGanak97jk9TEI4YePQbve46Jqy7xSFMly08ka1+1mhpRU7p2SMd4f57LCyMf5asWNm0lh6a1vliXmX17ml1d9y2y0R9+fS9PrdxN3mC0Z8v4hDIbZIjXLK6QD1axKFLcEAlkTiBTJ7iCd/Mmj3PMvburcQKkRdiRKz5QjDnZbtm6N7PWtOY2Y5ImZoUwOu717XKIwUBNEydPedCn/FtEhn7gQxiHY0qISWWHg73zJJYvTjjp0RFQiu6ngnxhIeeJMHO1iXXa+lNnuu43LHZUozpTI8iMqUUiv4Dc+AeaVZvSJrHHdDwihmw1+tMgsXyoJ/qLBJtSE2p68+uN9Wlh6S943I4obY+X39jRahYEfpj7EJKkqrR7y/iEMqtJaFSxHSAeqYLEpUsUIZE0gfnGTopQNs1pZdqXDLMsg8+r1RFLLlOW4N8iyk3Y9CTCv1LPd/FJnjet28S6+Whrd0fcNk3TTfKnkOd90NC1oQ1pYeluHOCFgN5sktLT4lsUJAz9MPcKghz6d1YF6SJJHhkCAAXwIkFuQRd73v2xhoKcX1vzGnmj4TebfIVCnJvWP0/OUvU6TYZ568dvBEmBeGSzfYaUeMq6rX4GUSYI5yF0tSTvz8pskX6p+TgyS/LbSxi8rPMT06YLLr3fnzZru9MQAYVBALwvpQL1mk3S3Qa/p+c+FpN/P7mLIxJzkPKgv0uqHHo9MK4r+MIAXTbSd6Q3y/W8nUWoNgfoSYF6pb9vZkoeM67qhsvuu4zommnHh6fXelbQLLbN8s5J8DJKEgb/2ss7R1kczzsfAFwb4GPTQp0M6UA/JRo+ELNx7TTs0/X6EQchRfpowkDKGpNELAwbwXqjxjE9gkO8/tCEAgXoRYF6pV3sllTZkXNedd3Ug1rTsCWyaL5W/WBcH5jtWPxTd9J7km+XfGJ8kDPy1kx9ERk2J9O8SBl6jEvnCYFBrsEH0lJD3rzI+BmnhqxZde7vb+MoWd9gBE7puC5UONenDB7otr22N/i4f7Tjy33q3gTqjyN9GjRrVUa5J0NUGzk/fvzth9oypHacY6azawfW4Sf5my+HnF+f8onHMtXPf/+CjbsGVN0aPSkeVKCk+DwnBOIg4uiEdaBAdlzSbRSBkAmlWjakNBCCQRIB5pRl9ownjehHmkEWkMcweEfL+VUIYxIUr/O1Lr3REgA03FRcWUKDqjZ/y32JWI4t7fU7+9qULl0U3KIuNW9onKX296Vi90a3nuz1q0tCBceXwY6Fbxz3/1MF+Z08MbL00vSJCk8YxCelAw+zQ5FVPAkVOIGn2qUqnXx+Ffp+P2wCw4T+LaEXdodLb02UT4tP/+6Nuw6bfdaI55c3HH4Py7oLZ3/c7WYa0c2j9sk5c49Kxu4eyATQIU83Q8jftd8wrzWjRIsf1Mon0E0BhUGuvQfIIef8qIQz8iTgt3FTcRUJyVHTOace7uZeu6Fx45IezuuCKG9xFXzmlp4uK4o6i7CRqhYH8t714Ke6GUW10O5GmmSP5wkBjlGtoQkkv7yQe0vFCOlBIOvym3QTqNoEULQyKbn1rz2rtZP27GPLm649BIf5Pmkecva58l3VjdN4y9vL7XoRBnrr3UqY2P8O80ozWr9u43gzq/dci5P2rhDDwbz5NCzfl+xSoUFBhILHM7UfMbw6ZOKErrnka2qT09eIgNfWRNNR0KE4YxJXDTpJxgsGaU9lIJ74wiBMqCIP+XxhSGAyBkAkk1JRw+pQj3cr7fhGdEFrTPnlfNr+2NYocIR8bMzvpJk09qdR3VXeGQ4SBvr9i8rfitlXuuQ2bIlM/ecd900V7X4KI+aT3POkeBNsqvlOcbEJI+WUsuuALJ7mbfrymc2KQlJ5vSyvs9ETUmkTKiWTobpp/QhA3HinXww7Y1y254Y6uW02lrNZE0raz3FQaF0ZQN0T0plLl7/diKcvOY8a4+x58NGJlTwDi+p08b7nqpXly6izt7NswJ5m6SjppEatsndIuwszbX+x8ERfvPY6/lDXtHo4tW7e51b98PKp/v1G4QhYmgxmJSLVIAiHjepH5kVYxBELev0oIg6wTA7sQTjsx+Jurvx8tDHyTnZCJXpGHnkiknRhoaCu/HHETlobw8r+zTjcIg2JeCFIph0DWBJLHlNC+n0tvvjuqkF52FnfzsUS5EDEv/kZqdiOOcBr+Ls400AoLeyrnL9DFn0nic8tFX7Jbr6eS8jtrumjHH/nOjg/6bsvmhT6fdLumPOsvwO04ZMutNx6L+aStt7CSPJWbjRBiw/Lp2BVqEuSbRV6y/BY3b/ZJzvLTBe60ow/ttNlPH3ki1mTUtrPccqpzgIhB5ffIE884fV78rJLMRaW+dz3wcHQrtj3RTbuB1eajz4TcrGpPu9Pa0d8Ms21i+1mceZdclif9Resb1756oZ4vDOSZOP6WX1x6ehO57VdZZrlJo03IwqSckYpc8xDIGtfzpMVvh0cg5P2rhDDIWhhYYWAnMh3ABKnvY2B9Bfyb8dKaICl931RJJkJdiKT5GNhy+AOpnUx98ZLmY+CfGAzKzi2kAw2vO5NTXQlkTSBZGwPWdM6eENqNgKQFsn/LphXcSQuwPMLA3oAZt8iXBbatnx+OT8uQdlIpO+b68cPipdXb31CJM0FMitetwiDEpMYff5JC99lFs6TvnwzHtbOO6/bmaGGh5Y7j75sv+Ytu7QOyKWPzTBJwfn9IM3UNbUf/XU7ql0m+FnEbWFqXa1be65KEgTWpTTuZt8JF0lMhmWYaGzo+Ma+Ekqr277LGdb/0vhgeRO1C+6d91/s1wYyrR8iGSpXD0FdCGAjYNFMCfyFsjzznz57hnn72RffV00+M2sdGJbJH+3mc/5LSv2vN2k6EIMlXjqVlF0kFioSzkl0pPypR0hG333ns0bI9rtW/a1Qin0dIJ+zlJWQA74Uaz/gEsiaQPKaEuiCaNX2K+5u//37Hbyh0gWwXYPY9lzKrOUceYWDD1llhYMcbOwlJlDHd5bYLfvvuKz9rsmIFhN4gKn9Lqrefjz8RqlmMpisnKnEnBiHCINS/wBeAWQtTGef8oA9aXt8cSv8e5yjsT8BWGFjTUNsH7GbPplc3dwmILFNXjSKX1o5x5Y8re1I4bv/vlm2aMLD90tbDnr5IvwxJjxODdo/1WeN6GcIgtEXyWJGEpmnHpm+uuM2detykVJ/WLN+nQZiHSxlD1nWVEQZ54Tfl90U0fhFpxPEM6UBNaQfqMTgCWRNI1olBnCmhnOCFnhjY53VROGv61K4gAUkL7CxTIt2xtjvYvk9T6ImBXfAntUa/JwaLzpvlvnXd7c4vtwicXoWBLVPa6aV/YuDvvCeZjIrJWNKJgR9PPI6bf2Kg/5YTg7TLj9RkyReKaeVO2vn3y+Uv7Ad1YmBPwOXk3J4YZPHPOoFAGAxuzKxDylnjutTBbviK8FWTNBlXk3xa7N/9zRF5d9WnyJqHvvra6+6Bh9a5ZX/5xcgHTcYFtRTxfWrUNFA2diV93wcsrlxSF9l0FjNUzT8pWpk/RsfVp+ph6FsnDGxH9V++fp2qenmZQ3bk0tJNOrbvpSz+MwiDIiiSRtYEkseUsBcfgxBhYE0D/YVgXAuqEJDvxNxFbLStj0HSiYH1RbChjadP+UjXok0mQA3ZbE8W+vUx8H0rdNJKOjEIOY0MDVOqY++ck4+NnLTt4tj6EUh9bTvLSa2esqjD+dzTjo/8O/y/qx+AbTMpny5I5O8aNc73MZDy6A2s8jsVBlk+BrZ/+eInqR1t3bVORx6034iwqHHpSdmkv6T5GNhw3+pjIwsleSaOf5aPQZxpEsKg3WN7L+O69VWJ86mSDRvrg2VNqq141s2WBV+aGW106AVqeXxqdIy2pkRJvlk6bup4L+OVDaPvjzfqO+r7CKWZiPvplWki3jph0O5XOV/tEQb5ePHreAJZE4i/s5R2c6VdMNqoRGef8im3dt3TsVGJ4oSBOiyr2Yc1DQzxSbI3etodLFksxZ2AWKHQy06ZkvUni7STjpB8ZNdLPrLwm3bUYdGumJpEhkQlCvUv0DYWDuPGjoluLw1tZxEKdqfQmmbavyft4PlRiezzdqPIRgbyJ/ReTV3jzMGEhY2IJb85/cRj3L0/+7dIZFohKL8NaUe7qWXTPvmTR0UXgNod1Dj+afkkRTlCGLR7xM8a19PGQd/UMcl3zI57Mjb5J4S+r1FamPq0QA02il2cb5Z/0uqPDyHjsx+Mxg8qU/aX2q4AACAASURBVKUw9AiDdr/bqbVHGNA5iiCQNYEUkccwHNtsOUOd3Iqom5/GIE8JbV79nmb65R6kXe8gODctzarwZ15pRs/KGtf9Mdn3tUryxdFdexsa154UWkHqj8Nx4XkvOuvUKDpaiDBI8s3SkwkVJknCIO7vdlOhLmHoEQbNeEcHUgsG8IFgbV2iWRNIL0B85820OPC9pO/voPppnH7iZPfSy5tH7GD1mlfe5wblV2TLUXQeVVmY5mXdlN9XhT/zSjN6VNa4nnViEOJTpeZDasqTdGKgf087MbBmedICcaZEftQ4FTd6YpAlDLLMf+oShh5h0Ix3dCC1YAAfCNbWJZo1gbQOCBWGQIsJMK80o/GzxnXfzMf6+sT5WolfjESbu+KGOzuXNPo2+UJOzEB1kf+XX/yMu2blfZ0NmjgfgySfml58DLKEgZTP97myZqR1CUOPMGjGOzqQWjCADwRr6xLNmkBaB4QKQ6DFBJhXmtH4IeO6NaMRP7An1z/fufgwKfpQUth2/5TYRiVKOjFI8mlSPxxpiTxRiUKEgW/qWccw9AiDZryjA6kFA/hAsLYu0ZAJpHVQqDAEWkqAeaUZDV/1cb0s07ksc6LQ1i/alFPzDXn/EAahrdTC34V0oBZioco5CWRNIGUN4GnVSLpxNmfVOz+3YT8l1J2EkBS/iD//3CfdmrXrYqPRhORl2cnx/PV3ro4ue/Qj28Sl5U9g/U5EWRf2hNQn6zd21/DUT01y659/KYpE5Uf8yEqH78sjwLxSHvsic84a14vMq5e0ypxXQsI8Z80/qx96fET44l44+M+EvH8IgyJINzSNkA7U0KpTrQIJZE0gZQ7gBVYzMSl/AW7tTPsVID47m3ZW3ezkJVE/Lll+S+eYP+vZsr63kZKkDDbmeVllIt98BJhX8vGq6q+zxvWqlrvt5Qp5/xAGbe8lKfUP6UDgg0AWgawJRBe3/g2VsgvsL5w1qoN/a23IDk1WPouuvd1tfGWLO+yACW76lCOjGzQ1rrx/46aEzIuzeY2L7W5tTq29qcTdt/UQjhKrW2L8y0dsaDU9a4+r3+kFXXqD51XnfyF6LnSBb8uVxE/57zx2jLvpx2u67h4QJr/+zXPRjaNyN4BEGdGLsPw4/VI2CRmYdB+A34ey6rvX7ru6iRP2cqse/FV08nL1gjOjU4O4+w7UUVHyGDVqlNOyaJ5+O6ZddGnbL+k+Bf27L9psX9YL3eK4SrmS6mFtpv0+kvUeVuV75pWqtER/5cga1/tLnacHRSDk/UMYDIp+A9IN6UANqCZVGDCBrAlEF4vTjj40OjpNuxFXhUHaDZlJ1cnKx9486d+8q6H17GLvmpX3RllJmeX3ITdhyu9tyDr/Jmd7Y62mJ8/oLaF66Zje+Cs31tqoF7qo1Js305rWmg4lnTToAl0Xu/KM1tm/1VdNifSiNHXUs22mtw6L4Em73TmpvnKyYW8k9m9JtVz0ZmC98ffisz7XEVqWi62TChq5WdkXef7lepr3I08807kx2d6c6l+U5wsDMSeL42pvYPbTszcXJ/Eb8Ovcd/LMK30jrEQCWeN6JQpJIUYQCHn/EAZ0nEQCIR0IfBDIIpA1gdj40rLwtZfEbHp1c7To0p17Pw60LIAP3v+D7psrbnOnHjcp1dY8Kx9786Qu4ubPPsktXH7LiPsK/AVk0oVncY5occLAz8cP9WcZ2wVmnDAIMSeyJjljRu+YyC/tkiIrjFSQyInBIRMnjBAr8n1aWnKakPRJWpDvusvOHZHw/j12jU5bPnbIRDdj6kejpFSoSHms0Ejrr2kX1yX5UPh/V6ExfcpHujj4wsD2N19w6smL1kP+X9Kz9ejXDC3rvR3U98wrgyI73HRDxnV/02K4JRyZW9HvTBV9x7IYh7x/CIMsii3+PqQDtRgPVQ8kkHcCCRUGagojC6YQp1vftCMtH51AzjnteDf30hXO30G2pjIWgzUvkb/H3YQZJwzi8rELTmtaIumquUuvwiDUv8CfSLMWsLKg3WfP8c4uepWPbx4kf0+6mC6pvkknBioM1AxL8xRzLX+B7ndba96k31kzLvlbklCL+7u2b5YwsIJXueoNq1bghKQX4mwe+LoO/GfMKwNHPJQM8o7rQynUEDOpqu9YFoKQ9w9hkEWxxd+HdKAW46HqgQRCJhC7E2p39v0TA9/sQ+zp999nL7f7buM6O8VJxfJPDNLyCTkxCHF8LeLEwF9oF3Fi4DtAJ0XA8Hf57b/lxMDf2c46MbCL4aR2istTn8sSBv7NqJJHmnO7v7Af5ImBbyLnn1Dpv+O4Sj3ShAbCIHAw4meFEQgZ1+XEAN+x4fqOZTVwyLoOYZBFscXfh3SgFuOh6oEEQiYQazvtL6DU1l5MR8RG/ciD9uuEcROhcNcDD3ecT9OKpDvDcTdhqjOomiz5tv+Srr1x86KvnOJW3vfzKDv9u9q0+7bpfhjQvD4GIo500ShmP2IyIx8pay8+BnnClOouv+6g+z4GccLgmCMOjtpJT1m0Pf1TEWtLb02JrDDw6xvqYyC/kzKIUEgybYo7CfDra/uT7ZeavtRx/QsbE30MtE+IuZttNw1ZG8c1zcfAmmYUbRYR+Dr3/TPmlb4RViKB0HEd37G73TB9x7I6R8j7hzDIotji70M6UIvxUPVAAiETiN1Z+qOJf9AVOUbNSuTvU4442G3ZurUjDPIsjnTneNzYMVHkH5uPn479t1QzLlqQH83GNyNSPGryJAJCPknCICQfMb059/QT3Mp7f+4uOuvUKD1ZAMsnNCpRqH+BpCll/8db33Gy/ueHHu+YMMkOdZxtvQqFpOhD9u9JZkSWa1x99aRGzYfWrnsmNiqRmBGpaEuzdbYmTvKMfKzgsd08LlqQfJ/0d41iJPU4/cTJTiJIiaATYZDENSm9tChHnBgEDkb8rDACIeO6PQkONRHVjRR8x1ZGY7xumoT4joU0bsi6DmEQQrKlvwnpQC1FQ7VzEMiaQHIkNeKneS7VKuu+hKJuwgzhVNTkYUVNiPlPSNn4zf8QyCNom8aNeaUZLZo1rufx6fI3S8S0Ed8xhEEz3pSG1YIBvGENWlJ1siaQXoqlu8ryrJr/+Dv4Nl3Zsb3gCydFsfjtLkwveffyTMg9C72ka58ZRB5tXsD22x5pz7eZK/PKIHvW8NLOGtfz+HThOza2686eXoNKhLR+yPs3tBODkALzm+oRuH/RKVGhJk+eXL3CUaJaEMiaQGpRCQoJAQgUQiBkYVJIRiQyUAJZ43qWTxe+Y8X6joU2dsj7NxRhoAVetWpVaNn5XcUIIAwq1iA1Kk7WBFKjqlBUCECgTwIhC5M+s+DxIRDIGtf9m+bxHeu+0T7Llyqv71hok4e8fwiDUJot/x3CoOUdoI/qZ00gfSTNoxCAQM0IhCxMalalVhZ3kOM6vmPdXapI37GQ9w9h0MpXOn+lEQb5mfHEOwQGOYGUyTjNmdmGtoyLGKORN6T8Wc69cZeklVnvrLxDy+uHcc1Kl++bQSBkYdKMmja7FoMY1/EdG9lnivYdC3n/hioMmv2aUDsIQCCOwCAmkKqTThMGfvjSrLqELrSz0hnW96HltWFT7T0Gwyon+ZRDIGRhUk7JyDUPgTaO63n4VPW3Ie8fwqCqrUe5INAQAiETiCwSxaZS4rxbW9SkePh+VBcNdzdn5ieiOwckjRW3rXLPbdjk7P0CaenZuPJJMfZtk8TFlZ+14Conz0768IFuy2tbOxGT9Dk/dKl/kdqWrdvc6l8+HpX7U5MOd/Nnn+QWLr8lunchqUw2Dr88o1GaZEf+1795zj3w0LouBlqWtOd2HjPG3ffgo1F7SGx/ieu/4Mobu8og6fu/U/5aXj8SlC8Gijwib8jr0vhqhCxMGg+hARUMGdcbUM3GVSHk/UMYNK7ZqRAEqkUgawLR42O5pVZuDdZF/qzpUzu3186Y+tHoEil7g6Q1wfGFgdxGKxd+vfy7LU4v2dGbkyWfuPRkUS830eotte/b/T2di9TiiFphIPkk3XJrTYn8Y2FfGPzisac65db09ttnL6eXeu37gT27iqJC5+KzPjei3JaXb85kQwnqRWFaX3nOL4feXurffKy3TitbuQnYllfT1rb1T0uKPiavVs+nNHEEQhYmkKs+gaxxvfo1aGcJQ94/hEE7+wa1hsDQCGRNIEm2+v7f7WJ206ubu2zzfWHwsUMmRot/+agj2yETJzh7A66f3uLrftS5cdleuJMEypbvqfXPd5UnKU69vzD2hYHkJbf1WrGUJgzibmzWelyz8p0bi/XGZVsP39QpqRy+WZDlYkWCpG0FnRUy9lTA9yvAnGhor2FlMgpZmFSmsBQkkUDWuO4/KGOMHWMHiXaYl1lmOUpnnYqmmV4O4nLMkPcPYTDI3knaEIBApvNx0oTh/90OoFnCQHeorTDYZ8/xXRNTSHpxi2ptUjv53P/go+6njzzRMeNJEga+4PAX5GKyI4ImVBj4C3xbJhEGmp7fDeU5MQ2yHzXhss9lCQObfpIw0FOBz5/0p+5b1/3IzZt9klOfAoRB+waIkIVJ+6jUr8YIg3faLE0YhJyIZvlkhaSRp/eEvH8IgzxE+S0EIJCbQNYEUsSJge5eq427nhjoAlv+nXViEGeaFCoMijoxyCsMsk4M0oTBM8++GHuaYCe6LGFgTyS0DcQEzJ4Y6K7Xvnu/17246dWuPBEGuV+n2j8QsjCpfSVbUIGscV0QWJ8u8VVSE0XZGIjzcZJn0nzE7DPyWzH9FPNT+fuia293G1/Z4g7a7wPu+ZdecY8++e+Rr9nXZk13y37wT+6wA/Z1S264I/KTEtNLOb1QHyod5/P6oD3yxDOdDRbry6bN75+Qyr+X3/rOSa7w8H2ylvzFGe6alfe5V197PfIN0/oVGcEt5P1DGLTgBaaKECiTQNYEoo7HYp+uPgay+37Oace7uZeucEk+AXE3Z+pAK/UVJ1y5Wj7Ux6AfYSD5ifO01EF9FLQMeXwM8gqDLB+DJGFgzajEb8H6Iyy9+e7OSUOWMNCJ3tY/zvRJTyh0otP+WPRuWJn9nLzDCIQsTMJS4ldlEggd1+34bX2XdFy2Pk46fsf5iEld9RkZs+xpqYgAnQ/kuzj/rzknH9vxLVPfKJum9ZOSecj3aUvyQUs6MfA3PewmjuT7zRW3uVOPm+QkX91I8f29tH3zRrJL6xch7x/CoMw3i7wh0AICWROIv7MUEpVIntHdF/n9lCMOdlu2bu3swIgjre7M2MVo3ihHoScG/g7Y/Nkz3NPPvui+evqJzgqDrKhEccJAhcbadc+4qxec6XwH5LToQknCQPjZ5yzzPCcGNoqS7pjpKY0tb9KpUJb9bQtej9ZVMWRh0jooNaxw1rge5yOmPl5JppcahS3OR0x9xuxiWTdzRBhY/wVfGPiCQk9LfXPSpDTkRDjJBy1JGKQFmrBzgi2DCgNbf50fr79z9Yj5pJduE/L+IQx6IcszEIBAMIGsCSQ4oYAf+hGOAh4Z+k+K3P0ZeuFNhllOd7Zscc7cmBGV2Xrl5R2yMCmvdOQcSiBrXPd9xHyfrDgfp0XnzXLfuu726JRYdu11A0g3OKwpjnyn4ZlFGNgTX18Y2KATdizyhYGcCtiPhohO8mmTjaOkcTDOz8z6dulGSpwwsPVHGIT2SH4HAQjUhkDWBFJkRYoUBpqWxOT3PyH3HKTVq0ib0SL55UkrVBjY8Kf2IrMmMMjDi9++QwBh0IyekDWuZ50YxPk4WZ8wG4RBdtD94BF24V2UMEi6hT7p3pw0YZBmJpkUXMIP76w9pUiTy5D3jxODZryj1AIClSWQNYFUtuAUDAIQKJxAyMKk8ExJsHACWeO6v8j370ex5j1qz6+mRFJY30dMdu3VnGfM6B0jJ2X9XRHCwPcxkN19EQpyH44fXMIP2xxnshl3maOKIWtSGudj4J8YFHnKHPL+IQwKf11IEAIQsASyJhBoQQAC7SEQsjBpD4361jRkXLc+XWef8in35PrnO+GK43ycdMEf5yNmT3DlxPbc009wK+/9ubvorFNHLNw1oIXQ1ahE8js5sUwyJVKnZblYUm6et6fCaScGah6UFZXIP4GO88nSqES+MCjydDXk/UMY1Pe9pOQQqAWBkAmkFhWhkBCAQN8EQhYmfWdCAgMnMIhxvUhT0IEDCMigCBOgItKwRQ15/xAGAY3LTyAAgd4JDGIC6b00PAkBCJRJIGRhUmb5yDuMwCDG9aYJAyHZT+Q1bj4O64v8CgIQqBmBQUwgNUNAcSEAgf8mgDBoRldgXK9nO4a8f5wY1LNtKTUEakOACaQ2TUVBITBwAiELk4EXggz6JsC43jfCUhIIef8QBqU0DZlCoD0EmEDa09bUFAJZBEIWJllp8H35BBjXy2+DXkoQ8v4hDHohyzMQgEAwASaQYFT8EAKNJxCyMGk8hAZUkHG9no0Y8v4hDOrZtpQaArUhwARSm6aioBAYOIGQhcnAC0EGfRNgXO8bYSkJhLx/CINSmoZMIdAeAkwg7WlragqBLAIhC5OsNPi+fAKM6+W3QS8lCHn/EAa9kOUZCEAgmAATSDAqfgiBxhMIWZg0HkIDKsi4Xs9GDHn/EAb1bFtKDYHaEJAJhA8EIAABJXD/olOi/5w8eTJQakoAYVDPhkMY1LPdKDUEGklg1apVjawXlYIABHojgDDojVsVnkIYVKEV8pcBYZCfGU9AAAIDIoAwGBBYkoVATQkgDGracM45hEE92w5hUM92o9QQaCQBhEEjm5VKQaBnAgiDntGV/iDCoPQm6KkACIOesPEQBCAAAQhAAAIQgEASAYRBPfsGwqCe7UapIQABCEAAAhCAQGUJIAwq2zSpBUMY1LPdKDUEIAABCEAAAhCoLAGEQWWbBmFQz6ah1BCAAAQgAAEIQKCeBBAG9Ww3Tgzq2W6UGgIQgAAEIAABCFSWAMKgsk3DiUE9m4ZSQwACEIAABCAAgXoSQBjUs904Mahnu1FqCEAAAhCAAAQgUFkC3Ghf2abJLFjWzeOjtm/fvj0zFX4AAQhAAAIQgAAEIAABQ6Bp99O8/vrr7pJLLnHz5s1z73rXuxrd1kn3iCAMGt3sVA4CEIAABCAAAQgMhkDThMGyZcvczTff7GbOnOnOPPPMwUCrSKoIg4o0BMWAAAQgAAEIQAACTSDQJGEgpwXTp093b775pttxxx3dypUrG31qgDBowhtIHSAAAQhAAAIQgAAECicwf/58t3jxYvfGG2+4nXbayc2dO9ctXLiw8HyqniCmRFVvIcoHAQhAAAIQgAAEIDAwAps3b3Z77LGH27ZtWyeP0aNHuw0bNrhx48YNLN8qJowwqGKrUCYIQAACEIAABCAAgaEQsKcFmmFbTw0QBkPpcmQCAQhAAAIQgAAEIFA1AnJaMH78eLfDDju4sWPHuo0bN0b/Fp+Dt956K/p3m04NEAZV66GUBwIQgAAEIAABCEBgKAQWLVrkvv71r0dhSs8++2w3atQoJ5H8lyxZEoUt/cY3vuHOPffcoZSlCpkgDKrQCpQBAhCAAAQgAAEIQKB0AioMSi9ISQVAGJQEnmwhAAEIQAACEIAABKpFAGHAzcfV6pGUBgIQgAAEIAABCECgFAIIA4RBKR2PTCEAAQhAAAIQgAAEqkUAYYAwqFaPpDQQgAAEIAABCEAAAqUQQBggDErpeGQKAQhAAAIQgAAEIFAtAggDhEG1eiSlgQAEIAABCEAAAhAohQDCAGFQSscjUwhAAAIQgAAEIACBahFAGCAMqtUjKQ0EIAABCEAAAhCAQCkEEAYIg1I6HplCAAIQgAAEIAABCFSLAMIAYVCtHklpIAABCEAAAhCAAARKIYAwQBiU0vHIFAIQgAAEIAABCECgWgQQBgiDavVISgMBCEAAAhCAAAQgUAoBhAHCoJSOR6YQgAAEIAABCEAAAtUigDBAGFSrR1IaCEAAAhCAAAQgAIFSCCAMEAaldDwyhQAEIAABCEAAAhCoFgGEAcKgWj2S0kAAAhCAAAQgAAEIlEIAYYAwKKXjkSkEIAABCEAAAhCAQLUIIAwQBtXqkZQGAhCAAAQgAAEIQKAUAggDhEEpHY9MIQABCEAAAhCAAASqRQBhgDCoVo+kNBCAAAQgAAEIQAACpRBAGCAMSul4ZAoBCEAAAhCAAAQgUC0CCAOEQbV6JKWBAAQgAAEIQAACECiFAMIAYVBKxyNTCEAAAhCAAAQgAIFqEUAYIAyq1SMpDQQgAAEIQAACEIBAKQQQBgiDUjoemUIAAhCAAAQgAAEIVIsAwgBhUK0eSWkgAAEIQAACEIBASwlMmTLF3X///aXV/phjjnH33XdfafmXnfGo7QiDstuA/CEAAQhAAAIQgAAEnHNt37EvuxMgDMpuAfKHAAQgAAEIQAACEIgIIAzK7QgIg3L5kzsEIAABCEAAAhCAwH8TQBiU2xUQBuXyJ3cIQAACEIAABCAAAYRBJfoAwqASzUAhIAABCEAAAhCAAAQ4MSi3DyAMyuVP7hCAAAQgAAEIQAACnBhUog8gDCrRDBQCAhCAAAQgAAEIQIATg3L7AMKgXP7kDgEIQAACEIAABCDAiUEl+gDCoBLNQCEgAAEIQAACEIAABDgxKLcPIAzK5U/uEIAABCAAAQhAAAKcGFSiDyAMKtEMFAICEIAABCAAAQhAgBODcvsAwqBc/uQOAQhAAAIQgAAEIMCJQSX6AMKgEs1AISAAAQhAAAIQgAAEODEotw8gDMrlT+4QgAAEIAABCEAAApwYVKIPIAwq0QwUAgIQgAAEIAABCECAE4Ny+wDCoFz+5A4BCEAAAhCAAAQgwIlBJfoAwqASzUAhIAABCEAAAhCAAAQ4MSi3DyAMyuVP7hCAAAQgAAEIQAACnBhUog8gDCrRDBQCAhCAAAQgAAEIQIATg3L7AMKgXP7kDgEIQAACEIAABCDAiUEl+gDCoBLNQCEgAAEIQAACEIAABDgxKLcPIAzK5U/uEIAABCAAAQhAAAKcGFSiDyAMKtEMFAICEIAABCAAAQhAgBODcvsAwqBc/uQOAQhAAAIQgAAEIMCJQSX6AMKgEs1AISAAAQhAAAIQgAAEODEotw8gDMrlT+4QgAAEIAABCEAAApwYVKIPIAwq0QwUAgIQgAAEIAABCLSPwKJFi9zXv/51d8kll7izzz7b6YnBkiVL3Lx589w3vvENd+6557YPTEk1RhiUBJ5sIQABCEAAAhCAQNsJbN682Y0fP9793u/9nnvXu97lNm7cGP379ddfd2+99Vb073HjxrUd09DqjzAYGmoyggAEIAABCEAAAhDwCcyfP98tXrzYvfHGG52vdtppJzd37ly3cOFCgA2RAMJgiLDJCgIQgAAEIAABCECgm4CcGuyxxx5u27ZtnS9Gjx7tNmzYwGnBkDsLwmDIwMkOAhCAAAQgAAEIQKCbgD014LSgvN6BMCiPPTlDAAIQgAAEIAABCDjn7KkBpwXldQmEQXnsyRkCEIAABCAAAQhA4L8JyKmBRCmSKET4FpTTLRAG5XAnVwhAAAIQgAAEIAABQ0BODc444wz3ne98B9+CknoGwqAk8GQLAQhAAAIQgEDzCIw64ovNqxQ1qhWB7Q/+fc/lRRj0jI4HIQABCEAAAhCAQDcBEQaP3nIZWCBQCoGDTzrHIQxKQU+mEIAABCAAAQhAAGFAH6gOAYRBddqCkkAAAhCAAAQg0HICnBi0vAOUXH0RBvcvOiUqxeTJk3OXBlOi3Mh4AAIQgAAEIAABCMQTQBjQM8okgDAokz55QwACEIAABCAAAUMAYUB3KJMAwqBM+uQNAQhAAAIQgAAEGi4Mnv6PF9yXLlzm5px8rJsx9aNDb+//3PaGW3rz3W7W9Klu113GDj3/OmWIMKhTa1FWCEAAAhCAAAQaTYATg+Kb97Lrbne/eOwpd9X5X0AYZOBFGBTf/0gRAhCAAAQgAAEI9ESgicLAnhhMO+ow91d/d1PEZuexY9xNP17j/mjiH0SL9jGjd+x899d/drL7/dE7Obuol2e+fPG33b8+8Zvo+Wsu/LI74qD9nab/mY//sbvvwUej72fPmOrOOe0Ed+u9P3MLrrwx+v3799jNXb3gTLfvB/aM0l1+673R3z816XCn+T342JNu1oKroufl+0mHH+CeWv9i57myTz966lQ5HkIY5IDFTyEAAQhAAAIQgMAgCbRFGKxd90y02JaPNTOShfz37/mXSCioEDjyoP3cnJmf6BINd61Z65bedE9XGocdMCFa4NvvVATYE4M4sSF5iJCwwkD+7QsBKZ/mK2k37YMwaFqLUh8IQAACEIAABGpLoC3CYP0LG0cs/u1C/OKzPhe1oezey8nAbu8e1yUgdMEuv9Pvph19aNfiXk8U0oSA5GEX+5te3dzJU04jxD9BTzjmzT7JXbL8lqhcesJQ246WUHCEQdNalPpAAAIQgAAEIFBbAm0RBrq43rrtzcg8SHfsdSH+vt3fE7Wh7vQ/tf75aMHufy7888+6QyZO6BINuusfJwxe/t2WEY7QacJAhYOcYsw97Xh3/uXfK82JehidGmEwDMrkAQEIQAACEIAABAIItF0Y2B388e/Z2X3k4P2jU4A0237/uzRhIOlbIWLzE9Mm/8RAvtf05UTirgce7vgbBDRn7X6CMKhdk1FgCEAAAhCAAASaSgBh8D8L8ec2bOo4GFuTHt+PQPqC9VNIEwYSrjTEx0BPGyRtzfuO1Q91OSo3sQ8iDJrYqtQJAhCAAAQgAIFaEkAY/M9CXP0Q9O6Bl3/3WmpUIr0nwRcG+m/pENa8SKMSaQQj+d5/VjuRRjcS06Uy7mIYVmdGGAyLNPlAAAIQgAAEIACBDAJNFAZNaPSmRyPSNkIYNKG3UgcIQAACEIAABBpBoInCQBabdf3cdvm8yExJzJr0tEDr8+gtl9W1WonlRhg0rkmpEAQgAAEI/WTjDwAADTdJREFUQAACdSXQRGFQ17ZoY7kRBm1sdeoMAQhAAAIQgEAlCSAMKtksrSkUwqA1TU1FIQABCEAAAhCoOgGEQdVbqNnlQxg0u32pHQQgAAEIQAACNSKAMKhRYzWwqAiDBjYqVYIABCAAAQhAoJ4EEAb1bLemlBph0JSWpB4QgAAEIAABCNSeAMKg9k1Y6wogDGrdfBQeAhCAAAQgAIEmEUAYNKk161cXhEH92owSQwACEIAABCDQUALf/va3G1ozqlV1Ah/60Ifc0Wdf6+5fdEpU1MmTJ+cu8qjt27dvz/0UD0AAAhCAAAQgAAEIjCCwZs0aJws0PhAog8BuH5+HMCgDPHlCAAIQgAAEIAABnwCmRPSJMglgSlQmffKGAAQgAAEIQAAChgDCgO5QJgGEQZn0yRsCEIAABCAAAQggDOgDFSGAMKhIQ1AMCEAAAhCAAAQgwIkBfaBMAgiDMumTNwQgAAEIQAACEODEgD5QEQIIg4o0BMWAAAQgAAEIQAACnBjQB8okgDAokz55QwACEIAABCAAAU4M6AMVIYAwqEhDUAwIQAACEIAABCDAiQF9oEwCCIMy6ZM3BCAAAQhAAAIQKPnE4OXfvea+fPG33dzTjndHHLR/7vZ4+j9ecJdes9JddNapbtddxiY+/+BjT7rv3/Mv7q//7GT3+6N36vwuK39J//o7V7uvfHaaW7j8FveZY/84tZy2PC//bkv07FdPP7ErT838sutuj/7znNNO6JTHPi9/FDZHHrRf129uvfdn7plnX4z+Fld/qeusBVd10pw9Y2rX86H5/usTv+ni+f49dnNXLzjT7fuBPXO3U8gDCIMQSvwGAhCAAAQgAAEIDIFAGScGWQvzrGqHCoOkdNLy/89tb7hvrrjNnXrcpODFsF8eWcTLZ8bUj44oQugC/aWXN3ctyNOEgXy39KZ7Or+XOvzV390U5a2iKDTfXsVaVpslfY8w6JUcz0EAAhCAAAQgAIGCCWQJA130HnbAvm7JDXc4u4Msu9SLrr3dbXxlizvsgAnRIvTRJ/+9s3P9qUmHdxamuli9Y/VDTv6+/oWN0YnBbu8e17X77+/yS/5funCZe27Dpui5c0473s29dIWTne0/mvgH7qrzv5B4amDTEmyyWPbz908s5JnVDz0e7bZrmeXEQMs5buyYKA35XHPhl91+++wV7fDb8sh3lyy/xc2bfdKIsoUs0C+4/Hq313t3c1te29rhlyQMJK+40xdf/ITk288pTq/dEmHQKzmegwAEIAABCEAAAgUTCBEGsjCfdvSh0WJZFqg/feSJjgg4//LvdXaqdRF/8Vmfcwfv/8FoIf6+3d8TPScL09++9EqXeJCFdZow2LrtTSeL5PNmTY927yWNCXu/1x0ycUJuU6KlN98dm78vDCSPSYcfGJkO+cJAOMw5+djoJMDW57kNL48oj03HNlnIAl3q/OXPTnPX3n5/x4wpSRiI6VKSWZXNKyRfhEHBLxfJQQACEIAABCAAgToRCBEGF1xxg7voK6dEi3PZidbF+qZXN7vF1/2os2vv7/bLv+X7RefNct+67vbOIjduJ179BWwacvoQ5yMQakqkac2ffVKXr4DN3woD34zIL6flYMsZJwySzIlCFuhxfO9/8NFYH4On1j/f1Qa271kxEZKvnnzYNOypzyD6NScGg6BKmhCAAAQgAAEIQKAHAiHCwO5I+8LALtztaYI4++oC/mtnzHDnX3F9x9k4VBjctWZt53TCOg/nFQZqfqT280nCwNZNRFBoOQclDOwpiTRtnPMxJwbbt2/vod/zCAQgAAEIQAACEICARyBEGNidclmU67/lxMAKgyJODHxTpSqdGFiB1OuJgd3F16bQkxXxl5CPNZ9S3h/abx+385jRI6ISye+tCZCIm3978t/dH+7/wa6/h+SLKRHDAwQgAAEIQAACEGgxgRBhYG3r0xbuaT4G/nMSWlN9DCR965cgzSGOzLITb0WJmsNMn/KR3D4G9vRBHaQl/zw+BnmEQZKPgYgA65ehpxLqi+GfWggLYbfgyhudhiCNi4KkUYl23WVnd+WNd7mbfrym83tJIyRfhEGLBwKqDgEIQAACEIAABEKEgSyINRqPjQQUd0+AjaefFpVo57Fj3LSjDo0W5rrwlYhHp584OYrwo2E249ITp2RZxMqn16hENn/bC7KiEsX5QvjlkfSSohLpIj3pzoE4YeCLh5B7DObPnuFW3LaqEy1KTLHS7jrQKEb+PQZS3jgBVdSbg49BUSRJBwIQgAAEIAABCPRJIFQYZF0m1mcxKvN4L/cY+IVPu8egMhWtSEEQBhVpCIoBAQhAAAIQgAAE6iwM7B0Hfkv2E01Hbz5Our04rdf082wbeyPCoI2tTp0hAAEIQAACEKgkgSxhUMlCU6jGEEAYNKYpqQgEIAABCEAAAnUngDCoewvWu/wIg3q3H6WHAAQgAAEIQKBBBBAGDWrMGlYFYVDDRqPIEIAABCAAAQg0k0CVhIGNtmMv7kq7xCutVdTe/yufndZ183HSM37+19+52vXiZ9DMnjKYWiEMBsOVVCEAAQhAAAIQgEBuAlUVBrvuMrZTl9Cbjm3le4kuFHc/gKQ5Y+pHc3PlgTACCIMwTvwKAhCAAAQgAAEIDJxAiDCw8e8lPv6ateuchC99av3zXTcf29t1/bj4/uVcei+CVFDi5O+3z17R3QQSR1/uSvjarOlu2Q/+yX3tjBnu/Cuu7/xdbvQ9YN+9O4v1pNCgWfcRZOWvtxCn3Ucw8MZpQQYIgxY0MlWEAAQgAAEIQKAeBLKEgS7w5552fHQZmdzo+4vHnoouFksSBnNmfsL91d/d5D52yMRoAS878XqDsVCxNylLer996ZXOTcd6u3CSKZHNU9L65orb3KnHTXL7fmDPLuD25mG9IOwzx/6x2+3d44Ly1xOLpBuM69G61S8lwqD6bUQJIQABCEAAAhBoCYEsYSA774uv+1HnhmFrbpN2YmDx2dt85e8qEmQxb29Pfm7Dyy5LGMjzuosv4iHOD8A3I/KFQUj+Kgy4rGywLwLCYLB8SR0CEIAABCAAAQgEEwgRBt+/51+iHf3fH71TtPuvi/c0YWDNj6Qw799jN3f1gjOjcunzsvjOKwzkGd3FX//Cxig93wfAChERH74wCMkfYRDchfr6IcKgL3w8DAEIQAACEIAABIojECIMQk8MZMEun1nTp0b+Amp+5J8YhCzM06ISiZi4a83DUV5xZkRZJwYh+SMMiutjaSkhDIbDmVwgAAEIQAACEIBAJoEsYaA+BmKfLzvzvo/B+Zd/LzoJ2HWXnSMxcORB+40QBmKOs/Smewo7MdAy7bPn+M5Jhl/RNB+DPMIAH4PMLtTXDxAGfeHjYQhAAAIQgAAEIFAcgSxhIDmJ+ZA4DD+3YZM7+ZNHuedf3BRFJVKznuW33htFEppyxMFuy9at7pzTTnAiBhZceWNUUIlkJNGG1Pk3aWG+ddubkbiQj0Ylknzko38Xp2fNd8Le700MJZoVlUjLb02ZbP5EJSquj3FiMByW5AIBCEAAAhCAAAT6IhAiDGwGvdwp0FcBYx72fQji0u/lHgM/HRyPi265kelxYjB4xuQAAQhAAAIQgAAEggjUTRjo6cWck4/NvHhMbz7u5fbifp4NAs+PIgIIAzoCBCAAAQhAAAIQqAiBvMKgIsWmGA0hgDBoSENSDQhAAAIQgAAE6k8AYVD/NqxzDRAGdW49yg4BCEAAAhCAQKMIIAwa1Zy1qwzCoHZNRoEhAAEIQAACEGgqAYRBU1u2HvVCGNSjnSglBCAAAQhAAAItIIAwaEEjV7iKCIMKNw5FgwAEIAABCECgXQQQBu1q76rVFmFQtRahPBCAAAQgAAEItJYAwqC1TV+JiiMMKtEMFAICEIAABCAAAQg4hzCgF5RJAGFQJn3yhgAEIAABCEAAAoYAwoDuUCYBhEGZ9MkbAhCAAAQgAAEIIAzoAxUhgDCoSENQDAhAAAIQgAAEIMCJAX2gTAIIgzLpkzcEIAABCEAAAhDgxIA+UBECCIOKNATFgAAEIAABCEAAApwY0AfKJIAwKJM+eUMAAhCAAAQgAAFODOgDFSGAMKhIQ1AMCEAAAhCAAAQgwIkBfaBMAgiDMumTNwQgAAEIQAACEODEgD5QEQIIg4o0BMWAAAQgAAEIQAACnBjQB8okgDAokz55QwACEIAABCAAAU4M6AMVIYAwqEhDUAwIQAACEIAABCDAiQF9oEwCCIMy6ZM3BCAAAQhAAAIQ8E4Mpk85wr311ttwgcDQCdyxeq27f9EpUb6TJ0/Onf+o7du3b8/9FA9AAAIQgAAEIAABCIwg8N07fxb9bd26ddCBQCkEPv7hCQiDUsiTKQQgAAEIQAACEIghsGrVKrhAoFQCnBiUip/MIQABCEAAAhCAwDsERBjssMMO4IBAKQTefvttTIlKIU+mEIAABCAAAQhAwCPwwgsvwAQCpRLYc889c+ePj0FuZDwAAQhAAAIQgAAEIACB5hH4/x6RRak79zfCAAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Image('data_diagram.png')" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "generic-myanmar", + "metadata": {}, + "outputs": [], + "source": [ + "data_dir = 'data_0407_0'" + ] + }, + { + "cell_type": "markdown", + "id": "biblical-honolulu", + "metadata": {}, + "source": [ + "#### Step 1. Based on the `coupons` table, create a `coupon_dates` dataframe which maps a date to all coupons available on that date." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "aquatic-processing", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "RangeIndex: 1688 entries, 0 to 1687\n", + "Data columns (total 7 columns):\n", + " # Column Non-Null Count Dtype \n", + "--- ------ -------------- ----- \n", + " 0 id 1688 non-null int64 \n", + " 1 type 1688 non-null object\n", + " 2 department 77 non-null object\n", + " 3 discount 1688 non-null int64 \n", + " 4 how_many 1688 non-null int64 \n", + " 5 start_date 1688 non-null object\n", + " 6 end_date 1688 non-null object\n", + "dtypes: int64(3), object(4)\n", + "memory usage: 92.4+ KB\n" + ] + } + ], + "source": [ + "coupons = pd.read_csv(os.path.join(data_dir, 'coupons.csv'))\n", + "coupons.info()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "bound-mercury", + "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", + "
idtypedepartmentdiscounthow_manystart_dateend_date
9091buy_moreNaN1832010-02-162010-03-15
10721073buy_moreNaN5032011-12-012011-12-25
646647buy_moreNaN3852011-02-272011-03-08
10341035buy_moreNaN1622011-11-042011-11-14
12911292just_discountNaN2712012-04-182012-05-17
\n", + "
" + ], + "text/plain": [ + " id type department discount how_many start_date \\\n", + "90 91 buy_more NaN 18 3 2010-02-16 \n", + "1072 1073 buy_more NaN 50 3 2011-12-01 \n", + "646 647 buy_more NaN 38 5 2011-02-27 \n", + "1034 1035 buy_more NaN 16 2 2011-11-04 \n", + "1291 1292 just_discount NaN 27 1 2012-04-18 \n", + "\n", + " end_date \n", + "90 2010-03-15 \n", + "1072 2011-12-25 \n", + "646 2011-03-08 \n", + "1034 2011-11-14 \n", + "1291 2012-05-17 " + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "coupons.sample(5)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "abstract-absorption", + "metadata": {}, + "outputs": [], + "source": [ + "coupons.start_date = pd.to_datetime(coupons.start_date, format='%Y-%m-%d')\n", + "coupons.end_date = pd.to_datetime(coupons.end_date, format='%Y-%m-%d')\n", + "coupons.rename(columns={'id': 'coupon_id'}, inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "excellent-webcam", + "metadata": {}, + "outputs": [], + "source": [ + "coupon_dates = coupons.drop(['type', 'department', 'discount', 'how_many'], axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "therapeutic-booking", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Start: 2010-01-01, end: 2013-01-24. 1120 days\n" + ] + } + ], + "source": [ + "# Get the earliest and latest date in the dataset\n", + "start = coupon_dates.start_date.min()\n", + "end = coupon_dates.end_date.max()\n", + "days = (end-start).days + 1\n", + "print(f'Start: {start.date()}, end: {end.date()}. {days} days')" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "graduate-merchant", + "metadata": {}, + "outputs": [], + "source": [ + "# Create a dataframe with row for each day from the earliest to the latest date in the set\n", + "all_dates = pd.DataFrame(pd.date_range(start=start, end=end, freq='D'), columns=['date'])\n", + "assert days == len(all_dates)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "proved-scope", + "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", + "
coupon_iddate
012010-01-01
112010-01-02
212010-01-03
312010-01-04
412010-01-05
.........
189054016882013-01-05
189054116882013-01-06
189054216882013-01-07
189054316882013-01-08
189054416882013-01-09
\n", + "

25956 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " coupon_id date\n", + "0 1 2010-01-01\n", + "1 1 2010-01-02\n", + "2 1 2010-01-03\n", + "3 1 2010-01-04\n", + "4 1 2010-01-05\n", + "... ... ...\n", + "1890540 1688 2013-01-05\n", + "1890541 1688 2013-01-06\n", + "1890542 1688 2013-01-07\n", + "1890543 1688 2013-01-08\n", + "1890544 1688 2013-01-09\n", + "\n", + "[25956 rows x 2 columns]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Step 1.1: Perform a cross join of `all_dates` and `coupon_dates` - which contains info on validity periods\n", + "coupon_dates['key'] = 1\n", + "all_dates['key'] = 1\n", + "coupon_dates = pd.merge(coupon_dates, all_dates, on='key').drop('key', axis=1)\n", + "\n", + "# Step 1.2 Drop rows where a date does not fall within the validity period of a coupon\n", + "coupon_dates = coupon_dates[(coupon_dates['date'] >= coupon_dates['start_date']) & \\\n", + " (coupon_dates['date'] <= coupon_dates['end_date'])]\n", + "coupon_dates.drop(['start_date', 'end_date'], axis=1, inplace=True)\n", + "coupon_dates" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "combined-apache", + "metadata": {}, + "outputs": [], + "source": [ + "coupon_dates.date = coupon_dates.date.dt.date" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "blind-membrane", + "metadata": {}, + "outputs": [], + "source": [ + "# Validate date_coupon is consistent with the original data in terms of coupon validity dates\n", + "coupons['days_valid'] = (coupons.end_date - coupons.start_date).dt.days + 1\n", + "df = pd.merge(coupons, coupon_dates.groupby(by='coupon_id').count().rename(columns={'date': 'days_valid'}), on='coupon_id')\n", + "assert 0 == len(df.loc[df.days_valid_x != df.days_valid_y])\n", + "coupons.drop(['days_valid'], axis=1, inplace=True)\n", + "\n", + "# Validate no coupon has been lost\n", + "assert len(coupons) == len(coupon_dates.coupon_id.unique())" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "directed-municipality", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "coupon_id 24.0\n", + "dtype: float64" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "coupon_dates.groupby('date').count().median()" + ] + }, + { + "cell_type": "markdown", + "id": "aggressive-grave", + "metadata": {}, + "source": [ + "#### Step 2. Merge `coupon_dates` with `orders` in order to create a dataframe mapping `order_id` to all coupons available on the date that order was made -> `order_coupons_available`" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "recovered-decrease", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "RangeIndex: 324683 entries, 0 to 324682\n", + "Data columns (total 3 columns):\n", + " # Column Non-Null Count Dtype \n", + "--- ------ -------------- ----- \n", + " 0 id 324683 non-null int64 \n", + " 1 customer_id 324683 non-null int64 \n", + " 2 order_date 324683 non-null object\n", + "dtypes: int64(2), object(1)\n", + "memory usage: 7.4+ MB\n" + ] + } + ], + "source": [ + "orders = pd.read_csv(os.path.join(data_dir, 'orders.csv'))\n", + "orders.info()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "attempted-palestine", + "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", + "
idcustomer_idorder_date
3219423219431112012-12-21 16:46:04
2596612596625442012-05-27 12:58:35
2635952635966372012-06-09 13:50:04
76819768208562010-09-16 10:39:13
4394407762010-01-02 13:10:04
\n", + "
" + ], + "text/plain": [ + " id customer_id order_date\n", + "321942 321943 111 2012-12-21 16:46:04\n", + "259661 259662 544 2012-05-27 12:58:35\n", + "263595 263596 637 2012-06-09 13:50:04\n", + "76819 76820 856 2010-09-16 10:39:13\n", + "439 440 776 2010-01-02 13:10:04" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "orders.sample(5)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "informed-lindsay", + "metadata": {}, + "outputs": [], + "source": [ + "orders.rename(columns={'id': 'order_id', 'order_date': 'date'}, inplace=True)\n", + "orders.date = pd.to_datetime(orders.date, format='%Y-%m-%d').dt.date" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "static-skill", + "metadata": {}, + "outputs": [], + "source": [ + "order_coupons_available = pd.merge(coupon_dates, orders[['order_id', 'date']], on='date', how='left')\\\n", + " .dropna().drop(['date'], axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "suburban-venture", + "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", + "
coupon_idorder_id
011.0
112.0
213.0
314.0
415.0
.........
76244151688324679.0
76244161688324680.0
76244171688324681.0
76244181688324682.0
76244191688324683.0
\n", + "

7624186 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " coupon_id order_id\n", + "0 1 1.0\n", + "1 1 2.0\n", + "2 1 3.0\n", + "3 1 4.0\n", + "4 1 5.0\n", + "... ... ...\n", + "7624415 1688 324679.0\n", + "7624416 1688 324680.0\n", + "7624417 1688 324681.0\n", + "7624418 1688 324682.0\n", + "7624419 1688 324683.0\n", + "\n", + "[7624186 rows x 2 columns]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "order_coupons_available" + ] + }, + { + "cell_type": "markdown", + "id": "announced-mandate", + "metadata": {}, + "source": [ + "#### Step 3. From the `order_details` table, select only `order_id` - `coupon_id` pairs, resulting in `order_coupons_used` - a dataframe mapping an order to the coupons used in that order, if any." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "naughty-denver", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "RangeIndex: 2395354 entries, 0 to 2395353\n", + "Data columns (total 7 columns):\n", + " # Column Dtype \n", + "--- ------ ----- \n", + " 0 id int64 \n", + " 1 order_id int64 \n", + " 2 product_id int64 \n", + " 3 quantity_ordered int64 \n", + " 4 original_price float64\n", + " 5 buy_price float64\n", + " 6 coupon_id float64\n", + "dtypes: float64(3), int64(4)\n", + "memory usage: 127.9 MB\n" + ] + } + ], + "source": [ + "order_details = pd.read_csv(os.path.join(data_dir, 'order_details.csv'))\n", + "order_details.info()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "human-candy", + "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", + "
idorder_idproduct_idquantity_orderedoriginal_pricebuy_pricecoupon_id
10782321078233145447196254.011.7243734.0
146338314633841966972168182.032.0300NaN
1597884159788521522458424.071.34311101.0
7324473245103171374512.184.628446.0
68056068056192155186422.652.6500NaN
\n", + "
" + ], + "text/plain": [ + " id order_id product_id quantity_ordered original_price \\\n", + "1078232 1078233 145447 1962 5 4.01 \n", + "1463383 1463384 196697 2168 18 2.03 \n", + "1597884 1597885 215224 584 2 4.07 \n", + "73244 73245 10317 1374 5 12.18 \n", + "680560 680561 92155 1864 2 2.65 \n", + "\n", + " buy_price coupon_id \n", + "1078232 1.7243 734.0 \n", + "1463383 2.0300 NaN \n", + "1597884 1.3431 1101.0 \n", + "73244 4.6284 46.0 \n", + "680560 2.6500 NaN " + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "order_details.sample(5)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "sophisticated-million", + "metadata": {}, + "outputs": [], + "source": [ + "order_details.drop(['id'], axis=1, inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "protective-hopkins", + "metadata": {}, + "outputs": [], + "source": [ + "order_coupon_used = order_details[['order_id', 'coupon_id']].dropna().drop_duplicates()" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "classified-garbage", + "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", + "
order_idcoupon_id
237.0
4322.0
1063.0
1478.0
1777.0
.........
23953443246811682.0
23953453246811672.0
23953513246821677.0
23953523246821682.0
23953533246831682.0
\n", + "

400236 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " order_id coupon_id\n", + "2 3 7.0\n", + "4 3 22.0\n", + "10 6 3.0\n", + "14 7 8.0\n", + "17 7 7.0\n", + "... ... ...\n", + "2395344 324681 1682.0\n", + "2395345 324681 1672.0\n", + "2395351 324682 1677.0\n", + "2395352 324682 1682.0\n", + "2395353 324683 1682.0\n", + "\n", + "[400236 rows x 2 columns]" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "order_coupon_used" + ] + }, + { + "cell_type": "markdown", + "id": "liked-republican", + "metadata": {}, + "source": [ + "#### Step 4. Combine `order_coupons_available` and `order_coupons_used`, add column `coupon_used` which stores `True` for coupons used in an order, and `False` for coupons not used in an order." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "beneficial-garbage", + "metadata": {}, + "outputs": [], + "source": [ + "order_coupon_used.set_index(['order_id', 'coupon_id'], inplace=True)\n", + "order_coupons_available.set_index(['order_id', 'coupon_id'], inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "organized-sewing", + "metadata": {}, + "outputs": [], + "source": [ + "order_coupons_available['coupon_used'] = order_coupons_available.index.isin(order_coupon_used.index)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "ranking-hypothesis", + "metadata": {}, + "outputs": [], + "source": [ + "order_coupons = order_coupons_available.reset_index()" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "reasonable-banking", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False 0.947504\n", + "True 0.052496\n", + "Name: coupon_used, dtype: float64" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "order_coupons.coupon_used.value_counts(normalize=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "worth-quarterly", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/training-with-artificial-data/Dockerfile b/training-with-artificial-data/Dockerfile new file mode 100644 index 0000000..7532a70 --- /dev/null +++ b/training-with-artificial-data/Dockerfile @@ -0,0 +1,18 @@ +FROM ubuntu:18.04 + +RUN apt update && \ + apt install -y software-properties-common && \ + apt install -y default-jre && \ + apt install -y python3-pip + +RUN mkdir data-mining + +COPY data_0408_0 data-mining/data_0408_0 +COPY *.ipynb data-mining/ +COPY requirements.txt data-mining/ + +RUN pip3 install -r /data-mining/requirements.txt + +WORKDIR /data-mining + +CMD jupyter notebook --ip 0.0.0.0 --port 8000 --no-browser --allow-root diff --git a/training-with-artificial-data/README.md b/training-with-artificial-data/README.md new file mode 100644 index 0000000..222f62b --- /dev/null +++ b/training-with-artificial-data/README.md @@ -0,0 +1,36 @@ +# Data preparation and training - artificial data set + + +This directory contains jupyter notebooks for preparing data and training a model for the coupon recommendation service. + +`01_data_prep.ipynb` - data preparation. This notebook contains data cleaning, feature engineering, as well as merging datasets. It requires the original dataset to be present in a directory - oath to the directory is specified at the top of the script. +`02_training_automl.ipynb` - Training using H2O AutoML. +`03_training.ipynb` - Training model using scikit-learn. Algorithm (GBM) and parameters are selected based on AutoML result. The notebook compares training on unbalanced and balanced dataset. + +In order to run the jupyter notebooks, use (specify `ip` and `port` according to your needs): +``` +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt + +jupyter notebook --ip 0.0.0.0 --port 8000 --no-browser +``` + +The notebooks should be run in the order they are numbered. + + +## Docker images + +This repository contains a Dockerfile for building a docker image. To build it, use: + +``` +docker build -t coupon-rec:0.0.1 . +``` + +To run it, use: + +``` +docker run -it -p 0.0.0.0:8002:8000 coupon-rec:0.0.1 +``` + + diff --git a/training-with-artificial-data/automl_output_about_gbm.txt b/training-with-artificial-data/automl_output_about_gbm.txt new file mode 100644 index 0000000..a3b150b --- /dev/null +++ b/training-with-artificial-data/automl_output_about_gbm.txt @@ -0,0 +1,191 @@ +Model Details +============= +H2OGradientBoostingEstimator : Gradient Boosting Machine +Model Key: GBM_4_AutoML_20210408_164243 + + +Model Summary: +number_of_trees number_of_internal_trees model_size_in_bytes min_depth max_depth mean_depth min_leaves max_leaves mean_leaves +0 70.0 70.0 578602.0 0.0 10.0 7.571429 1.0 994.0 654.6 + + +ModelMetricsBinomial: gbm +** Reported on train data. ** + +MSE: 0.2711710163001577 +RMSE: 0.5207408341009544 +LogLoss: 0.8622691624238035 +Mean Per-Class Error: 0.2690251558453727 +AUC: 0.8156996013643698 +AUCPR: 0.8394483144715442 +Gini: 0.6313992027287396 + +Confusion Matrix (Act/Pred) for max f1 @ threshold = 0.05733060682487723: +0 1 Error Rate +0 0 1729723.0 1297628.0 0.4286 (1297628.0/3027351.0) +1 1 513782.0 2513447.0 0.1697 (513782.0/3027229.0) +2 Total 2243505.0 3811075.0 0.2992 (1811410.0/6054580.0) + +Maximum Metrics: Maximum metrics at their respective thresholds +metric threshold value idx +0 max f1 0.057331 7.351083e-01 336.0 +1 max f2 0.032548 8.452362e-01 374.0 +2 max f0point5 0.133512 7.645287e-01 255.0 +3 max accuracy 0.095349 7.309765e-01 292.0 +4 max precision 0.880308 1.000000e+00 0.0 +5 max recall 0.007737 1.000000e+00 398.0 +6 max specificity 0.880308 1.000000e+00 0.0 +7 max absolute_mcc 0.139263 4.796984e-01 250.0 +8 max min_per_class_accuracy 0.078557 7.252985e-01 310.0 +9 max mean_per_class_accuracy 0.095349 7.309748e-01 292.0 +10 max tns 0.880308 3.027351e+06 0.0 +11 max fns 0.880308 3.027017e+06 0.0 +12 max fps 0.006237 3.027351e+06 399.0 +13 max tps 0.007737 3.027229e+06 398.0 +14 max tnr 0.880308 1.000000e+00 0.0 +15 max fnr 0.880308 9.999300e-01 0.0 +16 max fpr 0.006237 1.000000e+00 399.0 +17 max tpr 0.007737 1.000000e+00 398.0 + +Gains/Lift Table: Avg response rate: 50.00 %, avg score: 21.13 % +group cumulative_data_fraction lower_threshold lift cumulative_lift response_rate score cumulative_response_rate cumulative_score capture_rate cumulative_capture_rate gain cumulative_gain kolmogorov_smirnov +0 1 0.010002 0.837540 1.981545 1.981545 0.990753 0.846832 0.990753 0.846832 0.019819 0.019819 98.154496 98.154496 0.019634 +1 2 0.020009 0.828769 1.975119 1.978331 0.987540 0.832717 0.989146 0.839772 0.019766 0.039585 97.511902 97.833106 0.039151 +2 3 0.030006 0.822702 1.969374 1.975347 0.984667 0.825749 0.987654 0.835101 0.019686 0.059272 96.937366 97.534695 0.058531 +3 4 0.040002 0.817128 1.967028 1.973268 0.983494 0.819833 0.986614 0.831285 0.019663 0.078935 96.702794 97.326806 0.077864 +4 5 0.050000 0.812258 1.963398 1.971295 0.981679 0.814630 0.985627 0.827955 0.019630 0.098565 96.339839 97.129454 0.097127 +5 6 0.100008 0.785588 1.956363 1.963828 0.978162 0.799591 0.981894 0.813772 0.097833 0.196398 95.636342 96.382841 0.192776 +6 7 0.150011 0.751169 1.942474 1.956710 0.971217 0.769543 0.978335 0.799029 0.097131 0.293529 94.247368 95.671019 0.287029 +7 8 0.200001 0.346516 1.862231 1.933095 0.931097 0.641898 0.966528 0.759754 0.093093 0.386622 86.223067 93.309509 0.373233 +8 9 0.300003 0.126728 1.329560 1.731916 0.664767 0.164262 0.865941 0.561256 0.132958 0.519579 32.956029 73.191627 0.439145 +9 10 0.400000 0.098188 1.110646 1.576603 0.555312 0.111048 0.788285 0.448707 0.111062 0.630641 11.064569 57.660273 0.461273 +10 11 0.500001 0.078522 0.959023 1.453085 0.479502 0.088037 0.726528 0.376572 0.095904 0.726545 -4.097720 45.308544 0.453078 +11 12 0.600000 0.061271 0.817325 1.347127 0.408654 0.069658 0.673550 0.325421 0.081732 0.808276 -18.267544 34.712655 0.416544 +12 13 0.700001 0.049013 0.705426 1.255455 0.352706 0.054639 0.627715 0.286737 0.070543 0.878819 -29.457405 25.545453 0.357630 +13 14 0.800000 0.040772 0.577073 1.170657 0.288531 0.044764 0.585317 0.256491 0.057707 0.936526 -42.292719 17.065740 0.273046 +14 15 0.900000 0.032451 0.439379 1.089404 0.219685 0.036779 0.544691 0.232078 0.043938 0.980464 -56.062056 8.940442 0.160925 +15 16 1.000000 0.002556 0.195360 1.000000 0.097678 0.024191 0.499990 0.211290 0.019536 1.000000 -80.463982 0.000000 0.000000 + + +ModelMetricsBinomial: gbm +** Reported on cross-validation data. ** + +MSE: 0.06498795354987083 +RMSE: 0.2549273495525163 +LogLoss: 0.24260618450943383 +Mean Per-Class Error: 0.2770904651860334 +AUC: 0.8021582436506262 +AUCPR: 0.4995332242622148 +Gini: 0.6043164873012523 + +Confusion Matrix (Act/Pred) for max f1 @ threshold = 0.22919100477943258: +0 1 Error Rate +0 0 2977495.0 49856.0 0.0165 (49856.0/3027351.0) +1 1 206636.0 134827.0 0.6051 (206636.0/341463.0) +2 Total 3184131.0 184683.0 0.0761 (256492.0/3368814.0) + +Maximum Metrics: Maximum metrics at their respective thresholds +metric threshold value idx +0 max f1 0.229191 5.125079e-01 167.0 +1 max f2 0.098798 5.028615e-01 259.0 +2 max f0point5 0.541164 6.446633e-01 104.0 +3 max accuracy 0.533739 9.262245e-01 105.0 +4 max precision 0.883700 9.166667e-01 0.0 +5 max recall 0.007146 1.000000e+00 398.0 +6 max specificity 0.883700 9.999993e-01 0.0 +7 max absolute_mcc 0.477452 5.089233e-01 115.0 +8 max min_per_class_accuracy 0.077382 7.161830e-01 287.0 +9 max mean_per_class_accuracy 0.098798 7.229095e-01 259.0 +10 max tns 0.883700 3.027349e+06 0.0 +11 max fns 0.883700 3.414410e+05 0.0 +12 max fps 0.005775 3.027351e+06 399.0 +13 max tps 0.007146 3.414630e+05 398.0 +14 max tnr 0.883700 9.999993e-01 0.0 +15 max fnr 0.883700 9.999356e-01 0.0 +16 max fpr 0.005775 1.000000e+00 399.0 +17 max tpr 0.007146 1.000000e+00 398.0 + +Gains/Lift Table: Avg response rate: 10.14 %, avg score: 9.87 % +group cumulative_data_fraction lower_threshold lift cumulative_lift response_rate score cumulative_response_rate cumulative_score capture_rate cumulative_capture_rate gain cumulative_gain kolmogorov_smirnov +0 1 0.010001 0.816049 8.421531 8.421531 0.853606 0.830672 0.853606 0.830672 0.084220 0.084220 742.153097 742.153097 0.082591 +1 2 0.020000 0.794009 8.171586 8.296564 0.828272 0.805167 0.840940 0.817920 0.081713 0.165933 717.158645 729.656427 0.162393 +2 3 0.030000 0.768283 7.894065 8.162397 0.800142 0.781534 0.827341 0.805791 0.078943 0.244876 689.406521 716.239659 0.239112 +3 4 0.040000 0.728944 7.604888 8.023025 0.770831 0.751144 0.813214 0.792130 0.076046 0.320922 660.488801 702.302462 0.312608 +4 5 0.050000 0.376858 5.845286 7.587469 0.592478 0.588207 0.769066 0.751344 0.058454 0.379376 484.528645 658.746923 0.366527 +5 6 0.100000 0.140183 1.934508 4.761005 0.196082 0.177726 0.482576 0.464537 0.096725 0.476101 93.450753 376.100516 0.418523 +6 7 0.150000 0.116331 1.335611 3.619212 0.135378 0.127054 0.366843 0.352043 0.066780 0.542882 33.561146 261.921178 0.437196 +7 8 0.200001 0.101635 1.145526 3.000784 0.116110 0.108554 0.304159 0.291170 0.057277 0.600159 14.552575 200.078385 0.445293 +8 9 0.300000 0.081609 0.936360 2.312647 0.094909 0.091048 0.234410 0.224463 0.093635 0.693794 -6.364005 131.264731 0.438211 +9 10 0.400000 0.065673 0.765235 1.925794 0.077564 0.073385 0.195198 0.186694 0.076524 0.770318 -23.476472 92.579373 0.412087 +10 11 0.500000 0.053659 0.611310 1.662897 0.061962 0.059244 0.168551 0.161204 0.061131 0.831449 -38.869035 66.289660 0.368833 +11 12 0.600001 0.045913 0.502951 1.469572 0.050979 0.049540 0.148956 0.142593 0.050295 0.881744 -49.704892 46.957168 0.313522 +12 13 0.700000 0.040044 0.417208 1.319235 0.042288 0.042899 0.133718 0.128351 0.041720 0.923465 -58.279224 31.923506 0.248670 +13 14 0.800000 0.034543 0.354182 1.198604 0.035900 0.037320 0.121490 0.116972 0.035418 0.958883 -64.581772 19.860360 0.176804 +14 15 0.900000 0.027748 0.284277 1.097012 0.028814 0.031404 0.111193 0.107465 0.028428 0.987310 -71.572289 9.701187 0.097159 +15 16 1.000000 0.001796 0.126895 1.000000 0.012862 0.019823 0.101360 0.098700 0.012690 1.000000 -87.310507 0.000000 0.000000 + + +Cross-Validation Metrics Summary: +mean sd cv_1_valid cv_2_valid cv_3_valid cv_4_valid cv_5_valid +0 accuracy 0.9239439 6.1908754E-4 0.92440516 0.92351615 0.9231228 0.9240608 0.9246143 +1 auc 0.8021638 0.0012602289 0.8009509 0.80137473 0.80144393 0.8033893 0.80366004 +2 aucpr 0.4995746 0.0022548349 0.49709672 0.49797425 0.49965167 0.5002288 0.5029216 +3 err 0.076056145 6.1908754E-4 0.07559483 0.07648387 0.076877184 0.07593916 0.07538567 +4 err_count 51243.8 417.1387 50933.0 51532.0 51797.0 51165.0 50792.0 +5 f0point5 0.6247343 0.0031962688 0.624532 0.6235206 0.62106586 0.62473017 0.6298228 +6 f1 0.51262814 0.0020927442 0.51327825 0.50949 0.5123657 0.5127004 0.51530653 +7 f2 0.43464056 0.0022552824 0.4356685 0.43071926 0.43604782 0.43474048 0.43602678 +8 lift_top_group 8.421473 0.06115962 8.466085 8.333362 8.395673 8.4234 8.488844 +9 logloss 0.24260618 0.0010718547 0.24179175 0.243898 0.24364957 0.24194153 0.24175005 +10 max_per_class_error 0.6053806 0.0024194021 0.6042265 0.6095273 0.603341 0.6052736 0.6045347 +11 mcc 0.5020918 0.0025971201 0.5025298 0.4998116 0.4998053 0.5021768 0.50613576 +12 mean_per_class_accuracy 0.6891337 0.001123119 0.68969077 0.68717736 0.6897318 0.68919575 0.68987286 +13 mean_per_class_error 0.3108663 0.001123119 0.31030926 0.31282264 0.3102682 0.31080425 0.3101271 +14 mse 0.06498795 3.5334285E-4 0.064677946 0.06543439 0.06530259 0.064802855 0.06472199 +15 pr_auc 0.4995746 0.0022548349 0.49709672 0.49797425 0.49965167 0.5002288 0.5029216 +16 precision 0.73137695 0.005741021 0.73002064 0.7328715 0.7233771 0.7312541 0.7393614 +17 r2 0.28652275 0.0020135231 0.2858809 0.28392112 0.28594545 0.28759506 0.28927112 +18 recall 0.3946194 0.0024194021 0.39577347 0.3904727 0.39665905 0.39472643 0.39546534 +19 rmse 0.2549266 6.9268135E-4 0.2543186 0.25580147 0.2555437 0.25456405 0.25440517 + +See the whole table with table.as_data_frame() + +Scoring History: +timestamp duration number_of_trees training_rmse training_logloss training_auc training_pr_auc training_lift training_classification_error +0 2021-04-08 17:33:34 4 min 10.858 sec 0.0 0.639457 1.197953 0.500000 0.499990 1.000000 0.500010 +1 2021-04-08 17:33:39 4 min 15.279 sec 5.0 0.616837 1.091785 0.792265 0.824312 1.976849 0.322680 +2 2021-04-08 17:33:43 4 min 19.044 sec 10.0 0.593316 1.024019 0.799346 0.827751 1.969823 0.313274 +3 2021-04-08 17:33:46 4 min 22.648 sec 15.0 0.569878 0.971272 0.802795 0.830288 1.972070 0.311855 +4 2021-04-08 17:33:50 4 min 26.494 sec 20.0 0.551765 0.934923 0.804866 0.831898 1.973781 0.310356 +5 2021-04-08 17:33:54 4 min 30.256 sec 25.0 0.539484 0.910068 0.806598 0.833337 1.976033 0.304712 +6 2021-04-08 17:33:58 4 min 34.008 sec 30.0 0.532110 0.893191 0.808993 0.834841 1.976818 0.303763 +7 2021-04-08 17:34:01 4 min 37.697 sec 35.0 0.527083 0.881022 0.810823 0.836076 1.979631 0.304047 +8 2021-04-08 17:34:05 4 min 41.397 sec 40.0 0.524119 0.873296 0.812040 0.836947 1.980430 0.299840 +9 2021-04-08 17:34:09 4 min 45.047 sec 45.0 0.522347 0.868030 0.813448 0.837918 1.980489 0.298310 +10 2021-04-08 17:34:12 4 min 48.648 sec 50.0 0.521098 0.863513 0.815260 0.839107 1.981774 0.301199 +11 2021-04-08 17:34:14 4 min 50.656 sec 55.0 0.520741 0.862269 0.815700 0.839448 1.981545 0.299180 +12 2021-04-08 17:34:15 4 min 51.297 sec 60.0 0.520741 0.862269 0.815700 0.839448 1.981545 0.299180 +13 2021-04-08 17:34:16 4 min 51.956 sec 65.0 0.520741 0.862269 0.815700 0.839448 1.981545 0.299180 +14 2021-04-08 17:34:16 4 min 52.613 sec 70.0 0.520741 0.862269 0.815700 0.839448 1.981545 0.299180 + +Variable Importances: +variable relative_importance scaled_importance percentage +0 coupon_type_department 1.227769e+06 1.000000 0.534741 +1 coupon_mean_prod_price 2.428440e+05 0.197793 0.105768 +2 coupon_how_many 1.721953e+05 0.140251 0.074998 +3 coupon_type_just_discount 1.354846e+05 0.110350 0.059009 +4 coupon_type_buy_all 1.111026e+05 0.090492 0.048390 +5 coupon_discount 1.098288e+05 0.089454 0.047835 +6 coupon_type_buy_more 8.621582e+04 0.070222 0.037550 +7 coupon_days_valid 7.937698e+04 0.064651 0.034572 +8 cust_unique_coupons_used 2.874513e+04 0.023412 0.012520 +9 cust_mean_discount 2.412111e+04 0.019646 0.010506 +10 cust_total_products_bougth 2.372456e+04 0.019323 0.010333 +11 cust_mean_product_price 1.860420e+04 0.015153 0.008103 +12 cust_unique_products_bought 1.549414e+04 0.012620 0.006748 +13 cust_credit 4.810139e+03 0.003918 0.002095 +14 cust_age_mid 4.308483e+03 0.003509 0.001877 +15 cust_age_old 3.579277e+03 0.002915 0.001559 +16 cust_gender_F 3.050989e+03 0.002485 0.001329 +17 cust_age_young 2.436828e+03 0.001985 0.001061 +18 cust_gender_M 2.314412e+03 0.001885 0.001008 \ No newline at end of file diff --git a/training-with-artificial-data/data_diagram.png b/training-with-artificial-data/data_diagram.png new file mode 100644 index 0000000..994a23d Binary files /dev/null and b/training-with-artificial-data/data_diagram.png differ diff --git a/training-with-artificial-data/requirements.txt b/training-with-artificial-data/requirements.txt new file mode 100644 index 0000000..c67a43a --- /dev/null +++ b/training-with-artificial-data/requirements.txt @@ -0,0 +1,6 @@ +h2o +imblearn +notebook +matplotlib +pandas +scikit-learn