File size: 2,836 Bytes
bfde63f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import numpy as np
import pandas as pd

FEATURE_COLUMNS = [
    "recency_days",
    "frequency_purchases",
    "monetary_total_spend",
    "avg_order_value",
    "website_visits_30d",
    "product_views_30d",
    "cart_additions_30d",
    "cart_abandonments_30d",
    "email_opens_30d",
    "email_clicks_30d",
    "support_tickets_30d",
    "engagement_change_pct",
    "return_rate",
    "discount_dependency_ratio",
    "spend_30d",
    "spend_90d",
    "purchase_velocity",
    "cart_abandon_ratio",
    "email_click_ratio",
    "rfm_score"
]

class CustomerFeatureEngineer:
    def __init__(self):
        pass

    def transform(self, df: pd.DataFrame) -> pd.DataFrame:
        """
        Transforms raw customer DataFrame into structured features.
        """
        df = df.copy()
        
        # Avoid division by zero
        cart_additions = df["cart_additions_30d"].replace(0, 1)
        df["cart_abandon_ratio"] = (df["cart_abandonments_30d"] / cart_additions).clip(0, 1.0)
        
        email_opens = df["email_opens_30d"].replace(0, 1)
        df["email_click_ratio"] = (df["email_clicks_30d"] / email_opens).clip(0, 1.0)
        
        # Calculate RFM Scores (1-5 quantile or heuristic scoring)
        if len(df) > 5:
            try:
                recency_score = pd.qcut(df["recency_days"], q=5, labels=[5, 4, 3, 2, 1], duplicates='drop').astype(float)
            except Exception:
                recency_score = df["recency_days"].apply(lambda r: 5.0 if r <= 14 else (4.0 if r <= 30 else (3.0 if r <= 60 else (2.0 if r <= 90 else 1.0))))
                
            try:
                freq_score = pd.qcut(df["frequency_purchases"].rank(method='first'), q=5, labels=[1, 2, 3, 4, 5]).astype(float)
            except Exception:
                freq_score = df["frequency_purchases"].apply(lambda f: min(5.0, max(1.0, f / 4.0)))

            try:
                monetary_score = pd.qcut(df["monetary_total_spend"].rank(method='first'), q=5, labels=[1, 2, 3, 4, 5]).astype(float)
            except Exception:
                monetary_score = df["monetary_total_spend"].apply(lambda m: min(5.0, max(1.0, m / 8000.0)))
        else:
            recency_score = df["recency_days"].apply(lambda r: 5.0 if r <= 14 else (4.0 if r <= 30 else (3.0 if r <= 60 else (2.0 if r <= 90 else 1.0))))
            freq_score = df["frequency_purchases"].apply(lambda f: min(5.0, max(1.0, float(f) / 4.0)))
            monetary_score = df["monetary_total_spend"].apply(lambda m: min(5.0, max(1.0, float(m) / 8000.0)))

        df["rfm_score"] = (recency_score * 0.35 + freq_score * 0.35 + monetary_score * 0.30)
        
        return df[FEATURE_COLUMNS]

    def extract_single_customer_features(self, customer_dict: dict) -> pd.DataFrame:
        df = pd.DataFrame([customer_dict])
        return self.transform(df)