大数据集#

如果您尝试创建一个直接嵌入超过5000行数据集的图,您将看到一个 MaxRowsError:

import altair as alt
import pandas as pd

data = pd.DataFrame({"x": range(10000)})
alt.Chart(data).mark_point()
MaxRowsError: The number of rows in your dataset is greater than the maximum allowed (5000).

Try enabling the VegaFusion data transformer which raises this limit by pre-evaluating data
transformations in Python.
    >> import altair as alt
    >> alt.data_transformers.enable("vegafusion")

Or, see https://altair-viz.github.io/user_guide/large_datasets.html for additional information
on how to plot large datasets.

这不是因为 Altair 无法处理更大的数据集,而是因为用户需要仔细考虑如何处理大数据集。以下部分描述了处理大数据集的各种考虑因素以及方法。

如果您确定希望在可视化规范中嵌入完整的未转换数据集,可以禁用 MaxRows 检查:

alt.data_transformers.disable_max_rows()

挑战#

按设计,Altair 生成的不是由像素组成的图,而是由数据加上可视化规范组成的图。例如,这里是一个由包含三行数据的数据框制作的简单图表:

import altair as alt
import pandas as pd
data = pd.DataFrame({'x': [1, 2, 3], 'y': [2, 1, 2]})

chart = alt.Chart(data).mark_line().encode(
     x='x',
     y='y'
)

from pprint import pprint
pprint(chart.to_dict())
{'$schema': 'https://vega.github.io/schema/vega-lite/v2.4.1.json',
 'config': {'view': {'height': 300, 'width': 300}},
 'data': {'values': [{'x': 1, 'y': 2}, {'x': 2, 'y': 1}, {'x': 3, 'y': 2}]},
 'encoding': {'x': {'field': 'x', 'type': 'quantitative'},
              'y': {'field': 'y', 'type': 'quantitative'}},
 'mark': 'line'}

生成的规范包括将数据转换为JSON格式的表示,并且该规范嵌入在笔记本或网页中,可以被Vega-Lite用来渲染图表。随着数据大小的增长,这种显式的数据存储可能导致一些非常大的规范,从而带来各种负面影响:

  • 大型笔记本文件可能会减慢您的笔记本环境,例如 JupyterLab

  • 如果您在网站上显示图表,它会减慢页面的加载速度

  • 变换的慢评估,因为计算是在JavaScript中执行的,而JavaScript并不是处理大量数据的最快语言

VegaFusion 数据转换器#

处理MaxRowsError的最简单和最灵活的方法是启用"vegafusion"数据转换器,该转换器在Altair 5.1中添加。VegaFusion是一个外部项目,提供大多数Altair数据转换的高效Rust实现。通过在Python中评估数据转换(例如分箱和汇总),必须包含在最终图表规范中的数据集的大小通常大大减少。此外,VegaFusion会自动删除未使用的列,这甚至减少了没有数据转换的图表的数据集大小。

"vegafusion" 数据转换器处于活动状态时,数据转换将在 显示 Altair 图表保存 Altair 图表、转换的图表到字典,以及将图表转换为 JSON 时进行预先评估。当与 JupyterChart"jupyter" 渲染器结合使用时(请参见 自定义渲染器),数据转换还将在 Python 中动态评估以响应图表选择事件。

VegaFusion的开发由 Hex 赞助。

安装VegaFusion#

可以使用 pip 安装 VegaFusion 依赖项

pip install "vegafusion[embed]"

或 conda

conda install -c conda-forge vegafusion vegafusion-python-embed vl-convert-python

启用VegaFusion数据转换器#

通过以下方式激活VegaFusion数据转换器:

import altair as alt
alt.data_transformers.enable("vegafusion")

在激活VegaFusion数据转换器后创建的所有图表将适用于包含最多100,000行的数据集。VegaFusion的行限制是在应用所有支持的数据转换后施加的。因此,您不太可能在直方图等图表中达到此限制,但在大型散点图或包含交互性的图表中(当不使用 JupyterChart"jupyter" 渲染器时),您可能会达到此限制。

如果您需要处理更大的数据集,您可以禁用最大行限制或切换到使用 JupyterChart 或下面描述的 "jupyter" 渲染器。

转换为JSON或字典#

当使用 chart.to_json 将 VegaFusion 图表转换为 JSON 或使用 chart.to_dict 将其转换为 Python 字典时,format 参数必须被设置为 "vega" 而不是默认的 "vega-lite"。例如:

chart.to_json(format="vega")
chart.to_dict(format="vega")

这是因为VegaFusion使用的是Vega图表规范,而不是由Altair生成的Vega-Lite规范。当启用VegaFusion数据转换器时,vl-convert库用于执行从Vega-Lite到Vega的转换。

本地时区配置#

一些Altair转换(例如 TimeUnit)是基于本地时区的。通常情况下,使用浏览器的本地时区。然而,由于VegaFusion在渲染前在Python中评估这些转换,因此并不总是可以访问浏览器的时区。相反,默认将使用Python内核的本地时区。在云笔记本服务的情况下,这可能与浏览器的本地时区不同。

可以使用 vegafusion.set_local_tz 函数自定义VegaFusion的本地时区。例如:

import vegafusion as vf
vf.set_local_tz("America/New_York")

在使用 JupyterChart"jupyter" 渲染器时,浏览器的本地时区会被使用。

DuckDB 集成#

VegaFusion 提供了与 DuckDB 的可选集成。因为 DuckDB 可以在 pandas DataFrames 上执行查询而无需通过 Arrow 转换,所以它通常比 VegaFusion 的默认查询引擎更快,后者需要这个转换。有关更多信息,请参见 VegaFusion DuckDB 文档。

交互性#

当使用默认的 "html" 渲染器与使用选择来交互筛选数据的图表时,VegaFusion 数据转换器将包括所有参与交互的数据在生成的图表规范中。这使得它不适合构建筛选大数据集的交互式图表(例如,对超过一百万行的数据集进行交叉筛选)。

JupyterChart 小部件和 "jupyter" 渲染器旨在与 VegaFusion 数据转换器配合使用,以便在选择事件的响应中交互式地评估数据转换。这避免了将完整数据集传输到浏览器的需要,从而支持对数百万行的数据集的交互式探索。

直接使用 JupyterChart

import altair as alt
alt.data_transformers.enable("vegafusion")
...
alt.JupyterChart(chart)

或者,启用 "jupyter" 渲染器并像往常一样显示图表:

import altair as alt
alt.data_transformers.enable("vegafusion")
alt.renderers.enable("jupyter")
...
chart

以这种方式渲染的图表需要运行的 Python 内核和 Jupyter Widget 扩展才能显示,这在许多前端中工作,包括经典笔记本、本地 JupyterLab 和 VSCode,以及远程的 Colab 和 Binder。

通过URL传递数据#

处理大型数据集时常见的一种方法是不直接嵌入数据,而是将其单独存储并通过URL传递给图表。这不仅解决了大型笔记本的问题,还提高了与大型数据集的交互性能。

本地数据服务器#

一个方便的方法是使用altair_data_server包。它通过本地线程服务器为您的数据提供服务。首先安装该包:

pip install altair_data_server

然后启用数据转换器:

import altair as alt
alt.data_transformers.enable('data_server')

请注意,这种方法在某些基于云的Jupyter Notebook服务上可能无法正常工作。 这种方法的一个缺点是,如果您重新打开笔记本,图表可能无法再显示 因为数据服务器不再运行。

本地文件系统#

您还可以将数据持久化到磁盘,然后将路径传递给Altair:

url = 'data.json'
data.to_json(url, orient='records')

chart = alt.Chart(url).mark_line().encode(
    x='x:Q',
    y='y:Q'
)
pprint(chart.to_dict())
{'$schema': 'https://vega.github.io/schema/vega-lite/v2.4.1.json',
 'config': {'view': {'height': 300, 'width': 300}},
 'data': {'url': 'data.json'},
 'encoding': {'x': {'field': 'x', 'type': 'quantitative'},
              'y': {'field': 'y', 'type': 'quantitative'}},
 'mark': 'line'}

Altair 还拥有一个 JSON 数据转换器,当启用时,它会透明地执行此操作:

alt.data_transformers.enable('json')

有一个类似的CSV数据转换器,但必须更小心地使用,因为CSV不像JSON那样保留数据类型。

请注意,文件系统的方法可能不适用于某些基于云的 Jupyter notebook 服务。此方法的一个缺点是可移植性的丧失:如果 notebook 被移动,数据文件必须随之而来,否则图表可能无法显示。

维加数据集#

如果您正在使用其中一个vega数据集,您可以通过URL传递数据,使用url属性:

from vega_datasets import data
source = data.cars.url

alt.Chart(source).mark_point() # etc.

PNG和SVG渲染器#

通过 URL 传递数据 中提出的方法有一个缺点,即数据不再保存在笔记本中,因此您失去了可移植性,或者在重新打开笔记本时看不到图表。此外,数据仍然需要发送到前端,例如您的浏览器,并且所有计算将在那里进行。

通过启用PNG或SVG渲染器,您可能会实现加速,如Altair的渲染器框架中所述。它们将预渲染可视化,并仅将静态图像发送到您的笔记本,而不是使用Vega-Lite规范。这可以大大减少传输的数据量。这个方法的缺点是,您失去了Altair的所有交互功能。

两个渲染器都需要你安装 vl-convert 包,参见 PNG、SVG 和 PDF 格式

在pandas中预聚合和过滤#

另一种常见的方法是使用pandas在将数据传递给Altair之前进行数据转换,例如聚合和过滤。

例如,要为 barley 数据集创建一个柱状图,汇总按 site 分组的 yield,将未汇总的数据传递给 Altair 是很方便的:

import altair as alt
from vega_datasets import data

source = data.barley()

alt.Chart(source).mark_bar().encode(
    x="sum(yield):Q",
    y=alt.Y("site:N").sort("-x")
)

上述方法对于较小的数据集效果良好,但让我们想象一下,barley 数据集要更大,生成的 Altair 图表使您的笔记本环境变得缓慢。为了减少传递给 Altair 的数据,您可以将数据框子集化,只保留必要的列:

alt.Chart(source[["yield", "site"]]).mark_bar().encode(
    x="sum(yield):Q",
    y=alt.Y("site:N").sort("-x")
)

您还可以在pandas中预计算总和,这样可以进一步减少数据集的大小:

import altair as alt
from vega_datasets import data

source = data.barley()
source_aggregated = (
    source.groupby("site")["yield"].sum().rename("sum_yield").reset_index()
)

alt.Chart(source_aggregated).mark_bar().encode(
    x="sum_yield:Q",
    y=alt.Y("site:N").sort("-x")
)

预聚合箱线图#

箱线图是可视化数据分布的有效方式,且在Altair中简单易创建。

import altair as alt
from vega_datasets import data

df = data.cars()

alt.Chart(df).mark_boxplot().encode(
    x="Miles_per_Gallon:Q",
    y="Origin:N",
    color=alt.Color("Origin").legend(None)
)

如果您有大量数据,您可以在pandas中执行必要的计算,并仅将结果摘要统计信息传递给Altair。

首先,让我们定义几个参数,其中 k 代表用于计算须边界的乘数。

import altair as alt
import pandas as pd
from vega_datasets import data

k = 1.5
group_by_column = "Origin"
value_column = "Miles_per_Gallon"

在下一步中,我们将计算用于箱形图的汇总统计数据。

df = data.cars()

agg_stats = df.groupby(group_by_column)[value_column].describe()
agg_stats["iqr"] = agg_stats["75%"] - agg_stats["25%"]
agg_stats["min_"] = agg_stats["25%"] - k * agg_stats["iqr"]
agg_stats["max_"] = agg_stats["75%"] + k * agg_stats["iqr"]
data_points = df[[value_column, group_by_column]].merge(
    agg_stats.reset_index()[[group_by_column, "min_", "max_"]]
)
# Lowest data point which is still above or equal to min_
# This will be the lower end of the whisker
agg_stats["lower"] = (
    data_points[data_points[value_column] >= data_points["min_"]]
    .groupby(group_by_column)[value_column]
    .min()
)
# Highest data point which is still below or equal to max_
# This will be the upper end of the whisker
agg_stats["upper"] = (
    data_points[data_points[value_column] <= data_points["max_"]]
    .groupby(group_by_column)[value_column]
    .max()
)
# Store all outliers as a list
agg_stats["outliers"] = (
    data_points[
        (data_points[value_column] < data_points["min_"])
        | (data_points[value_column] > data_points["max_"])
    ]
    .groupby(group_by_column)[value_column]
    .apply(list)
)
agg_stats = agg_stats.reset_index()

# Show whole dataframe
pd.set_option("display.max_columns", 15)
print(agg_stats)
       Origin  count       mean       std   min   25%   50%    75%   max   iqr  \
    0  Europe   70.0  27.891429  6.723930  16.2  24.0  26.5  30.65  44.3  6.65   
    1   Japan   79.0  30.450633  6.090048  18.0  25.7  31.6  34.05  46.6  8.35   
    2     USA  249.0  20.083534  6.402892   9.0  15.0  18.5  24.00  39.0  9.00   
    
         min_    max_  lower  upper                              outliers  
    0  14.025  40.625   16.2   37.3  [43.1, 41.5, 44.3, 43.4, 40.9, 44.0]  
    1  13.175  46.575   18.0   44.6                                [46.6]  
    2   1.500  37.500    9.0   36.1                    [39.0, 38.0, 38.0]  

最后,我们可以创建与上面相同的箱形图,但我们只将计算出的摘要统计信息传递给Altair,而不是完整的数据集。

base = alt.Chart(agg_stats).encode(
    y="Origin:N"
)

rules = base.mark_rule().encode(
    x=alt.X("lower").title("Miles_per_Gallon"),
    x2="upper",
)

bars = base.mark_bar(size=14).encode(
    x="25%",
    x2="75%",
    color=alt.Color("Origin").legend(None),
)

ticks = base.mark_tick(color="white", size=14).encode(
    x="50%"
)

outliers = base.transform_flatten(
    flatten=["outliers"]
).mark_point(
    style="boxplot-outliers"
).encode(
    x="outliers:Q",
    color="Origin",
)

rules + bars + ticks + outliers