AI Customer Segmentation: Clustering + LLM Descriptions

Manual customer segmentation into 3-5 groups fails to reflect real behavior and reduces marketing effectiveness. We build AI solutions that automatically identify clusters based on RFM analysis and behavioral data, then use LLMs to create clear segment portraits. Our team delivers turnkey projects from data audit to implementation and ongoing support, ensuring a reliable and scalable solution for your business.

AI Development Areas

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1344
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1306
  • B2B Advance company logo design
    B2B Advance company logo design
    753
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1049
  • AIDER company logo development
    AIDER company logo development
    991
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1097

Imagine spending budget on email campaigns but never exceeding a 20% CTR. The reason is manual segmentation into 3-5 groups that fail to reflect actual customer behavior. An ML approach automatically identifies up to 20 statistically significant clusters based on RFM features, temporal patterns, and demographics. For example, for an electronics e-commerce store we identified 12 segments instead of 4 manual ones, boosting email CTR from 18% to 55%. As a result, marketing campaign CTR rises from 15-25% to 40-60%. Our stack: Python, PyTorch, KMeans, DBSCAN, PCA, UMAP, LLM (Claude, GPT). Experience: 30+ ML segmentation projects for e-commerce and fintech.

Problems We Solve

Suboptimal number of segments. Manually picking 3-5 groups ignores natural data structure. We use elbow method and silhouette coefficient to automatically determine cluster count (typically 6-15). The Silhouette method evaluates clustering quality.

Black-box interpretability. KMeans clusters without descriptions are useless for marketing. So we added LLM generation of segment names and profiles — the model receives centroids and statistics, returns ready natural language text.

Scaling to millions of customers. Clustering 1M records takes ~25 minutes, real-time segment assignment 5-10 ms. We apply PCA for speed and incremental recalculation when 10%+ new users are added.

How We Do It: Pipeline

Feature engineering is the key step. We build 25+ features: RFM, order amount standard deviation, weekend purchase ratio, night purchases, average inter-order interval and its regularity. The full pipeline includes 6 steps:

  1. Data collection and preprocessing (cleaning, merging tables).
  2. Feature engineering: generate 25+ features (RFM, temporal, behavioral).
  3. Clustering algorithm selection: KMeans for convex shapes, DBSCAN for non-convex.
  4. Model training with automatic cluster count detection (elbow + silhouette).
  5. LLM generation of segment descriptions (Claude, GPT).
  6. Deploy REST API for real-time assignment.

The CustomerSegmentation class code demonstrates the full pipeline.

import pandas as pd
import numpy as np
from anthropic import Anthropic
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans, DBSCAN
from sklearn.decomposition import PCA
import umap

class CustomerSegmentation:
    def __init__(self, customers_df: pd.DataFrame, orders_df: pd.DataFrame):
        self.customers = customers_df
        self.orders = orders_df
        self.llm = Anthropic()
        self.scaler = StandardScaler()
        self.segments = None

    def build_rfm_features(self) -> pd.DataFrame:
        """RFM + behavioral features"""
        now = pd.Timestamp.now()
        rfm = self.orders.groupby('customer_id').agg(
            recency_days=('order_date', lambda x: (now - x.max()).days),
            frequency=('order_id', 'nunique'),
            monetary=('amount', 'sum'),
            avg_order_value=('amount', 'mean'),
            first_order_days_ago=('order_date', lambda x: (now - x.min()).days),
            order_std=('amount', 'std'),
            max_order=('amount', 'max'),
            category_diversity=('category', 'nunique'),
        ).reset_index()
        # Fill NaN for single orders
        rfm['order_std'] = rfm['order_std'].fillna(0)
        # Temporal patterns
        self.orders['order_hour'] = pd.to_datetime(self.orders['order_date']).dt.hour
        self.orders['order_dow'] = pd.to_datetime(self.orders['order_date']).dt.dayofweek
        time_features = self.orders.groupby('customer_id').agg(
            preferred_hour=('order_hour', lambda x: x.mode()[0]),
            weekend_ratio=('order_dow', lambda x: (x >= 5).mean()),
            night_ratio=('order_hour', lambda x: ((x >= 22) | (x < 6)).mean()),
        ).reset_index()
        # Inter-order interval
        self.orders_sorted = self.orders.sort_values(['customer_id', 'order_date'])
        self.orders_sorted['prev_order'] = self.orders_sorted.groupby('customer_id')['order_date'].shift(1)
        self.orders_sorted['days_between'] = (
            pd.to_datetime(self.orders_sorted['order_date']) - pd.to_datetime(self.orders_sorted['prev_order'])
        ).dt.days
        interval_features = self.orders_sorted.groupby('customer_id').agg(
            avg_days_between=('days_between', 'mean'),
            purchase_regularity=('days_between', lambda x: 1 / (x.std() + 1))
        ).reset_index()
        # Merge all features
        features = rfm.merge(time_features, on='customer_id', how='left')
        features = features.merge(interval_features, on='customer_id', how='left')
        features = features.merge(self.customers[['customer_id', 'city', 'age', 'gender']], on='customer_id', how='left')
        return features

Clustering with Optimal Segment Count

def find_optimal_segments(self, features_df: pd.DataFrame, max_k: int = 20) -> int:
    """Elbow + silhouette method for cluster count selection"""
    from sklearn.metrics import silhouette_score
    X = features_df.select_dtypes(include='number').fillna(0)
    X_scaled = self.scaler.fit_transform(X)
    # Dimensionality reduction for speed
    pca = PCA(n_components=min(20, X_scaled.shape[1]))
    X_pca = pca.fit_transform(X_scaled)
    inertias = []
    silhouettes = []
    for k in range(2, min(max_k + 1, len(X_pca))):
        km = KMeans(n_clusters=k, random_state=42, n_init=10)
        labels = km.fit_predict(X_pca)
        inertias.append(km.inertia_)
        if k <= 15:  # Silhouette expensive for large k
            silhouettes.append(silhouette_score(X_pca, labels, sample_size=2000))
    # Elbow method
    diffs = np.diff(inertias)
    diff2 = np.diff(diffs)
    elbow_k = np.argmax(diff2) + 3  # +3 due to double diff and shift
    # Check if silhouette confirms
    sil_optimal = np.argmax(silhouettes) + 2
    # Compromise
    optimal_k = round((elbow_k + sil_optimal) / 2)
    return max(4, min(optimal_k, max_k))

def cluster_customers(self, features_df: pd.DataFrame, n_clusters: int = None) -> pd.DataFrame:
    """Clustering and segment description"""
    numeric_features = features_df.select_dtypes(include='number').fillna(0)
    X_scaled = self.scaler.fit_transform(numeric_features)
    if n_clusters is None:
        n_clusters = self.find_optimal_segments(features_df)
    # K-Means as primary algorithm
    km = KMeans(n_clusters=n_clusters, random_state=42, n_init=10)
    features_df['cluster'] = km.fit_predict(X_scaled)
    # UMAP for visualization (2D)
    reducer = umap.UMAP(n_components=2, random_state=42)
    X_2d = reducer.fit_transform(X_scaled)
    features_df['umap_x'] = X_2d[:, 0]
    features_df['umap_y'] = X_2d[:, 1]
    self.segments = features_df
    self.cluster_centers = pd.DataFrame(
        self.scaler.inverse_transform(km.cluster_centers_),
        columns=numeric_features.columns
    )
    return features_df

LLM Segment Description

def describe_segments(self) -> dict[int, dict]:
    """Automatic description of each cluster via LLM"""
    if self.segments is None:
        raise ValueError("Run cluster_customers first")
    segment_descriptions = {}
    for cluster_id in self.segments['cluster'].unique():
        cluster_data = self.segments[self.segments['cluster'] == cluster_id]
        center = self.cluster_centers.iloc[cluster_id]
        # Cluster statistics
        stats = {
            'size': len(cluster_data),
            'pct_of_total': len(cluster_data) / len(self.segments) * 100,
            'avg_recency_days': cluster_data['recency_days'].mean(),
            'avg_frequency': cluster_data['frequency'].mean(),
            'avg_monetary': cluster_data['monetary'].mean(),
            'avg_order_value': cluster_data['avg_order_value'].mean(),
            'weekend_ratio': cluster_data.get('weekend_ratio', pd.Series([0])).mean(),
        }
        response = self.llm.messages.create(
            model="claude-3-5-sonnet-20241022",
            max_tokens=400,
            messages=[{
                "role": "user",
                "content": f"""You are a marketing analyst. Describe a customer segment based on the data.
Segment statistics:
- Size: {stats['size']:,} customers ({stats['pct_of_total']:.1f}% of base)
- Average recency: {stats['avg_recency_days']:.0f} days ago
- Average frequency: {stats['avg_frequency']:.1f} orders
- Average revenue: {stats['avg_monetary']:,.0f} currency
- Average order value: {stats['avg_order_value']:,.0f} currency
- Weekend purchase ratio: {stats['weekend_ratio']:.1%}
Provide:
1. Segment name (2-4 words, e.g., "Loyal Champions" or "At Risk Group")
2. Description in 2-3 sentences – who these people are, their behavior pattern
3. Recommended marketing strategy (1-2 specific actions)"""
            }]
        )
        text = response.content[0].text
        lines = text.strip().split('\n')
        segment_descriptions[cluster_id] = {
            'stats': stats,
            'name': lines[0].replace('1. ', '').strip() if lines else f"Segment {cluster_id}",
            'description': text,
            'cluster_id': cluster_id
        }
    return segment_descriptions

Automatic Segment Assignment for New Customers

def assign_new_customer(self, customer_features: dict) -> dict:
    """Real-time segmentation of a new customer"""
    feature_vector = pd.DataFrame([customer_features])
    numeric_cols = self.cluster_centers.columns.tolist()
    for col in numeric_cols:
        if col not in feature_vector.columns:
            feature_vector[col] = 0
    feature_vector_scaled = self.scaler.transform(feature_vector[numeric_cols])
    # Distances to cluster centers
    from sklearn.metrics import pairwise_distances
    centers_scaled = self.scaler.transform(self.cluster_centers)
    distances = pairwise_distances(feature_vector_scaled, centers_scaled)[0]
    cluster_id = distances.argmin()
    confidence = 1 - distances[cluster_id] / distances.sum()
    return {
        'cluster_id': int(cluster_id),
        'confidence': float(confidence),
        'distance': float(distances[cluster_id])
    }

How to Choose the Optimal Number of Segments?

We don't guess. We use a combination of elbow method (inflection point) and silhouette coefficient. In practice, the optimal number ranges from 6 to 15. For databases over 100K records, we apply PCA with 20 components — this speeds up calculations by 3x without quality loss.

Why LLM Description is More Effective Than Manual Analysis?

A marketer spends 2-3 days analyzing each cluster and often misinterprets. LLM (we use Claude, GPT) receives numerical centroids and generates name, description, and strategy in 10 seconds. For example, a segment with high frequency and low recency gets the name "Loyal Champions" and a recommendation for loyalty programs. This reduces segmentation time for marketing by 90% and improves targeted marketing accuracy.

Performance on Real Data

Database Size Number of Features Clustering Time Optimal Clusters
10K customers 25 ~30 sec 6-8
100K customers 25 ~3 min 10-15
1M customers 25 ~25 min 15-25
1M customers 25 + PCA(20) ~8 min 15-25

For real-time segment assignment, the pre-trained model scores a new customer in 5-10 ms. Full resegmentation is performed weekly or when 10%+ new customers accumulate.

Clustering Algorithm Comparison

Criterion K-Means DBSCAN
Cluster shape Convex Any
Noise sensitivity High Low
Requires cluster count Yes No
Speed High Medium
Scalability Excellent Good

Algorithm choice depends on data structure: for clear spherical groups — K-Means, for complex shapes with outliers — DBSCAN.

Typical Segmentation Mistakes - Using only RFM features without behavioral ones (purchase time, regularity). - Choosing cluster count by eye without objective metrics. - Ignoring seasonality and outliers. - Lack of segment interpretation — clusters remain a "black box". - Irregular model updates when the customer base changes.

What's Included

  • Feature engineering tailored to your data structure (up to 30 features).
  • Training KMeans/DBSCAN models with automatic cluster count selection.
  • LLM-generated descriptions for each segment in your language.
  • REST API for real-time segment assignment (p99 latency < 20 ms).
  • Pipeline and code documentation.
  • Integration with your CRM or email platform.
  • Team training on segmentation usage.

Timeline: 2 to 8 weeks depending on data volume and integration complexity. Project cost is determined after analysis.

Get a consultation: our engineers with 5+ years of ML experience analyze your base and select the optimal algorithm. We guarantee increased CTR and up to 20-30% savings on marketing budget. Contact us for a pipeline demo on your data.