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

stat — одно число на весь прогон

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

Это третья и последняя ось «значения, которое не разыгрывается, а выводится». accumulate= считает итог по списку внутри одной записи. running считает итог по колонке на ходу, поэтому сороковая строка знает про строки с первой по тридцать девятую. Статистика знает про все.

<tdc>
<env count="8" seed="basket" local="en">
<sequence name="Price"><gen type="number" value="10..200" decimals="2"/></sequence>
<sequence name="Average"><gen type="stat" of="Price" op="mean" decimals="2"/></sequence>
<sequence name="Verdict">
<gen if="Price > Average" type="text" value="above average"/>
<gen type="text" value="below average"/>
</sequence>
</env>
<block>
<line><data>${{Price}} ${{Average}} ${{Verdict}}</data></line>
</block>
</tdc>
./run basket.tdc
156.83   122.52   above average
176.51   122.52   above average
135.17   122.52   above average
157.94   122.52   above average
50.71   122.52   below average
92.28   122.52   below average
50.37   122.52   below average
160.35   122.52   above average

«Выше ли эта строка среднего» иначе не спросить. Среднее неизвестно, пока не появилась последняя строка, а условный <gen> вычисляется в момент сборки строки — поэтому статистика должна быть отдельной колонкой, посчитанной заранее.

Вывод — для иллюстрации

Значения получены с фиксированным seed, то есть воспроизводимы, но точные строки могут отличаться между версиями ядра. Смотрите на них как на пример формы, а не как на гарантию.

Коротко

АтрибутОбязателенЧто делает
ofдаКолонка, по которой считать. Должна быть объявлена выше этой
opдаsum, mean, median, min, max, count или stddev
decimalsнетОкруглить ответ до N знаков, от 0 до 10

Статистика ничего не разыгрывает. Она читает уже существующую колонку, не тратит ни капли случайности — поэтому её добавление не сдвигает ни одну другую колонку.

Семь статистик

<env count="6" seed="lab" local="en">
<sequence name="Reading"><gen type="number" value="1..100"/></sequence>
<sequence name="Total"><gen type="stat" of="Reading" op="sum"/></sequence>
<sequence name="Smallest"><gen type="stat" of="Reading" op="min"/></sequence>
<sequence name="Largest"><gen type="stat" of="Reading" op="max"/></sequence>
<sequence name="Middle"><gen type="stat" of="Reading" op="median"/></sequence>
<sequence name="Spread"><gen type="stat" of="Reading" op="stddev" decimals="3"/></sequence>
<sequence name="Rows"><gen type="stat" of="Reading" op="count"/></sequence>
</env>
./run lab.tdc
60  sum=297 min=7 max=81 median=63 sd=27.415 n=6
81  sum=297 min=7 max=81 median=63 sd=27.415 n=6
66  sum=297 min=7 max=81 median=63 sd=27.415 n=6
7  sum=297 min=7 max=81 median=63 sd=27.415 n=6
17  sum=297 min=7 max=81 median=63 sd=27.415 n=6
66  sum=297 min=7 max=81 median=63 sd=27.415 n=6

Две из них стоит проговорить вслух — потому что альтернативы встречаются не реже и дают другие числа:

  • stddev — это стандартное отклонение по ГЕНЕРАЛЬНОЙ совокупности, деление на n, а не на n−1. Сгенерированная колонка — это всё описываемое целиком, а не выборка из чего-то большего, поэтому честный делитель — n. Тот же выбор делает функция stddev() в выражениях.
  • median при чётном количестве — это среднее двух средних значений, поэтому у колонки 2,4,4,4,5,5,7,9 медиана 4.5.

count — это сколько строк несли значение: строки, опустошённые фильтром parent, не считаются и не участвуют ни в одной из остальных статистик. То же правило соблюдают accumulate= и running, поэтому отфильтрованная колонка означает во всех трёх одно и то же, а не три разных вещи.

Точные копейки и decimals=

sum, min и max — это последнее значение соответствующей нарастающей колонки. Это не срезанный угол, а то, что не даёт двум возможностям разойтись: арифметика идёт по целым числам, отмасштабированным по самой длинной дробной части в колонке, и никогда по плавающей точке, поэтому 19.99 + 0.01 + 0.01 — это 20.01 во всех пяти реализациях, а не 20.009999999999998 в одной из них. min и max возвращают уже существующий элемент, поэтому значение, пришедшее как 007, так и остаётся 007.

mean, median и stddevотношения, и точными быть не могут. Без decimals= они печатаются целиком, со всеми знаками: среднее, которое тихо потеряло цифры, хуже, чем некрасивое. С decimals= они округляются — и половина уходит от нуля, по тому же правилу, что и round() везде в TDC, поэтому 117.045 до двух знаков — это 117.05.

Порядок объявления

of= называет колонку, и она должна быть объявлена выше статистики (TDC240) — то же правило, что у parent и у running, и по той же причине: статистика строится из уже существующей колонки.

Статистика, которая не говорит, что считать или какую именно статистику брать, — это TDC262.

./run lab.tdc
error[TDC262]: op="men" is not one of sum, mean, median, min, max, count, stddev
--> lab.tdc:4:63
|
4 |     <sequence name="Spread"><gen type="stat" of="Reading" op="men"/></sequence>
|                                                               ^^^
|
help: did you mean "mean"?
note: One of: sum, mean, median, min, max, count, stddev.

На каком движке это считается

Потоковый движок отказывает статистике по имени, и роутер уводит конфиг на движок памяти:

./run basket.tdc --engine 2
tdcv2: a statistic ("Average") is computed over every row of the run, including the ones after this one, so it cannot be computed one row at a time; the in-memory engine handles it (run without a forced streaming engine)

Обычно этого сообщения никто не видит — движок выбирает роутер, и отказ всплывает только тогда, когда конфиг сам назвал потоковый движок и тем самым попросил себе объяснение.

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

Всё остальное по-прежнему течёт потоком. Ограничение действует на конфиг, а не на проект: прогон без статистики его не касается.

См. также

  • running — та же колонка, но накопленная на ходу, а не подытоженная в конце
  • accumulate= на repeat-списке — та же идея внутри одной записи, бесплатно на любом движке
  • Выраженияsum, mean, median и stddev есть и как функции, но по списку внутри строки, а не по колонке