数据转换器#

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

  • pandas Dataframe 必须经过清理和序列化为 JSON。

  • 数据框的行可能需要进行抽样或限制为最大行数。

  • 出于性能考虑,数据框可能会被写入一个 .csv.json 文件。

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

注意

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

数据转换器是一个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数据框传递的数据集可以通过两种方式在图表中表示:

  • 作为在任何级别的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)

在可视化之前随机抽样一个数据框(不放回抽样):

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

在可视化之前,将数据框转换为单独的 .json 文件:

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

在可视化之前,将数据框转换为单独的 .csv 文件:

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

在可视化之前,将数据框转换为内联 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 维护一个数据转换器的注册表,其中包括一个默认的数据转换器,该转换器在渲染之前会自动应用于所有 Dataframe。

查看注册的转换器:

>>> 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 文件中。这两个数据转换器有许多性能优势:

  • 完整的数据集将不会保存在笔记本文档中。

  • Vega-Lite/Vega JavaScript 的性能在独立的 JSON/CSV 文件中似乎优于内联值。

JSON/CSV 数据转换器存在缺点:

  • 数据框将被导出到一个临时 .json.csv 文件,该文件位于笔记本旁边。

  • 该笔记本将无法在没有该临时文件(或重新运行单元格)的情况下重新渲染可视化。

根据我们的经验,性能提升非常显著,因此我们建议对任何大型数据集使用 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参数前加上我们所需的目录,并确保该目录实际存在。