Реализация AI-сегментации клиентов
Ручная сегментация клиентов — это 3-5 бизнес-сегментов, созданных интуитивно и пересматриваемых раз в квартал. ML-сегментация обнаруживает 10-20 статистически значимых кластеров, автоматически описывает их через LLM и обновляет ежедневно. Разница в precision маркетинговых кампаний: 15-25% vs 40-60% CTR.
Feature Engineering для сегментации
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 + поведенческие признаки"""
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()
# Заполнение NaN для одиночных заказов
rfm['order_std'] = rfm['order_std'].fillna(0)
# Временные паттерны
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()
# Межзаказный интервал
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()
# Объединение всех признаков
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
Кластеризация с оптимальным числом сегментов
def find_optimal_segments(self, features_df: pd.DataFrame,
max_k: int = 20) -> int:
"""Метод elbow + silhouette для выбора числа кластеров"""
from sklearn.metrics import silhouette_score
X = features_df.select_dtypes(include='number').fillna(0)
X_scaled = self.scaler.fit_transform(X)
# Снижение размерности для ускорения
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 дорогой для больших 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 из-за двойного diff и смещения
# Проверяем, что silhouette подтверждает
sil_optimal = np.argmax(silhouettes) + 2
# Компромисс
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:
"""Кластеризация и описание сегментов"""
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 как основной алгоритм
km = KMeans(n_clusters=n_clusters, random_state=42, n_init=10)
features_df['cluster'] = km.fit_predict(X_scaled)
# UMAP для визуализации (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-описание сегментов
def describe_segments(self) -> dict[int, dict]:
"""Автоматическое описание каждого кластера через 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]
# Статистика по кластеру
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"""Ты маркетинговый аналитик. Опиши сегмент клиентов по данным.
Статистика сегмента:
- Размер: {stats['size']:,} клиентов ({stats['pct_of_total']:.1f}% от базы)
- Средняя давность покупки: {stats['avg_recency_days']:.0f} дней назад
- Средняя частота: {stats['avg_frequency']:.1f} заказов
- Средняя выручка: {stats['avg_monetary']:,.0f} руб.
- Средний чек: {stats['avg_order_value']:,.0f} руб.
- Доля покупок в выходные: {stats['weekend_ratio']:.1%}
Дай:
1. Название сегмента (2-4 слова, например "Лояльные чемпионы" или "Группа риска")
2. Описание в 2-3 предложениях — кто эти люди, их паттерн поведения
3. Рекомендуемую маркетинговую стратегию (1-2 конкретных действия)"""
}]
)
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
Автоматическое назначение сегментов новым клиентам
def assign_new_customer(self, customer_features: dict) -> dict:
"""Real-time сегментация нового клиента"""
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])
# Расстояния до центров кластеров
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])
}
Производительность на реальных данных
| Размер базы | Число признаков | Время кластеризации | Оптимальных кластеров |
|---|---|---|---|
| 10K клиентов | 25 | ~30 сек | 6-8 |
| 100K клиентов | 25 | ~3 мин | 10-15 |
| 1M клиентов | 25 | ~25 мин | 15-25 |
| 1M клиентов | 25 + PCA(20) | ~8 мин | 15-25 |
Для real-time назначения сегментов предобученная модель скорит нового клиента за 5-10 мс. Пересчёт сегментации полной базы — раз в неделю или при накоплении 10%+ новых клиентов.







