Параметры, условия и фильтры
Параметры
Параметры — строительные блоки интерактивности Altair. Они бывают двух типов: переменные и выборы. Рассмотрим эти понятия на примерах.
Этот материал существенно изменился с выходом Altair 5.
Переменные: повторное использование значений
Переменные параметры позволяют определить значение один раз, а затем использовать его в разных частях диаграммы. Начнём с простой диаграммы рассеяния по набору cars:
import altair as alt
from altair.datasets import data
cars = data.cars.url
alt.Chart(cars).mark_circle().encode(
x='Horsepower:Q',
y='Miles_per_Gallon:Q',
color='Origin:N'
)
Переменные параметры создаются функцией param. Здесь свойство value задаёт начальное значение 0.1:
op_var = alt.param(value=0.1)
Чтобы использовать переменную в спецификации, явно добавим её к диаграмме методом add_params. Затем можно ссылаться на неё при задании кодирований, условий и фильтров. В этом примере параметр op_var задаёт непрозрачность:
op_var = alt.param(value=0.1)
alt.Chart(cars).mark_circle(opacity=op_var).encode(
x='Horsepower:Q',
y='Miles_per_Gallon:Q',
color='Origin:N'
).add_params(
op_var
)
Возникает вопрос: нужны ли все эти действия? Тот же результат можно получить более привычным способом, без param и add_params:
op_var2 = 0.1
alt.Chart(cars).mark_circle(opacity=op_var2).encode(
x='Horsepower:Q',
y='Miles_per_Gallon:Q',
color='Origin:N'
)
Преимущество param становится очевидным при добавлении ещё одного компонента. Свойство bind связывает параметр с элементом ввода, в данном случае с ползунком:
slider = alt.binding_range(min=0, max=1, step=0.05, name='opacity:')
op_var = alt.param(value=0.1, bind=slider)
alt.Chart(cars).mark_circle(opacity=op_var).encode(
x='Horsepower:Q',
y='Miles_per_Gallon:Q',
color='Origin:N'
).add_params(
op_var
)
Теперь ползунок динамически меняет непрозрачность точек. Подробнее о связывании параметров с элементами ввода см. «Привязки и виджеты».
Интерактивность Altair полностью выполняется в браузере. Диаграммы можно сохранить в HTML и передать коллегам: для взаимодействия с ними достаточно браузера, устанавливать Python не требуется.
Выборы: обработка взаимодействий с диаграммой
Параметры выбора задают запросы к данным, управляемые действиями пользователя на диаграмме: например, щелчками или перетаскиванием. Есть два типа выбора: selection_interval и selection_point.
Создадим простую диаграмму и добавим интервальный выбор. Его можно задать через param(select="interval"), но удобнее использовать сокращённую функцию selection_interval.
Вот диаграмма рассеяния по набору cars:
import altair as alt
from altair.datasets import data
cars = data.cars.url
alt.Chart(cars).mark_point().encode(
x='Horsepower:Q',
y='Miles_per_Gallon:Q',
color='Origin:N'
)
Сначала создадим интервальный выбор функцией selection_interval. Такой выбор также называют brush, то есть выделением области:
brush = alt.selection_interval()
Добавим его к диаграмме через add_params:
alt.Chart(cars).mark_point().encode(
x='Horsepower:Q',
y='Miles_per_Gallon:Q',
color='Origin:N'
).add_params(
brush
)
Теперь можно нажать кнопку мыши и перетащить указатель, чтобы создать область выбора, а затем перемещать эту область.
Как и в примере с переменной, мы создали параметр — brush = alt.selection_interval() — и добавили его методом add_params. Однако пока не определили, как диаграмма должна реагировать на выбор. Это показано в следующем разделе.
Условия
Этот материал существенно изменился с выходом Altair
5.5.0. Функцияwhenпоявилась в версии5.4.0; рекомендуется использовать её вместоcondition.
В предыдущем примере область выбора пока не влияет на диаграмму. Для реакции на выбор нужно обратиться к brush в спецификации. Создадим условное кодирование цвета функцией when:
conditional = alt.when(brush).then("Origin:N").otherwise(alt.value("lightgray"))
alt.Chart(cars).mark_point().encode(
x="Horsepower:Q",
y="Miles_per_Gallon:Q",
color=conditional,
).add_params(
brush
)
Цвет точек теперь зависит от того, попадают ли они в область выбора. Параметр brush используется как предикат — выражение, принимающее значение True или False.
Это задаёт определение conditional:
conditional = alt.when(brush).then("Origin:N").otherwise(alt.value("lightgray"))
Для точек внутри выбора предикат равен True, для точек снаружи — False. Строка "Origin:N" определяет цвет точек внутри области, а alt.value('lightgray') задаёт постоянный цвет остальных точек.
Как работает when
Синтаксис when-then-otherwise основан на polars.when и напоминает конструкцию if-else в Python:
# alt.when(brush)
if brush:
# .then("Origin:N")
color = "Origin:N"
else:
# .otherwise(alt.value("lightgray"))
color = alt.value("lightgray")
Если не указывать .otherwise(), используется значение канала по умолчанию:
source = data.cars()
brush = alt.selection_interval()
points = alt.Chart(source).mark_point().encode(
x="Horsepower",
y="Miles_per_Gallon",
color=alt.when(brush).then(alt.value("goldenrod"))
).add_params(
brush
)
points
Несколько условных ветвей, соответствующих if, elif, ..., elif в Python, задаются цепочкой вызовов when. После знакомства с типами выбора мы рассмотрим работающий пример.
Более сложные случаи описаны в справочнике функции when и в примерах галереи:
- Диаграмма с точками и штрихами;
- Выбор и выделение столбцов;
- Подсказка для нескольких линий;
- Траектории точек при наведении;
- Каскадная диаграмма.
Связь условий между диаграммами
Условные кодирования особенно полезны, когда один выбор действует в нескольких представлениях данных составной диаграммы. Создадим Chart по предыдущему примеру и расположим рядом две версии: в первой канал x связан с "Horsepower", во второй — с "Acceleration":
chart = alt.Chart(cars).mark_point().encode(
x='Horsepower:Q',
y='Miles_per_Gallon:Q',
color=alt.when(brush).then("Origin:N").otherwise(alt.value("lightgray")),
).properties(
width=250,
height=250
).add_params(
brush
)
chart | chart.encode(x='Acceleration:Q')
Обе диаграммы используют один объект выбора, поэтому механизм отображения связывает выделение между панелями. Такое динамическое представление помогает исследовать зависимости в наборе данных.
Поведение каждого типа выбора настраивается его атрибутами. Например, можно ограничить выделение каналом "x", чтобы сосредоточиться на этом признаке. Изменим определение brush, оставив остальные настройки прежними:
brush = alt.selection_interval(encodings=['x'])
chart = alt.Chart(cars).mark_point().encode(
x='Horsepower:Q',
y='Miles_per_Gallon:Q',
color=alt.when(brush).then("Origin:N").otherwise(alt.value("lightgray")),
).properties(
width=250,
height=250
).add_params(
brush
)
chart | chart.encode(x='Acceleration:Q')
Выбранные точки иногда перекрываются невыбранными. Чтобы вывести выбранные точки на передний план, изменим порядок отрисовки условным кодированием order:
# Lower numbers for `order` means further into the background
selected_on_top = alt.when(brush).then(alt.value(1)).otherwise(alt.value(0))
chart.encode(order=selected_on_top) | chart.encode(order=selected_on_top, x='Acceleration:Q')
Другой пример приведён в галерее: «Порядок отрисовки выбранных точек».
Фильтры
Параметр выбора фильтрует данные почти так же, как используется в when. В transform_filter(brush) параметр brush снова выступает предикатом. Точки с результатом True, то есть внутри области выбора, сохраняются; точки с результатом False исключаются.
В одной диаграмме нельзя одновременно создавать выбор и фильтровать по нему данные, поэтому обычно используют хотя бы две поддиаграммы. Здесь параметр выбора добавлен к верхней диаграмме, а нижняя фильтрует данные по этому выбору. Изменяйте размер и положение выделенной области и наблюдайте, как меняется количество автомобилей на столбчатой диаграмме:
brush = alt.selection_interval()
points = alt.Chart(cars).mark_point().encode(
x='Horsepower:Q',
y='Miles_per_Gallon:Q',
color='Origin:N'
).add_params(
brush
)
bars = alt.Chart(cars).mark_bar().encode(
x='count()',
y='Origin:N',
color='Origin:N'
).transform_filter(
brush
)
points & bars
Типы выбора
Рассмотрев основы взаимодействия, изучим типы параметров выбора систематически. Для действий мышью используются два основных типа:
- Интервальный выбор — выделение диапазонов данных нажатием и перетаскиванием.
- Точечный выбор — выделение отдельных элементов щелчком или наведением.
Для сравнения будем использовать одну визуализацию. Следующая вспомогательная функция создаёт тепловую карту по набору cars, где цвет реагирует на выбор:
def make_example(selector: alt.Parameter) -> alt.Chart:
cars = data.cars.url
return alt.Chart(cars).mark_rect().encode(
x="Cylinders:O",
y="Origin:N",
color=alt.when(selector).then("count()").otherwise(alt.value("lightgray")),
).properties(
width=300,
height=180
).add_params(
selector
)
Интервальный выбор
Интервальный выбор выделяет диапазон элементов данных нажатием и перетаскиванием. Такой выбор часто называют brush; его применяют для фильтрации, масштабирования и выделения диапазонов точек.
Простой интервальный выбор
Самый простой вариант создаётся функцией selection_interval:
interval = alt.selection_interval()
make_example(interval)
Нажатие и перетаскивание создаёт область выбора. Её можно перемещать, изменяя выбранные элементы данных.
Настройка интервального выбора
Функция selection_interval принимает несколько аргументов настройки. Например, можно ограничить выбор осью x и задать, что пустой выбор не содержит точек:
interval_x = alt.selection_interval(encodings=['x'], empty=False)
make_example(interval_x)
Аргумент empty=False можно также передать функции when, чтобы отдельно настроить реакцию конкретного условия на пустой выбор.
По умолчанию empty=True: при пустом выборе предикат равен True для всех точек. При empty=False он равен False для всех точек. Это определяет поведение до первого выделения:
brush = alt.selection_interval()
# Default behavior (empty=True): When no selection is made in this example, all points will be colored according to their "Origin" category (they are considered as *selected*, and colored according to the `then` clause)
color=alt.when(brush).then("Origin:N").otherwise(alt.value("lightgray"))
# Custom behavior (empty=False): When no selection is made in this example, all points will be lightgray (they are considered as *unselected*, and colored according to the `otherwise` clause )
color=alt.when(brush, empty=False).then("Origin:N").otherwise(alt.value("lightgray"))
Начальные значения
Часто полезно сразу показать выбранную область, чтобы направить внимание пользователя. Для этого служит аргумент value функции selection_interval.
Начальные значения особенно полезны для:
- представления «обзор и детали», в котором показана отфильтрованная часть данных;
- немедленного привлечения внимания к определённой области;
- сохранения состояния выбора между визуализациями или пользовательскими сеансами.
В этом примере временного ряда начальный выбор охватывает период финансового кризиса 2008 года:
import altair as alt
from altair.datasets import data
import datetime as dt
source = data.sp500.url
# Define initial date range to select
date_range = (dt.date(2007, 6, 30), dt.date(2009, 6, 30))
# Create interval selection with initial value
brush = alt.selection_interval(
encodings=['x'],
value={'x': date_range}
)
# Create base chart for both panels
base = alt.Chart(source, width=600, height=200).mark_area().encode(
x = 'date:T',
y = 'price:Q'
)
# Upper panel shows detailed view filtered by the brush
upper = base.encode(
alt.X('date:T').scale(domain=brush)
)
# Lower panel shows overview with the brush control
lower = base.properties(
height=60
).add_params(brush)
# Combine the two charts
upper & lower
Пример показывает приём «обзор и детали»: одна диаграмма отображает весь набор данных, а другая — подробное представление выбранной части.
Для дат можно напрямую использовать объекты Python datetime.date и datetime.datetime. Altair автоматически преобразует их в формат Vega-Lite.
Формат value зависит от каналов выбора:
- канал
x:value={'x': [min, max]}; - канал
y:value={'y': [min, max]}; - оба канала:
value={'x': [xmin, xmax], 'y': [ymin, ymax]}.
Можно также задать начальный выбор категориальных данных. Тогда список или кортеж содержит конкретные категории, а не диапазон. Например, выберем только автомобили с четырьмя или шестью цилиндрами:
cylinder_select = alt.selection_interval(
encodings=['x'],
value={"x": [4, 6]} # Select 4 and 6 (but not 5) cylinder cars
)
make_example(cylinder_select)
Точечный выбор
Точечный выбор выделяет отдельные элементы данных. Он работает с дискретными точками и подходит для интерактивных легенд, подсказок и выделения конкретных объектов.
Простой точечный выбор
Самый простой вариант создаётся функцией selection_point. По умолчанию точки выбираются щелчком:
point = alt.selection_point()
make_example(point)
Значение on='pointerover' задаёт выбор при наведении. Аргумент on принимает любой селектор потока событий Vega. Более сложный пример показан в конце раздела композиции параметров.
Флаг nearest=True выделяет ближайшую точку, не требуя точного попадания указателя:
point_nearest = alt.selection_point(on='pointerover', nearest=True)
make_example(point_nearest)
Точечные выборы поддерживают множественное выделение. По умолчанию щелчок с зажатой клавишей Shift добавляет или удаляет точку из выбора. Попробуйте это в примерах выше.
Начальные значения
Начальные значения точечного выбора тоже задаются аргументом value. Для такого выбора значения указывают через поля или каналы:
point_initial = alt.selection_point(
fields=['Origin'],
value=[{'Origin': 'USA'}] # Initially select USA
)
make_example(point_initial)
Что именно выбирается
При настройке более сложного выбора нужно определить, на какие свойства данных он нацелен. Аргументы fields и encodings задают свойства, по которым определяется принадлежность точек выбору.
Например, создадим небольшую диаграмму, выполняющую роль интерактивной легенды. Выбор fields=['Origin'] нацелен на поле Origin. Щелчок по точке в правой верхней диаграмме-легенде выбирает все точки с соответствующим значением Origin:
selection = alt.selection_point(fields=['Origin'])
color = (
alt.when(selection)
.then(alt.Color("Origin:N").legend(None))
.otherwise(alt.value("lightgray"))
)
scatter = alt.Chart(cars).mark_point().encode(
x='Horsepower:Q',
y='Miles_per_Gallon:Q',
color=color,
tooltip='Name:N'
)
legend = alt.Chart(cars).mark_point().encode(
alt.Y('Origin:N').axis(orient='right'),
color=color
).add_params(
selection
)
scatter | legend
Вместо fields=['Origin'] можно написать encodings=['color'], поскольку канал color связан с 'Origin'. В Altair также есть сокращённый способ создания интерактивных легенд, описанный в разделе привязки легенды.
Аналогично можно задать несколько полей или каналов, значения которых должны совпасть для включения элемента в выбор. Изменим пример, создав двумерную интерактивную легенду: она выбирает точки одновременно по Origin и числу цилиндров:
selection = alt.selection_point(fields=['Origin', 'Cylinders'])
color = (
alt.when(selection)
.then(alt.Color("Origin:N").legend(None))
.otherwise(alt.value("lightgray"))
)
scatter = alt.Chart(cars).mark_point().encode(
x='Horsepower:Q',
y='Miles_per_Gallon:Q',
color=color,
tooltip='Name:N'
)
legend = alt.Chart(cars).mark_rect().encode(
alt.Y('Origin:N').axis(orient='right'),
x='Cylinders:O',
color=color
).add_params(
selection
)
scatter | legend
Такая настройка позволяет создавать самые разные связанные интерактивные диаграммы.
Объединение параметров
В одной диаграмме можно объединять несколько параметров: через отдельные условия, разные ветви when или логическую композицию параметров.
Несколько условий
В этом примере точки увеличиваются при наведении, а после щелчка получают красную заливку. Аргумент empty=False гарантирует отсутствие начального выбора. Попробуйте удерживать Shift, чтобы выбирать несколько точек наведением или щелчками:
click = alt.selection_point(empty=False)
hover = alt.selection_point(on='pointerover', empty=False)
points = alt.Chart(cars).mark_point().encode(
x='Horsepower:Q',
y='Miles_per_Gallon:Q',
fill=alt.when(click).then(alt.value('red')),
size=alt.when(hover).then(alt.value(1000))
).add_params(
click, hover
)
points
Условные ветви
Функция when допускает несколько ветвей then, аналогичных elif. Они изменяют одно кодирование в ответ на разные параметры.
Здесь точка при наведении становится жёлтой, а после щелчка — красной. При щелчке указатель находится над точкой, поэтому активны оба условия. Приоритет имеет более ранняя ветвь. Поменяйте местами две конструкции when.then: тогда щелчок не будет менять заливку на красную.
click = alt.selection_point(empty=False)
hover = alt.selection_point(on='pointerover', empty=False)
points = alt.Chart(cars).mark_point().encode(
x='Horsepower:Q',
y='Miles_per_Gallon:Q',
fill=(
alt.when(click)
.then(alt.value('red'))
.when(hover)
.then(alt.value('gold'))
),
size=alt.when(hover).then(alt.value(1000))
).add_params(
click, hover
)
points
Логическая композиция параметров
Altair поддерживает объединение параметров операторами &, | и ~: логическими И, ИЛИ и НЕ соответственно. Полученные выражения используются и в фильтрах, и в условиях.
В следующем примере столбчатая диаграмма подсчитывает только точки, попавшие в области выбора обеих диаграмм рассеяния. Чтобы появились столбцы, нужно создать выбор в обеих диаграммах:
# empty=False ensure that no points are selected before a selection is drawn
brush = alt.selection_interval(empty=False)
brush2 = alt.selection_interval(empty=False)
points = alt.Chart(cars).mark_point(size=10).encode(
x='Horsepower:Q',
y='Miles_per_Gallon:Q',
color='Origin:N'
)
points2 = points.encode(
x='Acceleration:Q',
y='Miles_per_Gallon:Q',
)
bars = alt.Chart(cars).mark_bar().encode(
x='count()',
y='Origin:N',
color='Origin:N'
).transform_filter(
brush & brush2
)
(points.add_params(brush) | points2.add_params(brush2)) & bars
Для более сложного условного кодирования вернёмся к тепловой карте. Представим, что два человека выбирают области на одной диаграмме. Алекс выделяет область с зажатой клавишей Alt (Option в macOS), а Морган — с зажатой Shift.
Цветом отмечаются элементы, попавшие в выбор Алекса или Моргана, но не в оба одновременно. Чтобы увидеть эффект, создайте оба выделения. Класс BrushConfig задаёт области Моргана другой стиль, позволяя различать их:
alex = alt.selection_interval(
on="[pointerdown[event.altKey], pointerup] > pointermove",
empty=False,
)
morgan = alt.selection_interval(
on="[pointerdown[event.shiftKey], pointerup] > pointermove",
mark=alt.BrushConfig(fill="#fdbb84", fillOpacity=0.5, stroke="#e34a33"),
empty=False,
)
exlusive_or = (alex | morgan) & ~(alex & morgan)
alt.Chart(cars).mark_rect().encode(
x='Cylinders:O',
y='Origin:O',
color=alt.when(exlusive_or).then("count()").otherwise(alt.value("grey")),
).add_params(
alex, morgan
).properties(
width=300,
height=180
)
Эти операторы позволяют произвольно объединять выборы:
alex | morgan— прямоугольники внутри выбора Алекса или Моргана;~(alex & morgan)— прямоугольники, не входящие одновременно в оба выбора;alex | ~morgan— прямоугольники внутри выбора Алекса или вне выбора Моргана.
Другие настройки выбора, включая события мыши и клавиатуры, описаны в документации выбора Vega-Lite. Настройки внешнего вида области выбора описаны в справочнике класса BrushConfig.