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.cn/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 模式对其进行验证。Vega-Lite 模式定义了 Vega-Lite 图表规范中可以出现的有效属性和值。

有了 JSON 模式,就可以将其传递给一个知道如何读取规范并渲染图表(规范描述的图表)的库,例如 Vega-Embed,结果就是下面的可视化效果

点击显示代码
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 的一个很好的特点是,这种低级对象层次结构不是手动构建的,而是使用 generate_schema_wrapper.py 脚本从 Vega-Lite 模式程序化生成的,你可以在Altair 的仓库中找到该脚本。这种代码的自动生成将 Vega-Lite 模式中的描述传播到 Python 类的文档字符串中,然后 Altair 文档中的 API 参考也由此自动生成。这意味着随着 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 贡献者构建 Altair 示例库中许多初始示例的过程,他们从 Vega-Lite 示例库中汲取灵感。熟悉 Altair 和 Vega-Lite 在这个层面的映射关系,对于在 Altair 文档薄弱或不完整的地方利用 Vega-Lite 文档非常有益。