Трансформация extent

extent-трансформация в Vega и Vega-Lite представляет собой вычислительный механизм получения диапазона значений (минимума и максимума) для числовых данных или выражений, часто с учётом группировки. Эта операция лежит на стыке агрегации и подготовки шкал, позволяя динамически определять границы визуализации без явного предварительного вычисления статистики на стороне приложения.

Extent-трансформация вычисляет пару значений:

  • минимальное значение (min)
  • максимальное значение (max)

Результат формируется как массив фиксированной длины из двух элементов:

[min, max]

В отличие от классической агрегации, которая возвращает одно значение на группу, extent возвращает диапазон, что делает её особенно полезной для:

  • автоматического задания доменов шкал
  • вычисления диапазонов осей
  • нормализации данных
  • построения сравнительных визуализаций

Внутри Vega-Lite extent часто используется как промежуточный шаг, который затем связывается с масштабами (scale domains).

Базовая структура extent-трансформации

В Vega-Lite трансформация задаётся внутри массива transform. Простейшая форма выглядит следующим образом:

{
  "transform": [
    {
      "extent": "value",
      "signal": "value_extent"
    }
  ]
}

Где:

  • extent — поле данных или выражение
  • signal (или в Vega — output name) — имя результата

В Vega более низкого уровня конструкция выглядит аналогично, но с явным указанием output-поля:

{
  "type": "extent",
  "field": "value",
  "signal": "value_extent"
}

Вычисление диапазона по полю

Наиболее частый сценарий — вычисление min/max по числовому полю.

{
  "data": {"name": "table"},
  "transform": [
    {
      "type": "extent",
      "field": "price",
      "signal": "price_extent"
    }
  ]
}

После выполнения трансформации формируется сигнал:

price_extent = [min(price), max(price)]

Этот сигнал затем может быть использован в шкалах:

{
  "scales": [
    {
      "name": "y",
      "type": "linear",
      "domain": {"signal": "price_extent"},
      "range": "height"
    }
  ]
}

Extent и группировка данных

Одно из ключевых применений extent — вычисление диапазонов внутри групп. В Vega это достигается через groupby.

{
  "type": "extent",
  "field": "value",
  "groupby": ["category"],
  "signal": "category_extents"
}

Результатом становится структура вида:

category_extents = [
  {category: "A", extent: [minA, maxA]},
  {category: "B", extent: [minB, maxB]}
]

Такой формат используется для:

  • многосерийных графиков
  • независимых шкал для групп
  • сравнения диапазонов категорий

Extent по выражению (expression-based extent)

В Vega extent может вычисляться не только по полю, но и по выражению. Это важно для динамических преобразований данных:

{
  "type": "extent",
  "expr": "datum.a + datum.b",
  "signal": "sum_extent"
}

В Vega-Lite аналог достигается через calculate перед применением extent:

{
  "transform": [
    {
      "calculate": "datum.a + datum.b",
      "as": "sum"
    },
    {
      "extent": "sum",
      "signal": "sum_extent"
    }
  ]
}

Такая композиция позволяет строить производные метрики без изменения исходного датасета.

Связь extent с масштабами (scales)

Наиболее значимая роль extent — автоматическое формирование domain для шкал. Вместо ручного задания диапазона:

"domain": [0, 100]

используется вычисляемый сигнал:

"domain": {"signal": "value_extent"}

Это обеспечивает:

  • адаптивность визуализации
  • устойчивость к изменениям данных
  • отсутствие ручной синхронизации шкал

Extent в сравнении с агрегатами min и max

Extent можно рассматривать как комбинированный агрегат, однако между ними есть принципиальная разница:

Операция Результат
min одно значение
max одно значение
extent пара [min, max]

При этом extent часто эффективнее, чем два отдельных агрегата, поскольку:

  • выполняет один проход по данным
  • возвращает согласованный диапазон
  • уменьшает риск рассинхронизации вычислений

Extent в многоуровневых визуализациях

В сложных спецификациях extent применяется на разных уровнях:

  1. глобальный extent — для всей выборки
  2. групповой extent — для категорий
  3. локальный extent — для фильтрованных подмножеств

Пример комбинированного использования:

{
  "transform": [
    {
      "filter": "datum.year > 2000"
    },
    {
      "extent": "value",
      "groupby": ["region"],
      "signal": "region_extents"
    }
  ]
}

Такой подход позволяет строить независимые шкалы для каждой группы региона.

Использование extent в интерактивных сигналах

Extent часто применяется для управления интерактивностью, особенно при:

  • зумах
  • brush-selection
  • динамических фильтрах

Пример:

{
  "signals": [
    {
      "name": "brush",
      "value": null
    }
  ],
  "transform": [
    {
      "extent": "value",
      "signal": "full_extent"
    }
  ]
}

Далее extent может ограничиваться выбранным диапазоном:

filtered_extent = intersection(full_extent, brush)

Оптимизация вычислений extent

При работе с большими наборами данных extent становится критической операцией. Оптимизации включают:

  • предварительную сортировку (редко требуется, но полезно для потоковых данных)
  • использование индексов в источнике данных
  • агрегацию на стороне backend
  • кэширование extent при неизменных данных

В Vega runtime extent выполняется как потоковая редукция:

init: [∞, -∞]
update:
  min = Math.min(min, value)
  max = Math.max(max, value)

Extent и отсутствующие значения

При вычислении диапазона учитываются особенности данных:

  • null значения игнорируются
  • NaN исключаются
  • строки приводятся к числам только при явном преобразовании

Это важно при смешанных датасетах, где требуется предварительная очистка через transform:

{
  "calculate": "toNumber(datum.value)",
  "as": "value_num"
}

Композиция extent с другими трансформациями

Extent редко используется изолированно. Чаще он комбинируется с:

  • filter — для ограничения диапазона входных данных
  • calculate — для производных метрик
  • bin — для гистограмм
  • aggregate — для предварительного свёртывания

Пример цепочки:

{
  "transform": [
    {"filter": "datum.active == true"},
    {"calculate": "datum.x * datum.y", "as": "metric"},
    {"extent": "metric", "signal": "metric_extent"}
  ]
}

Такая последовательность формирует чистый вычислительный pipeline.

Extent в контексте декларативной графики

Extent является примером декларативного описания статистической операции. Вместо явного программирования цикла вычисления min/max, спецификация описывает:

  • что нужно вычислить (range)
  • над чем (field/expression)
  • с какой структурой (groupby)

Движок выполнения самостоятельно определяет способ вычисления, сохраняя согласованность между данными и визуальными компонентами.