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

Пропуски в данных — missing

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

missing — сквозной атрибут: он работает на любом <gen>, что бы тот ни генерировал.

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

Один и тот же генератор, 80 строк, с поочерёдно включёнными модификаторами.
  • Aчистый ряд
  • Bс anomaly= — отмеченные точки это подмешанные выбросы
  • Cс missing= — засечки внизу это строки, где значения нет

Как включить

Поставьте missing="p" на <gen>, где p — доля от 0 до 1, то есть доля значений, которые станут пустыми:

<gen type="number" value="30000..90000" missing="0.3"/>

Примерно 30% доходов выйдут пустыми, остальные — обычными числами.

Каждое значение «теряется» независимо с этой вероятностью. В статистике это называется MCAR — Missing Completely At Random, «пропуски совершенно случайны»: дыры выпадают безотносительно к другим полям и к самому значению.

По умолчанию пропуск — это пустая строка. Хотите свой маркер (NULL, NA, )? Задайте missing_as — см. раздел ниже.

До и после — что именно меняет пропуск

Проблема. Пока не увидите одно и то же поле «целым» и «дырявым» рядом, трудно понять, что вообще происходит: где значение потерялось, а где его и не было.

Инструмент. Возьмём один список городов и сделаем из него две колонки: Full — как есть, и Holey — тот же список, но с missing. order="sequential" берёт значения строго по порядку, поэтому обе колонки построчно совпадают — видно ровно то, что «выпало»:

<env count="8" seed="demo">
<sequence name="Full"> <gen type="text" value="Москва,Питер,Казань,Пермь,Сочи,Омск,Тула,Уфа" order="sequential"/></sequence>
<sequence name="Holey"><gen type="text" value="Москва,Питер,Казань,Пермь,Сочи,Омск,Тула,Уфа" order="sequential" missing="0.4"/></sequence>
</env>
...
<data>полное=${{Full}} | с пропусками=${{Holey}}</data>
./run cities.tdc (count=8, seed=demo)
полное=Москва  | с пропусками=Москва
полное=Питер   | с пропусками=Питер
полное=Казань  | с пропусками=
полное=Пермь   | с пропусками=
полное=Сочи    | с пропусками=Сочи
полное=Омск    | с пропусками=Омск
полное=Тула    | с пропусками=Тула
полное=Уфа     | с пропусками=Уфа

Слева всё на месте; справа — те же строки, но Казань и Пермь исчезли. Значение не заменилось на другое и не сдвинулось вверх — оно просто стало пустым. (На 8 строках при missing="0.4" выпало 2 — доля случайная, и на маленькой выборке разброс большой.)

Свой маркер вместо пустоты — missing_as

Проблема. Пустая строка в выгрузке невидима: в CSV это «ничего между двумя запятыми», глазами не отличить пропуск от короткого значения. В настоящих данных дыру обычно помечают явно — NULL, NA, .

Инструмент. missing_as="маркер" печатает ваш текст вместо пустоты. Строки, где случился пропуск, — те же самые (их выбирает seed и имя колонки, а не маркер); меняется только то, что стоит на месте дыры:

<sequence name="Holey">
<gen type="text" value="Москва,Питер,Казань,Пермь,Сочи,Омск,Тула,Уфа"
order="sequential" missing="0.4" missing_as="NULL"/>
</sequence>
./run cities.tdc (missing_as=NULL)
полное=Москва  | с пропусками=Москва
полное=Питер   | с пропусками=Питер
полное=Казань  | с пропусками=NULL
полное=Пермь   | с пропусками=NULL
полное=Сочи    | с пропусками=Сочи
полное=Омск    | с пропусками=Омск
полное=Тула    | с пропусками=Тула
полное=Уфа     | с пропусками=Уфа

Дыры на тех же строках 3 и 4, что и раньше, — но теперь их видно. Поставьте missing_as="—" или missing_as="NA" и получите свой маркер на тех же местах.

Сколько дырок — вариации доли

Проблема. Хочется понять, как поведёт себя система при «редких» и при «частых» пропусках. Одна доля этого не покажет.

Инструмент. Меняем только missing, всё остальное то же. X — значение на месте, [] — дыра. Три колонки, 20 строк:

<env count="20" seed="demo">
<sequence name="A"><gen type="text" value="X" order="sequential" missing="0.1"/></sequence>
<sequence name="B"><gen type="text" value="X" order="sequential" missing="0.3"/></sequence>
<sequence name="C"><gen type="text" value="X" order="sequential" missing="0.6"/></sequence>
</env>
...
<data>0.1:[${{A}}] 0.3:[${{B}}] 0.6:[${{C}}]</data>
./run rates.tdc (count=20, seed=demo)
0.1:[X]  0.3:[X]  0.6:[]
0.1:[X]  0.3:[]  0.6:[X]
0.1:[X]  0.3:[X]  0.6:[X]
0.1:[X]  0.3:[]  0.6:[X]
0.1:[X]  0.3:[]  0.6:[]
0.1:[X]  0.3:[]  0.6:[X]
0.1:[X]  0.3:[X]  0.6:[]
0.1:[]  0.3:[X]  0.6:[]
0.1:[X]  0.3:[X]  0.6:[]
0.1:[X]  0.3:[X]  0.6:[X]
0.1:[]  0.3:[X]  0.6:[X]
0.1:[X]  0.3:[X]  0.6:[X]
0.1:[X]  0.3:[X]  0.6:[]
0.1:[X]  0.3:[X]  0.6:[]
0.1:[X]  0.3:[]  0.6:[]
0.1:[X]  0.3:[X]  0.6:[]
0.1:[X]  0.3:[X]  0.6:[X]
0.1:[X]  0.3:[X]  0.6:[X]
0.1:[X]  0.3:[X]  0.6:[]
0.1:[X]  0.3:[X]  0.6:[X]

Считаем пустые [] по колонкам: 0.1 → 2 дыры из 20, 0.3 → 5, 0.6 → 10. Доля растёт — дыр становится больше. Точных 2 / 6 / 12 не будет: каждое значение теряется независимо, поэтому на 20 строках счёт «гуляет» вокруг ожидаемого.

Пропуски в нескольких полях

missing сочетается с чем угодно — с обычным диапазоном, с template, с шаблоном regex, со статистическим распределением. Соберём запись из трёх полей: имя (без пропусков), телефон (missing="0.3", маркер ) и доход (missing="0.25", маркер по умолчанию — пусто):

<env count="8" seed="rec" local="ru">
<sequence name="Name"> <gen type="template" value="person.male.firstName"/></sequence>
<sequence name="Phone"> <gen type="regex" value="\+7 \(9[0-9]{2}\) [0-9]{3}-[0-9]{2}-[0-9]{2}" missing="0.3" missing_as=""/></sequence>
<sequence name="Income"> <gen type="number" value="30000..90000" missing="0.25"/></sequence>
</env>
...
<data>${{Name}} | тел: ${{Phone}} | доход: ${{Income}}</data>
./run record.tdc (count=8, seed=rec)
Дмитрий   | тел: +7 (922) 889-53-97 | доход: 38370
Тимофей   | тел: +7 (906) 344-33-99 | доход: 70315
Станислав | тел: —                  | доход: 41008
Вадим     | тел: +7 (918) 997-61-79 | доход: 85063
Григорий  | тел: +7 (952) 879-35-78 | доход:
Евгений   | тел: +7 (914) 240-78-00 | доход: 48334
Демьян    | тел: —                  | доход: 46153
Ярослав   | тел: +7 (929) 079-00-13 | доход: 33986

У части строк телефон — , у пятой строки пустой доход (маркер по умолчанию — ничего). Пропуски в двух полях независимы: дыра в одной колонке ничего не говорит о другой.

Детали

  • Детерминированно. Тот же seed даёт те же пропуски — см. Детерминизм и пропорции. Один и тот же «дырявый» датасет воспроизводится байт в байт, запуск за запуском.
  • Оба движка, любой объём. Пропуски считаются по номеру строки, поэтому память не растёт с размером датасета — см. Большие выгрузки и стриминг.
  • missing="0" — совсем без пропусков, ровно как если бы атрибута не было.
Что дальше

Сейчас реализован только MCAR: пропуски равновероятны и ни от чего не зависят. В планах — MAR / MNAR, где вероятность дыры зависит от другого поля или от самого значения.

Смотрите также