Даты, время и часовые пояса

Работа с датами, временем и часовыми поясами часто оказывается одной из самых сложных частей анализа данных. В Altair сложность возрастает: код Python создаёт временные метки, сериализованные в JSON; JavaScript интерпретирует их, а браузер отображает. На каждом этапе возможны ошибки. Altair и Vega-Lite стараются обеспечить согласованную интерпретацию и визуализацию дат.

Даты и время в Altair и pandas

Altair лучше всего работает с временными рядами pandas. Стандартный столбец даты и времени без часового пояса в DataFrame pandas интерпретируется и отображается как локальное время пользователя. Например, возьмём почасовые измерения температуры в Сиэтле:

import altair as alt
from altair.datasets import data

temps = data.seattle_weather_hourly_normals()
temps.head()

Атрибут dtypes показывает, что время хранится в стандартном 64-битном формате datetime без указания часового пояса:

temps.dtypes

Построим визуализацию этих данных. Для наглядности ограничимся первыми двумя неделями:

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

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

Для даты и времени используется код T, обозначающий временное кодирование. Для входных данных datetime pandas он необязателен, но явно задавать тип полезно. Подробнее см. «Типы данных кодирования».

Чтобы четырёхзначные целые числа отображались как годы, сначала преобразуйте их в строки, а затем задайте временной тип. Подробнее см. «Тип данных и шкала оси».

Из даты и времени бывает полезно извлечь отдельные единицы: часы суток, дни месяца и другие. Для этого предназначено преобразование единиц времени. Например, построим тепловую карту с временем суток по оси 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('temperature:Q').title('temperature (C)')
)

В браузере с поддержкой ES6 (см. примечание о совместимости) часы на диаграмме начинаются с 00:00:00 первого января, как во входных данных. И исходные временные метки, и результат используют локальное время.

Указание часовых поясов

В поддерживаемом браузере (см. совместимость) время сериализуется и отображается как локальное: строка «1 января 00:00:00» отображается на диаграмме как 00:00 первого января.

В Altair даты без явно заданного часового пояса считаются локальными. Vega-Lite, если не указано иное, отображает время в локальном часовом поясе браузера.

Чтобы данные учитывали часовой пояс, задайте его во входном DataFrame. Сиэтл находится в поясе US/Pacific, поэтому временные метки можно локализовать в pandas так:

temps['date_pacific'] = temps['date'].dt.tz_localize('US/Pacific')
temps.dtypes

Теперь часовой пояс входит в тип данных 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('temperature:Q').title('temperature (C)')
)

На компьютере с часовым поясом западного побережья США диаграмма выглядит как первая версия. В другом часовом поясе применяется поправка, рассчитанная по региону, заданному в системе.

Использование 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('temperature:Q').title('temperature (C)')
)

Для максимальной переносимости, в том числе в браузерах без ES6, интерпретирующих даты без часового пояса как UTC, можно явно использовать 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('temperature:Q').title('temperature (C)')
)

Это менее удобно, чем стандартное поведение для дат без часового пояса, когда pandas и Vega-Lite считают время локальным, кроме браузеров без ES6 (см. совместимость). Но явное использование 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)

Если результат содержит красное «not OK»:

это означает, что обработка дат браузером не соответствует ES6. Если результат содержит зелёное «OK»:

браузер обрабатывает даты ожидаемым образом: либо он соответствует ES6, либо часовой пояс компьютера случайно установлен в UTC+0 (GMT).