Глава 7. Обработка дат и времени

7.0 Введение

Даты и время (datetimes) (например, время конкретной продажи или дата некоторой статистики общественного здравоохранения) часто встречаются при предварительной обработке для машинного обучения, будь то время конкретной продажи или год некоторой статистики общественного здравоохранения. Продольные данные (или данные временных рядов) — это данные, которые собираются повторно для одних и тех же переменных в течение времени. В этой главе мы создадим набор инструментов для обработки данных временных рядов, включая решение проблем с часовыми поясами и создание лаговых временных признаков. В частности, мы сосредоточимся на инструментах временных рядов в библиотеке pandas, которая централизует функциональность многих других общих библиотек, таких как datetime.

7.1 Преобразование строк в даты

Проблема

Учитывая вектор строк, представляющих даты и время, вы хотите преобразовать их в данные временных рядов.

Решение

Используйте to_datetime pandas с форматом даты и/или времени, указанным в параметре format:

# Load libraries
import numpy as np
import pandas as pd

# Create strings
date_strings = np.array(['03-04-2005 11:35 PM',
'23-05-2010 12:01 AM',
'04-09-2009 09:09 PM'])

# Convert to datetimes
[pd.to_datetime(date, format='%d-%m-%Y %I:%M %p') for date in date_strings]
[Timestamp('2005-04-03 23:35:00'),
Timestamp('2010-05-23 00:01:00'),
Timestamp('2009-09-04 21:09:00')]

Мы также можем добавить аргумент к параметру errors для обработки проблем:

# Convert to datetimes
[pd.to_datetime(date, format="%d-%m-%Y %I:%M %p", errors="coerce")
for date in date_strings]
[Timestamp('2005-04-03 23:35:00'),
Timestamp('2010-05-23 00:01:00'),
Timestamp('2009-09-04 21:09:00')]

Если errors="coerce", то любая проблема, возникающая, не вызовет ошибку (поведение по умолчанию), а вместо этого установит значение, вызывающее ошибку, в NaT (отсутствующее значение). Это позволяет работать с выбросами, заполняя их нулевыми значениями, в отличие от устранения ошибок для отдельных записей в данных.

Обсуждение

Когда даты и время приходят в виде строк, нам нужно преобразовать их в тип данных, который Python может понять. Хотя существует ряд инструментов Python для преобразования строк в datetime, следуя нашему использованию pandas в других рецептах, мы можем использовать to_datetime для выполнения преобразования. Одним из препятствий для строк, представляющих даты и время, является то, что формат строк может значительно варьироваться между источниками данных. Например, один вектор дат может представлять 23 марта 2015 года как «03-23-15», в то время как другой может использовать «3|23|2015». Мы можем использовать параметр format, чтобы точно указать формат строки. Вот некоторые распространенные коды форматирования даты и времени:

Code Description Example
%Y Full year 2001
%m Month w/ zero padding 04
%d Day of the month w/ zero padding 09
%I Hour (12hr clock) w/ zero padding 02
%p AM or PM AM
%M Minute w/ zero padding 05
%S Second w/ zero padding 09

См. также

  • Complete List of Python String Time Codes

7.2 Обработка часовых поясов

Проблема

У вас есть данные временных рядов, и вы хотите добавить или изменить информацию о часовом поясе.

Решение

Если не указано, объекты pandas не имеют часового пояса. Однако мы можем добавить часовой пояс, используя tz во время создания:

# Load library
import pandas as pd

# Create datetime
pd.Timestamp('2017-05-01 06:00:00', tz='Europe/London')
Timestamp('2017-05-01 06:00:00+0100', tz='Europe/London')

Мы можем добавить часовой пояс к ранее созданному datetime с помощью tz_localize:

# Create datetime
date = pd.Timestamp('2017-05-01 06:00:00')

# Set time zone
date_in_london = date.tz_localize('Europe/London')

# Show datetime
date_in_london
Timestamp('2017-05-01 06:00:00+0100', tz='Europe/London')

Мы также можем преобразовать в другой часовой пояс:

# Change time zone
date_in_london.tz_convert('Africa/Abidjan')
Timestamp('2017-05-01 05:00:00+0000', tz='Africa/Abidjan')

Наконец, объекты Series pandas могут применять tz_localize и tz_convert к каждому элементу:

# Create three dates
dates = pd.Series(pd.date_range('2/2/2002', periods=3, freq='M'))

# Set time zone
dates.dt.tz_localize('Africa/Abidjan')
0 2002-02-28 00:00:00+00:00
1 2002-03-31 00:00:00+00:00
2 2002-04-30 00:00:00+00:00
dtype: datetime64[ns, Africa/Abidjan]

Обсуждение

pandas поддерживает два набора строк, представляющих часовые пояса; однако я предлагаю использовать строки из библиотеки pytz. Мы можем увидеть все строки, используемые для представления часовых поясов, импортировав all_timezones:

# Load library
from pytz import all_timezones

# Show two time zones
all_timezones[0:2]
['Africa/Abidjan', 'Africa/Accra']

7.3 Выбор дат и времени

Проблема

У вас есть вектор дат, и вы хотите выбрать одну или несколько.

Решение

Используйте два булевых условия в качестве начальной и конечной дат:

# Load library
import pandas as pd

# Create data frame
dataframe = pd.DataFrame()
# Create datetimes
dataframe['date'] = pd.date_range('1/1/2001', periods=100000, freq='H')

# Select observations between two datetimes
dataframe[(dataframe['date'] > '2002-1-1 01:00:00') &
(dataframe['date'] <= '2002-1-1 04:00:00')]
date
8762 2002-01-01 02:00:00
8763 2002-01-01 03:00:00
8764 2002-01-01 04:00:00

Альтернативно, мы можем установить столбец даты в качестве индекса DataFrame, а затем нарезать с помощью loc:

# Set index
dataframe = dataframe.set_index(dataframe['date'])

# Select observations between two datetimes
dataframe.loc['2002-1-1 01:00:00':'2002-1-1 04:00:00']
date
date
2002-01-01 01:00:00 2002-01-01 01:00:00
2002-01-01 02:00:00 2002-01-01 02:00:00
2002-01-01 03:00:00 2002-01-01 03:00:00
2002-01-01 04:00:00 2002-01-01 04:00:00

Обсуждение

Использование булевых условий или нарезки по индексу зависит от ситуации. Если мы хотим выполнить сложные манипуляции с временными рядами, возможно, стоит затратить время на установку столбца даты в качестве индекса DataFrame, но если мы хотим выполнить простую обработку данных, булевы условия могут быть проще.

7.4 Разбиение данных дат на несколько признаков

Проблема

У вас есть столбец дат и времени, и вы хотите создать признаки для года, месяца, дня, часа и минуты.

Решение

Используйте временные свойства Series.dt pandas:

# Load library
import pandas as pd

# Create data frame
dataframe = pd.DataFrame()
# Create five dates
dataframe['date'] = pd.date_range('1/1/2001', periods=150, freq='W')

# Create features for year, month, day, hour, and minute
dataframe['year'] = dataframe['date'].dt.year
dataframe['month'] = dataframe['date'].dt.month
dataframe['day'] = dataframe['date'].dt.day
dataframe['hour'] = dataframe['date'].dt.hour
dataframe['minute'] = dataframe['date'].dt.minute

# Show three rows
dataframe.head(3)
date year month day hour minute
0 2001-01-07 2001 1 7 0 0
1 2001-01-14 2001 1 14 0 0
2 2001-01-21 2001 1 21 0 0

Обсуждение

Иногда полезно разбивать столбец дат на компоненты. Например, мы можем захотеть признак, который включает только год наблюдения, или мы можем захотеть рассмотреть только месяц некоторого наблюдения, чтобы мы могли сравнить их независимо от года.

7.5 Вычисление разницы между датами

Проблема

У вас есть два признака типа datetime, и вы хотите вычислить разницу во времени между ними для каждого наблюдения.

Решение

Вычтите два признака даты, используя pandas:

# Load library
import pandas as pd

# Create data frame
dataframe = pd.DataFrame()
# Create two datetime features
dataframe['Arrived'] = [pd.Timestamp('01-01-2017'), pd.Timestamp('01-04-2017')]
dataframe['Left'] = [pd.Timestamp('01-01-2017'), pd.Timestamp('01-06-2017')]

# Calculate duration between features
dataframe['Left'] - dataframe['Arrived']
0 0 days
1 2 days
dtype: timedelta64[ns]

Часто мы хотим удалить вывод days и оставить только числовое значение:

# Calculate duration between features
pd.Series(delta.days for delta in (dataframe['Left'] - dataframe['Arrived']))
0 0
1 2
dtype: int64

Обсуждение

Бывают случаи, когда признак, который мы хотим получить, — это изменение (дельта) между двумя точками во времени. Например, у нас могут быть даты заезда и выезда клиента из отеля, но признак, который мы хотим, — это продолжительность его пребывания. pandas упрощает этот расчет с помощью типа данных TimeDelta.

См. также

  • Pandas documentation: Time Deltas

7.6 Кодирование дней недели

Проблема

У вас есть вектор дат, и вы хотите узнать день недели для каждой даты.

Решение

Используйте метод day_name() Series.dt pandas:

# Load library
import pandas as pd

# Create dates
dates = pd.Series(pd.date_range("2/2/2002", periods=3, freq="M"))

# Show days of the week
dates.dt.day_name()
0 Thursday
1 Sunday
2 Tuesday
dtype: object

Если мы хотим получить числовое значение и, следовательно, более удобное для машинного обучения, мы можем использовать weekday, где дни недели представлены целыми числами (Понедельник — 0):

# Show days of the week
dates.dt.weekday
0 3
1 6
2 1
dtype: int64

Обсуждение

Знание дня недели может быть полезным, например, если мы хотим сравнить общие продажи по воскресеньям за последние три года. pandas упрощает создание вектора признаков, содержащего информацию о дне недели.

См. также

  • pandas Series datetimelike properties

7.7 Создание лагового признака

Проблема

Вы хотите создать признак, который лагирован на n периодов времени.

Решение

Используйте shift pandas:

# Load library
import pandas as pd

# Create data frame
dataframe = pd.DataFrame()
# Create data
dataframe["dates"] = pd.date_range("1/1/2001", periods=5, freq="D")
dataframe["stock_price"] = [1.1,2.2,3.3,4.4,5.5]

# Lagged values by one row
dataframe["previous_days_stock_price"] = dataframe["stock_price"].shift(1)

# Show data frame
dataframe
dates stock_price previous_days_stock_price
0 2001-01-01 1.1 NaN
1 2001-01-02 2.2 1.1
2 2001-01-03 3.3 2.2
3 2001-01-04 4.4 3.3
4 2001-01-05 5.5 4.4

Обсуждение

Очень часто данные основаны на регулярно расположенных временных периодах (например, каждый день, каждый час, каждые три часа), и мы заинтересованы в использовании прошлых значений для прогнозирования (это часто называется лагированием признака). Например, мы можем захотеть предсказать цену акции, используя цену, которая была за день до этого. С помощью pandas мы можем использовать shift для сдвига значений на одну строку, создавая новый признак, содержащий прошлые значения.

В нашем решении первая строка для previous_days_stock_price имеет пропущенное значение, потому что предыдущего значения stock_price нет.

7.8 Использование скользящих временных окон

Проблема

Имея данные временных рядов, вы хотите вычислить некоторую статистику для скользящего времени.

Решение

# Load library
import pandas as pd

# Create datetimes
time_index = pd.date_range("01/01/2010", periods=5, freq="M")

# Create data frame, set index
dataframe = pd.DataFrame(index=time_index)

# Create feature
dataframe["Stock_Price"] = [1,2,3,4,5]

# Calculate rolling mean
dataframe.rolling(window=2).mean()
Stock_Price
2010-01-31 NaN
2010-02-28 1.5
2010-03-31 2.5
2010-04-30 3.5
2010-05-31 4.5

Обсуждение

Скользящие (также называемые движущимися) временные окна концептуально просты, но могут быть трудны для понимания с первого раза. Представьте, что у нас есть ежемесячные наблюдения цены акции. Часто полезно иметь временное окно определенного количества месяцев, а затем перемещаться по наблюдениям, вычисляя статистику для всех наблюдений в этом временном окне.

Например, если у нас есть временное окно в три месяца, и мы хотим скользящее среднее, мы будем вычислять:

  1. среднее (Январь, Февраль, Март)

  2. среднее (Февраль, Март, Апрель)

  3. среднее (Март, Апрель, Май)

  4. и т.д.

Другой способ выразить это: наше трехмесячное временное окно «проходит» по наблюдениям, вычисляя среднее значение окна на каждом шаге.

rolling pandas позволяет нам указать размер окна с помощью window, а затем быстро вычислить некоторые общие статистики, включая максимальное значение (max()), среднее значение (mean()), количество значений (count()) и скользящую корреляцию (corr()).

Скользящие средние часто используются для сглаживания данных временных рядов, поскольку использование среднего значения всего временного окна ослабляет эффект краткосрочных колебаний.

См. также

  • pandas documentation: Rolling Windows

  • What are Moving Average or Smoothing Techniques?

7.9 Обработка пропущенных данных во временных рядах

Проблема

У вас есть пропущенные значения в данных временных рядов.

Решение

В дополнение к стратегиям обработки пропущенных данных, рассмотренным ранее, при наличии данных временных рядов мы можем использовать интерполяцию для заполнения пробелов, вызванных пропущенными значениями:

# Load libraries
import pandas as pd
import numpy as np

# Create date
time_index = pd.date_range("01/01/2010", periods=5, freq="M")

# Create data frame, set index
dataframe = pd.DataFrame(index=time_index)

# Create feature with a gap of missing values
dataframe["Sales"] = [1.0,2.0,np.nan,np.nan,5.0]

# Interpolate missing values
dataframe.interpolate()
Sales
2010-01-31 1.0
2010-02-28 2.0
2010-03-31 3.0
2010-04-30 4.0
2010-05-31 5.0

Альтернативно, мы можем заменить пропущенные значения последним известным значением (т.е. заполнением вперед):

# Forward-fill
dataframe.ffill()
Sales
2010-01-31 1.0
2010-02-28 2.0
2010-03-31 2.0
2010-04-30 2.0
2010-05-31 5.0

Мы также можем заменить пропущенные значения последним известным значением (т.е. заполнением назад):

# Back-fill
dataframe.bfill()
Sales
2010-01-31 1.0
2010-02-28 2.0
2010-03-31 5.0
2010-04-30 5.0
2010-05-31 5.0

Обсуждение

Интерполяция — это метод заполнения пробелов, вызванных пропущенными значениями, путем, по сути, проведения линии или кривой между известными значениями, граничащими с пробелом, и использования этой линии или кривой для предсказания разумных значений. Интерполяция может быть особенно полезна, когда временные интервалы между наблюдениями постоянны, данные не подвержены сильным колебаниям, а пробелы, вызванные пропущенными значениями, невелики. Например, в нашем решении пробел из двух пропущенных значений граничил с 2.0 и 5.0. Построив линию, начинающуюся с 2.0 и заканчивающуюся на 5.0, мы можем сделать разумные предположения для двух пропущенных значений в промежутке: 3.0 и 4.0.

Если мы считаем, что линия между двумя известными точками нелинейна, мы можем использовать метод interpolate для указания метода интерполяции:

# Interpolate missing values
dataframe.interpolate(method="quadratic")
Sales
2010-01-31 1.000000
2010-02-28 2.000000
2010-03-31 3.059808
2010-04-30 4.038069
2010-05-31 5.000000

Наконец, могут быть случаи, когда у нас есть большие пробелы в пропущенных значениях, и мы не хотим интерполировать значения по всему пробелу. В этих случаях мы можем использовать limit, чтобы ограничить количество интерполированных значений, и limit_direction, чтобы установить, следует ли интерполировать значения вперед от последнего известного значения перед пробелом или наоборот:

# Interpolate missing values
dataframe.interpolate(limit=1, limit_direction="forward")
Sales
2010-01-31 1.0
2010-02-28 2.0
2010-03-31 3.0
2010-04-30 NaN
2010-05-31 5.0

Заполнение назад и заполнение вперед можно рассматривать как форму наивной интерполяции, когда мы проводим плоскую линию от известного значения и используем ее для заполнения пропущенных значений. Одно (незначительное) преимущество заполнения назад и вперед перед интерполяцией заключается в отсутствии необходимости в известных значениях по обе стороны от пропущенного значения(ий).

Предыдущая глава    Следующая глава

Другие статьи по этой теме: