| import os |
| import pickle |
| import pandas as pd |
| import numpy as np |
| from sklearn.model_selection import train_test_split |
| from sklearn.preprocessing import LabelEncoder, StandardScaler |
| from sklearn.ensemble import RandomForestClassifier |
| from sklearn.metrics import classification_report, accuracy_score, roc_auc_score, confusion_matrix |
| import xgboost as xgb |
|
|
| def train_delay_model(): |
| cleaned_csv_path = os.path.join("Data", "cleaned_data.csv") |
| models_dir = "Models" |
| os.makedirs(models_dir, exist_ok=True) |
| |
| if not os.path.exists(cleaned_csv_path): |
| print(f"Error: Cleaned data not found at {cleaned_csv_path}. Run data_cleaning.py first.") |
| return |
|
|
| print("Loading cleaned dataset...") |
| df = pd.read_csv(cleaned_csv_path) |
| |
| |
| target_col = "late_delivery_risk" |
| if target_col not in df.columns: |
| print(f"Error: Target column '{target_col}' not found in dataset.") |
| return |
| |
| print(f"Target distribution:\n{df[target_col].value_counts(normalize=True)}") |
|
|
| |
| leakage_cols = [ |
| "days_for_shipping_real", |
| "delivery_status", |
| "shipping_date", |
| "order_status" |
| ] |
| |
| |
| id_text_cols = [ |
| "customer_fname", |
| "customer_lname", |
| "customer_street", |
| "customer_zipcode", |
| "order_zipcode", |
| "product_name", |
| "category_name", |
| "department_name" |
| ] |
| |
| drop_cols = leakage_cols + id_text_cols |
| drop_cols = [col for col in drop_cols if col in df.columns] |
| |
| print(f"Dropping post-shipment (leakage) and text/id columns: {drop_cols}") |
| df_features = df.drop(columns=drop_cols) |
| |
| |
| print("Performing advanced feature engineering...") |
| |
| df_features["order_date"] = pd.to_datetime(df_features["order_date"]) |
| df_features["order_year"] = df_features["order_date"].dt.year |
| df_features["order_month"] = df_features["order_date"].dt.month |
| df_features["order_day"] = df_features["order_date"].dt.day |
| df_features["order_hour"] = df_features["order_date"].dt.hour |
| df_features["order_dayofweek"] = df_features["order_date"].dt.dayofweek |
| df_features["is_weekend"] = df_features["order_dayofweek"].isin([5, 6]).astype(int) |
| |
| |
| |
| df_features["is_domestic"] = (df_features["customer_country"] == df_features["order_country"]).astype(int) |
| |
| df_features["is_same_state"] = (df_features["customer_state"] == df_features["order_state"]).astype(int) |
| |
| df_features["is_same_city"] = (df_features["customer_city"] == df_features["order_city"]).astype(int) |
| |
| |
| df_features["discount_amount"] = df_features["sales"] * df_features["order_item_discount_rate"] |
| |
| |
| df_features["price_per_item"] = df_features["sales"] / (df_features["order_item_quantity"] + 1e-5) |
| |
| |
| df_features = df_features.drop(columns=["order_date"]) |
| |
| |
| print("Encoding categorical features...") |
| categorical_cols = [ |
| "type", |
| "customer_segment", |
| "customer_city", |
| "customer_state", |
| "customer_country", |
| "market", |
| "order_city", |
| "order_state", |
| "order_country", |
| "order_region", |
| "shipping_mode" |
| ] |
| |
| label_encoders = {} |
| for col in categorical_cols: |
| if col in df_features.columns: |
| df_features[col] = df_features[col].fillna("Unknown").astype(str) |
| le = LabelEncoder() |
| df_features[col] = le.fit_transform(df_features[col]) |
| label_encoders[col] = le |
| |
| |
| encoders_path = os.path.join(models_dir, "label_encoders.pkl") |
| with open(encoders_path, "wb") as f: |
| pickle.dump(label_encoders, f) |
| print("Saved label encoders.") |
|
|
| |
| X = df_features.drop(columns=[target_col]) |
| y = df_features[target_col] |
| |
| print(f"Number of features: {X.shape[1]}") |
| |
| X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y) |
| print(f"Train shape: {X_train.shape}, Test shape: {X_test.shape}") |
| |
| |
| |
| numerical_cols = [ |
| "benefit_per_order", "sales_per_customer", "latitude", "longitude", |
| "order_item_discount", "order_item_discount_rate", "order_item_product_price", |
| "order_item_profit_ratio", "order_item_quantity", "sales", "order_item_total", |
| "order_profit_per_order", "product_price", "days_for_shipping_scheduled", |
| "discount_amount", "price_per_item" |
| ] |
| numerical_cols = [col for col in numerical_cols if col in X.columns] |
| |
| scaler = StandardScaler() |
| X_train[numerical_cols] = scaler.fit_transform(X_train[numerical_cols]) |
| X_test[numerical_cols] = scaler.transform(X_test[numerical_cols]) |
| |
| |
| scaler_path = os.path.join(models_dir, "scaler.pkl") |
| with open(scaler_path, "wb") as f: |
| pickle.dump(scaler, f) |
| print("Saved feature scaler.") |
|
|
| |
| |
| print("\nTraining Random Forest Classifier (Baseline)...") |
| rf_model = RandomForestClassifier(n_estimators=100, max_depth=12, random_state=42, n_jobs=-1) |
| rf_model.fit(X_train, y_train) |
| rf_preds = rf_model.predict(X_test) |
| |
| print("\nRandom Forest Test Evaluation:") |
| print(f"Accuracy: {accuracy_score(y_test, rf_preds):.4f}") |
| print(f"ROC-AUC: {roc_auc_score(y_test, rf_model.predict_proba(X_test)[:, 1]):.4f}") |
| print("\nClassification Report:") |
| print(classification_report(y_test, rf_preds)) |
| |
| |
| print("\nTraining Tuned XGBoost Classifier...") |
| xgb_model = xgb.XGBClassifier( |
| n_estimators=300, |
| max_depth=8, |
| learning_rate=0.05, |
| subsample=0.8, |
| colsample_bytree=0.8, |
| random_state=42, |
| eval_metric="logloss", |
| n_jobs=-1 |
| ) |
| xgb_model.fit(X_train, y_train) |
| xgb_preds = xgb_model.predict(X_test) |
| xgb_probs = xgb_model.predict_proba(X_test)[:, 1] |
| |
| print("\nTuned XGBoost Test Evaluation:") |
| print(f"Accuracy: {accuracy_score(y_test, xgb_preds):.4f}") |
| print(f"ROC-AUC: {roc_auc_score(y_test, xgb_probs):.4f}") |
| print("\nClassification Report:") |
| print(classification_report(y_test, xgb_preds)) |
| print("\nConfusion Matrix:") |
| print(confusion_matrix(y_test, xgb_preds)) |
|
|
| |
| importances = xgb_model.feature_importances_ |
| feat_imp = pd.Series(importances, index=X.columns).sort_values(ascending=False) |
| print("\nTop 10 Feature Importances (XGBoost):") |
| print(feat_imp.head(10)) |
|
|
| |
| xgb_model_path = os.path.join(models_dir, "best_xgb_model.json") |
| xgb_model.save_model(xgb_model_path) |
| print(f"\nSaved best XGBoost model to {xgb_model_path}") |
| |
| |
| xgb_pkl_path = os.path.join(models_dir, "best_xgb_model.pkl") |
| with open(xgb_pkl_path, "wb") as f: |
| pickle.dump(xgb_model, f) |
| print(f"Saved best XGBoost model as pickle to {xgb_pkl_path}") |
| print("Model training completed successfully!") |
|
|
| if __name__ == "__main__": |
| train_delay_model() |
|
|