В основе Vega и Vega-Lite лежит декларативная модель преобразования данных, в которой исходный датасет проходит через упорядоченную последовательность трансформаций. Каждая трансформация принимает на вход поток данных, модифицирует его и передаёт результат следующему шагу. Такая структура формирует цепочку трансформаций, где итоговый визуальный результат определяется не одной операцией, а композицией нескольких преобразований.
Ключевая особенность подхода заключается в том, что порядок операций строго фиксирован и влияет на результат. Даже при использовании одинакового набора трансформаций изменение их последовательности приводит к различным итоговым наборам данных.
Основные свойства цепочек трансформаций:
В Vega-Lite трансформации задаются внутри поля transform
и располагаются до этапа кодирования (encoding). Общая схема обработки
выглядит следующим образом:
data)transform)encoding)Трансформации образуют промежуточный слой между исходными данными и визуальными каналами, позволяя подготавливать данные без необходимости изменения источника.
Пример базовой структуры:
{
"data": {"url": "data.csv"},
"transform": [
{"filter": "datum.value > 10"},
{"calculate": "datum.value * 2", "as": "scaled"}
],
"mark": "line",
"encoding": {
"x": {"field": "date", "type": "temporal"},
"y": {"field": "scaled", "type": "quantitative"}
}
}
Цепочка трансформаций в Vega-Lite всегда выполняется слева направо в
массиве transform. Каждый элемент получает на вход
результат предыдущего шага.
Существенные последствия такой модели:
Пример влияния порядка:
"transform": [
{"calculate": "datum.a + datum.b", "as": "sum"},
{"filter": "datum.sum > 100"}
]
и обратный порядок:
"transform": [
{"filter": "datum.a + datum.b > 100"},
{"calculate": "datum.a + datum.b", "as": "sum"}
]
Оба варианта логически различны по производительности и иногда по результату, особенно при работе с производными полями.
Трансформация filter ограничивает поток данных по
логическому условию. Это один из наиболее ранних этапов цепочки, часто
используемый для уменьшения объёма данных перед более дорогими
операциями.
{"filter": "datum.price > 50"}
Фильтрация влияет на все последующие шаги, включая агрегации и построение шкал.
Трансформация calculate создаёт новые поля на основе
выражений. Она расширяет структуру данных без изменения исходных
полей.
{"calculate": "datum.sales * 1.2", "as": "adjusted_sales"}
Особенность: вычисления выполняются в контексте текущей строки, без доступа к глобальным агрегатам.
Трансформация aggregate выполняет группировку и свёртку
данных. Она является одной из ключевых операций для построения диаграмм
с обобщёнными значениями.
{
"aggregate": [
{"op": "mean", "field": "value", "as": "avg_value"}
],
"groupby": ["category"]
}
Агрегация радикально изменяет структуру данных: множество строк заменяется группами.
Влияние на цепочку:
Трансформация bin используется для дискретизации
непрерывных значений.
{
"bin": true,
"field": "age",
"as": "age_bin"
}
Биннинг часто применяется перед агрегацией, формируя категории из числовых значений.
timeUnit выполняет группировку временных значений по
календарным интервалам.
{
"timeUnit": "month",
"field": "date",
"as": "month"
}
Такие преобразования часто выступают промежуточным шагом перед агрегацией или построением временных рядов.
Трансформация window позволяет вычислять скользящие
агрегаты, ранжирование и накопительные суммы.
{
"window": [
{"op": "sum", "field": "value", "as": "running_total"}
],
"sort": [{"field": "date"}]
}
Оконные функции зависят от порядка сортировки, что делает их чувствительными к структуре цепочки.
Трансформация lookup используется для соединения двух
наборов данных по ключу.
{
"lookup": "key",
"from": {
"data": {"url": "other.csv"},
"key": "id",
"fields": ["category"]
}
}
Эта операция расширяет строки дополнительными полями из внешнего источника, изменяя контекст последующих вычислений.
fold преобразует широкий формат данных в длинный,
создавая пары ключ-значение.
{
"fold": ["a", "b", "c"],
"as": ["key", "value"]
}
flatten используется для разворачивания вложенных
структур, упрощая доступ к данным.
Цепочки трансформаций часто представляют собой комбинацию нескольких типов операций. На практике формируются типовые паттерны:
"transform": [
{"filter": "datum.value != null"},
{"calculate": "datum.value * 100", "as": "scaled"},
{
"aggregate": [
{"op": "mean", "field": "scaled", "as": "avg"}
],
"groupby": ["category"]
}
]
"transform": [
{"bin": true, "field": "x", "as": "x_bin"},
{
"aggregate": [
{"op": "count", "as": "count"}
],
"groupby": ["x_bin"]
},
{"calculate": "datum.count", "as": "y"}
]
Результат цепочки трансформаций напрямую определяет доступные поля для визуального кодирования. После выполнения всех шагов:
Пример зависимости:
"transform": [
{"calculate": "datum.sales - datum.expenses", "as": "profit"}
],
"encoding": {
"y": {"field": "profit", "type": "quantitative"}
}
transformВ Vega цепочка трансформаций становится не линейной последовательностью, а ориентированным графом, где данные могут расходиться и сходиться.
Производительность цепочек трансформаций зависит от нескольких факторов:
Типичная оптимизационная стратегия заключается в минимизации данных до применения дорогих операций.
calculate после aggregate,
когда требуется доступ к исходным строкамwindow без явной сортировкиbin после агрегации вместо до неёКаждая трансформация не модифицирует входной поток, а создаёт новый. Это поведение формирует предсказуемую модель вычислений, в которой:
Такая модель упрощает анализ цепочек и их отладку, особенно в сложных визуализациях с множественными преобразованиями.