JupyterChart

Класс JupyterChart, появившийся в Vega-Altair 5.1, позволяет обновлять уже отображённые диаграммы и получать из Python состояние интерактивных параметров.

Поддерживаемые среды

JupyterChart — виджет Jupyter, созданный на основе библиотеки AnyWidget. Он совместим со средами разработки и инструментами для панелей мониторинга, которые поддерживают сторонние виджеты Jupyter. Проверена работа в следующих средах:

  • Classic Jupyter Notebook;
  • JupyterLab;
  • Visual Studio Code;
  • Google Colab;
  • Voila.

Если вы попробуете JupyterChart в другой среде с поддержкой виджетов Jupyter, расскажите разработчикам о результате, чтобы они могли обновить этот список.

Основы использования

Для создания JupyterChart передайте обычный экземпляр Chart конструктору alt.JupyterChart. Диаграмма отображается автоматически, если результат последнего выражения в ячейке блокнота — экземпляр JupyterChart:

import altair as alt
import pandas as pd

source = pd.DataFrame({
    'a': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'],
    'b': [28, 55, 43, 91, 81, 53, 19, 87, 52]
})

chart = alt.Chart(source).mark_bar().encode(
    x='a',
    y='b'
)

jchart = alt.JupyterChart(chart)
jchart

Столбчатая диаграмма с буквами от A до I на оси x

Обновление диаграмм

Свойству chart объекта JupyterChart можно присвоить новый экземпляр диаграммы. Он сразу заменит предыдущую диаграмму:

jchart.chart = chart.mark_bar(color="crimson", cornerRadius=10)

Параметры: переменные и выборы

Как описано в разделе интерактивности, грамматика интерактивности Vega-Altair основана на параметрах. Переменные параметры хранят простое значение, а параметры выбора связывают действия пользователя с запросами к данным.

Класс JupyterChart предоставляет доступ из Python к обоим типам параметров.

Переменные параметры

JupyterChart позволяет получать значения переменных параметров, наблюдать за их изменениями, задавать значения и связывать параметры с другими виджетами.

Доступ к переменным параметрам

Переменные параметры диаграммы хранятся в свойстве params экземпляра JupyterChart. К отдельным именованным параметрам можно обращаться как к обычным атрибутам. В этом примере привязка связывает параметр cutoff с ползунком. Текущее значение доступно через jchart.params.cutoff:

import altair as alt
import pandas as pd
import numpy as np

rand = np.random.RandomState(42)

df = pd.DataFrame({
    'xval': range(100),
    'yval': rand.randn(100).cumsum()
})

slider = alt.binding_range(min=0, max=100, step=1)
cutoff = alt.param(name="cutoff", bind=slider, value=50)
predicate = alt.datum.xval < cutoff

chart = alt.Chart(df).mark_point().encode(
    x='xval',
    y='yval',
    color=alt.when(predicate).then(alt.value("red")).otherwise(alt.value("blue")),
).add_params(
    cutoff
)
jchart = alt.JupyterChart(chart)
jchart

Наблюдение за переменными параметрами

Метод observe свойства params регистрирует функцию обратного вызова, которая выполняется при изменении параметра. Здесь простая функция выводит значение параметра cutoff:

def on_cutoff_change(change):
    print(change.new)

jchart.params.observe(on_cutoff_change, ["cutoff"])

Задание значений переменных параметров

Значение переменного параметра можно изменить из Python, присвоив его соответствующему атрибуту params. Ниже значение cutoff обновляется присваиванием атрибуту jchart.params.cutoff:

Связывание переменных параметров

Поскольку params — объект traitlets, функция link из ipywidgets позволяет связать параметры с другими виджетами ipywidgets. Здесь переменный параметр cutoff связан со значением IntSlider:

from ipywidgets import IntSlider, link
slider = IntSlider(23, min=0, max=100)
link((slider, "value"), (jchart.params, "cutoff"))
slider

Если виджет ipywidgets уже связан с переменным параметром Vega-Altair, дополнительно привязывать этот параметр к виджету Vega-Altair не требуется. В следующем варианте значение cutoff управляется только виджетом IntSlider из ipywidgets:

import pandas as pd
import numpy as np

rand = np.random.RandomState(42)

df = pd.DataFrame({
    'xval': range(100),
    'yval': rand.randn(100).cumsum()
})

cutoff = alt.param(name="cutoff", value=50)
predicate = alt.datum.xval < cutoff

chart = alt.Chart(df).mark_point().encode(
    x='xval',
    y='yval',
    color=alt.when(predicate).then(alt.value("red")).otherwise(alt.value("blue"))
).add_params(
    cutoff
)
jchart = alt.JupyterChart(chart)
jchart

Параметры выбора

JupyterChart позволяет получать параметры выбора и наблюдать за ними. Для доступа из Python выборы делятся на три типа: точечные, индексные и интервальные. Им соответствуют классы Python PointSelection, IndexSelection и IntervalSelection.

Экземпляры этих классов доступны как атрибуты свойства selections объекта JupyterChart.

Точечные выборы

Класс PointSelection хранит текущее состояние точечного выбора Vega-Altair, созданного через alt.selection_point(), если задано fields или encodings. Распространённый пример — выбор с encodings=["color"], привязанный к легенде:

import altair as alt
from altair.datasets import data

source = data.cars()
brush = alt.selection_point(name="point", encodings=["color"], bind="legend")

chart = alt.Chart(source).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color=alt.when(brush).then("Origin:N").otherwise(alt.value("grey")),
).add_params(brush)

jchart = alt.JupyterChart(chart)
jchart

Экземпляр PointSelection доступен через jchart.selections.point, где point — значение аргумента name функции alt.selection_point.

Свойство jchart.selections.point.value содержит список словарей: каждый элемент описывает одну выбранную точку. Этот список можно преобразовать в строку запроса pandas query:

filter = " or ".join([
    " and ".join([
        f"`{col}` == {repr(val)}" for col, val in sel.items()
    ])
    for sel in jchart.selections.point.value
])
source.query(filter)

Например, при выборе элементов легенды Japan и Europe строка filter принимает значение "`Origin` == 'Japan' or `Origin` == 'Europe'". Выражение source.query(filter) возвращает DataFrame pandas со строками исходного набора source, которые входят в выбор.

Индексные выборы

Класс IndexSelection хранит текущее состояние точечного выбора Vega-Altair, созданного через alt.selection_point(), если не заданы ни fields, ни encodings. В этом случае свойство value содержит список индексов выбранных строк. Эти индексы можно передать атрибуту iloc объекта DataFrame pandas, чтобы получить выбранные строки исходного набора данных:

import altair as alt
from altair.datasets import data

source = data.cars()
brush = alt.selection_point(name="point")

chart = alt.Chart(source).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color=alt.when(brush).then("Origin:N").otherwise(alt.value("grey")),
).add_params(brush)

jchart = alt.JupyterChart(chart)
jchart

Внимание. Возвращаемые индексы соответствуют исходному DataFrame только в диаграммах без агрегации. Если диаграмма содержит агрегацию, задайте в alt.selection_point аргумент fields или encodings. Тогда JupyterChart будет содержать PointSelection, а не IndexSelection.

Интервальные выборы

Класс IntervalSelection хранит текущее состояние интервального выбора Vega-Altair, созданного через alt.selection_interval(). Свойство value содержит словарь, сопоставляющий имена столбцов с интервалами выбора:

import altair as alt
from altair.datasets import data

source = data.cars()
brush = alt.selection_interval(name="interval")

chart = alt.Chart(source).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color=alt.when(brush).then("Cylinders:O").otherwise(alt.value("grey")),
).add_params(brush)

jchart = alt.JupyterChart(chart)
jchart

Словарь выбора можно преобразовать в строку запроса pandas query:

filter = " and ".join([
    f"{v[0]} <= `{k}` <= {v[1]}"
    for k, v in jchart.selections.interval.value.items()
])
source.query(filter)

Например, при выборе по оси x от 120 до 160 и по оси y от 25 до 35 свойство jchart.selections.interval.value принимает значение {'Horsepower': [120, 160], 'Miles_per_Gallon': [25, 35]}. Строка filter будет равна "120 <= `Horsepower` <= 160 and 25 <= `Miles_per_Gallon` <= 35". Выражение source.query(filter) возвращает DataFrame pandas со строками source, которые входят в выбор.

Наблюдение за выборами

Как и для переменных параметров, метод observe свойства selections регистрирует функцию обратного вызова, выполняемую при изменении выбора. В этом примере функция реагирует на изменения интервального выбора, фильтрует исходный DataFrame по его значениям и отображает таблицу в HTML. Виджет VBox из ipywidgets располагает диаграмму и таблицу друг под другом:

import ipywidgets
from IPython.display import display
from ipywidgets import HTML, VBox

import altair as alt
from altair.datasets import data

source = data.cars()
brush = alt.selection_interval(name="brush")

chart_widget = alt.JupyterChart(alt.Chart(source).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color=alt.when(brush).then("Cylinders:O").otherwise(alt.value("grey")),
).add_params(brush))

table_widget = HTML(value=source.iloc[:0].to_html())

def on_select(change):
    sel = change.new.value
    if sel is None or 'Horsepower' not in sel:
        filtered = source.iloc[:0]
    else:
        filter_query = (
            f"{sel['Horsepower'][0]} <= `Horsepower` <= {sel['Horsepower'][1]} and "
            f"{sel['Miles_per_Gallon'][0]} <= `Miles_per_Gallon` <= {sel['Miles_per_Gallon'][1]}"
        )
        filtered = source.query(filter_query)

    table_widget.value = filtered.to_html()

chart_widget.selections.observe(on_select, ["brush"])

VBox([chart_widget, table_widget])

Использование без подключения к интернету

По умолчанию виджет JupyterChart динамически загружает зависимости JavaScript из CDN, поэтому ему требуется интернет. Начиная с Altair 5.3, JupyterChart может загружать эти зависимости из пакета vl-convert-python, что позволяет работать без подключения к сети.

Автономный режим включается методом класса JupyterChart.enable_offline:

import altair as alt
alt.JupyterChart.enable_offline()

Достаточно вызвать его один раз: после этого все отображаемые объекты JupyterChart будут работать в автономном режиме.

Для отключения этого режима передайте тому же методу offline=False:

import altair as alt
alt.JupyterChart.enable_offline(offline=False)

Ограничения

Задание состояния выбора

В настоящее время задавать состояние выбора из Python нельзя.