{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":103436,"databundleVersionId":12476472,"sourceType":"competition"}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport warnings\nfrom sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV\nfrom sklearn.preprocessing import StandardScaler, LabelEncoder, OneHotEncoder\nfrom sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.metrics import accuracy_score, classification_report, confusion_matrix, roc_auc_score\nfrom sklearn.impute import SimpleImputer\nfrom datetime import datetime\nimport xgboost as xgb\nimport lightgbm as lgb\n\nwarnings.filterwarnings('ignore')\nplt.style.use('seaborn-v0_8')\nsns.set_palette(\"husl\")\n\nprint(\"Libraries imported successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T08:00:18.214118Z","iopub.execute_input":"2025-06-17T08:00:18.214321Z","iopub.status.idle":"2025-06-17T08:00:31.759242Z","shell.execute_reply.started":"2025-06-17T08:00:18.214304Z","shell.execute_reply":"2025-06-17T08:00:31.758426Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\ndelay_4_6 = pd.read_csv('/kaggle/input/ds-108-p-21-assigment-06/delay_4_6_CONDITION_PRODUCT_SUPPLIER.csv')\nnot_delay_4_6 = pd.read_csv('/kaggle/input/ds-108-p-21-assigment-06/not_delay_4_6_CONDITION_PRODUCT_SUPPLIER.csv')\ndelay_7_9 = pd.read_csv('/kaggle/input/ds-108-p-21-assigment-06/delay_7_9_CONDITION_PRODUCT_SUPPLIER.csv')\nnot_delay_7_9 = pd.read_csv('/kaggle/input/ds-108-p-21-assigment-06/not_delay_7_9_CONDITION_PRODUCT_SUPPLIER.csv')\npilot_10 = pd.read_csv('/kaggle/input/ds-108-p-21-assigment-06/PILOT_10.csv')\nsample_solution = pd.read_csv('/kaggle/input/ds-108-p-21-assigment-06/sample_Solution.csv')\n\nprint(\"Dataset shapes:\")\nprint(f\"Delay 4-6: {delay_4_6.shape}\")\nprint(f\"Not Delay 4-6: {not_delay_4_6.shape}\")\nprint(f\"Delay 7-9: {delay_7_9.shape}\")\nprint(f\"Not Delay 7-9: {not_delay_7_9.shape}\")\nprint(f\"Pilot 10: {pilot_10.shape}\")\nprint(f\"Sample Solution: {sample_solution.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T08:00:31.760037Z","iopub.execute_input":"2025-06-17T08:00:31.760741Z","iopub.status.idle":"2025-06-17T08:00:43.893248Z","shell.execute_reply.started":"2025-06-17T08:00:31.760697Z","shell.execute_reply":"2025-06-17T08:00:43.892557Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Add label column to datasets\ndelay_4_6['label'] = 1\nnot_delay_4_6['label'] = 0\ndelay_7_9['label'] = 1\nnot_delay_7_9['label'] = 0\n\n# Combine all training data\ntrain_data = pd.concat([\n    delay_4_6,\n    not_delay_4_6,\n    delay_7_9,\n    not_delay_7_9\n], ignore_index=True)\n\nprint(f\"Combined training data shape: {train_data.shape}\")\nprint(f\"Label distribution:\")\nprint(train_data['label'].value_counts())\nprint(f\"Label distribution (%):\") \nprint(train_data['label'].value_counts(normalize=True) * 100)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T08:00:43.894926Z","iopub.execute_input":"2025-06-17T08:00:43.895340Z","iopub.status.idle":"2025-06-17T08:00:44.394945Z","shell.execute_reply.started":"2025-06-17T08:00:43.895320Z","shell.execute_reply":"2025-06-17T08:00:44.394322Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Basic information about the dataset\nprint(\"Training Data Info:\")\nprint(train_data.info())\nprint(\"\\nFirst few rows:\")\nprint(train_data.head())\nprint(\"\\nBasic statistics:\")\nprint(train_data.describe())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T08:00:44.395634Z","iopub.execute_input":"2025-06-17T08:00:44.395927Z","iopub.status.idle":"2025-06-17T08:00:46.941037Z","shell.execute_reply.started":"2025-06-17T08:00:44.395900Z","shell.execute_reply":"2025-06-17T08:00:46.940361Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Check missing values\nmissing_values = train_data.isnull().sum()\nmissing_percent = (missing_values / len(train_data)) * 100\n\nmissing_df = pd.DataFrame({\n    'Column': missing_values.index,\n    'Missing_Count': missing_values.values,\n    'Missing_Percent': missing_percent.values\n})\n\nmissing_df = missing_df[missing_df['Missing_Count'] > 0].sort_values('Missing_Percent', ascending=False)\n\nprint(\"Missing values summary:\")\nprint(missing_df)\n\n# Visualize missing values\nif len(missing_df) > 0:\n    plt.figure(figsize=(12, 6))\n    plt.subplot(1, 2, 1)\n    sns.barplot(data=missing_df.head(10), x='Missing_Percent', y='Column')\n    plt.title('Top 10 Columns with Missing Values (%)')\n    plt.xlabel('Missing Percentage')\n    \n    plt.subplot(1, 2, 2)\n    sns.heatmap(train_data.isnull().head(100), cbar=True, yticklabels=False)\n    plt.title('Missing Values Heatmap (First 100 rows)')\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T08:00:46.941762Z","iopub.execute_input":"2025-06-17T08:00:46.941976Z","iopub.status.idle":"2025-06-17T08:00:49.609344Z","shell.execute_reply.started":"2025-06-17T08:00:46.941959Z","shell.execute_reply":"2025-06-17T08:00:49.608614Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Analyze label distribution\nplt.figure(figsize=(15, 10))\n\n# Label distribution\nplt.subplot(2, 3, 1)\nsns.countplot(data=train_data, x='label')\nplt.title('Label Distribution')\nplt.xlabel('Label (0: No Delay, 1: Delay)')\n\n# Distribution by Ship Mode\nif 'Ship Mode' in train_data.columns:\n    plt.subplot(2, 3, 2)\n    sns.countplot(data=train_data, x='Ship Mode', hue='label')\n    plt.title('Delay by Ship Mode')\n    plt.xticks(rotation=45)\n\n# Distribution by Stock class\nif 'Stock class' in train_data.columns:\n    plt.subplot(2, 3, 3)\n    sns.countplot(data=train_data, x='Stock class', hue='label')\n    plt.title('Delay by Stock Class')\n\n# Distribution by SUBSIDIARY_CD\nif 'SUBSIDIARY_CD' in train_data.columns:\n    plt.subplot(2, 3, 4)\n    top_subsidiaries = train_data['SUBSIDIARY_CD'].value_counts().head(5).index\n    subset = train_data[train_data['SUBSIDIARY_CD'].isin(top_subsidiaries)]\n    sns.countplot(data=subset, x='SUBSIDIARY_CD', hue='label')\n    plt.title('Delay by Top 5 Subsidiaries')\n    plt.xticks(rotation=45)\n\n# SO QTY distribution\nif 'SO QTY' in train_data.columns:\n    plt.subplot(2, 3, 5)\n    train_data.boxplot(column='SO QTY', by='label', ax=plt.gca())\n    plt.title('SO QTY Distribution by Label')\n    plt.suptitle('')\n\n# SUPPLIER INV AMOUNT distribution\nif 'SUPPLIER INV AMOUNT' in train_data.columns:\n    plt.subplot(2, 3, 6)\n    train_data.boxplot(column='SUPPLIER INV AMOUNT', by='label', ax=plt.gca())\n    plt.title('Supplier Inv Amount by Label')\n    plt.suptitle('')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T08:00:49.610095Z","iopub.execute_input":"2025-06-17T08:00:49.610307Z","iopub.status.idle":"2025-06-17T08:00:57.033024Z","shell.execute_reply.started":"2025-06-17T08:00:49.610289Z","shell.execute_reply":"2025-06-17T08:00:57.032106Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def preprocess_data(df, is_training=True):\n    \"\"\"\n    Preprocess the data for machine learning\n    \"\"\"\n    df_processed = df.copy()\n    \n    # Convert Order date to datetime with mixed format handling\n    if 'Order date' in df_processed.columns:\n        df_processed['Order date'] = pd.to_datetime(df_processed['Order date'], format='mixed', errors='coerce')\n        # Extract date features\n        df_processed['order_month'] = df_processed['Order date'].dt.month\n        df_processed['order_day'] = df_processed['Order date'].dt.day\n        df_processed['order_dayofweek'] = df_processed['Order date'].dt.dayofweek\n        df_processed['order_quarter'] = df_processed['Order date'].dt.quarter\n    \n    # Convert VSD to datetime if exists\n    if 'VSD' in df_processed.columns:\n        df_processed['VSD'] = pd.to_datetime(df_processed['VSD'], format='mixed', errors='coerce')\n        # Calculate days between order and VSD\n        if 'Order date' in df_processed.columns:\n            df_processed['days_to_vsd'] = (df_processed['VSD'] - df_processed['Order date']).dt.days\n    \n    # Handle categorical variables with high cardinality\n    categorical_cols = df_processed.select_dtypes(include=['object']).columns.tolist()\n    \n    # Remove datetime and ID columns from categorical processing\n    categorical_cols = [col for col in categorical_cols if col not in ['Order date', 'VSD', 'ID']]\n    \n    # For high cardinality categorical variables, use frequency encoding\n    high_cardinality_cols = []\n    for col in categorical_cols:\n        if df_processed[col].nunique() > 50:\n            high_cardinality_cols.append(col)\n            # Frequency encoding\n            freq_map = df_processed[col].value_counts().to_dict()\n            df_processed[f'{col}_freq'] = df_processed[col].map(freq_map).fillna(0)\n    \n    # Drop original high cardinality columns\n    df_processed = df_processed.drop(columns=high_cardinality_cols)\n    \n    # Update categorical columns list\n    categorical_cols = [col for col in categorical_cols if col not in high_cardinality_cols]\n    \n    # Handle remaining categorical variables with Label Encoding\n    le_dict = {}\n    for col in categorical_cols:\n        if col in df_processed.columns:\n            le = LabelEncoder()\n            # Handle missing values\n            df_processed[col] = df_processed[col].fillna('Unknown')\n            df_processed[col] = le.fit_transform(df_processed[col].astype(str))\n            le_dict[col] = le\n    \n    # Handle numerical columns\n    numerical_cols = df_processed.select_dtypes(include=[np.number]).columns.tolist()\n    \n    # Remove label from numerical columns if it exists\n    if 'label' in numerical_cols:\n        numerical_cols.remove('label')\n    \n    # Fill missing values in numerical columns\n    imputer = SimpleImputer(strategy='median')\n    df_processed[numerical_cols] = imputer.fit_transform(df_processed[numerical_cols])\n    \n    # Create additional features\n    if 'SO QTY' in df_processed.columns and 'ALLOCATION QTY' in df_processed.columns:\n        df_processed['qty_ratio'] = df_processed['ALLOCATION QTY'] / (df_processed['SO QTY'] + 1)\n    \n    if 'SUPPLIER INV AMOUNT' in df_processed.columns and 'SO QTY' in df_processed.columns:\n        df_processed['price_per_unit'] = df_processed['SUPPLIER INV AMOUNT'] / (df_processed['SO QTY'] + 1)\n    \n    # Drop datetime columns as they're not needed for modeling\n    datetime_cols = ['Order date', 'VSD']\n    df_processed = df_processed.drop(columns=[col for col in datetime_cols if col in df_processed.columns])\n    \n    return df_processed, le_dict\n\n# Preprocess training data\nprint(\"Preprocessing training data...\")\nX_processed, label_encoders = preprocess_data(train_data, is_training=True)\ny = X_processed['label']\nX = X_processed.drop(columns=['label'])\n\nprint(f\"Processed training data shape: {X.shape}\")\nprint(f\"Number of features: {X.shape[1]}\")\nprint(f\"Features: {list(X.columns)[:10]}...\")  # Show first 10 features","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T08:00:57.033881Z","iopub.execute_input":"2025-06-17T08:00:57.034169Z","iopub.status.idle":"2025-06-17T08:01:23.365687Z","shell.execute_reply.started":"2025-06-17T08:00:57.034138Z","shell.execute_reply":"2025-06-17T08:01:23.365028Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport hashlib\n\ndef hash_series(series):\n    \"\"\"Tạo mã hash duy nhất cho một Series để kiểm tra trùng lặp nhanh.\"\"\"\n    return hashlib.md5(pd.util.hash_pandas_object(series, index=False).values).hexdigest()\n\n# 1. Remove duplicate columns\nprint(\"Checking for duplicate columns...\")\n\n# Tạo hash cho từng cột để so sánh\ncol_hashes = {col: hash_series(X[col]) for col in X.columns}\nreverse_map = {}\nfor col, h in col_hashes.items():\n    reverse_map.setdefault(h, []).append(col)\n\n# Lấy ra các cột trùng lặp (chỉ giữ lại một cột đầu tiên mỗi nhóm)\nduplicate_cols = [col for group in reverse_map.values() if len(group) > 1 for col in group[1:]]\n\nif duplicate_cols:\n    print(f\"Found and removed {len(duplicate_cols)} duplicate columns:\")\n    print(duplicate_cols)\n    X.drop(columns=duplicate_cols, inplace=True)\nelse:\n    print(\"No duplicate columns found.\")\n\n# 2. Remove highly correlated features\nprint(\"\\nChecking for highly correlated features (> 0.95)...\")\n\n# Chỉ lấy cột số để tính correlation\nnum_cols = X.select_dtypes(include=[np.number])\ncorr_matrix = num_cols.corr().abs()\n\n# Tam giác trên\nupper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(bool))\n\n# Lấy các cột có tương quan > 0.95\nto_drop = [col for col in upper.columns if any(upper[col] > 0.95)]\n\nif to_drop:\n    print(f\"Found and removed {len(to_drop)} highly correlated columns:\")\n    print(to_drop)\n    X.drop(columns=to_drop, inplace=True)\nelse:\n    print(\"No highly correlated features found.\")\n\nprint(f\"\\n✅ Shape of training data after cleanup: {X.shape}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T08:01:23.366505Z","iopub.execute_input":"2025-06-17T08:01:23.367253Z","iopub.status.idle":"2025-06-17T08:01:33.627566Z","shell.execute_reply.started":"2025-06-17T08:01:23.367226Z","shell.execute_reply":"2025-06-17T08:01:33.626833Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Feature scaling\nscaler = StandardScaler()\nX_scaled = scaler.fit_transform(X)\nX_scaled = pd.DataFrame(X_scaled, columns=X.columns)\n\nprint(\"Data scaling completed!\")\nprint(f\"Scaled data shape: {X_scaled.shape}\")\n\n# Feature correlation analysis\nplt.figure(figsize=(20, 16))\ncorr_matrix = X_scaled.corrwith(y).abs().sort_values(ascending=False)\n\n# Plot top 20 features with highest correlation to target\ntop_features = corr_matrix.head(20)\nplt.subplot(2, 2, 1)\nsns.barplot(x=top_features.values, y=top_features.index)\nplt.title('Top 20 Features Correlation with Target')\nplt.xlabel('Absolute Correlation')\n\n# Plot correlation heatmap for top features\ntop_feature_names = top_features.index[:15]\nsubset_data = pd.concat([X_scaled[top_feature_names], y], axis=1)\nplt.subplot(2, 2, 2)\nsns.heatmap(subset_data.corr(), annot=True, cmap='coolwarm', center=0, fmt='.2f')\nplt.title('Correlation Heatmap - Top Features')\n\n# Feature importance using Random Forest\nrf_temp = RandomForestClassifier(n_estimators=100, random_state=42)\nrf_temp.fit(X_scaled, y)\nfeature_importance = pd.DataFrame({\n    'feature': X_scaled.columns,\n    'importance': rf_temp.feature_importances_\n}).sort_values('importance', ascending=False)\n\nplt.subplot(2, 2, 3)\nsns.barplot(data=feature_importance.head(15), x='importance', y='feature')\nplt.title('Top 15 Feature Importance (Random Forest)')\nplt.xlabel('Importance')\n\n# Distribution of target variable\nplt.subplot(2, 2, 4)\nsns.countplot(x=y)\nplt.title('Target Distribution')\nplt.xlabel('Label (0: No Delay, 1: Delay)')\n\nplt.tight_layout()\nplt.show()\n\nprint(\"\\nTop 10 most important features:\")\nprint(feature_importance.head(10))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T08:01:33.629997Z","iopub.execute_input":"2025-06-17T08:01:33.630399Z","iopub.status.idle":"2025-06-17T08:08:32.706261Z","shell.execute_reply.started":"2025-06-17T08:01:33.630381Z","shell.execute_reply":"2025-06-17T08:08:32.705565Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split, cross_val_score\nfrom sklearn.metrics import accuracy_score, roc_auc_score\nimport xgboost as xgb\nimport lightgbm as lgb\n\n# 1. Split dữ liệu\nX_train, X_test, y_train, y_test = train_test_split(\n    X_scaled, y, test_size=0.2, random_state=42, stratify=y\n)\n\nprint(f\"Training set shape: {X_train.shape}\")\nprint(f\"Test set shape: {X_test.shape}\")\nprint(f\"Label distribution in training set:\\n{y_train.value_counts(normalize=True)}\")\n\n# 2. Định nghĩa 2 mô hình\nmodels = {\n    'XGBoost': xgb.XGBClassifier(\n        n_estimators=200,\n        random_state=42,\n        eval_metric='logloss',\n        tree_method='hist',  # GPU-friendly (mới)\n        device='cuda',\n        scale_pos_weight=len(y_train[y_train==0])/len(y_train[y_train==1])\n    ),\n    'LightGBM': lgb.LGBMClassifier(\n        n_estimators=200,\n        random_state=42,\n        class_weight='balanced',\n        device='gpu'\n    )\n}\n\n# 3. Train và đánh giá\nmodel_results = {}\n\nfor name, model in models.items():\n    print(f\"\\n🚀 Training {name}...\")\n    model.fit(X_train, y_train)\n    \n    y_pred = model.predict(X_test)\n    y_pred_proba = model.predict_proba(X_test)[:, 1]\n    \n    accuracy = accuracy_score(y_test, y_pred)\n    auc_score = roc_auc_score(y_test, y_pred_proba)\n    cv_scores = cross_val_score(model, X_train, y_train, cv=3, scoring='roc_auc')\n    \n    model_results[name] = {\n        'model': model,\n        'accuracy': accuracy,\n        'auc': auc_score,\n        'cv_mean': cv_scores.mean(),\n        'cv_std': cv_scores.std(),\n        'y_pred': y_pred,  # <-- cần dòng này\n        'y_pred_proba': y_pred_proba\n    }\n    \n    print(f\"✅ Accuracy: {accuracy:.4f}\")\n    print(f\"✅ AUC: {auc_score:.4f}\")\n    print(f\"✅ CV AUC: {cv_scores.mean():.4f} ± {cv_scores.std()*2:.4f}\")\n\n# 4. Chọn mô hình tốt nhất\nbest_model_name = max(model_results, key=lambda x: model_results[x]['auc'])\nbest_model = model_results[best_model_name]['model']\nprint(f\"\\n🎯 Best model: {best_model_name} (AUC = {model_results[best_model_name]['auc']:.4f})\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T08:25:17.766066Z","iopub.execute_input":"2025-06-17T08:25:17.766541Z","iopub.status.idle":"2025-06-17T08:26:30.530067Z","shell.execute_reply.started":"2025-06-17T08:25:17.766520Z","shell.execute_reply":"2025-06-17T08:26:30.529208Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.metrics import confusion_matrix, classification_report\n\n# 1. Tạo bảng so sánh kết quả\nresults_df = pd.DataFrame({\n    'Model': list(model_results.keys()),\n    'Accuracy': [results['accuracy'] for results in model_results.values()],\n    'AUC Score': [results['auc'] for results in model_results.values()],\n    'CV Mean': [results['cv_mean'] for results in model_results.values()],\n    'CV Std': [results['cv_std'] for results in model_results.values()]\n})\n\nprint(\"📊 Model Performance Comparison:\")\nprint(results_df.round(4))\n\n# 2. Vẽ biểu đồ so sánh\nfig, axes = plt.subplots(2, 2, figsize=(15, 12))\n\n# Biểu đồ Accuracy\naxes[0, 0].bar(results_df['Model'], results_df['Accuracy'], color='skyblue')\naxes[0, 0].set_title('🔍 Model Accuracy Comparison')\naxes[0, 0].set_ylabel('Accuracy')\naxes[0, 0].tick_params(axis='x', rotation=45)\n\n# Biểu đồ AUC\naxes[0, 1].bar(results_df['Model'], results_df['AUC Score'], color='lightgreen')\naxes[0, 1].set_title('💯 AUC Score Comparison')\naxes[0, 1].set_ylabel('AUC Score')\naxes[0, 1].tick_params(axis='x', rotation=45)\n\n# Biểu đồ Cross-validation AUC\naxes[1, 0].bar(results_df['Model'], results_df['CV Mean'], yerr=results_df['CV Std'], \n               color='lightcoral', capsize=5)\naxes[1, 0].set_title('📉 CV AUC Score (± 2 Std)')\naxes[1, 0].set_ylabel('CV AUC Score')\naxes[1, 0].tick_params(axis='x', rotation=45)\n\n# Confusion Matrix của best model\nbest_model_name = results_df.loc[results_df['AUC Score'].idxmax(), 'Model']\nbest_model_results = model_results[best_model_name]\n\ncm = confusion_matrix(y_test, best_model_results['y_pred'])\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues', ax=axes[1, 1])\naxes[1, 1].set_title(f'🧮 Confusion Matrix - {best_model_name}')\naxes[1, 1].set_xlabel('Predicted')\naxes[1, 1].set_ylabel('Actual')\n\nplt.tight_layout()\nplt.show()\n\n# 3. In kết quả tổng hợp\nprint(f\"\\n🏆 Best model: {best_model_name}\")\nprint(f\"⭐ AUC Score: {best_model_results['auc']:.4f}\")\n\n# 4. Classification report chi tiết\nprint(f\"\\n🧾 Classification Report for {best_model_name}:\")\nprint(classification_report(y_test, best_model_results['y_pred']))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T08:28:12.631908Z","iopub.execute_input":"2025-06-17T08:28:12.632166Z","iopub.status.idle":"2025-06-17T08:28:14.105357Z","shell.execute_reply.started":"2025-06-17T08:28:12.632151Z","shell.execute_reply":"2025-06-17T08:28:14.104730Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import GridSearchCV\nfrom sklearn.metrics import accuracy_score, roc_auc_score\n\nprint(f\"🎯 Hyperparameter tuning for {best_model_name}...\")\n\nbest_model = model_results[best_model_name]['model']\n\n# Chỉ định grid search cho 2 model\nif best_model_name == 'XGBoost':\n    param_grid = {\n        'n_estimators': [200, 300],\n        'max_depth': [3, 6, 9],\n        'learning_rate': [0.05, 0.1],\n        'subsample': [0.8, 1.0]\n    }\nelif best_model_name == 'LightGBM':\n    param_grid = {\n        'n_estimators': [200, 300],\n        'max_depth': [5, 10],\n        'learning_rate': [0.05, 0.1],\n        'num_leaves': [20, 31]\n    }\n\n# Grid Search\ngrid_search = GridSearchCV(\n    best_model,\n    param_grid,\n    cv=3,\n    scoring='roc_auc',\n    n_jobs=-1,\n    verbose=1\n)\ngrid_search.fit(X_train, y_train)\n\nprint(f\"\\n✅ Best parameters: {grid_search.best_params_}\")\nprint(f\"📈 Best CV AUC: {grid_search.best_score_:.4f}\")\n\n# Evaluate tuned model\ntuned_model = grid_search.best_estimator_\ny_pred_tuned = tuned_model.predict(X_test)\ny_pred_proba_tuned = tuned_model.predict_proba(X_test)[:, 1]\n\ntuned_accuracy = accuracy_score(y_test, y_pred_tuned)\ntuned_auc = roc_auc_score(y_test, y_pred_proba_tuned)\n\nprint(f\"\\n🔍 Tuned model performance:\")\nprint(f\"Accuracy: {tuned_accuracy:.4f}\")\nprint(f\"AUC Score: {tuned_auc:.4f}\")\nprint(f\"📊 AUC Improvement: {tuned_auc - best_model_results['auc']:.4f}\")\n\n# Lưu lại mô hình tốt nhất\nfinal_model = tuned_model\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T08:41:59.421345Z","iopub.execute_input":"2025-06-17T08:41:59.421839Z","iopub.status.idle":"2025-06-17T08:47:08.267029Z","shell.execute_reply.started":"2025-06-17T08:41:59.421816Z","shell.execute_reply":"2025-06-17T08:47:08.266287Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def preprocess_test_data(df, label_encoders, scaler):\n    \"\"\"\n    Preprocess test data using the same transformations as training data\n    \"\"\"\n    df_processed = df.copy()\n    \n    # Convert Order date to datetime\n    if 'Order date' in df_processed.columns:\n        df_processed['Order date'] = pd.to_datetime(df_processed['Order date'])\n        # Extract date features\n        df_processed['order_month'] = df_processed['Order date'].dt.month\n        df_processed['order_day'] = df_processed['Order date'].dt.day\n        df_processed['order_dayofweek'] = df_processed['Order date'].dt.dayofweek\n        df_processed['order_quarter'] = df_processed['Order date'].dt.quarter\n    \n    # Convert VSD to datetime if exists\n    if 'VSD' in df_processed.columns:\n        df_processed['VSD'] = pd.to_datetime(df_processed['VSD'], errors='coerce')\n        # Calculate days between order and VSD\n        if 'Order date' in df_processed.columns:\n            df_processed['days_to_vsd'] = (df_processed['VSD'] - df_processed['Order date']).dt.days\n    \n    # Handle categorical variables with high cardinality (frequency encoding)\n    categorical_cols = df_processed.select_dtypes(include=['object']).columns.tolist()\n    categorical_cols = [col for col in categorical_cols if col not in ['Order date', 'VSD', 'ID']]\n    \n    # Apply frequency encoding for high cardinality columns\n    # Note: In a real scenario, we should save frequency maps from training data\n    high_cardinality_cols = []\n    for col in categorical_cols:\n        if col in df_processed.columns and df_processed[col].nunique() > 50:\n            high_cardinality_cols.append(col)\n            # For test data, use a simple approach or saved frequency maps\n            freq_map = df_processed[col].value_counts().to_dict()\n            df_processed[f'{col}_freq'] = df_processed[col].map(freq_map).fillna(0)\n    \n    # Drop original high cardinality columns\n    df_processed = df_processed.drop(columns=high_cardinality_cols)\n    \n    # Update categorical columns list\n    categorical_cols = [col for col in categorical_cols if col not in high_cardinality_cols]\n    \n    # Handle remaining categorical variables\n    for col in categorical_cols:\n        if col in df_processed.columns:\n            df_processed[col] = df_processed[col].fillna('Unknown')\n            # For test data, handle unseen categories\n            try:\n                # This is a simplified approach - in practice, save encoders from training\n                le = LabelEncoder()\n                df_processed[col] = le.fit_transform(df_processed[col].astype(str))\n            except:\n                df_processed[col] = 0  # Default value for unseen categories\n    \n    # Handle numerical columns\n    numerical_cols = df_processed.select_dtypes(include=[np.number]).columns.tolist()\n    \n    # Fill missing values in numerical columns\n    imputer = SimpleImputer(strategy='median')\n    df_processed[numerical_cols] = imputer.fit_transform(df_processed[numerical_cols])\n    \n    # Create additional features\n    if 'SO QTY' in df_processed.columns and 'ALLOCATION QTY' in df_processed.columns:\n        df_processed['qty_ratio'] = df_processed['ALLOCATION QTY'] / (df_processed['SO QTY'] + 1)\n    \n    if 'SUPPLIER INV AMOUNT' in df_processed.columns and 'SO QTY' in df_processed.columns:\n        df_processed['price_per_unit'] = df_processed['SUPPLIER INV AMOUNT'] / (df_processed['SO QTY'] + 1)\n    \n    # Drop datetime columns\n    datetime_cols = ['Order date', 'VSD']\n    df_processed = df_processed.drop(columns=[col for col in datetime_cols if col in df_processed.columns])\n    \n    # Keep only features that exist in training data\n    common_features = [col for col in X.columns if col in df_processed.columns]\n    missing_features = [col for col in X.columns if col not in df_processed.columns]\n    \n    if missing_features:\n        print(f\"Missing features in test data: {missing_features}\")\n        # Add missing features with default values\n        for feature in missing_features:\n            df_processed[feature] = 0\n    \n    # Select and reorder features to match training data\n    df_processed = df_processed[X.columns]\n    \n    return df_processed\n\n# Preprocess test data (PILOT_10.csv)\nprint(\"Preprocessing test data...\")\ntest_data_processed = preprocess_test_data(pilot_10, label_encoders, scaler)\n\n# Scale test data\ntest_data_scaled = scaler.transform(test_data_processed)\ntest_data_scaled = pd.DataFrame(test_data_scaled, columns=X.columns)\n\nprint(f\"Test data shape: {test_data_scaled.shape}\")\nprint(\"Test data preprocessing completed!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T08:47:38.624803Z","iopub.execute_input":"2025-06-17T08:47:38.625085Z","iopub.status.idle":"2025-06-17T08:47:39.828287Z","shell.execute_reply.started":"2025-06-17T08:47:38.625066Z","shell.execute_reply":"2025-06-17T08:47:39.827443Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Make predictions on test data\nprint(\"Making predictions on test data...\")\n\npredictions = final_model.predict(test_data_scaled)\nprediction_probabilities = final_model.predict_proba(test_data_scaled)[:, 1]\n\nprint(f\"Predictions made for {len(predictions)} samples\")\nprint(f\"Prediction distribution:\")\nprint(pd.Series(predictions).value_counts())\nprint(f\"Prediction distribution (%):\")\nprint(pd.Series(predictions).value_counts(normalize=True) * 100)\n\n# Create submission file\nsubmission = pd.DataFrame({\n    'ID': pilot_10['ID'],\n    'label': predictions\n})\n\n# Save submission file\nsubmission.to_csv('submission.csv', index=False)\nprint(\"\\nSubmission file created: submission.csv\")\nprint(\"First 10 predictions:\")\nprint(submission.head(10))\n\n# Verify submission format matches sample\nprint(f\"\\nSubmission shape: {submission.shape}\")\nprint(f\"Sample solution shape: {sample_solution.shape}\")\nprint(f\"Columns match: {list(submission.columns) == list(sample_solution.columns)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-17T08:47:48.546510Z","iopub.execute_input":"2025-06-17T08:47:48.547281Z","iopub.status.idle":"2025-06-17T08:47:48.914010Z","shell.execute_reply.started":"2025-06-17T08:47:48.547259Z","shell.execute_reply":"2025-06-17T08:47:48.913405Z"}},"outputs":[],"execution_count":null}]}