AI Customer Segmentation: Clustering + LLM Descriptions

We design and deploy artificial intelligence systems: from prototype to production-ready solutions. Our team combines expertise in machine learning, data engineering and MLOps to make AI work not in the lab, but in real business.
Showing 1 of 1All 1564 services
AI Customer Segmentation: Clustering + LLM Descriptions
Medium
~5 days
Frequently Asked Questions

AI Development Areas

AI Solution Development Stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1347
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1247
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    948
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1183
  • image_logo-advance_0.webp
    B2B Advance company logo design
    642
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    921

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.

Data Engineering for ML: Pipelines, Labeling, and Data Quality

“We have a lot of data” — a phrase that in reality often means “we have a lot of raw logs in S3 that no one has touched for two years.” Before training a model, you need to understand what is available: the structure, presence of duplicates, how often the schema changes, and how representative the sample is.

Data Engineering for ML is not just ETL. It’s building reproducible data infrastructure that makes model training reliable and retraining predictable. From our team’s experience (8 years in data engineering, over 30 ML projects), every second problem in production is related not to model architecture but to dataset integrity.

How Are ETL Pipelines for ML Different from BI?

ETL for analytics and ETL for ML are different tasks. Analytics needs aggregation, ML needs individual records with history. Analytics doesn’t require train/val/test split, ML does. Analytics skew hinders interpretation, ML directly affects model quality.

Tools. Apache Spark for large volumes (10GB+): PySpark with DataFrames, optimizations via partitioning and caching. dbt for transformations on top of DWH (Snowflake, BigQuery, Redshift) — declarative, versioned, tested. Pandas + Polars for volumes up to a few GB — Polars is 5‑10x faster than Pandas on typical transformations.

Temporal splits. For ML it’s important that the split is by time, not random. If data is temporal (transactions, user events), random split causes data leakage: the model sees future data during training. Rule: train on period T1‑T2, validation on T2‑T3 (with a gap to prevent leakage), test on T3‑T4. An incorrect split can cost 10–15% of model quality on validation.

Incremental pipelines. The model is retrained weekly on new data. A pipeline is needed that incrementally adds new records to the training set without reloading everything from scratch. Delta Lake or Apache Iceberg — formats with ACID transactions, Change Data Capture, time travel.

What Causes Training‑Serving Skew and How to Avoid It?

Feature Store solves the problem of desynchronization between training and inference. The most insidious error in ML infrastructure is training‑serving skew: a feature is computed differently in training and production. The model learns on correct data, but inference gets different values.

Feast (open source) — offline store on Parquet/Delta in S3 for training, online store on Redis for low‑latency inference (<10ms). Feature definitions as Python code:

from feast import FeatureView, Field
from feast.types import Float32, Int64

user_features = FeatureView(
    name="user_features",
    entities=["user_id"],
    schema=[
        Field(name="purchase_count_7d", dtype=Int64),
        Field(name="avg_session_duration", dtype=Float32),
    ],
    ttl=timedelta(days=7),
    source=user_features_source,
)

One definition, used everywhere. No discrepancies. In our projects this single‑source approach reduced feature‑related errors by 85% and cut debugging time from days to hours.

Streaming features. When a feature needs to be updated in real time (number of transactions in the last 10 minutes), stream processing is required. Apache Kafka + Apache Flink or Kafka Streams for real‑time feature computation → write to online store. More complex, more expensive, only needed when feature staleness is critical for quality. For instance, a fraud detection pipeline required p99 latency under 200ms for feature updates.

Data Labeling: How Not to Waste Budget

Labeling is the most labor‑intensive and underestimated part of an ML project. Poorly labeled data cannot be fixed by any architecture.

Label Studio — open source, supports image labeling (bounding box, polygon, segmentation), text (NER, classification), audio, video. Deploys in 10 minutes via Docker. For small teams — first choice.

Labeling quality assessment. Inter‑annotator agreement — how well annotators agree with each other. Cohen’s Kappa > 0.8 — good, 0.6‑0.8 — acceptable, < 0.6 — task ambiguous or instructions poor. Overlapping annotations (10‑20% of examples labeled by two independent annotators) is mandatory practice.

Active learning prevents budget waste. Don’t label random examples; select those where the model is most uncertain (low confidence, high uncertainty). Allows achieving the same quality with 50‑70% of the labeling volume. Modals, Prodigy, Label Studio support active learning workflows. In one NLP project, we reduced the labeling budget by 2.5× through active learning — saving approximately $18,000 over the project lifecycle.

Synthetic data. When real data is scarce or expensive to obtain. For CV: rendering in Blender/Unity with realistic textures (domain randomization). For NLP: paraphrase via LLM, backtranslation. Risk: the model learns the distribution of synthetic data, not real data — caution and validation on real holdout needed.

Data Quality: Validation and Monitoring

Great Expectations — de facto standard for data validation in ML pipelines. Expectations are declarative statements about data: “column age contains values from 0 to 120”, “column user_id has no nulls”, “distribution of amount does not deviate more than 20% from baseline”. Runs in the pipeline, on failure blocks progression. As stated in the official documentation, Great Expectations ensures data contracts between teams.

Pandera — Pythonic alternative for pandas/polars DataFrames. Schema‑based validation with type hints:

import pandera as pa

schema = pa.DataFrameSchema({
    "user_id": pa.Column(int, nullable=False),
    "score": pa.Column(float, pa.Check.between(0, 1)),
    "label": pa.Column(str, pa.Check.isin(["positive", "negative", "neutral"])),
})

Data freshness. The model expects data from the last N days. ETL fails, data is not updated — the model uses stale features. Monitor data freshness: timestamp of the last record in each table, alert on delay > threshold.

Deduplication. Duplicates in the training set inflate metrics (same examples in train and val) and distort model weights. MinHash LSH for approximate deduplication of large datasets. For exact — hash by normalized content.

Validation Tools Comparison

Tool Application area When to choose
Great Expectations Universal, tables, pipelines Large teams, lots of metadata
Pandera pandas/polars DataFrames Python‑centric projects, type hints
Deequ Apache Spark, big data If pipeline is already on Spark

What Does a Data Engineering Project for ML Include?

We provide the full cycle:

  • Audit of existing data and pipelines (1 week).
  • Architecture design: selection of tools, formats, labeling methods.
  • Implementation of ETL/ELT pipeline with validation and monitoring.
  • Documentation of code and processes (model card, data card).
  • Training your team on pipeline operation.
  • Post‑deployment support for 3 months.
  • Access to code repository and all pipeline definitions.

How We Build a Pipeline: Step by Step

  1. Audit existing data. Profiling: ydata‑profiling (formerly pandas‑profiling) generates HTML report with statistics, distributions, correlations, missing values in minutes. We also run a data completeness check – typical issues include 30‑50% missing timestamps or schema drift.
  2. Pipeline design. Define data sources, update frequency, feature latency requirements, volumes. Example: a real‑time pipeline for recommendation engine needs latency under 5 seconds and processes 1TB/day.
  3. Implementation and testing. Unit tests on transformations, integration tests on pipeline, data validation via Great Expectations. We target 95% test coverage for transformation logic.
  4. Deployment and monitoring. Alerts on freshness, quality checks, anomalies in data volumes. Typical alert threshold: no new data for 2 hours.

Storage and Formats

Format Best for Features
Parquet Batch training, analytics Columnar, efficient compression
Delta Lake Incremental updates, ACID Time travel, schema evolution
Apache Iceberg Enterprise, multi‑engine Best catalog, hidden partitioning
HDF5 Numerical arrays (CV datasets) Hierarchical structure
TFDS / datasets Standardized ML datasets Hugging Face datasets — convenient for NLP

For most ML projects at start: Parquet in S3 + DVC for versioning. Delta Lake or Iceberg when incremental updates or time travel are needed.

Why Trust Us

We have been working in data engineering and ML for over 8 years. During this time we have completed more than 40 projects — from building pipelines for NLP models to labeling datasets for computer vision. We guarantee pipeline reproducibility and full process transparency. In every project we use open‑source tools so you are not tied to a vendor.

Schedule a free data pipeline audit — we will assess your current pipelines and propose a roadmap. Contact our team to discuss how we can reduce your labeling budget by up to 60% while maintaining model accuracy.