数据转换器#

在将 Vega-Lite 或 Vega 规范传递给渲染器之前,通常需要通过多种方式对其进行转换

  • pandas Dataframe 需要进行清理并序列化为 JSON。

  • Dataframe 的行可能需要进行采样或限制最大数量。

  • 出于性能原因,Dataframe 可能会被写入 .csv.json 文件。

这些数据转换由 Altair 的数据转换 API 管理。

注意

Altair 的数据转换 API 不应与 Vega 和 Vega-Lite 的 transform API 混淆。

数据转换器是一个 Python 函数,它接受 Vega-Lite 数据 dict 或 pandas DataFrame,并返回其中任一类型的转换版本

from typing import Union
Data = Union[dict, pd.DataFrame]

def data_transformer(data: Data) -> Data:
    # Transform and return the data
    return transformed_data

数据集整合#

作为 pandas dataframes 传递的数据集可以在图表中以两种方式表示

  • 作为规范任何级别的 data 属性中的字面数据集值

  • 作为顶层规范的 datasets 属性中的命名数据集。

前者更简单一些,但 Altair 中常见的用法模式往往会导致完整的数据集在单个规范中多次完整列出。

出于这个原因,Altair 2.2 及更新版本默认会将所有直接指定的数据集移至顶层 datasets 条目中,并通过数据表示哈希值确定的唯一名称引用它们。使用基于哈希的名称的好处是,即使用户在构建图表时在多个地方指定了数据集,规范中也只会包含一份副本。

可以通过设置数据转换器的 consolidate_datasets 属性来修改此行为。

例如,考虑这个简单的分层图表

import altair as alt
import pandas as pd

df = pd.DataFrame({'x': range(5),
                   'y': [1, 3, 4, 3, 5]})

line = alt.Chart(df).mark_line().encode(x='x', y='y')
points = alt.Chart(df).mark_point().encode(x='x', y='y')
chart = line + points

如果我们查看生成的规范,会发现尽管数据集指定了两次,但在规范中只输出了一份副本

from pprint import pprint
pprint(chart.to_dict())
{'$schema': 'https://vega.github.io/schema/vega-lite/v5.20.1.json',
 'config': {'view': {'continuousHeight': 300, 'continuousWidth': 300}},
 'data': {'name': 'data-cc0e6ca6677ef92e3b073d043f1ea320'},
 'datasets': {'data-cc0e6ca6677ef92e3b073d043f1ea320': [{'x': 0, 'y': 1},
                                                        {'x': 1, 'y': 3},
                                                        {'x': 2, 'y': 4},
                                                        {'x': 3, 'y': 3},
                                                        {'x': 4, 'y': 5}]},
 'layer': [{'encoding': {'x': {'field': 'x', 'type': 'quantitative'},
                         'y': {'field': 'y', 'type': 'quantitative'}},
            'mark': {'type': 'line'}},
           {'encoding': {'x': {'field': 'x', 'type': 'quantitative'},
                         'y': {'field': 'y', 'type': 'quantitative'}},
            'mark': {'type': 'point'}}]}

这种数据集整合是一项额外的处理,默认在所有渲染器中启用。

如果由于任何原因希望禁用此数据集整合,可以通过设置 alt.data_transformers.consolidate_datasets = False 来实现,或者使用 enable() 上下文管理器仅暂时禁用它

with alt.data_transformers.enable(consolidate_datasets=False):
    pprint(chart.to_dict())
{'$schema': 'https://vega.github.io/schema/vega-lite/v5.20.1.json',
 'config': {'view': {'continuousHeight': 300, 'continuousWidth': 300}},
 'data': {'values': [{'x': 0, 'y': 1},
                     {'x': 1, 'y': 3},
                     {'x': 2, 'y': 4},
                     {'x': 3, 'y': 3},
                     {'x': 4, 'y': 5}]},
 'layer': [{'encoding': {'x': {'field': 'x', 'type': 'quantitative'},
                         'y': {'field': 'y', 'type': 'quantitative'}},
            'mark': {'type': 'line'}},
           {'encoding': {'x': {'field': 'x', 'type': 'quantitative'},
                         'y': {'field': 'y', 'type': 'quantitative'}},
            'mark': {'type': 'point'}}]}

请注意,现在数据集没有在顶层 datasets 属性中指定,而是在每个单独图层的 data 属性中作为值指定。这种数据重复是默认将数据集整合设置为 True 的原因。

内置数据转换器#

Altair 包含一组默认数据转换器,具有以下签名。

如果 Dataframe 行数超过 max_rows,则引发 MaxRowsError

limit_rows(data, max_rows=5000)

在可视化之前随机采样 Dataframe(无放回)

sample(data, n=None, frac=None)

在可视化之前将 Dataframe 转换为单独的 .json 文件

to_json(data, prefix='altair-data'):

在可视化之前将 Dataframe 转换为单独的 .csv 文件

to_csv(data, prefix='altair-data'):

在可视化之前将 Dataframe 转换为内联 JSON 值

to_values(data):

管道#

可以使用 pipe 将多个数据转换器连接起来

from altair import limit_rows, to_values
from toolz.curried import pipe
pipe(data, limit_rows(10000), to_values)

管理数据转换器#

Altair 维护一个数据转换器注册表,其中包含一个默认数据转换器,该转换器在渲染前自动应用于所有 Dataframes。

查看已注册的转换器

>>> import altair as alt
>>> alt.data_transformers.names()
['default', 'json', 'csv']

默认数据转换器如下

def default_data_transformer(data):
    return pipe(data, limit_rows, to_values)

jsoncsv 数据转换器会在渲染前将 Dataframe 保存到临时 .json.csv 文件。这两种数据转换器具有许多性能优势

  • 完整数据集不会保存在 notebook 文档中。

  • Vega-Lite/Vega JavaScript 对于独立 JSON/CSV 文件的性能似乎比内联值更好。

JSON/CSV 数据转换器也有缺点

  • Dataframe 将导出到与 notebook 位于同一位置的临时 .json.csv 文件。

  • 没有该临时文件(或重新运行单元格),该 notebook 将无法重新渲染可视化。

根据我们的经验,性能提升非常显著,因此我们建议对任何大型数据集使用 json 数据转换器

alt.data_transformers.enable('json')

我们希望其他人能编写额外的数据转换器——想象一个将数据集保存到 S3 上的 JSON 文件的转换器,它可以注册并启用为

alt.data_transformers.register('s3', lambda data: pipe(data, to_s3('mybucket')))
alt.data_transformers.enable('s3')

将 JSON 数据存储在单独目录中#

使用 alt.data_transformers.enable('json') 创建大量图表时,工作目录可能会变得有些杂乱。为了避免这种情况,我们可以构建一个简单的自定义数据转换器,将所有 JSON 文件存储在单独的目录中。

import os
import altair as alt
from toolz.curried import pipe


def json_dir(data, data_dir='altairdata'):
    os.makedirs(data_dir, exist_ok=True)
    return pipe(data, alt.to_json(filename=data_dir + '/{prefix}-{hash}.{extension}') )


alt.data_transformers.register('json_dir', json_dir)
alt.data_transformers.enable('json_dir', data_dir='mydata')

启用此数据转换器后,JSON 文件将存储在启用转换器时设置的 data_dir 或默认的 'altairdata' 目录中。我们只需将 alt.to_json 函数的 filename 参数加上我们所需的目录前缀,并确保该目录实际存在。