
Линейное преобразование для расчёта рейтинга пользователя
Во многих backend-системах возникает одна и та же задача: у пользователя есть несколько метрик, а нам нужно одно число, по которому можно принимать решения.
Например:
- кто VIP
- кого продвигать
- кому дать скидку
- кто особо не активен
Для этого идеально подходит линейное преобразование.
Линейное преобразование — это способ: превратить набор показателей в одну итоговую оценку, учитывая важность каждого показателя.
Математически оно выглядит так:
score = X @ W + b
- X — вектор признаков пользователя
- W -веса (важность каждого признака)
- b — смещение (порог / базовый уровень)
Зачем вообще нужно преобразование
Без преобразования у нас есть набор разрозненных данных:
- частота покупок
- сумма покупок
- давность последней активности
С ними сложно:
- сортировать пользователей
- сравнивать между собой
- принимать автоматические решения
Линейное преобразование решает эту проблему, сводя всё к одному числу — рейтингу.
Определяем данные для расчета
orders_30d — количество заказов за 30 дней
spend_30d — сумма покупок за 30 дней
days_since_last — дней с последней покупки
avg_check_90d — средний чек за 90 дней
Это и есть наш вектор признаков:
X = [orders_30d, spend_30d, days_since_last, avg_check_90d]
Нормализуем
Признаки находятся в разных масштабах:
- заказы — единицы
- суммы — тысячи
- дни — десятки
Если их не привести к одному диапазону, большие числа «сломают» формулу.
Простейшая нормализация:
orders_norm = min(orders_30d, 10) / 10
spend_norm = min(spend_30d, 50000) / 50000
recency_norm = min(days_since_last, 60) / 60
avg_check_norm = min(avg_check_90d, 10000) / 10000
Теперь все значения лежат в диапазоне 0..1.
Задаём веса — бизнес-смысл формулы
Веса отвечают на вопрос:
что для нас важнее?
Допустим:
- частота покупок — очень важна
- сумма — важна
- давность последней покупки — негативный фактор
- средний чек — дополнительный плюс
Тогда:
W = [ 0.45, 0.35, -0.50, 0.20 ]
b = 0
Знак веса важен:
- + усиливает вклад признака
- — уменьшает рейтинг при росте признака
Считаем рейтинг пользователя
Формула:
score =
orders_norm * 0.45 +
spend_norm * 0.35 +
recency_norm * (-0.50) +
avg_check_norm * 0.20
Пример пользователя:
- 6 заказов → 0.6
- 18 000 ₽ → 0.36
- 3 дня → 0.05
- средний чек 4200 → 0.42
Подставляем:
score =
0.6 * 0.45 +
0.36 * 0.35 +
0.05 * (-0.50) +
0.42 * 0.20
= 0.455
0.455 — рейтинг пользователя
Пример расчета на PHP
// Входные данные
$userData = [
'orders_30d' => 6, // заказов за 30 дней
'spend_30d' => 18000, // сумма покупок за 30 дней (₽)
'days_since_last' => 3, // дней с последней покупки
'avg_check_90d' => 4200, // средний чек за 90 дней
];
// Настройки веса
$config = [
'max_orders_30d' => 10,
'max_spend_30d' => 50000,
'max_days_since_last' => 60,
'max_avg_check_90d' => 10000,
'weights' => [
'orders_30d' => 0.45,
'spend_30d' => 0.35,
'days_since_last' => -0.50,
'avg_check_90d' => 0.20,
],
'bias' => 0.0,
];
// Функции расчета
function clamp01(float $value): float {
return max(0.0, min(1.0, $value));
}
function normalizeUserData(array $data, array $config): array {
return [
'orders_30d' => clamp01(
min($data['orders_30d'], $config['max_orders_30d'])
/ $config['max_orders_30d']
),
'spend_30d' => clamp01(
min($data['spend_30d'], $config['max_spend_30d'])
/ $config['max_spend_30d']
),
'days_since_last' => clamp01(
min($data['days_since_last'], $config['max_days_since_last'])
/ $config['max_days_since_last']
),
'avg_check_90d' => clamp01(
min($data['avg_check_90d'], $config['max_avg_check_90d'])
/ $config['max_avg_check_90d']
),
];
}
function calculateUserRating(array $normalized, array $weights, float $bias = 0.0): float {
$score = 0.0;
foreach ($weights as $key => $weight) {
$score += ($normalized[$key] ?? 0.0) * $weight;
}
return $score + $bias;
}
// Запускаем
$normalized = normalizeUserData($userData, $config);
$rating = calculateUserRating($normalized, $config['weights'], $config['bias']);
echo "Normalized data:\n";
print_r($normalized);
echo "\nUser rating: " . round($rating, 3) . PHP_EOL;
Результат
Array
(
[orders_30d] => 0.6
[spend_30d] => 0.36
[days_since_last] => 0.05
[avg_check_90d] => 0.42
)
User rating: 0.455