File size: 7,887 Bytes
6c2997c 8956b1e 6c2997c 8956b1e 6c2997c 8956b1e 6c2997c 8956b1e 6c2997c 8956b1e 6c2997c 8956b1e 6c2997c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | 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)
# Define Target Variable
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)}")
# 1. PREVENT DATA LEAKAGE: Identify and drop post-shipment features
leakage_cols = [
"days_for_shipping_real",
"delivery_status",
"shipping_date",
"order_status"
]
# Drop columns that are unique identifiers or text fields that can't be easily modeled
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)
# 2. FEATURE ENGINEERING
print("Performing advanced feature engineering...")
# Convert order_date to datetime to extract temporal features
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)
# Cross-feature interactions
# 1. Check if shipping is domestic or international (cross-border)
df_features["is_domestic"] = (df_features["customer_country"] == df_features["order_country"]).astype(int)
# 2. Check if shipping is within same state
df_features["is_same_state"] = (df_features["customer_state"] == df_features["order_state"]).astype(int)
# 3. Check if shipping is within same city
df_features["is_same_city"] = (df_features["customer_city"] == df_features["order_city"]).astype(int)
# 4. Calculate discount amount in currency
df_features["discount_amount"] = df_features["sales"] * df_features["order_item_discount_rate"]
# 5. Calculate unit price per item
df_features["price_per_item"] = df_features["sales"] / (df_features["order_item_quantity"] + 1e-5)
# Drop original order_date
df_features = df_features.drop(columns=["order_date"])
# 3. ENCODE CATEGORICAL VARIABLES
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
# Save encoders for future inference
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.")
# 4. TRAIN-TEST SPLIT
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}")
# 5. SCALE NUMERICAL FEATURES
# Identify numerical columns to scale
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])
# Save scaler for future inference
scaler_path = os.path.join(models_dir, "scaler.pkl")
with open(scaler_path, "wb") as f:
pickle.dump(scaler, f)
print("Saved feature scaler.")
# 6. MODEL TRAINING
# Model A: Random Forest (Baseline)
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))
# Model B: XGBoost Classifier (Advanced - Tuned)
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))
# Feature Importance (XGBoost)
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))
# 7. SAVE THE BEST MODEL
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}")
# Save a pickle version of the model for easy loading
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()
|