Трансформация joinaggregate в Vega-Lite используется для
вычисления агрегированных значений и последующего присоединения
результата к каждой строке исходного набора данных. В отличие от
aggregate, где данные схлопываются до групп,
joinaggregate сохраняет оригинальные записи.
Это особенно важно при построении:
Синтаксически joinaggregate напоминает
SQL-конструкцию:
AVG(value) OVER ()
или
SUM(value) OVER (PARTITION BY category)
{
joinaggregate: [
{
op: "average",
field: "price",
as: "avg_price"
}
]
}
После выполнения трансформации каждая строка получает новое поле
avg_price.
joinaggregate от aggregateaggregatetransform: [
{
aggregate: [
{ op: "sum", field: "sales", as: "total_sales" }
],
groupby: ["category"]
}
]
Результат:
| category | total_sales |
|---|---|
| A | 100 |
| B | 200 |
Исходные строки исчезают.
joinaggregatetransform: [
{
joinaggregate: [
{ op: "sum", field: "sales", as: "total_sales" }
],
groupby: ["category"]
}
]
Результат:
| category | sales | total_sales |
|---|---|---|
| A | 30 | 100 |
| A | 70 | 100 |
| B | 50 | 200 |
| B | 150 | 200 |
Исходные строки сохраняются.
joinaggregate| Поле | Назначение |
|---|---|
op |
Агрегирующая операция |
field |
Поле для агрегации |
as |
Имя результирующего поля |
groupby |
Группировка |
| Операция | Описание |
|---|---|
sum |
Сумма |
average |
Среднее |
mean |
Синоним average |
median |
Медиана |
min |
Минимум |
max |
Максимум |
count |
Количество |
variance |
Дисперсия |
stdev |
Стандартное отклонение |
| Операция | Назначение |
|---|---|
q1 |
Первый квартиль |
q3 |
Третий квартиль |
stderr |
Стандартная ошибка |
ci0 |
Нижняя граница доверительного интервала |
ci1 |
Верхняя граница доверительного интервала |
[
{ product: "A", sales: 100 },
{ product: "B", sales: 150 },
{ product: "C", sales: 200 }
]
{
data: {
values: [
{ product: "A", sales: 100 },
{ product: "B", sales: 150 },
{ product: "C", sales: 200 }
]
},
transform: [
{
joinaggregate: [
{
op: "average",
field: "sales",
as: "avg_sales"
}
]
}
],
mark: "bar",
encoding: {
x: { field: "product" },
y: { field: "sales", type: "quantitative" },
color: {
condition: {
test: "datum.sales > datum.avg_sales",
value: "tomato"
},
value: "steelblue"
}
}
}
Трансформация вычисляет:
(100 + 150 + 200) / 3 = 150
После этого каждая запись получает:
{
avg_sales: 150
}
Это позволяет сравнивать строку с глобальным средним.
{
transform: [
{
joinaggregate: [
{
op: "average",
field: "sales",
as: "category_avg"
}
],
groupby: ["category"]
}
]
}
[
{ category: "A", sales: 10 },
{ category: "A", sales: 20 },
{ category: "B", sales: 50 },
{ category: "B", sales: 70 }
]
| category | sales | category_avg |
|---|---|---|
| A | 10 | 15 |
| A | 20 | 15 |
| B | 50 | 60 |
| B | 70 | 60 |
Одна из самых распространённых задач.
{
transform: [
{
joinaggregate: [
{
op: "sum",
field: "sales",
as: "total_sales"
}
]
},
{
calculate: "datum.sales / datum.total_sales",
as: "percent"
}
]
}
100 + 150 + 250 = 500
{
total_sales: 500
}
sales / total_sales
| sales | total_sales | percent |
|---|---|---|
| 100 | 500 | 0.2 |
| 150 | 500 | 0.3 |
| 250 | 500 | 0.5 |
{
transform: [
{
joinaggregate: [
{
op: "average",
field: "value",
as: "avg"
}
]
},
{
calculate: "datum.value - datum.avg",
as: "deviation"
}
]
}
Стандартизация значений.
{
transform: [
{
joinaggregate: [
{
op: "average",
field: "value",
as: "mean"
},
{
op: "stdev",
field: "value",
as: "std"
}
]
},
{
calculate: "(datum.value - datum.mean) / datum.std",
as: "zscore"
}
]
}
{
transform: [
{
joinaggregate: [
{
op: "sum",
field: "sales",
as: "category_total"
}
],
groupby: ["category"]
},
{
calculate: "datum.sales / datum.category_total",
as: "share"
}
]
}
| category | sales | category_total | share |
|---|---|---|---|
| A | 10 | 40 | 0.25 |
| A | 30 | 40 | 0.75 |
joinaggregate часто используется для создания линий
среднего значения.
{
layer: [
{
mark: "bar",
encoding: {
x: { field: "product" },
y: { field: "sales", type: "quantitative" }
}
},
{
transform: [
{
joinaggregate: [
{
op: "average",
field: "sales",
as: "avg_sales"
}
]
}
],
mark: {
type: "rule",
color: "red"
},
encoding: {
y: {
field: "avg_sales",
type: "quantitative"
}
}
}
]
}
windowjoinaggregate |
window |
|---|---|
| Простые агрегаты | Порядковые вычисления |
| Не требует сортировки | Обычно требует сортировку |
| Нет frame | Есть frame |
| Нет row_number | Есть ranking |
joinaggregateПодходит для:
windowПодходит для:
SUM(sales) OVER (PARTITION BY category)
{
joinaggregate: [
{
op: "sum",
field: "sales",
as: "category_total"
}
],
groupby: ["category"]
}
{
transform: [
{
joinaggregate: [
{
op: "sum",
field: "sales",
as: "total"
},
{
op: "average",
field: "sales",
as: "avg"
},
{
op: "max",
field: "sales",
as: "max_sales"
},
{
op: "min",
field: "sales",
as: "min_sales"
}
]
}
]
}
calculateНаиболее типичная схема:
joinaggregate
↓
calculate
↓
encoding
{
transform: [
{
joinaggregate: [
{
op: "sum",
field: "profit",
as: "total_profit"
}
]
},
{
calculate: "datum.profit / datum.total_profit * 100",
as: "profit_percent"
}
]
}
При использовании facet трансформации работают внутри
каждого фасета.
{
facet: {
field: "region"
},
spec: {
transform: [
{
joinaggregate: [
{
op: "average",
field: "sales",
as: "avg_sales"
}
]
}
]
}
}
Среднее будет вычисляться отдельно для каждого региона.
{
transform: [
{
timeUnit: "month",
field: "date",
as: "month"
},
{
joinaggregate: [
{
op: "average",
field: "sales",
as: "month_avg"
}
],
groupby: ["month"]
}
]
}
joinaggregate требует прохода по данным и хранения
агрегированных значений.
Для больших наборов данных:
groupby;window только при необходимости.Если указан groupby:
groupby: ["category"]
создаются отдельные группы.
Для каждой группы вычисляются:
sum
average
median
count
...
Полученные значения копируются в каждую строку группы.
joinaggregate не уменьшает количество строк.
groupby{
joinaggregate: [
{
op: "sum",
field: "sales",
as: "total"
}
]
}
Без groupby вычисляется глобальная сумма.
as: "sales"
Может перезаписать оригинальное поле.
field: "name"
op: "sum"
Числовые операции требуют numeric field.
{
data: {
values: [
{ region: "East", sales: 100 },
{ region: "East", sales: 200 },
{ region: "West", sales: 50 },
{ region: "West", sales: 150 }
]
},
transform: [
{
joinaggregate: [
{
op: "sum",
field: "sales",
as: "region_total"
}
],
groupby: ["region"]
},
{
calculate: "datum.sales / datum.region_total",
as: "share"
}
],
mark: "bar",
encoding: {
x: { field: "region" },
y: {
field: "share",
type: "quantitative"
},
tooltip: [
{ field: "sales" },
{ field: "region_total" },
{ field: "share", format: ".2%" }
]
}
}
{
transform: [
{
joinaggregate: [
{
op: "average",
field: "value",
as: "mean"
},
{
op: "stdev",
field: "value",
as: "std"
}
]
},
{
calculate: "abs(datum.value - datum.mean) > datum.std * 2",
as: "isOutlier"
}
]
}
joinaggregate +
filter[
{
joinaggregate: [
{
op: "average",
field: "sales",
as: "avg"
}
]
},
{
filter: "datum.sales > datum.avg"
}
]
joinaggregate +
lookup[
{
lookup: "id",
from: ...
},
{
joinaggregate: ...
}
]
joinaggregate +
fold[
{
fold: ["A", "B", "C"]
},
{
joinaggregate: ...
}
]
joinaggregateТрансформация занимает промежуточное положение между:
aggregate);window).Она обеспечивает:
joinaggregate является лучшим выборомДля таких задач используется window.
Исходные строки
↓
groupby
↓
агрегирование
↓
присоединение результата
↓
calculate/filter/window
↓
encoding
↓
render