Spaces:
Sleeping
Sleeping
| """Customer segmentation page.""" | |
| import sys | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent.parent)) | |
| import pandas as pd | |
| import streamlit as st | |
| from sklearn.decomposition import PCA | |
| from sqlalchemy import text | |
| from customer_intelligence.analytics.customers import rfm_segment_distribution | |
| from customer_intelligence.app.components.charts import make_bar_chart, make_scatter | |
| from customer_intelligence.db import engine | |
| from customer_intelligence.ml.segmentation import SEGMENT_NAMES | |
| st.title("🔢 Customer Segmentation") | |
| SEGMENT_FEATURES = [ | |
| "recency_days", "frequency", "monetary", | |
| "avg_order_value", "tenure_days", | |
| ] | |
| def load_segment_data(): | |
| sql = text(""" | |
| SELECT | |
| dc.customer_unique_id, | |
| dc.segment_label, | |
| dc.rfm_segment, | |
| CAST(julianday(:today) - julianday(MAX(f.order_date_id)) AS INTEGER) AS recency_days, | |
| COUNT(DISTINCT f.order_id) AS frequency, | |
| SUM(f.price + f.freight_value) AS monetary, | |
| AVG(f.price + f.freight_value) AS avg_order_value, | |
| CAST(julianday(MAX(f.order_date_id)) - julianday(MIN(f.order_date_id)) AS INTEGER) AS tenure_days | |
| FROM fact_orders f | |
| JOIN dim_customers dc ON f.customer_key = dc.customer_key | |
| WHERE dc.segment_label IS NOT NULL | |
| AND f.order_status NOT IN ('canceled', 'unavailable') | |
| GROUP BY dc.customer_unique_id, dc.segment_label, dc.rfm_segment | |
| LIMIT 20000 | |
| """) | |
| from datetime import date | |
| import pandas as pd | |
| with engine.connect() as conn: | |
| df = pd.read_sql(sql, conn, params={"today": str(date.today())}) | |
| df[SEGMENT_FEATURES] = df[SEGMENT_FEATURES].apply(pd.to_numeric, errors="coerce") | |
| df = df.dropna(subset=SEGMENT_FEATURES) | |
| return df | |
| df = load_segment_data() | |
| segment_counts = ( | |
| df.groupby("segment_label").size().reset_index(name="customers") | |
| .assign(segment_name=lambda x: x["segment_label"].map(SEGMENT_NAMES)) | |
| ) | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.plotly_chart( | |
| make_bar_chart(segment_counts, x="segment_name", y="customers", title="Cluster Sizes"), | |
| width="stretch", | |
| ) | |
| with col2: | |
| rfm_dist = rfm_segment_distribution(engine) | |
| st.plotly_chart( | |
| make_bar_chart(rfm_dist, x="rfm_segment", y="customers", title="RFM Segment Distribution"), | |
| width="stretch", | |
| ) | |
| st.subheader("2D PCA of Customer Clusters") | |
| X = df[SEGMENT_FEATURES].values | |
| pca = PCA(n_components=2, random_state=42) | |
| coords = pca.fit_transform(X) | |
| pca_df = pd.DataFrame({ | |
| "PC1": coords[:, 0], | |
| "PC2": coords[:, 1], | |
| "Cluster": df["segment_label"].map(lambda x: SEGMENT_NAMES.get(x, f"Cluster {x}")), | |
| }) | |
| st.plotly_chart( | |
| make_scatter(pca_df, x="PC1", y="PC2", color="Cluster", | |
| title="Customer Clusters (PCA 2D)", opacity=0.4), | |
| width="stretch", | |
| ) | |
| st.subheader("Segment Profiles") | |
| profile = df.groupby("segment_label")[SEGMENT_FEATURES].mean().round(2) | |
| profile.index = profile.index.map(lambda x: SEGMENT_NAMES.get(x, f"Cluster {x}")) | |
| profile.index.name = "Segment" | |
| st.dataframe(profile, width="stretch") | |