Трансформация window предназначена для вычислений,
зависящих от соседних строк, порядка данных или накопительного
контекста. В отличие от обычных агрегатов (aggregate),
работающих над всей группой сразу, window выполняет
вычисления построчно, сохраняя каждую запись в результирующем
наборе.
Через window реализуются:
Структурно window напоминает SQL-конструкции
OVER (PARTITION BY ... ORDER BY ...).
window{
"transform": [
{
"window": [
{
"op": "sum",
"field": "sales",
"as": "running_total"
}
]
}
]
}
Основные элементы:
| Поле | Назначение |
|---|---|
window |
список вычислений |
op |
операция |
field |
поле для обработки |
as |
имя результирующего поля |
sort |
порядок строк |
groupby |
разбиение на группы |
frame |
границы окна |
const spec = {
data: {
values: [
{day: 1, sales: 10},
{day: 2, sales: 15},
{day: 3, sales: 7},
{day: 4, sales: 20}
]
},
transform: [
{
window: [
{
op: "sum",
field: "sales",
as: "running_total"
}
],
sort: [
{field: "day"}
],
frame: [null, 0]
}
],
mark: "line",
encoding: {
x: {field: "day", type: "quantitative"},
y: {field: "running_total", type: "quantitative"}
}
};
frameframe: [null, 0]
Окно задаётся двумя границами:
| Значение | Смысл |
|---|---|
null |
начало или конец всей группы |
0 |
текущая строка |
-1 |
предыдущая строка |
1 |
следующая строка |
[null, 0]
означает:
от первой строки до текущей.
Именно поэтому получается накопительный итог.
const spec = {
data: {
values: [
{day: 1, value: 5},
{day: 2, value: 8},
{day: 3, value: 12},
{day: 4, value: 7},
{day: 5, value: 10}
]
},
transform: [
{
window: [
{
op: "mean",
field: "value",
as: "moving_avg"
}
],
sort: [
{field: "day"}
],
frame: [-1, 1]
}
],
layer: [
{
mark: "line",
encoding: {
x: {field: "day", type: "quantitative"},
y: {field: "value", type: "quantitative"}
}
},
{
mark: {
type: "line",
color: "red"
},
encoding: {
x: {field: "day", type: "quantitative"},
y: {field: "moving_avg", type: "quantitative"}
}
}
]
};
[-1, 1]frame: [-1, 1]
означает:
| Позиция | Значение |
|---|---|
-1 |
предыдущая строка |
0 |
текущая |
1 |
следующая |
Таким образом окно состоит из трёх элементов.
frame: [-2, 2]
Включает:
Используется для сглаживания временных рядов.
frame: [-4, 0]
Включает:
Это типичное «ретроспективное» окно.
rankФункция rank вычисляет позицию строки в отсортированном
наборе.
{
transform: [
{
window: [
{
op: "rank",
as: "rank"
}
],
sort: [
{
field: "sales",
order: "descending"
}
]
}
]
}
rankПри одинаковых значениях появляются пропуски.
Пример:
| sales | rank |
|---|---|
| 100 | 1 |
| 90 | 2 |
| 90 | 2 |
| 80 | 4 |
Ранг 3 отсутствует.
dense_rank{
transform: [
{
window: [
{
op: "dense_rank",
as: "dense_rank"
}
],
sort: [
{
field: "sales",
order: "descending"
}
]
}
]
}
rank| sales | rank | dense_rank |
|---|---|---|
| 100 | 1 | 1 |
| 90 | 2 | 2 |
| 90 | 2 | 2 |
| 80 | 4 | 3 |
dense_rank не создаёт пропусков.
row_number{
transform: [
{
window: [
{
op: "row_number",
as: "row"
}
]
}
]
}
Результат:
| row |
|---|
| 1 |
| 2 |
| 3 |
| 4 |
percent_rank{
transform: [
{
window: [
{
op: "percent_rank",
as: "percent"
}
],
sort: [
{
field: "score"
}
]
}
]
}
Результат находится в диапазоне:
0 → 1
Используется для:
{
transform: [
{
window: [
{
op: "mean",
field: "sales",
as: "running_avg"
}
],
sort: [
{field: "date"}
],
frame: [null, 0]
}
]
}
{
transform: [
{
window: [
{
op: "max",
field: "temperature",
as: "max_so_far"
}
],
sort: [
{field: "time"}
],
frame: [null, 0]
}
]
}
lagФункция lag возвращает значение из предыдущей
строки.
{
transform: [
{
window: [
{
op: "lag",
field: "price",
as: "prev_price"
}
],
sort: [
{field: "date"}
]
}
]
}
{
transform: [
{
window: [
{
op: "lag",
field: "price",
as: "prev_price"
}
],
sort: [
{field: "date"}
]
},
{
calculate: "datum.price - datum.prev_price",
as: "delta"
}
]
}
lead{
transform: [
{
window: [
{
op: "lead",
field: "price",
as: "next_price"
}
],
sort: [
{field: "date"}
]
}
]
}
groupby{
transform: [
{
window: [
{
op: "sum",
field: "sales",
as: "category_total"
}
],
groupby: ["category"],
sort: [
{field: "date"}
],
frame: [null, 0]
}
]
}
groupbyБез groupby окно строится по всему набору данных.
С groupby:
Аналог SQL:
PARTITION BY category
sortsort: [
{
field: "date",
order: "ascending"
}
]
Без сортировки:
lag и lead работают неправильно.{
transform: [
{
window: [
{
op: "sum",
field: "sales",
as: "running_sum"
},
{
op: "mean",
field: "sales",
as: "running_avg"
},
{
op: "rank",
as: "sales_rank"
}
],
sort: [
{
field: "sales",
order: "descending"
}
],
frame: [null, 0]
}
]
}
{
transform: [
{
window: [
{
op: "median",
field: "value",
as: "rolling_median"
}
],
sort: [
{field: "date"}
],
frame: [-3, 0]
}
]
}
{
transform: [
{
window: [
{
op: "stdev",
field: "value",
as: "rolling_std"
}
],
sort: [
{field: "date"}
],
frame: [-5, 0]
}
]
}
window{
transform: [
{
window: [
{
op: "sum",
field: "sales",
as: "total_sales"
}
],
frame: [null, null]
},
{
calculate: "datum.sales / datum.total_sales",
as: "percent"
}
]
}
frame: [null, null]
Означает:
использовать все строки набора.
{
transform: [
{
window: [
{
op: "sum",
field: "sales",
as: "running_sum"
},
{
op: "sum",
field: "sales",
as: "total_sum"
}
],
sort: [
{field: "date"}
],
frame: [null, 0]
},
{
joinaggregate: [
{
op: "sum",
field: "sales",
as: "grand_total"
}
]
},
{
calculate: "datum.running_sum / datum.grand_total",
as: "running_percent"
}
]
}
window и joinaggregatejoinaggregate{
joinaggregate: [
{
op: "mean",
field: "value",
as: "avg"
}
]
}
Вычисляет агрегат по всей группе и копирует его в каждую строку.
window{
window: [
{
op: "mean",
field: "value",
as: "rolling_avg"
}
],
frame: [-2, 0]
}
Вычисляет агрегат относительно текущей позиции.
const spec = {
data: {
url: "data/stocks.csv"
},
transform: [
{
window: [
{
op: "mean",
field: "price",
as: "avg_price"
},
{
op: "stdev",
field: "price",
as: "std_price"
}
],
sort: [
{field: "date"}
],
frame: [-10, 0]
},
{
calculate: "datum.price - datum.avg_price",
as: "deviation"
}
],
layer: [
{
mark: "line",
encoding: {
x: {field: "date", type: "temporal"},
y: {field: "price", type: "quantitative"}
}
},
{
mark: {
type: "line",
color: "red"
},
encoding: {
x: {field: "date", type: "temporal"},
y: {field: "avg_price", type: "quantitative"}
}
}
]
};
window| Операция | Назначение |
|---|---|
sum |
сумма |
mean |
среднее |
median |
медиана |
min |
минимум |
max |
максимум |
count |
количество |
variance |
дисперсия |
stdev |
стандартное отклонение |
| Операция | Назначение |
|---|---|
rank |
обычный ранг |
dense_rank |
плотный ранг |
row_number |
номер строки |
percent_rank |
процентный ранг |
cume_dist |
накопительное распределение |
| Операция | Назначение |
|---|---|
lag |
предыдущая строка |
lead |
следующая строка |
first_value |
первое значение окна |
last_value |
последнее значение окна |
nth_value |
N-е значение |
window — одна из наиболее тяжёлых трансформаций
Vega-Lite.
Причины:
sortwindow: [...]
без:
sort: [...]
часто приводит к неправильным результатам.
frameframe: [0, 0]
Такое окно содержит только текущую строку.
Скользящего вычисления не будет.
Ошибка:
frame: ["-1", "1"]
Правильно:
frame: [-1, 1]
windowfilter{
filter: "datum.rank <= 10"
}
Позволяет реализовать Top-N после ранжирования.
calculate{
calculate: "datum.value / datum.avg",
as: "normalized"
}
Используется после оконных вычислений.
foldЧасто применяется перед window для подготовки широких
таблиц к временной аналитике.
const spec = {
data: {
values: [
{category: "A", product: "P1", sales: 100},
{category: "A", product: "P2", sales: 80},
{category: "A", product: "P3", sales: 60},
{category: "B", product: "P4", sales: 120},
{category: "B", product: "P5", sales: 110},
{category: "B", product: "P6", sales: 50}
]
},
transform: [
{
window: [
{
op: "rank",
as: "rank"
}
],
groupby: ["category"],
sort: [
{
field: "sales",
order: "descending"
}
]
},
{
filter: "datum.rank <= 2"
}
],
mark: "bar",
encoding: {
x: {field: "product"},
y: {field: "sales"},
color: {field: "category"}
}
};
window работает поверх уже подготовленного набора
данных.
Порядок выполнения:
filtercalculateaggregatewindowencoding)Из-за этого результаты оконных вычислений напрямую зависят от всех предыдущих трансформаций.