Spaces:
Sleeping
Sleeping
| # ============================================================================== | |
| # 1. IMPORT NECESSARY LIBRARIES | |
| # ============================================================================== | |
| import pandas as pd | |
| import scipy | |
| import lime | |
| import lime.lime_tabular | |
| import numpy as np | |
| from datetime import date | |
| # ============================================================================== | |
| # 2. GLOBAL CONFIGURATION | |
| # ============================================================================== | |
| RANDOM_SEED = 42 | |
| TARGET_LABELS = ["Not Returned (0)", "Returned (1)"] | |
| # ============================================================================== | |
| # 3. DATA PREPARATION FUNCTIONS (FEATURE ENGINEERING) | |
| # ============================================================================== | |
| def prepare_data_pre( | |
| customer_age: int, | |
| product_category: str, | |
| payment_method: str, | |
| order_value_usd: float, | |
| order_date: date | |
| ) -> pd.DataFrame: | |
| """ | |
| Transforms raw pre-delivery input parameters into a structured Pandas DataFrame. | |
| Extracts temporal features from the order date. | |
| """ | |
| print("โณ [FEATURE ENG] Structuring raw Pre-Delivery inputs...") | |
| # Wrap scalars in lists to correctly construct a single-row DataFrame | |
| df_testing = pd.DataFrame({ | |
| "customer_age": [customer_age], | |
| "product_category": [product_category], | |
| "payment_method": [payment_method], | |
| "order_value_usd": [order_value_usd], | |
| "order_date": [order_date] | |
| }) | |
| print("๐ [FEATURE ENG] Extracting temporal features from 'order_date'...") | |
| df_testing["order_date"] = pd.to_datetime(df_testing["order_date"]) | |
| df_testing["day"] = df_testing["order_date"].dt.day | |
| df_testing["month"] = df_testing["order_date"].dt.month | |
| df_testing["week"] = df_testing["order_date"].dt.isocalendar().week.astype(int) | |
| # Boolean flag: True if Monday-Friday, False if Saturday/Sunday | |
| df_testing["working_day"] = df_testing["order_date"].dt.day_of_week < 5 | |
| # Drop the original datetime column to prevent model errors | |
| df_testing.drop("order_date", axis=1, inplace=True) | |
| print(f"โ [FEATURE ENG] Pre-Delivery DataFrame ready. Shape: {df_testing.shape}") | |
| return df_testing | |
| def prepare_data_post( | |
| customer_age: int, | |
| product_category: str, | |
| payment_method: str, | |
| order_value_usd: float, | |
| delivery_time_days: int, | |
| customer_rating: float, | |
| order_date: date | |
| ) -> pd.DataFrame: | |
| """ | |
| Transforms raw post-delivery input parameters into a structured Pandas DataFrame. | |
| Includes additional post-delivery metrics (delivery time and rating). | |
| """ | |
| print("โณ [FEATURE ENG] Structuring raw Post-Delivery inputs...") | |
| df_testing = pd.DataFrame({ | |
| "customer_age": [customer_age], | |
| "product_category": [product_category], | |
| "payment_method": [payment_method], | |
| "order_value_usd": [order_value_usd], | |
| "delivery_time_days": [delivery_time_days], | |
| "customer_rating": [customer_rating], | |
| "order_date": [order_date] | |
| }) | |
| print("๐ [FEATURE ENG] Extracting temporal features from 'order_date'...") | |
| df_testing["order_date"] = pd.to_datetime(df_testing["order_date"]) | |
| df_testing["day"] = df_testing["order_date"].dt.day | |
| df_testing["month"] = df_testing["order_date"].dt.month | |
| df_testing["week"] = df_testing["order_date"].dt.isocalendar().week.astype(int) | |
| df_testing["working_day"] = df_testing["order_date"].dt.day_of_week < 5 | |
| df_testing.drop("order_date", axis=1, inplace=True) | |
| print(f"โ [FEATURE ENG] Post-Delivery DataFrame ready. Shape: {df_testing.shape}") | |
| return df_testing | |
| # ============================================================================== | |
| # 4. INFERENCE FUNCTION (PREDICTION) | |
| # ============================================================================== | |
| def predict(best_model, df_testing: pd.DataFrame, target_labels: list = TARGET_LABELS) -> tuple: | |
| """ | |
| Executes model inference on the prepared DataFrame. | |
| Extracts the predicted class, return probability, and overall confidence for a single instance. | |
| """ | |
| print("๐ง [INFERENCE] Executing model prediction...") | |
| # Generate predictions and probabilities using the loaded model | |
| y_pred = best_model.predict(df_testing) | |
| y_pred_proba = best_model.predict_proba(df_testing) | |
| # Map the numeric prediction to the corresponding string label | |
| result = [target_labels[pred] for pred in y_pred] | |
| # Extract the specific values for the single transaction (index 0) | |
| prediction = result[0] | |
| returned_proba = ((y_pred_proba[:, 1] * 100).round(2).astype(str) + '%')[0] | |
| prediction_conf = ((y_pred_proba.max(axis=1) * 100).round(2).astype(str) + '%')[0] | |
| # Log the extracted metrics to the terminal for observability | |
| print(f"๐ฏ [INFERENCE] Result: {prediction} | Prob: {returned_proba} | Conf: {prediction_conf}") | |
| return prediction, returned_proba, prediction_conf | |
| # ============================================================================== | |
| # 5. EXPLAINABILITY FUNCTION (LIME) | |
| # ============================================================================== | |
| def explain(best_model, X_train_processed: np.ndarray, df_testing: pd.DataFrame, target_label: list = TARGET_LABELS) -> str: | |
| """ | |
| Generates a LIME (Local Interpretable Model-agnostic Explanations) HTML output. | |
| Bypasses background data transformation as X_train_processed is already encoded. | |
| """ | |
| print("๐ [XAI] Initializing LIME Explainer...") | |
| # Extract the preprocessing step and the machine learning model from the pipeline | |
| try: | |
| preprocessor = best_model.named_steps["Preprocessor"] | |
| ml_model = best_model.named_steps["Model"] | |
| except KeyError as e: | |
| raise KeyError(f"๐จ [PIPELINE ERROR] Expected steps 'Preprocessor' and 'Model' not found in pipeline. Details: {e}") | |
| print("๐งฎ [XAI] Loading pre-processed background training data...") | |
| # Ensure the background data is a dense array for LIME compatibility | |
| if scipy.sparse.issparse(X_train_processed): | |
| X_train_processed = X_train_processed.toarray() | |
| # Extract feature names directly from the preprocessor to label the LIME output | |
| features = preprocessor.get_feature_names_out() | |
| # Initialize the Tabular Explainer with the pre-processed background data | |
| explainer = lime.lime_tabular.LimeTabularExplainer( | |
| training_data=X_train_processed, | |
| feature_names=features, | |
| class_names=target_label, | |
| mode="classification", | |
| random_state=RANDOM_SEED | |
| ) | |
| print("๐ [XAI] Processing single transaction instance for explanation...") | |
| # Isolate the single row to explain and apply the preprocessing pipeline | |
| transaction_raw = df_testing.iloc[[0]] | |
| processed_data = preprocessor.transform(transaction_raw) | |
| # Convert the processed single instance to a dense array if necessary | |
| if scipy.sparse.issparse(processed_data): | |
| processed_data = processed_data.toarray() | |
| # Flatten to a 1D array as required by LIME's explain_instance method | |
| transaction_data = processed_data[0] | |
| print("๐งฉ [XAI] Generating instance explanation (Top 10 features)...") | |
| # Generate the explanation using the isolated ML model's predict_proba method | |
| explanation = explainer.explain_instance( | |
| data_row=transaction_data, | |
| predict_fn=ml_model.predict_proba, | |
| num_features=10 | |
| ) | |
| print("โ [XAI] LIME HTML generation complete.") | |
| return explanation.as_html() |