编码#

创建有意义可视化的关键在于将数据属性映射到视觉属性,以便有效传达信息。在 Altair 中,这种将视觉属性映射到数据列的过程称为编码,通常通过 Chart.encode() 方法来表达。

例如,这里我们将使用四种可用的编码通道(详情请参阅通道)来可视化 cars 数据集:x(x 轴值),y(y 轴值),color(标记颜色)和 shape(点标记形状)

import altair as alt
from vega_datasets import data


cars = data.cars()

alt.Chart(cars).mark_point().encode(
    x='Horsepower',
    y='Miles_per_Gallon',
    color='Origin',
    shape='Origin'
)

通道选项#

每个编码通道都接受多个通道选项(详情请参阅通道选项),这些选项可用于进一步配置图表。Altair 5.0 引入了一种基于方法的语法来设置通道选项,作为传统基于属性的语法(详见基于属性的语法)更方便的替代方案(但如果您愿意,仍然可以使用基于属性的语法)。

注意

随着 Altair 5 的发布,文档已更新,倾向于推荐使用基于方法的语法。画廊示例中仍包含基于属性的语法以及基于方法的语法。

基于方法的语法#

基于方法的语法用方法取代了关键字参数。例如,x 通道编码的 axis 选项传统上是使用 axis 关键字参数设置的:x=alt.X('Horsepower', axis=alt.Axis(tickMinStep=50))。要使用基于方法的语法定义相同的 X 对象,我们可以使用更简洁的 x=alt.X('Horsepower').axis(tickMinStep=50)

同样的技术适用于所有编码通道和所有通道选项。例如,请注意我们如何对 y 通道的 title 选项进行类似的更改。以下代码生成与先前示例相同的图表。

alt.Chart(cars).mark_point().encode(
    alt.X('Horsepower').axis(tickMinStep=50),
    alt.Y('Miles_per_Gallon').title('Miles per Gallon'),
    color='Origin',
    shape='Origin'
)

这些选项设置方法也可以链式调用,如下所示,我们使用相应的方法(axisbinscale)设置 x 通道的 axisbinscale 选项。我们可以将 x 的定义分解到多行以提高可读性。(由于 encode 的外围括号,这是有效的语法。)

alt.Chart(cars).mark_point().encode(
    alt.X('Horsepower')
        .axis(ticks=False)
        .bin(maxbins=10)
        .scale(domain=(30,300), reverse=True),
    alt.Y('Miles_per_Gallon').title('Miles per Gallon'),
    color='Origin',
    shape='Origin'
)

基于属性的语法#

上面章节中的两个示例使用传统的基于属性的语法如下所示

alt.Chart(cars).mark_point().encode(
    alt.X('Horsepower', axis=alt.Axis(tickMinStep=50)),
    alt.Y('Miles_per_Gallon', title="Miles per Gallon"),
    color='Origin',
    shape='Origin'
)

对于大量使用通道选项的规范,基于属性的语法可能会变得相当冗长

alt.Chart(cars).mark_point().encode(
    alt.X(
        'Horsepower',
        axis=alt.Axis(ticks=False),
        bin=alt.Bin(maxbins=10),
        scale=alt.Scale(domain=(30,300), reverse=True)
    ),
    alt.Y('Miles_per_Gallon', title='Miles per Gallon'),
    color='Origin',
    shape='Origin'
)

编码数据类型#

任何映射的细节都取决于数据的类型。Altair 识别出五种主要数据类型

数据类型

缩写代码

描述

quantitative (定量)

Q

连续实数值数量

ordinal (有序)

O

离散有序数量

nominal (名义)

N

离散无序类别

temporal (时间)

T

时间或日期值

geojson

G

地理形状

对于指定为 DataFrame 的数据,Altair 可以自动确定每个编码的正确数据类型,并创建适当的比例尺和图例来表示数据。

如果对于作为 DataFrame 输入的数据未指定类型,Altair 会对任何数值数据默认为 quantitative,对日期/时间数据默认为 temporal,对字符串数据默认为 nominal,但请注意,这些默认设置绝非总是正确的选择!

这些类型可以用长格式表示,使用通道编码类,如 XY,也可以使用下面讨论的缩写语法以短格式表示。例如,以下两种指定类型的方法将产生相同的图表

alt.Chart(cars).mark_point().encode(
    x='Acceleration:Q',
    y='Miles_per_Gallon:Q',
    color='Origin:N'
)
alt.Chart(cars).mark_point().encode(
    alt.X('Acceleration', type='quantitative'),
    alt.Y('Miles_per_Gallon', type='quantitative'),
    alt.Color('Origin', type='nominal')
)

缩写形式,x="name:Q",在进行快速数据探索时非常有用,因为它减少了模板代码。长格式,alt.X('name', type='quantitative'),在需要使用通道选项(如分箱、坐标轴和比例尺)对编码进行更精细调整时非常有用。

为您的数据指定正确的类型非常重要,因为它会影响 Altair 在最终图表中表示您的编码的方式。

数据类型对颜色比例尺的影响#

作为一个例子,这里我们将用三种不同的方式表示相同的数据,颜色编码为 quantitative(定量)、ordinal(有序)和 nominal(名义)类型,使用三个水平连接的图表(参见水平连接

base = alt.Chart(cars).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
).properties(
    width=140,
    height=140
)

alt.hconcat(
   base.encode(color='Cylinders:Q').properties(title='quantitative'),
   base.encode(color='Cylinders:O').properties(title='ordinal'),
   base.encode(color='Cylinders:N').properties(title='nominal'),
)

类型规范会影响 Altair 通过 Vega-Lite 决定如何选择颜色比例尺来表示值,并影响是使用离散图例还是连续图例。

数据类型对坐标轴比例尺的影响#

类似地,对于 x 轴和 y 轴编码,数据使用的类型将影响所使用的比例尺和标记的特性。例如,这里显示了一个包含指定年份的整数列,使用 ordinal(有序)、quantitative(定量)和 temporal(时间)比例尺的区别

pop = data.population()

base = alt.Chart(pop).mark_bar().encode(
    alt.Y('mean(people):Q').title('Total population')
).properties(
    width=140,
    height=140
)

alt.hconcat(
    base.encode(x='year:O').properties(title='ordinal'),
    base.encode(x='year:Q').properties(title='quantitative'),
    base.encode(x='year:T').properties(title='temporal')
)

由于定量和时间比例尺上的值没有固有宽度,因此条形图不会填充值之间的整个空间。这些比例尺清楚地显示了缺失年份的数据,这在我们将年份视为有序数据时并不立即显而易见,但这两种情况下的坐标轴格式都不理想。

为了以正确的坐标轴格式(即不带千位分隔符)将四位整数绘制为年份,我们建议首先将整数转换为字符串,然后在 Altair 中指定时间数据类型。虽然也可以使用 .axis(format='i') 更改坐标轴格式,但首选向 Altair 指定适当的数据类型。

pop['year'] = pop['year'].astype(str)

base.mark_bar().encode(x='year:T').properties(title='temporal')

这种行为有时会让新用户感到惊讶,但这强调了在可视化数据时仔细思考数据类型的重要性:适合分类数据的视觉编码可能不适合定量数据或时间数据,反之亦然。

编码缩写#

为了方便起见,Altair 允许在简单的缩写字符串语法中指定变量名以及聚合和类型。这利用了编码数据类型中列出的类型缩写代码以及分箱与聚合中列出的聚合名称。下表显示了缩写规范及其长格式等效项的示例

缩写

等效长格式

x='name'

alt.X('name')

x='name:Q'

alt.X('name', type='quantitative')

x='sum(name)'

alt.X('name', aggregate='sum')

x='sum(name):Q'

alt.X('name', aggregate='sum', type='quantitative')

x='count():Q'

alt.X(aggregate='count', type='quantitative')

在列名中转义特殊字符#

鉴于 Altair 使用 : 作为特殊字符来指示编码数据类型,您可能会想知道当您的数据中的列名包含冒号时会发生什么。在这种情况下,您需要重命名列或转义冒号。这对于其他特殊字符也适用,例如 .[],它们用于在某些数据结构中访问嵌套属性。

当列名中包含特殊字符时,推荐的做法是重命名您的列。例如,在 pandas 中,您可以通过 df.rename(columns=lambda x: x.replace(':', '_')): 替换为 _。如果您不想重命名列,则需要使用带反斜杠的原始字符串来转义特殊字符

import pandas as pd

source = pd.DataFrame({
    'col:colon': [1, 2, 3],
    'col.period': ['A', 'B', 'C'],
    'col[brackets]': range(3),
})

alt.Chart(source).mark_bar().encode(
    x=r'col\:colon',
    # Remove the backslash in the title
    y=alt.Y(r'col\.period').title('col.period'),
    # Specify the data type
    color=r'col\[brackets\]:N',
)

如上所示,指示数据类型是可选的,就像没有转义字符的列一样。请注意,坐标轴标题默认包含反斜杠,您需要手动设置标题字符串以将其删除。如果您使用长格式语法进行编码,则无需转义冒号,因为类型是显式的,例如 alt.X(field='col:colon', type='quantitative')(但长格式语法中的句号和方括号仍然需要转义,除非它们用于索引嵌套数据结构)。

分箱与聚合#

除了简单的通道编码之外,Altair 的可视化是建立在数据库风格的分组和聚合概念上的;也就是说,是许多数据分析方法基础的分割-应用-组合抽象。

例如,从一维数据集构建直方图涉及根据数据落入的分箱进行分割,使用数据的计数对每个分箱内的结果进行聚合,然后将结果组合成最终图表。

在 Altair 中,这样的操作看起来像这样

alt.Chart(cars).mark_bar().encode(
    alt.X('Horsepower').bin(),
    y='count()'
    # could also use alt.Y(aggregate='count', type='quantitative')
)

注意,这里我们使用表达编码通道的缩写版本(参见编码缩写)以及 count 聚合,这是唯一不需要指定字段的聚合。

类似地,我们可以创建一个二维直方图,例如使用点的大小来表示网格内的计数(有时称为“气泡图”)

alt.Chart(cars).mark_point().encode(
    alt.X('Horsepower').bin(),
    alt.Y('Miles_per_Gallon').bin(),
    size='count()',
)

然而,没有必要将聚合仅限于计数。例如,我们可以类似地创建一个图表,其中每个点的颜色代表第三个数量的平均值,例如加速度

alt.Chart(cars).mark_circle().encode(
    alt.X('Horsepower').bin(),
    alt.Y('Miles_per_Gallon').bin(),
    size='count()',
    color='mean(Acceleration):Q'
)

聚合函数#

除了 countmean 之外,Altair 内置了大量可用的聚合函数

聚合

描述

示例

argmin

包含最小字段值的输入数据对象。

不适用

argmax

包含最大字段值的输入数据对象。

带自定义图例的折线图

average (平均)

字段的平均值。与 mean 相同。

带分层聚合的折线图

count (计数)

组中数据对象的总计数。

简单热力图

distinct (去重计数)

字段值的去重计数。

不适用

max (最大值)

字段的最大值。

带最小值/最大值须的箱线图

mean (平均值)

字段的平均值。

交互式散点图和链接的分层直方图

median (中位数)

字段的中位数

带最小值/最大值须的箱线图

min (最小值)

字段的最小值。

带最小值/最大值须的箱线图

missing (缺失值计数)

null 或 undefined 字段值的计数。

不适用

q1 (第一四分位数)

值的下四分位数边界。

带最小值/最大值须的箱线图

q3 (第三四分位数)

值的上四分位数边界。

带最小值/最大值须的箱线图

ci0

均值自举法 95% 置信区间的下边界。

显示置信区间的排序误差条

ci1

均值自举法 95% 置信区间的上边界。

显示置信区间的排序误差条

stderr (标准误)

字段值的标准误。

不适用

stdev (样本标准差)

字段值的样本标准差。

不适用

stdevp (总体标准差)

字段值的总体标准差。

不适用

sum (总和)

字段值的总和。

流图

valid (有效值计数)

非 null 或 undefined 字段值的计数。

不适用

values (值列表)

组中的数据对象列表。

不适用

variance (样本方差)

字段值的样本方差。

不适用

variancep (总体方差)

字段值的总体方差。

不适用

排序选项#

某些通道接受 sort 选项,该选项决定通道所用比例尺的顺序。默认情况下,比例尺按字母升序排序,除非传入有序的 pandas 分类列(未指定显式类型),在这种情况下,Altair 将使用列的固有顺序对比例尺进行排序。有多种不同的选项可用于更改排序顺序

  • sort='ascending'(默认)将按升序对字段值进行排序。对于字符串数据,这使用标准的字母顺序。

  • sort='descending' 将按降序对字段值进行排序

  • 将编码通道的名称传递给 sort,例如 "x""y",允许按该通道进行排序。可以使用可选的负号前缀进行降序排序。例如 sort='-x' 将按 x 通道降序排序。

  • 序列 传递给 sort 允许您显式设置编码出现的顺序

  • 使用 fieldop 参数指定要排序的字段和聚合操作。

这里有一个使用 barley 数据集在 x 轴上应用这五种不同排序方法的示例

import altair as alt
from vega_datasets import data

barley = data.barley()

base = alt.Chart(barley).mark_bar().encode(
    y='mean(yield):Q',
    color=alt.Color('mean(yield):Q').legend(None)
).properties(width=100, height=100)

# Sort x in ascending order
ascending = base.encode(
    alt.X('site:N').sort('ascending')
).properties(
    title='Ascending'
)

# Sort x in descending order
descending = base.encode(
    alt.X('site:N').sort('descending')
).properties(
    title='Descending'
)

# Sort x in an explicitly-specified order
explicit = base.encode(
    alt.X('site:N').sort(
        ['Duluth', 'Grand Rapids', 'Morris', 'University Farm', 'Waseca', 'Crookston']
    )
).properties(
    title='Explicit'
)

# Sort according to encoding channel
sortchannel = base.encode(
    alt.X('site:N').sort('y')
).properties(
    title='By Channel'
)

# Sort according to another field
sortfield = base.encode(
    alt.X('site:N').sort(field='yield', op='mean')
).properties(
    title='By Yield'
)

alt.concat(
    ascending,
    descending,
    explicit,
    sortchannel,
    sortfield,
    columns=3
)

最后两个图表是相同的,因为默认聚合(参见分箱与聚合)是 mean。为了突出通过通道排序和通过字段排序之间的区别,请考虑以下示例:我们不聚合数据,而是使用 op 参数指定排序时使用的聚合不同于 mean

import altair as alt
from vega_datasets import data

barley = data.barley()
base = alt.Chart(barley).mark_point().encode(
    y='yield:Q',
).properties(width=200)

# Sort according to encoding channel
sortchannel = base.encode(
    alt.X('site:N').sort('y')
).properties(
    title='By Channel'
)

# Sort according to another field
sortfield = base.encode(
    alt.X('site:N').sort(field='yield', op='max')
).properties(
    title='By Max Yield'
)
sortchannel | sortfield

图例排序#

正如上面的示例通过在 XY 编码中指定 sort 来显示坐标轴排序一样,通过在图例中使用的编码(例如颜色、形状、大小等)中指定 sort 可以对图例进行排序。下面我们展示一个使用 Color 编码的示例

alt.Chart(barley).mark_bar().encode(
    alt.X('mean(yield):Q'),
    alt.Y('site:N').sort('x'),
    alt.Color('site:N').sort([
        'Morris', 'Duluth', 'Grand Rapids', 'University Farm', 'Waseca', 'Crookston'
    ])
)

这里 y 轴根据 x 值排序,而颜色图例按指定顺序排序,以 'Morris' 开头。

在下一个示例中,通过指定 fieldoporder,根据所选数据字段和操作对图例进行排序。

alt.Chart(barley).mark_bar().encode(
    alt.X('mean(yield):Q'),
    alt.Y('site:N').sort('x'),
    color=alt.Color('site').sort(field='yield', op='max', order='ascending')
)

Datum 和 Value#

到目前为止,我们总是将编码通道映射到数据集中的一列。然而,有时将其映射到单个常量值也很有用。在 Altair 中,您可以使用以下方式实现:

  • datum,它通过使用与基础数据相同单位的比例尺来编码一个恒定的域值

  • value,它使用绝对单位编码一个恒定的视觉值,例如以像素为单位的精确位置、颜色的名称或 RGB 值、形状的名称等

datum 对于注释特定数据值特别有用。例如,您可以将其与规则标记一起使用,以突出显示阈值(例如,300 美元的股票价格)。

import altair as alt
from vega_datasets import data

source = data.stocks()
base = alt.Chart(source)
lines = base.mark_line().encode(
    x="date:T",
    y="price:Q",
    color="symbol:N"
)
rule = base.mark_rule(strokeDash=[2, 2]).encode(
    y=alt.datum(300)
)

lines + rule

如果在此示例中我们改用 alt.value,我们将规则标记放置在距离图表边框顶部 300 像素的位置,而不是在 300 美元的位置。由于默认图表高度为 300 像素,这将显示虚线刚好在 x 轴线上方

rule = base.mark_rule(strokeDash=[2, 2]).encode(
    y=alt.value(300)
)

lines + rule

如果我们要使用 datum 在 x 轴上突出显示某个年份,我们不能简单地输入整数年份,而是需要将 datumDateTime 一起使用。这里我们还使用 alt.datum("MSFT") 将规则标记的颜色设置为与符号 MSFT 的线条颜色相同。

import altair as alt
from vega_datasets import data

source = data.stocks()
base = alt.Chart(source)
lines = base.mark_line().encode(
    x="date:T",
    y="price:Q",
    color="symbol:N"
)
rule = base.mark_rule(strokeDash=[2, 2]).encode(
    x=alt.datum(alt.DateTime(year=2006)),
    color=alt.datum("MSFT")
)

lines + rule

与映射到数据列类似,使用 datum 时,不同的编码通道可能支持 bandscaleaxislegendformatcondition 属性。但是,不能应用数据转换(例如 aggregatebintimeUnitsort)。

在上例基础上扩展,如果您希望对 rule 标记着色,而无论线条使用何种颜色比例尺,您可以使用 value,例如 alt.value("red")

import altair as alt
from vega_datasets import data

source = data.stocks()
base = alt.Chart(source)
lines = base.mark_line().encode(
    x="date:T",
    y="price:Q",
    color="symbol:N"
)
rule = base.mark_rule(strokeDash=[2, 2]).encode(
    x=alt.datum(alt.DateTime(year=2006)),
    color=alt.value("red")
)

lines + rule

需要注意的是,alt.datumalt.value 不具备 基于方法的语法 中描述的(Altair 5.0 新引入的)用于设置通道选项的基于方法的语法。例如,如果您在 y 通道编码中使用 alt.datum,并且希望使用选项设置方法(例如 scale),则可以使用 YDatum 代替。这是一个简单示例。

import altair as alt

alt.Chart().mark_bar().encode(
    y=alt.YDatum(220).scale(domain=(0,500)),
    color=alt.value("darkkhaki")
)

如果您改为使用 y=alt.datum(220).scale(domain=(0,500)),则会引发 AttributeError,因为 alt.datum(220) 只是返回一个 Python 字典,不包含 scale 属性。如果您坚持使用 alt.datum 生成前面的示例,一种选择是使用 y=alt.datum(220, scale={"domain": (0,500)})。然而,强烈推荐使用 alt.YDatum 方法,而不是这种“手动”向 scale 提供字典的方法。一个好处是,使用 alt.YDatum 方法可以使用 Tab 键补全。例如,在 JupyterLab 等环境中输入 alt.YDatum(220).scale(do 并按下 tab 键,将会提供 domaindomainMaxdomainMiddomainMin 等可能的补全选项。