{"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":"none","dataSources":[{"sourceId":103436,"databundleVersionId":12476472,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport lightgbm as lgb\nimport optuna\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import f1_score\nfrom sklearn.preprocessing import LabelEncoder","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T18:47:48.447006Z","iopub.execute_input":"2025-06-18T18:47:48.447364Z","iopub.status.idle":"2025-06-18T18:47:55.280616Z","shell.execute_reply.started":"2025-06-18T18:47:48.447328Z","shell.execute_reply":"2025-06-18T18:47:55.279798Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Tải tất cả các file dữ liệu\npath = \"/kaggle/input/ds-108-p-21-assigment-06/\"\ntry:\n    # --- Dữ liệu huấn luyện (Train) ---\n    delay_4_6 = pd.read_csv(path + 'delay_4_6_CONDITION_PRODUCT_SUPPLIER.csv')\n    delay_7_9 = pd.read_csv(path + 'delay_7_9_CONDITION_PRODUCT_SUPPLIER.csv')\n    not_delay_4_6 = pd.read_csv(path + 'not_delay_4_6_CONDITION_PRODUCT_SUPPLIER.csv')\n    not_delay_7_9 = pd.read_csv(path + 'not_delay_7_9_CONDITION_PRODUCT_SUPPLIER.csv')\n\n    # --- Dữ liệu kiểm tra (Test) ---\n    test_df = pd.read_csv(path + 'PILOT_10.csv')\n    # Giữ lại ID để tạo file submission\n    test_ids = test_df['ID']\n\nexcept FileNotFoundError as e:\n    print(f\"Lỗi: Không tìm thấy file. Hãy chắc chắn các file csv nằm đúng thư mục.\")\n    print(e)\n    # Thoát nếu không có file\n    exit()\n\n# --- Gộp dữ liệu huấn luyện ---\ndelay_4_6['is_late'] = 1\ndelay_7_9['is_late'] = 1\nnot_delay_4_6['is_late'] = 0\nnot_delay_7_9['is_late'] = 0\n\ntrain_df = pd.concat([delay_4_6, delay_7_9, not_delay_4_6, not_delay_7_9], ignore_index=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T18:47:55.282143Z","iopub.execute_input":"2025-06-18T18:47:55.282853Z","iopub.status.idle":"2025-06-18T18:48:08.357759Z","shell.execute_reply.started":"2025-06-18T18:47:55.282824Z","shell.execute_reply":"2025-06-18T18:48:08.356796Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def rename_duplicate_columns(df):\n    cols = []\n    count = {}\n    for column in df.columns:\n        # Làm sạch tên cột và thay dấu cách bằng gạch dưới\n        clean_column = column.replace(' ', '_')\n        if clean_column in count:\n            count[clean_column] += 1\n            # Đổi tên cột thứ hai, thứ ba... bị trùng\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\nprint(\"Tên cột gốc (có thể có trùng lặp):\")\n# In ra một vài tên cột có khả năng gây lỗi\nprint([col for col in train_df.columns if 'SPECIAL' in col.upper()])\n\ntrain_df = rename_duplicate_columns(train_df)\ntest_df = rename_duplicate_columns(test_df) # Áp dụng cho cả tập test để nhất quán\n\nprint(\"\\nTên cột đã được đổi tên (duy nhất):\")\nprint([col for col in train_df.columns if 'SPECIAL' in col.upper()])\n# ==============================================================================\n\n\nprint(f\"\\nSố dòng dữ liệu train: {len(train_df)}\")\nprint(f\"Số dòng dữ liệu test: {len(test_df)}\")\nprint(\"Phân phối của biến mục tiêu 'is_late' trong tập train:\")\nprint(train_df['is_late'].value_counts(normalize=True))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T18:48:08.358625Z","iopub.execute_input":"2025-06-18T18:48:08.358876Z","iopub.status.idle":"2025-06-18T18:48:08.383061Z","shell.execute_reply.started":"2025-06-18T18:48:08.358856Z","shell.execute_reply":"2025-06-18T18:48:08.382137Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def preprocess(df):\n    \"\"\"\n    Hàm này thực hiện tất cả các bước tiền xử lý và feature engineering.\n    (Hàm này giữ nguyên, không cần thay đổi)\n    \"\"\"\n    # 1. Xử lý các cột ngày tháng\n    for col in ['Order_date', 'VSD']:\n        df[col] = pd.to_datetime(df[col], errors='coerce')\n\n    # 2. Kỹ thuật đặc trưng thời gian\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'] = df['Order_date'].dt.day\n\n    # 3. Xử lý các cột có vấn đề\n    cols_to_drop = [\n        'Order_date', 'VSD',\n        'SOUF_RCV_NO', 'QTUF_RCV_NO',\n        'GLOBAL_NO',\n        'PRODUCT_ATTRIBUTION', 'Stock_class', 'label'\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    # 4. Xử lý giá trị thiếu\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    # 5. Mã hóa cột phân loại\n    for col in df.select_dtypes(include=['object']).columns:\n        df[col] = df[col].astype('category')\n\n    return df\n\n# 1. Chuẩn bị để gộp\ny = train_df['is_late'].copy() # Lưu lại biến mục tiêu\ntrain_df_to_combine = train_df.drop(columns=['is_late'])\n\n# Đảm bảo index của test không bị trùng với train khi gộp\noriginal_test_index = test_df.index\ntest_df.index = test_df.index + len(train_df)\n\n# 2. Gộp train và test\nprint(\"Gộp train và test để xử lý đồng nhất...\")\ncombined_df = pd.concat([train_df_to_combine, test_df], ignore_index=False)\n\n# 3. Áp dụng preprocess trên dữ liệu đã gộp\ncombined_processed = preprocess(combined_df)\n\n# 4. Tách lại thành train và test đã xử lý\ntrain_processed = combined_processed.loc[:len(train_df)-1]\ntest_processed = combined_processed.loc[len(train_df):]\n\n# Khôi phục lại index gốc của test_processed để khớp với test_ids\ntest_processed.index = original_test_index\n\nprint(\"\\nTách dữ liệu thành công.\")\nprint(f\"Số đặc trưng được sử dụng: {len(train_processed.columns)}\")\nprint(f\"Kích thước train_processed: {train_processed.shape}\")\nprint(f\"Kích thước test_processed: {test_processed.shape}\")\n\n# Kiểm tra sự nhất quán của cột\nif not all(train_processed.columns == test_processed.columns):\n    raise ValueError(\"Lỗi: Tên cột của train và test không khớp sau khi xử lý!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T18:48:08.384580Z","iopub.execute_input":"2025-06-18T18:48:08.385370Z","iopub.status.idle":"2025-06-18T18:48:20.674379Z","shell.execute_reply.started":"2025-06-18T18:48:08.385337Z","shell.execute_reply":"2025-06-18T18:48:20.673448Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from collections import Counter\n\ndef objective(trial):\n\n    params = {\n        'objective': 'binary',\n        'metric': 'binary_logloss',\n        'verbosity': -1,\n        'boosting_type': 'gbdt',\n        'n_estimators': trial.suggest_int('n_estimators', 300, 2000),\n        'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.2),\n        'num_leaves': trial.suggest_int('num_leaves', 20, 300),\n        'max_depth': trial.suggest_int('max_depth', 3, 12),\n        'min_child_samples': trial.suggest_int('min_child_samples', 5, 100),\n        'min_split_gain': trial.suggest_float(\"min_split_gain\", 0.0, 1.0),\n        'min_data_in_leaf': trial.suggest_int(\"min_data_in_leaf\", 10, 100),\n        'subsample': trial.suggest_float('subsample', 0.6, 1.0),\n        'colsample_bytree': trial.suggest_float('colsample_bytree', 0.6, 1.0),\n        'bagging_freq': trial.suggest_int(\"bagging_freq\", 1, 10),\n        'reg_alpha': trial.suggest_float('reg_alpha', 1e-8, 10.0, log=True),\n        'reg_lambda': trial.suggest_float('reg_lambda', 1e-8, 10.0, log=True),\n        'boost_from_average': False\n    }\n\n    # Stratified K-Fold\n    kf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\n    f1_scores = []\n\n    \n    for train_index, val_index in kf.split(train_processed, y):\n        X_train, X_val = train_processed.iloc[train_index].copy(), train_processed.iloc[val_index].copy()\n        y_train, y_val = y.iloc[train_index], y.iloc[val_index]\n\n        # Tạo thêm đặc trưng mới trong quá trình huấn luyện\n        for df_part in [X_train, X_val]:\n            if 'planned_lead_time' in df_part.columns and 'PO_QTY' in df_part.columns:\n                df_part['lead_time_per_product'] = df_part['planned_lead_time'] / (df_part['PO_QTY'] + 1)\n            if 'order_day_of_week' in df_part.columns and 'order_month' in df_part.columns:\n                df_part['weekday_vs_month'] = df_part['order_day_of_week'] * df_part['order_month']\n            if 'SUPPLIER_CODE' in df_part.columns and 'MATERIAL_CODE' in df_part.columns:\n                comb = df_part['SUPPLIER_CODE'].astype(str) + '_' + df_part['MATERIAL_CODE'].astype(str)\n                df_part['supplier_material_comb'] = LabelEncoder().fit_transform(comb)\n\n        class_counts = Counter(y_train)\n        neg_pos_ratio = class_counts[0] / class_counts[1]\n        params['scale_pos_weight'] = neg_pos_ratio\n        params['is_unbalance'] = False\n\n        model = lgb.LGBMClassifier(**params)\n\n        model.fit(X_train, y_train,\n                  eval_set=[(X_val, y_val)],\n                  eval_metric='logloss',\n                  callbacks=[lgb.early_stopping(100, verbose=False)])\n\n        preds = model.predict(X_val)\n        f1 = f1_score(y_val, preds, average='macro')\n        f1_scores.append(f1)\n\n    return np.mean(f1_scores)\n\n\n# Tạo study và chạy tối ưu\nstudy = optuna.create_study(direction=\"maximize\")\nprint(\"\\nBắt đầu quá trình tối ưu hóa...\")\nstudy.optimize(objective, n_trials=22)\n\nprint(\"Quá trình tối ưu hóa hoàn tất.\")\nprint(\"Số lần thử nghiệm: \", len(study.trials))\nprint(\"Bộ tham số tốt nhất tìm được: \", study.best_params)\nprint(\"Điểm Macro F1-Score tốt nhất (cross-validation): \", study.best_value)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-18T18:48:20.675259Z","iopub.execute_input":"2025-06-18T18:48:20.675570Z","execution_failed":"2025-06-18T19:09:55.950Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Lấy bộ tham số tốt nhất từ study\nbest_params = study.best_params\n\n# Thêm các tham số cố định\nbest_params['objective'] = 'binary'\nbest_params['metric'] = 'binary_logloss'\nbest_params['verbosity'] = -1\nbest_params['boosting_type'] = 'gbdt'\nbest_params['scale_pos_weight'] = 40.0\n\n# Huấn luyện mô hình cuối cùng trên TOÀN BỘ dữ liệu train\nfinal_model = lgb.LGBMClassifier(**best_params)\nfinal_model.fit(train_processed, y)\n\nprint(\"\\nHuấn luyện mô hình cuối cùng hoàn tất.\")\n\n# Dự đoán trên tập test\ntest_preds = final_model.predict(test_processed)\n\n# Tạo file submission\nsubmission_df = pd.DataFrame({'ID': test_ids, 'label': test_preds})\n\n# Lưu file submission\nsubmission_df.to_csv('submission.csv', index=False)\n\nprint(\"\\nĐã tạo file 'submission.csv' thành công!\")\nprint(submission_df.head())\nprint(\"Phân phối dự đoán trên tập test:\")\nprint(submission_df['label'].value_counts(normalize=True))","metadata":{"trusted":true,"execution":{"execution_failed":"2025-06-18T19:09:55.951Z"}},"outputs":[],"execution_count":null}]}