File size: 4,360 Bytes
72d0706 | 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 | import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.decomposition import PCA
import joblib
import os
import sys
# Add parent directory to sys.path to import path_utils
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from path_utils import SCALED_DATA_PATH, KMEANS_MODEL_PATH, OUTPUTS_DIR, CUSTOMER_SEGMENTS_PATH, CLEANED_DATA_PATH, SEGMENT_PRODUCTS_PATH
def evaluate_and_visualize():
print("Starting evaluation and visualization...")
# Load data
if not os.path.exists(SCALED_DATA_PATH) or not os.path.exists(KMEANS_MODEL_PATH):
print("Error: Required files not found. Run previous steps first.")
return
data_dict = joblib.load(SCALED_DATA_PATH)
X = data_dict['rfm_scaled']
rfm_raw = data_dict['rfm_raw']
kmeans = joblib.load(KMEANS_MODEL_PATH)
# Assign labels
cluster_labels = kmeans.labels_
rfm_raw['Cluster'] = cluster_labels
# 1. PCA for 2D Visualization
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
pca_df = pd.DataFrame(X_pca, columns=['PCA1', 'PCA2'])
pca_df['Cluster'] = cluster_labels
plt.figure(figsize=(10, 8))
sns.scatterplot(x='PCA1', y='PCA2', hue='Cluster', data=pca_df, palette='viridis', alpha=0.7)
plt.title('Customer Segments PCA Visualization')
plt.savefig(os.path.join(OUTPUTS_DIR, "cluster_pca_plot.png"))
plt.close()
# 2. Compute mean RFM per cluster to assign business labels
cluster_summary = rfm_raw.groupby('Cluster').agg({
'Recency': 'mean',
'Frequency': 'mean',
'Monetary': 'mean'
}).sort_values('Monetary', ascending=False)
print("\nCluster RFM Means:")
print(cluster_summary)
# 3. Label Clusters Based on Profile
# Note: We need a dynamic way or manual based on summary.
# Usually: Highest monetary/freq + Lowest Recency = Champions
# Let's map based on the sorted summary (by Monetary primarily)
# This is a heuristic since cluster IDs can change.
# Mapping based on sorted monetary:
# Top 1: Champions
# Top 2: Loyal
# Top 3: At-Risk
# Top 4: Lost
cluster_mapping = {}
sorted_ids = cluster_summary.index.tolist()
labels = ["Champions", "Loyal Customers", "At-Risk", "Lost/Hibernating"]
for i, cid in enumerate(sorted_ids):
if i < len(labels):
cluster_mapping[cid] = labels[i]
else:
cluster_mapping[cid] = f"Other {i}"
rfm_raw['Segment'] = rfm_raw['Cluster'].map(cluster_mapping)
print("\nCluster Mapping Applied:")
for cid, label in cluster_mapping.items():
print(f"Cluster {cid} -> {label}")
# 4. RFM Heatmap
# Normalize values for better heatmap visualization (0 to 1 scaling of the means)
summary_norm = (cluster_summary - cluster_summary.min()) / (cluster_summary.max() - cluster_summary.min())
plt.figure(figsize=(10, 6))
sns.heatmap(summary_norm.T, annot=True, cmap='RdYlGn')
plt.title('Relative Behavioral Metrics by Cluster')
plt.savefig(os.path.join(OUTPUTS_DIR, "rfm_cluster_heatmap.png"))
plt.close()
# 5. Save results
rfm_raw.to_csv(CUSTOMER_SEGMENTS_PATH)
print(f"Customer segments saved to {CUSTOMER_SEGMENTS_PATH}")
# 6. Segment Product Affinity (Market Basket Analysis)
print("Calculating Segment Product Affinity...")
if os.path.exists(CLEANED_DATA_PATH):
df_clean = pd.read_csv(CLEANED_DATA_PATH)
# Merge with segment labels
df_merged = df_clean.merge(rfm_raw[['Cluster', 'Segment']], left_on='Customer ID', right_index=True)
# Calculate Top 10 Products per Segment (by Total Quantity)
top_products = df_merged.groupby(['Segment', 'Description'])['Quantity'].sum().reset_index()
top_products = top_products.sort_values(['Segment', 'Quantity'], ascending=[True, False])
# Take Top 10 for each segment
top_10_per_segment = top_products.groupby('Segment').head(10)
top_10_per_segment.to_csv(SEGMENT_PRODUCTS_PATH, index=False)
print(f"Segment product affinity saved to {SEGMENT_PRODUCTS_PATH}")
else:
print(f"Warning: Cleaned data not found at {CLEANED_DATA_PATH}. Skipping product affinity.")
if __name__ == "__main__":
evaluate_and_visualize()
|