Трансформация lookup в Vega и Vega-Lite используется для
объединения данных из разных источников. По принципу работы она
напоминает SQL-операцию LEFT JOIN: для каждой записи
основной таблицы выполняется поиск совпадений во внешнем наборе
данных.
lookup особенно полезен в следующих ситуациях:
На практике lookup часто применяется для:
lookup берет:
После этого в каждую запись основной таблицы добавляются найденные значения.
Схема работы:
Основная таблица
[
{ id: 1, value: 120 },
{ id: 2, value: 95 }
]
Внешняя таблица
[
{ id: 1, name: "A" },
{ id: 2, name: "B" }
]
Результат lookup
[
{ id: 1, value: 120, name: "A" },
{ id: 2, value: 95, name: "B" }
]
lookup в Vega-Lite{
"transform": [
{
"lookup": "id",
"from": {
"data": {
"values": [
{ "id": 1, "name": "Alpha" },
{ "id": 2, "name": "Beta" }
]
},
"key": "id",
"fields": ["name"]
}
}
]
}
lookupПоле основной таблицы, по которому выполняется поиск.
"lookup": "id"
fromОписание внешнего набора данных.
"from": {
...
}
keyПоле внешней таблицы, с которым сравнивается lookup.
"key": "id"
fieldsСписок полей, которые нужно перенести.
"fields": ["name"]
Если поле не указано, может быть присоединен весь объект.
[
{ "country": "KZ", "sales": 120 },
{ "country": "RU", "sales": 200 },
{ "country": "US", "sales": 340 }
]
[
{ "code": "KZ", "name": "Kazakhstan" },
{ "code": "RU", "name": "Russia" },
{ "code": "US", "name": "United States" }
]
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"data": {
"values": [
{ "country": "KZ", "sales": 120 },
{ "country": "RU", "sales": 200 },
{ "country": "US", "sales": 340 }
]
},
"transform": [
{
"lookup": "country",
"from": {
"data": {
"values": [
{ "code": "KZ", "name": "Kazakhstan" },
{ "code": "RU", "name": "Russia" },
{ "code": "US", "name": "United States" }
]
},
"key": "code",
"fields": ["name"]
}
}
],
"mark": "bar",
"encoding": {
"x": {
"field": "name",
"type": "nominal"
},
"y": {
"field": "sales",
"type": "quantitative"
}
}
}
После выполнения lookup поле name
становится частью основной таблицы.
lookup позволяет присоединять сразу несколько
колонок.
"fields": [
"name",
"region",
"population"
]
{
"transform": [
{
"lookup": "country",
"from": {
"data": {
"url": "countries.json"
},
"key": "code",
"fields": [
"name",
"region",
"population"
]
}
}
]
}
После объединения каждая запись получит:
{
"country": "KZ",
"sales": 120,
"name": "Kazakhstan",
"region": "Asia",
"population": 19000000
}
{
"data": {
"url": "sales.json"
},
"transform": [
{
"lookup": "country",
"from": {
"data": {
"url": "countries.json"
},
"key": "code",
"fields": ["name"]
}
}
]
}
{
"from": {
"data": {
"url": "countries.csv"
},
"key": "code",
"fields": ["name"]
}
}
Одно из важнейших применений lookup — связывание
статистики с GeoJSON.
[
{ "id": 398, "value": 120 },
{ "id": 840, "value": 340 }
]
{
"url": "world.geojson",
"format": {
"type": "geojson"
}
}
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"data": {
"url": "world.geojson",
"format": {
"type": "geojson"
}
},
"transform": [
{
"lookup": "properties.id",
"from": {
"data": {
"values": [
{ "id": 398, "value": 120 },
{ "id": 840, "value": 340 }
]
},
"key": "id",
"fields": ["value"]
}
}
],
"mark": "geoshape",
"encoding": {
"color": {
"field": "value",
"type": "quantitative"
}
}
}
asПо умолчанию имена присоединяемых полей сохраняются. Иногда это неудобно, особенно при совпадении названий.
Для переименования используется as.
{
"lookup": "country",
"from": {
"data": {
"url": "countries.json"
},
"key": "code",
"fields": ["name"]
},
"as": ["country_name"]
}
{
"country": "KZ",
"country_name": "Kazakhstan"
}
Если fields отсутствует, Vega-Lite может вставить весь
найденный объект.
{
"lookup": "country",
"from": {
"data": {
"url": "countries.json"
},
"key": "code"
},
"as": "countryInfo"
}
{
"country": "KZ",
"countryInfo": {
"code": "KZ",
"name": "Kazakhstan",
"region": "Asia"
}
}
После вставки объекта можно обращаться к вложенным полям.
"encoding": {
"tooltip": [
{
"field": "countryInfo.name",
"type": "nominal"
},
{
"field": "countryInfo.region",
"type": "nominal"
}
]
}
Если соответствие не найдено:
{
"country": "XX",
"sales": 100
}
то новые поля получают значение null.
{
"country": "XX",
"sales": 100,
"name": null
}
nullПосле lookup часто применяется
calculate.
{
"transform": [
{
"lookup": "country",
"from": {
"data": {
"url": "countries.json"
},
"key": "code",
"fields": ["name"]
}
},
{
"calculate": "datum.name || 'Unknown'",
"as": "countryLabel"
}
]
}
lookupТрансформации можно комбинировать.
{
"transform": [
{
"lookup": "country",
"from": {
"data": {
"url": "countries.json"
},
"key": "code",
"fields": ["name"]
}
},
{
"lookup": "product",
"from": {
"data": {
"url": "products.json"
},
"key": "id",
"fields": ["category"]
}
}
]
}
Иногда одна lookup-операция зависит от предыдущей.
[
{
"lookup": "cityId",
"from": {
"data": {
"url": "cities.json"
},
"key": "id",
"fields": ["countryId"]
}
},
{
"lookup": "countryId",
"from": {
"data": {
"url": "countries.json"
},
"key": "id",
"fields": ["countryName"]
}
}
]
lookup в VegaВ Vega используется более низкоуровневый синтаксис.
{
"data": [
{
"name": "sales",
"values": [
{ "country": "KZ", "sales": 120 },
{ "country": "RU", "sales": 200 }
],
"transform": [
{
"type": "lookup",
"from": "countries",
"key": "code",
"fields": ["country"],
"values": ["name"],
"as": ["countryName"]
}
]
},
{
"name": "countries",
"values": [
{ "code": "KZ", "name": "Kazakhstan" },
{ "code": "RU", "name": "Russia" }
]
}
]
}
lookup в
VegafromИмя другого набора данных.
"from": "countries"
keyПоле таблицы-источника.
"key": "code"
fieldsПоля текущего набора.
"fields": ["country"]
valuesКакие значения переносить.
"values": ["name"]
asНазвания результирующих полей.
"as": ["countryName"]
Соответствие SQL:
SEL ECT
s.country,
s.sales,
c.name
FR OM sales s
LEFT JOIN countries c
ON s.country = c.code
Эквивалент Vega-Lite:
{
"lookup": "country",
"from": {
"data": {
"url": "countries.json"
},
"key": "code",
"fields": ["name"]
}
}
lookup может стать дорогой операцией при больших объемах
данных.
Особенно это заметно:
Плохо:
"fields": [
"a",
"b",
"c",
"d",
"e",
"f"
]
Лучше:
"fields": ["name"]
До объединения полезно сократить объем данных.
{
"aggregate": [
{
"op": "sum",
"field": "sales",
"as": "total"
}
],
"groupby": ["country"]
}
lookupИногда эффективнее подготовить данные заранее на сервере.
Частая проблема:
"country": 1
и
"code": "1"
Число и строка не совпадут.
keyОшибка:
"key": "country"
при отсутствии такого поля.
Ошибка:
"fields": ["title"]
если поле называется name.
Если во внешней таблице несколько одинаковых ключей:
[
{ "id": 1, "name": "A" },
{ "id": 1, "name": "B" }
]
результат может быть неоднозначным.
lookupПолезно временно выводить данные в tooltip.
"tooltip": [
{ "field": "country" },
{ "field": "name" }
]
filter{
"transform": [
{
"lookup": "country",
"from": {
"data": {
"url": "countries.json"
},
"key": "code",
"fields": ["region"]
}
},
{
"filter": "datum.region === 'Asia'"
}
]
}
calculate{
"transform": [
{
"lookup": "country",
"from": {
"data": {
"url": "countries.json"
},
"key": "code",
"fields": ["population"]
}
},
{
"calculate": "datum.sales / datum.population",
"as": "salesPerCapita"
}
]
}
Очень распространенный сценарий — загрузка вспомогательных данных только для подсказок.
{
"transform": [
{
"lookup": "productId",
"from": {
"data": {
"url": "products.json"
},
"key": "id",
"fields": [
"name",
"description"
]
}
}
],
"encoding": {
"tooltip": [
{ "field": "name" },
{ "field": "description" }
]
}
}
lookup часто используется вместе с selection и
параметрами.
{
"transform": [
{
"lookup": "country",
"from": {
"data": {
"url": "countries.json"
},
"key": "code",
"fields": ["region"]
}
}
],
"params": [
{
"name": "regionSelect",
"bind": {
"input": "select",
"options": [
"Asia",
"Europe"
]
}
}
],
"transform": [
{
"filter": "datum.region === regionSelect"
}
]
}
Наиболее частые сценарии:
| Сценарий | Что объединяется |
|---|---|
| Карты | GeoJSON + статистика |
| Dashboard | факты + справочники |
| BI | ID + текстовые подписи |
| Аналитика продаж | товары + категории |
| Финансы | тикеры + метаданные |
| Логирование | события + описания |
| IoT | датчики + география |
lookup
и joinaggregatelookupИспользуется для объединения разных наборов данных.
{
"lookup": "id"
}
joinaggregateРаботает внутри одного набора.
{
"joinaggregate": [
{
"op": "mean",
"field": "sales",
"as": "avgSales"
}
]
}
lookuplookup подходит, если:
lookupИногда объединение лучше выполнить заранее:
Это снижает нагрузку на браузер и упрощает спецификацию.
Хорошая практика:
sales.json
countries.json
products.json
regions.json
Это облегчает поддержку и переиспользование.
Лучше:
"id": 398
хуже:
"name": "Kazakhstan"
Текстовые поля менее надежны.
Ключи lookup-таблицы должны быть уникальными.
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"data": {
"url": "sales.json"
},
"transform": [
{
"lookup": "country",
"from": {
"data": {
"url": "countries.json"
},
"key": "code",
"fields": [
"name",
"region",
"population"
]
}
},
{
"calculate": "datum.sales / datum.population",
"as": "perCapita"
},
{
"filter": "datum.region !== null"
}
],
"mark": "circle",
"encoding": {
"x": {
"field": "population",
"type": "quantitative"
},
"y": {
"field": "sales",
"type": "quantitative"
},
"size": {
"field": "perCapita",
"type": "quantitative"
},
"color": {
"field": "region",
"type": "nominal"
},
"tooltip": [
{ "field": "name" },
{ "field": "sales" },
{ "field": "population" },
{ "field": "perCapita" }
]
}
}