时间和日期#

处理日期、时间以及时区通常是数据分析中较具挑战性的一个方面。在 Altair 中,由于用户编写的是 Python 代码,输出 JSON 序列化的时间戳,这些时间戳由 Javascript 解析,然后由您的浏览器渲染,这使得困难加剧。在这些步骤中的每一步都可能出错,但 Altair 和 Vega-Lite 会尽力确保日期以一致的方式被解释和可视化。

Altair 和 pandas Datetimes#

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 可视化这些 datetime 数据;为了清晰起见,在此示例中,我们将数据限制在前两周

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

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

请注意,对于日期/时间值,我们使用 T 来指示时间编码:虽然对于 pandas datetime 输入来说这是可选的,但明确指定类型是一个好的习惯;更多讨论请参阅 编码数据类型。如果您想让 Altair 将四位整数绘制为年份,您需要在将数据类型更改为 temporal 之前将其转换为字符串,详细信息请参阅 数据类型对轴比例尺的影响

对于此类日期时间输入,有时提取特定的时间单位(例如一天中的小时、月份中的日期等)会很有用。在 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 这一行在图表中会渲染为 January 1st00:00

在 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 浏览器中),您可以在 pandas 端和 Vega-Lite 端明确地使用 UTC 时间

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 的现代浏览器,在这些浏览器中,没有尾随 "Z" 的时间字符串(例如 "2018-01-01T12:00:00")被视为本地时间,而不是 协调世界时 (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)

如果上述输出包含红色“不OK”

点击显示代码
alt.Chart(df).mark_text(size=10, baseline='middle').encode(
    alt.TextValue('not OK'),
    alt.ColorValue('red')
).properties(width=40, height=25)

则意味着您的浏览器的日期解析不符合 ES6 标准。如果它包含绿色“OK”

点击显示代码
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) 时区。