Диаграммы со слоями и несколькими представлениями
Помимо основного объекта Chart, Altair предоставляет составные типы диаграмм: с накоплением, слоями, разбиением на панели и повторением. Они перечислены в таблицах:
| Класс | Функция | Оператор | Раздел |
|---|---|---|---|
LayerChart |
alt.layer(chart1, chart2) |
chart1 + chart2 |
Слои |
HConcatChart |
alt.hconcat(chart1, chart2) |
chart1 | chart2 |
Горизонтальное объединение |
VConcatChart |
alt.vconcat(chart1, chart2) |
chart1 & chart2 |
Вертикальное объединение |
| Класс | Метод | Раздел |
|---|---|---|
RepeatChart |
chart.repeat(row, column) |
Повторение |
FacetChart |
chart.facet(facet, row, column) |
Разбиение на панели |
Диаграммы со слоями
Слои позволяют наложить две разные диаграммы на общие оси. Например, это удобно, если одни данные нужно показать несколькими примитивами:
import altair as alt
from altair.datasets import data
stocks = data.stocks.url
base = alt.Chart(stocks).encode(
x='date:T',
y='price:Q',
color='symbol:N'
).transform_filter(
alt.datum.symbol == 'GOOG'
)
base.mark_line() + base.mark_point()
Здесь оператор + создаёт диаграмму со слоями. Вместо него можно использовать функцию alt.layer, принимающую любое число диаграмм:
alt.layer(
base.mark_line(),
base.mark_point(),
base.mark_rule()
).interactive()
Обычно оба способа возвращают объект LayerChart со свойствами и методами, похожими на Chart. Если у всех диаграмм одинаковые каналы row, column или facet, эти кодирования автоматически выносятся на общий уровень, и возвращается FacetChart. Подробнее см. ниже.
Порядок слоёв
Порядок слоёв определяется порядком их указания. В записи layer1 + layer2 или alt.layer(layer1, layer2) слой layer1 находится под layer2, поэтому второй слой может закрывать примитивы первого.
Например, покажем точки поверх тепловой карты:
import altair as alt
from altair.datasets import data
source = data.movies.url
heatmap = alt.Chart(source).mark_rect().encode(
alt.X('IMDB Rating:Q').bin(),
alt.Y('Rotten Tomatoes Rating:Q').bin(),
alt.Color('count()').scale(scheme='greenblue')
)
points = alt.Chart(source).mark_circle(
color='black',
size=5,
).encode(
x='IMDB Rating:Q',
y='Rotten Tomatoes Rating:Q',
)
heatmap + points
Если поменять порядок, точки рисуются первыми, а примитивы тепловой карты закрывают их:
points + heatmap
Если результат не соответствует ожиданиям, проверьте порядок слоёв.
Горизонтальное объединение
Для размещения двух диаграмм рядом обычно используют HConcatChart, создаваемый функцией hconcat или оператором |.
Например, объединим диаграмму рассеяния с гистограммой распределения её точек:
import altair as alt
from altair.datasets import data
penguins = data.penguins.url
chart1 = alt.Chart(penguins).mark_point().encode(
x=alt.X('Flipper Length (mm):Q', scale=alt.Scale(zero=False)),
y=alt.Y('Body Mass (g):Q', scale=alt.Scale(zero=False)),
color='Species:N'
).properties(
height=300,
width=300
)
chart2 = alt.Chart(penguins).mark_bar().encode(
x='count()',
y=alt.Y('Body Mass (g):Q', bin=alt.Bin(maxbins=30)),
color='Species:N'
).properties(
height=300,
width=100
)
chart1 | chart2
Здесь используется оператор |, но тот же результат можно получить функцией hconcat:
alt.hconcat(chart1, chart2)
Оба способа возвращают HConcatChart со многими теми же методами и атрибутами верхнего уровня, что и Chart.
Если каждая панель меняет только один аспект визуализации, удобнее могут быть повторяемые диаграммы или разбиение на панели.
Вертикальное объединение
Подобно горизонтальному объединению, Altair позволяет объединять диаграммы вертикально функцией vconcat или оператором &.
Например, разместим друг над другом два представления одних данных и добавим интерактивный выбор brush:
import altair as alt
from altair.datasets import data
source = data.sp500.url
brush = alt.selection_interval(encodings=['x'])
base = alt.Chart(source).mark_area().encode(
x = 'date:T',
y = 'price:Q'
).properties(
width=600,
height=200
)
upper = base.encode(alt.X('date:T').scale(domain=brush))
lower = base.properties(
height=60
).add_params(brush)
alt.vconcat(upper, lower)
Вместо более длинной записи alt.vconcat(upper, lower) можно использовать upper & lower.
Если в каждой панели меняется только группировка или кодирование, повторение или разбиение на панели может быть эффективнее.
Повторяемые диаграммы
RepeatChart удобен для горизонтального или вертикального объединения панелей, различающихся только одним или несколькими кодированиями.
Предположим, нужна многопанельная диаграмма рассеяния с разными проекциями многомерного набора данных. Сначала построим её вручную через hconcat и vconcat, а затем рассмотрим более короткую запись с repeat:
import altair as alt
from altair.datasets import data
penguins = data.penguins.url
base = alt.Chart().mark_point().encode(
color='Species:N'
).properties(
width=200,
height=200
).interactive()
chart = alt.vconcat(data=penguins)
for y_encoding in ['Flipper Length (mm):Q', 'Body Mass (g):Q']:
row = alt.hconcat()
for x_encoding in ['Beak Length (mm):Q', 'Beak Depth (mm):Q']:
row |= base.encode(
x=alt.X(x_encoding, scale=alt.Scale(zero=False)),
y=alt.Y(y_encoding, scale=alt.Scale(zero=False)),
)
chart &= row
chart
Циклы по кодированиям x и y создают сетку 2 × 2 с разными представлениями данных. Код понятен, хотя и довольно длинный.
Метод Chart.repeat, создающий RepeatChart, упрощает эту задачу:
import altair as alt
from altair.datasets import data
penguins = data.penguins.url
alt.Chart(penguins).mark_point().encode(
alt.X(alt.repeat("column"), type='quantitative', scale=alt.Scale(zero=False)),
alt.Y(alt.repeat("row"), type='quantitative', scale=alt.Scale(zero=False)),
color='Species:N'
).properties(
width=200,
height=200
).repeat(
row=['Flipper Length (mm)', 'Body Mass (g)'],
column=['Beak Length (mm)', 'Beak Depth (mm)']
).interactive()
Chart.repeat задаёт набор кодирований строк и/или столбцов. В спецификации на них можно ссылаться через alt.repeat('row') и alt.repeat('column').
Ещё один вариант — повторение слоёв. Здесь столбцы US Gross и Worldwide Gross накладываются по оси y с помощью alt.repeat('layer'):
import altair as alt
from altair.datasets import data
source = data.movies()
alt.Chart(source).mark_line().encode(
x=alt.X("IMDB Rating").bin(),
y=alt.Y(alt.repeat('layer')).aggregate('mean').title("Mean of US and Worldwide Gross"),
color=alt.ColorDatum(alt.repeat('layer'))
).repeat(layer=["US Gross", "Worldwide Gross"])
Сейчас repeat применяется только к кодированиям, а не, например, к преобразованиям данных. В сообществе Vega-Lite обсуждается возможность расширить этот подход.
Разбиение на панели
Как и повторение, разбиение на панели создаёт несколько представлений набора данных. Но панели различаются подмножествами данных, а не кодированиями: например, отдельная панель для каждого из трёх видов пингвинов.
Такие диаграммы также называют малыми множественными диаграммами (small multiples), trellis, lattice, grid или panel charts.
Можно построить их вручную, сочетая фильтрацию с горизонтальным объединением:
import altair as alt
from altair.datasets import data
penguins = data.penguins.url
base = alt.Chart(penguins).mark_point().encode(
x=alt.X('Flipper Length (mm):Q').scale(zero=False),
y=alt.Y('Body Mass (g):Q').scale(zero=False),
color='Species:N'
).properties(
width=160,
height=160
)
chart = alt.hconcat()
for species in ['Adelie', 'Chinstrap', 'Gentoo']:
chart |= base.transform_filter(alt.datum.Species == species)
chart
Как и при ручном построении повторения, код понятен, но длиннее необходимого.
С методом .facet запись становится короче:
alt.Chart(penguins).mark_point().encode(
x=alt.X('Flipper Length (mm):Q').scale(zero=False),
y=alt.Y('Body Mass (g):Q').scale(zero=False),
color='Species:N'
).properties(
width=180,
height=180
).facet(
column='Species:N'
)
Для таких простых диаграмм тот же результат даёт канал кодирования column:
alt.Chart(penguins).mark_point().encode(
x=alt.X('Flipper Length (mm):Q').scale(zero=False),
y=alt.Y('Body Mass (g):Q').scale(zero=False),
color='Species:N',
column='Species:N'
).properties(
width=180,
height=180
)
Преимущество .facet — возможность разбивать на панели более сложные составные диаграммы. Например, вот диаграмма со слоями и выбором при наведении:
hover = alt.selection_point(on='pointerover', nearest=True, empty=False)
when_hover = alt.when(hover)
base = alt.Chart(penguins).encode(
x=alt.X('Flipper Length (mm):Q').scale(zero=False),
y=alt.Y('Body Mass (g):Q').scale(zero=False),
color=alt.condition(hover, 'Species:N', alt.value('lightgray'))
).properties(
width=180,
height=180,
)
points = base.mark_point().add_params(hover)
text = base.mark_text(dy=-5).encode(
text = 'Species:N',
opacity = alt.condition(hover, alt.value(1), alt.value(0))
)
alt.layer(points, text).facet(
'Species:N',
)
Во всех примерах данные разбиваются по столбцам. Также поддерживается разбиение по строкам или одновременно по строкам и столбцам.
Слои с общим кодированием панели
Если все диаграммы, переданные layer или объединённые через +, содержат одинаковые каналы row, column или facet, эти кодирования автоматически выносятся на общий уровень, а результатом становится FacetChart. Две записи ниже создают одну и ту же спецификацию Vega-Lite:
import altair as alt
from altair.datasets import data
penguins = data.penguins.url
base = alt.Chart(penguins).encode(
x=alt.X('Flipper Length (mm):Q').scale(zero=False),
y=alt.Y('Body Mass (g):Q').scale(zero=False),
row='Species:N',
)
# facet encoding shared on each layer — hoisted automatically
alt.layer(
base.mark_point(),
base.mark_line(),
)
Эквивалентная явная запись:
base = alt.Chart(penguins).encode(
x=alt.X('Flipper Length (mm):Q').scale(zero=False),
y=alt.Y('Body Mass (g):Q').scale(zero=False),
)
alt.layer(
base.mark_point(),
base.mark_line(),
).facet(row='Species:N')
Если кодирования панелей различаются между слоями или присутствуют только в части слоёв, как и раньше возникает TypeError.