{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":103436,"databundleVersionId":12476472,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# # ==============================================================================\n# # BƯỚC 0: KHỞI TẠO - TẢI THƯ VIỆN VÀ DỮ LIỆU\n# # ==============================================================================\n# import pandas as pd\n# import numpy as np\n# import lightgbm as lgb\n# import optuna\n# from sklearn.model_selection import StratifiedKFold\n# from sklearn.metrics import f1_score\n# import warnings\n\n# # Tắt các cảnh báo không cần thiết để output gọn gàng hơn\n# warnings.filterwarnings('ignore')\n\n# print(\"BƯỚC 0: Đang tải dữ liệu...\")\n# # Tải tất cả các file dữ liệu\n# try:\n#     delay_4_6 = pd.read_csv('/kaggle/input/ds-108-p-21-assigment-06/delay_4_6_CONDITION_PRODUCT_SUPPLIER.csv')\n#     delay_7_9 = pd.read_csv('/kaggle/input/ds-108-p-21-assigment-06/delay_7_9_CONDITION_PRODUCT_SUPPLIER.csv')\n#     not_delay_4_6 = pd.read_csv('/kaggle/input/ds-108-p-21-assigment-06/not_delay_4_6_CONDITION_PRODUCT_SUPPLIER.csv')\n#     not_delay_7_9 = pd.read_csv('/kaggle/input/ds-108-p-21-assigment-06/not_delay_7_9_CONDITION_PRODUCT_SUPPLIER.csv')\n#     test_df = pd.read_csv('/kaggle/input/ds-108-p-21-assigment-06/PILOT_10.csv')\n#     test_ids = test_df['ID']\n# except FileNotFoundError as e:\n#     print(f\"LỖI: Không tìm thấy file csv. Vui lòng kiểm tra lại đường dẫn.\")\n#     print(e)\n#     exit()\n\n# # Gộp dữ liệu huấn luyện và tạo biến mục tiêu\n# delay_4_6['is_late'] = 1\n# delay_7_9['is_late'] = 1\n# not_delay_4_6['is_late'] = 0\n# not_delay_7_9['is_late'] = 0\n# train_df = pd.concat([delay_4_6, delay_7_9, not_delay_4_6, not_delay_7_9], ignore_index=True)\n\n# # Hàm đổi tên cột trùng lặp\n# def rename_duplicate_columns(df):\n#     cols, count = [], {}\n#     for column in df.columns:\n#         clean_column = column.replace(' ', '_')\n#         if clean_column in count:\n#             count[clean_column] += 1\n#             cols.append(f\"{clean_column}_{count[clean_column]}\")\n#         else:\n#             count[clean_column] = 1\n#             cols.append(clean_column)\n#     df.columns = cols\n#     return df\n\n# train_df = rename_duplicate_columns(train_df)\n# test_df = rename_duplicate_columns(test_df)\n# print(\"BƯỚC 0: Tải dữ liệu và chuẩn bị ban đầu hoàn tất.\\n\")\n\n# # ==============================================================================\n# # BƯỚC 1: KỸ THUẬT ĐẶC TRƯNG (FEATURE ENGINEERING)\n# # ==============================================================================\n# print(\"BƯỚC 1: Bắt đầu quá trình Feature Engineering...\")\n\n# def feature_engineer(df):\n#     \"\"\"Hàm tạo ra các đặc trưng mới và giàu thông tin.\"\"\"\n    \n#     # 1. Chuyển đổi và tạo đặc trưng từ ngày tháng\n#     for col in ['Order_date', 'VSD']:\n#         df[col] = pd.to_datetime(df[col], errors='coerce')\n    \n#     df['planned_lead_time'] = (df['VSD'] - df['Order_date']).dt.days\n#     df['order_day_of_week'] = df['Order_date'].dt.dayofweek\n#     df['order_month'] = df['Order_date'].dt.month\n#     df['order_day_of_year'] = df['Order_date'].dt.dayofyear\n#     # Tính tuần trong năm, có thể tạo ra NaN nếu ngày tháng là NaT\n#     week_of_year = df['Order_date'].dt.isocalendar().week\n\n#     # Điền giá trị thay thế (-1) cho các NaN rồi mới chuyển sang kiểu số nguyên\n#     df['order_week_of_year'] = week_of_year.fillna(0).astype(int)\n\n#     # 2. Tạo đặc trưng tương tác (Interaction Features)\n#     # Kết hợp các mã để tạo ra các thực thể duy nhất\n#     df['cust_supplier'] = df['CUST_CD'].astype(str) + '_' + df['SUPPLIER_CD'].astype(str)\n#     df['cust_brand'] = df['CUST_CD'].astype(str) + '_' + df['BRAND_CD'].astype(str)\n#     df['supplier_brand'] = df['SUPPLIER_CD'].astype(str) + '_' + df['BRAND_CD'].astype(str)\n\n#     # 3. Tạo đặc trưng tổng hợp (Aggregation Features)\n#     # Tính toán các giá trị thống kê cho các nhóm để nắm bắt hành vi\n#     agg_features = {\n#         'planned_lead_time': ['mean', 'std', 'max'],\n#         'SO_QTY': ['mean', 'std', 'sum'],\n#         'PURCHASE_AMOUNT': ['mean', 'sum']\n#     }\n    \n#     # Nhóm theo nhà cung cấp\n#     supplier_agg = df.groupby('SUPPLIER_CD').agg(agg_features)\n#     supplier_agg.columns = ['supplier_' + '_'.join(col).strip() for col in supplier_agg.columns.values]\n#     df = df.merge(supplier_agg, on='SUPPLIER_CD', how='left')\n    \n#     # Nhóm theo khách hàng\n#     cust_agg = df.groupby('CUST_CD').agg(agg_features)\n#     cust_agg.columns = ['cust_' + '_'.join(col).strip() for col in cust_agg.columns.values]\n#     df = df.merge(cust_agg, on='CUST_CD', how='left')\n\n#     return df\n\n# # --- Áp dụng theo phương pháp \"Gộp - Xử lý - Tách\" ---\n# # 1. Lưu lại biến mục tiêu và gộp train/test\n# y = train_df['is_late']\n# combined_df = pd.concat([train_df.drop(columns=['is_late']), test_df], ignore_index=True)\n\n# # 2. Áp dụng hàm feature_engineer\n# combined_featured = feature_engineer(combined_df)\n# print(\"BƯỚC 1: Feature Engineering hoàn tất.\\n\")\n\n\n# # ==============================================================================\n# # BƯỚC 2: TIỀN XỬ LÝ DỮ LIỆU (PREPROCESSING)\n# # ==============================================================================\n# print(\"BƯỚC 2: Bắt đầu Preprocessing...\")\n\n# def preprocess(df):\n#     \"\"\"Hàm dọn dẹp, xử lý giá trị thiếu và mã hóa.\"\"\"\n    \n#     # 1. Loại bỏ các cột không cần thiết\n#     cols_to_drop = [\n#         'Order_date', 'VSD', 'GLOBAL_NO', 'label', 'ID',\n#         'SOUF_RCV_NO', 'QTUF_RCV_NO', 'PRODUCT_ATTRIBUTION', 'Stock_class'\n#     ]\n#     existing_cols_to_drop = [col for col in cols_to_drop if col in df.columns]\n#     df = df.drop(columns=existing_cols_to_drop)\n\n#     # 2. Điền giá trị thiếu (Impute Missing Values)\n#     # Dùng median cho cột số, 'Unknown' cho cột object\n#     for col in df.select_dtypes(include=np.number).columns:\n#         df[col] = df[col].fillna(df[col].median())\n#     for col in df.select_dtypes(include=['object', 'category']).columns:\n#         df[col] = df[col].fillna('Unknown')\n        \n#     # 3. Mã hóa các cột phân loại\n#     # Chuyển thành kiểu 'category' để LightGBM xử lý hiệu quả\n#     for col in df.select_dtypes(include=['object']).columns:\n#         df[col] = df[col].astype('category')\n        \n#     return df\n\n# # Áp dụng hàm preprocess\n# combined_processed = preprocess(combined_featured)\n\n# # Tách lại thành train và test\n# train_processed = combined_processed.iloc[:len(train_df)]\n# test_processed = combined_processed.iloc[len(train_df):]\n\n# print(\"BƯỚC 2: Preprocessing hoàn tất.\")\n# print(f\"Số đặc trưng cuối cùng: {len(train_processed.columns)}\\n\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T08:56:44.987000Z","iopub.execute_input":"2025-06-18T08:56:44.987307Z","iopub.status.idle":"2025-06-18T08:57:34.468929Z","shell.execute_reply.started":"2025-06-18T08:56:44.987281Z","shell.execute_reply":"2025-06-18T08:57:34.467955Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # ==============================================================================\n# # BƯỚC 3: TỐI ƯU HÓA SIÊU THAM SỐ VỚI OPTUNA\n# # ==============================================================================\n# print(\"BƯỚC 3: Bắt đầu tối ưu hóa siêu tham số (Hyperparameter Tuning)...\")\n\n# def objective(trial):\n#     \"\"\"Hàm mục tiêu cho Optuna để tối đa hóa Macro F1-Score.\"\"\"\n#     params = {\n#         'objective': 'binary', 'metric': 'binary_logloss', 'verbosity': -1,\n#         'boosting_type': 'gbdt',\n#         'scale_pos_weight': 40.0, # Tham số quan trọng nhất\n#         'n_estimators': trial.suggest_int('n_estimators', 500, 2500),\n#         'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.1),\n#         'num_leaves': trial.suggest_int('num_leaves', 20, 300),\n#         'max_depth': trial.suggest_int('max_depth', 5, 15),\n#         'min_child_samples': trial.suggest_int('min_child_samples', 20, 100),\n#         'subsample': trial.suggest_float('subsample', 0.6, 1.0),\n#         'colsample_bytree': trial.suggest_float('colsample_bytree', 0.5, 1.0)\n#     }\n    \n#     kf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\n#     f1_scores = []\n    \n#     for train_idx, val_idx in kf.split(train_processed, y):\n#         X_train, X_val = train_processed.iloc[train_idx], train_processed.iloc[val_idx]\n#         y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]\n        \n#         model = lgb.LGBMClassifier(**params)\n#         model.fit(X_train, y_train, eval_set=[(X_val, y_val)],\n#                   eval_metric='logloss', callbacks=[lgb.early_stopping(100, verbose=False)])\n        \n#         preds = model.predict(X_val)\n#         f1_scores.append(f1_score(y_val, preds, average='macro'))\n        \n#     return np.mean(f1_scores)\n\n# study = optuna.create_study(direction=\"maximize\")\n# study.optimize(objective, n_trials=15) \n\n# print(\"BƯỚC 3: Tối ưu hóa hoàn tất.\")\n# print(f\"Điểm Macro F1-Score tốt nhất (CV): {study.best_value:.5f}\")\n# print(\"Bộ tham số tốt nhất:\", study.best_params)\n# print(\"\\n\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T08:57:34.470427Z","iopub.execute_input":"2025-06-18T08:57:34.470756Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# study.optimize(objective, n_trials=5)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # ==============================================================================\n# # BƯỚC 4: TÌM NGƯỠNG TỐI ƯU VÀ HUẤN LUYỆN MÔ HÌNH CUỐI CÙNG\n# # ==============================================================================\n# print(\"BƯỚC 4: Tìm ngưỡng tối ưu và huấn luyện mô hình cuối cùng...\")\n\n# # Lấy bộ tham số tốt nhất và thêm các tham số cố định\n# best_params = study.best_params\n# best_params.update({'objective': 'binary', 'metric': 'binary_logloss', \n#                     'verbosity': -1, 'boosting_type': 'gbdt', 'scale_pos_weight': 40.0})\n\n# # best_params['n_estimators'] = 1752\n# # best_params['learning_rate'] = 0.05285619391955323\n# # best_params['num_leaves'] = 250\n# # best_params['max_depth'] = 11\n# # best_params['min_child_samples'] = 11\n# # best_params['subsample'] = 0.8950150544982376\n# # best_params['colsample_bytree'] = 0.7951485778113276\n# # best_params['reg_alpha'] = 0.000511973595955625\n# # best_params['reg_lambda'] = 0.04430188400980013\n\n# # Lấy dự đoán Out-of-Fold (OOF) để tìm ngưỡng\n# kf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\n# oof_preds_proba = np.zeros(len(train_df))\n\n# for train_idx, val_idx in kf.split(train_processed, y):\n#     X_train, X_val = train_processed.iloc[train_idx], train_processed.iloc[val_idx]\n#     y_train = y.iloc[train_idx]\n    \n#     model = lgb.LGBMClassifier(**best_params)\n#     model.fit(X_train, y_train)\n#     oof_preds_proba[val_idx] = model.predict_proba(X_val)[:, 1]\n\n# # Tìm ngưỡng tốt nhất\n# thresholds = np.arange(0.1, 0.9, 0.01)\n# f1_scores = [f1_score(y, (oof_preds_proba >= t).astype(int), average='macro') for t in thresholds]\n# best_threshold = thresholds[np.argmax(f1_scores)]\n\n# print(f\"Ngưỡng xác suất tối ưu tìm được: {best_threshold:.2f}\")\n# print(f\"Macro F1-Score tương ứng trên tập OOF: {max(f1_scores):.5f}\")\n\n# # Huấn luyện mô hình cuối cùng trên TOÀN BỘ dữ liệu train\n# final_model = lgb.LGBMClassifier(**best_params)\n# final_model.fit(train_processed, y)\n# print(\"BƯỚC 4: Huấn luyện mô hình cuối cùng hoàn tất.\\n\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# # ==============================================================================\n# # BƯỚC 5: DỰ ĐOÁN VÀ TẠO FILE SUBMISSION\n# # ==============================================================================\n# print(\"BƯỚC 5: Dự đoán trên tập test và tạo file submission...\")\n\n# # Dự đoán xác suất trên tập test\n# test_preds_proba = final_model.predict_proba(test_processed)[:, 1]\n\n# # Áp dụng ngưỡng tối ưu để ra quyết định cuối cùng\n# test_preds_final = (test_preds_proba >= best_threshold).astype(int)\n\n# # Tạo DataFrame để nộp bài\n# submission_df = pd.DataFrame({'ID': test_ids, 'label': test_preds_final})\n# submission_df.to_csv('submission_final_new.csv', index=False)\n\n# print(\"Đã tạo file 'submission_final_new.csv' thành công!\")\n# print(\"Xem trước 5 dòng đầu của file submission:\")\n# print(submission_df.head())\n# print(\"\\nPhân phối dự đoán trên tập test:\")\n# print(submission_df['label'].value_counts(normalize=True))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"\nFinal Kaggle pipeline for: Predict product shipping delay (binary) - maximize Macro F1\nCombines: feature engineering, LightGBM + CatBoost stacking ensemble, Optuna tuning, OOF threshold search\n\nHow to use:\n- Place train CSVs and test CSV in the same folder as this script (defaults below).\n- Run: python final_solution_pipeline.py\n- Outputs: submission_ensemble.csv and oof_predictions.npy\n\nNotes:\n- This script is written to be robust to column name differences by normalizing spaces to underscores.\n- Tune `N_TRIALS`, `N_FOLDS` for more compute; defaults are conservative for quick runs.\n- Requires: pandas, numpy, scikit-learn, lightgbm, catboost, optuna\n\"\"\"\n\nimport os\nimport gc\nimport warnings\nwarnings.filterwarnings('ignore')\n\nimport numpy as np\nimport pandas as pd\nfrom sklearn.model_selection import StratifiedKFold, train_test_split\nfrom sklearn.metrics import f1_score\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.preprocessing import LabelEncoder\n\nimport lightgbm as lgb\nfrom catboost import CatBoostClassifier, Pool\nimport optuna\n\n# ----------------------------- User Config -----------------------------\nTRAIN_FILES = [\n    '/kaggle/input/ds-108-p-21-assigment-06/delay_4_6_CONDITION_PRODUCT_SUPPLIER.csv',\n    '/kaggle/input/ds-108-p-21-assigment-06/delay_7_9_CONDITION_PRODUCT_SUPPLIER.csv',\n    '/kaggle/input/ds-108-p-21-assigment-06/not_delay_4_6_CONDITION_PRODUCT_SUPPLIER.csv',\n    '/kaggle/input/ds-108-p-21-assigment-06/not_delay_7_9_CONDITION_PRODUCT_SUPPLIER.csv'\n]\nTEST_FILE = '/kaggle/input/ds-108-p-21-assigment-06/PILOT_10.csv'  # or 'test.csv' depending on user\nSUBMISSION_NAME = 'submission_ensemble.csv'\nTARGET_COL = 'label'  # or 'is_late' — script will normalize\nID_COL = 'ID'\nN_FOLDS = 5\nN_TRIALS = 30  # increase for better hyperparams (100+ recommended)\nRANDOM_STATE = 42\nVERBOSE = True\n\n# ----------------------------- Utilities -----------------------------\n\ndef load_and_concatenate(train_files):\n    dfs = []\n    for f in train_files:\n        if not os.path.exists(f):\n            raise FileNotFoundError(f\"Train file not found: {f}\")\n        df = pd.read_csv(f)\n        dfs.append(df)\n    df_all = pd.concat(dfs, ignore_index=True)\n    return df_all\n\n\ndef normalize_columns(df):\n    df = df.copy()\n    new_cols = []\n    for c in df.columns:\n        nc = str(c).strip().replace(' ', '_')\n        new_cols.append(nc)\n    df.columns = new_cols\n    return df\n\n\n# ----------------------------- Load data -----------------------------\nprint('Loading data...')\ntrain_df = load_and_concatenate(TRAIN_FILES)\n# train_df = normalize_columns(train_df)\n\ndef rename_duplicate_columns(df):\n    cols, count = [], {}\n    for c in df.columns:\n        clean = str(c).strip().replace(' ', '_')\n        if clean in count:\n            count[clean] += 1\n            cols.append(f\"{clean}_{count[clean]}\")\n        else:\n            count[clean] = 1\n            cols.append(clean)\n    df.columns = cols\n    return df\n\ndef reduce_mem(df):\n    for col in df.select_dtypes(include=['int64', 'float64']).columns:\n        col_min, col_max = df[col].min(), df[col].max()\n        if str(df[col].dtype).startswith('int'):\n            if col_min > np.iinfo(np.int8).min and col_max < np.iinfo(np.int8).max:\n                df[col] = df[col].astype(np.int8)\n            elif col_min > np.iinfo(np.int16).min and col_max < np.iinfo(np.int16).max:\n                df[col] = df[col].astype(np.int16)\n            elif col_min > np.iinfo(np.int32).min and col_max < np.iinfo(np.int32).max:\n                df[col] = df[col].astype(np.int32)\n        else:\n            df[col] = pd.to_numeric(df[col], downcast='float')\n    return df\n\n\ntrain_df = rename_duplicate_columns(train_df)\ntrain_df = reduce_mem(train_df)\n\n\nif os.path.exists(TEST_FILE):\n    test_df = pd.read_csv(TEST_FILE)\n    # test_df = normalize_columns(test_df)\n    test_df = rename_duplicate_columns(test_df)\n    test_df = reduce_mem(test_df)\n\n    if ID_COL in test_df.columns:\n        test_ids = test_df[ID_COL].values\n    else:\n        test_ids = np.arange(1, len(test_df) + 1)\nelse:\n    raise FileNotFoundError(f\"Test file not found: {TEST_FILE}\")\n\n# Detect target\nif 'label' in train_df.columns:\n    train_df[TARGET_COL] = train_df['label']\nelif 'is_late' in train_df.columns:\n    train_df[TARGET_COL] = train_df['is_late']\nelif TARGET_COL not in train_df.columns:\n    raise ValueError('Target column not found in training data')\n\n# Drop exact duplicate rows if any\ntrain_df = train_df.drop_duplicates().reset_index(drop=True)\n\nprint('Train shape:', train_df.shape, 'Test shape:', test_df.shape)\nimport gc\ngc.collect()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T10:31:36.255997Z","iopub.execute_input":"2025-10-24T10:31:36.256442Z","iopub.status.idle":"2025-10-24T10:31:46.604213Z","shell.execute_reply.started":"2025-10-24T10:31:36.256420Z","shell.execute_reply":"2025-10-24T10:31:46.603650Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# ----------------------------- Feature Engineering -----------------------------\nprint('Feature engineering...')\n\ndef add_date_features(df):\n    for col in ['Order_date', 'VSD']:\n        if col in df.columns:\n            df[col] = pd.to_datetime(df[col], errors='coerce')\n    if 'Order_date' in df.columns and 'VSD' in df.columns:\n        df['planned_lead_time'] = (df['VSD'] - df['Order_date']).dt.days\n    if 'Order_date' in df.columns:\n        df['order_day_of_week'] = df['Order_date'].dt.dayofweek\n        df['order_month'] = df['Order_date'].dt.month\n        df['order_day_of_month'] = df['Order_date'].dt.day\n        df['order_week_of_year'] = df['Order_date'].dt.isocalendar().week.astype('Int64')\n    return df\n\n\ndef create_interactions_and_aggregations(df, group_cols=['SUPPLIER_CD','CUST_CD']):\n    # Example interactions\n    if 'CUST_CD' in df.columns and 'SUPPLIER_CD' in df.columns:\n        df['cust_supplier'] = df['CUST_CD'].astype(str) + '_' + df['SUPPLIER_CD'].astype(str)\n    # Aggregations: numeric columns\n    num_cols = df.select_dtypes(include=[np.number]).columns.tolist()\n    num_cols = [c for c in num_cols if c not in [TARGET_COL]]\n    for g in group_cols:\n        if g in df.columns:\n            agg = df.groupby(g)[num_cols].agg(['mean','std','sum']).reset_index()\n            # flatten columns\n            agg.columns = [g] + [f'{g}_{col}_{stat}' for col, stat in agg.columns.tolist()[1:]]\n            df = df.merge(agg, on=g, how='left')\n    return df\n\n\n# Apply to combined (train+test) to prevent mismatch\ncombined = pd.concat([train_df.drop(columns=[TARGET_COL]), test_df], ignore_index=True)\ncombined = add_date_features(combined)\ncombined = create_interactions_and_aggregations(combined)\n\n# Back to splits\ntrain = combined.iloc[:len(train_df)].copy()\ntest = combined.iloc[len(train_df):].copy()\ntrain[TARGET_COL] = train_df[TARGET_COL].values\n\n# ----------------------------- Preprocessing -----------------------------\nprint('Preprocessing...')\n\ndef preprocess(df, fit_encoders=None):\n    df = df.copy()\n    # Replace obvious missing values\n    df = df.replace([' ', ''], np.nan)\n    # Numeric impute\n    for c in df.select_dtypes(include=[np.number]).columns:\n        df[c] = df[c].fillna(df[c].median())\n    # Object columns -> fillna 'Unknown'\n    obj_cols = df.select_dtypes(include=['object']).columns.tolist()\n    for c in obj_cols:\n        df[c] = df[c].fillna('Unknown').astype(str)\n    # Label encode high-cardinality categoricals for LGB\n    encoders = {} if fit_encoders is None else fit_encoders\n    cat_cols = [c for c in obj_cols if df[c].nunique() < 10000]\n    for c in cat_cols:\n        if fit_encoders is None:\n            le = LabelEncoder()\n            df[c] = le.fit_transform(df[c])\n            encoders[c] = le\n        else:\n            le = encoders.get(c)\n            if le is not None:\n                # unseen labels -> set to -1\n                df[c] = df[c].map(lambda x: le.transform([x])[0] if x in le.classes_ else -1)\n    return df, encoders, cat_cols\n\ntrain_proc, encoders, cat_cols = preprocess(train)\ntest_proc, _, _ = preprocess(test, fit_encoders=encoders)\n\n# Prepare features list\ndrop_cols = ['ID'] if 'ID' in train_proc.columns else []\n# Ensure target not in features\nif TARGET_COL in train_proc.columns:\n    drop_cols.append(TARGET_COL)\n\nfeatures = [c for c in train_proc.columns if c not in drop_cols]\nprint('Number of features:', len(features))\n\n# ----------------------------- CV and Blending Utilities -----------------------------\n\ndef get_oof_preds(clf_factory, X, y, X_test, n_folds=5, random_state=42, cat_features_for_cb=None):\n    \"\"\"Return OOF train probs and test probs (mean across folds), and per-fold models if needed\"\"\"\n    kf = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=random_state)\n    oof = np.zeros(len(X))\n    test_preds = np.zeros(len(X_test))\n    fold_models = []\n    for fold, (tr_idx, val_idx) in enumerate(kf.split(X, y)):\n        X_tr, X_val = X.iloc[tr_idx], X.iloc[val_idx]\n        y_tr, y_val = y.iloc[tr_idx], y.iloc[val_idx]\n        clf = clf_factory()\n        if isinstance(clf, CatBoostClassifier) and cat_features_for_cb is not None:\n            train_pool = Pool(X_tr, label=y_tr, cat_features=cat_features_for_cb)\n            val_pool = Pool(X_val, label=y_val, cat_features=cat_features_for_cb)\n            clf.fit(train_pool, eval_set=val_pool, verbose=False)\n            oof[val_idx] = clf.predict_proba(X_val)[:,1]\n            test_preds += clf.predict_proba(X_test)[:,1] / n_folds\n        else:\n            clf.fit(X_tr, y_tr)\n            oof[val_idx] = clf.predict_proba(X_val)[:,1]\n            test_preds += clf.predict_proba(X_test)[:,1] / n_folds\n        fold_models.append(clf)\n        if VERBOSE:\n            print(f'  Fold {fold+1} done')\n    return oof, test_preds, fold_models\n\nimport gc\ngc.collect()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T10:31:46.605194Z","iopub.execute_input":"2025-10-24T10:31:46.605440Z","iopub.status.idle":"2025-10-24T10:35:00.594201Z","shell.execute_reply.started":"2025-10-24T10:31:46.605423Z","shell.execute_reply":"2025-10-24T10:35:00.593650Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# from lightgbm import early_stopping\n\n\n\n\n# # ----------------------------- LightGBM: Optuna Tuning -----------------------------\n# print('Tuning LightGBM with Optuna...')\n# X = train_proc[features].reset_index(drop=True)\n# y = train_proc[TARGET_COL].reset_index(drop=True)\n# X_test_final = test_proc[features].reset_index(drop=True)\n\n# # Prepare categorical feature indices for CatBoost (object columns in original combined)\n# cat_features_for_cb = [i for i, c in enumerate(features) if c in cat_cols]\n\n# # ✅ Convert non-numeric columns to hashed integers\n# bad_cols = X.select_dtypes(include=['object', 'datetime']).columns\n# for c in bad_cols:\n#     X[c] = X[c].astype(str).apply(lambda x: hash(x) % (10 ** 8))\n#     X_test_final[c] = X_test_final[c].astype(str).apply(lambda x: hash(x) % (10 ** 8))\n\n\n# def lgb_objective(trial):\n#     param = {\n#         'objective': 'binary',\n#         'metric': 'binary_logloss',\n#         'verbosity': -1,\n#         'boosting_type': 'gbdt',\n#         'n_estimators': trial.suggest_int('n_estimators', 500, 2000),\n#         'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.15),\n#         'num_leaves': trial.suggest_int('num_leaves', 31, 512),\n#         'max_depth': trial.suggest_int('max_depth', 5, 20),\n#         'min_child_samples': trial.suggest_int('min_child_samples', 5, 200),\n#         'subsample': trial.suggest_float('subsample', 0.4, 1.0),\n#         'colsample_bytree': trial.suggest_float('colsample_bytree', 0.4, 1.0),\n#         'reg_alpha': trial.suggest_float('reg_alpha', 0.0, 10.0),\n#         'reg_lambda': trial.suggest_float('reg_lambda', 0.0, 10.0),\n#         'random_state': RANDOM_STATE,\n#         'n_jobs': -1,\n#     }\n#     # scale_pos_weight from class ratio\n#     pos = y.sum(); neg = len(y) - pos\n#     param['scale_pos_weight'] = max(1.0, neg/pos)\n\n#     kf = StratifiedKFold(n_splits=N_FOLDS, shuffle=True, random_state=RANDOM_STATE)\n#     scores = []\n#     for tr_idx, val_idx in kf.split(X, y):\n#         X_tr, X_val = X.iloc[tr_idx], X.iloc[val_idx]\n#         y_tr, y_val = y.iloc[tr_idx], y.iloc[val_idx]\n#         clf = lgb.LGBMClassifier(**param)\n#         # clf.fit(X_tr, y_tr, eval_set=[(X_val, y_val)], eval_metric='logloss', early_stopping_rounds=100, verbose=False)\n#         clf.fit(\n#             X_tr, y_tr,\n#             eval_set=[(X_val, y_val)],\n#             eval_metric='logloss',\n#             callbacks=[early_stopping(stopping_rounds=100, verbose=False)]\n#         )\n\n#         preds = clf.predict(X_val)\n#         scores.append(f1_score(y_val, preds, average='macro'))\n#     return np.mean(scores)\n\n# study = optuna.create_study(direction='maximize')\n# study.optimize(lgb_objective, n_trials=N_TRIALS)\n# print('Best LGB params:', study.best_params)\n\n# # Build final tuned lgb factory\n# best_lgb_params = study.best_params\n# best_lgb_params.update({'objective':'binary','metric':'binary_logloss','random_state':RANDOM_STATE,'n_jobs':-1})\n\n# import gc\n# gc.collect()\n\n\n# def make_lgb():\n#     return lgb.LGBMClassifier(**best_lgb_params)\n\n# # ----------------------------- CatBoost (default tuned minimal) -----------------------------\n# print('Preparing CatBoost (no heavy tuning to save time)...')\n\n# def make_cat():\n#     # Base parameters: allow CatBoost to handle categories\n#     params = {\n#         'iterations': 1000,\n#         'learning_rate': 0.05,\n#         'depth': 6,\n#         'loss_function': 'Logloss',\n#         'verbose': 0,\n#         'random_seed': RANDOM_STATE,\n#         'early_stopping_rounds': 100,\n#         'auto_class_weights': 'Balanced'\n#     }\n#     return CatBoostClassifier(**params)\n\n# # ----------------------------- Get OOF preds for base models -----------------------------\n# print('Generating OOF predictions for base models...')\n\n# oof_lgb, test_lgb, lgb_models = get_oof_preds(make_lgb, X, y, X_test_final, n_folds=N_FOLDS)\n# # For CatBoost we need to give original categorical features as indices\n# # We'll re-create X_cat where categorical columns are left as original strings\n# X_cat_full = train.iloc[:, :][features].copy()\n# for c in cat_cols:\n#     if c in X_cat_full.columns:\n#         X_cat_full[c] = train[c].astype(str)\n# # same for test\n# X_test_cat_full = test[features].copy()\n# for c in cat_cols:\n#     if c in X_test_cat_full.columns:\n#         X_test_cat_full[c] = test[c].astype(str)\n\n# # For CatBoost factory that uses Pool, wrap factory to include cat feature names\n\n# def make_cat_for_oof():\n#     return CatBoostClassifier(iterations=1000, learning_rate=0.05, depth=6, loss_function='Logloss', verbose=0, random_seed=RANDOM_STATE, early_stopping_rounds=100, auto_class_weights='Balanced')\n\n# # Custom OOF generator for CatBoost using Pools\n\n# def get_oof_cat(clf_factory, X, y, X_test, n_folds=5, random_state=42, cat_features=None):\n#     kf = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=random_state)\n#     oof = np.zeros(len(X))\n#     test_preds = np.zeros(len(X_test))\n#     for fold, (tr_idx, val_idx) in enumerate(kf.split(X, y)):\n#         X_tr, X_val = X.iloc[tr_idx], X.iloc[val_idx]\n#         y_tr, y_val = y.iloc[tr_idx], y.iloc[val_idx]\n#         clf = clf_factory()\n#         train_pool = Pool(X_tr, label=y_tr, cat_features=cat_features)\n#         val_pool = Pool(X_val, label=y_val, cat_features=cat_features)\n#         clf.fit(train_pool, eval_set=val_pool, verbose=False)\n#         oof[val_idx] = clf.predict_proba(X_val)[:,1]\n#         test_preds += clf.predict_proba(X_test)[:,1] / n_folds\n#         print(f'  CatBoost Fold {fold+1} done')\n#     return oof, test_preds\n\n# # Map cat feature names to indices\n# cat_feature_names = [c for c in features if c in cat_cols]\n# cat_feature_indices = [features.index(c) for c in cat_feature_names if c in features]\n\n# try:\n#     oof_cat, test_cat = get_oof_cat(make_cat_for_oof, X_cat_full[features], y, X_test_cat_full[features], n_folds=N_FOLDS, cat_features=cat_feature_names)\n# except Exception as e:\n#     print('CatBoost OOF failed:', e)\n#     # Fall back to converting categories with LabelEncoder then train\n#     oof_cat, test_cat, _ = get_oof_preds(make_cat, X, y, X_test_final, n_folds=N_FOLDS)\n\n# # Store OOFs\n# base_oof = np.vstack([oof_lgb, oof_cat]).T\n# base_test = np.vstack([test_lgb, test_cat]).T\n\n# # ----------------------------- Stacker (Logistic Regression) -----------------------------\n# print('Training stacker...')\n# stack_oof = np.zeros_like(oof_lgb)\n# stack_test = np.zeros(X_test_final.shape[0])\n# kf = StratifiedKFold(n_splits=N_FOLDS, shuffle=True, random_state=RANDOM_STATE)\n# for fold, (tr_idx, val_idx) in enumerate(kf.split(base_oof, y)):\n#     X_tr, X_val = base_oof[tr_idx], base_oof[val_idx]\n#     y_tr, y_val = y.iloc[tr_idx], y.iloc[val_idx]\n#     lr = LogisticRegression(max_iter=2000)\n#     lr.fit(X_tr, y_tr)\n#     stack_oof[val_idx] = lr.predict_proba(X_val)[:,1]\n#     stack_test += lr.predict_proba(base_test)[:,1] / N_FOLDS\n#     print(f'  Stacker fold {fold+1} done')\n\n# # Evaluate OOF macro F1 via threshold search\n# print('Searching best threshold on OOF...')\n# thresholds = np.linspace(0.01, 0.99, 99)\n# f1s = [f1_score(y, (stack_oof >= t).astype(int), average='macro') for t in thresholds]\n# best_t = thresholds[np.argmax(f1s)]\n# print('Best OOF Macro F1:', max(f1s), 'at threshold', best_t)\n\n# # Final test preds\n# final_test_preds = (stack_test >= best_t).astype(int)\n\n# # ----------------------------- Save submission -----------------------------\n# submission = pd.DataFrame({ID_COL: test_ids, TARGET_COL: final_test_preds})\n# submission.to_csv(SUBMISSION_NAME, index=False)\n# print('Saved', SUBMISSION_NAME)\n\n# # Save OOF arrays for debugging\n# np.save('oof_stack.npy', stack_oof)\n\n# print('Done.')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T10:36:43.139382Z","iopub.execute_input":"2025-10-24T10:36:43.140098Z","iopub.status.idle":"2025-10-24T10:40:01.250451Z","shell.execute_reply.started":"2025-10-24T10:36:43.140073Z","shell.execute_reply":"2025-10-24T10:40:01.249504Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from lightgbm import early_stopping\nimport lightgbm as lgb\nimport optuna\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import f1_score\nfrom catboost import CatBoostClassifier, Pool\nfrom sklearn.linear_model import LogisticRegression\nimport numpy as np\nimport pandas as pd\nimport gc\n\n# ----------------------------- LightGBM: Optuna Tuning -----------------------------\nprint('Tuning LightGBM with Optuna...')\nX = train_proc[features].reset_index(drop=True)\ny = train_proc[TARGET_COL].reset_index(drop=True)\nX_test_final = test_proc[features].reset_index(drop=True)\n\n# Prepare categorical feature indices for CatBoost\ncat_features_for_cb = [i for i, c in enumerate(features) if c in cat_cols]\n\n# ✅ Convert non-numeric columns to hashed integers\nbad_cols = X.select_dtypes(include=['object', 'datetime']).columns\nfor c in bad_cols:\n    X[c] = X[c].astype(str).apply(lambda x: hash(x) % (10 ** 8))\n    X_test_final[c] = X_test_final[c].astype(str).apply(lambda x: hash(x) % (10 ** 8))\n\ndef lgb_objective(trial):\n    # param = {\n    #     'objective': 'binary',\n    #     'metric': 'binary_logloss',\n    #     'verbosity': -1,\n    #     'boosting_type': 'gbdt',\n    #     'n_estimators': trial.suggest_int('n_estimators', 500, 2000),\n    #     'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.15),\n    #     'num_leaves': trial.suggest_int('num_leaves', 31, 512),\n    #     'max_depth': trial.suggest_int('max_depth', 5, 20),\n    #     'min_child_samples': trial.suggest_int('min_child_samples', 5, 200),\n    #     'subsample': trial.suggest_float('subsample', 0.4, 1.0),\n    #     'colsample_bytree': trial.suggest_float('colsample_bytree', 0.4, 1.0),\n    #     'reg_alpha': trial.suggest_float('reg_alpha', 0.0, 10.0),\n    #     'reg_lambda': trial.suggest_float('reg_lambda', 0.0, 10.0),\n    #     'random_state': RANDOM_STATE,\n    #     'n_jobs': -1,\n    #     'device': 'gpu',           # ✅ GPU: use GPU device\n    #     'gpu_platform_id': 0,      # ✅ GPU: default platform\n    #     'gpu_device_id': 0         # ✅ GPU: default GPU device\n    # }\n    param = {\n        'objective': 'binary',\n        'metric': 'binary_logloss',\n        'verbosity': -1,\n        'boosting_type': 'gbdt',\n        'n_estimators': trial.suggest_int('n_estimators', 500, 2000),\n        'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.15),\n        'num_leaves': trial.suggest_int('num_leaves', 31, 256),  # ✅ smaller limit\n        'max_depth': trial.suggest_int('max_depth', 4, 12),       # ✅ avoid huge trees\n        'min_child_samples': trial.suggest_int('min_child_samples', 20, 200),\n        'subsample': trial.suggest_float('subsample', 0.5, 1.0),\n        'colsample_bytree': trial.suggest_float('colsample_bytree', 0.5, 1.0),\n        'reg_alpha': trial.suggest_float('reg_alpha', 0.0, 5.0),\n        'reg_lambda': trial.suggest_float('reg_lambda', 0.0, 5.0),\n        'random_state': RANDOM_STATE,\n        'n_jobs': -1,\n        'device': 'gpu',\n        'gpu_platform_id': 0,\n        'gpu_device_id': 0,\n        'min_data_in_bin': 10,          # ✅ ensures stable splits on GPU\n        'max_bin': 255,                 # ✅ default safe GPU bin size\n        'force_col_wise': True,         # ✅ avoids some GPU histogram issues\n    }\n\n    # scale_pos_weight from class ratio\n    pos = y.sum(); neg = len(y) - pos\n    param['scale_pos_weight'] = max(1.0, neg/pos)\n\n    kf = StratifiedKFold(n_splits=N_FOLDS, shuffle=True, random_state=RANDOM_STATE)\n    scores = []\n    for tr_idx, val_idx in kf.split(X, y):\n        X_tr, X_val = X.iloc[tr_idx], X.iloc[val_idx]\n        y_tr, y_val = y.iloc[tr_idx], y.iloc[val_idx]\n        clf = lgb.LGBMClassifier(**param)\n        # clf.fit(\n        #     X_tr, y_tr,\n        #     eval_set=[(X_val, y_val)],\n        #     eval_metric='logloss',\n        #     callbacks=[early_stopping(stopping_rounds=100, verbose=False)]\n        # )\n        try:\n            clf.fit(\n                X_tr, y_tr,\n                eval_set=[(X_val, y_val)],\n                eval_metric='logloss',\n                callbacks=[early_stopping(stopping_rounds=100, verbose=False)]\n            )\n        except lgb.basic.LightGBMError as e:\n            print(\"⚠️ GPU failed, retrying on CPU:\", e)\n            param['device'] = 'cpu'\n            clf = lgb.LGBMClassifier(**param)\n            clf.fit(\n                X_tr, y_tr,\n                eval_set=[(X_val, y_val)],\n                eval_metric='logloss',\n                callbacks=[early_stopping(stopping_rounds=100, verbose=False)]\n            )\n\n        preds = clf.predict(X_val)\n        scores.append(f1_score(y_val, preds, average='macro'))\n    return np.mean(scores)\n\nstudy = optuna.create_study(direction='maximize')\nstudy.optimize(lgb_objective, n_trials=N_TRIALS)\nprint('Best LGB params:', study.best_params)\n\n# Build final tuned lgb factory\nbest_lgb_params = study.best_params\nbest_lgb_params.update({\n    'objective': 'binary',\n    'metric': 'binary_logloss',\n    'random_state': RANDOM_STATE,\n    'n_jobs': -1,\n    'device': 'gpu',           # ✅ GPU\n    'gpu_platform_id': 0,      # ✅ GPU\n    'gpu_device_id': 0         # ✅ GPU\n})\n\ngc.collect()\n\ndef make_lgb():\n    return lgb.LGBMClassifier(**best_lgb_params)\n\n# ----------------------------- CatBoost (GPU) -----------------------------\nprint('Preparing CatBoost (GPU)...')\n\ndef make_cat():\n    params = {\n        'iterations': 1000,\n        'learning_rate': 0.05,\n        'depth': 6,\n        'loss_function': 'Logloss',\n        'verbose': 0,\n        'random_seed': RANDOM_STATE,\n        'early_stopping_rounds': 100,\n        'auto_class_weights': 'Balanced',\n        'task_type': 'GPU',       # ✅ GPU\n        'devices': '0'            # ✅ GPU\n    }\n    return CatBoostClassifier(**params)\n\n# ----------------------------- Get OOF preds for base models -----------------------------\nprint('Generating OOF predictions for base models...')\n\noof_lgb, test_lgb, lgb_models = get_oof_preds(make_lgb, X, y, X_test_final, n_folds=N_FOLDS)\n\nX_cat_full = train.iloc[:, :][features].copy()\nfor c in cat_cols:\n    if c in X_cat_full.columns:\n        X_cat_full[c] = train[c].astype(str)\nX_test_cat_full = test[features].copy()\nfor c in cat_cols:\n    if c in X_test_cat_full.columns:\n        X_test_cat_full[c] = test[c].astype(str)\n\ndef make_cat_for_oof():\n    return CatBoostClassifier(\n        iterations=1000, learning_rate=0.05, depth=6,\n        loss_function='Logloss', verbose=0, random_seed=RANDOM_STATE,\n        early_stopping_rounds=100, auto_class_weights='Balanced',\n        task_type='GPU', devices='0'   # ✅ GPU\n    )\n\ndef get_oof_cat(clf_factory, X, y, X_test, n_folds=5, random_state=42, cat_features=None):\n    kf = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=random_state)\n    oof = np.zeros(len(X))\n    test_preds = np.zeros(len(X_test))\n    for fold, (tr_idx, val_idx) in enumerate(kf.split(X, y)):\n        X_tr, X_val = X.iloc[tr_idx], X.iloc[val_idx]\n        y_tr, y_val = y.iloc[tr_idx], y.iloc[val_idx]\n        clf = clf_factory()\n        train_pool = Pool(X_tr, label=y_tr, cat_features=cat_features)\n        val_pool = Pool(X_val, label=y_val, cat_features=cat_features)\n        clf.fit(train_pool, eval_set=val_pool, verbose=False)\n        oof[val_idx] = clf.predict_proba(X_val)[:,1]\n        test_preds += clf.predict_proba(X_test)[:,1] / n_folds\n        print(f'  CatBoost Fold {fold+1} done')\n    return oof, test_preds\n\ncat_feature_names = [c for c in features if c in cat_cols]\ncat_feature_indices = [features.index(c) for c in cat_feature_names if c in features]\n\ntry:\n    oof_cat, test_cat = get_oof_cat(make_cat_for_oof, X_cat_full[features], y, X_test_cat_full[features], n_folds=N_FOLDS, cat_features=cat_feature_names)\nexcept Exception as e:\n    print('CatBoost OOF failed:', e)\n    oof_cat, test_cat, _ = get_oof_preds(make_cat, X, y, X_test_final, n_folds=N_FOLDS)\n\n# ----------------------------- Stacker (Logistic Regression) -----------------------------\nprint('Training stacker...')\nstack_oof = np.zeros_like(oof_lgb)\nstack_test = np.zeros(X_test_final.shape[0])\nkf = StratifiedKFold(n_splits=N_FOLDS, shuffle=True, random_state=RANDOM_STATE)\nfor fold, (tr_idx, val_idx) in enumerate(kf.split(np.vstack([oof_lgb, oof_cat]).T, y)):\n    X_tr, X_val = np.vstack([oof_lgb, oof_cat]).T[tr_idx], np.vstack([oof_lgb, oof_cat]).T[val_idx]\n    y_tr, y_val = y.iloc[tr_idx], y.iloc[val_idx]\n    lr = LogisticRegression(max_iter=2000)\n    lr.fit(X_tr, y_tr)\n    stack_oof[val_idx] = lr.predict_proba(X_val)[:,1]\n    stack_test += lr.predict_proba(np.vstack([test_lgb, test_cat]).T)[:,1] / N_FOLDS\n    print(f'  Stacker fold {fold+1} done')\n\nprint('Searching best threshold on OOF...')\nthresholds = np.linspace(0.01, 0.99, 99)\nf1s = [f1_score(y, (stack_oof >= t).astype(int), average='macro') for t in thresholds]\nbest_t = thresholds[np.argmax(f1s)]\nprint('Best OOF Macro F1:', max(f1s), 'at threshold', best_t)\n\nfinal_test_preds = (stack_test >= best_t).astype(int)\n\nsubmission = pd.DataFrame({ID_COL: test_ids, TARGET_COL: final_test_preds})\nsubmission.to_csv(SUBMISSION_NAME, index=False)\nprint('Saved', SUBMISSION_NAME)\nnp.save('oof_stack.npy', stack_oof)\nprint('Done.')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T10:52:51.827254Z","iopub.execute_input":"2025-10-24T10:52:51.827847Z","iopub.status.idle":"2025-10-24T11:38:57.859783Z","shell.execute_reply.started":"2025-10-24T10:52:51.827824Z","shell.execute_reply":"2025-10-24T11:38:57.858910Z"}},"outputs":[],"execution_count":null}]}