В Vega и Vega-Lite данные нередко содержат вложенные массивы:
{
"category": "A",
"values": [10, 20, 30]
}
Подобная структура удобна для хранения, но плохо подходит для визуализации. Большинство графиков ожидают плоский набор записей, где каждая строка представляет отдельное наблюдение.
Трансформация flatten предназначена для разворачивания
массивов в отдельные строки данных.
После применения flatten предыдущий объект превращается
в три строки:
{ "category": "A", "values": 10 }
{ "category": "A", "values": 20 }
{ "category": "A", "values": 30 }
Это одна из ключевых операций при работе:
flatten берет поле-массив и создает новую строку для
каждого элемента массива.
Исходная запись:
{
"name": "Server 1",
"cpu": [20, 35, 40]
}
Результат:
{ "name": "Server 1", "cpu": 20 }
{ "name": "Server 1", "cpu": 35 }
{ "name": "Server 1", "cpu": 40 }
Все остальные поля копируются в каждую новую строку.
flatten в Vega-Lite{
"transform": [
{
"flatten": ["values"]
}
]
}
[
{
"group": "A",
"values": [5, 10, 15]
},
{
"group": "B",
"values": [7, 9]
}
]
{
"data": {
"values": [
{
"group": "A",
"values": [5, 10, 15]
},
{
"group": "B",
"values": [7, 9]
}
]
},
"transform": [
{
"flatten": ["values"]
}
],
"mark": "bar",
"encoding": {
"x": {
"field": "values",
"type": "quantitative"
},
"y": {
"field": "group",
"type": "nominal"
}
}
}
До flatten:
| group | values |
|---|---|
| A | [5,10,15] |
| B | [7,9] |
После flatten:
| group | values |
|---|---|
| A | 5 |
| A | 10 |
| A | 15 |
| B | 7 |
| B | 9 |
flatten умеет одновременно разворачивать несколько
полей.
{
"flatten": ["x", "y"]
}
[
{
"x": [1, 2, 3],
"y": [10, 20, 30]
}
]
Результат:
{ "x": 1, "y": 10 }
{ "x": 2, "y": 20 }
{ "x": 3, "y": 30 }
Массивы разворачиваются по индексам.
То есть:
x[0] ↔ y[0]
x[1] ↔ y[1]
x[2] ↔ y[2]
{
"x": [1, 2],
"y": [10, 20, 30]
}
Результат:
{ "x": 1, "y": 10 }
{ "x": 2, "y": 20 }
{ "x": null, "y": 30 }
Недостающие значения заполняются null.
asПо умолчанию развёрнутые данные записываются в исходное поле.
Иногда это неудобно:
Для переименования используется as.
{
"flatten": ["values"],
"as": ["value"]
}
Исходно:
{
"values": [1, 2, 3]
}
После:
{ "value": 1 }
{ "value": 2 }
{ "value": 3 }
Очень частая задача — сохранить индекс элемента массива.
Например:
[10, 20, 30]
Нужно получить:
index: 0 → value: 10
index: 1 → value: 20
index: 2 → value: 30
Для этого применяется свойство index.
{
"transform": [
{
"flatten": ["values"],
"as": ["value"],
"index": "position"
}
]
}
{ "value": 10, "position": 0 }
{ "value": 20, "position": 1 }
{ "value": 30, "position": 2 }
flatten особенно полезен при работе с временными рядами,
которые приходят массивами.
[
{
"sensor": "A",
"temps": [21, 22, 23, 24]
}
]
{
"data": {
"values": [
{
"sensor": "A",
"temps": [21, 22, 23, 24]
}
]
},
"transform": [
{
"flatten": ["temps"],
"as": ["temperature"],
"index": "time"
}
],
"mark": "line",
"encoding": {
"x": {
"field": "time",
"type": "quantitative"
},
"y": {
"field": "temperature",
"type": "quantitative"
}
}
}
Многие REST API возвращают массивы внутри объектов.
{
"department": "Sales",
"employees": [
{ "name": "Alice", "salary": 3000 },
{ "name": "Bob", "salary": 3500 }
]
}
Чтобы строить графики по сотрудникам, массив необходимо развернуть.
{
"transform": [
{
"flatten": ["employees"]
}
]
}
После этого каждое значение employees становится
отдельной строкой.
flattenflatten работает только с массивами.
Если поле содержит:
null;то преобразование не создаст ожидаемый результат.
{
"value": 10
}
{
"flatten": ["value"]
}
Такой код не имеет смысла, потому что value не является
массивом.
{
"matrix": [
[1, 2],
[3, 4]
]
}
Один flatten даст:
{ "matrix": [1,2] }
{ "matrix": [3,4] }
Для полного разворачивания потребуется повторная трансформация.
flatten{
"transform": [
{
"flatten": ["matrix"]
},
{
"flatten": ["matrix"]
}
]
}
{ "matrix": 1 }
{ "matrix": 2 }
{ "matrix": 3 }
{ "matrix": 4 }
flatten и
calculateОчень часто после разворачивания требуется вычисление новых полей.
{
"transform": [
{
"flatten": ["sales"],
"as": ["amount"]
},
{
"calculate": "datum.amount * 1.2",
"as": "amountWithTax"
}
]
}
flatten и агрегацииПосле преобразования данные становятся обычными строками, поэтому можно использовать:
aggregatejoinaggregatewindowstackfilter{
"transform": [
{
"flatten": ["values"]
},
{
"aggregate": [
{
"op": "mean",
"field": "values",
"as": "avg"
}
]
}
]
}
flatten и foldЭти трансформации часто путают.
flattenРазворачивает массивы.
{
"values": [1,2,3]
}
{ "values": 1 }
{ "values": 2 }
{ "values": 3 }
foldПреобразует несколько колонок в пары ключ-значение.
{
"a": 10,
"b": 20
}
{ "key": "a", "value": 10 }
{ "key": "b", "value": 20 }
flatten в VegaВ Vega трансформация используется аналогичным образом.
{
"type": "flatten",
"fields": ["values"]
}
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"width": 400,
"height": 200,
"data": [
{
"name": "table",
"values": [
{
"category": "A",
"values": [1, 2, 3]
}
],
"transform": [
{
"type": "flatten",
"fields": ["values"]
}
]
}
],
"scales": [
{
"name": "x",
"type": "linear",
"domain": {"data": "table", "field": "values"},
"range": "width"
},
{
"name": "y",
"type": "band",
"domain": {"data": "table", "field": "category"},
"range": "height"
}
],
"marks": [
{
"type": "rect",
"from": {"data": "table"},
"encode": {
"enter": {
"x": {"value": 0},
"x2": {"scale": "x", "field": "values"},
"y": {"scale": "y", "field": "category"},
"height": {"scale": "y", "band": 1}
}
}
}
]
}
{
"flatten": ["values"]
}
{
"type": "flatten",
"fields": ["values"]
}
{
"user": "Alice",
"metrics": {
"scores": [10, 20, 30]
}
}
{
"flatten": ["metrics.scores"]
}
flatten способен существенно увеличивать объем
данных.
Исходно:
1000 объектов
В каждом:
массив из 500 элементов
После flatten:
500 000 строк
Возможны:
flattenЛучше:
{
"transform": [
{
"filter": "datum.active"
},
{
"flatten": ["values"]
}
]
}
Хуже:
{
"transform": [
{
"flatten": ["values"]
},
{
"filter": "datum.active"
}
]
}
Во втором варианте сначала создается огромное количество строк.
Массивы по десяткам тысяч элементов лучше предварительно обрабатывать на сервере.
asЭто улучшает читаемость:
{
"flatten": ["temperatures"],
"as": ["temperature"]
}
вместо:
{
"flatten": ["temperatures"]
}
{
"device": "sensor-1",
"readings": [10,11,13,15]
}
{
"symbol": "AAPL",
"prices": [170,172,175]
}
{
"session": "abc",
"events": ["click","scroll","submit"]
}
{
"polygon": [
[10,20],
[30,40]
]
}
{
"simulation": [0.1,0.4,0.8]
}
Неверно:
{
"flatten": ["user"]
}
если:
"user": {
"name": "Alice"
}
{
"x": [1,2,3],
"y": [10]
}
Результат содержит null.
Без as исходный массив заменяется скалярным
значением.
Многократный flatten может создать миллионы строк.
flatten + filter{
"transform": [
{
"flatten": ["values"]
},
{
"filter": "datum.values > 10"
}
]
}
flatten + window{
"transform": [
{
"flatten": ["values"]
},
{
"window": [
{
"op": "row_number",
"as": "rank"
}
]
}
]
}
flatten + stack{
"transform": [
{
"flatten": ["values"]
},
{
"stack": "values"
}
]
}
flatten
особенно полезенНаиболее важные случаи: