Внутреннее устройство Altair

Этот раздел объясняет, как API Altair связан со спецификацией визуализации Vega-Lite и как эти знания помогают эффективнее пользоваться библиотекой.

Прежде всего, сам Altair не отрисовывает визуализации. Его основная задача точно определена:

  • Altair предоставляет Python API для создания спецификаций Vega-Lite с проверкой их корректности.

Чтобы превратить спецификацию в визуализацию, нужен правильно настроенный интерфейс отображения. Строго говоря, отрисовка обычно не управляется самим пакетом Altair.

Из диаграммы Altair в спецификацию Vega-Lite

Поскольку Altair создаёт спецификации диаграмм, основные методы каждого объекта — Chart.to_dict и Chart.to_json. Они возвращают спецификацию как словарь Python или строку JSON соответственно. Например, создадим простую диаграмму рассеяния и выведем JSON:

import altair as alt
from altair.datasets import data

chart = alt.Chart(data.cars.url).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color='Origin:N',
).configure_view(
    continuousHeight=300,
    continuousWidth=300,
)

print(chart.to_json(indent=2))

Перед возвратом словаря или JSON Altair проверяет результат по схеме Vega-Lite с помощью пакета jsonschema. Схема задаёт допустимые атрибуты и значения спецификации диаграммы.

Полученную JSON-спецификацию можно передать библиотеке, например Vega-Embed, которая умеет прочитать её и отрисовать описанную диаграмму:

При работе в JupyterLab, Jupyter Notebook и других интерфейсах их расширения извлекают JSON из объекта диаграммы Altair и передают его соответствующему коду отрисовки.

Низкоуровневая структура объектов Altair

Обычные методы API Altair — Chart.mark_point, Chart.encode, configure_*(), transform_*() и другие — это удобные высокоуровневые функции, оборачивающие низкоуровневый API. Последний представляет собой иерархию объектов Python, соответствующую иерархии определения JSON-схемы.

Например, вместо удобных методов можно построить предыдущую диаграмму напрямую из низкоуровневых объектов:

alt.Chart(
    data=alt.UrlData(
        url='https://vega.github.io/vega-datasets/data/cars.json'
    ),
    mark='point',
    encoding=alt.FacetedEncoding(
        x=alt.PositionFieldDef(
            field='Horsepower',
            type='quantitative'
        ),
        y=alt.PositionFieldDef(
            field='Miles_per_Gallon',
            type='quantitative'
        ),
        color=alt.StringFieldDefWithCondition(
            field='Origin',
            type='nominal'
        )
    ),
    config=alt.Config(
        view=alt.ViewConfig(
            continuousHeight=300,
            continuousWidth=300
        )
    )
)

Запись гораздо длиннее обычного кода Altair, но яснее показывает соответствие между структурой объектов Python и структурой схемы Vega-Lite.

Эта иерархия не создаётся вручную: она генерируется программно из схемы Vega-Lite скриптом generate_schema_wrapper.py из репозитория Altair.

При генерации описания из схемы Vega-Lite попадают в строки документации классов Python, по которым затем автоматически создаётся справочник API. Благодаря этому Altair можно быстро обновлять вслед за схемой Vega-Lite; вручную достаточно обновить высокоуровневые методы диаграмм.

Преобразование Vega-Lite в Altair

С этими знаниями и небольшой практикой построить диаграмму Altair по спецификации Vega-Lite довольно просто. Рассмотрим простую столбчатую диаграмму из документации Vega-Lite со следующей JSON-спецификацией:

{
  "$schema": "https://vega.github.io/schema/vega-lite/v6.json",
  "description": "A simple bar chart with embedded data.",
  "data": {
    "values": [
      {"a": "A","b": 28}, {"a": "B","b": 55}, {"a": "C","b": 43},
      {"a": "D","b": 91}, {"a": "E","b": 81}, {"a": "F","b": 53},
      {"a": "G","b": 19}, {"a": "H","b": 87}, {"a": "I","b": 52}
    ]
  },
  "mark": {"type": "bar"},
  "encoding": {
    "x": {"field": "a", "type": "ordinal"},
    "y": {"field": "b", "type": "quantitative"}
  }
}

На самом низком уровне метод класса Chart.from_json создаёт объект диаграммы Altair из строки JSON Vega-Lite:

import altair as alt

alt.Chart.from_json("""
{
  "$schema": "https://vega.github.io/schema/vega-lite/v6.json",
  "description": "A simple bar chart with embedded data.",
  "data": {
    "values": [
      {"a": "A","b": 28}, {"a": "B","b": 55}, {"a": "C","b": 43},
      {"a": "D","b": 91}, {"a": "E","b": 81}, {"a": "F","b": 53},
      {"a": "G","b": 19}, {"a": "H","b": 87}, {"a": "I","b": 52}
    ]
  },
  "mark": {"type": "bar"},
  "encoding": {
    "x": {"field": "a", "type": "ordinal"},
    "y": {"field": "b", "type": "quantitative"}
  }
}
""")

Если вместо строки у вас соответствующий словарь Python, используйте Chart.from_dict:

import altair as alt

alt.Chart.from_dict({
  "$schema": "https://vega.github.io/schema/vega-lite/v6.json",
  "description": "A simple bar chart with embedded data.",
  "data": {
    "values": [
      {"a": "A","b": 28}, {"a": "B","b": 55}, {"a": "C","b": 43},
      {"a": "D","b": 91}, {"a": "E","b": 81}, {"a": "F","b": 53},
      {"a": "G","b": 19}, {"a": "H","b": 87}, {"a": "I","b": 52}
    ]
  },
  "mark": {"type": "bar"},
  "encoding": {
    "x": {"field": "a", "type": "ordinal"},
    "y": {"field": "b", "type": "quantitative"}
  }
})

С небольшими дополнительными усилиями и аккуратным копированием можно вручную переписать эту спецификацию в более привычный код Altair, в том числе создав DataFrame pandas из значений:

import altair as alt
import pandas as pd

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

alt.Chart(data).mark_bar().encode(
    x='a:O',
    y='b:Q'
)

Важно понимать, что свойства "encoding" обычно задаются методом Chart.encode, типы кодирования выводятся из сокращённых кодов типов, а свойства "transform" и "config" соответствуют методам transform_*() и configure_*().

Таким способом участники проекта создали многие первые примеры галереи Altair, опираясь на галерею Vega-Lite. Знание соответствий между Altair и Vega-Lite помогает использовать документацию Vega-Lite там, где материалы Altair недостаточно подробны или неполны.