Статистические распределения — числа «как в жизни»
Обычный <gen type="number" value="150..200"/> выдаёт
числа равномерно — 150 так же вероятно, как 175 или 199. Но реальные
данные почти никогда так не выглядят: рост людей кучкуется вокруг среднего,
зарплаты перекошены (много средних, мало очень больших), а богатство и вовсе
с длинным, тяжёлым хвостом. Чтобы сгенерировать правдоподобные числа,
добавьте к генератору чисел атрибут distribution.
Как включить
Никакого нового тега — distribution это атрибут на знакомом
<gen type="number">:
<gen type="number" distribution="normal" mean="170" sd="10"/>
Как только есть distribution="...", генератор переключается из режима
«диапазон» в режим «распределение». Нет этого атрибута — работает
в точности как раньше.
Вот четыре колонки, каждая из своего распределения (сид demo, 8 строк):
<env count="8" seed="demo">
<sequence name="Рост"> <gen type="number" distribution="normal" mean="170" sd="10"/></sequence>
<sequence name="Зарплата"><gen type="number" distribution="lognormal" meanlog="10.8" sdlog="0.5"/></sequence>
<sequence name="Пауза"> <gen type="number" distribution="exponential" rate="0.5" decimals="1"/></sequence>
<sequence name="Баланс"> <gen type="number" distribution="pareto" alpha="1.5" xmin="1000"/></sequence>
</env>
156 см | 29884 ₽ | 0.4 с | 1329 ₽ 175 см | 75675 ₽ | 0.0 с | 2337 ₽ 179 см | 35084 ₽ | 1.1 с | 3587 ₽ 160 см | 74516 ₽ | 3.2 с | 3130 ₽ 178 см | 65256 ₽ | 1.1 с | 3285 ₽ 170 см | 39817 ₽ | 0.4 с | 1664 ₽ 159 см | 107734 ₽ | 1.9 с | 8781 ₽ 182 см | 65121 ₽ | 1.4 с | 2924 ₽
Рост держится около 170, зарплаты перекошены вправо, паузы почти всегда короткие, а баланс не опускается ниже 1000, но иногда «выстреливает».
Гистограммы и значения ниже — это то, что выдаёт типичный запуск. Точные числа
могут отличаться в зависимости от версии ядра, но неизменной остаётся форма,
которую гарантирует каждое распределение. Распределения детерминированы: тот же
seed — те же числа.
Равномерно или «как в жизни» — видно на гистограмме
Проблема. Глядя на столбик чисел, не поймёшь, «плоские» они или собраны
в кучу. Нарисуем форму: сгенерируем 300 «ростов» двумя способами и разложим их
по корзинам. Каждая строка гистограммы — это диапазон, а # показывают,
сколько значений в него попало (внизу n — всего значений, min/max — края).
Обычный диапазон — value="150..200". Числа рассыпаны ровным слоем: рост
150 так же вероятен, как 175 или 199. В жизни так не бывает:
<gen type="number" value="150..200"/>
150 | ############################# 27 154 | ####################### 21 158 | ############################### 29 163 | ######################## 22 167 | ##################### 19 171 | ######################## 22 175 | ################### 18 179 | ################################## 31 183 | ############################# 27 188 | ##################################### 34 192 | ############## 13 196 | ######################################## 37 n=300 min=150 max=200
То же, но distribution="normal" (центр 175, разброс 8). Появляется
«колокол»: большинство ростов у центра, к краям всё реже — как у настоящих людей:
<gen type="number" distribution="normal" mean="175" sd="8"/>
157 | ### 5 161 | ############# 19 165 | ###################### 33 169 | ####################################### 57 172 | ######################################## 59 176 | ########################## 39 180 | ################################## 50 184 | ################# 25 188 | #### 6 192 | ## 3 195 | ## 3 199 | # 1 n=300 min=157 max=203
- обычный диапазон: любой рост примерно так же вероятен, как любой другой
- тот же диапазон с нормальным распределением: холм вокруг центра
Диапазон значений почти тот же (≈150–200), а форма совершенно разная: у равномерного все столбики примерно одной высоты, у нормального — горка с пиком у 175 и редкими краями. В этом весь смысл — переход от «равномерно (нереалистично)» к «как в жизни».
Все девять форм, каждая сгенерирована с теми атрибутами, что подписаны снизу. Горизонтальная ось обрезана по 99-му перцентилю — иначе длинный хвост сжимает всё видимое в одну первую колонку:
- сгенерированные значения, разложенные по корзинам
normal — «колокол»
Что это. Значения кучкуются вокруг центра и становятся тем реже, чем дальше от него, симметрично в обе стороны. Классическая колоколообразная кривая.
Где встречается. Рост, вес, температура, оценки на экзамене, ошибки измерений — всё, что колеблется вокруг типичного значения.
Параметры. mean — центр (среднее). sd — разброс (стандартное отклонение:
насколько широко значения расходятся от центра). Правило: примерно две трети
значений попадают в mean ± sd.
<gen type="number" distribution="normal" mean="170" sd="10"/>
147 | #### 6 153 | ################# 28 159 | ################################## 55 164 | ######################################## 65 170 | ################################### 57 176 | #################################### 59 182 | ############# 21 188 | ### 5 193 | ## 3 199 | # 1 n=300 min=147 max=205
→ рост в сантиметрах: большинство между 160 и 180, редко 150 или 190. Симметричный «колокол» с пиком у 170.
Когда брать. Колонка вроде «рост», «вес» или «температура» — любая величина с типичным значением и симметричным разбросом вокруг него.
lognormal — перекос вправо
Что это. Как normal, но «завалено» вправо: очень много небольших значений
и длинный хвост крупных. Никогда не бывает отрицательным.
Где встречается. Зарплаты, цены, размеры городов, длительность сессий — там, где «много обычного, мало огромного».
Параметры. meanlog и sdlog — это центр и разброс логарифма значения
(так это распределение задают в статистике). На практике: больше meanlog →
крупнее типичное значение; больше sdlog → длиннее хвост богатых. Удобный
ориентир — типичное значение ≈ e^meanlog (при meanlog=10.8 это ≈ 49 000).
<gen type="number" distribution="lognormal" meanlog="10.8" sdlog="0.5"/>
15633 | ######################################## 114 41886 | ####################################### 110 68140 | ################## 51 94393 | ###### 17 120646 | # 3 146900 | # 1 173153 | # 2 199406 | # 1 225659 | 0 251913 | # 1 n=300 min=15633 max=278166
→ зарплата: толпа около 50 000, немного людей — сильно больше. Пик слева, длинный хвост вправо (до ~278 000).
Когда брать. «Зарплата», «цена», «размер файла» — величины, которые не бывают отрицательными и имеют перекос в сторону больших значений.
exponential — время между событиями
Что это. Промежутки между случайными событиями: много коротких, изредка длинные. Всегда неотрицательное.
Где встречается. Время между заказами, между кликами, между отказами сервера, между звонками в колл-центр.
Параметры. rate — это λ (лямбда): сколько событий в среднем происходит
за единицу времени. Среднее ожидание = 1 / rate. При rate=0.5 события
происходят примерно раз в 2 единицы, поэтому типичная пауза — около 2.
<gen type="number" distribution="exponential" rate="0.5"/>
0 | ############################ 70 1 | ######################################## 98 2 | ################### 47 3 | ################ 38 4 | ####### 17 5 | ####### 18 6 | # 3 7 | # 3 8 | # 2 9 | # 1 10 | # 2 12 | # 1 n=300 min=0 max=12
→ «секунд до следующего события»: обычно немного, иногда долго. Самый высокий столбик — у нижнего края, дальше спад: коротких пауз больше всего.
Когда брать. Любая колонка «сколько времени прошло до…» / «интервал между…».
pareto — «правило 80/20», длинный хвост
Что это. Экстремальный перекос: почти все значения небольшие, но редкие — огромные. Тот самый принцип «20% причин дают 80% следствий».
Где встречается. Богатство (немного людей владеют почти всем), просмотры роликов, размеры файлов, население городов, число подписчиков.
Параметры. xmin — минимально возможное значение (ниже него не бывает).
alpha — «толщина хвоста»: чем меньше alpha, тем толще хвост (больше
редких гигантов). При alpha около 1–2 хвост очень выражен.
<gen type="number" distribution="pareto" alpha="1.5" xmin="1000"/>
1002 | ######################################## 259 4234 | #### 23 7466 | # 8 10698 | # 5 13930 | # 1 17162 | 0 20393 | # 1 23625 | # 2 26857 | 0 30089 | # 1 n=300 min=1002 max=33321
→ «баланс на счёте»: у всех не меньше 1000, но у единиц — в разы больше. Почти всё (259 из 300) в первой корзине, а редкие гиганты уходят до ~33 000 — тот самый «длинный хвост».
Когда брать. Величины, где несколько экземпляров доминируют над всеми остальными: деньги, популярность, размеры.
weibull — время до отказа, надёжность
Что это. Гибкое распределение «времени до события»: в отличие от
exponential (где риск постоянен), здесь риск может расти или падать
со временем. Форму задаёт один параметр.
Где встречается. Срок службы деталей до поломки, время до отказа оборудования, скорость ветра, размеры частиц.
Параметры. shape (форма): shape < 1 — «детская смертность» (ломается
рано или живёт долго); shape = 1 — ведёт себя как exponential (постоянный
риск); shape > 1 — износ (чем дольше работает, тем вероятнее откажет).
scale — характерный масштаб времени (примерно типичное время до отказа).
<gen type="number" distribution="weibull" shape="1.5" scale="1000"/>
30 | ########################## 57 358 | ######################################## 88 687 | ##################### 47 1015 | ######################## 52 1343 | ########## 21 1672 | ########## 23 2000 | ### 6 2328 | # 2 2656 | # 3 2985 | # 1 n=300 min=30 max=3313
→ «часов до отказа»: обычно сотни–тысячи, с износом. При shape=1.5 пик не
прижат к нулю (как у exponential), а сдвинут вправо: сначала риск растёт,
потом хвост спадает.
Когда брать. Любая «наработка до отказа» / «время до события», где риск не постоянен во времени.
poisson — счётчики событий
Что это. Сколько случайных событий произошло за интервал. Целое число. Значения кучкуются около среднего, но это счётчики (0, 1, 2, 3…), а не непрерывная величина.
Где встречается. Звонки в колл-центр за час, дефекты на партию, письма в день, голы за матч, посетители в минуту.
Параметры. lambda — среднее число событий за интервал. При lambda=4
обычно получается 2–6 событий, изредка 0 или 8+.
<gen type="number" distribution="poisson" lambda="4"/>
0 | #### 5 1 | ############ 17 2 | #################################### 50 3 | ####################################### 53 4 | ##################################### 51 5 | ######################################## 55 6 | #################### 28 7 | ############### 21 8 | ########## 14 9 | ### 4 10 | # 2 n=300 min=0 max=10
→ «звонков за час»: 3, 0, 7, 5, 3… Это счётчики (целые), пик у среднего
lambda=4, значения расходятся в обе стороны, но никогда не бывают меньше 0.
Ограничение. lambda ограничена сверху числом 700. Для очень больших
средних берите normal с mean=lambda и sd ≈ корень из lambda — это
стандартное приближение.
Когда брать. Любая колонка «сколько раз случилось за период».
zipf — ранжированные данные, «звёзды и длинный хвост»
Что это. Немного «звёзд» (ранг 1, 2, 3…) встречаются очень часто, а огромный хвост редких — почти никогда. Выдаёт номер ранга (1, 2, … n).
Где встречается. Частота слов в языке, популярность сайтов, города по населению, число подписчиков, продажи товаров.
Параметры. n — сколько всего рангов (например, 100 товаров). s —
«крутизна»: больше s → сильнее перекос в сторону первых рангов. При s≈1
это классический закон Ципфа.
<gen type="number" distribution="zipf" n="100" s="1.1"/>
1 | ######################################## 174 11 | ########## 42 20 | ###### 27 30 | ### 12 39 | # 4 49 | ### 11 59 | ## 7 68 | ## 8 78 | ## 9 87 | # 6 n=300 min=1 max=97
→ «ранг товара»: чаще всего #1–#5, изредка #58 или #37. Первые ранги забирают почти всё (174 из 300 — это ранги 1–10), остальные 90 позиций — редкий длинный хвост.
Когда брать. Когда нужно смоделировать «топ доминирует» — выбор из пронумерованного списка, где первые позиции берут почти всё.
gamma — суммарное время ожидания
Что это. Обобщение exponential: если exponential — это время до
одного события, то gamma — суммарное время до нескольких. Всегда
неотрицательное, с перекосом вправо.
Где встречается. Время до накопления N событий, страховые выплаты, объёмы осадков, размеры очередей, время ответа сервиса (сумма этапов).
Параметры. shape (k) — «сколько этапов/событий суммируется» (больше
shape → пик симметричнее и правее). scale (θ) — масштаб. Среднее ≈
shape · scale.
<gen type="number" distribution="gamma" shape="2" scale="1000"/>
71 | ############################ 53 803 | ######################################## 77 1534 | ############################# 56 2266 | ############################# 55 2997 | ######### 18 3729 | ######### 17 4460 | ###### 11 5192 | #### 8 5923 | # 2 6655 | ## 3 n=300 min=71 max=7386
→ «миллисекунд ожидания»: обычно около 2000 (2·1000), с хвостом. В отличие
от exponential, пик не прижат к нулю, а сдвинут вправо — это сумма двух этапов.
Когда брать. Суммарные времена или объёмы, где складывается несколько случайных вкладов.
beta — доли и вероятности (число от 0 до 1)
Что это. Единственное здесь распределение, чьи значения всегда между 0 и 1. Форму (где пик, насколько симметрично) задают два параметра.
Где встречается. Доли, проценты, вероятности, коэффициенты конверсии, рейтинги 0–1, доля брака.
Параметры. alpha и beta «перетягивают» массу к 1 и к 0 соответственно.
alpha = beta — симметрично вокруг 0.5; alpha < beta — перекос к нулю (как
низкая конверсия); alpha > beta — к единице. Среднее ≈ alpha / (alpha + beta).
<gen type="number" distribution="beta" alpha="2" beta="8" decimals="3"/>
Поскольку значения лежат между 0 и 1, подписи корзин слева округляются до 0 —
смотрите на min/max внизу, чтобы увидеть реальный разброс (0.008..0.583):
0 | ######################## 35 0 | ######################################## 59 0 | ################################# 49 0 | ############################### 46 0 | ################################# 48 0 | ############## 21 0 | ############ 18 0 | ######### 14 0 | #### 6 1 | ### 4 n=300 min=0.008 max=0.583
→ «коэффициент конверсии»: обычно 0.05–0.4 (среднее ≈ 0.2). Всё зажато в 0..1,
пик у ~0.2, к единице почти ничего не доходит. Значения дробные — задайте
decimals, иначе они округлятся до 0/1.
Когда брать. Любая величина-доля в диапазоне 0..1: вероятность, процент, конверсия, рейтинг.
Управление выводом: decimals, min, max
decimals — знаки после запятой
По умолчанию 0 — значение округляется до целого (рост «170», а не «170.4213»).
Нужны дроби — задайте явно:
<gen type="number" distribution="exponential" rate="1" decimals="3"/> <!-- например, 0.693 -->
Это важнее всего для beta (чьи значения живут в 0..1) и для любого времени или
отношения, где округление до целого «съело» бы детали.
min / max — обрезка по границам
Распределения иногда выдают крайние значения (normal при малом mean может
уйти в минус). min / max удерживают значение в границах: всё, что ниже min,
прижимается к min, всё, что выше max, — к max.
<gen type="number" distribution="normal" mean="30" sd="20" min="0"/> <!-- возраст не бывает отрицательным -->
Это видно на гистограмме. Без min нормальное с mean=30 sd=20 заходит
в отрицательную область (min=-16):
-16 | ### 6 -4 | ############### 27 7 | ############################## 52 19 | ################################## 60 30 | ######################################## 70 42 | ############################### 55 53 | ############ 21 65 | ## 4 76 | ## 4 88 | # 1 n=300 min=-16 max=99
С min="0" всё, что было ниже нуля, прижимается к нулю — левый край ровно
0, а нулевая корзина «раздувается» (она вобрала бывшие отрицательные):
0 | ################################# 46 10 | ################################# 46 20 | ####################################### 53 30 | ######################################## 55 40 | ####################################### 54 50 | ####################### 31 59 | ###### 8 69 | ## 3 79 | ## 3 89 | # 1 n=300 min=0 max=99
Оба атрибута необязательны. Без них вы получаете «сырое» распределение. Берите их, когда у колонки есть жёсткий пол или потолок — возраст, процент с потолком 100, неотрицательная сумма.
- слева без границ, справа тот же прогон с границами 160 и 180
Чего нельзя совмещать
distribution заменяет обычный числовой режим, поэтому его нельзя ставить
на тот же генератор вместе с диапазоном или процентами — TDC об этом честно
сообщит:
value(диапазон),percent,length,includeиexclude— все несовместимы сdistribution, ошибкаTDC088:
error[TDC088]: <gen type="number" distribution="..."> cannot be combined with "value" note: A distribution replaces the range/percent. Remove "value", or drop "distribution" to use a range.
- незнакомое имя распределения или пропущенный обязательный параметр (например,
normalбезsd) — ошибкаTDC089с подсказкой, что требуется:
error[TDC089]: distribution "normal": "sd" is required and must be a number note: Distributions: normal (mean, sd), lognormal (meanlog, sdlog), exponential (rate), pareto (alpha, xmin). Optional: decimals, min, max.
Детерминизм и большие объёмы
Как и всё в TDC, распределения детерминированы: тот же seed даёт тот же
результат. И они работают на любом объёме — значение каждой строки считается
по её номеру, поэтому память не растёт с числом строк (см.
Большие объёмы вывода). Миллиард логнормальных
«зарплат» — не проблема.
Все девять распределений готовы: normal, lognormal, exponential,
pareto, weibull, poisson, zipf, gamma и beta — точные,
детерминированные и работающие потоково на любом объёме.
Смотрите также
- Number — режимы диапазона и строки-из-цифр, которые заменяет этот атрибут.
- Большие объёмы вывода — почему распределения остаются дешёвыми даже на миллиарде строк.
- Связанные данные — как удерживать сгенерированные поля согласованными внутри строки.