演示:探索汽车数据集

我们将从一个演示开始本教程,以激发您进一步学习的兴趣。本节会快速浏览许多概念(例如,数据、标记、编码、聚合、数据类型、选择等)。我们将在本教程后面更深入地探讨这些概念,因此如果感觉进度有点快,请不要担心!

在教程本身中,这将从空白笔记本开始从头完成。然而,为了方便那些想回顾我们现场演示内容的人,我会尽力在此处重现示例和讨论。

1. 导入与数据

我们将从导入 Altair 包开始

import altair as alt

现在我们将使用 vega_datasets 包来加载一个示例数据集

from vega_datasets import data

cars = data.cars()
cars.head()
名称 每加仑英里数 气缸数 排量 马力 重量(磅) 加速度 年份 产地
0 chevrolet chevelle malibu 18.0 8 307.0 130.0 3504 12.0 1970-01-01 USA
1 buick skylark 320 15.0 8 350.0 165.0 3693 11.5 1970-01-01 USA
2 plymouth satellite 18.0 8 318.0 150.0 3436 11.0 1970-01-01 USA
3 amc rebel sst 16.0 8 304.0 150.0 3433 12.0 1970-01-01 USA
4 ford torino 17.0 8 302.0 140.0 3449 10.5 1970-01-01 USA

请注意,此数据采用列式格式:也就是说,每列包含一个数据点的属性,每行包含数据的单个实例(此处指汽车的单个品牌&型号)。

2. 零维、一维和二维图表

使用 Altair,我们可以开始探索此数据。

最基本的图表包含数据集,以及一个代表每一行的标记

alt.Chart(cars).mark_point()

这是一个相当愚蠢的图表,因为它包含 406 个点,全部叠加在一起。

为了使其更有趣,我们需要将数据的列编码到图表的视觉特征中(例如,x 位置、y 位置、大小、颜色等)。

让我们使用 encode() 方法在 x 轴上编码每加仑英里数

alt.Chart(cars).mark_point().encode(
    x='Miles_per_Gallon'
)

这稍微好一些,但 point 标记可能不是适合这种一维图表的最佳选择。

让我们改用 tick 标记

alt.Chart(cars).mark_tick().encode(
    x='Miles_per_Gallon'
)

或者我们可以通过同时编码 y 值将其扩展为二维图表。我们将回到使用 point 标记,并将 Horsepower 放在 y 轴上

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

3. 简单交互

Altair 最棒的功能之一是它提供的交互语法。最简单的交互类型是沿图表平移和缩放的能力;Altair 提供了一个快捷方式,可以通过 interactive() 方法启用此功能

alt.Chart(cars).mark_point().encode(
    x='Miles_per_Gallon',
    y='Horsepower'
).interactive()

这允许您通过点击和拖动,以及使用计算机的滚动/缩放行为来放大和缩小图表。

我们稍后将看到其他交互。

4. 第三个维度:颜色

二维图表允许我们编码数据的两个维度。让我们看看如何使用颜色编码第三个维度

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

请注意,当我们对颜色使用分类值时,它会为分类数据选择适当的颜色映射。

让我们看看使用连续颜色值会发生什么

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

连续颜色会生成适合连续数据的颜色比例。

介于两者之间的情况呢:有序分类,比如气缸数?

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

Altair 仍然选择一个连续值,因为气缸数是数值型的。

我们可以通过指定数据应被视为离散有序值来改进这一点;我们可以在编码后添加 ":O"(“O”代表“序数”或“有序分类”)来实现

alt.Chart(cars).mark_point().encode(
    x='Miles_per_Gallon',
    y='Horsepower',
    color='Cylinders:O'
)

现在我们获得了一个带有有序颜色映射的离散图例。

5. 分箱与聚合

让我们快速回到我们的每加仑英里数的一维图表

alt.Chart(cars).mark_tick().encode(
    x='Miles_per_Gallon',
)

表示此数据的另一种方式是创建直方图:对 x 数据进行分箱并在 y 轴上显示计数。在许多绘图库中,这是通过 hist() 等特殊方法完成的。在 Altair 中,这种分箱和聚合是声明性 API 的一部分。

为了超越简单的字段名称,我们使用 alt.X() 进行 x 编码,并使用 'count()' 进行 y 编码

alt.Chart(cars).mark_bar().encode(
    x=alt.X('Miles_per_Gallon', bin=True),
    y='count()'
)

如果我们想对分箱有更多控制,我们可以使用 alt.Bin 来调整分箱参数

alt.Chart(cars).mark_bar().encode(
    x=alt.X('Miles_per_Gallon', bin=alt.Bin(maxbins=30)),
    y='count()'
)

如果我们应用另一个编码(例如 color),数据将在每个分箱内自动分组

alt.Chart(cars).mark_bar().encode(
    x=alt.X('Miles_per_Gallon', bin=alt.Bin(maxbins=30)),
    y='count()',
    color='Origin'
)

如果您更喜欢为每个类别单独绘制图表,column 编码可以提供帮助

alt.Chart(cars).mark_bar().encode(
    x=alt.X('Miles_per_Gallon', bin=alt.Bin(maxbins=30)),
    y='count()',
    color='Origin',
    column='Origin'
)

分箱和聚合在二维中也有效;我们可以使用 rect 标记并使用颜色可视化计数

alt.Chart(cars).mark_rect().encode(
    x=alt.X('Miles_per_Gallon', bin=True),
    y=alt.Y('Horsepower', bin=True),
    color='count()'
)

聚合不仅仅是简单的计数;我们还可以在每个分箱内聚合并计算第三个数量的平均值

alt.Chart(cars).mark_rect().encode(
    x=alt.X('Miles_per_Gallon', bin=True),
    y=alt.Y('Horsepower', bin=True),
    color='mean(Weight_in_lbs)'
)

6. 时间序列&分层

到目前为止,我们一直在忽略 date 列,但查看时间趋势(例如,每加仑英里数)很有趣

alt.Chart(cars).mark_point().encode(
    x='Year',
    y='Miles_per_Gallon'
)

每年都有一定数量的汽车,并且数据存在很多重叠。我们可以通过在每个 x 值处绘制平均值来稍微整理一下

alt.Chart(cars).mark_line().encode(
    x='Year',
    y='mean(Miles_per_Gallon)',
)

或者,我们可以将标记更改为 area,并使用 ci0ci1 标记来绘制平均值估计的置信区间

alt.Chart(cars).mark_area().encode(
    x='Year',
    y='ci0(Miles_per_Gallon)',
    y2='ci1(Miles_per_Gallon)'
)

让我们稍微调整一下这个图表:增加一些不透明度,按产地国家着色,加宽一点宽度,并添加一个更清晰的轴标题

alt.Chart(cars).mark_area(opacity=0.3).encode(
    x=alt.X('Year', timeUnit='year'),
    y=alt.Y('ci0(Miles_per_Gallon)', axis=alt.Axis(title='Miles per Gallon')),
    y2='ci1(Miles_per_Gallon)',
    color='Origin'
).properties(
    width=800
)

最后,我们可以使用 Altair 的分层 API 将代表平均值的折线图叠加在代表置信区间的面积图之上

spread = alt.Chart(cars).mark_area(opacity=0.3).encode(
    x=alt.X('Year', timeUnit='year'),
    y=alt.Y('ci0(Miles_per_Gallon)', axis=alt.Axis(title='Miles per Gallon')),
    y2='ci1(Miles_per_Gallon)',
    color='Origin'
).properties(
    width=800
)

lines = alt.Chart(cars).mark_line().encode(
    x=alt.X('Year', timeUnit='year'),
    y='mean(Miles_per_Gallon)',
    color='Origin'
).properties(
    width=800
)

spread + lines

7. 交互性:选择

让我们回到散点图,看看 Altair 提供的其他类型的交互性

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

回想一下,您可以在图表末尾添加 interactive() 来启用最基本的交互式比例

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

Altair 提供了一个通用的 selection API 用于创建交互式图表;例如,此处我们创建一个区间选择

interval = alt.selection_interval()

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

当前此选择实际上什么也不做,但我们可以通过根据此选择条件化颜色来改变这一点

interval = alt.selection_interval()

alt.Chart(cars).mark_point().encode(
    x='Miles_per_Gallon',
    y='Horsepower',
    color=alt.condition(interval, 'Origin', alt.value('lightgray'))
).add_selection(
    interval
)

此选择 API 的优点在于它可以自动应用于任何复合图表;例如,此处我们可以水平连接两个图表,由于它们都具有相同的选择,它们都会做出适当的响应

interval = alt.selection_interval()

base = alt.Chart(cars).mark_point().encode(
    y='Horsepower',
    color=alt.condition(interval, 'Origin', alt.value('lightgray')),
    tooltip='Name'
).add_selection(
    interval
)

base.encode(x='Miles_per_Gallon') | base.encode(x='Acceleration')

我们还可以使用选择做更复杂的事情。例如,让我们创建一个按产地划分的汽车数量直方图,并将其堆叠在我们的散点图上

interval = alt.selection_interval()

base = alt.Chart(cars).mark_point().encode(
    y='Horsepower',
    color=alt.condition(interval, 'Origin', alt.value('lightgray')),
    tooltip='Name'
).add_selection(
    interval
)

hist = alt.Chart(cars).mark_bar().encode(
    x='count()',
    y='Origin',
    color='Origin'
).properties(
    width=800,
    height=80
).transform_filter(
    interval
)

scatter = base.encode(x='Miles_per_Gallon') | base.encode(x='Acceleration')

scatter & hist

本演示涵盖了 Altair 的许多可用组件。在接下来的部分中,我们将更系统地深入研究每个组件。