{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Python librairies","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport cudf\nimport matplotlib.pyplot as plt","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-08-30T13:00:19.143506Z","iopub.execute_input":"2022-08-30T13:00:19.144408Z","iopub.status.idle":"2022-08-30T13:00:22.445611Z","shell.execute_reply.started":"2022-08-30T13:00:19.144357Z","shell.execute_reply":"2022-08-30T13:00:22.444648Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Variables","metadata":{}},{"cell_type":"code","source":"TRAIN_PATH = '../input/amex-data-integer-dtypes-parquet-format/train.parquet'\nTEST_PATH = '../input/amex-data-integer-dtypes-parquet-format/test.parquet'","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:00:22.447532Z","iopub.execute_input":"2022-08-30T13:00:22.447859Z","iopub.status.idle":"2022-08-30T13:00:22.454373Z","shell.execute_reply.started":"2022-08-30T13:00:22.447832Z","shell.execute_reply":"2022-08-30T13:00:22.453423Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# functions","metadata":{}},{"cell_type":"code","source":"def read_dataset(path, fmt='csv', fillnan=None) :\n    \"\"\"\n    Function to read dataset in different formats, with an option to fill NaN values\n        \n    cat_feat = [\"B_30\",\"B_38\",\"D_114\",\"D_116\",\"D_117\",\"D_120\",\\\n                    \"D_126\",\"D_63\",\"D_64\",\"D_66\",\"D_68\"]\n    obj_features = ['D_63', 'D_64']\n    \"\"\"\n    if fmt == 'csv' :\n        data = cudf.read_csv(path)\n    elif fmt == 'feather' :\n        data = cudf.read_feather(path)\n    elif fmt == 'parquet' :\n        data = cudf.read_parquet(path)\n    else :\n        print(f\"Unknown format {fmt}\")\n        return None\n    data['customer_ID'] = data['customer_ID'].str[-16:].str.hex_to_int().astype('int64')\n    if 'S_2' in data.columns :\n        data.S_2 = cudf.to_datetime( data.S_2 )\n    if 'target' in data.columns and len(data.columns)>2:\n        data.drop('target', axis=1, inplace=True)\n    if fillnan is not None :\n        data = data.fillna(fillnan)\n    return data.set_index('customer_ID')","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:00:22.456045Z","iopub.execute_input":"2022-08-30T13:00:22.456427Z","iopub.status.idle":"2022-08-30T13:00:22.466411Z","shell.execute_reply.started":"2022-08-30T13:00:22.456392Z","shell.execute_reply":"2022-08-30T13:00:22.465460Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Load dataset","metadata":{}},{"cell_type":"markdown","source":"## Labels","metadata":{}},{"cell_type":"code","source":"train_labels = read_dataset('../input/amex-default-prediction/train_labels.csv', fmt='csv')\ntrain_labels.head()","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:00:23.813792Z","iopub.execute_input":"2022-08-30T13:00:23.814487Z","iopub.status.idle":"2022-08-30T13:00:26.401870Z","shell.execute_reply.started":"2022-08-30T13:00:23.814454Z","shell.execute_reply":"2022-08-30T13:00:26.400910Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(f\"Numbers of customers : {len(train_labels)}\")\nprint(f\"Numbers of duplicates : {train_labels.to_pandas().index.duplicated().sum()}\")\nprint(f\"Numbers of NaN : {train_labels.isna().any().sum()}\")\ntrain_labels.to_pandas().target.value_counts().plot.pie()","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:00:26.403762Z","iopub.execute_input":"2022-08-30T13:00:26.404636Z","iopub.status.idle":"2022-08-30T13:00:26.580873Z","shell.execute_reply.started":"2022-08-30T13:00:26.404599Z","shell.execute_reply":"2022-08-30T13:00:26.579542Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Train label database does not contained duplicated neither NaN value","metadata":{}},{"cell_type":"code","source":"train_labels.head()","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:00:26.582661Z","iopub.execute_input":"2022-08-30T13:00:26.583390Z","iopub.status.idle":"2022-08-30T13:00:26.609226Z","shell.execute_reply.started":"2022-08-30T13:00:26.583350Z","shell.execute_reply":"2022-08-30T13:00:26.607989Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Data\nThe dataset of this competition has a considerable size.\nIf you read the original csv files, the data barely fits into memory.\nThat's why we read the data from @RADAR's AMEX data - integer dtypes - parquet format dataset.\nIn this Feather file, the floating point precision has been reduced from 64 bit to 16 bit.\nAnd reading a parquet file is faster than reading a csv file because the parquet file\nformat is binary.","metadata":{}},{"cell_type":"code","source":"train = read_dataset(TRAIN_PATH, fmt='parquet').to_pandas()\ntest = read_dataset(TEST_PATH, fmt='parquet')\nwith pd.option_context(\"display.min_rows\", 6):\n    display(train)","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:00:28.105566Z","iopub.execute_input":"2022-08-30T13:00:28.106249Z","iopub.status.idle":"2022-08-30T13:01:26.613455Z","shell.execute_reply.started":"2022-08-30T13:00:28.106205Z","shell.execute_reply":"2022-08-30T13:01:26.612247Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Label and  the train database are merge","metadata":{}},{"cell_type":"code","source":"train = train.merge(train_labels.to_pandas(), left_index=True, right_index=True, how='left')\ndel train_labels","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:02:09.937288Z","iopub.execute_input":"2022-08-30T13:02:09.937654Z","iopub.status.idle":"2022-08-30T13:05:28.066372Z","shell.execute_reply.started":"2022-08-30T13:02:09.937624Z","shell.execute_reply":"2022-08-30T13:05:28.065373Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train = train.reset_index()\ntest = test.reset_index()","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:05:28.068112Z","iopub.execute_input":"2022-08-30T13:05:28.068461Z","iopub.status.idle":"2022-08-30T13:05:35.356424Z","shell.execute_reply.started":"2022-08-30T13:05:28.068423Z","shell.execute_reply":"2022-08-30T13:05:35.355206Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"count = train.customer_ID.value_counts()\nprint(f\"Numbers of rows : {len(train)}\")\nprint(f\"Numbers of duplicates : {len(count[count>1])}\")\nprint(f\"Numbers of variables : {len(train.columns)}\")\nprint(f\"Numbers of NaN : {train.isna().any().sum()}\")","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:05:40.468359Z","iopub.execute_input":"2022-08-30T13:05:40.469107Z","iopub.status.idle":"2022-08-30T13:05:40.991782Z","shell.execute_reply.started":"2022-08-30T13:05:40.469071Z","shell.execute_reply":"2022-08-30T13:05:40.990673Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":" This dataset contains 190 variables. Features are anonymized and normalized,\n and fall into the following general categories:\n \nD_* = Delinquency variables\nS_* = Spend variables\nP_* = Payment variables\nB_* = Balance variables\nR_* = Risk variables\n\nMany IDs are duplicated since each operation is reported.\nThe variable S_2 is the date of each operations","metadata":{}},{"cell_type":"markdown","source":"# Exploration Data Analysis\nAn interesting EDA was performed by @AMBROSM.\nThis part is inspired by his notebook.","metadata":{}},{"cell_type":"markdown","source":"## NaN values","metadata":{}},{"cell_type":"code","source":"counter = train.isnull().sum(axis=0).sort_values(ascending=False)/len(train)*100\ncounter.hist(bins=50)","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:05:47.544466Z","iopub.execute_input":"2022-08-30T13:05:47.544854Z","iopub.status.idle":"2022-08-30T13:05:49.297541Z","shell.execute_reply.started":"2022-08-30T13:05:47.544820Z","shell.execute_reply":"2022-08-30T13:05:49.296569Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Some features are majoritary not filled,\nthe completion will not be efficient\nso we decide to remove features with more 50% NaN values.","metadata":{}},{"cell_type":"code","source":"rm_feat = counter[counter>50].index.tolist()\nrm_feat","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:05:51.606633Z","iopub.execute_input":"2022-08-30T13:05:51.607028Z","iopub.status.idle":"2022-08-30T13:05:51.615286Z","shell.execute_reply.started":"2022-08-30T13:05:51.606998Z","shell.execute_reply":"2022-08-30T13:05:51.614110Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Counting the statements per customer","metadata":{}},{"cell_type":"markdown","source":"Now we can count how many rows (credit card statements) there are per customer.\nWe see that 80 % of the customers have 13 statements;\nthe other 20 % of the customers have between 1 and 12 statements.\n​  \n**Insight:** Our model will have to deal with a variable-sized input per customer\n(unless we simplify our life and look only at the most recent statement as @inversion suggests\n[here](https://www.kaggle.com/competitions/amex-default-prediction/discussion/327094)\nor at the average over all statements).","metadata":{}},{"cell_type":"code","source":"train","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:05:54.240737Z","iopub.execute_input":"2022-08-30T13:05:54.241360Z","iopub.status.idle":"2022-08-30T13:05:54.706765Z","shell.execute_reply.started":"2022-08-30T13:05:54.241326Z","shell.execute_reply":"2022-08-30T13:05:54.705665Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))\ntrain_sc = train.customer_ID.value_counts().value_counts()\ntrain_sc = train_sc.sort_index(ascending=False).rename('Train statements per customer')\nax1.pie(train_sc, labels=train_sc.index)\nax1.set_title(train_sc.name)\ntest_sc = test.customer_ID.value_counts().value_counts()\ntest_sc = test_sc.sort_index(ascending=False).rename('Test statements per customer')\ntest_sc.index.to_pandas()\nax2.pie(test_sc.to_array(), labels=test_sc.index.to_pandas())\nax2.set_title(test_sc.name)\nplt.show()\n","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:14:50.326018Z","iopub.execute_input":"2022-08-30T13:14:50.326630Z","iopub.status.idle":"2022-08-30T13:14:50.748270Z","shell.execute_reply.started":"2022-08-30T13:14:50.326595Z","shell.execute_reply":"2022-08-30T13:14:50.746977Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's find out when these customers got their last statement.\nThe histogram of the last statement dates shows that every train\ncustomer got his last statement in March of 2018. The first four\nSaturdays (March 3, 10, 17, 24) have more statements than an average day.\n\nThe test customers are split in two: half of them got their last statement in April of 2019\nand half in October of 2019. As was [discussed here]\n(https://www.kaggle.com/competitions/amex-default-prediction/discussion/327602),\nthe April 2019 data is used for the public leaderboard and the October 2019 data is used\nfor the private leaderboard.","metadata":{}},{"cell_type":"code","source":"temp = train.S_2.groupby(train.customer_ID).max()\nplt.figure(figsize=(16, 4))\nplt.hist(temp, bins=pd.date_range(\"2018-03-01\", \"2018-04-01\", freq=\"d\"),\n         rwidth=0.8, color='#ffd700')\nplt.title('When did the train customers get their last statements?', fontsize=20)\nplt.xlabel('Last statement date per customer')\nplt.ylabel('Count')\nplt.gca().set_facecolor('#0057b8')\nplt.show()\ndel temp\n\ntemp = test.S_2.groupby(test.customer_ID).max().to_pandas()\nplt.figure(figsize=(16, 4))\nplt.hist(temp, bins=pd.date_range(\"2019-04-01\", \"2019-11-01\", freq=\"d\"),\n         rwidth=0.74, color='#ffd700')\nplt.title('When did the test customers get their last statements?', fontsize=20)\nplt.xlabel('Last statement date per customer')\nplt.ylabel('Count')\nplt.gca().set_facecolor('#0057b8')\nplt.show()\ndel temp","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:16:39.428128Z","iopub.execute_input":"2022-08-30T13:16:39.428688Z","iopub.status.idle":"2022-08-30T13:16:40.481712Z","shell.execute_reply.started":"2022-08-30T13:16:39.428649Z","shell.execute_reply":"2022-08-30T13:16:40.480656Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Insight:** Although the data are a kind of time series,\nwe cannot cross-validate with a TimeSeriesSplit because all training happens in the same month.\n\nFor most customers, the first and last statement is about a year apart.\nTogether with the fact that we typically have 13 statements per customer,\nthis indicates that the customers get one credit card statement every month.","metadata":{}},{"cell_type":"code","source":"temp = train.S_2.groupby(train.customer_ID).agg(['max', 'min'])\nplt.figure(figsize=(16, 3))\nplt.hist((temp['max'] - temp['min']).dt.days, bins=400, color='#ffd700')\nplt.xlabel('days')\nplt.ylabel('count')\nplt.title('Number of days between first and last statement of customer (train)', fontsize=20)\nplt.gca().set_facecolor('#0057b8')\nplt.show()\n\ntemp = test.S_2.groupby(test.customer_ID).agg(['max', 'min']).to_pandas()\nplt.figure(figsize=(16, 3))\nplt.hist((temp['max'] - temp['min']).dt.days, bins=400, color='#ffd700')\nplt.xlabel('days')\nplt.ylabel('count')\nplt.title('Number of days between first and last statement of customer (test)', fontsize=20)\nplt.gca().set_facecolor('#0057b8')\nplt.show()\ndel temp","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:16:53.882891Z","iopub.execute_input":"2022-08-30T13:16:53.883475Z","iopub.status.idle":"2022-08-30T13:16:55.646750Z","shell.execute_reply.started":"2022-08-30T13:16:53.883433Z","shell.execute_reply":"2022-08-30T13:16:55.645632Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"If we color every statement (i.e. row of train or test) according to the dataset it belongs\n(training, public lb, and private lb), we see that every dataset covers thirteen months.\nTrain and test don't overlap, but public and private lb periods overlap.","metadata":{}},{"cell_type":"code","source":"temp = pd.concat([train[['customer_ID', 'S_2']], test[['customer_ID', 'S_2']].to_pandas()], axis=0)\ntemp.set_index('customer_ID', inplace=True)\ntemp['last_month'] = temp.groupby('customer_ID').S_2.max().dt.month\nlast_month = temp['last_month'].values\n\nplt.figure(figsize=(16, 4))\nplt.hist([temp.S_2[temp.last_month == 3],   # ending 03/18 -> training\n          temp.S_2[temp.last_month == 4],   # ending 04/19 -> public lb\n          temp.S_2[temp.last_month == 10]], # ending 10/19 -> private lb\n         bins=pd.date_range(\"2017-03-01\", \"2019-11-01\", freq=\"MS\"),\n         label=['Training', 'Public leaderboard', 'Private leaderboard'],\n         stacked=True)\nplt.xticks(pd.date_range(\"2017-03-01\", \"2019-11-01\", freq=\"QS\"))\nplt.xlabel('Statement date')\nplt.ylabel('Count')\nplt.title('The three datasets over time', fontsize=20)\nplt.legend()\nplt.show()\n","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:19:18.331428Z","iopub.execute_input":"2022-08-30T13:19:18.331812Z","iopub.status.idle":"2022-08-30T13:19:22.585644Z","shell.execute_reply.started":"2022-08-30T13:19:18.331779Z","shell.execute_reply":"2022-08-30T13:19:22.584631Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now we'll look at the distribution of missing values over time.\nB_29 is most interesting. Given the each of the three datasets has almost half a million\ncustomers, we see that until May of 2019 fewer than a tenth of the customers\nhave a value for B_29. The other nine tenths are missing. Starting in June of 2019,\nwe have B_29 data for almost every customer.\n\n**Insight:** The distribution of the missing B_29 differs between train and test datasets.\nWhereas in the training and public leaderboard data >90 % are missing,\nduring the last five months of private leaderboard, we have B_29 data for almost every customer.\nIf we use this feature, we should be prepared for surprises in the private leaderboard.\nIs it better to drop the feature?","metadata":{}},{"cell_type":"code","source":"for f in [ 'B_29', 'S_9','D_87']:\n    #, 'D_88', 'R_26', 'R_27', 'D_108', 'D_110', 'D_111', 'B_39', 'B_42']:\n    temp = pd.concat([train[[f, 'S_2']], test[[f, 'S_2']].to_pandas()], axis=0)\n    temp['last_month'] = last_month\n    temp['has_f'] = ~temp[f].isna()\n\n    plt.figure(figsize=(16, 4))\n    plt.hist([temp.S_2[temp.has_f & (temp.last_month == 3)],   # ending 03/18 -> training\n              temp.S_2[temp.has_f & (temp.last_month == 4)],   # ending 04/19 -> public lb\n              temp.S_2[temp.has_f & (temp.last_month == 10)]], # ending 10/19 -> private lb\n             bins=pd.date_range(\"2017-03-01\", \"2019-11-01\", freq=\"MS\"),\n             label=['Training', 'Public leaderboard', 'Private leaderboard'],\n             stacked=True)\n    plt.xticks(pd.date_range(\"2017-03-01\", \"2019-11-01\", freq=\"QS\"))\n    plt.xlabel('Statement date')\n    plt.ylabel(f'Count of {f} non-null values')\n    plt.title(f'{f} non-null values over time', fontsize=20)\n    plt.legend()\n    plt.show()\n","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:19:32.326096Z","iopub.execute_input":"2022-08-30T13:19:32.326468Z","iopub.status.idle":"2022-08-30T13:19:39.238534Z","shell.execute_reply.started":"2022-08-30T13:19:32.326439Z","shell.execute_reply":"2022-08-30T13:19:39.237605Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## The categorical features","metadata":{}},{"cell_type":"markdown","source":"According to the data description, there are eleven categorical features.\nWe plot histograms for target=0 and target=1.\nFor the ten features which have missing values,\nthe missing values are represented by the rightmost bar of the histogram.","metadata":{}},{"cell_type":"code","source":"cat_features = ['B_30', 'B_38', 'D_114', 'D_116', 'D_117', 'D_120',\\\n                'D_126', 'D_63', 'D_64', 'D_66', 'D_68']\nplt.figure(figsize=(16, 16))\nfor i, f in enumerate(cat_features):\n    plt.subplot(4, 3, i+1)\n    temp = train[f][train.target == 0].value_counts(dropna=False, normalize=True)\n    temp = pd.DataFrame(temp.sort_index().rename('count'))\n    temp.index.name = 'value'\n    temp.reset_index(inplace=True)\n    plt.bar(temp.index, temp['count'], alpha=0.5, label='target=0')\n    temp = train[f][train.target == 1].value_counts(dropna=False, normalize=True)\n    temp = pd.DataFrame(temp.sort_index().rename('count'))\n    temp.index.name = 'value'\n    temp.reset_index(inplace=True)\n    plt.bar(temp.index, temp['count'], alpha=0.5, label='target=1')\n    plt.xlabel(f)\n    plt.ylabel('frequency')\n    plt.legend()\n    plt.xticks(temp.index, temp.value)\nplt.suptitle('Categorical features', fontsize=20, y=0.93)\nplt.show()\ndel temp","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:19:39.240367Z","iopub.execute_input":"2022-08-30T13:19:39.240938Z","iopub.status.idle":"2022-08-30T13:19:41.350751Z","shell.execute_reply.started":"2022-08-30T13:19:39.240898Z","shell.execute_reply":"2022-08-30T13:19:41.349850Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Insight:**\n- Every feature has at most eight categories (including a nan category). One-hot encodings are feasible.\n- The distributions for target=0 and target=1 differ. This means that every feature gives some information about the target.\n","metadata":{}},{"cell_type":"markdown","source":"## The binary features\n\nTwo features are binary:\n- B_31 is always 0 or 1.\n- D_87 is always 1 or missing.","metadata":{}},{"cell_type":"code","source":"bin_features = ['B_31', 'D_87']\nplt.figure(figsize=(16, 4))\nfor i, f in enumerate(bin_features):\n    plt.subplot(1, 2, i+1)\n    temp = train[f][train.target == 0].value_counts(dropna=False, normalize=True)\n    temp = pd.DataFrame(temp.sort_index().rename('count'))\n    temp.index.name = 'value'\n    temp.reset_index(inplace=True)\n    plt.bar(temp.index, temp['count'], alpha=0.5, label='target=0')\n    temp = train[f][train.target == 1].value_counts(dropna=False, normalize=True)\n    temp = pd.DataFrame(temp.sort_index().rename('count'))\n    temp.index.name = 'value'\n    temp.reset_index(inplace=True)\n    plt.bar(temp.index, temp['count'], alpha=0.5, label='target=1')\n    plt.xlabel(f)\n    plt.ylabel('frequency')\n    plt.legend()\n    plt.xticks(temp.index, temp.value)\nplt.suptitle('Binary features', fontsize=20)\nplt.show()\ndel temp","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:19:47.081615Z","iopub.execute_input":"2022-08-30T13:19:47.082313Z","iopub.status.idle":"2022-08-30T13:19:47.506623Z","shell.execute_reply.started":"2022-08-30T13:19:47.082272Z","shell.execute_reply":"2022-08-30T13:19:47.505633Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Insight:** If you impute missing values for D_87,\ndon't fall into the trap of imputing the mean - the feature would become useless...\n","metadata":{}},{"cell_type":"markdown","source":"## The numerical features\n\nIf we plot histograms of the 175 numerical features,\nwe see that they have all kinds of distributions:","metadata":{}},{"cell_type":"code","source":"cont_features = sorted([f for f in train.columns \n                        if f not in cat_features + bin_features + ['customer_ID', 'target', 'S_2']])\nprint(len(cont_features))\n# print(cont_features)\nNCOLS = 4\nfor i, f in enumerate(cont_features):\n    if i % NCOLS == 0:\n        if i > 0:\n            plt.show()\n        plt.figure(figsize=(16, 3))\n        if i == 0:\n    plt.subplot(1, NCOLS, i % NCOLS + 1)\n    plt.hist(train[f], bins=200)\n    plt.xlabel(f)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-08-30T13:19:50.705214Z","iopub.execute_input":"2022-08-30T13:19:50.705572Z","iopub.status.idle":"2022-08-30T13:21:20.803666Z","shell.execute_reply.started":"2022-08-30T13:19:50.705542Z","shell.execute_reply":"2022-08-30T13:21:20.802644Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Insight:** Histograms with white space at the left or right end can indicate\nthat the data contain outliers. We will have to deal with these outliers.\nBut are these data really outliers? Maybe they are, but they could as well be\nlegitimate traces of rare events. We do not know...","metadata":{}},{"cell_type":"code","source":"del train, test, train_labels","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}