大型数据集#
如果您尝试创建一个将直接嵌入行数超过 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://vega-altair.cn/user_guide/large_datasets.html for additional information
on how to plot large datasets.
这并不是因为 Altair 无法处理大型数据集,而是因为用户仔细考虑如何处理大型数据集非常重要。以下各节描述了处理大型数据集的各种考虑因素和方法。
如果您确定要将完整的、未转换的数据集嵌入到可视化规范中,可以禁用 MaxRows
检查。
alt.data_transformers.disable_max_rows()
挑战#
按照设计,Altair 生成的图表不是由像素组成的,而是由数据加上可视化规范组成的。例如,这是一个使用包含三行数据的 dataframe 创建的简单图表:
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 格式的数据表示,该规范嵌入在 notebook 或网页中,可由 Vega-Lite 用于渲染图表。随着数据大小的增长,这种显式数据存储可能导致非常大的规范,从而带来各种负面影响:
大型 notebook 文件,可能拖慢您的 notebook 环境,例如 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 或字典#
将 VegaFusion 图表使用 chart.to_json
转换为 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 转换(例如 时间单位)基于本地时区。通常使用浏览器的本地时区。然而,由于 VegaFusion 在渲染之前在 Python 中评估这些转换,因此并非总是能够访问浏览器的时区。默认情况下将使用 Python 内核的本地时区。在使用云端 notebook 服务的情况下,这可能与浏览器的本地时区不同。
可以使用 vegafusion.set_local_tz
函数自定义 VegaFusion 的本地时区。例如:
import vegafusion as vf
vf.set_local_tz("America/New_York")
使用 JupyterChart
或 "jupyter"
渲染器时,使用的是浏览器的本地时区。
DuckDB 集成#
VegaFusion 提供了与 DuckDB 的可选集成。由于 DuckDB 可以在 pandas DataFrame 上执行查询而无需通过 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 扩展才能显示,这在许多前端都有效,包括本地的经典 notebook、JupyterLab 和 VSCode,以及远程的 Colab 和 Binder。
通过 URL 传递数据#
处理大型数据集时,一种常见方法不是直接嵌入数据,而是将其单独存储并通过 URL 传递给图表。这不仅解决了大型 notebook 的问题,还能提高大型数据集的交互性能。
本地数据服务器#
一个方便的方法是使用 altair_data_server 包。它通过本地线程服务器提供数据。首先安装该包:
pip install altair_data_server
然后启用数据转换器:
import altair as alt
alt.data_transformers.enable('data_server')
请注意,此方法可能在某些基于云的 Jupyter notebook 服务上不起作用。这种方法的一个缺点是,如果重新打开 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 数据集#
如果您使用的是 Vega 数据集之一,可以使用 url
属性通过 URL 传递数据:
from vega_datasets import data
source = data.cars.url
alt.Chart(source).mark_point() # etc.
PNG 和 SVG 渲染器#
通过 URL 传递数据 中介绍的方法的缺点是数据不再包含在 notebook 中,因此会失去可移植性,或者在重新打开 notebook 时看不到图表。此外,数据仍然需要发送到前端(例如您的浏览器),并且所有计算都将在那里进行。
您可以通过启用 Altair 的渲染器框架 中描述的 PNG 或 SVG 渲染器来提高速度。它们不会生成 Vega-Lite 规范,而是会预渲染可视化并仅将静态图像发送到您的 notebook。这可以大大减少传输的数据量。这种方法的缺点是,您会失去 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 图表拖慢了您的 notebook 环境。为了减少传递给 Altair 的数据,您可以将 dataframe 子集化到仅包含必要的列:
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