Как посчитать Gini coefficient в SQL
Содержание:
Зачем Gini
Gini coefficient — мера неравенства. В макроэкономике — про доходы населения, в продукте — про любую неравномерность: выручка по клиентам, активность по пользователям, продажи по SKU. 0 = все равны, 1 = всё у одного.
В отличие от HHI, Gini чувствителен к распределению в средней части, не только к топу. Подходит для вопросов вида: «Сколько процентов пользователей делает 80% выручки?»
Через кривую Лоренца
Идея: упорядочиваем игроков по возрастанию, строим cumulative share по N и по выручке, считаем площадь между диагональю равенства и кривой Лоренца.
Формула через ranks:
Gini = (Σ (2*i − N − 1) * x_i) / (N * Σ x_i)где x_i — значение i-го элемента в отсортированной выборке.
Gini в SQL
WITH ranked AS (
SELECT
customer_id,
SUM(amount) AS revenue,
ROW_NUMBER() OVER (ORDER BY SUM(amount)) AS rnk,
COUNT(*) OVER () AS n
FROM orders
WHERE order_date >= '2026-01-01'
GROUP BY customer_id
)
SELECT
SUM((2 * rnk - n - 1) * revenue)::NUMERIC
/ NULLIF(n * SUM(revenue), 0) AS gini_coefficient
FROM ranked;Результат в [0, 1]. На реальных продуктах обычно 0.5–0.9 — выручка крайне неравномерна.
Gini по сегментам
В каких странах самая большая неравномерность по выручке:
WITH ranked AS (
SELECT
u.country,
o.customer_id,
SUM(o.amount) AS revenue,
ROW_NUMBER() OVER (PARTITION BY u.country ORDER BY SUM(o.amount)) AS rnk,
COUNT(*) OVER (PARTITION BY u.country) AS n
FROM orders o
JOIN users u USING (customer_id)
WHERE o.order_date >= CURRENT_DATE - INTERVAL '90 days'
GROUP BY u.country, o.customer_id
)
SELECT
country,
MAX(n) AS customers,
SUM((2 * rnk - n - 1) * revenue)::NUMERIC
/ NULLIF(MAX(n) * SUM(revenue), 0) AS gini
FROM ranked
GROUP BY country
HAVING MAX(n) >= 30
ORDER BY gini DESC;Gini во времени
Тренд неравенства помесячно:
WITH ranked AS (
SELECT
DATE_TRUNC('month', order_date)::DATE AS month,
customer_id,
SUM(amount) AS revenue,
ROW_NUMBER() OVER (PARTITION BY DATE_TRUNC('month', order_date) ORDER BY SUM(amount)) AS rnk,
COUNT(*) OVER (PARTITION BY DATE_TRUNC('month', order_date)) AS n
FROM orders
GROUP BY DATE_TRUNC('month', order_date), customer_id
)
SELECT
month,
SUM((2 * rnk - n - 1) * revenue)::NUMERIC
/ NULLIF(MAX(n) * SUM(revenue), 0) AS gini,
MAX(n) AS active_customers
FROM ranked
GROUP BY month
ORDER BY month;Растущий Gini = whales становятся ещё больше, бизнес зависит от хвоста меньше.
Частые ошибки
Ошибка 1. Не отсортировать.
Формула предполагает упорядоченность по возрастанию. ROW_NUMBER() OVER (ORDER BY x) обязателен.
Ошибка 2. Включить отрицательные значения.
Gini определён для неотрицательных. Refunds дают отрицательную revenue, индекс может выйти за [0,1]. Используйте GREATEST(amount, 0) или фильтр.
Ошибка 3. Маленькая выборка. Для < 30 игроков Gini шумит сильно. Сообщайте количество вместе с метрикой.
Ошибка 4. Сравнивать Gini между сегментами разного размера. Gini зависит от N. Используйте Atkinson или Theil, если нужно строгое сравнение.
Ошибка 5. Интерпретировать «0.4 как у Швеции». Контекст другой. Gini продукта 0.7 не значит, что он «несправедлив» — это норма для consumer apps (whales достаточно типичны).
Связанные темы
- Как посчитать Pareto в SQL
- Как посчитать HHI в SQL
- Как посчитать share of wallet в SQL
- Как посчитать customer segments в SQL
FAQ
Gini vs HHI?
HHI чувствителен к топ-игрокам (квадрат долей). Gini — к равномерности всей кривой. Для customer concentration HHI понятнее, для «насколько хвост слабый» — Gini.
Gini для активности?
Да: вместо revenue подставьте actions, sessions, posts. Так смотрят на «сколько % юзеров делает 80% активности».
Какой Gini «нормален»?
Для consumer SaaS — 0.6–0.8. Для B2B с enterprise клиентами — 0.8+. Для гомогенных сегментов (например, все на 1 тарифе) — 0.3–0.5.
Как объяснить продакту?
«Gini 0.7 = top-10% юзеров делают примерно 60% всей выручки». Точнее показывает кривая Лоренца.
Можно ли по событиям?
Да: например, неравенство постов в соцсети, vote-распределение в комьюнити, число сделок у sales-репов.