Altair 内部结构#

本节将提供一些关于Altair API如何与Vega-Lite可视化规范相关的信息,以及您如何利用这些知识更有效地使用该包。

首先,重要的是要意识到,当本质被剥离到核心时,Altair 本身无法渲染可视化。Altair 是一个 API,它仅执行一个非常明确定义的操作:

  • Altair 提供了一种用于生成经过验证的 Vega-Lite 规范的 Python API

就这样。为了将这些规格转换为实际的可视化,需要一个正确设置的前端,但严格来说,这种渲染通常不由Altair包控制。

Altair 图表到 Vega-Lite 规范#

由于 Altair 是关于构建图表规范的基本概念,因此任何图表对象的核心功能是 to_dict()to_json() 方法,它们分别将图表规范输出为 Python 字典或 JSON 字符串。例如,这里是一个简单的散点图,我们可以从中输出 JSON 表示:

import altair as alt
from vega_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))
{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.20.1.json",
  "config": {
    "view": {
      "continuousHeight": 300,
      "continuousWidth": 300
    }
  },
  "data": {
    "url": "https://cdn.jsdelivr.net/npm/vega-datasets@v1.29.0/data/cars.json"
  },
  "encoding": {
    "color": {
      "field": "Origin",
      "type": "nominal"
    },
    "x": {
      "field": "Horsepower",
      "type": "quantitative"
    },
    "y": {
      "field": "Miles_per_Gallon",
      "type": "quantitative"
    }
  },
  "mark": {
    "type": "point"
  }
}

在返回字典或JSON输出之前,Altair会使用jsonschema包对其进行验证,确保其符合Vega-Lite schema。Vega-Lite schema定义了可以出现在Vega-Lite图表规范中的有效属性和值。

手握JSON schema后,可以将其传递给一个库,比如 Vega-Embed,这个库知道如何读取 规范并渲染它所描述的图表,结果是以下可视化:

Click to show code
chart

每当您在 JupyterLab、Jupyter notebook 或其他前端中使用 Altair 时, 是前端扩展从 Altair 图表对象中提取 JSON 输出并将该规范传递给适当的渲染代码。

Altair的低级对象结构#

在Altair中使用的标准API方法(例如 mark_point(), encode(), configure_*(), transform_*() 等) 是包装低级API的高级便利函数。 该低级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图表的典型习惯用法要冗长得多,但它更清楚地显示了Altair的python对象结构与Vega-Lite的架构定义结构之间的映射。

Altair的一大优点是,这个低级对象层次结构并不是手动构建的,而是通过程序生成的,基于Vega-Lite架构,使用你可以在Altair’s repository中找到的generate_schema_wrapper.py脚本。代码的自动生成将描述从vega-lite架构传播到Python类文档字符串,从而在Altair的文档中自动生成API Reference。这意味着,随着Vega-Lite架构的发展,Altair可以非常快速地更新,只有更高级的图表方法需要手动更新。

将Vega-Lite转换为Altair#

牢记这些知识,并进行一些练习,从Vega-Lite规范构建Altair图表是相当简单的。比如,考虑Vega-Lite文档中的简单柱状图示例,它具有以下JSON规范:

{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.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"}
  }
}

在最低级别,我们可以使用 from_json() 类方法从这一串 Vega-Lite JSON 构建 Altair 图表对象:

import altair as alt

alt.Chart.from_json("""
{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.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"}
  }
}
""")

同样,如果您有JSON字符串的Python字典等效项,您可以使用from_dict()方法来构建图表对象:

import altair as alt

alt.Chart.from_dict({
  "$schema": "https://vega.github.io/schema/vega-lite/v5.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 代码,包括从数据值构建 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" 属性通常通过 encode() 方法设置,编码类型通常从简写类型代码计算得出,"transform""config" 属性来自 transform_*()configure_*() 方法,等等。

这种方法是Altair贡献者构建许多初始示例的过程,灵感来自于Vega-Lite示例库。在此级别上熟悉Altair与Vega-Lite之间的映射有助于在Altair的文档薄弱或不完整的地方利用Vega-Lite文档。