{"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"}],"dockerImageVersionId":30646,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import sys\nfrom pathlib import Path\nimport subprocess\nimport os\nimport gc\nfrom glob import glob\n\nimport numpy as np\nimport pandas as pd\nimport polars as pl\nfrom datetime import datetime\nimport seaborn as sns\nimport matplotlib.pyplot as plt\n\nfrom imblearn.over_sampling import SMOTE\n\nfrom sklearn.model_selection import TimeSeriesSplit, GroupKFold, StratifiedGroupKFold\nfrom sklearn.base import BaseEstimator, RegressorMixin\nfrom sklearn.metrics import roc_auc_score\nfrom sklearn.preprocessing import OrdinalEncoder\nfrom sklearn.impute import KNNImputer\nfrom sklearn.tree import DecisionTreeClassifier\nfrom sklearn.ensemble import RandomForestClassifier\nfrom sklearn.svm import SVC\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.naive_bayes import GaussianNB\nfrom sklearn.neighbors import KNeighborsClassifier\nfrom sklearn.ensemble import GradientBoostingClassifier\nfrom xgboost import XGBClassifier\nfrom lightgbm import LGBMClassifier\nfrom catboost import CatBoost\n\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"execution":{"iopub.status.busy":"2024-04-25T05:46:45.056473Z","iopub.execute_input":"2024-04-25T05:46:45.056841Z","iopub.status.idle":"2024-04-25T05:46:45.066552Z","shell.execute_reply.started":"2024-04-25T05:46:45.056814Z","shell.execute_reply":"2024-04-25T05:46:45.065570Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 데이터 로드","metadata":{}},{"cell_type":"code","source":"def read_file(path, depth=None):\n    # 주어진 경로에서 Parquet 파일을 읽어 데이터 프레임으로 로드\n    df = pl.read_parquet(path)\n    # Pipeline 클래스의 set_table_dtypes 메소드를 사용하여 데이터 타입을 설정\n    df = df.pipe(Pipeline.set_table_dtypes)\n    # depth가 1 또는 2인 경우, 'case_id'를 기준으로 그룹화하고 통계 연산을 수행\n    if depth in [1, 2]:\n        df = df.group_by(\"case_id\").agg(Aggregator.get_exprs(df))\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)  # 각 Parquet 파일을 데이터프레임으로 읽기\n        df = df.pipe(Pipeline.set_table_dtypes)  # 데이터 타입 설정 적용\n        if depth in [1, 2]:  # depth가 1 또는 2인 경우 그룹화 및 집계 수행\n            df = df.group_by(\"case_id\").agg(Aggregator.get_exprs(df))\n        chunks.append(df)  # 처리된 데이터프레임을 리스트에 추가\n    df = pl.concat(chunks, how=\"vertical_relaxed\")  # 모든 데이터프레임을 수직으로 연결\n    df = df.unique(subset=[\"case_id\"])  # 'case_id'를 기준으로 중복 제거\n    return df","metadata":{"execution":{"iopub.status.busy":"2024-04-25T05:46:45.068263Z","iopub.execute_input":"2024-04-25T05:46:45.068725Z","iopub.status.idle":"2024-04-25T05:46:45.082730Z","shell.execute_reply.started":"2024-04-25T05:46:45.068687Z","shell.execute_reply":"2024-04-25T05:46:45.081959Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 데이터 전처리","metadata":{}},{"cell_type":"markdown","source":"#### Pipeline 클래스 정의","metadata":{}},{"cell_type":"code","source":"class Pipeline:\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.Int64))\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        return df\n\n    def handle_dates(df): # 데이터 프레임 내의 날짜 관련 데이터를 처리\n        for col in df.columns:\n            if col[-1] in (\"D\",): # 컬럼 이름의 마지막 문자가 \"D\"인 경우\n                # 해당 컬럼에서 \"date_decision\" 컬럼의 날짜를 빼서 날짜 간의 차이를 계산\n                df = df.with_columns(pl.col(col) - pl.col(\"date_decision\"))\n                # 그 결과로 얻은 차이를 일 수로 변환\n                df = df.with_columns(pl.col(col).dt.total_days())\n        # \"date_decision\" 컬럼과 \"MONTH\" 컬럼을 데이터 프레임에서 제거합니다.\n        df = df.drop(\"date_decision\", \"MONTH\")\n        return df\n\n    def filter_cols(df): # 데이터 프레임의 컬럼을 필터링\n        for col in df.columns: # \"target\", \"case_id\", \"WEEK_NUM\"을 제외한 모든 컬럼을 순회\n            if col not in [\"target\", \"case_id\", \"WEEK_NUM\"]:\n                isnull = df[col].is_null().mean()\n                # 널 값의 비율이 70% 이상인 컬럼을 제거\n                if isnull > 0.7:\n                    df = df.drop(col)\n\n        for col in df.columns: # \"target\", \"case_id\", \"WEEK_NUM\"을 제외한 모든 컬럼\n            # 컬럼의 데이터 타입이 문자열인 경우\n            if (col not in [\"target\", \"case_id\", \"WEEK_NUM\"]) & (df[col].dtype == pl.String):\n                freq = df[col].n_unique()\n                # 고유 값의 수가 1이거나 200을 초과할 경우 해당 컬럼을 제거합\n                if (freq == 1) | (freq > 200):\n                    df = df.drop(col)\n\n        return df","metadata":{"execution":{"iopub.status.busy":"2024-04-25T05:46:45.083784Z","iopub.execute_input":"2024-04-25T05:46:45.084114Z","iopub.status.idle":"2024-04-25T05:46:45.098968Z","shell.execute_reply.started":"2024-04-25T05:46:45.084067Z","shell.execute_reply":"2024-04-25T05:46:45.098031Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Aggregator 클래스 정의","metadata":{}},{"cell_type":"code","source":"class Aggregator: \n    def num_expr(df): # 특정 종류의 컬럼들에 대해 다양한 통계적 연산을 수행\n        # 컬럼 이름의 마지막 문자가 \"P\" 또는 \"A\"로 끝나는 컬럼들을 선택\n        cols = [col for col in df.columns if col[-1] in (\"P\", \"A\")]\n        # 선택된 각 컬럼의 최대값을 계산하고, 결과 컬럼 이름은 max_{컬럼명}\n        expr_max = [pl.max(col).alias(f\"max_{col}\") for col in cols]\n        # 선택된 각 컬럼의 마지막 값을 가져오고, 결과 컬럼 이름은 last_{컬럼명}\n        expr_last = [pl.last(col).alias(f\"last_{col}\") for col in cols]\n        # 선택된 각 컬럼의 평균값을 계산. 결과 컬럼 이름은 mean_{컬럼명}\n        expr_mean = [pl.mean(col).alias(f\"mean_{col}\") for col in cols]\n        # 리스트를 결합하여 하나의 리스트로 반환\n        return expr_max + expr_last + expr_mean\n\n    def date_expr(df): # 날짜 타입의 컬럼들을 대상으로 특정 통계 연산을 수행하는 표현식을 생성\n        # 컬럼 이름의 마지막 문자가 \"D\"로 끝나는 컬럼들을 선택\n        cols = [col for col in df.columns if col[-1] in (\"D\")]\n        # 각 날짜 컬럼에 대해 최대값을 계산하고, 결과 컬럼 이름은 max_{컬럼명}\n        expr_max = [pl.max(col).alias(f\"max_{col}\") for col in cols]\n        # 각 날짜 컬럼에 대해 마지막 값을 계산하고, 결과 컬럼 이름은 max_{컬럼명}\n        expr_last = [pl.last(col).alias(f\"last_{col}\") for col in cols]\n        # 각 날짜 컬럼에 대해 평균값을 계산하고, 결과 컬럼 이름은 max_{컬럼명}\n        expr_mean = [pl.mean(col).alias(f\"mean_{col}\") for col in cols]\n        # expr_max, expr_last, expr_mean 리스트를 결합하여 하나의 리스트로 반환\n        return  expr_max + expr_last + expr_mean\n\n    def str_expr(df): # 문자열 타입의 컬럼들을 대상으로 특정 통계 연산을 수행하는 표현식을 생성\n        # 컬럼 이름의 마지막 문자가 \"M\"로 끝나는 컬럼들을 선택\n        cols = [col for col in df.columns if col[-1] in (\"M\",)]\n        # 각 문자열 컬럼에 대해 최대값을 계산하고, 결과 컬럼 이름은 max_{컬럼명}\n        expr_max = [pl.max(col).alias(f\"max_{col}\") for col in cols]\n        # 각 문자열 컬럼에 대해 마지막 값을 계산하고, 결과 컬럼 이름은 last_{컬럼명}\n        expr_last = [pl.last(col).alias(f\"last_{col}\") for col in cols]\n        # expr_max, expr_last 리스트를 결합하여 하나의 리스트로 반환\n        return  expr_max + expr_last\n\n\n    def other_expr(df): # \"T\" 또는 \"L\"로 끝나는 컬럼들을 대상으로 특정 통계 연산을 수행하는 표현식을 생성\n        # 컬럼 이름의 마지막 문자가 \"T\" 또는 \"L\"인 컬럼들을 선택\n        cols = [col for col in df.columns if col[-1] in (\"T\", \"L\")]\n        # 각 컬럼에 대해 최대값을 계산하고, 결과 컬럼 이름은 max_{컬럼명}\n        expr_max = [pl.max(col).alias(f\"max_{col}\") for col in cols]\n        # 각 컬럼에 대해 마지막 값을 계산하고, 결과 컬럼 이름은 last_{컬럼명}\n        expr_last = [pl.last(col).alias(f\"last_{col}\") for col in cols]\n        # expr_max, expr_last 리스트를 결합하여 하나의 리스트로 반환\n        return  expr_max + expr_last\n\n\n    def count_expr(df): # \"num_group\"이 포함된 컬럼들을 대상으로 특정 통계 연산을 수행하는 표현식을 생성\n        # 컬럼 이름에 \"num_group\"이 포함되어 있는 컬럼들을 선택\n        cols = [col for col in df.columns if \"num_group\" in col]\n        # 각 컬럼에 대해 최대값을 계산하고, 결과 컬럼 이름은 max_{컬럼명}\n        expr_max = [pl.max(col).alias(f\"max_{col}\") for col in cols]\n        # 각 컬럼에 대해 마지막 값을 계산하고, 결과 컬럼 이름은 last_{컬럼명}\n        expr_last = [pl.last(col).alias(f\"last_{col}\") for col in cols]\n        # expr_max, expr_last 리스트를 결합하여 하나의 리스트로 반환\n        return  expr_max + expr_last\n\n\n    def get_exprs(df): # 여러 통계 연산을 수행하는 표현식을 결합하여 반환\n        # Aggregator 클래스 내의 여러 메소드를 호출하여 얻은 표현식들을 결합\n        exprs = Aggregator.num_expr(df) + \\\n                Aggregator.date_expr(df) + \\\n                Aggregator.str_expr(df) + \\\n                Aggregator.other_expr(df) + \\\n                Aggregator.count_expr(df)\n        # 결합된 표현식 리스트를 반환\n        return exprs","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2024-04-25T05:46:45.175629Z","iopub.execute_input":"2024-04-25T05:46:45.175984Z","iopub.status.idle":"2024-04-25T05:46:45.193423Z","shell.execute_reply.started":"2024-04-25T05:46:45.175956Z","shell.execute_reply":"2024-04-25T05:46:45.192337Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def feature_eng(df_base, depth_0, depth_1, depth_2):\n    # 'date_decision'에서 파생된 새로운 컬럼 추가\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    # 추가적인 depth 데이터프레임을 기본 데이터프레임에 연결\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    df_base = df_base.pipe(Pipeline.handle_dates)  # Pipeline의 날짜 처리 적용\n    return df_base\n\n\ndef to_pandas(df_data, cat_cols=None):\n    df_data = df_data.to_pandas()  # 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\ndef reduce_mem_usage(df):\n    ''' 데이터프레임의 각 컬럼의 데이터 타입을 변경하여 메모리 사용량을 줄입니다. '''\n    \n    # 데이터프레임 전체의 초기 메모리 사용량을 계산하고 출력합니다.\n    start_mem = df.memory_usage().sum() / 1024**2\n    print('Memory usage of dataframe is {:.2f} MB'.format(start_mem))\n    \n    # 데이터프레임의 모든 컬럼을 순회합니다.\n    for col in df.columns:\n        col_type = df[col].dtype  # 현재 컬럼의 데이터 타입을 가져옵니다.\n        \n        # 'category' 타입의 경우 더 이상의 데이터 타입 변경을 수행하지 않습니다.\n        if str(col_type) == \"category\":\n            continue\n        \n        # 'object' 타입(문자열 등)이 아닌 경우, 데이터 타입을 최적화합니다.\n        if col_type != object:\n            c_min = df[col].min()  # 컬럼의 최소값을 계산합니다.\n            c_max = df[col].max()  # 컬럼의 최대값을 계산합니다.\n            \n            # 정수형 데이터 타입을 조건에 따라 최적화합니다.\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            \n            # 실수형 데이터 타입을 조건에 따라 최적화합니다.\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            # 'object' 타입 컬럼은 건너뜁니다.\n            continue\n    \n    # 최적화 후의 데이터프레임 전체 메모리 사용량을 계산하고 출력합니다.\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    # 최적화된 데이터프레임을 반환합니다.\n    return df\n","metadata":{"execution":{"iopub.status.busy":"2024-04-25T05:46:45.196037Z","iopub.execute_input":"2024-04-25T05:46:45.196638Z","iopub.status.idle":"2024-04-25T05:46:45.217142Z","shell.execute_reply.started":"2024-04-25T05:46:45.196601Z","shell.execute_reply":"2024-04-25T05:46:45.216357Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\n\n#ROOT = '/kaggle/input/home-credit-credit-risk-model-stability'\nROOT = Path(\"/kaggle/input/home-credit-credit-risk-model-stability\")\nTRAIN_DIR = ROOT / \"parquet_files\" / \"train\"\nTEST_DIR = ROOT / \"parquet_files\" / \"test\"\n\ndata_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        read_file(TRAIN_DIR / \"train_applprev_2.parquet\", 2),\n        read_file(TRAIN_DIR / \"train_person_2.parquet\", 2)\n    ]\n}","metadata":{"execution":{"iopub.status.busy":"2024-04-25T05:46:45.218501Z","iopub.execute_input":"2024-04-25T05:46:45.218830Z","iopub.status.idle":"2024-04-25T05:49:59.929067Z","shell.execute_reply.started":"2024-04-25T05:46:45.218803Z","shell.execute_reply":"2024-04-25T05:49:59.927767Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}