Перейти к основному содержимому

Генератор timeseries

Когда нужен. Когда значения должны двигаться как настоящий сигнал во времени — продажи по дням, показания датчика, веб-трафик. Реальные ряды — это не плоский шум и не одно распределение: это слои — общий тренд (рост или спад), повторяющаяся сезонность (недельная, годовая) и случайный шум сверху. Генератор timeseries собирает значение строки именно так:

значение(i) = base + trend·i + amplitude·sin(2π·i / period) + noise·случайное

где i — номер строки (ось времени), считается с нуля: первая строка (i = 0) равна ровно base.

Выводы ниже иллюстративны: точные цифры зависят от версии ядра и seed, но важна именно форма — тренд, волна, дрожание.

Один и тот же генератор четыре раза, с добавлением одного атрибута на каждом шаге — по 120 строк на панель.
  • Aтолько base: ровная линия
  • Bдобавлен trend: линия пошла вверх
  • Cдобавлены period и amplitude: поверх тренда легла волна
  • Dдобавлен noise: волна перестала быть идеальной

Зачем это, а не просто случайные числа

Обычный генератор number выдаёт белый шум — значения скачут вокруг среднего без всякой памяти. Вот number с нормальным распределением, центрированным на 1000:

<sequence name="Шум"><gen type="number" distribution="normal" mean="1000" sd="120"/></sequence>
./run noise.tdc
День 1     841
День 2     1341
День 3     1047
День 4     1010
День 5     1086
День 6     1077
День 7     862
День 8     1072
День 9     1114
День 10    782
День 11    979
День 12    1014

Ни роста, ни спада, ни повтора — каждый день всё топчется около 1000. На реальные метрики это не похоже: у продаж есть тренд (бизнес растёт), есть недельный ритм (в выходные иначе, чем в будни), и уже поверх этого — случайные отклонения. Именно эти три слоя и добавляет timeseries.

Атрибуты

<gen type="timeseries" base="1000" trend="20" period="7" amplitude="150" noise="30"/>
АтрибутЧто задаёт
baseСтартовый уровень (по умолчанию 0)
trendНаклон: на сколько растёт значение каждый шаг
periodПериод сезонной волны (в строках), напр. 7 — неделя
amplitudeВысота сезонной волны
noiseСила случайного шума (стандартное отклонение)
decimalsЗнаков после запятой (по умолчанию 0 — целое)

Любой слой необязателен. Разделы ниже разбирают их по одному — что каждый делает и когда его брать.

base — стартовый уровень

base фиксирует значение самой первой строки (i = 0) и уровень, от которого отсчитывается всё остальное. Сам по себе — без trend, волны и noise — это просто прямая линия.

<gen type="timeseries" base="500"/>
./run base.tdc
500
500
500
500
500

Используйте, чтобы закрепить метрику на реалистичном уровне — магазин со средними 500 заказами в день, датчик, простаивающий на 20 градусах, — прежде чем добавлять движение.

trend — направление

trend — это наклон: каждая строка добавляет trend к предыдущей. Положительный — растёт, отрицательный — падает. С одними лишь base + trend получается мёртвая прямая.

<gen type="timeseries" base="1000" trend="20"/>
./run trend.tdc
1000
1020
1040
1060
1080

Используйте для роста или спада, который хочется видеть явно, — число подписчиков, прибавляющее 20 в день, батарея, теряющая фиксированный заряд за цикл.

period и amplitude — сезонная волна

Эти два работают в паре, и по отдельности ни один ничего не делает. period — сколько строк занимает один полный цикл (7 — недельный ритм, 365 — годовой); amplitude — насколько волна отклоняется вверх и вниз от линии тренда. Вместе они кладут повторяющуюся волну sin на то, что дают base + trend.

<gen type="timeseries" base="1000" trend="20" period="7" amplitude="150"/>
./run season.tdc
1000
1137
1186
1125
1015
954
1003

Внутри каждого окна в 7 строк значение поднимается к пику и опускается ко дну, затем повторяется. Поскольку тренд всё время приподнимает линию, каждый цикл сидит выше предыдущего — волна едет вверх по склону. Используйте для всего, что живёт по календарю: трафик «будни против выходных», спрос «лето против зимы».

noise — шероховатость реального мира

noise — это стандартное отклонение случайного колебания, добавляемого к каждой строке. Это разница между учебниковой кривой и настоящим замером.

<gen type="timeseries" base="1000" trend="20" period="7" amplitude="150" noise="30"/>
./run noise-layer.tdc
985
1087
1192
1107
936
966
1031

Сравните с чистой волной выше: форма та же, но каждая точка слегка дрожит (1000 → 985). Прибавьте шума для шумного датчика, убавьте — для гладкого агрегата. Дрожание воспроизводимо — см. Детали.

decimals — дробные значения

По умолчанию вывод округляется до целого. decimals оставляет столько знаков после запятой — для температур, цен или любой измеренной величины.

<gen type="timeseries" base="20" trend="0.5" noise="0.3" decimals="1"/>
./run decimals.tdc
20.0
20.4
21.2
21.4
22.1

Собираем ряд по слоям

Лучший способ прочувствовать генератор — включать слои по одному. Ниже три колонки <sequence> бегут по одним и тем же «дням»: тренд (только trend), +сезон (добавили period + amplitude) и +шум (добавили noise).

<sequence name="A"><gen type="timeseries" base="1000" trend="20"/></sequence>
<sequence name="B"><gen type="timeseries" base="1000" trend="20" period="7" amplitude="150"/></sequence>
<sequence name="C"><gen type="timeseries" base="1000" trend="20" period="7" amplitude="150" noise="30"/></sequence>
...
<data>День ${{День}} тренд=${{A}} +сезон=${{B}} +шум=${{C}}</data>
./run series.tdc
День   тренд   +сезон   +шум
01     1000     1000     985
02     1020     1137     1087
03     1040     1186     1192
04     1060     1125     1107
05     1080     1015     936
06     1100     954      966
07     1120     1003     1031
08     1140     1140     1087
09     1160     1277     1311
10     1180     1326     1347
11     1200     1265     1261
12     1220     1155     1126

Читаем по колонкам:

  • тренд — мёртвая прямая: +20 каждый день, 1000, 1020, 1040 …. Направление есть, но живого сигнала нет.
  • +сезон — на прямую легла недельная волна (period="7"). Внутри каждой недели значение поднимается к пику и опускается ко дну: пики приходятся на дни 3 и 10 (1186 → 1326), провал — на день 6 (954). Пики и провалы повторяются через 7 строк, и каждый следующий выше предыдущего ровно на trend · period = 20 · 7 = 140 — волна едет вверх по тренду.
  • +шум — тот же рисунок, но слегка дрожит (1000 → 985), как у настоящих замеров.

Каждый следующий столбец — это предыдущий плюс один атрибут: сначала направление, потом ритм, потом живая шероховатость. Так и собирается реалистичный ряд.

Детали

  • Детерминированно: тот же seed даёт тот же ряд. Шум тоже воспроизводим — он считается по номеру строки, а не «броском монеты» на лету.
  • Любой объём, оба движка: значение считается по номеру строки, поэтому память не растёт (см. Большие выгрузки). Миллиард точек — не проблема.
  • Ось времени — это номер строки. Она естественно сочетается с increment (счётчик дней) или колонкой date рядом, так что каждое значение несёт реальную дату.
В планах

Сейчас — один тренд + одна сезонная волна + шум. В планах: несколько сезонностей сразу (недельная и годовая) и AR-шум (коррелированный во времени).

См. также

  • Number — одиночные случайные значения со статистическими распределениями.
  • Pattern — когда форму нельзя описать через тренд + сезон.
  • Counters / Date — индекс дня или реальная дата, чтобы поставить рядом с рядом.