{"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":"gpu","dataSources":[{"sourceId":50160,"databundleVersionId":7921029,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-04-18T08:30:12.374546Z","iopub.execute_input":"2024-04-18T08:30:12.374929Z","iopub.status.idle":"2024-04-18T08:30:12.390533Z","shell.execute_reply.started":"2024-04-18T08:30:12.374900Z","shell.execute_reply":"2024-04-18T08:30:12.389632Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Import Python modules","metadata":{}},{"cell_type":"code","source":"import warnings\nwarnings.filterwarnings(\"ignore\") # Ignore (that is, do not print) warnings\nimport matplotlib.pyplot as plt\nfrom IPython.display import Markdown # Markdown (to output Markdown from Python code)\nimport time # time - to compute elapsed running time\n\n# Manage data\nimport glob  # glob - to search for files whose names follow some pattern\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport polars as pl # polars - to efficiently manage (better than pandas) large quantities of data and to deal with tables\nimport seaborn as sns # Searborn - to plot statistical data\nimport collections # collections - for alternative containers. Useful for counting\n\n# Modelling\n# LightGBM - for applying gradient-boosting algorithms\nimport lightgbm as lgb\nfrom sklearn.model_selection import GridSearchCV\nfrom sklearn.metrics import (\n    accuracy_score,\n    roc_curve,\n    roc_auc_score,\n    classification_report,\n    confusion_matrix,\n    ConfusionMatrixDisplay,\n)\nimport shap","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:12.392322Z","iopub.execute_input":"2024-04-18T08:30:12.392609Z","iopub.status.idle":"2024-04-18T08:30:12.399278Z","shell.execute_reply.started":"2024-04-18T08:30:12.392587Z","shell.execute_reply":"2024-04-18T08:30:12.398319Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Competition data in the kaggle kernel","metadata":{}},{"cell_type":"code","source":"# Path to data directory in the kaggle kernel\nPATH_DATA_ROOT = \"/kaggle/input/home-credit-credit-risk-model-stability/\"\n\n# List of names for the data batches\nbatches = [\"train\", \"test\"]\n\n# Các đường dẫn đến thư mục dữ liệu Parquet training và test trong kernel của Kaggle\n# [NOTE: Xử lý các tệp Parquet thường nhanh hơn so với tệp CSV.]\nPATH_DATA = {\n    batch: f\"{PATH_DATA_ROOT}parquet_files/{batch}/\"\n    for batch in batches\n}","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:12.400422Z","iopub.execute_input":"2024-04-18T08:30:12.400767Z","iopub.status.idle":"2024-04-18T08:30:12.413721Z","shell.execute_reply.started":"2024-04-18T08:30:12.400743Z","shell.execute_reply":"2024-04-18T08:30:12.412865Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Create polars dataframe to train and test\nChọn sử dụng polars thay vì pandas để tạo các DataFrame vì polars được cho là \"blazingly fast\" và hiệu quả hơn trong việc xử lý các tập dữ liệu lớn.\n\nĐịnh dạng dữ liệu và nguồn dữ liệu: Dữ liệu được cung cấp dưới dạng các tệp CSV và Parquet. Do Parquet nhanh hơn CSV trong việc xử lý, nên chúng tôi quyết định chọn Parquet. Thông tin về các tệp dữ liệu có sẵn trên [competition Data page](https://www.kaggle.com/competitions/home-credit-credit-risk-model-stability/data), có thể xem trong tệp feature_definitions.csv.\n## The data files\nCác tệp Parquet được sử dụng cho tập huấn luyện và tập kiểm tra trong cuộc thi. Các bảng dữ liệu chi tiết cung cấp thông tin chi tiết về từng cột trong các tệp dữ liệu. Các cột \"Used?\" và \"As?\" cho biết xem trường tương ứng có được sử dụng hay không, và cách chúng được sử dụng.\n\n* base files: Được biểu diễn bằng các file `csv_files/train/train_base.csv` cho tập huấn luyện và `csv_files/test/test_base.csv` cho tập kiểm tra. \n\n| Column          | Type | Description | Used? | As? |\n| --------------- | ---- | ----------- | ----- | --- |\n| `case_id`       | int  | A unique identifier for each credit case. | Yes | Case identifier |\n| `date_decision` | str  | Date of decision for the approval of the credit (format \"YYYY-MM-DD\"). | Yes | Auxiliar feature component, $x^{(j)}$ |\n| `MONTH`         | int  | Month of `date_decision` (format \"format YYYYMM\"). | No | --- |\n| `WEEK_NUM`      | int  | Number of weeks passed since `date_decision`. | Yes | Performance metric parameter |\n| `target`        | int  | Target (label) value ($1$ if the credit contract was defaulted, or $0$ if not). | Yes | Label, $y$ |\n\n* static internal files: Các file này có tên là `csv_files/train/train_static_0_0.csv` và `csv_files/train/train_static_0_1.csv` cho tập huấn luyện và`csv_files/test/test_static_0_0.csv`, `csv_files/test/test_static_0_1.csv` và `csv_files/test/test_static_0_2.csv` cho tập kiểm tra. Các tệp này chứa thông tin tĩnh về hợp đồng tín dụng, không có dữ liệu được thêm vào theo thời gian. Các tệp dữ liệu nội bộ tĩnh có các thành phần đặc trưng của tất cả các loại biến đổi ngoại trừ loại \"T\".\n\n| Column          | Type  | Description | Used? | As? |\n| --------------- | ----- | ----------- | ----- | --- |\n| `case_id`       | int   | Same as base files' `case_id`. | Yes | Case identifier |\n| `*P`     | float   | Feature component of P-type (\"transform days past due\"). | Yes | Feature component, $x^{(j)}$ |\n| `*M`     | str   | Feature component of M-type (\"masking categories\"). | Yes | Feature component, $x^{(j)}$ |\n| `*A`     | float | Feature component of A-type (\"transform amount\"). | Yes | Feature component, $x^{(j)}$ |\n| `*D`     | str   | Feature component of D-type (\"transform dates\"). | Yes | Feature component, $x^{(j)}$ |\n| `cntpmts24_3658933L`     | int | Number of monthly payments done in the last $24$ months and in the current one. | Yes | Auxiliar feature component, $x^{(j)}$ |\n| `mobilephncnt_593L` | int | Number of persons of the same contract using the same mobile phone number. | Yes | Feature component, $x^{(j)}$ |\n| `pmtnum_254L` | int | Total number of payments made by the applicant. | Yes | Feature component, $x^{(j)}$ |\n| `other *L`     | ---   | Feature component of L-type (\"unspecified transform\"). | Yes | Feature component, $x^{(j)}$ |\n\n* static external (from a Credit Bureau) files : các tệp `csv_files/train/train_static_cb_0.csv` cho tập huấn luyện và `csv_files/test/test_static_cb_0.csv` cho tập kiểm tra. Các tệp này có các thành phần đặc trưng của tất cả các loại biến đổi ngoại trừ \"P\". \n\n| Column          | Type  | Description | Used? | As? |\n| --------------- | ----- | ----------- | ----- | --- |\n| `case_id`       | int   | Same as base files' `case_id`. | Yes | Case identifier |\n| `*M`     | str   | Feature component of M-type (\"masking categories\"). | Yes | Feature component, $x^{(j)}$ |\n| `*A`     | float | Feature component of A-type (\"transform amount\"). | Yes | Feature component, $x^{(j)}$ |\n| `*D`     | str   | Feature component of D-type (\"transform dates\"). | Yes | Feature component, $x^{(j)}$ |\n| `*L`     | ---   | Feature component of L-type (\"unspecified transform\"). | Yes | Feature component, $x^{(j)}$ |\n| `*T`     | ---   | Feature component of T-type (\"unspecified transform\"). | Yes | Feature component, $x^{(j)}$ |\n\n\n* contract persons' data files : dữ liệu về thông tin người ký hợp đồng. Các tệp này có tên là `csv_files/train/train_person_1.csv` cho tập huấn luyện và `csv_files/test/test_person_1.csv` cho tập kiểm tra. Các tệp này chứa các thành phần đặc trưng của tất cả các loại biến đổi ngoại trừ loại \"P\".\n\n| Column                  | Type  | Description | Used?| As? |\n| ----------------------- | ----- | ----------- | ---- | --- |\n| `case_id`               | int   | Same as base files' `case_id`. | Yes | Case identifier |\n| `num_group1`            | int   | Identifier of the person in the respective credit case ($0$ identifies the applicant). | Yes | Person identifier |\n| `mainoccupationinc_max_A` <br> (derived) | float | Maximum income (from main occupation) of the set of people associated with each `case_id` group. This column derives from the `max` operation on the grouped values of the original column `mainoccupationinc_384A`. | Yes | Feature component, $x^{(j)}$ |\n| `anyselfemployed_T` <br> (derived)       | bool  | Boolean that holds `True` if any of the set of people of each `case_id` group is self-employed. This column derives from the `any` operation on the outcome of `== \"SELFEMPLOYED\"` applied to the grouped values of the original column `incometype_1044T`. | Yes | Feature component, $x^{(j)}$ |\n| `housetype_applicant_905L` <br> (derived)   | str   | House type of the applicant of each `case_id` group. This column derives from the values of the original column `housetype_905L` for which the values of the column `num_group1` are $0$.| Yes | Feature component, $x^{(j)}$ |\n| `sex_applicant_738L` <br> (derived)   | str   | Gender of the applicant of each `case_id` group. This column derives from the values of the original column `sex_738L` for which the values of the column `num_group1` are $0$.| Yes | Feature component, $x^{(j)}$ |\n| `birth_applicant_259D` <br> (derived)   | str   | Birth date of the applicant of each `case_id` group. This column derives from the values of the original column `birth_259D` for which the values of the column `num_group1` are $0$.| Yes | Auxiliar feature component, $x^{(j)}$ |\n| `empl_employedfrom_applicant_271D` <br> (derived)   | str   | Starting employment date of the applicant of each `case_id` group. This column derives from the values of the original column `empl_employedfrom_271D` for which the values of the column `num_group1` are $0$.| Yes | Auxiliar feature component, $x^{(j)}$ |\n| `*_applicant_*M` <br> (derived)    | str   | Feature component of M-type (\"masking categories\") associated with the applicant (that is, for which `num_group1` is $0$). | Yes | Feature component, $x^{(j)}$ |\n| `other *_applicant_*A` <br> (derived)   | float | Feature component of A-type (\"transform amount\") associated with the applicant (that is, for which `num_group1` is $0$). | Yes | Feature component, $x^{(j)}$ |\n| `other *_applicant_*D` <br> (derived)    | str   | Feature component of D-type (\"transform dates\") associated with the applicant (that is, for which `num_group1` is $0$). | Yes | Feature component, $x^{(j)}$ |\n| `other *_applicant_*L` <br> (derived)    | ---   | Feature component of L-type (\"unspecified transform\") associated with the applicant (that is, for which `num_group1` is $0$). | Yes | Feature component, $x^{(j)}$ |\n| `other *_applicant_*T` <br> (derived)    | ---   | Feature component of T-type (\"unspecified transform\") associated with the applicant (that is, for which `num_group1` is $0$). | Yes | Feature component, $x^{(j)}$ |    \n<br>\n\n* Credit Bureau B's data files : `csv_files/train/train_credit_bureau_b_2.csv` cho tập huấn luyện, and `csv_files/test/test_credit_bureau_b_2.csv` cho tập kiểm tra.\n\n| Column                    | Type  | Description | Used? | As? |\n| ------------------------- | ----- | ----------- | ----- | --- |\n| `case_id`                 | int   | Same as base files' `case_id`. | Yes | Case identifier |\n| `num_group1`              | int   | Identifier of the contract in the respective credit case. | No | --- |\n| `num_group2`              | int   | Identifier of the payment in the respective credit case. | No | --- |\n| `pmts_date_1107D`         | date  | Payment date. | No | --- |\n| `pmts_dpdvalue_anyover31_P` <br> (derived) | bool  | Boolean that holds `True` if there is any number of days of overdue payment greater than $31$ for each `case_id` group. This column derives from the `any` operation on the outcome of `> 31` applied to the grouped values of the original column `pmts_dpdvalue_108P`. | Yes | Feature component, $x^{(j)}$ |\n| `pmts_pmtsoverdue_max_A` <br> (derived) | float | Maximum value of the number of overdue payments for each `case_id` group. This columns derives from the `max` operation on the grouped values of the original column `pmts_pmtsoverdue_635A`. | Yes | Feature component, $x^{(j)}$ |","metadata":{}},{"cell_type":"code","source":"# Hàm thiết lập kiểu dữ liệu của các cột trong DataFrame của polars\ndef set_table_dtypes(df: pl.DataFrame) -> pl.DataFrame:\n    for col in df.columns:\n        # Nếu cột liên quan đến biến đổi loại P hoặc A, đặt kiểu dữ liệu của nó thành Float64\n        if col[-1] in (\"P\", \"A\"):\n            df = df.with_columns(pl.col(col).cast(pl.Float64))\n            \n        # Nếu cột liên quan đến biến đổi loại M, đặt kiểu dữ liệu của nó thành Categorical\n        # [NOTE: Kiểu sắp xếp được đặt thành \"lexical\" để các loại được sắp xếp theo thứ tự bảng chữ cái,\n        # thay vì thứ tự xuất hiện trong DataFrame (\"physical\").]\n        if col[-1] in (\"M\"):\n            df = df.with_columns(pl.col(col).cast(pl.Categorical(\"lexical\")))\n        \n        # Nếu cột liên quan đến biến đổi loại D, đặt kiểu dữ liệu của nó thành Date\n        if col[-1] in (\"D\"):\n            df = df.with_columns(pl.col(col).cast(pl.Date))\n        \n        # Nếu cột liên quan đến biến đổi loại L hoặc T, và nó không phải là kiểu số,\n        # đặt kiểu dữ liệu của nó thành Categorical\n        if col[-1] in (\"L\", \"T\") and not df[col].is_numeric:\n            df = df.with_columns(pl.col(col).cast(pl.Categorical(\"lexical\")))\n    return df","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:12.416031Z","iopub.execute_input":"2024-04-18T08:30:12.416625Z","iopub.status.idle":"2024-04-18T08:30:12.424203Z","shell.execute_reply.started":"2024-04-18T08:30:12.416595Z","shell.execute_reply":"2024-04-18T08:30:12.423383Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Tạo các DataFrame polars từ training và test data\n\n# Dictionary of polars dataframes\ndt_data = {\n    batch: {\n        # Định nghĩa DataFrame polars chứa dữ liệu từ base Parquet file\n        # [NOTE: Cột \"date_decision\" được chuyển đổi sang kiểu dữ liệu Date của polars.]\n        \"base\": (pl.read_parquet(f\"{PATH_DATA[batch]}{batch}_base.parquet\")\\\n                 .with_columns(pl.col(\"date_decision\").cast(pl.Date))),\n        # Định nghĩa DataFrame polars chứa dữ liệu từ static internal Parquet files\n        \"static\": pl.concat([pl.read_parquet(PATH_DATA_STATIC)\\\n                             .pipe(set_table_dtypes) for PATH_DATA_STATIC in\n                             glob.glob(f\"{PATH_DATA[batch]}{batch}_static_0*.parquet\")],\n                            # Nối theo chiều dọc, và đồng thời đặt lại kiểu dữ liệu của các cột\n                            how=\"vertical_relaxed\"),\n        # Định nghĩa DataFrame polars chứa dữ liệu từ các tệp Parquet static external (từ một Credit Bureau)\n        \"static_cb\": (pl.read_parquet(f\"{PATH_DATA[batch]}{batch}_static_cb_0.parquet\")\\\n                      .pipe(set_table_dtypes)),\n        # Định nghĩa DataFrame polars chứa dữ liệu từ tệp Parquet về người ở depth 1\n        \"person_1\": (pl.read_parquet(f\"{PATH_DATA[batch]}{batch}_person_1.parquet\")\\\n                     .pipe(set_table_dtypes)),\n        # Định nghĩa DataFrame polars chứa dữ liệu từ tệp Parquet của Credit Bureau B ở depth 2\n        \"credit_bureau_b_2\": (pl.read_parquet(f\"{PATH_DATA[batch]}{batch}_credit_bureau_b_2.parquet\")\\\n                              .pipe(set_table_dtypes))\n    }   \n    for batch in batches\n}","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:12.426637Z","iopub.execute_input":"2024-04-18T08:30:12.427273Z","iopub.status.idle":"2024-04-18T08:30:23.190659Z","shell.execute_reply.started":"2024-04-18T08:30:12.427242Z","shell.execute_reply":"2024-04-18T08:30:23.189807Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Filter training and test dataframes\n\nfor batch in batches:\n    # Nhóm dữ liệu trong DataFrame person_1 theo cột case_id và tổng hợp thu nhập lớn nhất\n    # (từ nghề nghiệp chính) của các nhóm người liên quan, cũng như tạo cột boolean\n    # kiểm tra xem có bất kỳ người nào trong nhóm tự làm chủ không.\n    df_person_1_1 = dt_data[batch][\"person_1\"].group_by(\"case_id\").agg(\n        pl.col(\"mainoccupationinc_384A\").max().alias(\"mainoccupationinc_max_A\").cast(pl.Float64),\n        (pl.col(\"incometype_1044T\") == \"SELFEMPLOYED\").any().alias(\"anyselfemployed_T\").cast(pl.Boolean)\n    )\n\n    # Chọn các cột khác từ DataFrame person_1, loại bỏ dòng liên quan đến applicants \n    # (num_group1 = 0), loại bỏ cột \"num_group1\" và đổi tên các cột (ngoại trừ \"case_id\")\n    # để tham chiếu đến applicant.\n    df_person_1_2 = dt_data[batch][\"person_1\"].drop(\n        [\"mainoccupationinc_384A\", \"incometype_1044T\"])\\\n        .filter(pl.col(\"num_group1\") == 0).drop(\"num_group1\")\n    df_person_1_2 = df_person_1_2.rename(\n        {col: col.rsplit(\"_\",1)[0] + \"_applicant_\" + col.rsplit(\"_\",1)[1] for\n         col in df_person_1_2.drop(\"case_id\").columns}\n    )\n    \n    # Gán DataFrame person_1 trong batch bằng kết quả nối trái giữa df_person_1_1 và df_person_1_2\n    # theo cột \"case_id\".\n    dt_data[batch][\"person_1\"] = (\n        df_person_1_1.join(other=df_person_1_2, how=\"left\", on=\"case_id\")\n    )\n    \n    # Nhóm dữ liệu trong DataFrame credit_bureau_b_2 theo cột case_id và tổng hợp giá trị lớn\n    # nhất của số khoản thanh toán quá hạn và một cột boolean kiểm tra xem có bất kỳ số ngày\n    # quá hạn nào lớn hơn 31 hay không.\n    dt_data[batch][\"credit_bureau_b_2\"] = dt_data[batch][\"credit_bureau_b_2\"].group_by(\"case_id\").agg(\n        pl.col(\"pmts_pmtsoverdue_635A\").max().alias(\"pmts_pmtsoverdue_max_A\").cast(pl.Float64),\n        (pl.col(\"pmts_dpdvalue_108P\") > 31).any().alias(\"pmts_dpdvalue_anyover31_P\").cast(pl.Boolean)\n    )","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:23.191949Z","iopub.execute_input":"2024-04-18T08:30:23.192610Z","iopub.status.idle":"2024-04-18T08:30:26.174744Z","shell.execute_reply.started":"2024-04-18T08:30:23.192574Z","shell.execute_reply":"2024-04-18T08:30:26.173915Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Gộp các DataFrame của dữ liệu huấn luyện và kiểm tra thông qua việc nối trái (left join) của các DataFrame con trong mỗi batch\n# Kết quả là một dictionary mới với các batch đã được gộp lại thành một DataFrame duy nhất cho mỗi batch\ndt_data = {\n    batch: dt_data[batch][\"base\"]\\\n    .join(dt_data[batch][\"static\"],\n          how=\"left\", on=\"case_id\")\\\n    .join(dt_data[batch][\"static_cb\"],\n          how=\"left\", on=\"case_id\")\\\n    .join(dt_data[batch][\"person_1\"],\n          how=\"left\", on=\"case_id\")\\\n    .join(dt_data[batch][\"credit_bureau_b_2\"],\n          how=\"left\", on=\"case_id\")\n    for batch in batches\n}","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:26.176100Z","iopub.execute_input":"2024-04-18T08:30:26.176573Z","iopub.status.idle":"2024-04-18T08:30:28.145794Z","shell.execute_reply.started":"2024-04-18T08:30:26.176538Z","shell.execute_reply":"2024-04-18T08:30:28.144912Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# def reduce_mem_usage(df: pd.DataFrame) -> pd.DataFrame:\n#     \"\"\"iterate through all the columns of a dataframe and modify the data type\n#         to reduce memory usage.\n#     \"\"\"\n#     start_mem = df.memory_usage().sum() / 1024**2\n#     print('Memory usage of dataframe is {:.2f} MB'.format(start_mem))\n    \n#     for col in df.columns:\n#         col_type = df[col].dtype\n#         if col_type == 'datetime64[ns]' or col_type == 'datetime64[ms]':\n#             continue  # Bỏ qua các cột kiểu thời gian\n        \n#         if str(col_type) == \"category\":\n#             continue\n        \n#         if col_type != object:\n#             c_min = df[col].min()\n#             c_max = df[col].max()\n#             if str(col_type)[:3] == 'int':\n#                 if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:\n#                     df[col] = df[col].astype(np.int8)\n#                 elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:\n#                     df[col] = df[col].astype(np.int16)\n#                 elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:\n#                     df[col] = df[col].astype(np.int32)\n#                 elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:\n#                     df[col] = df[col].astype(np.int64)\n#             else:\n#                 if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:\n#                     df[col] = df[col].astype(np.float16)\n#                 elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:\n#                     df[col] = df[col].astype(np.float32)\n#                 else:\n#                     df[col] = df[col].astype(np.float64)\n#         else:\n#             continue\n#     end_mem = df.memory_usage().sum() / 1024**2\n#     print('Memory usage after optimization is: {:.2f} MB'.format(end_mem))\n#     print('Decreased by {:.1f}%'.format(100 * (start_mem - end_mem) / start_mem))\n    \n#     return df","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:28.148235Z","iopub.execute_input":"2024-04-18T08:30:28.148555Z","iopub.status.idle":"2024-04-18T08:30:28.154684Z","shell.execute_reply.started":"2024-04-18T08:30:28.148529Z","shell.execute_reply":"2024-04-18T08:30:28.153731Z"},"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#     if cat_cols is None:\n#         cat_cols = list(df_data.select_dtypes(\"object\").columns)\n#     df_data[cat_cols] = df_data[cat_cols].astype(\"category\")\n#     return df_data, cat_cols\n\n# #dt_data_pd = {batch: to_pandas(dt_data[batch])[0] for batch in batches}","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:28.155737Z","iopub.execute_input":"2024-04-18T08:30:28.156039Z","iopub.status.idle":"2024-04-18T08:30:28.167441Z","shell.execute_reply.started":"2024-04-18T08:30:28.156016Z","shell.execute_reply":"2024-04-18T08:30:28.166506Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# for batch in batches:\n#     dt_data[batch] = reduce_mem_usage(dt_data_pd[batch])","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:28.168513Z","iopub.execute_input":"2024-04-18T08:30:28.168775Z","iopub.status.idle":"2024-04-18T08:30:28.178291Z","shell.execute_reply.started":"2024-04-18T08:30:28.168753Z","shell.execute_reply":"2024-04-18T08:30:28.177448Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Display training and test dataframes","metadata":{}},{"cell_type":"code","source":"# Display first five entries of the training dataframe\ndt_data[\"train\"].head()","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:28.179475Z","iopub.execute_input":"2024-04-18T08:30:28.179774Z","iopub.status.idle":"2024-04-18T08:30:28.198241Z","shell.execute_reply.started":"2024-04-18T08:30:28.179751Z","shell.execute_reply":"2024-04-18T08:30:28.197326Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Display first five entries of the test dataframe\ndt_data[\"test\"].head()","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:28.199162Z","iopub.execute_input":"2024-04-18T08:30:28.199468Z","iopub.status.idle":"2024-04-18T08:30:28.216058Z","shell.execute_reply.started":"2024-04-18T08:30:28.199445Z","shell.execute_reply":"2024-04-18T08:30:28.215061Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Exploratory Data Analysis (EDA)","metadata":{}},{"cell_type":"markdown","source":"## Verify if the training data is balanced","metadata":{}},{"cell_type":"code","source":"# Kiểm tra xem data đã cân bằng chưa\n#Tạo 1 dict lưu số lượng các lớp khác nhau trong dữ liệu train\ndt_N_y_train = dict(collections.Counter(dt_data[\"train\"][\"target\"]))\n#Tính tỉ lệ giữa số trường hợp tín dụng đã giải quyết (y=0) và số trường hợp vỡ nợ(y=1)\ndt_N_y_train[\"ratio\"] = dt_N_y_train[0] / dt_N_y_train[1]\n# Khởi tạo sơ đồ và các trục\nplt.figure(figsize=(6.4, 4.8))\nax = plt.axes()\nplt.title(\"Number of counts per class\", pad=20)\n# Xây dựng biểu đồ countplot cho sự xuất hiện của các chữ số khác nhau trong dữ liệu train\nsns.countplot(\n    ax=ax,\n    x=dt_data[\"train\"][\"target\"].to_numpy(),\n    color=\"blue\",\n    alpha=0.5,\n    edgecolor=\"black\",\n    linewidth=1.0,\n    width=0.075,\n    hatch=\"////\",\n    zorder=2\n)\n# Gán nhãn cho các trục\nax.set_xlabel(r\"Class, $y$\", fontdict={\"fontsize\": 10})\nax.set_ylabel(r\"Counts\", fontdict={\"fontsize\": 10})\n# Cho phép các trục hiện các vạch chia nhỏ\nax.minorticks_on()\n# Xây dựng lưới\nax.grid(\n    visible=True,\n    which=\"major\",\n    color=\"lightgray\",\n    linestyle=\"solid\",\n    linewidth=0.5\n)\nax.grid(\n    visible=True,\n    which=\"minor\",\n    color=\"lightgray\",\n    linestyle=\"dotted\",\n    linewidth=0.5\n)\n# Trình chiếu đồ thị\nplt.show()\n\n# Tỷ lệ hiển thị giữa số trường hợp tín dụng đã giải quyết (y=0) và không trả được nợ (y=1)\nprint()\ndisplay(pd.DataFrame(data={\"$n^-$/$n^+$\": dt_N_y_train[\"ratio\"]},\n                     index=[0])\\\n        .style\\\n        .format({\"$n^-/n^+$\": \"{:.2f}\"})\\\n        .set_caption(\"Ratio between numbers of settled (y=0) and defaulted (y=1) credit contract cases\")\\\n        .set_table_styles([\n                # Thiệt lập chiều rộng cột\n                {\"selector\": \"th.col_heading,td\",\n                 \"props\": [(\"width\", \"300px\")]\n                 },\n                # Kiểu caption\n                {\"selector\": \"caption\",\n                 \"props\": [(\"font-size\", \"20px\"),\n                           (\"font-weight\", \"bold\"),\n                           (\"font-style\", \"italic\")]\n                 }\n            ]))","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:28.217208Z","iopub.execute_input":"2024-04-18T08:30:28.217510Z","iopub.status.idle":"2024-04-18T08:30:28.882411Z","shell.execute_reply.started":"2024-04-18T08:30:28.217477Z","shell.execute_reply":"2024-04-18T08:30:28.881381Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Như dự đoán, dữ liệu huấn luyện mất cân bằng - Số trường hợp \"vỡ nợ hợp đồng tín dụng\" ít hơn nhiều so với \"hợp đồng tín dụng đã giải quyết\" (ít hơn 30.81 lần).\nBởi vì dữ liệu huấn luyện mất cân bằng, nếu việc huấn luyện được thực hiện theo cách thông thường, mô hình thu được tương ứng có thể thể hiện hiệu suất sai lệch có xu hướng thiên về lớp đa số và bỏ qua lớp thiểu số. Một cách tiếp cận để huấn luyện mô hình đối với các lớp đa số và thiểu số một cách giống hệt nhau là xem xét trọng số cho các hàm tổn thất của lớp thiểu số. Các trọng số cần phải sao cho số điểm đào tạo liên quan đến lớp thiểu số được chia theo các trọng số này trùng với số điểm đào tạo liên quan đến lớp đa số.\n\nHãy mô tả toán học cách tiếp cận cân bằng này. Đặt $y_i$ là nhãn của điểm huấn luyện thứ $y=0$ và $y=1$ lần lượt đại diện cho nhóm thiểu số và nhóm đa số (\"tín dụng đã thanh toán\" và \"tín dụng không trả được nợ\"). Ngoài ra, giả sử $L_i$ là hàm mất mát liên quan đến điểm thứ $i$ và $w_i$ là trọng số tương ứng. Hàm chi phí $C$ khi đó sẽ tương ứng với:\n\n$$C = \\sum_{i=1}^{n} w_i \\cdot L_i\\text{ ,}$$\n\ntrong đó $n$ là số điểm đào tạo. Và trọng số $w_i$ sẽ được định nghĩa như sau:\n\n$$\nw_i=\n\\begin{cases}\n1\\text{, } & \\text{ if }y_i=0\\\\\n\\frac{n^-}{n^+}\\text{, } & \\text{ if }y_i=1\n\\end{cases}\n$$\n\ntrong đó $n^-$ và $n^+ = n - n^-$ lần lượt là số điểm đào tạo của các lớp \"tín dụng đã thanh toán\" và \"tín dụng không trả được\".\n\nViệc huấn luyện được cho là \"cân bằng\" nếu phần của hàm chi phí liên quan đến một lớp bằng phần được liên kết với lớp kia trong trường hợp hàm mất mát là đồng nhất (với $L_i=L,\\,\\forall i=1,\\,\\dots,\\,n$). Việc sử dụng các trọng số nêu trên thực sự tạo ra kết quả như sau:\n\n$$\nC = \\sum_{i=1}^{n} w_i \\cdot L_i = \\left(\\sum_{i=0 \\, \\land \\, y_i=0} w_i \\cdot L_i\\right) +  \\left(\\sum_{i=0 \\, \\land \\, y_i=1} w_i \\cdot L_i\\right) = \\underbrace{\\left(\\sum_{i=0 \\, \\land \\, y_i=0} 1 \\right)}_{=n^-} \\cdot L + \\underbrace{\\left(\\sum_{i=0 \\, \\land \\, y_i=1} 1 \\right)}_{=n^+} \\cdot \\frac{n^-}{n^+} \\cdot L =\n$$\n\n$$\n= \\underbrace{\\left(n^- \\cdot L\\right)}_{\\text{Majority class cost}} + \\underbrace{\\left(n^- \\cdot L\\right)}_{\\text{Minority class cost}}\\text{ .}\\quad\\quad\\quad \\scriptsize{■}\n$$","metadata":{}},{"cell_type":"code","source":"#Thêm cột trọng lượng mẫu vào khung dữ liệu huấn luyện\ndt_data[\"train\"] = dt_data[\"train\"].with_columns(\n    pl.when(pl.col(\"target\") == 1).then(dt_N_y_train[\"ratio\"]).otherwise(1)\\\n    .alias(\"sample_weight\").cast(pl.Float64)\n)","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:28.883602Z","iopub.execute_input":"2024-04-18T08:30:28.883869Z","iopub.status.idle":"2024-04-18T08:30:28.897310Z","shell.execute_reply.started":"2024-04-18T08:30:28.883845Z","shell.execute_reply":"2024-04-18T08:30:28.896609Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Kiểm tra các cột bị trống\n\nHãy để người ta nhận được biểu đồ về số lượng cột được phân bổ dọc theo các thùng có giá trị trống cho tập dữ liệu huấn luyện. Độ trống ở đây được định nghĩa là tỷ lệ giữa số lượng giá trị bị thiếu (giá trị `null` và `NaN` của cực) và số lượng mục trong cột.\n\nCác cột có khoảng trống cao hơn $99,5\\%$ được coi là \"gần như trống\" và do đó bị loại bỏ. Những cái tương tự trong tập dữ liệu thử nghiệm đều bị loại bỏ giống hệt nhau.","metadata":{}},{"cell_type":"code","source":"# ---> Get columns' emptiness value in the training set\n# Lấy giá trị trống của cột trong tập huấn luyện\n# Từ điển mô tả cột trống\ndt_emp = {\n    # Giá trị trống của cột\n    \"emptiness\": {col: dt_data[\"train\"][col].null_count() / len(dt_data[\"train\"][col]) for\n                  col in dt_data[\"train\"].columns},\n    # Columns' almost emptiness indicator: if true, more than 99.5 % of the entries are\n    # empty\n    \"almost_empty\": {col: dt_data[\"train\"][col].null_count() / len(dt_data[\"train\"][col]) > 0.995 for\n                     col in dt_data[\"train\"].columns}\n}","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:28.898329Z","iopub.execute_input":"2024-04-18T08:30:28.898613Z","iopub.status.idle":"2024-04-18T08:30:28.908943Z","shell.execute_reply.started":"2024-04-18T08:30:28.898589Z","shell.execute_reply":"2024-04-18T08:30:28.908091Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Vẽ biểu đồ phân bố trống của tập huấn luyện\n# Khởi tạo hình và trục\nplt.figure(figsize=(6.4, 4.8))\nax = plt.axes()\nplt.title(\"Number of counts per emptiness range in the training dataset\", pad=20)\n\n# Xác định biểu đồ biểu đồ cho số lượng (cột) so với phạm vi trống\nsns.histplot(\n    ax=ax,\n    data=dt_emp[\"emptiness\"].values(),\n    stat=\"count\",\n    bins=20,\n    binrange=(0, 1),\n    palette=[\"blue\"],\n    alpha=0.5,\n    edgecolor=\"black\",\n    linewidth=1.0,\n    hatch=\"////\",\n    zorder=2,\n    legend=False\n)\n\n# Plot verical line emptiness = 0.995\nplt.axvline(\n    x=0.995,\n    color=\"black\",\n    linewidth=2,\n    alpha=1,\n    linestyle=\"dashed\",\n    label=\"$\\mathrm{emptiness}=0.995$\"\n)\n\n# Xác định nhãn các trục\nax.set_xlabel(r\"Emptiness\", fontdict={\"fontsize\": 10})\nax.set_ylabel(r\"Counts\", fontdict={\"fontsize\": 10})\n\n# Xác định giới hạn các trục\nax.set_xlim(\n    left=0,\n    right=1\n)\n\n# Kích hoạt các dấu nhỏ của các trục\nax.minorticks_on()\n\n# Xây dựng lưới\nax.grid(\n    visible=True,\n    which=\"major\",\n    color=\"lightgray\",\n    linestyle=\"solid\",\n    linewidth=0.5\n)\nax.grid(\n    visible=True,\n    which=\"minor\",\n    color=\"lightgray\",\n    linestyle=\"dotted\",\n    linewidth=0.5\n)\n\n# Xác định kích thước của legend\nax.legend(fontsize=8)\n\n# Biểu diễn đồ thị\nplt.show()\n\n# Hiển thị số lượng các cột \"gần như trống\"\nprint()\ndisplay(pd.DataFrame(data={\"Counts (emptiness>0.995)\":\n                           sum(dt_emp[\"almost_empty\"].values())},\n                     index=[0])\\\n        .style\\\n        .format({\"Counts (emptiness>0.995)\": \"{:d}\"})\\\n        .set_caption(\"Number of almost empty columns in the training dataset\")\\\n        .set_table_styles([\n                # Column width\n                {\"selector\": \"th.col_heading,td\",\n                 \"props\": [(\"width\", \"300px\")]\n                 },\n                # Caption style\n                {\"selector\": \"caption\",\n                 \"props\": [(\"font-size\", \"16px\"),\n                           (\"font-weight\", \"bold\"),\n                           (\"font-style\", \"italic\")]\n                 }\n            ]))","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:28.912811Z","iopub.execute_input":"2024-04-18T08:30:28.913162Z","iopub.status.idle":"2024-04-18T08:30:29.426225Z","shell.execute_reply.started":"2024-04-18T08:30:28.913139Z","shell.execute_reply":"2024-04-18T08:30:29.425221Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# ---> Xóa các cột gần như trống\ncols_drop = [key for key in dt_emp[\"almost_empty\"].keys() if dt_emp[\"almost_empty\"][key] == True]\ndt_data[\"train\"] = dt_data[\"train\"].drop(cols_drop)\ndt_data[\"test\"] = dt_data[\"test\"].drop(cols_drop)","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:29.427649Z","iopub.execute_input":"2024-04-18T08:30:29.428024Z","iopub.status.idle":"2024-04-18T08:30:29.435283Z","shell.execute_reply.started":"2024-04-18T08:30:29.427989Z","shell.execute_reply":"2024-04-18T08:30:29.434366Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import polars as pl\nimport numpy as np\nimport pandas as pd\nfrom os.path import getsize, join, split, splitext\nfrom glob import glob\nfrom tqdm.notebook import tqdm\nfrom bokeh.models import NumeralTickFormatter","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:29.436635Z","iopub.execute_input":"2024-04-18T08:30:29.437004Z","iopub.status.idle":"2024-04-18T08:30:29.447425Z","shell.execute_reply.started":"2024-04-18T08:30:29.436973Z","shell.execute_reply":"2024-04-18T08:30:29.446463Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pl.Config(\n    fmt_str_lengths=80,\n    tbl_rows=80,\n    set_thousands_separator=' ',\n    float_precision=1,\n    set_fmt_float=\"full\",\n    tbl_cell_alignment = \"LEFT\",\n    tbl_cell_numeric_alignment=\"RIGHT\"\n)\nfrmt_big_numb = NumeralTickFormatter(format='0.0a')","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:29.448561Z","iopub.execute_input":"2024-04-18T08:30:29.448897Z","iopub.status.idle":"2024-04-18T08:30:29.456737Z","shell.execute_reply.started":"2024-04-18T08:30:29.448864Z","shell.execute_reply":"2024-04-18T08:30:29.455956Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"TRAIN_PATH_PARQUET = PATH_DATA_ROOT + '/parquet_files/train'\nTEST_PATH_PARQUET = PATH_DATA_ROOT + '/parquet_files/test'\nTRAIN_PATH_CSV = PATH_DATA_ROOT + '/csv_files/train'\nTEST_PATH_CSV = PATH_DATA_ROOT + '/csv_files/test'\n","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:29.457833Z","iopub.execute_input":"2024-04-18T08:30:29.458459Z","iopub.status.idle":"2024-04-18T08:30:29.464855Z","shell.execute_reply.started":"2024-04-18T08:30:29.458428Z","shell.execute_reply":"2024-04-18T08:30:29.463992Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def aggregate_nulls(path):\n    \n    id = splitext(split(path)[-1])[0]\n    feat_def = pl.read_csv(PATH_DATA_ROOT + '/feature_definitions.csv')\n    \n    df = pl.scan_parquet(path)\n    df_dtypes = pl.DataFrame({'features':df.columns, 'dtypes':[str(x) for x in df.dtypes]})\n    df = df.collect()\n    \n    df_shapes = pl.DataFrame({'file':id, 'n':df.shape[0], 'm':df.shape[1]}) \\\n                        .with_columns((pl.col('n')*pl.col('m')).alias('cells_numb'))\n    \n    df = df.null_count().select(pl.all().exclude(['case_id', 'num_group1', 'num_group2']), \n                                pl.lit(id).alias('file'),\n                               )\n    \n    df_nulls = df.melt('file', variable_name='features', value_name='nulls_numb') \\\n                 .with_columns(pl.col('features').str.slice(-1).alias('type')) \\\n                 .join(df_dtypes, on='features', how='left') \\\n                 .join(feat_def, left_on='features', right_on='Variable', how='left')\n    \n    return df_nulls, df_shapes","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:29.465867Z","iopub.execute_input":"2024-04-18T08:30:29.466123Z","iopub.status.idle":"2024-04-18T08:30:29.474839Z","shell.execute_reply.started":"2024-04-18T08:30:29.466096Z","shell.execute_reply":"2024-04-18T08:30:29.474038Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# taking all files except 'train_base'\npath_list = [path for path in glob(f'{TRAIN_PATH_PARQUET}/*.parquet') if 'train_base.parquet' not in path]\n\nfor i, path in tqdm(enumerate(path_list), total=(len(path_list))):\n    \n    nulls, shapes = aggregate_nulls(path)\n    \n    if i==0:\n        df_nulls, df_shapes = nulls, shapes\n    else:\n        df_nulls = df_nulls.vstack(nulls)\n        df_shapes = df_shapes.vstack(shapes)\n    \ndel nulls, shapes","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:30:29.475805Z","iopub.execute_input":"2024-04-18T08:30:29.476063Z","iopub.status.idle":"2024-04-18T08:31:11.696713Z","shell.execute_reply.started":"2024-04-18T08:30:29.476041Z","shell.execute_reply":"2024-04-18T08:31:11.695689Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pip install --upgrade hvplot\n","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:35:18.181226Z","iopub.execute_input":"2024-04-18T08:35:18.182153Z","iopub.status.idle":"2024-04-18T08:35:31.114926Z","shell.execute_reply.started":"2024-04-18T08:35:18.182119Z","shell.execute_reply":"2024-04-18T08:35:31.113660Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\n# Assuming df_nulls and df_shapes are already Polars DataFrames\n\n# Group and sum nulls and cell counts by file\nnull_prc = (\n    df_nulls.select([\"file\", \"nulls_numb\"])\n    .groupby(\"file\")\n    .sum()\n    .join(\n        df_shapes.select([\"file\", \"cells_numb\"]).groupby(\"file\").sum(), on=\"file\"\n    )\n)\n\n# Calculate null and filled percentages\nnull_prc = null_prc.with_columns(\n    (pl.col(\"nulls_numb\") / pl.col(\"cells_numb\")).alias(\"null_prc\")\n)\nnull_prc = null_prc.with_columns((1 - pl.col(\"null_prc\")).alias(\"filled_prc\"))\n\n# Calculate total null proportion\ntot_nulls = (\n    null_prc.select(\"nulls_numb\").sum() / null_prc.select(\"cells_numb\").sum()\n)[\"nulls_numb\"][0]\n\n# Print results\nprint(\n    f'total number of null cells - {tot_nulls:.1%}, hence filled cells - {1-tot_nulls:.1%}'\n)\n#null_prc_pd=to_pandas(null_prc)\n# Create bar plot\nplot = null_prc.plot.bar(\n    x=\"file\",\n    y=[\"filled_prc\", \"null_prc\"],\n    title=\"Percentage of filled cells per table\",\n    ylabel=\"percentage of filled cells\",\n    stacked=True,\n    rot=90,\n    height=500,\n    width=1000,\n    \n    \n)\n\n# Display plot (assuming you have a display function available)\ndisplay(plot)\n","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:35:44.144438Z","iopub.execute_input":"2024-04-18T08:35:44.144856Z","iopub.status.idle":"2024-04-18T08:35:44.215197Z","shell.execute_reply.started":"2024-04-18T08:35:44.144822Z","shell.execute_reply":"2024-04-18T08:35:44.213977Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# fell free to change threshold - it's funny!\nthreshold_of_nulls = 0.9 \n\nnulls_prc_per_feat = df_nulls.join(df_shapes, on='file').with_columns((pl.col('nulls_numb')/pl.col('n')).alias('nulls_prc'))\nft_wo_nulls = nulls_prc_per_feat.filter(pl.col('nulls_numb')==0) \\\n                                .group_by('file').agg(pl.col('features').count().alias('without nulls'))\nnull_above_th = nulls_prc_per_feat.filter(pl.col('nulls_prc')>threshold_of_nulls) \\\n                                  .group_by('file').agg(pl.col('features').count().alias(f'more {threshold_of_nulls:.0%} nulls'))\nnull_below_th = nulls_prc_per_feat.filter((pl.col('nulls_prc')<threshold_of_nulls) & (pl.col('nulls_numb')>0)) \\\n                    .group_by('file').agg(pl.col('features').count().alias(f'less {threshold_of_nulls:.0%} nulls'))\n\ndf_pl = df_shapes.join(ft_wo_nulls, on='file', how='left') \\\n                 .join(null_above_th, on='file', how='left') \\\n                 .join(null_below_th, on='file', how='left') \\\n                 .fill_null(strategy='zero').sort('m', descending=True)\n\ndisplay(\n    df_pl.plot.barh(x='file', \n                y=['without nulls', f'less {threshold_of_nulls:.0%} nulls', f'more {threshold_of_nulls:.0%} nulls'], \n                stacked=True, height=700, width=1200, legend='top_right', \n                title = f'Number of features without nulls and with nulls in less/more then {threshold_of_nulls:.0%} of cases',\n                ylabel='number of features',\n              )\n)","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:35:51.181686Z","iopub.execute_input":"2024-04-18T08:35:51.182624Z","iopub.status.idle":"2024-04-18T08:35:51.239751Z","shell.execute_reply.started":"2024-04-18T08:35:51.182583Z","shell.execute_reply":"2024-04-18T08:35:51.238558Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"hvplot.__version__","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:52:36.899781Z","iopub.execute_input":"2024-04-18T08:52:36.900194Z","iopub.status.idle":"2024-04-18T08:52:36.906902Z","shell.execute_reply.started":"2024-04-18T08:52:36.900154Z","shell.execute_reply":"2024-04-18T08:52:36.905991Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install --version hvplot\nimport hvplot\nnulls_prc_per_feat.select('nulls_prc').plot.hist(\n    bins=10, \n    title='Number of features by percentage of null values',\n    xlabel='percentage of null values',\n    ylabel='number of features'\n)","metadata":{"execution":{"iopub.status.busy":"2024-04-18T08:57:26.838382Z","iopub.execute_input":"2024-04-18T08:57:26.839218Z","iopub.status.idle":"2024-04-18T08:57:39.720212Z","shell.execute_reply.started":"2024-04-18T08:57:26.839185Z","shell.execute_reply":"2024-04-18T08:57:39.718793Z"},"trusted":true},"execution_count":null,"outputs":[]}]}