{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":50160,"databundleVersionId":7921029,"sourceType":"competition"},{"sourceId":8416548,"sourceType":"datasetVersion","datasetId":5009911},{"sourceId":8424829,"sourceType":"datasetVersion","datasetId":5016356},{"sourceId":8482839,"sourceType":"datasetVersion","datasetId":5059750}],"dockerImageVersionId":30698,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport gc\nfrom glob import glob\nfrom pathlib import Path\nfrom datetime import datetime\n\nimport numpy as np\nimport pandas as pd\nimport polars as pl\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\n# from sklearn.model_selection import StratifiedGroupKFold\n# from sklearn.metrics import roc_auc_score\n\nimport lightgbm as lgb\nfrom catboost import CatBoostClassifier","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-05-25T18:39:19.460334Z","iopub.execute_input":"2024-05-25T18:39:19.460719Z","iopub.status.idle":"2024-05-25T18:39:24.428989Z","shell.execute_reply.started":"2024-05-25T18:39:19.460688Z","shell.execute_reply":"2024-05-25T18:39:24.427796Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"CAT_NAN_VALUE = '_NAN_VALUE_'  # to fill NaNs in categorical columns\nCAT_RARE_VALUE = '_RARE_VALUE_'  # to replace rare categorical values","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.431331Z","iopub.execute_input":"2024-05-25T18:39:24.432026Z","iopub.status.idle":"2024-05-25T18:39:24.438563Z","shell.execute_reply.started":"2024-05-25T18:39:24.431986Z","shell.execute_reply":"2024-05-25T18:39:24.436306Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We decided to use several techniques to address stability:\n\n* introducing a custom loss;\n* preprocessing and feature engineering;\n* models ensembling.\n\nWe will discuss these concepts one by one throughout this notebook.","metadata":{}},{"cell_type":"markdown","source":"### 1. Custom Loss","metadata":{}},{"cell_type":"markdown","source":"The competition metric is given by the equation below:\n\n$$\n\\text{stability metric} = mean(gini) + 88.0 \\cdot min(0,a) - 0.5 \\cdot std(residuals) .\n$$\n\nBasically, it consists of three parts:\n\n1. ROC-AUC score;\n2. falling rate;\n3. standard deviation of the residuals.\n\nSince `WEEK_NUM` and other date columns have been modified, we can hardly address the second part of the metric (falling rate). For this reason we decided to focus on making the ROC-AUC score as high as possible and as stable as possible, that is to optimize the 1st and the 3rd parts mentioned above.\n\nLet us introduce the following notation:\n\n* $l$ --- the number of training samples;\n* $y_1, y_2, \\dots, y_l$ --- true labels ($0$ or $1$);\n* $z_1, z_2, \\dots, z_l$ --- model predictions;\n* $m$ --- the number of groups ($1 \\leq m \\leq l$);\n* $g_1, g_2, \\dots, g_m$ --- sizes of the groups;\n* $n_1, n_2, \\dots, n_m$ --- the number of negative samples (class $0$) in each group;\n* $p_1, p_2, \\dots, p_m$ --- the number of positive samples (class $1$) in each group;\n* $U_1, U_2, \\dots, U_m$ --- values of an intermediate loss for all groups;\n* $\\alpha, \\beta, \\gamma$ --- some parameters (of our loss function) to adjust.","metadata":{}},{"cell_type":"markdown","source":"We introduce the following loss function (the first term addresses ROC-AUC, the second one is responsible for minimizing variance in ROC-AUC scores):\n$$\n    L = L(U_1, U_2, \\dots, U_m) = \\frac{\\alpha}{m} \\sum_{i=1}^{m} U_i^2 + \\frac{\\beta}{m} \\sum_{j=1}^{m} (U_j - \\bar{U})^2 ,\n$$\nwhich is a function composition since $U_1, U_2, \\dots, U_m$ are functions of $y_1, y_2, \\dots, y_l$ and $z_1, z_2, \\dots, z_l$:\n\\begin{align*}\n    U_1 &= U_1 (y_1, z_1, y_2, z_2, \\dots, y_{g_1}, z_{g_1}) \\\\\n    U_2 &= U_2 (y_{g_1 + 1}, z_{g_1 + 1}, y_{g_1 + 2}, z_{g_1 + 2}, \\dots, y_{g_1 + g_2}, z_{g_1 + g_2}) \\\\\n    \\dots \\\\\n    U_m &= U_m (y_{g_{m-1} + 1}, z_{g_{m-1} + 1}, y_{g_{m-1} + 2}, z_{g_{m-1} + 2}, \\dots, y_{l}, z_{l}) .\n\\end{align*}","metadata":{}},{"cell_type":"markdown","source":"We cannot use the expression $1 - \\text{ROC-AUC}$ directly as $U_i$ (ROC-AUC is not differentiable), so we will use the approximate number of inversions (the number of negative-positive pairs in which the negative sample gets larger score than the positive sample) in the $i$-th group as $U_i$:\n$$\n    U_i = \\frac{1}{n_i p_i} \\sum_{n=1}^{n_i} \\sum_{p=1}^{p_i} R (z_n, z_p) , \\quad i = 1, 2, \\dots, m,\n$$\nwhere $R = R(z_n, z_p)$ can be expressed by the equation:\n$$\n    R (z_n, z_p) =\n    \\begin{cases}\n        \\frac{1}{(1 + \\gamma)^2} \\cdot \\bigl( - (z_p - z_n - \\gamma) \\bigr)^2 , & z_p - z_n < \\gamma\\\\ \n        0, & \\text{otherwise}       .\n    \\end{cases}\n$$","metadata":{}},{"cell_type":"markdown","source":"The idea is taken from https://cdn.aaai.org/ICML/2003/ICML03-110.pdf. Also, we apply a slight modification to normalize $U$ and $R$ to the interval $[0; 1]$.","metadata":{}},{"cell_type":"markdown","source":"In order to use $L$ to train a classifier, we need to calculate the first and the second derivatives:","metadata":{}},{"cell_type":"markdown","source":"$$\n    \\frac{\\partial{L}}{\\partial{U_i}} = \\frac{2 \\alpha U_i}{m} + \\frac{2 \\beta (U_i - \\bar{U}) }{m} ,\n$$","metadata":{}},{"cell_type":"markdown","source":"$$\n    \\frac{\\partial^2{L}}{\\partial{U_i^2}} = \n    \\frac{2 \\alpha}{m} + \\frac{2 \\beta (m-1) }{m^2} .\n$$","metadata":{}},{"cell_type":"markdown","source":"Now we can move on to the calculation of $U_i$ derivatives.\n\n$$\n    \\frac{\\partial{U_i}}{\\partial{z_n}} = \\frac{1}{n_i p_i} \\sum_{p=1}^{p_i} \\frac{\\partial{R}( z_n, z_p)}{\\partial{z_n}} \n$$\n$$\n    \\frac{\\partial{U_i}}{\\partial{z_p}} = \\frac{1}{n_i p_i} \\sum_{n=1}^{n_i} \\frac{\\partial{R}( z_n, z_p)}{\\partial{z_p}} \n$$\n$$\n    \\frac{\\partial^2{U_i}}{\\partial{z_n^2}} = \\frac{1}{n_i p_i} \\sum_{p=1}^{p_i} \\frac{\\partial^2{R}( z_n, z_p)}{\\partial{z_n^2}} \n$$\n$$\n    \\frac{\\partial^2{U_i}}{\\partial{z_p^2}} = \\frac{1}{n_i p_i} \\sum_{n=1}^{n_i} \\frac{\\partial^2{R}( z_n, z_p)}{\\partial{z_p^2}} \n$$\n$$\n    \\frac{\\partial{R} (z_n, z_p) }{\\partial{z_n}}  =\n    \\begin{cases}\n        \\frac{2}{(1+\\gamma)^2} \\cdot ( z_n - z_p + \\gamma)  , & z_p - z_n < \\gamma\\\\ \n        0, & \\text{otherwise}       .\n    \\end{cases}\n$$\n$$\n    \\frac{\\partial{R} (z_n, z_p) }{\\partial{z_p}}  =\n    \\begin{cases}\n        \\frac{2}{(1+\\gamma)^2} \\cdot ( z_p - z_n - \\gamma)  , & z_p - z_n < \\gamma\\\\ \n        0, & \\text{otherwise}       .\n    \\end{cases}\n$$\n$$\n    \\frac{\\partial^2{R} (z_n, z_p) }{\\partial{z_n^2}}  =\n    \\begin{cases}\n        \\frac{2}{(1+\\gamma)^2} , & z_p - z_n < \\gamma\\\\ \n        0, & \\text{otherwise}       .\n    \\end{cases}\n$$\n$$\n    \\frac{\\partial^2{R} (z_n, z_p) }{\\partial{z_p^2}}  =\n    \\begin{cases}\n        \\frac{2}{(1+\\gamma)^2} , & z_p - z_n < \\gamma\\\\ \n        0, & \\text{otherwise}       .\n    \\end{cases}\n$$","metadata":{}},{"cell_type":"markdown","source":"Using the chain rule we finally get:\n\n$$\n    \\frac{\\partial^2{L}}{\\partial{z_n^2}} = \\frac{\\partial^2{L}}{\\partial{U_i^2}} \\cdot \\left( \\frac{\\partial{U_i}}{\\partial{z_n}} \\right)^2 + \\frac{\\partial{L}}{\\partial{U_i}} \\cdot \\frac{\\partial^2{U_i}}{\\partial{z_n^2}}\n$$\n$$\n    \\frac{\\partial^2{L}}{\\partial{z_p^2}} = \\frac{\\partial^2{L}}{\\partial{U_i^2}} \\cdot \\left( \\frac{\\partial{U_i}}{\\partial{z_p}} \\right)^2 + \\frac{\\partial{L}}{\\partial{U_i}} \\cdot \\frac{\\partial^2{U_i}}{\\partial{z_p^2}}\n$$","metadata":{}},{"cell_type":"markdown","source":"Now we have everything to implement our custom loss for the given classification problem. The implementation is available in the class below.","metadata":{}},{"cell_type":"code","source":"def _validate_labels_preds(labels, preds):\n    # Check y_true and y_pred\n    assert len(labels.shape) == len(preds.shape) == 1\n    assert labels.shape[0] == preds.shape[0]\n\n\ndef _split_labels_into_neg_pos(labels):\n    # Return masks for negative and positive samples\n    assert len(labels.shape) == 1\n    # assert np.all(np.in1d(labels, [0, 1]))\n\n    neg_cls_mask = labels <= 0.5\n    pos_cls_mask = ~neg_cls_mask\n\n    assert len(neg_cls_mask.shape) == len(pos_cls_mask.shape) == 1\n    assert neg_cls_mask.shape[0] == pos_cls_mask.shape[0] == labels.shape[0]\n    return neg_cls_mask, pos_cls_mask\n\n\ndef _split_labels_into_groups(samples_n, approx_group_size):\n    # Return (begin_index, end_index) for every group\n    assert samples_n >= 1\n    assert approx_group_size >= 1\n\n    idx = np.arange(samples_n)\n    groups_n = max(1, samples_n // approx_group_size)\n\n    idx_groups = np.array_split(idx, groups_n)\n    assert len(idx_groups) >= 1\n\n    end_idx_groups = np.cumsum(\n        np.array([len(idx_group) for idx_group in idx_groups])\n    )\n    begin_idx_groups = np.concatenate([\n        np.array([0]),\n        end_idx_groups[:-1]\n    ])\n\n    groups = np.array([begin_idx_groups, end_idx_groups]).T\n\n    assert len(groups) == groups_n\n    assert 1 <= groups_n <= samples_n\n    return groups\n\n\nclass StableRocAucLoss:\n    \"\"\"Maximize ROC-AUC and minimize its variance simultaneously.\"\"\"\n\n    def __init__(self, approx_group_size, alpha=0.8, beta=1.0, gamma=0.25):\n        self.approx_group_size = approx_group_size\n        self.alpha = alpha\n        self.beta = beta\n        self.gamma = gamma\n\n    def validate(self):\n        \"\"\"Throw an exception in case of any inconsistency.\"\"\"\n        assert self.approx_group_size >= 2\n        assert 0.0 <= self.alpha <= 1.0\n        assert 0.0 <= self.beta <= 1.0\n        assert 0.0 <= self.gamma <= 1.0  # [0.1; 0.7]\n\n    def compute_value(self, labels, preds):\n        \"\"\"Return value of the loss at the specified point.\"\"\"\n        self.validate()\n        _validate_labels_preds(labels, preds)\n\n        if labels.shape[0] < 1:\n            return 0.0\n\n        intermediate_losses = self._compute_intermediate_losses(labels, preds)\n        groups_n = intermediate_losses.shape[0]\n        assert groups_n >= 1\n        assert intermediate_losses.shape == (groups_n,)\n        assert len(intermediate_losses.shape) == 1\n\n        mu = np.sum(intermediate_losses / groups_n)\n        term1 = np.sum(1.0 / groups_n * np.square(intermediate_losses))\n        term2 = np.sum(1.0 / groups_n * np.square(intermediate_losses - mu))\n        return self.alpha * term1 + self.beta * term2\n\n    def compute_grad_hess(self, labels, preds):\n        \"\"\"Return the first and the second derivatives.\"\"\"\n        self.validate()\n        _validate_labels_preds(labels, preds)\n\n        if labels.shape[0] < 1:\n            return np.zeros(labels.shape), np.zeros(labels.shape)\n\n        groups = _split_labels_into_groups(\n            samples_n=labels.shape[0],\n            approx_group_size=self.approx_group_size\n        )\n\n        intermediate_losses = self._compute_intermediate_losses(labels, preds)\n        outer_grad_hess = self._compute_outer_grad_hess(intermediate_losses)\n        inner_grad_hess = [\n            self._compute_inner_grad_hess(\n                labels=labels[group[0]:group[1]],\n                preds=preds[group[0]:group[1]]\n            ) for group in groups\n        ]\n\n        assert len(outer_grad_hess) == 2\n        assert len(outer_grad_hess[0]) == len(outer_grad_hess[1])\n        assert len(outer_grad_hess[0]) == groups.shape[0]\n        assert len(inner_grad_hess) == groups.shape[0]\n\n        grad = np.concatenate([\n            outer_grad_hess[0][i] * inner_grad_hess[i][0]\n            for i in range(groups.shape[0])\n        ])\n        hess = np.concatenate([\n            outer_grad_hess[1][i] * inner_grad_hess[i][0]**2\n            + outer_grad_hess[0][i] * inner_grad_hess[i][1]\n            for i in range(groups.shape[0])\n        ])\n\n        assert grad.shape == hess.shape == labels.shape\n        return grad, hess\n\n    def _compute_intermediate_loss(self, labels, preds):\n        # Return U_i\n        self.validate()\n        _validate_labels_preds(labels, preds)\n\n        neg_cls_mask, pos_cls_mask = _split_labels_into_neg_pos(labels)\n        neg_n = np.count_nonzero(neg_cls_mask)\n        pos_n = np.count_nonzero(pos_cls_mask)\n        if neg_n < 1 or pos_n < 1:\n            return 0.0\n\n        u_i = np.subtract.outer(preds[pos_cls_mask], preds[neg_cls_mask])\n        u_i = np.square(np.fmax(self.gamma - u_i, 0.0))\n        # u_i = np.where(u_i < self.gamma, (self.gamma - u_i)**2, 0.0)\n        u_i = np.sum(u_i / neg_n / pos_n) / (1.0 + self.gamma)**2\n\n        assert u_i >= 0.0\n        return u_i\n\n    def _compute_intermediate_losses(self, labels, preds):\n        # Return U_1, U_2, ..., U_m\n        self.validate()\n        _validate_labels_preds(labels, preds)\n\n        groups = _split_labels_into_groups(\n            samples_n=labels.shape[0],\n            approx_group_size=self.approx_group_size\n        )\n\n        intermediate_losses = np.array([\n            self._compute_intermediate_loss(\n                labels=labels[group[0]:group[1]],\n                preds=preds[group[0]:group[1]]\n            ) for group in groups\n        ])\n\n        assert intermediate_losses.shape == (groups.shape[0],)\n        return intermediate_losses\n\n    def _compute_outer_grad_hess(self, intermediate_losses):\n        # Return dL / dU_i and d^2 L / dU_i^2, i = 1, 2, ..., m\n        self.validate()\n        assert len(intermediate_losses.shape) == 1\n\n        groups_n = intermediate_losses.shape[0]\n        if groups_n < 1:\n            return np.zeros(groups_n), np.zeros(groups_n)\n\n        mu = np.sum(intermediate_losses / groups_n)\n        diff = intermediate_losses - mu\n\n        grad_term1 = self.alpha / groups_n * intermediate_losses\n        grad_term2 = self.beta / groups_n * diff\n        # grad_term3 = self.beta / groups_n * np.sum(diff / groups_n)\n        grad = 2.0 * (grad_term1 + grad_term2)\n\n        hess_term1 = 2.0 * self.alpha / groups_n\n        hess_term2 = 2.0 * self.beta * (groups_n - 1) / groups_n / groups_n\n        hess = np.repeat(hess_term1 + hess_term2, groups_n)\n\n        return grad, hess\n\n    def _compute_inner_grad_hess(self, labels, preds):\n        # Return dU_i / dz and d^2 U_i / dz^2\n        self.validate()\n        _validate_labels_preds(labels, preds)\n\n        neg_cls_mask, pos_cls_mask = _split_labels_into_neg_pos(labels)\n        neg_n = np.count_nonzero(neg_cls_mask)\n        pos_n = np.count_nonzero(pos_cls_mask)\n        if neg_n < 1 or pos_n < 1:\n            return np.zeros(preds.shape[0]), np.zeros(preds.shape[0])\n\n        factor = 2.0 / (1.0 + self.gamma) / (1.0 + self.gamma)\n        factor = factor / neg_n / pos_n\n\n        r = np.subtract.outer(preds[pos_cls_mask], preds[neg_cls_mask])\n        pos_grad = np.fmin(r - self.gamma, 0.0)\n        neg_grad = -1.0 * pos_grad\n        hess = np.where(r < self.gamma, factor, 0.0)\n\n        neg_grad = np.sum(factor * neg_grad, axis=0)\n        pos_grad = np.sum(factor * pos_grad, axis=1)\n        neg_hess = np.sum(hess, axis=0)\n        pos_hess = np.sum(hess, axis=1)\n\n        grad, hess = np.zeros(labels.shape), np.zeros(labels.shape)\n        grad[neg_cls_mask], grad[pos_cls_mask] = neg_grad, pos_grad\n        hess[neg_cls_mask], hess[pos_cls_mask] = neg_hess, pos_hess\n\n        return grad, hess","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.440557Z","iopub.execute_input":"2024-05-25T18:39:24.441659Z","iopub.status.idle":"2024-05-25T18:39:24.486442Z","shell.execute_reply.started":"2024-05-25T18:39:24.441620Z","shell.execute_reply":"2024-05-25T18:39:24.485211Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"To train lightgbm classifiers this custom loss was instantiated with the following parameters:","metadata":{}},{"cell_type":"code","source":"custom_loss = StableRocAucLoss(\n    approx_group_size=30000,\n    alpha=0.8,\n    beta=1.0,\n    gamma=0.25\n)","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.489505Z","iopub.execute_input":"2024-05-25T18:39:24.490346Z","iopub.status.idle":"2024-05-25T18:39:24.501265Z","shell.execute_reply.started":"2024-05-25T18:39:24.490306Z","shell.execute_reply":"2024-05-25T18:39:24.500171Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 2. Preprocessing","metadata":{}},{"cell_type":"markdown","source":"Our preprocessing consists of the following steps:\n\n1. Find and remove duplicated columns;\n2. Handle categorical features;\n3. Aggregations;\n3. Feature engineering;\n4. Feature selection.","metadata":{}},{"cell_type":"markdown","source":"#### 2.1 Find and remove duplicated columns\n\nIt is quite straight-forward. Moreover, we can ignore duplicated columns and not to extract them from the 1st and 2nd depth tables. It helps reduce memory consumption.","metadata":{}},{"cell_type":"markdown","source":"#### 2.2 Handle categorical features\n\nAfter some experiments, we decided to keep not more than 20 categories for one categorical column. Categories which are not in the top-20 for a given column are replaced with `CAT_RARE_VALUE`. Missing values are replaced with `CAT_NAN_VALUE` (these constants are initialized at the beginning of this notebook).","metadata":{}},{"cell_type":"markdown","source":"#### 2.3 Aggregations\n\nThis is a very important part of our preprocessing. The basic idea of one group aggregation is illustrated in the picture below.\n\n![agg.png](attachment:5263fc95-5ab4-4e73-a0a8-fd080cf78927.png)\n\nAs we can see, the number of missing values in every raw column is equal to 5, but in the aggregated column this number is decreased to 2. Besides, some values in raw columns can be incorrect. By applying aggregations we hope to minimize their effect.\n\nAlso, aggregations can be applied to a group of features with very high correlations.","metadata":{},"attachments":{"5263fc95-5ab4-4e73-a0a8-fd080cf78927.png":{"image/png":"iVBORw0KGgoAAAANSUhEUgAAA8wAAALQCAIAAADKKW5tAAAgAElEQVR4Aey9e1QUV9r/u/8WapLF0rjwdd6eJGN6kUQJolwkREgY4IyjxoyYi5cYEyThFzFioglqLpjjXZOjiRovM84gg4NhEgxjAgoRQQzSRJGLRBNQIy1DjCtxeBFfpOtEt2efmqqutruqutld/WW5XEXVrl27Ps+uZ3+62F1FRPyAAAiAAAiAAAiAAAiAAAgYSoAYWhsqAwEQAAEQAAEQAAEQAAEQECHZ6AQgAAIgAAIgAAIgAAIgYDABSLbBQFEdCIAACIAACIAACIAACECy0QdAAARAAARAAARAAARAwGACkGyDgaI6EAABEAABEAABEAABEIBkow+AAAiAAAiAAAiAAAiAgMEEINkGA0V1IAACIAACIAACIAACIADJRh8AARAAARAAARAAARAAAYMJQLINBorqQAAEQAAEQAAEQAAEQACSjT4AAiAAAiAAAiAAAiAAAgYTgGQbDBTVgQAIgAAIgAAIgAAIgAAkG30ABEAABEAABEAABEAABAwmAMk2GCiqAwEQAAEQAAEQAAEQAAFINvoACIAACIAACIAACIAACBhMAJJtMFBUBwIgAAIgAAIgAAIgAAKQbPQBEAABEAABEAABEAABEDCYACTbYKCoDgRAAARAAARAAARAAAQg2egDIAACIAACIAACIAACIGAwAUi2wUBRHQiAAAiAAAiAAAiAAAhAstEHQAAEQAAEQAAEQAAEQMBgApBsg4GiOhAAARAAARAAARAAARCAZKMPgAAIgAAIgAAIgAAIgIDBBCDZBgNFdSAAAiAAAiAAAiAAAiAAyUYfAAEQAAEQAAEQAAEQAAGDCUCyDQaK6kAABEAABEAABEAABEAAko0+AAIgAAIgAAIgAAIgAAIGE4BkGwwU1YEACIAACIAACIAACIAAJBt9AARAAARAAARAAARAAAQMJgDJNhgoqgMBEAABEAABEAABEAABSDb6AAiAAAiAAAiAAAiAAAgYTACSbTBQVAcCIAACIAACIAACIAACkGz0ARAAARAAARAAARAAARAwmAAk22CgqA4EQAAEQAAEQAAEQAAEINnoAyAAAiAAAiAAAiAAAiBgMAFItsFAUR0IgAAIgAAIgAAIgAAIQLLRB0AABEAABEAABEAABEDAYAKQbIOBojoQAAEQ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+qlv3PVqTZPJ1qsTaAcEQAAEQAAEQAAEQAAEzEQAJttM0cS1gAAIgAAIgAAIgAAIcEEAJpuLMKATIAACIAACIAACIAACZiIAk22maOJaQAAEQAAEQAAEQAAEuCAAk81FGNAJEAABEAABEAABEAABMxGAyTZTNHEtIAACIAACIAACIAACXBCAyeYiDOgECIAACIAACIAACICAmQjAZJspmrgWEAABEAABEAABEAABLgjAZHMRBnQCBEAABEAABEAABEDATARgss0UTVwLCIAACIAACIAACIAAFwRgsrkIAzoBAiAAAiAAAiAAAiBgJgIw2WaKJq4FBEAABEAABEAABECACwIw2VyEAZ0AARAAARAAARAAARAwEwGYbDNFE9cCAiAAAiAAAiAAAiDABQGYbC7CgE6AAAiAAAiAAAiAAAiYiQBMtpmiiWsBARAAARAAARAAARDgggBMNhdhQCdAAARAAARAAARAAATMRAAm20zRxLWAAAiAAAiAAAiAAAhwQQAmm4swoBMgAAIgAAIgAAIgAAJmIgCTbaZo4lpAAARAAARAAARAAAS4IACTzUUY0AkQAAEQAAEQAAEQAAEzEYDJNlM0cS0gAAIgAAIgAAIgAAJcEIDJ5iIM6AQIgAAIgAAIgAAIgICZCMBkmymauBYQAAEQAAEQAAEQAAEuCMBkcxEGdAIEQAAEQAAEQAAEQMBMBGCyzRRNXAsIgAAIgAAIgAAIgAAXBGCyuQgDOgECIAACIAACIAACIGAmAjDZZoomrgUEQAAEQAAEQAAEQIALAjDZXIQBnQABEAABEAABEAABEDATAZhsM0UT1wICIAACIAACIAACIMAFAZhsLsKAToAACIAACIAACIAACJiJAEy2maKJawEBEAABEAABEAABEOCCAEw2F2FAJ0AABEAABEAABEAABMxEACbbTNHEtYAACIAACIAACIAACHBBACabizCgEyAAAiAAAiAAAiAAAmYiAJNtpmjiWkAABEAABEAABEAABLggAJPNRRjQCRAAARAAARAAARAAATMRgMk2UzRxLSAAAiAAAiAAAiAAAlwQgMnmIgzoBAiAAAiAAAiAAAiAgJkIaDLZ9O9/ZsKBawEBEAABfghAY/mJBXrSOgn85Cc/IaLvv/++dV4+t1c9cuRIIjp27Bi3PWQdg8nmPEDoHgiAQOslAJPdemOPK+eDAEw2H3Fw7wVMtjsR/A4CIAACIKCIAEy2IlyoDAK6E4DJ1h2pLg3CZOuCEY2AAAiAQOslAJPdemOPK+eDAEw2H3Fw7wVMtjsR/A4CIAACIKCIAEy2IlyoDAK6E4DJ1h2pLg3CZOuCEY2AAAiAQOslAJPdemOPK+eDAEw2H3Fw7wVMtjsR/A4CIAACIKCIAEy2IlyoDAK6E4DJ1h2pLg3CZOuCEY2AAAiAQOslAJPdemOPK+eDAEw2H3Fw7wVMtjsR/A4CIAACIKCIAEy2IlyoDAK6E4DJ1h2pLg3CZOuCEY2AAAiAQOslAJPdemOPK+eDAEw2H3Fw70WrMNkhISFE9OWXX7pfPX4HARAAARDQRuDKlStE1LFjR23N4GgQAAH1BIKDg4noq6++Ut8EjvQBgf79+xNRUVGRD9rWs0lNb3wcPXo0Ee3du1fPHqEtEAABEAABl+u9994jopEjRwIGCICAvwgMHDiQiD777DN/dQDnbZZA+/btA+KPH00m+/XXXyeiOXPmNIsAO0EABEAABFQT+J//+R8imj9/vuoWcCAIgIBGAk8//TQRvf766xrbweE6EigsLCSin/70pzq26aOmNJnstLQ0IurVq9elS5d81D80CwIgAAKtkMAXX3zRq1cvItq5c2crvHxcMghwQmDfvn1E9Itf/OLOnTucdAndiIuLI6LnnnuOfxSaTPbdu3cfeughIho8ePDXX3/N/9WihyAAAiDAP4Gvv/566NChRPTb3/727t27/HcYPQQBExOIjIwkokcfffTevXsmvsxAubTZs2cT0X/9139VV1fz32dNJtvlcl26dKlz585ENHr0aHwDkv94o4cgAAKcE/jyyy8fffRRIurSpcsXX3zBeW/RPRAwPYGLFy926NCBiB5//PGbN2+a/np5vsA5c+awZy5t3ryZ534KfdNqsl0uV05ODrvm4ODgV199tbKyUmgdGyAAAiAAAl4SqKysfPXVV9nTDIgoLy/PywNRDQRAwKcEysrK2MBs3759oNg7nwIxvvEvvvhiyJAhzG0uXrzY+A6oO6MOJtvlcp05c2by5Mns4n/6059OnTo1LS0NC7XVhQRHgQAItCoC1dXVe/bsmTp16k9/+lOmopMnTz5z5kyrgoCLBQHOCdTV1Y0YMYKNUKvV+uKLL544cYLzPpuge7W1tX/5y18Eex0aGpqdnR1A16WPyWYXnJub++STT7IUZD+7dOkybty4d9555+TJk8XFxXa7vbq6uq6u7u9///v3338fQJjQVRAAARBQTeD777//+9//XldXV11dbbfbi4uLT548uWnTpkmTJoWHh4s188knn8zNzVV9IhwIAiDgUwIbN24MDQ0Vj9moqKi5c+euXbt2+/btGRkZubm5Nputqqrqq6+++u6773zaGfM1/tVXXxUVFe3Zs2fVqlVjx44Vo/71r3+9YsWKb775JrCuWk+Tza48KytrxYoV48aN69KlizgRsQ0CehH4yU9+EhQUFBwc/Otf/7pbt26RkZEDBw6MjY1NSEhYunTpe++9d/z48StXrqgYirW1tYcPH166dOns2bMnTZo0evTowYMH9+zZMyQk5Gc/+5le/Uc7ICAmEB4e/txzz61ZsyYrK0tF0uIQEAABgwmkp6fHxcX5cVL4xS9+ERYW9sADDwwZMuR3v/vdU089lZiYOG/evGXLliUnJ6elpWVlZdXW1irFYrfb9+/fv379+gULFowdO3bIkCERERGdO3dmS9LFqmXYdkREREJCwv79+5VeCyf19TfZ4guDZTEsEXGiZgk8/vjj27Ztq6qqEqel2zb+LGwWHXbqSOBnP/tZSEhIz549Bw8ePHr06EmTJv3rC/JLly49evSo0+l0S0j8CgIgECgEzp07t23btvnz548ZM2bAgAF+NKPN6lV4ePjUqVMzMzNleFZVVS1cuHD48OEWi6XZRny9s23btqGhod27dx8wYMDDDz88ZcqUN998c+fOnfn5+f/4xz9keh4QRb412QGBAJ0MOALNfvi+f//+zZs3L126dNq0aWPGjOnXr99PfvITQR0GDRr0/vvvu11+tX69AAAgAElEQVQpFji5AcGvIAACIAAC/BO4ffv23/72N4fDUV1dfeHChZKSktOnT+fl5aWmpi5ZsuSpp57q3bu3MP0R0b8M9NKlS92u69ixY/Hx8UK1//7v/46NjX3ttdfeeuutlJQU8dIXp9OJx4S70fPyV5hsL0GhWuARqK+vP3PmzJYtW8aMGcN0ZPTo0ez7uPiqbuCFEz0GARAAARBQQuDcuXMbNmwYNWoUmwFHjBjx7bffulwup9Mp/gbdSy+9hG9aK+GqoC5MtgJYqBq4BAoLC2NjY4lo0aJFBQUFTHHw0MnADSh6DgIgAAIg4CWBkpKSHj16ENEjjzxSXl7+i1/8gk2Cs2fPrqur87IRVFNBACZbBTQcEpAEjh8/TkS9evXq2bMnXp8UkCFEp0EABEAABFQR+Oqrr5ixDgsLI6Lw8HDcvVYFUtlBMNnKeKF2QBNgEkNEgwcP/vrrrwP6WtB5EAABEAABEPCegPDMt65du964ccP7A1FTNQGYbNXocGDgEWAmu1evXnhTUuAFDz0GARAAARDQQEAw2ZcvX9bQDA5VQAAmWwEsVA10Asxkz5kzJ9AvBP0HARAAARAAAUUE7r//fiJ65plnFB2FyloIwGRroYdjA4wAM9l79+4NsH6juyAAAiAAAiCgjQAz2Xv27NHWDI5WQAAmWwEsVA10Asxkf/nll4F+Ieg/CIAACIAACCgiwJaLnD17VtFRqKyFAEy2Fno4NsAIMJMdYJ1Gd0EABEAABEBAMwF2JxsLsjWDVNAATLYCWKga6ARgsgM9gug/CIAACICAOgIw2eq4aTkKJlsLPRwbYARgsgMsYOguCIAACICATgRgsnUCqaAZmGwFsFA10AnAZAd6BNF/EAABEAABdQRgstVx03IUTLYWejg2wAjAZAdYwNBdEAABEAABnQjAZOsEUkEzMNkKYKFqoBOAyQ70CKL/IAACIAAC6gjAZKvjpuUomGwt9HBsgBGAyQ6wgKG7IAACIAACOhHo1q0bEV29elWn9tBMywRgsltmhBqmIZCQkPDqq6+a5nJwISAAAiAAAiDgJYEtW7Y8//zzXlZGNV0IwGTrghGNgAAIgAAIgAAIgAAIgMCPBGCyf2SBLRAAARAAARAAARAAARDQhQBMti4Y0QgIgAAIgAAIgAAIgAAI/EgAJvtHFtgCARAAARAAARAAARAAAV0IwGTrghGNgAAIgAAIgAAIgAAIgMCPBGCyf2SBLRAAARAAARAAARAAARDQhQBMti4Y0QgIgAAIgAAIgAAIgAAI/EgAJvtHFtgCARAAARAAARAAARAAAV0IwGTrghGNgAAIgAAIgAAIgAAIgMCPBGCyf2SBLRAAARAAARAAARAAARDQhQBMti4Y0QgIgAAIgAAIgAAIgAAI/EgAJvtHFtgCARAAARAAARAAARAAAV0IwGTrghGNgAAIgAAIgAAIgAAIgMCPBGCyf2SBLRAAARAAARAAARAAARDQhQBMti4Y0QgIgAAIgAAIgAAIgAAI/EgAJvtHFtgCARAAARAAARAAARAAAV0IwGTrghGNgAAIgAAIgAAIgAAIgMCPBGCyf2SBLRAAARAAARAAARAAARDQhQBMtiTGO3fuTJ06lST+hYWFjR8//sMPP7xz545kEwFVcPHixcjIyPj4+Js3bwZUx/Xp7A8//HDo0KERI0YQUVBQ0NKlS7///vtmm75y5UpycrIQd8O41dXVzZ8/PywsjIh69+5dXl7ebPe07Lxx48b69etv3bqlpRHdj7179+6sWbOIKC4uzuFw6N4+GgQBEAABdQTu3LmTlZWVkJAQHh7O5o4RI0YkJyd/9dVX6hr0+1FJSUlEtGvXLtaTgoICIkpKSlLaMbd2pA7ftWuXuvalGuRtP0y2ZEQEk927d++R//kvLi6OeR0imj59+u3btyVbCZwCw8win0g+/fTTf3lrIho0aNDIkSM3b97cbD8vXbrUt2/fqVOnGmyyb9++nZCQQERhYWFxcXFPPfXUtWvXmu2h6p23bt0aNWoUh39l2e327t27d+rUiYgyMjJUXyAOBAEQAAG9CDQ0NBQWFvbv35/diGM+YciQIWwead++/bp164RpQq+TGtCOmzmGydbIHCZbEqBgsoU/6cRVf/jhh+zs7MjISCLavn27uChAt1u5yWZ/T7/11lsNDQ0yEWSUjDfZN2/ejI+PHzhwYHV1tUz3tBSxU3Bosrds2UJECxcu7Nq1a0JCgjn+ptUSKRwLAiDgdwInTpwICwsLCgp67bXXrl+/Lkwcd+7cSU1NZTe2Z8+eHXB65WayVXP2sh3cyVZNOOAPlDfZ7PLY9P/SSy/94x//CPQLhskWf0YmFU3/mmyfOmA+TfatW7fGjBkTGRlps9kSEhI6depUVFQkFR3sBwEQAAEDCFRVVQ0cODAoKCgtLU2w1+LzVlZWDhw4kIi2bt3abAVxZa62vTTHLfbZy3ZgslskadoK3pjsvLw8InKzPjdv3nz33Xcffvhh9rFRWFjYiy++eOrUqR9++MHlcv3zn/+cM2dOp06dbDabmB1Ltejo6KtXrwr7mcNw2ymUChvXrl1btmxZ79692XKCF1988fz5824Du8U6biabWa7IyMiLFy8KJ3K5XJ77Wc937dp148aNRYsWsYU0I0aM+OSTT3744Yf6+vqMjAy21jk8PHzVqlXffPON0KD42BUrVrC//gcOHLhp0ya3GwB3797dt2+fQHXYsGHJycleLh+Xv3bWB/Hae7eACr1lqiHUZNUEbrW1tfv27WNXGhYWNmXKlCtXrgjHChtXrlyZO3cuu9Lw8PC5c+c2W02oz4ALJ2UbwqcrYixBQUEPP/zwvn377t69KxzONu7evZudnT158mQWHVYzLS1NgOwGQYi7lFC67WeHp6amfvTRRw888ED79u1feOGFmpoadvYbN26sWLFCPj/dOiz8ypbxsI8Otm/fTkSLFy++d++eUAEbIAACIGAkgXv37i1evJh9vFZfXy916qysrKCgIPHHj97Pdy6Xy0ttdzu7cBuotLR0zJgxRDRixIjs7GxWzZs23bS92eUi165dW7Vq1bBhw9h8xCayyspKsesQ2qmqqnr++efb//vf888/72ZOGBO3Nd/e9NPtwrn9FctFJEPTosmur69fuHAhEc2ZM+ef//wna0j4+5WtnRVWb4v/5GVZtWXLFuHczHmzr0189tlnwn6bzdapU6dZs2Z52iahTl5eHnNs7IzMyohP19DQkJGRwdxVeHj4yJEjhTqrV68WNEIwi8y5epppdkbP/exy5s6dO3DgwPbt2w8fPlxof+/evStXrmQLnYXFajNmzBBWqrFj58+fHxMTExQUNOTf/9gfJxMmTBC+gXf79u3p06cTEWtfoPrII48ITk4AIt7w5toPHDggMGHr6l566SXh1OLW3nnnHXYVDDWrxrhFRkY+8cQT7CuJQvfCw8PFf0pJdSYsLCwjI0MsT+KT3rp166WXXho+fDgTqeHDh48cOfLAgQMul+vGjRvPPfccS5shQ4awOkT03HPP3bhxQ2hEoMcICxcr/kbBgQMHxKcYP378F1984XK5BKEUWmMbbvtZHMePHx8WFjZo0KAhQ4Y89dRTjOGpU6fYqioGbdCgQazDycnJQu65NS78KkxmbCl2dXX1wH//892aGeHU2AABEACBZgnU1tbGxsb+//bO/jeK44zj8w/sL/dDpPsBrVQJnSyEThaKbEGtYHE6WyLCjsSJoIZ3E4SbQm1IKNSVKywBaYKxgkQBWRWW40KpaXFEo2BehMkJC7BVHBKDQLwJjM1BHHAv5uhxO0U80qPJ7O55/VKym3z9gzW3OzvzzGd2Zr47++zMmG/VRkZGqH/mORGP4533vt1uHo1HpaWlb7zxRiQSicViM2fOvHjxovc0tb7dLrLJT0YIQXKCxx3TNM+cOcMmUTqLFi2KRCKaMFCn/4mJKrI9jmuckc8DENmuFZRHZOdyuYGBgR07dhiG8UI6JJNJSoW/Ttu3bx/L4kwms2vXLiHE/PnzSXbQh1yqX28qlYrFYvRQqIpvckfhJmq39datWyRPm5ubKUfLsk6dOmWaZkFBwddffy2l7OvrKygoCIVCf/nLXzhOMpmMRqOGYRw8eJCSnaTIFkKsXr2atF0ulyPLX3vtteLi4gsXLpCC/Oqrr6LR6PTp0/v6+ihTamDqtVLKy5cv04u2jo4OikbTmWvXruVZ8JGRkbVr176QazvzelF7LLuUkizJg1qlpNYdcRNCRKPRs2fP0vsKNk99ALt48aL58u/YsWMUjT37CwoKmAllpP2nZxt1ij2TyWzYsEFDl0qlNm7cqE2x0ATw8uXLWXlblvXll18WFBQYhsEPdfYsxiuyhRAHDhzgNzZSSsf7s7e3Nx6PG4Zx/PhxrZjaT1LV/CaHn0V/Gl9BaIXFTxAAgUAQ6O7uNgwjFotxj+pmdmNjoxBi8+bN9PLN43jnvW+358vjUVVVFQ2X2Ww2l8t5TzO/yL5//34sFguHwx0dHdTVSylHRkZoMKqurn769ClZRenQVA4Lg2PHjtEgSLqfR14W2d7ttJfdn0cgsl3rhUW29qZe/fn666+fOnWK5yCvXr26YMGCRCKhzYPSgy+/gqcHXJYOUkqasX733XenT5/OsowMYK3saCh/E6ZOClqW1djYWFJS8sUXX/Bc4LZt27SX7PQy66233iJrJymy1ZdiUkpahUNbDoJFEqtn6nS0a6WUVK5t27ZRqSlaS0uLCqGvr6+4uPiDDz5gnwf1rJTSe9m5qU9GZGsLX/T19U2fPp2frLjsra2tfMOopcvvBWFXwHTPzJ8/f3BwUC34gwcPFixYwLdNOp3+4IMPCgsLNVfm58+fb968WXVDt2cxXpFdXl7+8OFD1RgaYxoaGtT7U0rZ1dUVDofH/IrR7h9Cj1t806p5IQwCIAACr4DAoUOHhBBevsWikYsnZTyOdx77dseS0jj+4hmA5/4omvc084vsZDJZUlLy29/+lqcRKX0a79RpIEpnwYIF6mqGlmXt379f9fojJiyyvdvpWHwfHoTIdq0UFtnqEn7kCCGEqK2t7enpYS8R11ReniD5wiJbSknigz2lWlpawuHwv/71r1gsxkrl9u3bs2fPXrJkCU/farl8//33VVVV9uakRqM5cnXymM8+fvx44cKF7B0+SZHN/Qilby8yHdcaMDUw9fGXolEvxg2vo6NDCBGPx7u6ujwyp7djsVjMS9knL7JnzZp1/fp1ZiulpCcr7nTop/pkxZHpgYTlOB9XA3YFTM8hjY2NajQKb9u2TQhx6NAh+yn1iFYX9izGK7K1eqSHSb7B1KxpuUA7NDUOvRfSbm/6SsEwjJMnT6qREQYBEACBV0NAk855MqWYPAp4HO8m07fTOG7vWr2nqY0LdncRx/Jq+oHHDvXNPF145cqVFx4sPN4REx7rvdvpaIYPD0Jku1YKi2x1djOXy/H7jjx+tFLK0dHRoaGhs2fP7tq1iz7XU0X2+fPnw+FwQ0ODZVk0xxmLxe7cuVNdXc2ikJSlo4oio92ErFok+63PZ3luleaVtZhuiduPa40kv21aA3a8VkqpNexUKrVo0SJ6hxAKhRKJRFtb2+DgoDYlzEWjgFYi9axW9smLbO5GORdNs9KDPrmm/XDV9Tj5tKm3ByfCAS01nodWnwA5WXoU5G6LE8lms8PDw//+97/b2tr4I0i+vbUs6Cqtvjgp7bhjPdJzBTuCs3nxeJzd1rXpFk5fSkltRJu0tixr586dQoj8Hyqo6SAMAiAAAlNIgLo7bV7JMX0tpmM/qY13E+vbOXfHUW9caWp9uzYWc0ZSyv/85z937949efLk1q1b6SNIdRykdLq6utRL7GsnqEzGZaeWrG9/QmS7Vo2jyJZSWpbV1tZG3tiqmz8llE6n9+zZQy7F7Fgybdq0UCikqqiHDx+Wl5cvXLjw8ePHpEXIS4RmuA8dOkR3m+MsIFts17t8igOOTY7Pqs1Ji+mWuP242kg4ZXs0OqXmyNLWLgftDdsR7FtvvXXlyhXOVAtoJdLOOlrCilOLzD8pTbV7dcuFCHCnQyXiW8IeUG8Pzo4DWmp8c9rT4SNMNZfLnT59urKykr4opQihUGjatGlT6y7COZLZRIbtcQy4iWx29XG8SgjB/jCMCAEQAAEQeAUEJumTrfWTmsgeV99uL6zjeDSuNLWR0T4W02pRtNwC98+/+MUvXnzhw+Mdz2Tbe3hNG6j6YVx22svuzyMQ2a71wvVtF16jo6PvvfeeEKK0tPTevXucBC/jEIlEqqurm5ubk8lk6uVfWVmZqqJIQ9CkNTkhUS60JmB9fX0qlSp/+ac5uXJe9idC9RSHHZscneXZXPJa0WJqLYETtB9XG0meaHRKa8CO12qdDqdJTziDg4MdHR1LliwJhUL5t9rWSqSmo5Wd5b69rtWrpJSU5gRENruaaf76WvpuPzWRzfaP6RPCz4T0BuDjjz/u7Oy8efPms2fPtLrQsiBLtDhsnnbcsR7J38n+4pITyROgz2scJ/7j8Tj17zvzfvOaJ3GcAgEQAIEJE6B5sQmvLpJfZHvv2x3tdxz1xpWm1rdrIntoaKiiooLW0Xr//ffb2touXLgwPDxsz5fScZvJ5nFBHTvGZadj8X14ECLbtVLyiGwpZX9/Py1Mpn7URQ4eFRUVQ0NDarrkGK2KbCllZ2cnzSPu3buXZ6xJlyxcuPDEiRPsT6ImpYbJ51VzWqUInZ2dhYWFH3/88YMHD8rLy9kFRb2c3Fs5a62R2MU0XUsWqmVRGwmn73a51oAdr80jsjl9KeXdu3fnz5//Qmfbn5UpGr0u8FL2VyCy7dzUsowZtitg/qYwv88M5astrkSPK/X19RObyeaukJ9JHOuRnP4d788xy3vkyBEhhPowo15CZ2Ox2P3799XjCIMACIDA/5sAv2fbsGGD9v2fmvWpU6fC4bD6Wb9jP2kf7zz27WpeHNbGcT7uPU1tjNZENqVTXV2trTdAntb2mWxtuQJa7kxdEkBj4t1OLprPAxDZrhWUX2TzR7KmafJiNHR32p9TaR0PVZhKKUn9rF69esWKFfyxI+nmaDT67rvvCiH4y0g3K+mO3LJli7p6Ay/gfeTIEe4O3FYX4ay1xun2VaW9LFojIVOnUGQ/ffq0rq5u9uzZ2voYXEFuItt72V+ByM5kMuvWrXPcAKyrq2vGjBkrV64c862F2oWRy7L2LoU+BlizZk1xcfHf//537r7VC6mC7t27V1paOqbIbmlpEULQxwN8E9qvdbwH2H9auz/phcDrr79eWVl57do1TpYDvBSmtmALR6B5bm3tGj6LAAiAAAj8Xwnwjo+tra28kp2aI++Yoe746NhPci/NysFj365mx2FtHOfj3tPMI7J5zOXpFUqf5ZA60FA6S5cuffLkCZthWVZTU5M6pmhMvNvJafo8AJHtWkFu9xNfkEqlaEclvo3odikrK+M9/LLZ7PHjx2nOWxPZrLq07WxoaQghxNy5cwcGBjg7x4B9HeJcLnf48GHTNHnpHI9rRWuN07KshoYGWob522+/pbnPCxcukLu5WhatkZCdUyiypZS0lFtFRcWdO3cofV4OfM6cOUzbjshj2ccrstUVUjVubIN97jmZTNISoa2trbxg+TfffBOPx4UQTU1Neeak7amxz9LixYuZwOjoaFNTk2EYjIXMC4fDx44do/Qty7px48bixYvJnY67S8pi5syZqpt7MpmkTct4V51UKrV69WrtWsd7QErJQ9FHH33EMx93796lPRrc5oFoIXl1BoipUoAfn8ZcBFC7ED9BAARAYEoI0J4shmFs2rRJfaU2Ojr62Wef0aCvzfi69ZPabLHHvt2xFG7jkfc084hsVgXLly8nVUDTOq2trbTbnV1kCyHq6+up82dxUlRUxNMrGhPvdjoW34cHIbJdK2VMkS2lpGld3tKF3ZVoRQVeQmHlypVvv/22uvEH5Uq3lzqbyG4kmvJ2tfLlksO8Rzdv5heNRs+dO0dXuW00aBhGnh0fWSHRVu2xWIzWrPj1r3+dSCResch+9OjRsmXLSNgVFxdzMfPvlUgPBl52u/QusskFhTzSli1bNjQ05Nap2WWxZVkHDx4kV3LaK4u2PxRCrF+/njWoY13bU5NSDg0NJRIJ3vGR77dIJMKecNlsljbdJJsZ3Zw5c373u98JIXgxcr7hI5FIZWVlf38/daD0+YG6H2c8Ht+0aZN632odpVoEbUdS3vgzkUhoXlV8u9L6IbxgvJoah2nCY0y3SI6PAAiAAAhMLYHe3t45c+bQwEQLPXH/FgqFGhsbeW9jytetn9REtse+3bEsbuOR9zTziGxylKWJNvpghpbGMgxj/fr1ZWVl7GnNHz7W1dXR1BJLCNM01d1F7Ey8jGuOZffnQYhs13phzcFTffaovDsRv7VXP7w1TXPVqlXnzp3jHRB3/vBTLVrWjb2iKX1qJON6FX7nzp2GhgaW2lu2bOEZX7b51q1bdXV1JJRN0/zNb35z6dIlderUsXEODAxs2bKFUp43b157e/t33323Zs2aVyyypZTpdLqtre3NN99kkVpbW8szuFxMx8CYZfcusqWU3d3d8+bNE0KQt7cjN/4sVX2yJ9F/48aN2tpaQkofI54+fdrxhaNaFkeRTSL48OHDtEYkyejt27dr+5BlMpn29nay2TCMFz76bW1t6XSabj91jbxr164lEglahIRdlUZHRw8cOEArNEUiEUqfekZuGvaOUjX+/v3727dvp3uPDGhvb3fzZaTHmDFXwibHKnVTAzVHhEEABEDgFRDIZDKdnZ3Lly/nLv3NN9/ct2+fugMLm+HWT9pFtse+nVPmgNt4RBFGR0fHHC/yi2zax7empoamriORSG1tbX9/fzabpd3N2MeP0kkmk729vTSsmKZZU1OjjdqOTLzYyUX2eQAi2+cVBPNAAARAAARAAARAAASCRwAiO3h1BotBAARAAARAAARAAAR8TgAi2+cVBPNAAARAAARAAARAAASCRwAiO3h1BotBAARAAARAAARAAAR8TgAi2+cVBPNAAARAAARAAARAAASCRwAiO3h1BotBAARAAARAAARAAAR8TgAi2+cVBPNAAARAAARAAARAAASCRwAiO3h1BotBAARAAARAAARAAAR8TgAi2+cVBPNAAARAAARAAARAAASCRwAiO3h1BotBAARAAARAAARAAAR8TgAi2+cVBPNAAARAAARAAARAAASCRwAiO3h1BotBAARAAARAAARAAAR8TgAi2+cVBPNAAARAAARAAARAAASCRwAiO3h1JqXM5XKfffbZvHnzhBCGYfzxj3/873//G4iSbN26VQjx6aefkrWPHj0qe/n36NEjOmJZVnd3d2Vl5YtyCSGqqqpGRkYGBwc3btxomqYQorCw8PLly34rbC6XO3HiREdHx5iG3bp1a/fu3aOjo2PG1CJo6LSz/PPq1avRaLSsrIyR8ikEQAAEQAAEfsIEPA4TP2ECfisaRLbfasSTPSdPniQNWlxcHI/H//znP3u6zAeRtC7ALrK//vrrgoICEtPxeLy+vn54eHj58uVCCNM0Y7HYokWL7ty544Oi/MCEQ4cOqQ8PPzin/Lh+/fqsWbPWrFkDka1QQRAEQAAEQGAKCGgj7BSkiCQmRwAie3L8fqSrP/300xdz2Dt37rQs60cyYYLZjtkFJJNJIcS6desymQzlQUK8qKjo5s2bE8z1/38Z1QjP0LtlSNPMENlufHAcBEAABEBgwgTGHGEnnDIunBgBiOyJcfuRr/Io6X5kK52yH7MLIJG9detWvto+282n/BPwWCMQ2f6pMlgCAiAAAj8xAmOOsD+x8vq/OBDZ/q+jH1hIYk4of+R9S03rzJkze/fuNV/+bdq0aXh4WEppWVZ/f39tbW0kEhFCRCKR2traW7duqemy+Pvuu+/++te/FhUVUcympqZ0Oi2l/Oqrr1asWBF6+bdixYpr166plzuGc7nc6dOnybvaNM3f//733377rdYFqAKa5LVSMjFjxoxf/vKX6hHVJYPSTyQSoVBICPHGG2/s2bOHrGV78mCRUt66dSs/FpbOqVRq+/btBLCoqEjNiIqgGqk+IbAlUkoyhmOqbtOPHj3at29feXk5eQGZprlq1apz587lcjlOgdFdu3ZNrYtLly6pLzQcfbK9sNKAONrAxiAAAiAAAiAwVQTS6XRbWxsNAYZhlJeXt7e38+tcysVLHB4m6BIawuxDknZ8MgJAvba9vZ0+FTNNs6amRpMZU8UqWOlAZAervuTRo0fj8XhhYSF7LVdVVQ0PD1PTeuedd1577bWSkpLi4uINGzZkMplsNrt7926SboWFhfF4nJRiKBQ6ePAgizNqJ4lE4p133hFCFBcXl5SU0FUbNmz44osvTNOMRCKxWIy+PiwqKsqvs7PZ7I4dO0hQFhcXl5aWhkKhiooK8q5mtwpVZPf09MTj8eLiYtL38Xi8srJy0aJFdG0oFCotLY3H40ePHpVSptPpLVu2cPpsWDweVw1zw2JZ1pEjR6gskUiEkZqmeeTIEcZCPdHGjRvnzJljGEbJyz/C8qtf/YqeYYaHh6uqqtQa+eSTTxzvqk8++YSoknM5VZyUsr+/n55q6DiXxTCMtrY2NobKsmjRokgkQjQoUy2aXWR7ZHXmzBn+tJQrwjCM5uZmtsGxXDgIAiAAAiAwYQJDQ0OJRIKWMVBHmerqap42unbtWjweF0JQ588DdCKRGBoa4qwnI7InJgBYPKxYsYJkCQ9hkUikp6eHbft5BiCyA1nvPMPK1lPTCofDJ06csF7+0Xojx48fNwwjEokcO3aMpkVzudzhw4dfTnabyWSSUqB2IoQoKiq6cOECiare3t6CggLDMMLhcHNzMz1Vj4yMVFVVCSH27t3LudsDR48eNQxDTW1wcMpmNq8AAAyNSURBVHDJkiUkix1FNiXixV3EsqympiYhxPz582/cuEEXptPpjz76yDCMpUuXPnnyhA66Ybl48SIRULF0dnZGo9GCgoK+vj66nDgLIVavXp1Kpejg5cuXSROra4nYa4Qia/+Js+qTnU6n6cFj3759PG+RyWR27dpFBSQpr06EV1dXkzG5XO7YsWNUkIsXL1Jemsj2yIrMME3z1KlTVPuWZZ06dco0TZ97w2uE8RMEQAAEAkQgk8ls2LBBG2Xu3btXUVEhhGhubpZSPnnyZOnSpUII7vyllDykvvfee/wl/WRE9sQEgKN4GBkZWbt2rRCipqbm2bNnAaqOKTcVInvKkb6KBO2SjpqWqt5oulebOSbjLMvav3+/+n0ht5MjR45wAZ4+fVpdXa1Go1MdHR35Gw8pNsMwjh8/zqlJKW/evEnydJIi+/bt27Nnz45Go/39/Wr6o6Oja9asMQzj5MmTdNwRy7Nnz2pqaoQQra2t2hwtgf3DH/7w/PlzKSX9tKvMvXv3CiG2bdvGudtrhE+pAbvIvnr16oIFCxKJBItpij8wMDB37txoNHr16lU6QmVZsGDBgwcPOE2uSrZZE9keWdErhVgsxs8SUsrnz59v2bKlpKSEH8Y4XwRAAARAAAQmT6Cvr2/69OmlpaX37t1TUzt//vzs2bO3bt367NkzWk9M6/yllPfu3SstLQ2Hw+fPn6drJymyJyAAHMWDlJLKNX/+fG1oU8v4cwhDZAeylu2SjpqWKvuklFeuXJk5c+bcuXMHBga0cpL2mj179u3bt6WU1E5USUfxtRZLB2myWRP0avq0UJ2m2Ei0bd68WfWrVt1F1MRVHzJ7nM7OTjeVT0vpMQdHLKRfuex2y7lfIM7V1dVPnz5Vo1EuqpH2GlHjc9gusvmUFqBSqzVCZbG/QKBaZps1ke2R1ePHjxcuXGgYxo4dOwYHB7VnD802/AQBEAABEJgSAi0tLUKIhoYGt173xfH6+nq3t8eNjY1CiMbGRjJGG7JpYFKHKoqmHZ+MAKBrZ82adf36dRUIyQD10yP17M8nDJEdyLq2SzqtaVGp8qhhElXhcJhcpjRlxlDGmyxd2N3dbRiGowqnDmWSM9k0kUy+1PEf/pFLN2ftaD89YbOTt5oA+X+ztNV6IsZi92mx1whHVgPEmc1TT0kpR0dHh4aGzp49u2vXLvoChi1hd5Guri7tKk2Oa1XpndXhw4fpE1Lyq6urqzt//vzP/E2fhho/QQAEQGBqCTgOUmoW9IZWCOH4RlF7sayl5jaEace1UYNz11Kj45qucLvWPjvGyf6sAhDZgaxuu6Tz0hjUomrt1q2djDdZykJrhGq+muX2dmjXr/Y4ZBUv02EPsIrNY7/9Kj7C0lbribggdiO1cnFMLUCc2Tw6m06n9+zZQ440bMO0adNCoRBbwiLb3s/mF9neWVmWdenSJVq3hM0wTXPPnj3sLK4VBz9BAARAAAQmQ8BxkFIT1AZr9ZSUUhtttdTchjDt+GQEgNu19oFbs/xn8hMiO5AVbZd0WtOiUmnNTy0qzWRPnz6dPvJzayfjTZayoJnsqqqq77//Xs2UvZwnOZNNL8jYJ0TLQv3paH9PT084HGb/CjW+FtZ6Ij47hSI7nU6T43skEqmurm5ubk4mk6mXf2VlZXaR7TaTzW/rtKr0zopL9+zZs56eng8//JB1P318wxEQAAEQAAEQmBICjoOUmnJ+kU0z2Zs3b6bviLTU3IYw7bg2anDuWmp0XNMVbtdCZBMuiGy+nYIU8Ciy6e539MmmbxDZL9mtnXhpY3Zw5PBtz5d9yyYpsqlb0eaD7Wbw7C9nR3H4W0D+ptDxWn4ksDu0TaHIprJUVFSoyzBJKVOpVCwWs4vslpYWzVrt+xKtKr2z0pKVUuZyuQMHDgghvKC2X44jIAACIAAC+QmQR5/dJ3tgYKC8vHzZsmUPHjxoaGhw9Mm2LGvnzp0T8MmmyRce2rRRgw32IgDcroXIJowQ2Xw7BSngUWTz8nCayuQlKVg8ubUTL23MDi6Tyaxbt04IoX6qzJ9CT/7DR3pCUNfaIxueP3++bdu2wsLCpqYmWq/Q0X42b//+/dq3Jl1dXTNmzFi5cuXDhw9fjcgmC7mzY5i09qJdZKsLFNJOQ7SaIffRWlV6ZNXT01NaWlpTU6N5hmiTFmweAiAAAiAAApMnQG9W7auL0BCwbt26TCaTf3URwzDYjVAb8rq6uuyzJLwgII872qjBhdJSo+PaoOB2LUQ24YLI5tspSAGPIltKOa51su0fAntpY47gksmkaZrRaDSZTJKQTaVSq1evJmdfFv32dmifJLbHef78+Z/+9CchRDwe7+3tpfSz2ezf/vY3bdFoR/vJj41itra2kqy0LOubb76h1f6bmpooTe2dGpfUbiTFrK+v11Q7X0IB6o/UdVfowrKyMt4cK5vNHj9+PBqNCiHsIlsIUV9fTzsU8JLn6t5AWpfnkdX9+/djsZhhGC0tLdlslqzltU4ZiFYc/AQBEAABEJgMAcd1sq9cuRKPx8PhMPkHsiwe7zrZNByYpvn555/T2PRi7KC1SoQQENmTqTiP10JkewTlr2jeRfa4dnycQpFtWVZbWxvvNMm7NpaUlEx+JpuWAF+/fj1J9uLiYredLN1EtmVZBw8epMU0aJUSWpZECLF+/XreZMu7yE4mk8bLv5KSkg8//JDc4+w3zcOHD8vLy2n5jmXLlg29/KNNB2hHSd4ra+XKlW+//bZhGN3d3ZQOlaWuro4eD2KxGO34qO4gw6sxqlWZTqe9sPrnP/9JOz7SxpNUZUKIJUuWqItn2wuFIyAAAiAAAhMmwDs+qrs5Goaxe/dunvKY2I6PPMlCGznz8lnkfwKRPeEq834hRLZ3Vj6K6V1kk0fBpUuX1q1bRxuqRyKRuro6njelUmnTn1xUR5GqvS3iyFpAXa3CMIxEItHb29va2jolIltKmc1mT548mUgkWCvX1tZq5XK0n+y0LOvGjRu1tbW8z3wikTh9+jT5mVAc7yI7m83u3buXklqyZMnIyIhGg392d3fPmzdPCMFfnaZSqe3bt9O1pmmuWrXq3LlzuVyOfPV27txJMxBUlmQy2dvbm0gkDMMwTbOmpkYrsmNVemFlWVZ/f39tbS3v1l5eXt7e3q45kHBBEAABEAABEJgSAul0uq2tjYaGUCi0ePHiL7/8Uh2MaGqpubmZVnc1DKOysvIf//iH1j/bh7xsNvv555/TVTxkaC9jHUcNt4+aNAHgdq39FfSUgApcIhDZgasyGAwCIAACIAACIAACIOB3AhDZfq8h2AcCIAACIAACIAACIBA4AhDZgasyGAwCIAACIAACIAACIOB3AhDZfq8h2AcCIAACIAACIAACIBA4AhDZgasyGAwCIAACIAACIAACIOB3AhDZfq8h2AcCIAACIAACIAACIBA4AhDZgasyGAwCIAACIAACIAACIOB3AhDZfq8h2AcCIAACIAACIAACIBA4AhDZgasyGAwCIAACIAACIAACIOB3AhDZfq8h2AcCIAACIAACIAACIBA4AhDZgasyGAwCIAACIAACIAACIOB3AhDZfq8h2AcCIAACIAACIAACIBA4AhDZgasyGAwCIAACIAACIAACIOB3AhDZfq8h2AcCIAACIAACIAACIBA4AhDZgasyGAwCIAACIAACIAACIOB3AhDZfq8h2AcCIAACIAACIAACIBA4AhDZgasyGAwCIAACIAACIAACIOB3AhDZfq8h2AcCIAACIAACIAACIBA4AhDZgasyGAwCIAACIAACIAACIOB3AhDZfq8h2AcCIAACIAACIAACIBA4AhDZgasyGAwCIAACIAACIAACIOB3AhDZfq8h2AcCIAACIAACIAACIBA4AhDZgasyGAwCIAACIAACIAACIOB3AhDZfq8h2AcCIAACIAACIAACIBA4AhDZgasyGAwCIAACIAACIAACIOB3AhDZfq8h2AcCIAACIAACIAACIBA4AhDZgasyGAwCIAACIAACIAACIOB3AhDZfq8h2AcCIAACIAACIAACIBA4AhDZgasyGAwCIAACIAACIAACIOB3AhDZfq8h2AcCIAACIAACIAACIBA4AhDZgasyGAwCIAACIAACIAACIOB3AhDZfq8h2AcCIAACIAACIAACIBA4AhDZgasyGAwCIAACIAACIAACIOB3AhDZfq8h2AcCIAACIAACIAACIBA4AhDZgasyGAwCIAACIAACIAACIOB3AhDZfq8h2AcCIAACIAACIAACIBA4Av8D14aG6Rw/qn4AAAAASUVORK5CYII="}}},{"cell_type":"markdown","source":"#### 2.4 Feature engineering\n\nA tree-based model compares a value of one feature with a threshold in every tree node. However, in some situations difference or ratio of two features might be important. In order to help our models deal with such cases we decided to construct new features. Every new feature is the result of combination of exactly two original features. For example, the ratio `maxpmtlast3m_4525190A` / `maininc_215A` (maximum payment made by the client in the last 3 months divided by client's primary income amount) seems to be useful at first glance. Our feature selection procedure confirmed that this feature is useful for our models. Let's discuss this procedure in more detail.","metadata":{}},{"cell_type":"markdown","source":"#### 2.5 Feature selection\n\nOur feature selection procedure includes two steps:\n\n1. Select the most important features (primary features in the `Preprocessor` class);\n2. Add additional features, which also seem to be useful for our models (secondary features).\n\nPrimary features were selected based on feature weights of preliminary models, common sense, ROC-AUC scores of individual features (predicted scores can be just values of one column, that is `y_pred = X[col]`, and there are some columns which provide ~0.7 ROC-AUC without any model), etc.\n\nSecondary features are those which improve ROC-AUC when they are added to the primary set (a simple lightgbm model with `depth=4` and `n_estimators=100` was used). In a nutshell, we have a primary set of features, and we try to add a new feature. If this feature improves ROC-AUC, it is included in the secondary set.","metadata":{}},{"cell_type":"code","source":"class Preprocessor:\n\n    def __init__(self):\n        self._cat_col2valid_values = dict()\n\n    def fit(self, X):\n        assert len(X.shape) == 2\n        assert X.shape[0] >= 2\n\n        self._cat_col2valid_values.clear()\n        for col in X.columns:\n            if X[col].dtype.name == 'category':\n                value_counts = X[col].value_counts(normalize=True, sort=True)\n                max_unique_values_n = 20\n                assert max_unique_values_n >= 2\n                valid_values = value_counts.index[:max_unique_values_n]\n                valid_values = set(valid_values.tolist())\n                valid_values = valid_values.union({CAT_NAN_VALUE, CAT_RARE_VALUE})\n                self._cat_col2valid_values[col] = list(valid_values)\n\n        return self\n\n    def transform(self, X):\n        assert len(X.shape) == 2\n        assert X.shape[0] >= 1\n\n        out_df = X.copy()\n        out_df = out_df.set_index('case_id')\n\n        target = None\n        if 'target' in out_df.columns:\n            out_df = out_df.sort_values(by=['WEEK_NUM', 'case_id'])\n            target = out_df['target']\n            out_df = out_df.drop(columns=['target'])\n\n        # ============================================================\n        # Preprocess categorical features\n        for cat_col, valid_values in self._cat_col2valid_values.items():\n            assert out_df[cat_col].dtype.name == 'category'\n            out_df[cat_col] = out_df[cat_col].cat.add_categories([\n                CAT_NAN_VALUE, CAT_RARE_VALUE\n            ])\n            out_df[cat_col] = out_df[cat_col].fillna(CAT_NAN_VALUE)\n            mask = ~out_df[cat_col].isin(valid_values)\n            out_df.loc[mask, cat_col] = CAT_RARE_VALUE\n            out_df[cat_col] = out_df[cat_col].cat.set_categories(valid_values)\n            assert out_df[cat_col].dtype.name == 'category'\n        # ============================================================\n\n        # ============================================================\n        # drop redundant columns\n        out_df.drop(columns=Preprocessor._get_redundant_columns(), inplace=True)\n        # ============================================================\n\n        # ============================================================\n        # aggregate some columns\n        agg2group = Preprocessor._get_agg2group()\n        agg_df = Preprocessor._get_agg_df(out_df, agg2group)\n\n        mode2group = Preprocessor._get_mode2group()\n        mode_df = Preprocessor._get_mode_df(out_df, mode2group)\n\n        out_df.drop(\n            columns=Preprocessor._get_groups_to_drop(agg2group, mode2group),\n            inplace=True\n        )\n        out_df = pd.concat([agg_df, mode_df, out_df], axis=1)\n        # ============================================================\n\n        # ============================================================\n        # construct new features (sometimes, e.g.\n        # feature1 - feature2 > 0 => higher chance of default)\n        mix_df = pd.concat([\n            Preprocessor._get_add_df(out_df, Preprocessor._get_add_pairs()),\n            Preprocessor._get_sub_df(out_df, Preprocessor._get_sub_pairs()),\n            Preprocessor._get_mul_df(out_df, Preprocessor._get_mul_pairs()),\n            Preprocessor._get_div_df(out_df, Preprocessor._get_div_pairs()),\n            # Preprocessor._get_compound_df(out_df, agg_df),\n        ], axis=1)\n\n        out_df = pd.concat([mix_df, out_df], axis=1)\n\n        # ============================================================\n        # select columns\n        out_df = out_df[\n            ['WEEK_NUM']\n            + Preprocessor._get_primary_features()\n            + Preprocessor._get_secondary_features()\n        ]\n        # ============================================================\n\n        # ============================================================\n        # handle NaNs - fillna?\n        # out_df['missing_values_n'] = out_df.isna().sum(axis=1)\n        # ============================================================\n\n        return out_df, target\n    \n    @staticmethod\n    def _get_redundant_columns():\n        return [\n            'deferredmnthsnum_166L',         # only 1 unique value\n            'commnoinclast6m_3546845L',      # only 0 and nan\n            'mastercontrelectronic_519L',    # only 0 and nan\n            'mastercontrexist_109L',         # [ 0. nan]\n            'bankacctype_710L',              # [NaN, 'CA']\n            'isdebitcard_729L',              # [NaN, False]\n            'paytype1st_925L',               # ['OTHER', NaN]\n            'paytype_783L',                  # ['OTHER', NaN]\n            'typesuite_864L',                # [NaN, 'AL'] \n            # 'mode1_subjectrole_182M',        # [NaN, 'a55475b1']\n            # 'mode1_subjectrole_93M',         # [NaN, 'a55475b1']\n            # 'mode1_subjectroles_name_838M',  # [NaN, 'a55475b1']\n\n            # duplicated columns\n            'interestrate_311L',\n            'paytype_783L',\n            'mean_debtoutstand_525A',\n            \n            # other columns contain the same values, but have less nans\n            'assignmentdate_4527235D',\n\n            # score decrease\n            'month_decision',\n            'weekday_decision',\n            'firstclxcampaign_1125D',\n\n            'firstquarter_103L',\n            'secondquarter_766L',\n            # 'thirdquarter_1082L',\n            'fourthquarter_440L',\n            'max_num_group1_5',\n            'max_num_group1_6',\n            'max_num_group1',\n            'max_num_group1_9',\n            'max_num_group1_10',\n            'max_num_group1_11',\n\n            'pmtcount_4527229L',\n            'pmtcount_693L',\n            'pmtscount_423L',\n            'pmtssum_45A',\n            # 'assignmentdate_238D',\n            # 'responsedate_1012D',\n            'applicationscnt_464L',\n            'applicationscnt_629L',\n        ]\n\n    @staticmethod\n    def _get_mode2group():\n        return {\n            'agg_applicant_education_M': [\n                'education_1103M',\n                'applicant_max_education_1138M',\n                'education_88M',\n                'applicant_max_education_927M',\n            ],\n        }\n    \n    @staticmethod\n    def _get_mode_df(df, mode2group):\n        assert len(df.shape) == 2\n        assert type(mode2group) is dict\n        assert len(mode2group) == len(Preprocessor._get_mode2group())\n        \n        mode_data = dict()\n        for mode, group in mode2group.items():\n            mode_data[mode] = df[group].mode(axis=1).iloc[:, 0].astype('category')\n\n        return pd.DataFrame(index=df.index, data=mode_data).astype('category')\n\n    @staticmethod\n    def _get_agg2group():\n        # Return aggregation feature name -> group of features\n        agg2group = {\n            'agg_birth_D': [\n                'birthdate_574D', 'dateofbirth_337D',\n                'applicant_max_birth_259D',\n                'max_birth_259D', 'min_birth_259D',\n            ],\n            'agg_credit_bureau_queries_L': [\n                'days120_123L', 'days180_256L', 'days30_165L',\n                'days360_512L', 'days90_310L',\n            ],\n            'agg_avgdbddpdlast24m_P': [\n                'avgdpdtolclosure24_3658938P',\n                'avgdbddpdlast24m_3658932P',\n                # 'avgdbddpdlast3m_4187120P',\n                'avgdbdtollast24m_4525197P',\n            ],\n            'agg_phone_number_shares_L': [\n                'clientscnt_100L', 'clientscnt_1022L',\n                'clientscnt_1071L', 'clientscnt_1130L',\n                'clientscnt_157L', 'clientscnt_257L',\n                'clientscnt_304L', 'clientscnt_360L',\n                'clientscnt_493L', 'clientscnt_533L',\n                'clientscnt_887L', 'clientscnt_946L',\n                'clientscnt12m_3712952L',\n                'clientscnt3m_3712950L',\n                'clientscnt6m_3712949L',\n            ],\n            'agg_applicant_dtlastpmt_D': [\n                'applicant_max_dtlastpmt_581D',\n                'applicant_max_dtlastpmtallstes_3545839D',\n            ],\n            'agg_min_dtlastpmt_D': [\n                'min_dtlastpmt_581D',\n                'min_dtlastpmtallstes_3545839D',\n            ],\n            'agg_maxdbddpd_P': [  # diffs are interesting\n                'maxdbddpdlast1m_3658939P',\n                'maxdbddpdtollast12m_3658940P',\n                'maxdbddpdtollast6m_4187119P',\n            ],\n            'agg_maxdpdlast_P': [  # diffs are interesting\n                'maxdpdlast12m_727P',\n                'maxdpdlast24m_143P',\n                'maxdpdlast3m_392P',\n                'maxdpdlast6m_474P',\n                'maxdpdlast9m_1059P',\n            ],\n            'agg_mindpdlast24m_P': [\n                'mindbddpdlast24m_3658935P',\n                'mindbdtollast24m_4525191P',\n            ],\n            'agg_numactivecreds_L': [\n                'numactivecreds_622L',\n                'numactivecredschannel_414L',\n            ],\n            # 'agg_pmtcount_L': [\n            #     'pmtcount_4527229L',\n            #     'pmtcount_693L',\n            #     'pmtscount_423L',\n            # ],\n            'agg_pmtaverage_A': [\n                'pmtaverage_3A',\n                'pmtaverage_4527227A',\n            ],\n            'agg_numinstpaid_L': [\n                'numinstlsallpaid_934L',\n                'numinstpaid_4499208L',\n            ],\n            'agg_numinstnotpaid_L': [\n                'numinsttopaygr_769L',\n                'numinsttopaygrest_4493213L',\n            ],\n            'agg_numinstpaidearlymix_L': [\n                'numinstpaidearly3d_3546850L',\n                'numinstpaidearly3dest_4493216L',\n                'numinstmatpaidtearly2d_4499204L',\n                'numinstlallpaidearly3d_817L',\n                'numinstpaidearly5dobd_4499205L',\n                'numinstpaidearly_338L',\n                'numinstpaidearlyest_4493214L',\n            ],\n            'agg_numinstpaidearly5d_L': [\n                'numinstpaidearly5d_1087L',\n                'numinstpaidearly5dest_4493211L',\n            ],\n            'agg_numinstregularpaid_L': [\n                'numinstregularpaid_973L',\n                'numinstregularpaidest_4493210L',\n            ],\n            'agg_numinstunpaidmax_L': [\n                'numinstunpaidmax_3546851L',\n                'numinstunpaidmaxest_4493212L',\n            ],\n            'agg_pctinstlsallpaidlat_L': [\n                'pctinstlsallpaidlat10d_839L',\n                'pctinstlsallpaidlate1d_3546856L',\n                'pctinstlsallpaidlate4d_3546849L',\n                'pctinstlsallpaidlate6d_3546844L',\n            ],\n\n            # correlation is very close to 1 (0.9999...)\n            'agg_dpd_P': [\n                'actualdpdtolerance_344P',\n                'max_actualdpd_943P',\n            ],\n            'agg_currtotaldebt_A': [\n                'currdebt_22A',\n                'totaldebt_9A',\n            ],\n            'agg_activateddate_dateactivated_D': [\n                'lastactivateddate_801D',\n                'max_dateactivated_425D',\n                'applicant_max_dateactivated_425D',\n            ],\n            'agg_apprdate_D': [\n                'lastapprdate_640D',\n                'applicant_max_approvaldate_319D',\n            ],\n            'agg_sumoutstandtotal_A': [\n                'sumoutstandtotal_3546847A',\n                'sumoutstandtotalest_4493215A',\n            ],\n            'agg_firstnonzeroinstldate_D': [\n                'max_firstnonzeroinstldate_307D',\n                'applicant_max_firstnonzeroinstldate_307D',\n            ],\n            'agg_dpdmax_139_pmts_dpd_1073_P': [\n                'max_dpdmax_139P',\n                'max_pmts_dpd_1073P',\n            ],\n            'agg_dpdmax_757_pmts_dpd_303_P': [\n                'max_dpdmax_757P',\n                'max_pmts_dpd_303P',\n            ],\n            'agg_totaldebtoverduevalue_A': [\n                'max_overdueamount_31A',\n                'max_totaldebtoverduevalue_718A',\n                'mean_totaldebtoverduevalue_718A',\n                'min_totaldebtoverduevalue_718A',\n                'applicant_max_totaldebtoverduevalue_718A',\n            ],\n            'agg_numberofoutstandinstls_L': [\n                'mode1_numberofoutstandinstls_520L',\n                'mode1_additional_param_numberofoutstandinstls_520L',\n            ],\n\n            # correlation analysis (correlations 0.95+)\n            'agg_dtlastpmtallstes_D': [\n                'dtlastpmtallstes_4499206D',\n                'max_dtlastpmtallstes_3545839D',\n            ],\n            'agg_approvaldate_firstnonzeroinstldate_D': [\n                'lastapplicationdate_877D',\n                'applicant_max_approvaldate_319D',\n                'applicant_max_dateactivated_425D',\n                'lastapprdate_640D',\n                'applicant_max_firstnonzeroinstldate_307D',\n            ],\n            'agg_min_approvaldate_dateactivated_D': [\n                'min_approvaldate_319D',\n                'min_dateactivated_425D',\n            ],\n            'agg_creationdate_firstnonzeroinstldate_D': [\n                'min_creationdate_885D',\n                'min_firstnonzeroinstldate_307D',\n            ],\n            'agg_max_amount_A': [\n                'max_amount_4527230A',\n                'max_amount_4917619A',\n                'applicant_max_amount_4527230A',\n                'applicant_max_amount_4917619A',\n            ],\n            'agg_min_amount_A': [\n                'min_amount_4527230A',\n                'min_amount_4917619A',\n            ],\n            'agg_mean_amount_A': [\n                'mean_amount_4527230A',\n                'mean_amount_4917619A',\n            ],\n            'agg_std_amount_A': [\n                'std_amount_4527230A',\n                'std_amount_4917619A',\n            ],\n            'agg_mode1_additional_param_employername_M': [\n                'mode1_additional_param_name_4527232M',\n                'mode1_additional_param_name_4917606M',\n                'mode1_additional_param_employername_160M',\n            ],\n            'agg_max_num_group1_3_4': [\n                'max_num_group1_3',\n                'max_num_group1_4',\n            ],\n            'agg_totaldebtoverdue_A': [\n                'max_debtoverdue_47A',\n                'max_overdueamount_659A',\n                'max_totaldebtoverduevalue_178A',\n                'mean_totaldebtoverduevalue_178A',\n            ],\n            'agg_applicant_credlmt_totalamount_A': [\n                'applicant_max_credlmt_230A',\n                'applicant_max_totalamount_6A',\n            ],\n            'agg_overdueamountmaxdateyear_T': [\n                'mode1_dpdmaxdateyear_896T',\n                'mode1_overdueamountmaxdateyear_994T',\n            ],\n            'agg_numberofcontrsvalue_L': [\n                'mode1_additional_param_numberofcontrsvalue_358L',\n                'applicant_max_numberofcontrsvalue_358L',\n                'mode1_numberofcontrsvalue_358L',\n            ],\n            'agg_applicant_overdueamountmaxdateyear_T': [\n                'applicant_max_dpdmaxdateyear_896T',\n                'applicant_max_overdueamountmaxdateyear_994T',\n            ],\n            'agg_mode1_additional_param_contaddr_registaddr_district_M': [\n                'mode1_additional_param_contaddr_district_15M',\n                'mode1_additional_param_registaddr_district_1083M',\n            ],\n            'agg_mode1_additional_param_empladdr_district_zipcode_M': [\n                'mode1_additional_param_empladdr_district_926M',\n                'mode1_additional_param_empladdr_zipcode_114M',\n            ],\n            'agg_max_openingdate_D': [\n                'max_openingdate_313D',\n                'max_openingdate_857D',\n            ],\n            'agg_min_openingdate_D': [\n                'min_openingdate_313D',\n                'min_openingdate_857D',\n            ],\n            'agg_applicant_openingdate_D': [\n                'applicant_max_openingdate_313D',\n                'applicant_max_openingdate_857D',\n            ],\n            'agg_mode1_additional_param_13_545_591_426_M': [\n                'mode1_additional_param_classificationofcontr_13M',\n                'mode1_additional_param_contractst_545M',\n                'mode1_additional_param_financialinstitution_591M',\n                'mode1_additional_param_purposeofcred_426M',\n            ],\n        }\n\n        for prefix in ['max', 'min', 'mean', 'std', 'applicant_max']:\n            agg2group['agg_' + prefix + '_overdueamountmax_14155A'] = [\n                prefix + '_overdueamountmax2_14A',\n                prefix + '_overdueamountmax_155A',\n            ]\n            agg2group['agg_' + prefix + '_overdueamountmax_35398A'] = [\n                prefix + '_overdueamountmax2_398A',\n                prefix + '_overdueamountmax_35A',\n            ]\n            \n        return agg2group\n    \n    @staticmethod\n    def _get_agg_df(df, agg2group):\n        assert len(df.shape) == 2\n        assert type(agg2group) is dict\n        assert len(agg2group) == len(Preprocessor._get_agg2group())\n\n        agg_data = dict()\n        for agg, group in agg2group.items():\n            agg_data[agg] = df[group].mean(axis=1)\n\n        return pd.DataFrame(index=df.index, data=agg_data)\n\n    @staticmethod\n    def _get_add_pairs():\n        return [\n            ['agg_currtotaldebt_A', 'agg_sumoutstandtotal_A', 1.4, 1.0],\n            [\n                'mode1_numberofoverdueinstlmax_1039L',\n                'mode1_numberofoverdueinstlmax_1151L',\n                4.0, 1.0\n            ],\n            # ['mean_dpdmax_757P', 'maxdpdlast24m_143P', 0.35, 1.0],\n            # ['mean_pmts_dpd_303P', 'maxdpdlast24m_143P', 0.38, 1.0],\n            ['mean_pmts_overdue_1140A', 'mean_pmts_overdue_1152A', 3.0, 1.0],\n        ]\n    \n    @staticmethod\n    def _get_add_df(df, add_pairs):\n        assert len(df.shape) == 2\n        assert type(add_pairs) is list\n        assert len(add_pairs) == len(Preprocessor._get_add_pairs())\n        \n        for add_pair in add_pairs:\n            assert len(add_pair) == 4\n\n        add_data = dict()\n        for (f1, f2, w1, w2) in add_pairs:\n            feature_name = '_'.join(['add', f1, f2])\n            add_data[feature_name] = w1 * df[f1] + w2 * df[f2]\n        \n        return pd.DataFrame(index=df.index, data=add_data)\n\n    @staticmethod\n    def _get_sub_pairs():\n        return [\n            ['agg_currtotaldebt_A', 'max_outstandingdebt_522A', 1.27, 1.0],\n            # ['agg_totaldebtoverdue_A', 'mean_totaldebtoverduevalue_178A', 1.0, 1.0],\n            # ['agg_totaldebtoverdue_A', 'max_pmts_overdue_1140A', 2.0, 1.0],\n            ['agg_totaldebtoverdue_A', 'agg_max_overdueamountmax_14155A', 2.0, 1.0],\n            ['mean_totaldebtoverduevalue_178A', 'mean_pmts_overdue_1140A', 0.65, 1.0],\n            ['numincomingpmts_3546848L', 'numinstlswithdpd10_728L', 0.11, 1.0],\n            \n            # ['min_annuity_853A', 'min_totaldebtoverduevalue_178A', 0.75, 1.0],\n            ['numinstlswithdpd5_4187116L', 'numinstlswithoutdpd_562L', 14.1, 1.0],\n            # ['datelastinstal40dpd_247D', 'max_dateofcredstart_181D', 0.42, 1.0],\n            ['agg_min_overdueamountmax_35398A', 'min_annuity_853A', 1.3, 1.0],\n            # ['agg_totaldebtoverdue_A', 'mean_annuity_853A', 1.65, 1.0],\n            ['annuity_780A', 'mean_annuity_853A', 0.83, 1.0],\n            ['annuity_780A', 'applicant_max_annuity_853A', 0.9, 1.0],\n            ['agg_avgdbddpdlast24m_P', 'avgdbddpdlast3m_4187120P', 0.65, 1.0],\n            ['maxdbddpdlast1m_3658939P', 'maxdbddpdtollast12m_3658940P', 1.17, 1.0],\n            ['mean_overdueamount_659A', 'mean_overdueamount_31A', 0.02, 1.0],\n            # ['maxdpdlast12m_727P', 'maxdpdlast24m_143P', 1.6, 1.0],\n            # ['maxdpdlast9m_1059P', 'maxdpdlast12m_727P', 1.24, 1.0],\n            # ['maxdpdlast6m_474P', 'maxdpdlast9m_1059P', 1.33, 1.0],\n            ['maxdpdlast3m_392P', 'maxdpdlast6m_474P', 1.5, 1.0],\n            # ['maxdpdlast3m_392P', 'maxdpdlast24m_143P', 4.0, 1.0],\n            # ['currdebt_22A', 'currdebt_94A', 1.0, 1.0],\n            ['maininc_215A', 'mean_mainoccupationinc_437A', 0.83, 1.0],\n            # ['mean_dpdmax_139P', 'mean_dpdmax_757P', 4.5, 1.0],\n            ['mean_monthlyinstlamount_332A', 'mean_monthlyinstlamount_674A', 1.2, 1.0],\n            ['mean_totalamount_996A', 'mean_totalamount_6A', 0.35, 1.0],\n        ]\n\n    @staticmethod\n    def _get_sub_df(df, sub_pairs):\n        assert len(df.shape) == 2\n        assert type(sub_pairs) is list\n        assert len(sub_pairs) == len(Preprocessor._get_sub_pairs())\n        \n        for sub_pair in sub_pairs:\n            assert len(sub_pair) == 4\n\n        sub_data = dict()\n        for (f1, f2, w1, w2) in sub_pairs:\n            feature_name = '_'.join(['sub', f1, f2])\n            sub_data[feature_name] = w1 * df[f1] - w2 * df[f2]\n\n        return pd.DataFrame(index=df.index, data=sub_data)\n    \n    @staticmethod\n    def _get_mul_pairs():\n        return [\n            # ['maxdpdlast12m_727P', 'agg_sumoutstandtotal_A'],\n            # ['agg_sumoutstandtotal_A', 'applicant_max_dateofcredend_289D'],\n            # ['mean_residualamount_488A', 'mean_pmts_overdue_1152A'],\n        ]\n    \n    @staticmethod\n    def _get_mul_df(df, mul_pairs):\n        assert len(df.shape) == 2\n        assert type(mul_pairs) is list\n        assert len(mul_pairs) == len(Preprocessor._get_mul_pairs())\n        \n        for mul_pair in mul_pairs:\n            assert len(mul_pair) == 2\n\n        mul_data = dict()\n        for (f1, f2) in mul_pairs:\n            feature_name = '_'.join(['mul', f1, f2])\n            mul_data[feature_name] = df[f1] * df[f2]\n\n        return pd.DataFrame(index=df.index, data=mul_data)\n\n    @staticmethod\n    def _get_div_pairs():\n        return [\n            ['annuity_780A', 'maininc_215A'],\n            ['agg_currtotaldebt_A', 'maininc_215A'],\n            ['mean_debtoverdue_47A', 'maininc_215A'],\n            ['mean_totaloutstanddebtvalue_39A', 'maininc_215A'],\n            ['avglnamtstart24m_4525187A', 'maininc_215A'],\n            ['avgpmtlast12m_4525200A', 'maininc_215A'],\n            ['agg_sumoutstandtotal_A', 'maininc_215A'],\n            ['maxdpdlast12m_727P', 'maininc_215A'],\n            ['maxpmtlast3m_4525190A', 'maininc_215A'],\n            ['inittransactionamount_650A', 'price_1097A'],\n            ['agg_sumoutstandtotal_A', 'price_1097A'],\n            ['agg_currtotaldebt_A', 'price_1097A'],\n            # ['maxpmtlast3m_4525190A', 'price_1097A'],\n            ['mean_credacc_actualbalance_314A', 'agg_numactivecreds_L'],\n            ['mean_credacc_actualbalance_314A', 'avgpmtlast12m_4525200A'],\n            ['agg_currtotaldebt_A', 'applicant_max_dateofcredend_289D'],\n            # ['mean_debtoverdue_47A', 'applicant_max_dateofcredend_289D'],\n            # ['agg_sumoutstandtotal_A', 'applicant_max_dateofcredend_289D'],\n            ['maininc_215A', 'applicant_max_childnum_21L'],\n            ['mean_monthlyinstlamount_332A', 'inittransactionamount_650A'],\n            ['mean_monthlyinstlamount_332A', 'maininc_215A'],\n            ['mean_totaldebtoverduevalue_178A', 'maininc_215A'],\n        ]\n    \n    @staticmethod\n    def _get_div_df(df, div_pairs):\n        assert len(df.shape) == 2\n        assert type(div_pairs) is list\n        assert len(div_pairs) == len(Preprocessor._get_div_pairs())\n        \n        for div_pair in div_pairs:\n            assert len(div_pair) == 2\n\n        div_data = dict()\n        for (f1, f2) in div_pairs:\n            feature_name = '_'.join(['div', f1, f2])\n            div_data[feature_name] = df[f1] / (1.0 + df[f2])\n\n        return pd.DataFrame(index=df.index, data=div_data)\n    \n#     @staticmethod\n#     def _get_compound_df(df):\n#         assert len(df.shape) == 2\n\n#         compound_data = {\n#             'compound_coef_1_DA': (\n#                 df['mean_monthlyinstlamount_332A']\n#                 / (1.0 + df['maininc_215A'])\n#                 * df['applicant_max_dateofcredend_289D']\n#             ),\n#             'compound_coef_2_DA': (\n#                 df['maininc_215A']\n#                 * df['applicant_max_dateofcredend_289D']\n#                 / (1.0 + df['price_1097A'])\n#             ),\n#             'compound_coef_3_DA': (\n#                 df['maininc_215A']\n#                 * df['applicant_max_dateofcredend_289D']\n#                 - df['agg_sumoutstandtotal_A']\n#             ),\n#             'compound_coef_4_DA': (\n#                 df['maininc_215A'] * df['applicant_max_dateofcredend_289D']\n#                 + df['mean_credacc_actualbalance_314A']\n#                 - df['agg_sumoutstandtotal_A']\n#             ) / (1.0 + df['agg_numactivecreds_L']),\n#         }\n\n#         return pd.DataFrame(index=df.index, data=compound_data)\n\n    @staticmethod\n    def _get_groups_to_drop(agg2group, mode2group):\n        assert type(agg2group) is dict\n        assert type(mode2group) is dict\n        assert len(agg2group) == len(Preprocessor._get_agg2group())\n        assert len(mode2group) == len(Preprocessor._get_mode2group())\n\n        features_to_drop = []\n        features_to_keep = [\n            'mean_totaldebtoverduevalue_178A',\n            'maxdbddpdlast1m_3658939P',\n            'maxdbddpdtollast12m_3658940P',\n            'maxdpdlast12m_727P',\n            'maxdpdlast24m_143P',\n            'maxdpdlast9m_1059P',\n            'maxdpdlast6m_474P',\n            'maxdpdlast3m_392P',\n        ]\n\n        for _, group in agg2group.items():\n            for feature in group:\n                if feature not in features_to_keep:\n                    features_to_drop.append(feature)\n\n        for _, group in mode2group.items():\n            for feature in group:\n                if feature not in features_to_keep:\n                    features_to_drop.append(feature)\n\n        return features_to_drop\n\n    @staticmethod\n    def _get_primary_features():\n        return [\n            'annuity_780A',\n            'pmtnum_254L',\n            'eir_270L',\n            'mobilephncnt_593L',\n            'price_1097A',\n\n            'agg_birth_D',\n            'requesttype_4525192L',\n            'amtinstpaidbefduel24m_4187115A',\n            'annuitynextmonth_57A',\n            'agg_avgdbddpdlast24m_P',\n\n            'mean_pmts_dpd_1073P',\n            'mean_pmts_overdue_1140A',\n            'applicant_max_dpdmax_139P',\n            'above_0.0_share_pmts_dpd_1073P',\n            'lastrejectdate_50D',\n\n            'agg_applicant_education_M',\n            'mean_dpdmax_139P',\n            'lastcancelreason_561M',\n\n            'mode1_relationshiptoclient_415T',\n            'min_dateofcredstart_739D',\n            'agg_max_amount_A',\n            'datefirstoffer_1144D',\n            'agg_currtotaldebt_A',\n\n            'mode1_incometype_1044T',\n            'mode1_numberofoverdueinstlmax_1151L',\n            'mode1_additional_param_numberofoverdueinstlmax_1151L',\n            'agg_mean_overdueamountmax_35398A',\n            'above_0.0_share_pmts_dpd_303P',\n    \n            'monthsannuity_845L',\n            'lastdelinqdate_224D',\n            'isbidproduct_1095L',\n            'maxdpdtolerance_374P',\n            'mean_residualamount_856A',\n            'std_overdueamount_659A',\n            'agg_maxdbddpd_P',\n            'mode1_numberofoverdueinstlmax_1039L',\n            'agg_numberofcontrsvalue_L',\n            'mean_maxdpdtolerance_577P',\n            'mean_totalamount_6A',\n            'agg_numinstunpaidmax_L',\n            'applicationscnt_867L',\n\n            'sub_numincomingpmts_3546848L_numinstlswithdpd10_728L',\n            'sub_numinstlswithdpd5_4187116L_numinstlswithoutdpd_562L',\n            'div_inittransactionamount_650A_price_1097A',\n            'min_credacc_actualbalance_314A',\n            'add_mode1_numberofoverdueinstlmax_1039L_mode1_numberofoverdueinstlmax_1151L',\n            'sub_agg_currtotaldebt_A_max_outstandingdebt_522A',\n            'sub_maxdbddpdlast1m_3658939P_maxdbddpdtollast12m_3658940P',\n            'agg_totaldebtoverduevalue_A',\n            'min_mainoccupationinc_437A',\n        ]\n    \n    @staticmethod\n    def _get_secondary_features():\n        return [\n            'agg_credit_bureau_queries_L',\n            'maritalst_385M',\n            'responsedate_4527233D',\n            'max_employedfrom_700D',\n            'mode1_education_1138M',\n            'mode1_additional_param_postype_4733339M',\n            'mode1_additional_param_rejectreasonclient_4145042M',\n            'mode1_isdebitcard_527L',\n            'agg_max_num_group1_3_4',\n            'max_instlamount_852A',\n            'max_monthlyinstlamount_332A',\n            'min_totaloutstanddebtvalue_39A',\n            'mean_totalamount_996A',\n            'agg_std_overdueamountmax_35398A',\n            'applicant_max_purposeofcred_426M',\n            'mode1_collater_valueofguarantee_1124L',\n\n            'agg_mean_amount_A',\n            'agg_overdueamountmaxdateyear_T',\n            'agg_applicant_overdueamountmaxdateyear_T',\n            'agg_mode1_additional_param_13_545_591_426_M',\n            'cntincpaycont9m_3716944L',\n            'lastrejectreason_759M',\n            'posfpd30lastmonth_3976960P',\n            'max_credamount_590A',\n            'mean_credamount_590A',\n            'max_overdueamountmax2date_1142D',\n            'applicant_max_dateofcredend_289D',\n            'applicant_max_overdueamountmax2date_1002D',\n            'mode1_additional_param_purposeofcred_874M',\n            'mode1_sex_738L',\n            'max_amount_416A',\n            'mean_amount_416A',\n            'max_pmts_overdue_1152A',\n            \n            'equalitydataagreement_891L',\n            'lastapprcredamount_781A',\n            'lastrejectcredamount_222A',\n            'maxdpdfrom6mto36m_3546853P',\n            'mode1_financialinstitution_382M',  # check\n\n            # 'sub_agg_totaldebtoverdue_A_max_overdueamount_659A',\n            'sub_agg_totaldebtoverdue_A_agg_max_overdueamountmax_14155A',\n            'sub_mean_totalamount_996A_mean_totalamount_6A',\n            'div_mean_totaloutstanddebtvalue_39A_maininc_215A',\n            'div_maxdpdlast12m_727P_maininc_215A',\n            'div_agg_currtotaldebt_A_price_1097A',\n            'div_mean_monthlyinstlamount_332A_maininc_215A',\n            'agg_numinstpaid_L',\n            'agg_dtlastpmtallstes_D',\n            'agg_creationdate_firstnonzeroinstldate_D',\n            'agg_max_overdueamountmax_35398A',\n            'thirdquarter_1082L',\n            'avgdbddpdlast3m_4187120P',\n            'credamount_770A',\n            'credtype_322L',\n            'daysoverduetolerancedd_3976961L',\n            'inittransactioncode_186L',\n            'lastrejectcommoditycat_161M',\n            'maxannuity_159A',\n            'numinstls_657L',\n            'numinstpaidlate1d_3546852L',\n            'sellerplacescnt_216L',\n            'max_credacc_maxhisbal_375A',\n            'max_credacc_minhisbal_90A',\n            'min_credacc_maxhisbal_375A',\n            'min_credacc_minhisbal_90A',\n            'mean_credacc_minhisbal_90A',\n            'mean_currdebt_94A',\n            'mean_mainoccupationinc_437A',\n            'mean_revolvingaccount_394A',\n            'applicant_max_credamount_590A',\n            'applicant_max_currdebt_94A',\n            'applicant_max_downpmt_134A',\n            'mode1_credacc_status_367L',\n            'mode1_credtype_587L',\n            'mode1_familystate_726L',\n            'max_credlmt_935A',\n            'max_outstandingamount_362A',\n            'min_totalamount_6A',\n            'mean_overdueamount_659A',\n            'std_instlamount_768A',\n            'std_monthlyinstlamount_674A',\n            'applicant_max_totalamount_996A',\n            'max_dateofcredend_289D',\n            'max_dateofcredstart_739D',\n            'min_numberofoverdueinstlmaxdat_641D',\n            'mode1_nominalrate_281L',\n            'mode1_nominalrate_498L',\n            'mode1_numberofinstls_229L',\n            'mode1_periodicityofpmts_1102L',\n            'applicant_max_numberofoverdueinstlmax_1039L',\n            'mode1_collater_valueofguarantee_876L',\n            \n            'mean_outstandingdebt_522A',\n            'agg_mean_overdueamountmax_14155A',\n            'agg_std_amount_A',\n            'agg_firstnonzeroinstldate_D',\n            'applicant_max_familystate_726L',\n            'applicant_max_dateofcredstart_739D',\n            'min_overdueamountmax2date_1002D',\n            'maxdbddpdtollast12m_3658940P',\n            'maxdbddpdlast1m_3658939P',\n            'agg_maxdpdlast_P',\n            'div_annuity_780A_maininc_215A',\n            'div_agg_sumoutstandtotal_A_price_1097A',\n            'sub_mean_totaldebtoverduevalue_178A_mean_pmts_overdue_1140A',\n\n            'add_agg_currtotaldebt_A_agg_sumoutstandtotal_A',\n            'add_mean_pmts_overdue_1140A_mean_pmts_overdue_1152A',\n            'sub_annuity_780A_applicant_max_annuity_853A',\n            'sub_agg_avgdbddpdlast24m_P_avgdbddpdlast3m_4187120P',\n            'sub_mean_monthlyinstlamount_332A_mean_monthlyinstlamount_674A',\n            'div_avglnamtstart24m_4525187A_maininc_215A',\n            'div_maxpmtlast3m_4525190A_maininc_215A',\n            'agg_min_dtlastpmt_D',\n            'agg_mindpdlast24m_P',\n            'agg_numinstregularpaid_L',\n            'agg_pctinstlsallpaidlat_L',\n            'agg_activateddate_dateactivated_D',\n            'agg_sumoutstandtotal_A',\n            'agg_dpdmax_139_pmts_dpd_1073_P',\n            'agg_dpdmax_757_pmts_dpd_303_P',\n            'agg_approvaldate_firstnonzeroinstldate_D',\n            'agg_min_amount_A',\n            'agg_applicant_credlmt_totalamount_A',\n            'agg_mode1_additional_param_empladdr_district_zipcode_M',\n            'agg_min_openingdate_D',\n            'agg_max_overdueamountmax_14155A',\n            'agg_min_overdueamountmax_14155A',\n            'agg_min_overdueamountmax_35398A',\n            'agg_applicant_max_overdueamountmax_14155A',\n            'avglnamtstart24m_4525187A',\n            'avgmaxdpdlast9m_3716943P',\n            'avgpmtlast12m_4525200A',\n            'cntpmts24_3658933L',\n            'datelastinstal40dpd_247D',\n            'disbursedcredamount_1113A',\n            'homephncnt_628L',\n            'inittransactionamount_650A',\n            'lastapprcommoditycat_1041M',\n            'lastrejectreasonclient_4145040M',\n            'lastst_736L',\n            'maxdebt4_972A',\n            'maxdpdlast24m_143P',\n            'maxdpdlast9m_1059P',\n            'maxinstallast24m_3658928A',\n            'maxlnamtstart6m_4525199A',\n            'numcontrs3months_479L',\n            'numincomingpmts_3546848L',\n            'numnotactivated_1143L',\n            'numrejects9m_859L',\n            'pctinstlsallpaidearl3d_427L',\n            'totalsettled_863A',\n            'totinstallast1m_4525188A',\n            'max_maxdpdtolerance_577P',\n            'max_annuity_853A',\n            'max_mainoccupationinc_437A',\n            'max_outstandingdebt_522A',\n            'mean_annuity_853A',\n            'std_credamount_590A',\n            'applicant_max_annuity_853A',\n            'min_employedfrom_700D',\n            'applicant_max_employedfrom_700D',\n            'mode1_childnum_21L',\n            'applicant_max_credtype_587L',\n            'applicant_max_inittransactioncode_279L',\n            'mean_dpdmax_757P',\n            'applicant_max_dpdmax_757P',\n            'max_credlmt_230A',\n            'max_instlamount_768A',\n            'max_monthlyinstlamount_674A',\n            'max_residualamount_488A',\n            'max_residualamount_856A',\n            'max_totalamount_6A',\n            'max_totalamount_996A',\n            'min_credlmt_230A',\n            'min_instlamount_852A',\n            'min_monthlyinstlamount_332A',\n            'min_monthlyinstlamount_674A',\n            'min_totaldebtoverduevalue_178A',\n            'mean_credlmt_230A',\n            'mean_outstandingamount_362A',\n            'mean_residualamount_488A',\n            'mean_totaldebtoverduevalue_178A',\n            'std_instlamount_852A',\n            'std_monthlyinstlamount_332A',\n            'std_residualamount_488A',\n            'std_residualamount_856A',\n            'std_totalamount_6A',\n            'applicant_max_monthlyinstlamount_332A',\n            'max_dateofcredstart_181D',\n            'max_dateofrealrepmt_138D',\n            'max_lastupdate_388D',\n            'max_numberofoverdueinstlmaxdat_641D',\n            'max_overdueamountmax2date_1002D',\n            'min_dateofcredend_289D',\n            'min_dateofcredstart_181D',\n            'min_dateofrealrepmt_138D',\n            'applicant_max_dateofcredend_353D',\n            'applicant_max_dateofrealrepmt_138D',\n            'applicant_max_lastupdate_388D',\n            'applicant_max_refreshdate_3813885D',\n            'mode1_numberofinstls_320L',\n            'mode1_prolongationcount_1120L',\n            'applicant_max_numberofinstls_320L',\n            'applicant_max_numberofoverdueinstlmax_1151L',\n            'mean_pmts_overdue_1152A',\n            'maxdpdinstldate_3546855D',\n            'maxdpdlast3m_392P',\n            'mode1_numberofoverdueinstls_725L',\n            'agg_numinstnotpaid_L',\n\n            'mode1_rejectreason_755M',\n            'mode1_financialinstitution_591M',\n            'mode1_familystate_447L',\n            'mode1_education_927M',\n            'datelastunpaid_3546854D',\n            'mode1_cancelreason_3545846M',\n        ]","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.503302Z","iopub.execute_input":"2024-05-25T18:39:24.504259Z","iopub.status.idle":"2024-05-25T18:39:24.608296Z","shell.execute_reply.started":"2024-05-25T18:39:24.504217Z","shell.execute_reply":"2024-05-25T18:39:24.606939Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now we can proceed to our ensemble.","metadata":{}},{"cell_type":"markdown","source":"### 3. Ensembling","metadata":{}},{"cell_type":"markdown","source":"Our ensemble consists of 21 models:\n\n* 7 lightgbm models (one model for each of the 7 folds) trained using a default loss;\n* 7 catboost models trained using a default loss (one model for each fold);\n* 7 lightgbm models trained using the custom loss (which was introduced above).\n\nSince fitting one model can be time-consuming (for instance, training one lightgbm model using our custom loss requires ~10 hours), all 21 models have been pre-fitted. The parameters of our models are listed below.","metadata":{}},{"cell_type":"code","source":"lgb_standard_loss_params = {\n    'objective': 'binary',\n    'boosting_type': 'gbdt',\n    'bagging_freq': 1,\n    'pos_bagging_fraction': 0.7,\n    'neg_bagging_fraction': 0.7,\n    'colsample_bynode': 0.8,\n    'colsample_bytree': 0.8,\n    'device': 'cpu',\n    'learning_rate': 0.04,\n    'max_depth': 11,\n    'metric': 'auc',\n    'n_estimators': 1000,\n    'num_leaves': 33,\n    'l1_regularization': 0.01,\n    'l2_regularization': 1.0,\n    'verbose': -1,\n}","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.609586Z","iopub.execute_input":"2024-05-25T18:39:24.610979Z","iopub.status.idle":"2024-05-25T18:39:24.623450Z","shell.execute_reply.started":"2024-05-25T18:39:24.610943Z","shell.execute_reply":"2024-05-25T18:39:24.622138Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_lgb_standard_loss_predictions(x, folds_n):\n    assert len(x.shape) == 2 and x.shape[1] == 262\n    assert 'WEEK_NUM' not in x.columns\n    assert folds_n == 7\n\n    y_pred = np.zeros(x.shape[0])\n\n    for i in range(folds_n):\n        base_path = f'/kaggle/input/home-credit-lgb{folds_n}/'\n        path_to_file = base_path + f'lgb_model_{i}.txt'\n        lgb_model = lgb.Booster(model_file=path_to_file)\n        curr_y_pred = lgb_model.predict(x)\n        assert curr_y_pred.shape == (x.shape[0],)\n        y_pred += (1.0 / folds_n) * curr_y_pred\n\n    assert (0.0 <= y_pred).all() and (y_pred <= 1.0).all()\n    return y_pred","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.625572Z","iopub.execute_input":"2024-05-25T18:39:24.626094Z","iopub.status.idle":"2024-05-25T18:39:24.636168Z","shell.execute_reply.started":"2024-05-25T18:39:24.626051Z","shell.execute_reply":"2024-05-25T18:39:24.634844Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"lgb_standard_loss_cv_scores = np.array([\n    0.6979209819763105,\n    0.6994747307837811,\n    0.676226311428907,\n    0.6737122363211739,\n    0.67776833290611,\n    0.7028070685699034,\n    0.7015272382308332,\n])\n\nnp.mean(lgb_standard_loss_cv_scores), np.std(lgb_standard_loss_cv_scores)","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.637935Z","iopub.execute_input":"2024-05-25T18:39:24.639310Z","iopub.status.idle":"2024-05-25T18:39:24.651635Z","shell.execute_reply.started":"2024-05-25T18:39:24.639263Z","shell.execute_reply":"2024-05-25T18:39:24.650329Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"cat_standard_loss_params = {\n    'eval_metric': 'AUC',\n    'loss_function': 'Logloss',\n}","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.653245Z","iopub.execute_input":"2024-05-25T18:39:24.653587Z","iopub.status.idle":"2024-05-25T18:39:24.660025Z","shell.execute_reply.started":"2024-05-25T18:39:24.653558Z","shell.execute_reply":"2024-05-25T18:39:24.658602Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_cat_standard_loss_predictions(x, folds_n):\n    assert len(x.shape) == 2 and x.shape[1] == 262\n    assert 'WEEK_NUM' not in x.columns\n    assert folds_n == 7\n\n    y_pred = np.zeros(x.shape[0])\n\n    for i in range(folds_n):\n        base_path = f'/kaggle/input/home-credit-cat{folds_n}/'\n        path_to_file = base_path + f'cat_model_{i}'\n        cat_model = CatBoostClassifier()\n        cat_model.load_model(path_to_file)\n        curr_y_pred = cat_model.predict_proba(x)[:, 1]\n        assert curr_y_pred.shape == (x.shape[0],)\n        y_pred += (1.0 / folds_n) * curr_y_pred\n\n    assert (0.0 <= y_pred).all() and (y_pred <= 1.0).all()\n    return y_pred","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.664999Z","iopub.execute_input":"2024-05-25T18:39:24.665506Z","iopub.status.idle":"2024-05-25T18:39:24.676406Z","shell.execute_reply.started":"2024-05-25T18:39:24.665465Z","shell.execute_reply":"2024-05-25T18:39:24.674849Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"cat_standard_loss_cv_scores = np.array([\n    0.6898337379671444,\n    0.694894414743338,\n    0.6724219838315976,\n    0.6717646160284778,\n    0.667851895160009,\n    0.702788124581769,\n    0.6939067520723806,\n])\n\nnp.mean(cat_standard_loss_cv_scores), np.std(cat_standard_loss_cv_scores)","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.678057Z","iopub.execute_input":"2024-05-25T18:39:24.678471Z","iopub.status.idle":"2024-05-25T18:39:24.693978Z","shell.execute_reply.started":"2024-05-25T18:39:24.678434Z","shell.execute_reply":"2024-05-25T18:39:24.692484Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"lgb_custom_loss_params = {\n    'objective': custom_loss.compute_grad_hess,\n    'boosting_type': 'gbdt',\n    'bagging_freq': 1,\n    'pos_bagging_fraction': 0.7,\n    'neg_bagging_fraction': 0.7,\n    'colsample_bynode': 0.9,\n    'colsample_bytree': 0.9,\n    'device': 'cpu',\n    'learning_rate': 0.04,\n    'max_depth': 10,\n    'metric': 'auc',\n    'n_estimators': 800,\n    'num_leaves': 33,\n}","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.695597Z","iopub.execute_input":"2024-05-25T18:39:24.695986Z","iopub.status.idle":"2024-05-25T18:39:24.703307Z","shell.execute_reply.started":"2024-05-25T18:39:24.695948Z","shell.execute_reply":"2024-05-25T18:39:24.701842Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_lgb_custom_loss_predictions(x, folds_n):\n    assert len(x.shape) == 2 and x.shape[1] == 262\n    assert 'WEEK_NUM' not in x.columns\n    assert folds_n == 7\n\n    y_pred = np.zeros(x.shape[0])\n\n    for i in range(folds_n):\n        base_path = f'/kaggle/input/home-credit-lgb{folds_n}-custom-loss/'\n        path_to_file = base_path + f'custom_loss_lgb_model_{i}.txt'\n        lgb_model = lgb.Booster(model_file=path_to_file)\n        curr_y_pred = lgb_model.predict(x)\n        assert curr_y_pred.shape == (x.shape[0],)\n        y_pred += (1.0 / folds_n) * curr_y_pred\n\n    # if a custom loss is used, the model produces\n    # raw scores rather than probabilities\n    y_pred = 1.0 / (1.0 + np.exp(-1.0 * y_pred))\n\n    assert (0.0 <= y_pred).all() and (y_pred <= 1.0).all()\n    return y_pred","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.705196Z","iopub.execute_input":"2024-05-25T18:39:24.705806Z","iopub.status.idle":"2024-05-25T18:39:24.716019Z","shell.execute_reply.started":"2024-05-25T18:39:24.705737Z","shell.execute_reply":"2024-05-25T18:39:24.714899Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"lgb_custom_loss_cv_scores = np.array([\n    0.6872206942502364,\n    0.6943166145254615,\n    0.6696829896530077,\n    0.6573192649806858,\n    0.6688261314655405,\n    0.6938251928380464,\n    0.6935517381923513,\n])\n\nnp.mean(lgb_custom_loss_cv_scores), np.std(lgb_custom_loss_cv_scores)","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.717087Z","iopub.execute_input":"2024-05-25T18:39:24.717469Z","iopub.status.idle":"2024-05-25T18:39:24.733521Z","shell.execute_reply.started":"2024-05-25T18:39:24.717409Z","shell.execute_reply":"2024-05-25T18:39:24.732395Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_predictions(x, folds_n):\n    assert len(x.shape) == 2\n    assert x.shape[1] == 262\n    assert 'WEEK_NUM' not in x.columns\n    assert folds_n == 7\n    \n    lgb_standard_loss_y_pred = get_lgb_standard_loss_predictions(x, folds_n)\n    cat_standard_loss_y_pred = get_cat_standard_loss_predictions(x, folds_n)\n    lgb_custom_loss_y_pred = get_lgb_custom_loss_predictions(x, folds_n)\n\n    assert len(lgb_standard_loss_y_pred.shape) == 1\n    assert len(cat_standard_loss_y_pred.shape) == 1\n    assert len(lgb_custom_loss_y_pred.shape) == 1\n    assert lgb_standard_loss_y_pred.shape == cat_standard_loss_y_pred.shape\n    assert cat_standard_loss_y_pred.shape == lgb_custom_loss_y_pred.shape\n\n    y_pred = (\n        lgb_standard_loss_y_pred +\n        cat_standard_loss_y_pred +\n        lgb_custom_loss_y_pred\n    ) / 3.0\n\n    assert len(y_pred.shape) == 1\n    assert y_pred.shape == (x.shape[0],)\n\n    assert (0.0 <= y_pred).all()\n    assert (y_pred <= 1.0).all()\n    return y_pred","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.735127Z","iopub.execute_input":"2024-05-25T18:39:24.735518Z","iopub.status.idle":"2024-05-25T18:39:24.745349Z","shell.execute_reply.started":"2024-05-25T18:39:24.735477Z","shell.execute_reply":"2024-05-25T18:39:24.744174Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Ok, we have discussed everything we need. Let's move on to the inference part.","metadata":{}},{"cell_type":"markdown","source":"### Pipeline","metadata":{}},{"cell_type":"code","source":"class Pipeline:\n\n    @staticmethod\n    def set_table_dtypes(df):\n        for col in df.columns:\n            if col in [\"case_id\", \"WEEK_NUM\", \"num_group1\", \"num_group2\"]:\n                df = df.with_columns(pl.col(col).cast(pl.Int32))\n            elif col in [\"date_decision\"]:\n                df = df.with_columns(pl.col(col).cast(pl.Date))\n            elif col[-1] in (\"P\", \"A\"):\n                df = df.with_columns(pl.col(col).cast(pl.Float64))\n            elif col[-1] in (\"M\",):\n                df = df.with_columns(pl.col(col).cast(pl.String))\n            elif col[-1] in (\"D\",):\n                df = df.with_columns(pl.col(col).cast(pl.Date))            \n\n        return df\n    \n    @staticmethod\n    def handle_dates(df):\n        for col in df.columns:\n            if col[-1] in (\"D\",):\n                df = df.with_columns(pl.col(col) - pl.col(\"date_decision\"))\n                df = df.with_columns(pl.col(col).dt.total_days())\n                df = df.with_columns(pl.col(col).cast(pl.Float32))\n\n        df = df.drop(\"date_decision\", \"MONTH\")\n\n        return df\n\n    @staticmethod\n    def filter_cols(df):\n        for col in df.columns:\n            if col not in [\"target\", \"case_id\", \"WEEK_NUM\"]:\n                isnull = df[col].is_null().mean()\n\n                if isnull > 0.95:  # 0.7\n                    df = df.drop(col)\n\n        for col in df.columns:\n            if (col not in [\"target\", \"case_id\", \"WEEK_NUM\"]) and (df[col].dtype == pl.String):\n                freq = df[col].n_unique()\n\n                if (freq <= 1) or (freq > 255):  # (freq <= 1) | (freq > 200)\n                    df = df.drop(col)\n\n        return df","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.746848Z","iopub.execute_input":"2024-05-25T18:39:24.747214Z","iopub.status.idle":"2024-05-25T18:39:24.763247Z","shell.execute_reply.started":"2024-05-25T18:39:24.747186Z","shell.execute_reply":"2024-05-25T18:39:24.762099Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Automatic Aggregation","metadata":{}},{"cell_type":"code","source":"class Aggregator:\n\n    @staticmethod\n    def _applicant_max_expr(df, depth, cols):\n        assert depth == 1\n        assert \"num_group1\" in df.columns\n        assert \"num_group2\" not in df.columns\n        assert \"num_group1\" not in cols\n\n        redundant_cols = [\n            'creationdate_885D',\n            'recorddate_4527225D',\n            'deductiondate_4917603D',\n            'debtoutstand_525A',\n            'debtoverdue_47A',\n            'empl_employedfrom_271D',\n            'empl_employedtotal_800L',\n            'empl_industry_691L',\n            'familystate_447L',\n            'housetype_905L',\n            'incometype_1044T',\n            'safeguarantyflag_411L',\n            'sex_738L',\n            'revolvingaccount_394A',\n            'residualamount_488A',\n            'instlamount_768A',\n            'instlamount_852A',\n            'outstandingamount_362A',\n            'totaldebtoverduevalue_178A',\n            'totaloutstanddebtvalue_39A',\n            'dateofcredstart_181D',\n            'numberofoverdueinstlmaxdat_641D',\n            'overdueamountmax2date_1142D',\n            'numberofcontrsvalue_258L',\n            'maxdpdtolerance_577P',\n            'outstandingdebt_522A',\n            'actualdpd_943P',\n            'financialinstitution_591M',\n            'contractst_545M',\n            'contractst_964M',\n        ]\n\n        return [\n            pl.col(\"num_group1\", col)\n            .filter(pl.col(\"num_group1\") == 0)\n            .exclude(\"num_group1\")\n            .max()\n            .alias(f\"applicant_max_{col}\")\n            for col in cols if col not in redundant_cols\n        ]\n\n    @staticmethod\n    def _above_expr(df, depth, cols, threshold):\n        assert depth in [1, 2]\n        assert \"num_group1\" in df.columns\n        return [\n            pl.col(col)\n            .filter(pl.col(col) > threshold)\n            .len()\n            .cast(pl.Float64)\n            .truediv(1 + pl.len())\n            .alias(f\"above_{threshold}_share_{col}\")\n            for col in cols\n        ]\n\n    @staticmethod\n    def _mode_share_expr(df, depth, cols):\n        assert depth in [1, 2]\n        assert \"num_group1\" in df.columns\n        \n        additional_param_cols = [\n            'numberofoverdueinstlmax_1151L',\n            'postype_4733339M',\n            'rejectreasonclient_4145042M',\n            'purposeofcred_874M',\n            'purposeofcred_426M',\n            'purposeofcred_722M',\n            'classificationofcontr_13M',\n            'contractst_545M',\n            'financialinstitution_591M',\n            'numberofoutstandinstls_520L',\n            'employername_M',\n            'name_4527232M',\n            'name_4917606M',\n            'employername_160M',\n            'numberofcontrsvalue_358L',\n            'contaddr_district_15M',\n            'registaddr_district_1083M',\n            'empladdr_district_926M',\n            'empladdr_zipcode_114M',\n        ]\n\n        expr = []\n\n        for col in cols:\n            if df[col].dtype == pl.String:\n                mode1 = pl.col(col).drop_nulls().mode().max()\n                # mode2 = pl.col(col).filter(pl.col(col) != mode1).mode().first()\n                expr.append(mode1.alias(f\"mode1_{col}\"))\n\n                if col in additional_param_cols:\n                    expr.append(\n                        (pl.col(col) == mode1).cast(pl.Float64).sum()\n                        .truediv(1 + pl.len())\n                        .alias(f\"mode1_additional_param_{col}\")\n                    )\n            else:\n                expr.append(\n                    pl.mean(col).alias(f\"mode1_{col}\")\n                )\n\n                if col in additional_param_cols:\n                    expr.append(\n                        pl.max(col).alias(f\"mode1_additional_param_{col}\")\n                    )\n\n        return expr\n\n    @staticmethod\n    def num_dpd_expr(df, depth):\n        assert depth in [1, 2]\n        \n        redundant_cols = [\n            'avgdbdtollast24m_4525197P',\n        ]\n\n        cols = [\n            col for col in df.columns\n            if (col[-1] == \"P\") and (col not in redundant_cols)\n        ]\n\n        expr_max = [pl.max(col).alias(f\"max_{col}\") for col in cols]\n        expr_mean = [pl.mean(col).alias(f\"mean_{col}\") for col in cols]\n\n        expr_above = Aggregator._above_expr(\n            df,\n            depth,\n            [col for col in cols if 'pmts_dpd' in col],\n            threshold=0.0\n        )\n        \n        if depth == 2:\n            return expr_mean + expr_max + expr_above\n\n        expr_applicant_max = Aggregator._applicant_max_expr(df, depth, cols)\n        return expr_mean + expr_max + expr_above + expr_applicant_max\n\n    @staticmethod\n    def num_amnt_expr(df, depth):\n        assert depth in [1, 2]\n        \n        redundant_cols = [\n            'avgpmtlast12m_4525200A',\n            'disbursedcredamount_1113A',\n            'maxpmtlast3m_4525190A',\n            'pmtamount_36A',\n            'totaloutstanddebtvalue_668A',\n            'mainoccupationinc_384A',\n            'credacc_credlmt_575A',\n        ]\n\n        cols = [\n            col for col in df.columns\n            if (col[-1] == \"A\") and (col not in redundant_cols)\n        ]\n\n        expr_max = [pl.max(col).alias(f\"max_{col}\") for col in cols]\n        expr_min = [\n            pl.min(col).alias(f\"min_{col}\") for col in cols\n            if col not in [\n                'debtoutstand_525A',\n                'debtoverdue_47A',\n                'outstandingamount_362A',\n                'residualamount_488A',\n                'totalamount_996A',\n                'credlmt_935A',\n                'residualamount_856A',\n                'instlamount_768A',\n                \n            ]\n        ]\n\n        expr_mean = [pl.mean(col).alias(f\"mean_{col}\") for col in cols]\n        expr_std = [\n            pl.std(col).alias(f\"std_{col}\")\n            for col in cols if 'amount' in col\n        ]\n\n        if depth == 2:\n            return expr_max + expr_min + expr_mean + expr_std\n\n        expr_applicant_max = Aggregator._applicant_max_expr(df, depth, cols)\n        return expr_max + expr_min + expr_mean + expr_std + expr_applicant_max\n\n    @staticmethod\n    def date_expr(df, depth):\n        assert depth in [1, 2]\n\n        redundant_cols = [\n            'responsedate_1012D',\n            'processingdate_168D',\n            'numberofoverdueinstlmaxdat_148D',\n        ]\n\n        cols = [\n            col for col in df.columns\n            if (col[-1] == \"D\") and (col not in redundant_cols)\n        ]\n\n        expr_max = [\n            pl.max(col).alias(f\"max_{col}\") for col in cols\n            if col not in [\n                'recorddate_4527225D',\n                'approvaldate_319D',\n                'creationdate_885D',\n            ]\n        ]\n        expr_min = [\n            pl.min(col).alias(f\"min_{col}\") for col in cols\n            if col not in [\n                'empl_employedfrom_271D',\n                'recorddate_4527225D',\n            ]\n        ]\n\n        if depth == 1:\n            expr_applicant_max = Aggregator._applicant_max_expr(df, depth, cols)\n            return expr_max + expr_min + expr_applicant_max\n\n        return expr_max + expr_min\n\n    @staticmethod\n    def str_expr(df, depth):\n        assert depth in [1, 2]\n\n        redundant_cols = [\n            'lastrejectcommodtypec_5251769M',\n            'language1_981M',\n            'subjectrole_182M',\n            'subjectrole_93M',\n            'subjectroles_name_838M',\n        ]\n\n        cols = [\n            col for col in df.columns\n            if (col[-1] == \"M\") and (col not in redundant_cols)\n        ]\n\n        expr_mode_share = Aggregator._mode_share_expr(df, depth, cols)\n\n        if depth == 2:\n            return expr_mode_share\n\n        expr_applicant_max = Aggregator._applicant_max_expr(df, depth, cols)\n        return expr_mode_share + expr_applicant_max\n\n    @staticmethod\n    def other_expr(df, depth):\n        assert depth in [1, 2]\n        \n        redundant_cols = [\n            'pmts_month_158T',\n            'pmts_year_1139T',\n            'pmts_month_706T',\n            'dpdmaxdateyear_596T',\n            'overdueamountmaxdatemonth_284T',\n            'overdueamountmaxdatemonth_365T',\n            'dpdmaxdatemonth_89T',\n            'dpdmaxdatemonth_442T',\n\n            'contractssum_5085716L',\n            'applicationscnt_629L',\n            'clientscnt_1130L',\n            'clientscnt_360L',\n            'clientscnt_533L',\n            'clientscnt_887L',\n            'clientscnt_946L',\n            'numinstpaidlastcontr_4325080L',\n            'numnotactivated_1143L',\n            'contaddr_smempladdr_334L',\n            'type_25L',\n\n            # only 1 unique value\n            'personindex_1023L',\n            'persontype_1072L',\n            'persontype_792L',\n\n            # only 2 unique values\n            'contaddr_matchlist_1032L',  # [null, false]\n            'remitter_829L',             # [ 0. null]\n\n            # duplicates\n            'tenor_203L',  # ~ pmtnum_8L\n            \n            # score decrease\n            'periodicityofpmts_837L',\n            'overdueamountmaxdateyear_2T',\n            'numberofoutstandinstls_59L',\n            'annualeffectiverate_63L',\n            'pmtnum_8L',\n            'status_219L',\n        ]\n\n        cols = [\n            col for col in df.columns\n            if (col[-1] in (\"T\", \"L\")) and (col not in redundant_cols)\n        ]\n\n        expr_mode_share = Aggregator._mode_share_expr(df, depth, cols)\n\n        if depth == 2:\n            return expr_mode_share\n\n        expr_applicant_max = Aggregator._applicant_max_expr(df, depth, cols)\n        return expr_mode_share + expr_applicant_max\n    \n    @staticmethod\n    def count_expr(df, depth):\n        assert depth in [1, 2]\n\n        cols = [\n            col for col in df.columns\n            if \"num_group\" in col\n        ]\n\n        if depth == 1:\n            assert \"num_group1\" in df.columns\n            assert \"num_group2\" not in df.columns\n            expr_max = [pl.max(col).alias(f\"max_{col}\") for col in cols]\n            return expr_max\n\n        assert \"num_group1\" in df.columns\n        assert \"num_group2\" in df.columns\n\n        return []  # expr_n_unique + expr_applicant_share\n\n    @staticmethod\n    def get_exprs(df, depth):\n        assert depth in [1, 2]\n\n        exprs = Aggregator.num_dpd_expr(df, depth) + \\\n                Aggregator.num_amnt_expr(df, depth) + \\\n                Aggregator.date_expr(df, depth) + \\\n                Aggregator.str_expr(df, depth) + \\\n                Aggregator.other_expr(df, depth) + \\\n                Aggregator.count_expr(df, depth)\n\n        return exprs","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.765086Z","iopub.execute_input":"2024-05-25T18:39:24.765826Z","iopub.status.idle":"2024-05-25T18:39:24.811787Z","shell.execute_reply.started":"2024-05-25T18:39:24.765787Z","shell.execute_reply":"2024-05-25T18:39:24.810618Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### File I/O","metadata":{}},{"cell_type":"code","source":"def read_file(path, depth=None):\n    df = pl.read_parquet(path)\n    df = df.pipe(Pipeline.set_table_dtypes)\n\n    if depth in [1, 2]:\n        df = df.group_by(\"case_id\").agg(Aggregator.get_exprs(df, depth))\n\n    return df\n\n\ndef read_files(regex_path, depth=None):\n    chunks = []\n    for path in glob(str(regex_path)):\n        df = pl.read_parquet(path)\n        df = df.pipe(Pipeline.set_table_dtypes)\n\n        if depth in [1, 2]:\n            df = df.group_by(\"case_id\").agg(Aggregator.get_exprs(df, depth))\n        \n        chunks.append(df)\n\n    df = pl.concat(chunks, how=\"vertical_relaxed\")\n    df = df.unique(subset=[\"case_id\"])\n    \n    return df","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.816840Z","iopub.execute_input":"2024-05-25T18:39:24.817645Z","iopub.status.idle":"2024-05-25T18:39:24.829255Z","shell.execute_reply.started":"2024-05-25T18:39:24.817597Z","shell.execute_reply":"2024-05-25T18:39:24.828000Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Feature Engineering","metadata":{}},{"cell_type":"code","source":"def feature_eng(df_base, depth_0, depth_1, depth_2):\n    df_base = (\n        df_base.with_columns(\n            month_decision = pl.col(\"date_decision\").dt.month(),\n            weekday_decision = pl.col(\"date_decision\").dt.weekday(),\n        )\n    )\n\n    for i, df in enumerate(depth_0 + depth_1 + depth_2):\n        df_base = df_base.join(df, how=\"left\", on=\"case_id\", suffix=f\"_{i}\")\n\n    df_base = df_base.pipe(Pipeline.handle_dates)\n\n    return df_base","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.830429Z","iopub.execute_input":"2024-05-25T18:39:24.830827Z","iopub.status.idle":"2024-05-25T18:39:24.842333Z","shell.execute_reply.started":"2024-05-25T18:39:24.830790Z","shell.execute_reply":"2024-05-25T18:39:24.841202Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def to_pandas(df_data, cat_cols=None):\n    df_data = df_data.to_pandas()\n\n    if cat_cols is None:\n        cat_cols = list(df_data.select_dtypes(\"object\").columns)\n\n    df_data[cat_cols] = df_data[cat_cols].astype(\"category\")\n\n    return df_data, cat_cols","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.843874Z","iopub.execute_input":"2024-05-25T18:39:24.844225Z","iopub.status.idle":"2024-05-25T18:39:24.851739Z","shell.execute_reply.started":"2024-05-25T18:39:24.844187Z","shell.execute_reply":"2024-05-25T18:39:24.850527Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Configuration","metadata":{}},{"cell_type":"code","source":"ROOT      = Path(\"/kaggle/input/home-credit-credit-risk-model-stability\")\nTRAIN_DIR = ROOT / \"parquet_files\" / \"train\"\nTEST_DIR  = ROOT / \"parquet_files\" / \"test\"","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.853232Z","iopub.execute_input":"2024-05-25T18:39:24.854093Z","iopub.status.idle":"2024-05-25T18:39:24.861645Z","shell.execute_reply.started":"2024-05-25T18:39:24.854063Z","shell.execute_reply":"2024-05-25T18:39:24.860488Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data_store = {\n    \"df_base\": read_file(TRAIN_DIR / \"train_base.parquet\"),\n    \"depth_0\": [\n        read_file(TRAIN_DIR / \"train_static_cb_0.parquet\"),\n        read_files(TRAIN_DIR / \"train_static_0_*.parquet\"),\n    ],\n    \"depth_1\": [\n        read_files(TRAIN_DIR / \"train_applprev_1_*.parquet\", 1),\n        read_file(TRAIN_DIR / \"train_tax_registry_a_1.parquet\", 1),\n        read_file(TRAIN_DIR / \"train_tax_registry_b_1.parquet\", 1),\n        read_file(TRAIN_DIR / \"train_tax_registry_c_1.parquet\", 1),\n        read_files(TRAIN_DIR / \"train_credit_bureau_a_1_*.parquet\", 1),\n        read_file(TRAIN_DIR / \"train_credit_bureau_b_1.parquet\", 1),\n        read_file(TRAIN_DIR / \"train_other_1.parquet\", 1),\n        read_file(TRAIN_DIR / \"train_person_1.parquet\", 1),\n        read_file(TRAIN_DIR / \"train_deposit_1.parquet\", 1),\n        read_file(TRAIN_DIR / \"train_debitcard_1.parquet\", 1),\n    ],\n    \"depth_2\": [\n        read_file(TRAIN_DIR / \"train_credit_bureau_b_2.parquet\", 2),\n        read_files(TRAIN_DIR / \"train_credit_bureau_a_2_*.parquet\", 2),\n    ]\n}","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:39:24.863163Z","iopub.execute_input":"2024-05-25T18:39:24.863565Z","iopub.status.idle":"2024-05-25T18:45:56.446145Z","shell.execute_reply.started":"2024-05-25T18:39:24.863534Z","shell.execute_reply":"2024-05-25T18:45:56.443992Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_train = feature_eng(**data_store)\nprint(\"train data shape:\\t\", df_train.shape)","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:45:56.448851Z","iopub.execute_input":"2024-05-25T18:45:56.449247Z","iopub.status.idle":"2024-05-25T18:46:17.334627Z","shell.execute_reply.started":"2024-05-25T18:45:56.449219Z","shell.execute_reply":"2024-05-25T18:46:17.333465Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del data_store\ngc.collect()","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:46:17.335741Z","iopub.execute_input":"2024-05-25T18:46:17.336105Z","iopub.status.idle":"2024-05-25T18:46:18.118409Z","shell.execute_reply.started":"2024-05-25T18:46:17.336076Z","shell.execute_reply":"2024-05-25T18:46:18.117226Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_train = df_train.pipe(Pipeline.filter_cols)\n\nassert len(df_train.shape) == 2\nprint(\"train data shape:\\t\", df_train.shape)","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:46:18.119700Z","iopub.execute_input":"2024-05-25T18:46:18.120103Z","iopub.status.idle":"2024-05-25T18:46:22.605105Z","shell.execute_reply.started":"2024-05-25T18:46:18.120073Z","shell.execute_reply":"2024-05-25T18:46:22.603689Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_train, cat_cols = to_pandas(df_train)\ntrain_cols = list(df_train.columns)","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:46:22.606889Z","iopub.execute_input":"2024-05-25T18:46:22.607326Z","iopub.status.idle":"2024-05-25T18:46:50.851381Z","shell.execute_reply.started":"2024-05-25T18:46:22.607285Z","shell.execute_reply":"2024-05-25T18:46:50.849931Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\n\npreprocessor = Preprocessor()\npreprocessor.fit(df_train)\n\nX, y = preprocessor.transform(df_train)","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:46:50.853121Z","iopub.execute_input":"2024-05-25T18:46:50.853576Z","iopub.status.idle":"2024-05-25T18:56:45.286885Z","shell.execute_reply.started":"2024-05-25T18:46:50.853532Z","shell.execute_reply":"2024-05-25T18:56:45.284290Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"assert df_train.shape[0] == X.shape[0]\nassert y.shape == (X.shape[0],)\nassert (X.index == y.index).all()\n\nfor i in range(1, X.shape[0]):\n    assert X['WEEK_NUM'].iloc[i - 1] <= X['WEEK_NUM'].iloc[i]\n\nfor col in X.columns:\n    assert X[col].isna().sum() <= 0.95 * X.shape[0]\n    assert X[col].unique().shape[0] >= 2\n    assert X[col].dtype.name != 'object'\n\n    if X[col].dtype.name == 'category':\n        assert X[col].isna().sum() == 0","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:56:45.294479Z","iopub.execute_input":"2024-05-25T18:56:45.294921Z","iopub.status.idle":"2024-05-25T18:57:48.422720Z","shell.execute_reply.started":"2024-05-25T18:56:45.294887Z","shell.execute_reply":"2024-05-25T18:57:48.421693Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"date_cols = [\n    'WEEK_NUM',\n    'max_refreshdate_3813885D',\n    'min_refreshdate_3813885D',\n]","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:57:48.424144Z","iopub.execute_input":"2024-05-25T18:57:48.424469Z","iopub.status.idle":"2024-05-25T18:57:48.435221Z","shell.execute_reply.started":"2024-05-25T18:57:48.424443Z","shell.execute_reply":"2024-05-25T18:57:48.434030Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_date_df = df_train[date_cols]","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:57:48.438942Z","iopub.execute_input":"2024-05-25T18:57:48.439297Z","iopub.status.idle":"2024-05-25T18:57:48.742093Z","shell.execute_reply.started":"2024-05-25T18:57:48.439271Z","shell.execute_reply":"2024-05-25T18:57:48.740659Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"(train_date_df.isna().sum() / train_date_df.shape[0]).sort_values()","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:57:48.744155Z","iopub.execute_input":"2024-05-25T18:57:48.744601Z","iopub.status.idle":"2024-05-25T18:57:48.896523Z","shell.execute_reply.started":"2024-05-25T18:57:48.744561Z","shell.execute_reply":"2024-05-25T18:57:48.895313Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del train_date_df\ndel df_train\ndel X\ndel y\ngc.collect()","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:57:48.897920Z","iopub.execute_input":"2024-05-25T18:57:48.898265Z","iopub.status.idle":"2024-05-25T18:57:49.054124Z","shell.execute_reply.started":"2024-05-25T18:57:48.898236Z","shell.execute_reply":"2024-05-25T18:57:49.053012Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Test Files","metadata":{}},{"cell_type":"code","source":"data_store = {\n    \"df_base\": read_file(TEST_DIR / \"test_base.parquet\"),\n    \"depth_0\": [\n        read_file(TEST_DIR / \"test_static_cb_0.parquet\"),\n        read_files(TEST_DIR / \"test_static_0_*.parquet\"),\n    ],\n    \"depth_1\": [\n        read_files(TEST_DIR / \"test_applprev_1_*.parquet\", 1),\n        read_file(TEST_DIR / \"test_tax_registry_a_1.parquet\", 1),\n        read_file(TEST_DIR / \"test_tax_registry_b_1.parquet\", 1),\n        read_file(TEST_DIR / \"test_tax_registry_c_1.parquet\", 1),\n        read_files(TEST_DIR / \"test_credit_bureau_a_1_*.parquet\", 1),\n        read_file(TEST_DIR / \"test_credit_bureau_b_1.parquet\", 1),\n        read_file(TEST_DIR / \"test_other_1.parquet\", 1),\n        read_file(TEST_DIR / \"test_person_1.parquet\", 1),\n        read_file(TEST_DIR / \"test_deposit_1.parquet\", 1),\n        read_file(TEST_DIR / \"test_debitcard_1.parquet\", 1),\n    ],\n    \"depth_2\": [\n        read_file(TEST_DIR / \"test_credit_bureau_b_2.parquet\", 2),\n        read_files(TEST_DIR / \"test_credit_bureau_a_2_*.parquet\", 2),\n    ]\n}","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:57:49.055821Z","iopub.execute_input":"2024-05-25T18:57:49.056195Z","iopub.status.idle":"2024-05-25T18:57:49.426470Z","shell.execute_reply.started":"2024-05-25T18:57:49.056165Z","shell.execute_reply":"2024-05-25T18:57:49.425368Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_test = feature_eng(**data_store)\nprint(\"test data shape:\\t\", df_test.shape)","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:57:49.428059Z","iopub.execute_input":"2024-05-25T18:57:49.428503Z","iopub.status.idle":"2024-05-25T18:57:49.530232Z","shell.execute_reply.started":"2024-05-25T18:57:49.428463Z","shell.execute_reply":"2024-05-25T18:57:49.529001Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del data_store\ngc.collect()","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:57:49.531798Z","iopub.execute_input":"2024-05-25T18:57:49.532789Z","iopub.status.idle":"2024-05-25T18:57:49.653684Z","shell.execute_reply.started":"2024-05-25T18:57:49.532724Z","shell.execute_reply":"2024-05-25T18:57:49.652334Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_test = df_test.select([col for col in train_cols if col != \"target\"])\nassert len(df_test.shape) == 2\nprint(\"test data shape:\\t\", df_test.shape)","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:57:49.655365Z","iopub.execute_input":"2024-05-25T18:57:49.655779Z","iopub.status.idle":"2024-05-25T18:57:49.668058Z","shell.execute_reply.started":"2024-05-25T18:57:49.655735Z","shell.execute_reply":"2024-05-25T18:57:49.666701Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_test, _ = to_pandas(df_test, cat_cols)","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:57:49.669706Z","iopub.execute_input":"2024-05-25T18:57:49.670166Z","iopub.status.idle":"2024-05-25T18:57:49.748488Z","shell.execute_reply.started":"2024-05-25T18:57:49.670125Z","shell.execute_reply":"2024-05-25T18:57:49.747077Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_test, _ = preprocessor.transform(df_test)","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:57:49.750817Z","iopub.execute_input":"2024-05-25T18:57:49.751279Z","iopub.status.idle":"2024-05-25T18:57:50.039351Z","shell.execute_reply.started":"2024-05-25T18:57:49.751238Z","shell.execute_reply":"2024-05-25T18:57:50.037851Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_test.shape, X_test.shape","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:57:50.041058Z","iopub.execute_input":"2024-05-25T18:57:50.041526Z","iopub.status.idle":"2024-05-25T18:57:50.049974Z","shell.execute_reply.started":"2024-05-25T18:57:50.041484Z","shell.execute_reply":"2024-05-25T18:57:50.048818Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_date_df = df_test[date_cols]","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:57:50.051727Z","iopub.execute_input":"2024-05-25T18:57:50.052535Z","iopub.status.idle":"2024-05-25T18:57:50.059484Z","shell.execute_reply.started":"2024-05-25T18:57:50.052503Z","shell.execute_reply":"2024-05-25T18:57:50.057846Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del df_test\ngc.collect()","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:57:50.061070Z","iopub.execute_input":"2024-05-25T18:57:50.061439Z","iopub.status.idle":"2024-05-25T18:57:50.248503Z","shell.execute_reply.started":"2024-05-25T18:57:50.061409Z","shell.execute_reply":"2024-05-25T18:57:50.247345Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Prediction","metadata":{}},{"cell_type":"code","source":"get_lgb_standard_loss_predictions(\n    X_test.drop(columns=['WEEK_NUM']),\n    folds_n=7\n)","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:57:50.249929Z","iopub.execute_input":"2024-05-25T18:57:50.250297Z","iopub.status.idle":"2024-05-25T18:57:51.233958Z","shell.execute_reply.started":"2024-05-25T18:57:50.250269Z","shell.execute_reply":"2024-05-25T18:57:51.232823Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"get_cat_standard_loss_predictions(\n    X_test.drop(columns=['WEEK_NUM']),\n    folds_n=7\n)","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:57:51.235458Z","iopub.execute_input":"2024-05-25T18:57:51.235872Z","iopub.status.idle":"2024-05-25T18:57:51.537022Z","shell.execute_reply.started":"2024-05-25T18:57:51.235839Z","shell.execute_reply":"2024-05-25T18:57:51.535585Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"get_lgb_custom_loss_predictions(\n    X_test.drop(columns=['WEEK_NUM']),\n    folds_n=7\n)","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:57:51.538668Z","iopub.execute_input":"2024-05-25T18:57:51.539082Z","iopub.status.idle":"2024-05-25T18:57:52.069350Z","shell.execute_reply.started":"2024-05-25T18:57:51.539050Z","shell.execute_reply":"2024-05-25T18:57:52.068152Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"y_pred = get_predictions(\n    X_test.drop(columns=['WEEK_NUM']),\n    folds_n=7\n)","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:57:52.070859Z","iopub.execute_input":"2024-05-25T18:57:52.071244Z","iopub.status.idle":"2024-05-25T18:57:52.879379Z","shell.execute_reply.started":"2024-05-25T18:57:52.071214Z","shell.execute_reply":"2024-05-25T18:57:52.877991Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"y_pred","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:57:52.880983Z","iopub.execute_input":"2024-05-25T18:57:52.881353Z","iopub.status.idle":"2024-05-25T18:57:52.889160Z","shell.execute_reply.started":"2024-05-25T18:57:52.881321Z","shell.execute_reply":"2024-05-25T18:57:52.888028Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"t = (1.0 / 7.0) * (\n    # test_date_df['max_refreshdate_3813885D'].to_numpy() -\n    -1.0 * test_date_df['min_refreshdate_3813885D'].to_numpy()\n)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"assert t.shape == y_pred.shape\nassert len(t.shape) == len(y_pred.shape) == 1","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"np.random.seed(42)\ny_post = np.zeros(y_pred.shape[0])\n\nfor i in range(y_post.shape[0]):\n    left_border = 90.0\n    right_border = 145.0\n\n    delta = 0.0\n    if left_border <= t[i] <= right_border:\n        r = (t[i] - left_border) / (right_border - left_border)\n        delta = np.random.normal(0.0, 0.05 * (1.0 - r))\n\n    y_post[i] = y_pred[i] + delta","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:58:42.490275Z","iopub.execute_input":"2024-05-25T18:58:42.490734Z","iopub.status.idle":"2024-05-25T18:58:42.496632Z","shell.execute_reply.started":"2024-05-25T18:58:42.490693Z","shell.execute_reply":"2024-05-25T18:58:42.495337Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"y_post","metadata":{"execution":{"iopub.status.busy":"2024-05-25T19:15:35.426819Z","iopub.execute_input":"2024-05-25T19:15:35.427287Z","iopub.status.idle":"2024-05-25T19:15:35.432393Z","shell.execute_reply.started":"2024-05-25T19:15:35.427251Z","shell.execute_reply":"2024-05-25T19:15:35.431272Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Submission","metadata":{}},{"cell_type":"code","source":"df_subm = pd.read_csv(ROOT / \"sample_submission.csv\")\ndf_subm = df_subm.set_index(\"case_id\")\n\nassert (df_subm.index == X_test.index).all()\n\ndf_subm[\"score\"] = y_post","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:58:42.806906Z","iopub.execute_input":"2024-05-25T18:58:42.807263Z","iopub.status.idle":"2024-05-25T18:58:42.823453Z","shell.execute_reply.started":"2024-05-25T18:58:42.807234Z","shell.execute_reply":"2024-05-25T18:58:42.822099Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_subm","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:58:42.827320Z","iopub.execute_input":"2024-05-25T18:58:42.827659Z","iopub.status.idle":"2024-05-25T18:58:42.843384Z","shell.execute_reply.started":"2024-05-25T18:58:42.827630Z","shell.execute_reply":"2024-05-25T18:58:42.842403Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_subm.to_csv(\"submission.csv\")","metadata":{"execution":{"iopub.status.busy":"2024-05-25T18:58:42.844880Z","iopub.execute_input":"2024-05-25T18:58:42.845219Z","iopub.status.idle":"2024-05-25T18:58:42.855009Z","shell.execute_reply.started":"2024-05-25T18:58:42.845191Z","shell.execute_reply":"2024-05-25T18:58:42.853532Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}