时间 & 日期#

处理日期、时间和时区通常是数据分析中最具挑战性的方面之一。在Altair中,这些困难因用户编写Python代码而加剧,该代码输出JSON序列化的时间戳,这些时间戳由Javascript解释,然后由您的浏览器呈现。在这些步骤中,总会有可能出错的地方,但Altair和Vega-Lite尽力确保日期以一致的方式被解释和可视化。

Altair 和 pandas 时间日期#

Altair旨在与pandas时间序列协同工作。一个标准的与时区无关的日期/时间列在pandas数据框中将被解释为并显示为当地用户时间。例如,这里有一个包含在西雅图测量的每小时温度的数据集:

import altair as alt
from vega_datasets import data

temps = data.seattle_temps()
temps.head()
                     date  temp
    0 2010-01-01 00:00:00  39.4
    1 2010-01-01 01:00:00  39.2
    2 2010-01-01 02:00:00  39.0
    3 2010-01-01 03:00:00  38.9
    4 2010-01-01 04:00:00  38.8

我们可以从 dtypes 属性看到,时间被编码为标准的 64 位 datetime,未指定任何时区:

temps.dtypes
    date    datetime64[ns]
    temp           float64
    dtype: object

我们可以使用Altair来可视化这些日期时间数据;为了清晰起见,在这个例子中,我们将数据限制在前两周:

temps = temps[temps.date < '2010-01-15']

alt.Chart(temps).mark_line().encode(
    x='date:T',
    y='temp:Q'
)

请注意,对于日期/时间值,我们使用T来表示时间编码:虽然这对于pandas datetime输入是可选的,但明确指定类型是良好的做法;有关更多讨论,请参见数据类型编码。如果您想让Altair将四位整数绘制为年份,您需要在将数据类型更改为时间类型之前将其转换为字符串,请参见数据类型对坐标轴刻度的影响获取详细信息。

对于诸如这些的日期时间输入,有时提取特定的时间单位(例如,天中的小时,月中的日期等)是有用的。在Altair中,可以通过时间单位变换来实现此功能,详细讨论见TimeUnit。例如,我们可能决定我们想要一个热图,x轴为天中的小时,y轴为月中的日期:

alt.Chart(temps).mark_rect().encode(
    alt.X('hoursminutes(date):O').title('hour of day'),
    alt.Y('monthdate(date):O').title('date'),
    alt.Color('temp:Q').title('temperature (F)')
)

除非您正在使用非ES6浏览器(请参见 关于浏览器兼容性的说明), 您会注意到由此代码创建的图表反映了从1月1日00:00:00开始的小时, 正如我们输入的数据一样。这是因为输入的时间戳和图表输出都使用本地时间。

指定时区#

如果您在支持的浏览器中查看上述可视化效果(请参见 关于浏览器合规性的说明),时间都被序列化并以本地时间呈现,因此 January 1st 00:00:00 行在图表中显示为 00:00January 1st

在Altair中,没有明确时区的简单日期被视为当地时间,而在Vega-Lite中,除非另有说明,否则时间会以执行渲染的浏览器的当地时间显示。

如果您希望您的日期能够识别时区,您可以在输入数据框中明确设置时区。由于西雅图位于US/Pacific时区,我们可以在pandas中如下本地化时间戳:

temps['date_pacific'] = temps['date'].dt.tz_localize('US/Pacific')
temps.dtypes
    date                        datetime64[ns]
    temp                               float64
    date_pacific    datetime64[ns, US/Pacific]
    dtype: object

请注意,时区现在是pandas数据类型的一部分。 如果我们用这个具有时区感知的数据重复上述图表,结果将 根据渲染它的浏览器的时区进行呈现

alt.Chart(temps).mark_rect().encode(
    alt.X('hoursminutes(date_pacific):O').title('hour of day'),
    alt.Y('monthdate(date_pacific):O').title('date'),
    alt.Color('temp:Q').title('temperature (F)')
)

如果您在将时间设置为美国西海岸的计算机上查看此图表,它应该与第一个版本完全相同。如果您在其他时区中渲染图表,则将使用根据您系统中设置的位置计算的时区修正来渲染。

使用UTC时间#

这种用户本地渲染有时可能会让人困惑,因为它导致相同的输出被不同用户以不同的方式可视化。如果您希望无论用户位于何处,时区感知数据在所有用户面前显示一致,最佳方法是采用一个标准时区来渲染数据。一种常用的标准是 协调世界时 (UTC)。在 Altair 中,任何的 timeUnit 区间都可以前缀 utc 以提取 UTC 时间单位。

这是以上图表以UTC时间可视化的结果,无论系统位置如何,渲染效果都将相同:

alt.Chart(temps).mark_rect().encode(
    alt.X('utchoursminutes(date_pacific):O').title('UTC hour of day'),
    alt.Y('utcmonthdate(date_pacific):O').title('UTC date'),
    alt.Color('temp:Q').title('temperature (F)')
)

为了使您的图表尽可能可移植(即使在解析与时区无关的时间为UTC的非ES6浏览器中),您可以明确地在UTC时间下工作,包括在pandas端和Vega-Lite端:

temps['date_utc'] = temps['date'].dt.tz_localize('UTC')

alt.Chart(temps).mark_rect().encode(
    alt.X('utchoursminutes(date_utc):O').title('hour of day'),
    alt.Y('utcmonthdate(date_utc):O').title('date'),
    alt.Color('temp:Q').title('temperature (F)')
)

这在某种程度上比时区无关的日期的默认行为要不方便一些,pandas和Vega-Lite都假设时间是本地的(除了在非ES6浏览器中;见关于浏览器兼容性的说明),但是通过明确在UTC中工作,它避免了浏览器的不兼容性,即使在较旧的浏览器中也能得到类似的结果。

关于浏览器兼容性的说明#

注意

关于非ES6浏览器的警告

以下讨论适用于支持ECMAScript 6的现代浏览器,在这些浏览器中,像"2018-01-01T12:00:00"这样的时间字符串如果没有一个尾随的"Z",则被视为本地时间,而不是协调世界时间 (UTC)。例如,最近版本的Chrome和Firefox是符合ES6标准的,而Safari 11则不是。如果您使用的是非ES6浏览器,这意味着在Altair图表中显示的时间可能会加上时区偏移,除非您明确使用UTC时间(见使用UTC时间)。

以下图表将帮助您确定您的浏览器是否以 Altair 期望的方式解析日期:

import altair as alt
import pandas as pd

df = pd.DataFrame({'local': ['2018-01-01T00:00:00'],
                   'utc': ['2018-01-01T00:00:00Z']})
when_compliant = alt.when(compliant=True)

alt.Chart(df).transform_calculate(
    compliant="hours(datum.local) != hours(datum.utc) ? true : false",
).mark_text(size=20, baseline="middle").encode(
    text=when_compliant.then(alt.value("OK")).otherwise(alt.value("not OK")),
    color=when_compliant.then(alt.value("green")).otherwise(alt.value("red")),
).properties(width=80, height=50)

如果上述输出包含一个红色的“未通过”:

Click to show code
alt.Chart(df).mark_text(size=10, baseline='middle').encode(
    alt.TextValue('not OK'),
    alt.ColorValue('red')
).properties(width=40, height=25)

这意味着您的浏览器的日期解析不符合ES6标准。
如果它包含一个绿色的“确定”:

Click to show code
alt.Chart(df).mark_text(size=10, baseline='middle').encode(
    alt.TextValue('OK'),
    alt.ColorValue('green')
).properties(width=40, height=25)

那么这意味着你的浏览器解析日期的方式符合Altair的预期,要么是因为它符合ES6,要么是因为你的计算机区域设置恰好设置为UTC+0(GMT)时区。