参数、条件和筛选器#

参数#

参数是 Altair 中交互的构建块。参数有两种类型:变量选择。我们将通过一系列示例来介绍这些概念。

注意

此内容在 Altair 5 版本发布后发生了很大变化。

变量:复用值#

变量参数允许只定义一个值一次,然后在图表的其余部分重复使用。这是一个使用 cars 数据集创建的简单散点图

import altair as alt
from vega_datasets import data

cars = data.cars.url

alt.Chart(cars).mark_circle().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color='Origin:N'
)

使用 param() 函数创建变量参数。在这里,我们使用 value 属性创建了一个默认值为 0.1 的参数

op_var = alt.param(value=0.1)

为了在图表规范中使用此变量,我们使用 add_params() 方法将其显式添加到图表中,然后可以在图表规范中引用该变量。在这里,我们使用 op_var 参数设置不透明度。

op_var = alt.param(value=0.1)

alt.Chart(cars).mark_circle(opacity=op_var).encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color='Origin:N'
).add_params(
    op_var
)

有理由问所有这些努力是否必要。这里有一种更自然的方式来实现同样的功能,并且避免使用 param()add_params

op_var2 = 0.1

alt.Chart(cars).mark_circle(opacity=op_var2).encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color='Origin:N'
)

使用 param() 的好处直到我们加入一个额外组件后才变得明显。在下面的示例中,我们使用参数的 bind 属性,以便参数绑定到一个输入元素。在本例中,该输入元素是一个滑块控件。

slider = alt.binding_range(min=0, max=1, step=0.05, name='opacity:')
op_var = alt.param(value=0.1, bind=slider)

alt.Chart(cars).mark_circle(opacity=op_var).encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color='Origin:N'
).add_params(
    op_var
)

现在,我们可以使用滑块动态更改图表中点的不透明度。您将在 绑定和控件 部分了解更多关于将参数绑定到控件等输入元素的信息。

注意

Altair 交互性的一个值得注意的方面是这些效果完全在 Web 浏览器中控制。这意味着您可以将图表另存为 HTML 文件,并与您的同事共享,他们无需安装 Python 即可通过浏览器访问交互功能。

选择:捕获图表交互#

选择参数定义了由用户通过交互操作图表(例如,通过鼠标点击或拖动)驱动的数据查询。选择有两种类型:selection_interval()selection_point()

在这里,我们将创建一个简单图表,然后向其添加一个选择区间。我们可以通过 param(select="interval") 创建一个选择区间,但使用更短的 selection_interval 更方便。

这是一个使用 cars 数据集创建的简单散点图

import altair as alt
from vega_datasets import data

cars = data.cars.url

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

首先,我们将使用 selection_interval() 函数创建一个区间选择(区间选择也称为“刷选”)

brush = alt.selection_interval()

现在我们可以通过 add_params 将此选择区间添加到我们的图表中

alt.Chart(cars).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color='Origin:N'
).add_params(
    brush
)

上面的结果是一个图表,允许您点击并拖动以创建选择区域,并在创建区域后移动该区域。

到目前为止,这个例子与我们在变量示例中所做非常相似:我们使用 brush = alt.selection_interval() 创建了一个选择参数,并使用 add_params 将该参数附加到图表中。一个不同之处在于,在这里我们尚未定义图表应该如何响应选择;您将在下一节中学习这一点。

条件#

注意

此内容在 Altair 5.5.0 版本发布后发生了很大变化。when()5.4.0 中引入,应优先于 condition() 使用。

上面的例子很简洁,但选择区间实际上还没有做任何事情。为了使图表响应此选择,我们需要在图表规范中引用 brush。在这里,我们将使用 when() 函数创建一个条件颜色编码

conditional = alt.when(brush).then("Origin:N").otherwise(alt.value("lightgray"))

alt.Chart(cars).mark_point().encode(
    x="Horsepower:Q",
    y="Miles_per_Gallon:Q",
    color=conditional,
).add_params(
    brush
)

如您所见,点的颜色现在根据它们是在选择内部还是外部而改变。上面我们使用选择参数 brush 作为谓词(评估为 TrueFalse 的事物)。

这由我们的定义 conditional 控制

conditional = alt.when(brush).then("Origin:N").otherwise(alt.value("lightgray"))

落入选择范围内的数据点评估为 True,落在选择范围外的数据点评估为 False"Origin:N" 指定了如何为落入选择范围内的点着色,而 alt.value('lightgray') 指定了外部的点应赋予一个恒定的颜色值。

理解 when()#

模块的 when-then-otherwise 语法直接受到 polars.when 的启发,类似于 Python 中编写的 if-else 语句

# alt.when(brush)
if brush:
    # .then("Origin:N")
    color = "Origin:N"
else:
    # .otherwise(alt.value("lightgray"))
    color = alt.value("lightgray")

省略 .otherwise() 子句将改为使用通道默认值

source = data.cars()
brush = alt.selection_interval()

points = alt.Chart(source).mark_point().encode(
    x="Horsepower",
    y="Miles_per_Gallon",
    color=alt.when(brush).then(alt.value("goldenrod"))
).add_params(
    brush
)

points

多个条件分支(Python 中的 if, elif, ..., elif)通过对 when() 的链式调用来表达。当您了解了不同的选择类型后,将在条件分支中看到包含工作代码的示例。

条件的更高级用法可以在 when() API 参考和这些图表示例中找到

跨图表链接条件#

当选择行为绑定到复合图表中数据的多个视图时,条件编码变得更加强大。例如,在这里我们使用与上面相同的代码创建了一个 Chart,并将此图表的两个版本水平连接起来:一个 x 编码绑定到 "Horsepower",另一个 x 编码绑定到 "Acceleration"

chart = alt.Chart(cars).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color=alt.when(brush).then("Origin:N").otherwise(alt.value("lightgray")),
).properties(
    width=250,
    height=250
).add_params(
    brush
)

chart | chart.encode(x='Acceleration:Q')

由于图表的两个副本引用相同的选择对象,渲染器将跨面板的选择绑定在一起,从而产生一个动态显示,帮助您深入了解数据集中的关系。

每种选择类型都有属性,可以通过这些属性自定义其行为;例如,我们可能希望我们的刷选仅绑定到 "x" 编码,以强调数据中的该特征。我们可以修改刷选定义,而其余代码保持不变

brush = alt.selection_interval(encodings=['x'])

chart = alt.Chart(cars).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color=alt.when(brush).then("Origin:N").otherwise(alt.value("lightgray")),
).properties(
    width=250,
    height=250
).add_params(
    brush
)

chart | chart.encode(x='Acceleration:Q')

您可能已经注意到,选定的点有时会被一些未选定的点遮挡。要将选定的点带到前景,我们可以通过以下编码更改它们的布局顺序

hover = alt.selection_point(on='pointerover', nearest=True, empty=False)
order = alt.when(hover).then(alt.value(1)).otherwise(alt.value(0))

筛选器#

使用选择参数筛选数据的方式与在 when() 中使用它的方式非常相似。例如,在 transform_filter(brush) 中,我们再次使用选择参数 brush 作为谓词。评估为 True 的数据点(即位于选择范围内的点)将被保留,而评估为 False 的数据点将被筛选掉。

无法在同一图表中同时进行选择和筛选,因此此功能通常在存在至少两个子图表时使用。在以下示例中,我们将选择参数附加到上部图表,然后根据上部图表中的选择筛选下部图表中的数据。您可以探索条形图中的计数如何根据散点图中的选择大小和位置而变化。

brush = alt.selection_interval()

points = alt.Chart(cars).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color='Origin:N'
).add_params(
    brush
)

bars = alt.Chart(cars).mark_bar().encode(
    x='count()',
    y='Origin:N',
    color='Origin:N'
).transform_filter(
    brush
)

points & bars

选择类型#

现在我们已经了解了如何使用选择与图表交互的基础知识,接下来我们将更系统地查看 Altair 中可用的选择参数类型。为了简单起见,我们在以下所有示例中都将使用一个通用图表;一个基于 cars 数据集的简单热力图。为了方便起见,我们快速编写一个 Python 函数,它将接受一个选择对象,并创建一个图表,其中图表元素的颜色与此选择链接

def make_example(selector: alt.Parameter) -> alt.Chart:
    cars = data.cars.url

    return alt.Chart(cars).mark_rect().encode(
        x="Cylinders:O",
        y="Origin:N",
        color=alt.when(selector).then("count()").otherwise(alt.value("lightgray")),
    ).properties(
        width=300,
        height=180
    ).add_params(
        selector
    )

接下来,我们将使用此函数演示各种选择的属性。

区间选择#

区间选择允许您通过点击和拖动来选择图表元素。您可以使用 selection_interval() 函数创建这样的选择

interval = alt.selection_interval()
make_example(interval)

当您在图上点击并拖动时,您会发现鼠标创建了一个框,随后可以移动此框以更改选择。

模块的 selection_interval() 函数接受一些附加参数;例如,我们可以将区间仅绑定到 x 轴,并将其设置为空选择不包含任何点

interval_x = alt.selection_interval(encodings=['x'], empty=False)
make_example(interval_x)

模块的 empty=False 参数也可以在 when() 内部设置,以便在传递空选择时改变每个条件的行为,而不是必须定义单独的选择对象

brush = alt.selection_interval()
...
color=alt.when(brush).then(...)
size=alt.when(brush, empty=False).then(...)
...

点选择#

选择允许您通过鼠标操作逐个选择图表元素。默认情况下,点在点击时被选中

point = alt.selection_point()
make_example(point)

通过改变一些参数,我们可以使点在鼠标悬停时而不是点击时被选中。我们还可以将 nearest 标志设置为 True,以便高亮显示最近的点

point_nearest = alt.selection_point(on='pointerover', nearest=True)
make_example(point_nearest)

点选择还允许选择多个图表对象。默认情况下,可以通过在点击图表元素时按住 shift 键来将它们添加到选择中或从选择中移除,您可以在上面的两个图表中尝试。

选择目标#

对于除了最简单的选择之外的任何选择,用户需要准确思考选择的目标是什么,这可以通过 fieldsencodings 参数来控制。这些参数控制用于确定哪些点属于选择的数据属性。

例如,在这里我们通过使用 fields=['Origin'] 针对 Origin 字段来创建一个充当交互式图例的小图表。点击右上图(图例)中的点将传播选择具有匹配 Origin 的所有点。

selection = alt.selection_point(fields=['Origin'])
color = (
    alt.when(selection)
    .then(alt.Color("Origin:N").legend(None))
    .otherwise(alt.value("lightgray"))
)

scatter = alt.Chart(cars).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color=color,
    tooltip='Name:N'
)

legend = alt.Chart(cars).mark_point().encode(
    alt.Y('Origin:N').axis(orient='right'),
    color=color
).add_params(
    selection
)

scatter | legend

或者,我们可以将 fields=['Origin'] 表达为 encodings=['color'],因为我们的图表将 color 映射到 'Origin'。另请注意,在 图例绑定 部分描述了在 Altair 中创建交互式图例的快捷方式。

类似地,我们可以指定必须匹配的多个字段和/或编码,以便将数据包含在选择中。例如,我们可以修改上面的图表,创建一个二维可点击图例,它将按 Origin 和气缸数选择点

selection = alt.selection_point(fields=['Origin', 'Cylinders'])
color = (
    alt.when(selection)
    .then(alt.Color("Origin:N").legend(None))
    .otherwise(alt.value("lightgray"))
)

scatter = alt.Chart(cars).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color=color,
    tooltip='Name:N'
)

legend = alt.Chart(cars).mark_rect().encode(
    alt.Y('Origin:N').axis(orient='right'),
    x='Cylinders:O',
    color=color
).add_params(
    selection
)

scatter | legend

通过这种方式微调选择的行为,可以将它们用于创建各种链接的交互式图表类型。

组合参数#

可以在单个图表中组合多个参数,通过多个独立的响应条件、when() 中的不同条件分支,或参数组合。

多个条件#

在此示例中,鼠标悬停的点将增大尺寸,点击的点将填充为红色。empty=False 是为了确保开始时没有点被选中。尝试按住 shift 键以在悬停或点击时选择多个点。

click = alt.selection_point(empty=False)
hover = alt.selection_point(on='pointerover', empty=False)

points = alt.Chart(cars).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    fill=alt.when(click).then(alt.value('red')),
    size=alt.when(hover).then(alt.value(1000))
).add_params(
    click, hover
)

points

条件分支#

when() 允许使用多个 then (elif) 分支,这些分支可以根据多个不同参数改变单个编码的行为。在这里,我们将鼠标悬停的点填充为黄色,然后在点击点时将其填充更改为红色。由于在点击点时鼠标也悬停在点上,两个条件都将激活,并且较早的分支具有优先权(您可以通过更改两个 when.then 子句的顺序并观察点击点时颜色不会变为红色来尝试)。

click = alt.selection_point(empty=False)
hover = alt.selection_point(on='pointerover', empty=False)

points = alt.Chart(cars).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    fill=(
        alt.when(click)
        .then(alt.value('red'))
        .when(hover)
        .then(alt.value('gold'))
    ),
    size=alt.when(hover).then(alt.value(1000))
).add_params(
    click, hover
)

points

参数组合#

Altair 还支持使用 &|~ 分别表示 ANDORNOT 逻辑组合运算符来组合多个参数。

回到我们的热力图示例,我们可以构建一个场景,其中有两个人可以在同一图表中进行区间选择。当按下 alt 键(macOS:option 键)时,Alex 进行选择框;当按下 shift 键时,Morgan 进行选择框。我们使用 BrushConfig 为 Morgan 的选择框赋予不同的样式。现在,当矩形落入 Alex 或 Morgan 的选择范围内时,我们为其着色(请注意,您需要在看到效果之前创建两个选择)。

alex = alt.selection_interval(
    on="[pointerdown[event.altKey], pointerup] > pointermove",
    name='alex'
)
morgan = alt.selection_interval(
    on="[pointerdown[event.shiftKey], pointerup] > pointermove",
    mark=alt.BrushConfig(fill="#fdbb84", fillOpacity=0.5, stroke="#e34a33"),
    name='morgan'
)

alt.Chart(cars).mark_rect().encode(
    x='Cylinders:O',
    y='Origin:O',
    color=alt.when(alex | morgan).then("count()").otherwise(alt.value("grey")),
).add_params(
    alex, morgan
).properties(
    width=300,
    height=180
)

使用这些运算符,可以选择以任意方式组合

  • ~(alex & morgan):选择落在 Alex 和 Morgan 选择范围之外的矩形。

  • alex | ~morgan:选择落在 Alex 选择范围内或 Morgan 选择范围之外的矩形

有关如何微调选择的更多信息,包括指定其他鼠标和按键选项,请参阅 Vega-Lite 选择文档