Spec-Zone.ru › pandas 0.19

Временные ряды/функциональность дат

Библиотека pandas зарекомендовала себя как очень успешный инструмент для работы с временными рядами, особенно в области финансового анализа данных. Используя типы данных NumPy datetime64 и timedelta64, мы объединили большое количество функций из других библиотек Python, таких как scikits.timeseries, а также создали огромное количество новых функций для работы с временными рядами.

При работе с временными рядами мы часто будем стремиться к:

  • генерации последовательностей дат и временных интервалов с фиксированной частотой
  • приведению или преобразованию временных рядов к определенной частоте
  • вычислению «относительных» дат на основе различных нестандартных временных интервалов (например, 5 рабочих дней до последнего рабочего дня года) или «продвижению» дат вперед или назад

pandas предоставляет относительно компактный и автономный набор инструментов для выполнения вышеперечисленных задач.

Создание диапазона дат:

# 72 hours starting with midnight Jan 1st, 2011
In [1]: rng = pd.date_range('1/1/2011', periods=72, freq='H')

In [2]: rng[:5]
Out[2]: 
DatetimeIndex(['2011-01-01 00:00:00', '2011-01-01 01:00:00',
               '2011-01-01 02:00:00', '2011-01-01 03:00:00',
               '2011-01-01 04:00:00'],
              dtype='datetime64[ns]', freq='H')

Индексирование объектов pandas с датами:

In [3]: ts = pd.Series(np.random.randn(len(rng)), index=rng)

In [4]: ts.head()
Out[4]: 
2011-01-01 00:00:00    0.469112
2011-01-01 01:00:00   -0.282863
2011-01-01 02:00:00   -1.509059
2011-01-01 03:00:00   -1.135632
2011-01-01 04:00:00    1.212112
Freq: H, dtype: float64

Изменение частоты и заполнение пробелов:

# to 45 minute frequency and forward fill
In [5]: converted = ts.asfreq('45Min', method='pad')

In [6]: converted.head()
Out[6]: 
2011-01-01 00:00:00    0.469112
2011-01-01 00:45:00    0.469112
2011-01-01 01:30:00   -0.282863
2011-01-01 02:15:00   -1.509059
2011-01-01 03:00:00   -1.135632
Freq: 45T, dtype: float64

Перевыборка:

# Daily means
In [7]: ts.resample('D').mean()
Out[7]: 
2011-01-01   -0.319569
2011-01-02   -0.337703
2011-01-03    0.117258
Freq: D, dtype: float64

Обзор

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

Класс Примечания Как создать
Timestamp Представляет собой единичную временную метку to_datetime, Timestamp
DatetimeIndex Индекс Timestamp to_datetime, date_range, DatetimeIndex
Period Представляет собой единичный временной интервал Period
PeriodIndex Индекс Period period_range, PeriodIndex

Временные метки против временных интервалов

Данные со временными метками — это наиболее базовый тип данных временных рядов, который связывает значения с точками во времени. Для объектов pandas это означает использование точек во времени.

In [8]: pd.Timestamp(datetime(2012, 5, 1))
Out[8]: Timestamp('2012-05-01 00:00:00')

In [9]: pd.Timestamp('2012-05-01')
Out[9]: Timestamp('2012-05-01 00:00:00')

In [10]: pd.Timestamp(2012, 5, 1)
Out[10]: Timestamp('2012-05-01 00:00:00')

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

Например:

In [11]: pd.Period('2011-01')
Out[11]: Period('2011-01', 'M')

In [12]: pd.Period('2012-05', freq='D')
Out[12]: Period('2012-05-01', 'D')

Timestamp и Period могут быть индексом. Списки Timestamp и Period автоматически преобразуются в DatetimeIndex и PeriodIndex соответственно.

In [13]: dates = [pd.Timestamp('2012-05-01'), pd.Timestamp('2012-05-02'), pd.Timestamp('2012-05-03')]

In [14]: ts = pd.Series(np.random.randn(3), dates)

In [15]: type(ts.index)
Out[15]: pandas.tseries.index.DatetimeIndex

In [16]: ts.index
Out[16]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[ns]', freq=None)

In [17]: ts
Out[17]: 
2012-05-01   -0.410001
2012-05-02   -0.078638
2012-05-03    0.545952
dtype: float64

In [18]: periods = [pd.Period('2012-01'), pd.Period('2012-02'), pd.Period('2012-03')]

In [19]: ts = pd.Series(np.random.randn(3), periods)

In [20]: type(ts.index)
Out[20]: pandas.tseries.period.PeriodIndex

In [21]: ts.index
Out[21]: PeriodIndex(['2012-01', '2012-02', '2012-03'], dtype='period[M]', freq='M')

In [22]: ts
Out[22]: 
2012-01   -1.219217
2012-02   -1.226825
2012-03    0.769804
Freq: M, dtype: float64

pandas позволяет захватывать оба представления и преобразовывать их друг в друга. Внутри pandas временные метки представляются экземплярами Timestamp, а последовательности временных меток — экземплярами DatetimeIndex. Для обычных временных интервалов pandas использует объекты Period для скалярных значений и PeriodIndex для последовательностей интервалов. Более лучшая поддержка нерегулярных интервалов с произвольными начальными и конечными точками будет в будущих выпусках.

Преобразование во временные метки

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

In [23]: pd.to_datetime(pd.Series(['Jul 31, 2009', '2010-01-10', None]))
Out[23]: 
0   2009-07-31
1   2010-01-10
2          NaT
dtype: datetime64[ns]

In [24]: pd.to_datetime(['2005/11/23', '2010.12.31'])
Out[24]: DatetimeIndex(['2005-11-23', '2010-12-31'], dtype='datetime64[ns]', freq=None)

Если вы используете даты, которые начинаются с дня (т. е. европейский стиль), вы можете передать флаг dayfirst.

In [25]: pd.to_datetime(['04-01-2012 10:00'], dayfirst=True)
Out[25]: DatetimeIndex(['2012-01-04 10:00:00'], dtype='datetime64[ns]', freq=None)

In [26]: pd.to_datetime(['14-01-2012', '01-14-2012'], dayfirst=True)
Out[26]: DatetimeIndex(['2012-01-14', '2012-01-14'], dtype='datetime64[ns]', freq=None)

Предупреждение

Как видно из приведенного выше примера, dayfirst не строгое, поэтому, если дату нельзя разобрать с учетом дня в первую очередь, она будет разобрана так, как если бы dayfirst было False.

Примечание

Указание аргумента format потенциально значительно ускорит преобразование, а в версиях, более поздних, чем 0.13.0, явное указание формата строки ‘%Y%m%d’ обеспечивает еще более быстрый путь.

Если вы передаете одну строку в to_datetime, она возвращает единственную Timestamp. Кроме того, Timestamp может принимать строковый ввод. Обратите внимание, что Timestamp не поддерживает вариант парсинга строк, как dayfirst или format, используйте to_datetime если они необходимы.

In [27]: pd.to_datetime('2010/11/12')
Out[27]: Timestamp('2010-11-12 00:00:00')

In [28]: pd.Timestamp('2010/11/12')
Out[28]: Timestamp('2010-11-12 00:00:00')

Введено в версии 0.18.1.

Вы также можете передать DataFrame целых или строковых столбцов для сборки в Series Timestamps.

In [29]: df = pd.DataFrame({'year': [2015, 2016],
   ....:                    'month': [2, 3],
   ....:                    'day': [4, 5],
   ....:                    'hour': [2, 3]})
   ....: 

In [30]: pd.to_datetime(df)
Out[30]: 
0   2015-02-04 02:00:00
1   2016-03-05 03:00:00
dtype: datetime64[ns]

Вы можете передать только необходимые для сборки столбцы.

In [31]: pd.to_datetime(df[['year', 'month', 'day']])
Out[31]: 
0   2015-02-04
1   2016-03-05
dtype: datetime64[ns]

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

  • обязательные: year, month, day
  • необязательные: hour, minute, second, millisecond, microsecond, nanosecond

Неверные данные

Примечание

В версии 0.17.0 значение по умолчанию для to_datetime теперь errors='raise', а не errors='ignore'. Это означает, что некорректный парсинг будет вызывать исключение, а не возвращать исходный ввод, как в предыдущих версиях.

Передайте errors='coerce' для преобразования неверных данных в NaT (не время):

Вызвать исключение при неразборчивости, это значение по умолчанию

In [2]: pd.to_datetime(['2009/07/31', 'asd'], errors='raise')
ValueError: Unknown string format

Возвратить исходный ввод при неразборчивости

In [4]: pd.to_datetime(['2009/07/31', 'asd'], errors='ignore')
Out[4]: array(['2009/07/31', 'asd'], dtype=object)

Возвратить NaT для ввода при неразборчивости

In [6]: pd.to_datetime(['2009/07/31', 'asd'], errors='coerce')
Out[6]: DatetimeIndex(['2009-07-31', 'NaT'], dtype='datetime64[ns]', freq=None)

Маркировки эпохи

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

Типичные единицы измерения эпохи

In [32]: pd.to_datetime([1349720105, 1349806505, 1349892905,
   ....:                 1349979305, 1350065705], unit='s')
   ....: 
Out[32]: 
DatetimeIndex(['2012-10-08 18:15:05', '2012-10-09 18:15:05',
               '2012-10-10 18:15:05', '2012-10-11 18:15:05',
               '2012-10-12 18:15:05'],
              dtype='datetime64[ns]', freq=None)

In [33]: pd.to_datetime([1349720105100, 1349720105200, 1349720105300,
   ....:                 1349720105400, 1349720105500 ], unit='ms')
   ....: 
Out[33]: 
DatetimeIndex(['2012-10-08 18:15:05.100000', '2012-10-08 18:15:05.200000',
               '2012-10-08 18:15:05.300000', '2012-10-08 18:15:05.400000',
               '2012-10-08 18:15:05.500000'],
              dtype='datetime64[ns]', freq=None)

Эти работают, но результаты могут быть неожиданными.

In [34]: pd.to_datetime([1])
Out[34]: DatetimeIndex(['1970-01-01 00:00:00.000000001'], dtype='datetime64[ns]', freq=None)

In [35]: pd.to_datetime([1, 3.14], unit='s')
Out[35]: DatetimeIndex(['1970-01-01 00:00:01', '1970-01-01 00:00:03.140000'], dtype='datetime64[ns]', freq=None)

Примечание

Временные метки эпохи будут округляться до ближайшей наносекунды.

Генерация диапазонов временных меток

Для генерации индекса с временными метками можно использовать конструктор DatetimeIndex или Index и передать список объектов datetime:

In [36]: dates = [datetime(2012, 5, 1), datetime(2012, 5, 2), datetime(2012, 5, 3)]

# Note the frequency information
In [37]: index = pd.DatetimeIndex(dates)

In [38]: index
Out[38]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[ns]', freq=None)

# Automatically converted to DatetimeIndex
In [39]: index = pd.Index(dates)

In [40]: index
Out[40]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[ns]', freq=None)

На практике это становится очень громоздким, поскольку нам часто нужен очень длинный индекс с большим количеством временных меток. Если нам нужны временные метки с регулярной частотой, мы можем использовать функции pandas date_range и bdate_range для создания индексов временных меток.

In [41]: index = pd.date_range('2000-1-1', periods=1000, freq='M')

In [42]: index
Out[42]: 
DatetimeIndex(['2000-01-31', '2000-02-29', '2000-03-31', '2000-04-30',
               '2000-05-31', '2000-06-30', '2000-07-31', '2000-08-31',
               '2000-09-30', '2000-10-31',
               ...
               '2082-07-31', '2082-08-31', '2082-09-30', '2082-10-31',
               '2082-11-30', '2082-12-31', '2083-01-31', '2083-02-28',
               '2083-03-31', '2083-04-30'],
              dtype='datetime64[ns]', length=1000, freq='M')

In [43]: index = pd.bdate_range('2012-1-1', periods=250)

In [44]: index
Out[44]: 
DatetimeIndex(['2012-01-02', '2012-01-03', '2012-01-04', '2012-01-05',
               '2012-01-06', '2012-01-09', '2012-01-10', '2012-01-11',
               '2012-01-12', '2012-01-13',
               ...
               '2012-12-03', '2012-12-04', '2012-12-05', '2012-12-06',
               '2012-12-07', '2012-12-10', '2012-12-11', '2012-12-12',
               '2012-12-13', '2012-12-14'],
              dtype='datetime64[ns]', length=250, freq='B')

Функции-удобства, такие как date_range и bdate_range, используют различные псевдонимы частоты. Значение частоты по умолчанию для date_range — это календарный день, а значение по умолчанию для bdate_range — это рабочий день

In [45]: start = datetime(2011, 1, 1)

In [46]: end = datetime(2012, 1, 1)

In [47]: rng = pd.date_range(start, end)

In [48]: rng
Out[48]: 
DatetimeIndex(['2011-01-01', '2011-01-02', '2011-01-03', '2011-01-04',
               '2011-01-05', '2011-01-06', '2011-01-07', '2011-01-08',
               '2011-01-09', '2011-01-10',
               ...
               '2011-12-23', '2011-12-24', '2011-12-25', '2011-12-26',
               '2011-12-27', '2011-12-28', '2011-12-29', '2011-12-30',
               '2011-12-31', '2012-01-01'],
              dtype='datetime64[ns]', length=366, freq='D')

In [49]: rng = pd.bdate_range(start, end)

In [50]: rng
Out[50]: 
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
               '2011-01-07', '2011-01-10', '2011-01-11', '2011-01-12',
               '2011-01-13', '2011-01-14',
               ...
               '2011-12-19', '2011-12-20', '2011-12-21', '2011-12-22',
               '2011-12-23', '2011-12-26', '2011-12-27', '2011-12-28',
               '2011-12-29', '2011-12-30'],
              dtype='datetime64[ns]', length=260, freq='B')

date_range и bdate_range позволяют легко сгенерировать диапазон дат с помощью различных комбинаций параметров, таких как start, end, periods, и freq:

In [51]: pd.date_range(start, end, freq='BM')
Out[51]: 
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29',
               '2011-05-31', '2011-06-30', '2011-07-29', '2011-08-31',
               '2011-09-30', '2011-10-31', '2011-11-30', '2011-12-30'],
              dtype='datetime64[ns]', freq='BM')

In [52]: pd.date_range(start, end, freq='W')
Out[52]: 
DatetimeIndex(['2011-01-02', '2011-01-09', '2011-01-16', '2011-01-23',
               '2011-01-30', '2011-02-06', '2011-02-13', '2011-02-20',
               '2011-02-27', '2011-03-06', '2011-03-13', '2011-03-20',
               '2011-03-27', '2011-04-03', '2011-04-10', '2011-04-17',
               '2011-04-24', '2011-05-01', '2011-05-08', '2011-05-15',
               '2011-05-22', '2011-05-29', '2011-06-05', '2011-06-12',
               '2011-06-19', '2011-06-26', '2011-07-03', '2011-07-10',
               '2011-07-17', '2011-07-24', '2011-07-31', '2011-08-07',
               '2011-08-14', '2011-08-21', '2011-08-28', '2011-09-04',
               '2011-09-11', '2011-09-18', '2011-09-25', '2011-10-02',
               '2011-10-09', '2011-10-16', '2011-10-23', '2011-10-30',
               '2011-11-06', '2011-11-13', '2011-11-20', '2011-11-27',
               '2011-12-04', '2011-12-11', '2011-12-18', '2011-12-25',
               '2012-01-01'],
              dtype='datetime64[ns]', freq='W-SUN')

In [53]: pd.bdate_range(end=end, periods=20)
Out[53]: 
DatetimeIndex(['2011-12-05', '2011-12-06', '2011-12-07', '2011-12-08',
               '2011-12-09', '2011-12-12', '2011-12-13', '2011-12-14',
               '2011-12-15', '2011-12-16', '2011-12-19', '2011-12-20',
               '2011-12-21', '2011-12-22', '2011-12-23', '2011-12-26',
               '2011-12-27', '2011-12-28', '2011-12-29', '2011-12-30'],
              dtype='datetime64[ns]', freq='B')

In [54]: pd.bdate_range(start=start, periods=20)
Out[54]: 
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
               '2011-01-07', '2011-01-10', '2011-01-11', '2011-01-12',
               '2011-01-13', '2011-01-14', '2011-01-17', '2011-01-18',
               '2011-01-19', '2011-01-20', '2011-01-21', '2011-01-24',
               '2011-01-25', '2011-01-26', '2011-01-27', '2011-01-28'],
              dtype='datetime64[ns]', freq='B')

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

Ограничения временных меток

Поскольку pandas представляет временные метки с разрешением в наносекунды, временной интервал, который можно представить с помощью 64-битного целого числа, ограничен примерно 584 годами:

In [55]: pd.Timestamp.min
Out[55]: Timestamp('1677-09-21 00:12:43.145225')

In [56]: pd.Timestamp.max
Out[56]: Timestamp('2262-04-11 23:47:16.854775807')

См. здесь способы представления данных за пределами этих границ.

DatetimeIndex

Одно из основных применений DatetimeIndex — это индекс для объектов pandas. Класс DatetimeIndex содержит много оптимизаций, связанных с временными рядами:

  • Большой диапазон дат для различных смещений предварительно вычисляется и кешируется под капотом, чтобы ускорить генерацию последующих диапазонов дат (достаточно получить срез)
  • Быстрое сдвижение с помощью метода shift и tshift для объектов pandas
  • Объединение перекрывающихся объектов DatetimeIndex с одинаковой частотой очень быстро (важно для быстрого выравнивания данных)
  • Быстрый доступ к полям дат через свойства, такие как year, month, и т. д.
  • Функции регуляризации, такие как snap и очень быстрая логика asof

Объекты DatetimeIndex имеют все основные функции обычных объектов Index и множество расширенных методов, специфичных для временных рядов, для удобной обработки частоты.

См. также

Методы переиндексации

Примечание

Хотя pandas не требует наличия отсортированного индекса дат, некоторые из этих методов могут иметь неожиданное или некорректное поведение, если даты не отсортированы. Поэтому будьте осторожны.

DatetimeIndex можно использовать как обычный индекс и предлагает все его интеллектуальные функции, такие как выбор, срез и т. д.

In [57]: rng = pd.date_range(start, end, freq='BM')

In [58]: ts = pd.Series(np.random.randn(len(rng)), index=rng)

In [59]: ts.index
Out[59]: 
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29',
               '2011-05-31', '2011-06-30', '2011-07-29', '2011-08-31',
               '2011-09-30', '2011-10-31', '2011-11-30', '2011-12-30'],
              dtype='datetime64[ns]', freq='BM')

In [60]: ts[:5].index
Out[60]: 
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29',
               '2011-05-31'],
              dtype='datetime64[ns]', freq='BM')

In [61]: ts[::2].index
Out[61]: 
DatetimeIndex(['2011-01-31', '2011-03-31', '2011-05-31', '2011-07-29',
               '2011-09-30', '2011-11-30'],
              dtype='datetime64[ns]', freq='2BM')

Частичное индексирование строк DatetimeIndex

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

In [62]: ts['1/31/2011']
Out[62]: -1.2812473076599531

In [63]: ts[datetime(2011, 12, 25):]
Out[63]: 
2011-12-30    0.687738
Freq: BM, dtype: float64

In [64]: ts['10/31/2011':'12/31/2011']
Out[64]: 
2011-10-31    0.149748
2011-11-30   -0.732339
2011-12-30    0.687738
Freq: BM, dtype: float64

Для обеспечения удобства доступа к более длинным временным рядам вы также можете передавать год или год и месяц как строки:

In [65]: ts['2011']
Out[65]: 
2011-01-31   -1.281247
2011-02-28   -0.727707
2011-03-31   -0.121306
2011-04-29   -0.097883
2011-05-31    0.695775
2011-06-30    0.341734
2011-07-29    0.959726
2011-08-31   -1.110336
2011-09-30   -0.619976
2011-10-31    0.149748
2011-11-30   -0.732339
2011-12-30    0.687738
Freq: BM, dtype: float64

In [66]: ts['2011-6']
Out[66]: 
2011-06-30    0.341734
Freq: BM, dtype: float64

Этот тип среза будет работать и с DataFrame с DateTimeIndex в качестве индекса. Поскольку частичный выбор по строкам является формой выбора по меткам, конечные точки будут включены. Это включало бы совпадение времен в указанную дату. Вот пример:

In [67]: dft = pd.DataFrame(randn(100000,1),
   ....:                    columns=['A'],
   ....:                    index=pd.date_range('20130101',periods=100000,freq='T'))
   ....: 

In [68]: dft
Out[68]: 
                            A
2013-01-01 00:00:00  0.176444
2013-01-01 00:01:00  0.403310
2013-01-01 00:02:00 -0.154951
2013-01-01 00:03:00  0.301624
2013-01-01 00:04:00 -2.179861
2013-01-01 00:05:00 -1.369849
2013-01-01 00:06:00 -0.954208
...                       ...
2013-03-11 10:33:00 -0.293083
2013-03-11 10:34:00 -0.059881
2013-03-11 10:35:00  1.252450
2013-03-11 10:36:00  0.046611
2013-03-11 10:37:00  0.059478
2013-03-11 10:38:00 -0.286539
2013-03-11 10:39:00  0.841669

[100000 rows x 1 columns]

In [69]: dft['2013']
Out[69]: 
                            A
2013-01-01 00:00:00  0.176444
2013-01-01 00:01:00  0.403310
2013-01-01 00:02:00 -0.154951
2013-01-01 00:03:00  0.301624
2013-01-01 00:04:00 -2.179861
2013-01-01 00:05:00 -1.369849
2013-01-01 00:06:00 -0.954208
...                       ...
2013-03-11 10:33:00 -0.293083
2013-03-11 10:34:00 -0.059881
2013-03-11 10:35:00  1.252450
2013-03-11 10:36:00  0.046611
2013-03-11 10:37:00  0.059478
2013-03-11 10:38:00 -0.286539
2013-03-11 10:39:00  0.841669

[100000 rows x 1 columns]

Это начинается с самого первого времени в месяце и включает последнее время в месяце.

In [70]: dft['2013-1':'2013-2']
Out[70]: 
                            A
2013-01-01 00:00:00  0.176444
2013-01-01 00:01:00  0.403310
2013-01-01 00:02:00 -0.154951
2013-01-01 00:03:00  0.301624
2013-01-01 00:04:00 -2.179861
2013-01-01 00:05:00 -1.369849
2013-01-01 00:06:00 -0.954208
...                       ...
2013-02-28 23:53:00  0.103114
2013-02-28 23:54:00 -1.303422
2013-02-28 23:55:00  0.451943
2013-02-28 23:56:00  0.220534
2013-02-28 23:57:00 -1.624220
2013-02-28 23:58:00  0.093915
2013-02-28 23:59:00 -1.087454

[84960 rows x 1 columns]

Это указывает конечное время, которое включает все времена в последний день

In [71]: dft['2013-1':'2013-2-28']
Out[71]: 
                            A
2013-01-01 00:00:00  0.176444
2013-01-01 00:01:00  0.403310
2013-01-01 00:02:00 -0.154951
2013-01-01 00:03:00  0.301624
2013-01-01 00:04:00 -2.179861
2013-01-01 00:05:00 -1.369849
2013-01-01 00:06:00 -0.954208
...                       ...
2013-02-28 23:53:00  0.103114
2013-02-28 23:54:00 -1.303422
2013-02-28 23:55:00  0.451943
2013-02-28 23:56:00  0.220534
2013-02-28 23:57:00 -1.624220
2013-02-28 23:58:00  0.093915
2013-02-28 23:59:00 -1.087454

[84960 rows x 1 columns]

Это указывает точное конечное время (и не то же самое, что выше)

In [72]: dft['2013-1':'2013-2-28 00:00:00']
Out[72]: 
                            A
2013-01-01 00:00:00  0.176444
2013-01-01 00:01:00  0.403310
2013-01-01 00:02:00 -0.154951
2013-01-01 00:03:00  0.301624
2013-01-01 00:04:00 -2.179861
2013-01-01 00:05:00 -1.369849
2013-01-01 00:06:00 -0.954208
...                       ...
2013-02-27 23:54:00  0.897051
2013-02-27 23:55:00 -0.309230
2013-02-27 23:56:00  1.944713
2013-02-27 23:57:00  0.369265
2013-02-27 23:58:00  0.053071
2013-02-27 23:59:00 -0.019734
2013-02-28 00:00:00  1.388189

[83521 rows x 1 columns]

Мы останавливаемся на включенной конечной точке, так как она является частью индекса

In [73]: dft['2013-1-15':'2013-1-15 12:30:00']
Out[73]: 
                            A
2013-01-15 00:00:00  0.501288
2013-01-15 00:01:00 -0.605198
2013-01-15 00:02:00  0.215146
2013-01-15 00:03:00  0.924732
2013-01-15 00:04:00 -2.228519
2013-01-15 00:05:00  1.517331
2013-01-15 00:06:00 -1.188774
...                       ...
2013-01-15 12:24:00  1.358314
2013-01-15 12:25:00 -0.737727
2013-01-15 12:26:00  1.838323
2013-01-15 12:27:00 -0.774090
2013-01-15 12:28:00  0.622261
2013-01-15 12:29:00 -0.631649
2013-01-15 12:30:00  0.193284

[751 rows x 1 columns]

Предупреждение

Следующий выбор вызовет KeyError; в противном случае эта методология выбора будет несовместима с другими методами выбора в pandas (так как это не срез, и он не разрешается в него)

dft['2013-1-15 12:30:00']

Для выбора одной строки используйте .loc

In [74]: dft.loc['2013-1-15 12:30:00']
Out[74]: 
A    0.193284
Name: 2013-01-15 12:30:00, dtype: float64

Новая в версии 0.18.0.

Частичный строковый индекс DateTimeIndex также работает с DataFrame с MultiIndex. Например:

In [75]: dft2 = pd.DataFrame(np.random.randn(20, 1),
   ....:                     columns=['A'],
   ....:                     index=pd.MultiIndex.from_product([pd.date_range('20130101',
   ....:                                                                     periods=10,
   ....:                                                                     freq='12H'),
   ....:                                                      ['a', 'b']]))
   ....: 

In [76]: dft2
Out[76]: 
                              A
2013-01-01 00:00:00 a -0.659574
                    b  1.494522
2013-01-01 12:00:00 a -0.778425
                    b -0.253355
2013-01-02 00:00:00 a -2.816159
                    b -1.210929
2013-01-02 12:00:00 a  0.144669
...                         ...
2013-01-04 00:00:00 b -1.624463
2013-01-04 12:00:00 a  0.056912
                    b  0.149867
2013-01-05 00:00:00 a -1.256173
                    b  2.324544
2013-01-05 12:00:00 a -1.067396
                    b -0.660996

[20 rows x 1 columns]

In [77]: dft2.loc['2013-01-05']
Out[77]: 
                              A
2013-01-05 00:00:00 a -1.256173
                    b  2.324544
2013-01-05 12:00:00 a -1.067396
                    b -0.660996

In [78]: idx = pd.IndexSlice

In [79]: dft2 = dft2.swaplevel(0, 1).sort_index()

In [80]: dft2.loc[idx[:, '2013-01-05'], :]
Out[80]: 
                              A
a 2013-01-05 00:00:00 -1.256173
  2013-01-05 12:00:00 -1.067396
b 2013-01-05 00:00:00  2.324544
  2013-01-05 12:00:00 -0.660996

Индексирование по дате и времени

Индексирование DateTimeIndex частичной строкой зависит от «точности» периода, другими словами, от того, насколько специфичен интервал по отношению к частоте индекса. В отличие от этого, индексирование с помощью объектов datetime является точным, так как объекты имеют точное значение. Они также следуют семантике включения обоих конечных точек.

Эти datetime объекты являются специфическими hours, minutes, и seconds, даже если они не были явно указаны (они являются 0).

In [81]: dft[datetime(2013, 1, 1):datetime(2013,2,28)]
Out[81]: 
                            A
2013-01-01 00:00:00  0.176444
2013-01-01 00:01:00  0.403310
2013-01-01 00:02:00 -0.154951
2013-01-01 00:03:00  0.301624
2013-01-01 00:04:00 -2.179861
2013-01-01 00:05:00 -1.369849
2013-01-01 00:06:00 -0.954208
...                       ...
2013-02-27 23:54:00  0.897051
2013-02-27 23:55:00 -0.309230
2013-02-27 23:56:00  1.944713
2013-02-27 23:57:00  0.369265
2013-02-27 23:58:00  0.053071
2013-02-27 23:59:00 -0.019734
2013-02-28 00:00:00  1.388189

[83521 rows x 1 columns]

Без значений по умолчанию.

In [82]: dft[datetime(2013, 1, 1, 10, 12, 0):datetime(2013, 2, 28, 10, 12, 0)]
Out[82]: 
                            A
2013-01-01 10:12:00 -0.246733
2013-01-01 10:13:00 -1.429225
2013-01-01 10:14:00 -1.265339
2013-01-01 10:15:00  0.710986
2013-01-01 10:16:00 -0.818200
2013-01-01 10:17:00  0.543542
2013-01-01 10:18:00  1.577713
...                       ...
2013-02-28 10:06:00  0.311249
2013-02-28 10:07:00  2.366080
2013-02-28 10:08:00 -0.490372
2013-02-28 10:09:00  0.373340
2013-02-28 10:10:00  0.638442
2013-02-28 10:11:00  1.330135
2013-02-28 10:12:00 -0.945450

[83521 rows x 1 columns]

Усечение и индексирование с помощью произвольных значений

Предоставлена удобная функция усечения, которая эквивалентна срезу:

In [83]: ts.truncate(before='10/31/2011', after='12/31/2011')
Out[83]: 
2011-10-31    0.149748
2011-11-30   -0.732339
2011-12-30    0.687738
Freq: BM, dtype: float64

Даже сложное индексирование с произвольными значениями, нарушающее регулярность частоты DatetimeIndex, приведет к DatetimeIndex (но частота теряется):

In [84]: ts[[0, 2, 6]].index
Out[84]: DatetimeIndex(['2011-01-31', '2011-03-31', '2011-07-29'], dtype='datetime64[ns]', freq=None)

Компоненты даты и времени

Существует несколько свойств даты и времени, которые можно получить из Timestamp или коллекции временных меток, например, DateTimeIndex.

Свойство Описание
year Год даты и времени
month Месяц даты и времени
day День даты и времени
hour Час даты и времени
minute Минуты даты и времени
second Секунды даты и времени
microsecond Микросекунды даты и времени
nanosecond Наносекунды даты и времени
date Возвращает datetime.date (не содержит информации о часовом поясе)
time Возвращает datetime.time (не содержит информации о часовом поясе)
dayofyear Порядковый день года
weekofyear Порядковый номер недели года
week Порядковый номер недели года
dayofweek Номер дня недели, где понедельник=0, воскресенье=6
weekday Номер дня недели, где понедельник=0, воскресенье=6
weekday_name Название дня недели (например, пятница)
quarter Квартал даты: Янв=Мар = 1, Апр-Июн = 2 и т. д.
days_in_month Количество дней в месяце даты и времени
is_month_start Логическое значение, указывающее, является ли это первым днем месяца (определяется частотой)
is_month_end Логическое значение, указывающее, является ли это последним днем месяца (определяется частотой)
is_quarter_start Логическое значение, указывающее, является ли это первым днем квартала (определяется частотой)
is_quarter_end Логическое значение, указывающее, является ли это последним днем квартала (определяется частотой)
is_year_start Логическое значение, указывающее, является ли это первым днем года (определяется частотой)
is_year_end Логическое значение, указывающее, является ли это последним днем года (определяется частотой)
is_leap_year Логическое значение, указывающее, принадлежит ли дата високосному году

Кроме того, если у вас есть Series со значениями datetimelike, то вы можете получить доступ к этим свойствам через .dt аксессор, см. документацию

Объекты DateOffset

В предыдущих примерах мы создавали объекты DatetimeIndex с различными частотами, передавая в freq ключевое слово строки частоты, такие как ‘M’, ‘W’, и ‘BM. Под капотом эти строки частоты переводятся в экземпляр DateOffset pandas, который представляет собой регулярное увеличение частоты. Специфическая логика смещения, такая как «месяц», «рабочий день» или «один час», представлена в различных подклассах.

Имя класса Описание
DateOffset Общий класс смещения, по умолчанию равен 1 календарному дню
BDay рабочий день (день недели)
CDay настраиваемый рабочий день (экспериментально)
Week одна неделя, необязательно привязанная к дню недели
WeekOfMonth x-й день y-й недели каждого месяца
LastWeekOfMonth x-й день последней недели каждого месяца
MonthEnd конец календарного месяца
MonthBegin начало календарного месяца
BMonthEnd конец рабочего месяца
BMonthBegin начало рабочего месяца
CBMonthEnd конец настраиваемого рабочего месяца
CBMonthBegin начало настраиваемого рабочего месяца
SemiMonthEnd 15-е число (или другой day_of_month) и конец календарного месяца
SemiMonthBegin 15-е число (или другой day_of_month) и начало календарного месяца
QuarterEnd конец календарного квартала
QuarterBegin начало календарного квартала
BQuarterEnd конец рабочего квартала
BQuarterBegin начало рабочего квартала
FY5253Quarter розничный (также известный как 52-53 недельный) квартал
YearEnd конец календарного года
YearBegin начало календарного года
BYearEnd конец рабочего года
BYearBegin начало рабочего года
FY5253 розничный (также известный как 52-53 недельный) год
BusinessHour рабочий час
CustomBusinessHour настраиваемый рабочий час
Hour один час
Minute одна минута
Second одна секунда
Milli одна миллисекунда
Micro одна микросекунда
Nano одна наносекунда

Базовый DateOffset принимает те же аргументы, что и dateutil.relativedelta, который работает следующим образом:

In [85]: d = datetime(2008, 8, 18, 9, 0)

In [86]: d + relativedelta(months=4, days=5)
Out[86]: datetime.datetime(2008, 12, 23, 9, 0)

Мы могли бы сделать то же самое с DateOffset:

In [87]: from pandas.tseries.offsets import *

In [88]: d + DateOffset(months=4, days=5)
Out[88]: Timestamp('2008-12-23 09:00:00')

Ключевые особенности объекта DateOffset:

  • его можно добавлять/вычитать к/из объекта datetime, чтобы получить сдвинутую дату
  • его можно умножать на целое число (положительное или отрицательное), чтобы смещение применялось несколько раз
  • он имеет rollforward и rollback методы для перемещения даты вперед или назад к следующей или предыдущей «дате смещения»

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

class BDay(DateOffset):
    """DateOffset increments between business days"""
    def apply(self, other):
        ...
In [89]: d - 5 * BDay()
Out[89]: Timestamp('2008-08-11 09:00:00')

In [90]: d + BMonthEnd()
Out[90]: Timestamp('2008-08-29 09:00:00')

Методы rollforward и rollback делают ровно то, что вы ожидаете:

In [91]: d
Out[91]: datetime.datetime(2008, 8, 18, 9, 0)

In [92]: offset = BMonthEnd()

In [93]: offset.rollforward(d)
Out[93]: Timestamp('2008-08-29 09:00:00')

In [94]: offset.rollback(d)
Out[94]: Timestamp('2008-07-31 09:00:00')

Стоит обязательно изучить модуль pandas.tseries.offsets и различные строки документации для классов.

Эти операции (apply, rollforward и rollback) по умолчанию сохраняют информацию о времени (час, минута и т. д.). Для сброса времени используйте normalize=True ключевое слово при создании экземпляра смещения. Если normalize=True, результат нормализуется после применения функции.

In [95]: day = Day()

In [96]: day.apply(pd.Timestamp('2014-01-01 09:00'))
Out[96]: Timestamp('2014-01-02 09:00:00')

In [97]: day = Day(normalize=True)

In [98]: day.apply(pd.Timestamp('2014-01-01 09:00'))
Out[98]: Timestamp('2014-01-02 00:00:00')

In [99]: hour = Hour()

In [100]: hour.apply(pd.Timestamp('2014-01-01 22:00'))
Out[100]: Timestamp('2014-01-01 23:00:00')

In [101]: hour = Hour(normalize=True)

In [102]: hour.apply(pd.Timestamp('2014-01-01 22:00'))
Out[102]: Timestamp('2014-01-01 00:00:00')

In [103]: hour.apply(pd.Timestamp('2014-01-01 23:00'))
Out[103]: Timestamp('2014-01-02 00:00:00')

Параметрические смещения

Некоторые смещения могут быть «параметризованы» при создании, что приводит к различным результатам. Например, смещение Week для генерации еженедельных данных принимает параметр weekday, который приводит к тому, что сгенерированные даты всегда находятся в определенный день недели:

In [104]: d
Out[104]: datetime.datetime(2008, 8, 18, 9, 0)

In [105]: d + Week()
Out[105]: Timestamp('2008-08-25 09:00:00')

In [106]: d + Week(weekday=4)
Out[106]: Timestamp('2008-08-22 09:00:00')

In [107]: (d + Week(weekday=4)).weekday()
Out[107]: 4

In [108]: d - Week()
Out[108]: Timestamp('2008-08-11 09:00:00')

normalize параметр будет действовать для сложения и вычитания.

In [109]: d + Week(normalize=True)
Out[109]: Timestamp('2008-08-25 00:00:00')

In [110]: d - Week(normalize=True)
Out[110]: Timestamp('2008-08-11 00:00:00')

Другой пример — параметризация YearEnd с конкретным конечным месяцем:

In [111]: d + YearEnd()
Out[111]: Timestamp('2008-12-31 09:00:00')

In [112]: d + YearEnd(month=6)
Out[112]: Timestamp('2009-06-30 09:00:00')

Использование смещений с Series / DatetimeIndex

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

In [113]: rng = pd.date_range('2012-01-01', '2012-01-03')

In [114]: s = pd.Series(rng)

In [115]: rng
Out[115]: DatetimeIndex(['2012-01-01', '2012-01-02', '2012-01-03'], dtype='datetime64[ns]', freq='D')

In [116]: rng + DateOffset(months=2)
Out[116]: DatetimeIndex(['2012-03-01', '2012-03-02', '2012-03-03'], dtype='datetime64[ns]', freq='D')

In [117]: s + DateOffset(months=2)
Out[117]: 
0   2012-03-01
1   2012-03-02
2   2012-03-03
dtype: datetime64[ns]

In [118]: s - DateOffset(months=2)
Out[118]: 
0   2011-11-01
1   2011-11-02
2   2011-11-03
dtype: datetime64[ns]

Если класс смещения напрямую соответствует Timedelta (Day, Hour, Minute, Second, Micro, Milli, Nano), его можно использовать точно так же, как Timedelta — см. раздел Временные интервалы для получения дополнительных примеров.

In [119]: s - Day(2)
Out[119]: 
0   2011-12-30
1   2011-12-31
2   2012-01-01
dtype: datetime64[ns]

In [120]: td = s - pd.Series(pd.date_range('2011-12-29', '2011-12-31'))

In [121]: td
Out[121]: 
0   3 days
1   3 days
2   3 days
dtype: timedelta64[ns]

In [122]: td + Minute(15)
Out[122]: 
0   3 days 00:15:00
1   3 days 00:15:00
2   3 days 00:15:00
dtype: timedelta64[ns]

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

In [123]: rng + BQuarterEnd()
Out[123]: DatetimeIndex(['2012-03-30', '2012-03-30', '2012-03-30'], dtype='datetime64[ns]', freq=None)

Настраиваемые рабочие дни (Экспериментально)

Класс CDay или CustomBusinessDay предоставляет параметрический BusinessDay класс, который можно использовать для создания настраиваемых календарей рабочих дней, учитывающих местные праздники и местные выходные.

В качестве интересного примера рассмотрим Египет, где выходные — пятница и суббота.

In [124]: from pandas.tseries.offsets import CustomBusinessDay

In [125]: weekmask_egypt = 'Sun Mon Tue Wed Thu'

# They also observe International Workers' Day so let's
# add that for a couple of years
In [126]: holidays = ['2012-05-01', datetime(2013, 5, 1), np.datetime64('2014-05-01')]

In [127]: bday_egypt = CustomBusinessDay(holidays=holidays, weekmask=weekmask_egypt)

In [128]: dt = datetime(2013, 4, 30)

In [129]: dt + 2 * bday_egypt
Out[129]: Timestamp('2013-05-05 00:00:00')

Давайте отобразим дни недели

In [130]: dts = pd.date_range(dt, periods=5, freq=bday_egypt)

In [131]: pd.Series(dts.weekday, dts).map(pd.Series('Mon Tue Wed Thu Fri Sat Sun'.split()))
Out[131]: 
2013-04-30    Tue
2013-05-02    Thu
2013-05-05    Sun
2013-05-06    Mon
2013-05-07    Tue
Freq: C, dtype: object

С версии v0.14 календари праздников можно использовать для предоставления списка праздников. Подробнее см. раздел календарь праздников.

In [132]: from pandas.tseries.holiday import USFederalHolidayCalendar

In [133]: bday_us = CustomBusinessDay(calendar=USFederalHolidayCalendar())

# Friday before MLK Day
In [134]: dt = datetime(2014, 1, 17)

# Tuesday after MLK Day (Monday is skipped because it's a holiday)
In [135]: dt + bday_us
Out[135]: Timestamp('2014-01-21 00:00:00')

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

In [136]: from pandas.tseries.offsets import CustomBusinessMonthBegin

In [137]: bmth_us = CustomBusinessMonthBegin(calendar=USFederalHolidayCalendar())

# Skip new years
In [138]: dt = datetime(2013, 12, 17)

In [139]: dt + bmth_us
Out[139]: Timestamp('2014-01-02 00:00:00')

# Define date index with custom offset
In [140]: pd.DatetimeIndex(start='20100101',end='20120101',freq=bmth_us)
Out[140]: 
DatetimeIndex(['2010-01-04', '2010-02-01', '2010-03-01', '2010-04-01',
               '2010-05-03', '2010-06-01', '2010-07-01', '2010-08-02',
               '2010-09-01', '2010-10-01', '2010-11-01', '2010-12-01',
               '2011-01-03', '2011-02-01', '2011-03-01', '2011-04-01',
               '2011-05-02', '2011-06-01', '2011-07-01', '2011-08-01',
               '2011-09-01', '2011-10-03', '2011-11-01', '2011-12-01'],
              dtype='datetime64[ns]', freq='CBMS')

Примечание

Строка частоты ‘C’ используется для указания того, что используется смещение даты CustomBusinessDay. Важно отметить, что так как CustomBusinessDay является параметризованным типом, экземпляры CustomBusinessDay могут отличаться, и это не обнаруживается из строки частоты ‘C’. Поэтому пользователь должен гарантировать, что строка частоты ‘C’ используется последовательно в приложении пользователя.

Часы работы

Класс BusinessHour предоставляет представление часов работы BusinessDay, позволяя использовать конкретные начальное и конечное время.

По умолчанию, BusinessHour использует часы работы с 9:00 до 17:00. Добавление BusinessHour увеличит Timestamp на час. Если целевое значение Timestamp находится вне часов работы, переместитесь к ближайшим часам работы, а затем увеличьте его. Если результат превышает конечное время работы, остаток добавляется к следующему рабочему дню.

In [141]: bh = BusinessHour()

In [142]: bh
Out[142]: <BusinessHour: BH=09:00-17:00>

# 2014-08-01 is Friday
In [143]: pd.Timestamp('2014-08-01 10:00').weekday()
Out[143]: 4

In [144]: pd.Timestamp('2014-08-01 10:00') + bh
Out[144]: Timestamp('2014-08-01 11:00:00')

# Below example is the same as: pd.Timestamp('2014-08-01 09:00') + bh
In [145]: pd.Timestamp('2014-08-01 08:00') + bh
Out[145]: Timestamp('2014-08-01 10:00:00')

# If the results is on the end time, move to the next business day
In [146]: pd.Timestamp('2014-08-01 16:00') + bh
Out[146]: Timestamp('2014-08-04 09:00:00')

# Remainings are added to the next day
In [147]: pd.Timestamp('2014-08-01 16:30') + bh
Out[147]: Timestamp('2014-08-04 09:30:00')

# Adding 2 business hours
In [148]: pd.Timestamp('2014-08-01 10:00') + BusinessHour(2)
Out[148]: Timestamp('2014-08-01 12:00:00')

# Subtracting 3 business hours
In [149]: pd.Timestamp('2014-08-01 10:00') + BusinessHour(-3)
Out[149]: Timestamp('2014-07-31 15:00:00')

Также вы можете указать время start и end с помощью ключевых слов. Аргумент должен быть str, который имеет представление hour:minute или экземпляр datetime.time. Указание секунд, микросекунд и наносекунд как часов работы приводит к ValueError.

In [150]: bh = BusinessHour(start='11:00', end=time(20, 0))

In [151]: bh
Out[151]: <BusinessHour: BH=11:00-20:00>

In [152]: pd.Timestamp('2014-08-01 13:00') + bh
Out[152]: Timestamp('2014-08-01 14:00:00')

In [153]: pd.Timestamp('2014-08-01 09:00') + bh
Out[153]: Timestamp('2014-08-01 12:00:00')

In [154]: pd.Timestamp('2014-08-01 18:00') + bh
Out[154]: Timestamp('2014-08-01 19:00:00')

Передача времени start позже, чем end представляет полночь в часах работы. В этом случае часы работы превышают полночь и перекрываются со следующим днем. Действительные часы работы определяются тем, начались ли они с действительного BusinessDay.

In [155]: bh = BusinessHour(start='17:00', end='09:00')

In [156]: bh
Out[156]: <BusinessHour: BH=17:00-09:00>

In [157]: pd.Timestamp('2014-08-01 17:00') + bh
Out[157]: Timestamp('2014-08-01 18:00:00')

In [158]: pd.Timestamp('2014-08-01 23:00') + bh
Out[158]: Timestamp('2014-08-02 00:00:00')

# Although 2014-08-02 is Satuaday,
# it is valid because it starts from 08-01 (Friday).
In [159]: pd.Timestamp('2014-08-02 04:00') + bh
Out[159]: Timestamp('2014-08-02 05:00:00')

# Although 2014-08-04 is Monday,
# it is out of business hours because it starts from 08-03 (Sunday).
In [160]: pd.Timestamp('2014-08-04 04:00') + bh
Out[160]: Timestamp('2014-08-04 18:00:00')

Применение BusinessHour.rollforward и rollback к внерабочим часам приводит к началу ближайших рабочих часов или концу предыдущего дня. В отличие от других смещений, BusinessHour.rollforward может выдавать разные результаты, чем apply по определению.

Это происходит потому, что конец часов работы одного дня равен началу часов работы следующего дня. Например, при стандартных часах работы (9:00 - 17:00) нет разрыва (0 минут) между 2014-08-01 17:00 и 2014-08-04 09:00.

# This adjusts a Timestamp to business hour edge
In [161]: BusinessHour().rollback(pd.Timestamp('2014-08-02 15:00'))
Out[161]: Timestamp('2014-08-01 17:00:00')

In [162]: BusinessHour().rollforward(pd.Timestamp('2014-08-02 15:00'))
Out[162]: Timestamp('2014-08-04 09:00:00')

# It is the same as BusinessHour().apply(pd.Timestamp('2014-08-01 17:00')).
# And it is the same as BusinessHour().apply(pd.Timestamp('2014-08-04 09:00'))
In [163]: BusinessHour().apply(pd.Timestamp('2014-08-02 15:00'))
Out[163]: Timestamp('2014-08-04 10:00:00')

# BusinessDay results (for reference)
In [164]: BusinessHour().rollforward(pd.Timestamp('2014-08-02'))
Out[164]: Timestamp('2014-08-04 09:00:00')

# It is the same as BusinessDay().apply(pd.Timestamp('2014-08-01'))
# The result is the same as rollworward because BusinessDay never overlap.
In [165]: BusinessHour().apply(pd.Timestamp('2014-08-02'))
Out[165]: Timestamp('2014-08-04 10:00:00')

BusinessHour считает субботу и воскресенье выходными. Чтобы использовать произвольные выходные дни, вы можете использовать смещение CustomBusinessHour, см. Специальные часы работы:

Специальные часы работы

Новое в версии 0.18.1.

CustomBusinessHour представляет собой сочетание BusinessHour и CustomBusinessDay, что позволяет указать произвольные выходные дни. CustomBusinessHour работает так же, как BusinessHour за исключением пропуска указанных специальных выходных.

In [166]: from pandas.tseries.holiday import USFederalHolidayCalendar

In [167]: bhour_us = CustomBusinessHour(calendar=USFederalHolidayCalendar())

# Friday before MLK Day
In [168]: dt = datetime(2014, 1, 17, 15)

In [169]: dt + bhour_us
Out[169]: Timestamp('2014-01-17 16:00:00')

# Tuesday after MLK Day (Monday is skipped because it's a holiday)
In [170]: dt + bhour_us * 2
Out[170]: Timestamp('2014-01-21 09:00:00')

Вы можете использовать ключевые аргументы, поддерживаемые BusinessHour и CustomBusinessDay.

In [171]: bhour_mon = CustomBusinessHour(start='10:00', weekmask='Tue Wed Thu Fri')

# Monday is skipped because it's a holiday, business hour starts from 10:00
In [172]: dt + bhour_mon * 2
Out[172]: Timestamp('2014-01-21 10:00:00')

Псевдонимы смещений

Для полезных общих частот временных рядов задано несколько строковых псевдонимов. Мы будем называть эти псевдонимы псевдонимами смещения (ранее назывались правилами времени до версии v0.8.0).

Псевдоним Описание
B частота рабочих дней
C частота специальных рабочих дней (экспериментально)
D частота календарных дней
W недельная частота
M частота конца месяца
SM частота конца полумесяца (15-е и конец месяца)
BM частота конца рабочего месяца
CBM частота конца специального рабочего месяца
MS частота начала месяца
SMS частота начала полумесяца (1-е и 15-е)
BMS частота начала рабочего месяца
CBMS частота начала специального рабочего месяца
Q частота конца квартала
BQ частота конца квартала рабочих дней
QS частота начала квартала
BQS частота начала квартала рабочих дней
A частота конца года
BA частота конца года рабочих дней
AS частота начала года
BAS частота начала года рабочих дней
BH частота рабочих часов
H частота в час
T, min частота в минуту
S частота в секунду
L, ms миллисекунды
U, us микросекунды
N наносекунды

Комбинирование псевдонимов

Как мы видели ранее, псевдоним и экземпляр смещения взаимозаменяемы в большинстве функций:

In [173]: pd.date_range(start, periods=5, freq='B')
Out[173]: 
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
               '2011-01-07'],
              dtype='datetime64[ns]', freq='B')

In [174]: pd.date_range(start, periods=5, freq=BDay())
Out[174]: 
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
               '2011-01-07'],
              dtype='datetime64[ns]', freq='B')

Вы можете комбинировать смещения дней и внутридневных смещений:

In [175]: pd.date_range(start, periods=10, freq='2h20min')
Out[175]: 
DatetimeIndex(['2011-01-01 00:00:00', '2011-01-01 02:20:00',
               '2011-01-01 04:40:00', '2011-01-01 07:00:00',
               '2011-01-01 09:20:00', '2011-01-01 11:40:00',
               '2011-01-01 14:00:00', '2011-01-01 16:20:00',
               '2011-01-01 18:40:00', '2011-01-01 21:00:00'],
              dtype='datetime64[ns]', freq='140T')

In [176]: pd.date_range(start, periods=10, freq='1D10U')
Out[176]: 
DatetimeIndex([       '2011-01-01 00:00:00', '2011-01-02 00:00:00.000010',
               '2011-01-03 00:00:00.000020', '2011-01-04 00:00:00.000030',
               '2011-01-05 00:00:00.000040', '2011-01-06 00:00:00.000050',
               '2011-01-07 00:00:00.000060', '2011-01-08 00:00:00.000070',
               '2011-01-09 00:00:00.000080', '2011-01-10 00:00:00.000090'],
              dtype='datetime64[ns]', freq='86400000010U')

Якорные смещения

Для некоторых частот вы можете указать суффикс якорной точки:

Псевдоним Описание
W-SUN недельная частота (воскресенья). То же самое, что и ‘W’
W-MON недельная частота (понедельники)
W-TUE недельная частота (вторники)
W-WED недельная частота (среды)
W-THU недельная частота (четверги)
W-FRI недельная частота (пятницы)
W-SAT недельная частота (субботы)
(B)Q(S)-DEC квартальная частота, конец года в декабре. То же самое, что и ‘Q’
(B)Q(S)-JAN квартальная частота, конец года в январе
(B)Q(S)-FEB квартальная частота, конец года в феврале
(B)Q(S)-MAR квартальная частота, конец года в марте
(B)Q(S)-APR квартальная частота, конец года в апреле
(B)Q(S)-MAY квартальная частота, конец года в мае
(B)Q(S)-JUN квартальная частота, конец года в июне
(B)Q(S)-JUL квартальная частота, конец года в июле
(B)Q(S)-AUG квартальная частота, конец года в августе
(B)Q(S)-SEP квартальная частота, конец года в сентябре
(B)Q(S)-OCT квартальная частота, конец года в октябре
(B)Q(S)-NOV квартальная частота, конец года в ноябре
(B)A(S)-DEC годовая частота, якорная точка в конце декабря. То же самое, что и ‘A’
(B)A(S)-JAN годовая частота, якорная точка в конце января
(B)A(S)-FEB годовая частота, якорная точка в конце февраля
(B)A(S)-MAR годовая частота, якорная точка в конце марта
(B)A(S)-APR годовая частота, якорная точка в конце апреля
(B)A(S)-MAY годовая частота, якорная точка в конце мая
(B)A(S)-JUN годовая частота, якорная точка в конце июня
(B)A(S)-JUL годовая частота, якорная точка в конце июля
(B)A(S)-AUG годовая частота, якорная точка в конце августа
(B)A(S)-SEP годовая частота, якорная точка в конце сентября
(B)A(S)-OCT годовая частота, якорная точка в конце октября
(B)A(S)-NOV годовая частота, якорная точка в конце ноября

Эти значения могут быть использованы в качестве аргументов date_range, bdate_range, конструкторов для DatetimeIndex, а также в различных других функциях временных рядов в pandas.

Семантика якорных смещений

Для смещений, закрепленных к началу или концу определённой частоты (MonthEnd, MonthBegin, WeekEnd, и т. д.), применяются следующие правила для прокрутки вперёд и назад.

Когда n не равно 0, если заданная дата не совпадает с точкой якорной точки, она перемещается к ближайшей точке якорной точки, и затем перемещается на |n|-1 дополнительных шагов вперёд или назад.

In [177]: pd.Timestamp('2014-01-02') + MonthBegin(n=1)
Out[177]: Timestamp('2014-02-01 00:00:00')

In [178]: pd.Timestamp('2014-01-02') + MonthEnd(n=1)
Out[178]: Timestamp('2014-01-31 00:00:00')

In [179]: pd.Timestamp('2014-01-02') - MonthBegin(n=1)
Out[179]: Timestamp('2014-01-01 00:00:00')

In [180]: pd.Timestamp('2014-01-02') - MonthEnd(n=1)
Out[180]: Timestamp('2013-12-31 00:00:00')

In [181]: pd.Timestamp('2014-01-02') + MonthBegin(n=4)
Out[181]: Timestamp('2014-05-01 00:00:00')

In [182]: pd.Timestamp('2014-01-02') - MonthBegin(n=4)
Out[182]: Timestamp('2013-10-01 00:00:00')

Если заданная дата совпадает с точкой якорной точки, она перемещается на |n| точек вперёд или назад.

In [183]: pd.Timestamp('2014-01-01') + MonthBegin(n=1)
Out[183]: Timestamp('2014-02-01 00:00:00')

In [184]: pd.Timestamp('2014-01-31') + MonthEnd(n=1)
Out[184]: Timestamp('2014-02-28 00:00:00')

In [185]: pd.Timestamp('2014-01-01') - MonthBegin(n=1)
Out[185]: Timestamp('2013-12-01 00:00:00')

In [186]: pd.Timestamp('2014-01-31') - MonthEnd(n=1)
Out[186]: Timestamp('2013-12-31 00:00:00')

In [187]: pd.Timestamp('2014-01-01') + MonthBegin(n=4)
Out[187]: Timestamp('2014-05-01 00:00:00')

In [188]: pd.Timestamp('2014-01-31') - MonthBegin(n=4)
Out[188]: Timestamp('2013-10-01 00:00:00')

В случае, когда n=0, дата не перемещается, если она находится на якорной точке, иначе она переносится на следующую якорную точку.

In [189]: pd.Timestamp('2014-01-02') + MonthBegin(n=0)
Out[189]: Timestamp('2014-02-01 00:00:00')

In [190]: pd.Timestamp('2014-01-02') + MonthEnd(n=0)
Out[190]: Timestamp('2014-01-31 00:00:00')

In [191]: pd.Timestamp('2014-01-01') + MonthBegin(n=0)
Out[191]: Timestamp('2014-01-01 00:00:00')

In [192]: pd.Timestamp('2014-01-31') + MonthEnd(n=0)
Out[192]: Timestamp('2014-01-31 00:00:00')

Праздники/Календари праздников

Праздники и календари предоставляют простой способ определения правил праздников для использования с CustomBusinessDay или в других анализах, требующих предварительно определённого набора праздничных дней. Класс AbstractHolidayCalendar предоставляет все необходимые методы для возврата списка праздничных дней, и только rules необходимо определить в конкретном классе календаря праздников. Кроме того, атрибуты класса start_date и end_date определяют диапазон дат, для которого генерируются праздничные дни. Они должны быть переопределены в классе AbstractHolidayCalendar для применения диапазона ко всем подклассам календаря. USFederalHolidayCalendar — единственный существующий календарь, который главным образом служит примером для разработки других календарей.

Для праздников, которые происходят в фиксированные даты (например, День памяти США или 4 июля), правило соблюдения определяет, когда этот праздник отмечается, если он выпадает на выходной или какой-либо другой нерабочий день. Определённые правила соблюдения:

Правило Описание
nearest_workday переместить субботу на пятницу, а воскресенье на понедельник
sunday_to_monday перенести воскресенье на следующий понедельник
next_monday_or_tuesday переместить субботу на понедельник, а воскресенье/понедельник на вторник
previous_friday переместить субботу и воскресенье на предыдущую пятницу
next_monday переместить субботу и воскресенье на следующий понедельник

Пример того, как определяются праздники и календари праздников:

In [193]: from pandas.tseries.holiday import Holiday, USMemorialDay,\
   .....:     AbstractHolidayCalendar, nearest_workday, MO
   .....: 

In [194]: class ExampleCalendar(AbstractHolidayCalendar):
   .....:     rules = [
   .....:         USMemorialDay,
   .....:         Holiday('July 4th', month=7, day=4, observance=nearest_workday),
   .....:         Holiday('Columbus Day', month=10, day=1,
   .....:             offset=DateOffset(weekday=MO(2))), #same as 2*Week(weekday=2)
   .....:         ]
   .....: 

In [195]: cal = ExampleCalendar()

In [196]: cal.holidays(datetime(2012, 1, 1), datetime(2012, 12, 31))
Out[196]: DatetimeIndex(['2012-05-28', '2012-07-04', '2012-10-08'], dtype='datetime64[ns]', freq=None)

Используя этот календарь, создание индекса или выполнение арифметических операций со смещениями пропускает выходные и праздничные дни (то есть День памяти/4 июля). Например, ниже определено пользовательское смещение рабочего дня, использующее ExampleCalendar. Как и любое другое смещение, его можно использовать для создания DatetimeIndex или добавить к datetime или Timestamp объектам.

In [197]: from pandas.tseries.offsets import CDay

In [198]: pd.DatetimeIndex(start='7/1/2012', end='7/10/2012',
   .....:     freq=CDay(calendar=cal)).to_pydatetime()
   .....: 
Out[198]: 
array([datetime.datetime(2012, 7, 2, 0, 0),
       datetime.datetime(2012, 7, 3, 0, 0),
       datetime.datetime(2012, 7, 5, 0, 0),
       datetime.datetime(2012, 7, 6, 0, 0),
       datetime.datetime(2012, 7, 9, 0, 0),
       datetime.datetime(2012, 7, 10, 0, 0)], dtype=object)

In [199]: offset = CustomBusinessDay(calendar=cal)

In [200]: datetime(2012, 5, 25) + offset
Out[200]: Timestamp('2012-05-29 00:00:00')

In [201]: datetime(2012, 7, 3) + offset
Out[201]: Timestamp('2012-07-05 00:00:00')

In [202]: datetime(2012, 7, 3) + 2 * offset
Out[202]: Timestamp('2012-07-06 00:00:00')

In [203]: datetime(2012, 7, 6) + offset
Out[203]: Timestamp('2012-07-09 00:00:00')

Диапазоны определяются атрибутами класса start_date и end_date класса AbstractHolidayCalendar. Значения по умолчанию приведены ниже.

In [204]: AbstractHolidayCalendar.start_date
Out[204]: Timestamp('1970-01-01 00:00:00')

In [205]: AbstractHolidayCalendar.end_date
Out[205]: Timestamp('2030-12-31 00:00:00')

Эти даты можно переопределить, установив атрибуты как datetime/Timestamp/строку.

In [206]: AbstractHolidayCalendar.start_date = datetime(2012, 1, 1)

In [207]: AbstractHolidayCalendar.end_date = datetime(2012, 12, 31)

In [208]: cal.holidays()
Out[208]: DatetimeIndex(['2012-05-28', '2012-07-04', '2012-10-08'], dtype='datetime64[ns]', freq=None)

Каждый класс календаря доступен по имени с помощью функции get_calendar, которая возвращает экземпляр класса выходных. Любой импортированный класс календаря будет автоматически доступен с помощью этой функции. Также, HolidayCalendarFactory предоставляет простой интерфейс для создания календарей, которые являются комбинациями календарей или календарей с дополнительными правилами.

In [209]: from pandas.tseries.holiday import get_calendar, HolidayCalendarFactory,\
   .....:     USLaborDay
   .....: 

In [210]: cal = get_calendar('ExampleCalendar')

In [211]: cal.rules
Out[211]: 
[Holiday: MemorialDay (month=5, day=31, offset=<DateOffset: kwds={'weekday': MO(-1)}>),
 Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7ff271135aa0>),
 Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: kwds={'weekday': MO(+2)}>)]

In [212]: new_cal = HolidayCalendarFactory('NewExampleCalendar', cal, USLaborDay)

In [213]: new_cal.rules
Out[213]: 
[Holiday: Labor Day (month=9, day=1, offset=<DateOffset: kwds={'weekday': MO(+1)}>),
 Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: kwds={'weekday': MO(+2)}>),
 Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7ff271135aa0>),
 Holiday: MemorialDay (month=5, day=31, offset=<DateOffset: kwds={'weekday': MO(-1)}>)]

Методы экземпляров, связанные с временными рядами

Сдвиг / запаздывание

Иногда нужно сдвинуть или замедлить значения во временном ряду вперед или назад во времени. Метод для этого — shift, который доступен для всех объектов pandas.

In [214]: ts = ts[:5]

In [215]: ts.shift(1)
Out[215]: 
2011-01-31         NaN
2011-02-28   -1.281247
2011-03-31   -0.727707
2011-04-29   -0.121306
2011-05-31   -0.097883
Freq: BM, dtype: float64

Метод shift принимает аргумент freq, который может принимать класс DateOffset, или другой объект, подобный timedelta, или также псевдоним сдвига псевдоним сдвига:

In [216]: ts.shift(5, freq=offsets.BDay())
Out[216]: 
2011-02-07   -1.281247
2011-03-07   -0.727707
2011-04-07   -0.121306
2011-05-06   -0.097883
2011-06-07    0.695775
dtype: float64

In [217]: ts.shift(5, freq='BM')
Out[217]: 
2011-06-30   -1.281247
2011-07-29   -0.727707
2011-08-31   -0.121306
2011-09-30   -0.097883
2011-10-31    0.695775
Freq: BM, dtype: float64

Вместо изменения выравнивания данных и индекса, объекты DataFrame и Series также имеют удобный метод tshift, который изменяет все даты в индексе на указанное количество сдвигов:

In [218]: ts.tshift(5, freq='D')
Out[218]: 
2011-02-05   -1.281247
2011-03-05   -0.727707
2011-04-05   -0.121306
2011-05-04   -0.097883
2011-06-05    0.695775
dtype: float64

Обратите внимание, что с tshift, ведущая запись больше не NaN, потому что данные не перевыравниваются.

Преобразование частоты

Основная функция для изменения частот — функция asfreq. Для DatetimeIndex, это в основном просто тонкий, но удобный обертка над reindex, которая генерирует date_range и вызывает reindex.

In [219]: dr = pd.date_range('1/1/2010', periods=3, freq=3 * offsets.BDay())

In [220]: ts = pd.Series(randn(3), index=dr)

In [221]: ts
Out[221]: 
2010-01-01    0.532005
2010-01-06    0.544874
2010-01-11   -1.001788
Freq: 3B, dtype: float64

In [222]: ts.asfreq(BDay())
Out[222]: 
2010-01-01    0.532005
2010-01-04         NaN
2010-01-05         NaN
2010-01-06    0.544874
2010-01-07         NaN
2010-01-08         NaN
2010-01-11   -1.001788
Freq: B, dtype: float64

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

In [223]: ts.asfreq(BDay(), method='pad')
Out[223]: 
2010-01-01    0.532005
2010-01-04    0.532005
2010-01-05    0.532005
2010-01-06    0.544874
2010-01-07    0.544874
2010-01-08    0.544874
2010-01-11   -1.001788
Freq: B, dtype: float64

Заполнение вперед / назад

Связанные с asfreq и reindex функция fillna, описанная в разделе пропущенные данные.

Преобразование в Python datetime

DatetimeIndex может быть преобразован в массив объектов Python native datetime.datetime с помощью метода to_pydatetime.

Перевыборка

Предупреждение

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

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

.resample() — это группировка по времени, за которой следует метод уменьшения для каждой из ее групп.

Начиная с версии 0.18.1, функцию resample() можно использовать непосредственно из объектов DataFrameGroupBy, см. документацию по groupby.

Примечание

.resample() аналогично использованию операции .rolling() с временным сдвигом, см. обсуждение here <stats.moments.ts-versus-resampling>

См. некоторые примеры в руководстве для некоторых продвинутых стратегий

In [224]: rng = pd.date_range('1/1/2012', periods=100, freq='S')

In [225]: ts = pd.Series(np.random.randint(0, 500, len(rng)), index=rng)

In [226]: ts.resample('5Min').sum()
Out[226]: 
2012-01-01    24390
Freq: 5T, dtype: int64

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

Параметр how может быть именем функции или функцией numpy-массива, которая принимает массив и генерирует агрегированные значения:

In [227]: ts.resample('5Min').mean()
Out[227]: 
2012-01-01    243.9
Freq: 5T, dtype: float64

In [228]: ts.resample('5Min').ohlc()
Out[228]: 
            open  high  low  close
2012-01-01   161   495    1    245

In [229]: ts.resample('5Min').max()
Out[229]: 
2012-01-01    495
Freq: 5T, dtype: int64

Любая функция, доступная через диспетчеризацию, может быть указана по имени для параметра how, включая sum, mean, std, sem, max, min, median, first, last, ohlc.

Для уменьшения выборки параметр closed можно установить в ‘left’ или ‘right’, чтобы указать, какой конец интервала закрыт:

In [230]: ts.resample('5Min', closed='right').mean()
Out[230]: 
2011-12-31 23:55:00    161.000000
2012-01-01 00:00:00    244.737374
Freq: 5T, dtype: float64

In [231]: ts.resample('5Min', closed='left').mean()
Out[231]: 
2012-01-01    243.9
Freq: 5T, dtype: float64

Параметры, такие как label и loffset, используются для управления метками результата. label указывает, метятся ли результаты началом или концом интервала. loffset выполняет временную корректировку меток результата.

In [232]: ts.resample('5Min').mean() # by default label='right'
Out[232]: 
2012-01-01    243.9
Freq: 5T, dtype: float64

In [233]: ts.resample('5Min', label='left').mean()
Out[233]: 
2012-01-01    243.9
Freq: 5T, dtype: float64

In [234]: ts.resample('5Min', label='left', loffset='1s').mean()
Out[234]: 
2012-01-01 00:00:01    243.9
dtype: float64

Параметр axis может быть установлен в 0 или 1 и позволяет перевыбирать указанную ось для DataFrame.

kind может быть установлен в ‘timestamp’ или ‘period’, чтобы преобразовать результирующий индекс в/из представления временных меток и временных интервалов. По умолчанию resample сохраняет исходное представление.

convention может быть установлен в ‘start’ или ‘end’ при перевыборке данных периода (подробности ниже). Он определяет, как периоды низкой частоты преобразуются в периоды высокой частоты.

Увеличение выборки

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

# from secondly to every 250 milliseconds
In [235]: ts[:2].resample('250L').asfreq()
Out[235]: 
2012-01-01 00:00:00.000    161.0
2012-01-01 00:00:00.250      NaN
2012-01-01 00:00:00.500      NaN
2012-01-01 00:00:00.750      NaN
2012-01-01 00:00:01.000    199.0
Freq: 250L, dtype: float64

In [236]: ts[:2].resample('250L').ffill()
Out[236]: 
2012-01-01 00:00:00.000    161
2012-01-01 00:00:00.250    161
2012-01-01 00:00:00.500    161
2012-01-01 00:00:00.750    161
2012-01-01 00:00:01.000    199
Freq: 250L, dtype: int64

In [237]: ts[:2].resample('250L').ffill(limit=2)
Out[237]: 
2012-01-01 00:00:00.000    161.0
2012-01-01 00:00:00.250    161.0
2012-01-01 00:00:00.500    161.0
2012-01-01 00:00:00.750      NaN
2012-01-01 00:00:01.000    199.0
Freq: 250L, dtype: float64

Редкая перевыборка

Редкие временные ряды — это ряды, в которых у вас значительно меньше точек, чем интервал времени, который вы хотите перевыбрать. Непосредственное увеличение выборки редкого ряда может потенциально генерировать множество промежуточных значений. Когда вы не хотите использовать метод для заполнения этих значений, например, если fill_method равно None, то промежуточные значения будут заполнены NaN.

Поскольку resample представляет собой группировку по времени, следующий метод позволяет эффективно перевыбирать только те группы, которые не все NaN

In [238]: rng = pd.date_range('2014-1-1', periods=100, freq='D') + pd.Timedelta('1s')

In [239]: ts = pd.Series(range(100), index=rng)

Если мы хотим перевыбрать весь диапазон ряда

In [240]: ts.resample('3T').sum()
Out[240]: 
2014-01-01 00:00:00     0.0
2014-01-01 00:03:00     NaN
2014-01-01 00:06:00     NaN
2014-01-01 00:09:00     NaN
2014-01-01 00:12:00     NaN
2014-01-01 00:15:00     NaN
2014-01-01 00:18:00     NaN
                       ... 
2014-04-09 23:42:00     NaN
2014-04-09 23:45:00     NaN
2014-04-09 23:48:00     NaN
2014-04-09 23:51:00     NaN
2014-04-09 23:54:00     NaN
2014-04-09 23:57:00     NaN
2014-04-10 00:00:00    99.0
Freq: 3T, dtype: float64

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

In [241]: from functools import partial

In [242]: from pandas.tseries.frequencies import to_offset

In [243]: def round(t, freq):
   .....:     freq = to_offset(freq)
   .....:     return pd.Timestamp((t.value // freq.delta.value) * freq.delta.value)
   .....: 

In [244]: ts.groupby(partial(round, freq='3T')).sum()
Out[244]: 
2014-01-01     0
2014-01-02     1
2014-01-03     2
2014-01-04     3
2014-01-05     4
2014-01-06     5
2014-01-07     6
              ..
2014-04-04    93
2014-04-05    94
2014-04-06    95
2014-04-07    96
2014-04-08    97
2014-04-09    98
2014-04-10    99
dtype: int64

Агрегирование

Аналогично агрегированию в groupby и функциям окна, Resampler можно выборочно перевыбирать.

При перевыборке DataFrame, по умолчанию будет применяться та же функция ко всем столбцам.

In [245]: df = pd.DataFrame(np.random.randn(1000, 3),
   .....:                   index=pd.date_range('1/1/2012', freq='S', periods=1000),
   .....:                   columns=['A', 'B', 'C'])
   .....: 

In [246]: r = df.resample('3T')

In [247]: r.mean()
Out[247]: 
                            A         B         C
2012-01-01 00:00:00 -0.220339  0.034854 -0.073757
2012-01-01 00:03:00  0.037070  0.040013  0.053754
2012-01-01 00:06:00 -0.041597 -0.144562 -0.007614
2012-01-01 00:09:00  0.043127 -0.076432 -0.032570
2012-01-01 00:12:00 -0.027609  0.054618  0.056878
2012-01-01 00:15:00 -0.014181  0.043958  0.077734

Мы можем выбрать конкретный столбец или столбцы, используя стандартный метод обращения к элементу.

In [248]: r['A'].mean()
Out[248]: 
2012-01-01 00:00:00   -0.220339
2012-01-01 00:03:00    0.037070
2012-01-01 00:06:00   -0.041597
2012-01-01 00:09:00    0.043127
2012-01-01 00:12:00   -0.027609
2012-01-01 00:15:00   -0.014181
Freq: 3T, Name: A, dtype: float64

In [249]: r[['A','B']].mean()
Out[249]: 
                            A         B
2012-01-01 00:00:00 -0.220339  0.034854
2012-01-01 00:03:00  0.037070  0.040013
2012-01-01 00:06:00 -0.041597 -0.144562
2012-01-01 00:09:00  0.043127 -0.076432
2012-01-01 00:12:00 -0.027609  0.054618
2012-01-01 00:15:00 -0.014181  0.043958

Вы можете передать список или словарь функций для агрегирования, выходящий DataFrame:

In [250]: r['A'].agg([np.sum, np.mean, np.std])
Out[250]: 
                           sum      mean       std
2012-01-01 00:00:00 -39.660974 -0.220339  1.033912
2012-01-01 00:03:00   6.672559  0.037070  0.971503
2012-01-01 00:06:00  -7.487453 -0.041597  1.018418
2012-01-01 00:09:00   7.762901  0.043127  1.025842
2012-01-01 00:12:00  -4.969624 -0.027609  0.961649
2012-01-01 00:15:00  -1.418119 -0.014181  0.978847

Если передается словарь, ключи будут использоваться для именования столбцов. В противном случае используется имя функции (сохраненное в объекте функции).

In [251]: r['A'].agg({'result1' : np.sum,
   .....:             'result2' : np.mean})
   .....: 
Out[251]: 
                      result2    result1
2012-01-01 00:00:00 -0.220339 -39.660974
2012-01-01 00:03:00  0.037070   6.672559
2012-01-01 00:06:00 -0.041597  -7.487453
2012-01-01 00:09:00  0.043127   7.762901
2012-01-01 00:12:00 -0.027609  -4.969624
2012-01-01 00:15:00 -0.014181  -1.418119

Для перевыбранного DataFrame вы можете передать список функций, которые нужно применить к каждому столбцу, что генерирует агрегированный результат с иерархическим индексом:

In [252]: r.agg([np.sum, np.mean])
Out[252]: 
                             A                    B                    C  \
                           sum      mean        sum      mean        sum   
2012-01-01 00:00:00 -39.660974 -0.220339   6.273786  0.034854 -13.276324   
2012-01-01 00:03:00   6.672559  0.037070   7.202361  0.040013   9.675632   
2012-01-01 00:06:00  -7.487453 -0.041597 -26.021155 -0.144562  -1.370600   
2012-01-01 00:09:00   7.762901  0.043127 -13.757837 -0.076432  -5.862640   
2012-01-01 00:12:00  -4.969624 -0.027609   9.831208  0.054618  10.237970   
2012-01-01 00:15:00  -1.418119 -0.014181   4.395766  0.043958   7.773442   

                               
                         mean  
2012-01-01 00:00:00 -0.073757  
2012-01-01 00:03:00  0.053754  
2012-01-01 00:06:00 -0.007614  
2012-01-01 00:09:00 -0.032570  
2012-01-01 00:12:00  0.056878  
2012-01-01 00:15:00  0.077734  

Передав словарь в aggregate, вы можете применить разное агрегирование к столбцам DataFrame:

In [253]: r.agg({'A' : np.sum,
   .....:        'B' : lambda x: np.std(x, ddof=1)})
   .....: 
Out[253]: 
                             A         B
2012-01-01 00:00:00 -39.660974  1.004756
2012-01-01 00:03:00   6.672559  0.963559
2012-01-01 00:06:00  -7.487453  0.950766
2012-01-01 00:09:00   7.762901  0.949182
2012-01-01 00:12:00  -4.969624  1.093736
2012-01-01 00:15:00  -1.418119  1.028869

Имена функций также могут быть строками. Для того чтобы строка была допустимой, она должна быть реализована в объекте Resampled.

In [254]: r.agg({'A' : 'sum', 'B' : 'std'})
Out[254]: 
                             A         B
2012-01-01 00:00:00 -39.660974  1.004756
2012-01-01 00:03:00   6.672559  0.963559
2012-01-01 00:06:00  -7.487453  0.950766
2012-01-01 00:09:00   7.762901  0.949182
2012-01-01 00:12:00  -4.969624  1.093736
2012-01-01 00:15:00  -1.418119  1.028869

Кроме того, вы можете также указать несколько функций агрегирования для каждого столбца отдельно.

In [255]: r.agg({'A' : ['sum','std'], 'B' : ['mean','std'] })
Out[255]: 
                             A                   B          
                           sum       std      mean       std
2012-01-01 00:00:00 -39.660974  1.033912  0.034854  1.004756
2012-01-01 00:03:00   6.672559  0.971503  0.040013  0.963559
2012-01-01 00:06:00  -7.487453  1.018418 -0.144562  0.950766
2012-01-01 00:09:00   7.762901  1.025842 -0.076432  0.949182
2012-01-01 00:12:00  -4.969624  0.961649  0.054618  1.093736
2012-01-01 00:15:00  -1.418119  0.978847  0.043958  1.028869

Если у DataFrame нет индекса datetimelike, но вместо этого вы хотите перевыбирать на основе столбца datetimelike в фрейме, его можно передать в ключевое слово on.

In [256]: df = pd.DataFrame({'date': pd.date_range('2015-01-01', freq='W', periods=5),
   .....:                    'a': np.arange(5)},
   .....:                   index=pd.MultiIndex.from_arrays([
   .....:                            [1,2,3,4,5],
   .....:                            pd.date_range('2015-01-01', freq='W', periods=5)],
   .....:                        names=['v','d']))
   .....: 

In [257]: df
Out[257]: 
              a       date
v d                       
1 2015-01-04  0 2015-01-04
2 2015-01-11  1 2015-01-11
3 2015-01-18  2 2015-01-18
4 2015-01-25  3 2015-01-25
5 2015-02-01  4 2015-02-01

In [258]: df.resample('M', on='date').sum()
Out[258]: 
            a
date         
2015-01-31  6
2015-02-28  4

Аналогично, если вы хотите перевыбрать по уровню datetimelike в MultiIndex, его имя или положение можно передать в ключевое слово level.

In [259]: df.resample('M', level='d').sum()
Out[259]: 
            a
d            
2015-01-31  6
2015-02-28  4

Представление временного интервала

Регулярные интервалы времени представлены объектами Period в pandas, а последовательности объектов Period собираются в PeriodIndex, которые можно создать с помощью удобной функции period_range.

Период

Period представляет собой временной интервал (например, день, месяц, квартал и т. д.). Вы можете указать интервал с помощью ключевого слова freq с помощью псевдонима частоты, как показано ниже. Поскольку Period представляет собой интервал Period, он не может быть отрицательным, например, «-3D».

In [260]: pd.Period('2012', freq='A-DEC')
Out[260]: Period('2012', 'A-DEC')

In [261]: pd.Period('2012-1-1', freq='D')
Out[261]: Period('2012-01-01', 'D')

In [262]: pd.Period('2012-1-1 19:00', freq='H')
Out[262]: Period('2012-01-01 19:00', 'H')

In [263]: pd.Period('2012-1-1 19:00', freq='5H')
Out[263]: Period('2012-01-01 19:00', '5H')

Добавление и вычитание целых чисел из периодов сдвигает период на свою частоту. Арифметика не разрешена между Period с различной частотой (интервалом).

In [264]: p = pd.Period('2012', freq='A-DEC')

In [265]: p + 1
Out[265]: Period('2013', 'A-DEC')

In [266]: p - 3
Out[266]: Period('2009', 'A-DEC')

In [267]: p = pd.Period('2012-01', freq='2M')

In [268]: p + 2
Out[268]: Period('2012-05', '2M')

In [269]: p - 1
Out[269]: Period('2011-11', '2M')

In [270]: p == pd.Period('2012-01', freq='3M')
---------------------------------------------------------------------------
IncompatibleFrequency                     Traceback (most recent call last)
<ipython-input-270-ff54ce3238f5> in <module>()
----> 1 p == pd.Period('2012-01', freq='3M')

/home/joris/scipy/pandas/pandas/src/period.pyx in pandas._period._Period.__richcmp__ (pandas/src/period.c:11340)()
    729             if other.freq != self.freq:
    730                 msg = _DIFFERENT_FREQ.format(self.freqstr, other.freqstr)
--> 731                 raise IncompatibleFrequency(msg)
    732             return PyObject_RichCompareBool(self.ordinal, other.ordinal, op)
    733         elif other is tslib.NaT:

IncompatibleFrequency: Input has different freq=3M from Period(freq=2M)

Если частота Period суточная или выше (D, H, T, S, L, U, N)., offsets и объекты, подобные timedelta, могут быть добавлены, если результат может иметь ту же частоту. В противном случае будет выброшено исключение ValueError.

In [271]: p = pd.Period('2014-07-01 09:00', freq='H')

In [272]: p + Hour(2)
Out[272]: Period('2014-07-01 11:00', 'H')

In [273]: p + timedelta(minutes=120)
Out[273]: Period('2014-07-01 11:00', 'H')

In [274]: p + np.timedelta64(7200, 's')
Out[274]: Period('2014-07-01 11:00', 'H')
In [1]: p + Minute(5)
Traceback
   ...
ValueError: Input has different freq from Period(freq=H)

Если у Period есть другие частоты, можно добавить только одинаковые offsets. В противном случае будет выброшено исключение ValueError.

In [275]: p = pd.Period('2014-07', freq='M')

In [276]: p + MonthEnd(3)
Out[276]: Period('2014-10', 'M')
In [1]: p + MonthBegin(3)
Traceback
   ...
ValueError: Input has different freq from Period(freq=M)

Вычитание экземпляров Period с одинаковой частотой вернет количество единиц частоты между ними:

In [277]: pd.Period('2012', freq='A-DEC') - pd.Period('2002', freq='A-DEC')
Out[277]: 10

PeriodIndex и period_range

Регулярные последовательности объектов Period могут быть собраны в PeriodIndex, который может быть построен с помощью удобной функции period_range:

In [278]: prng = pd.period_range('1/1/2011', '1/1/2012', freq='M')

In [279]: prng
Out[279]: 
PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04', '2011-05', '2011-06',
             '2011-07', '2011-08', '2011-09', '2011-10', '2011-11', '2011-12',
             '2012-01'],
            dtype='period[M]', freq='M')

Конструктор PeriodIndex также может быть использован напрямую:

In [280]: pd.PeriodIndex(['2011-1', '2011-2', '2011-3'], freq='M')
Out[280]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]', freq='M')

Передача частоты, умноженной на несколько значений, выводит последовательность Period, у которой интервал умножается.

In [281]: pd.PeriodIndex(start='2014-01', freq='3M', periods=4)
Out[281]: PeriodIndex(['2014-01', '2014-04', '2014-07', '2014-10'], dtype='period[3M]', freq='3M')

Так же, как и DatetimeIndex, PeriodIndex также может использоваться для индексации объектов pandas:

In [282]: ps = pd.Series(np.random.randn(len(prng)), prng)

In [283]: ps
Out[283]: 
2011-01   -1.022670
2011-02    1.371155
2011-03    1.035277
2011-04    1.694400
2011-05   -1.659733
2011-06    0.511432
2011-07    0.433176
2011-08   -0.317955
2011-09   -0.517114
2011-10   -0.310466
2011-11    0.543957
2011-12    0.492003
2012-01    0.193420
Freq: M, dtype: float64

PeriodIndex поддерживает сложение и вычитание по тому же правилу, что и Period.

In [284]: idx = pd.period_range('2014-07-01 09:00', periods=5, freq='H')

In [285]: idx
Out[285]: 
PeriodIndex(['2014-07-01 09:00', '2014-07-01 10:00', '2014-07-01 11:00',
             '2014-07-01 12:00', '2014-07-01 13:00'],
            dtype='period[H]', freq='H')

In [286]: idx + Hour(2)
Out[286]: 
PeriodIndex(['2014-07-01 11:00', '2014-07-01 12:00', '2014-07-01 13:00',
             '2014-07-01 14:00', '2014-07-01 15:00'],
            dtype='period[H]', freq='H')

In [287]: idx = pd.period_range('2014-07', periods=5, freq='M')

In [288]: idx
Out[288]: PeriodIndex(['2014-07', '2014-08', '2014-09', '2014-10', '2014-11'], dtype='period[M]', freq='M')

In [289]: idx + MonthEnd(3)
Out[289]: PeriodIndex(['2014-10', '2014-11', '2014-12', '2015-01', '2015-02'], dtype='period[M]', freq='M')

PeriodIndex имеет собственный тип данных, названный period, см. Типы данных Period.

Типы данных Period

Впервые добавлено в версии 0.19.0.

PeriodIndex имеет пользовательский тип данных period. Это расширенный тип данных pandas, подобный типу данных с часовым поясом сознательным о часовом поясе (datetime64[ns, tz]).

Тип данных period содержит атрибут freq и представляется с помощью period[freq], таких как period[D] или period[M], используя строки частот.

In [290]: pi = pd.period_range('2016-01-01', periods=3, freq='M')

In [291]: pi
Out[291]: PeriodIndex(['2016-01', '2016-02', '2016-03'], dtype='period[M]', freq='M')

In [292]: pi.dtype
Out[292]: period[M]

Тип данных period может использоваться в .astype(...). Он позволяет изменять freq PeriodIndex, например, .asfreq(), и преобразовывать DatetimeIndex в PeriodIndex, например, to_period():

# change monthly freq to daily freq
In [293]: pi.astype('period[D]')
Out[293]: PeriodIndex(['2016-01-31', '2016-02-29', '2016-03-31'], dtype='period[D]', freq='D')

# convert to DatetimeIndex
In [294]: pi.astype('datetime64[ns]')
Out[294]: DatetimeIndex(['2016-01-01', '2016-02-01', '2016-03-01'], dtype='datetime64[ns]', freq='MS')

# convert to PeriodIndex
In [295]: dti = pd.date_range('2011-01-01', freq='M', periods=3)

In [296]: dti
Out[296]: DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31'], dtype='datetime64[ns]', freq='M')

In [297]: dti.astype('period[M]')
Out[297]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]', freq='M')

Частичное строковое индексирование PeriodIndex

Вы можете передавать даты и строки в Series и DataFrame с использованием PeriodIndex, так же, как и DatetimeIndex. Подробности см. в Частичное строковое индексирование DatetimeIndex.

In [298]: ps['2011-01']
Out[298]: -1.022669594890105

In [299]: ps[datetime(2011, 12, 25):]
Out[299]: 
2011-12    0.492003
2012-01    0.193420
Freq: M, dtype: float64

In [300]: ps['10/31/2011':'12/31/2011']
Out[300]: 
2011-10   -0.310466
2011-11    0.543957
2011-12    0.492003
Freq: M, dtype: float64

Передача строки, представляющей частоту ниже PeriodIndex, возвращает частично обрезанные данные.

In [301]: ps['2011']
Out[301]: 
2011-01   -1.022670
2011-02    1.371155
2011-03    1.035277
2011-04    1.694400
2011-05   -1.659733
2011-06    0.511432
2011-07    0.433176
2011-08   -0.317955
2011-09   -0.517114
2011-10   -0.310466
2011-11    0.543957
2011-12    0.492003
Freq: M, dtype: float64

In [302]: dfp = pd.DataFrame(np.random.randn(600,1),
   .....:                    columns=['A'],
   .....:                    index=pd.period_range('2013-01-01 9:00', periods=600, freq='T'))
   .....: 

In [303]: dfp
Out[303]: 
                         A
2013-01-01 09:00  0.197720
2013-01-01 09:01 -0.284769
2013-01-01 09:02  0.061491
2013-01-01 09:03  1.630257
2013-01-01 09:04  2.042442
2013-01-01 09:05 -0.804392
2013-01-01 09:06  0.212760
...                    ...
2013-01-01 18:53  0.150586
2013-01-01 18:54 -0.679569
2013-01-01 18:55 -0.910216
2013-01-01 18:56 -0.413168
2013-01-01 18:57 -0.247752
2013-01-01 18:58  1.590875
2013-01-01 18:59 -2.005294

[600 rows x 1 columns]

In [304]: dfp['2013-01-01 10H']
Out[304]: 
                         A
2013-01-01 10:00 -0.569936
2013-01-01 10:01 -1.179183
2013-01-01 10:02 -0.838602
2013-01-01 10:03 -1.727539
2013-01-01 10:04  1.334027
2013-01-01 10:05  0.417423
2013-01-01 10:06 -0.221189
...                    ...
2013-01-01 10:53 -0.375925
2013-01-01 10:54  0.212750
2013-01-01 10:55 -0.592417
2013-01-01 10:56 -0.466064
2013-01-01 10:57 -1.715347
2013-01-01 10:58 -0.634913
2013-01-01 10:59 -0.809471

[60 rows x 1 columns]

Как и в случае с DatetimeIndex, конечные точки будут включены в результат. Пример ниже вырезает данные с 10:00 до 11:59.

In [305]: dfp['2013-01-01 10H':'2013-01-01 11H']
Out[305]: 
                         A
2013-01-01 10:00 -0.569936
2013-01-01 10:01 -1.179183
2013-01-01 10:02 -0.838602
2013-01-01 10:03 -1.727539
2013-01-01 10:04  1.334027
2013-01-01 10:05  0.417423
2013-01-01 10:06 -0.221189
...                    ...
2013-01-01 11:53  0.616198
2013-01-01 11:54  2.843156
2013-01-01 11:55  0.572537
2013-01-01 11:56  1.709706
2013-01-01 11:57 -0.205490
2013-01-01 11:58  1.759719
2013-01-01 11:59 -1.181485

[120 rows x 1 columns]

Преобразование частоты и ресемплинг с PeriodIndex

Частоту Period и PeriodIndex можно преобразовать с помощью метода asfreq. Начнём с финансового года 2011, заканчивающегося в декабре:

In [306]: p = pd.Period('2011', freq='A-DEC')

In [307]: p
Out[307]: Period('2011', 'A-DEC')

Мы можем преобразовать его в ежемесячную частоту. С помощью параметра how мы можем указать, нужно ли вернуть начальный или конечный месяц:

In [308]: p.asfreq('M', how='start')
Out[308]: Period('2011-01', 'M')

In [309]: p.asfreq('M', how='end')
Out[309]: Period('2011-12', 'M')

Для удобства используются сокращения «s» и «e»:

In [310]: p.asfreq('M', 's')
Out[310]: Period('2011-01', 'M')

In [311]: p.asfreq('M', 'e')
Out[311]: Period('2011-12', 'M')

Преобразование в «сверхпериод» (например, годовая частота является сверхпериодом квартальной частоты) автоматически возвращает сверхпериод, который включает входной период:

In [312]: p = pd.Period('2011-12', freq='M')

In [313]: p.asfreq('A-NOV')
Out[313]: Period('2012', 'A-NOV')

Обратите внимание, что так как мы перешли к годовой частоте, заканчивающейся в ноябре, ежемесячный период декабря 2011 года фактически находится в периоде 2012 A-НОЯБРЯ.

Преобразования периодов с привязанными частотами особенно полезны при работе с различными квартальными данными, распространёнными в экономике, бизнесе и других областях. Многие организации определяют кварталы относительно месяца, в котором начинается и заканчивается их финансовый год. Таким образом, первый квартал 2011 года мог начаться в 2010 году или в течение нескольких месяцев 2011 года. С помощью привязанных частот pandas работает со всеми квартальными частотами Q-JAN через Q-DEC.

Q-DEC определяет регулярные календарные кварталы:

In [314]: p = pd.Period('2012Q1', freq='Q-DEC')

In [315]: p.asfreq('D', 's')
Out[315]: Period('2012-01-01', 'D')

In [316]: p.asfreq('D', 'e')
Out[316]: Period('2012-03-31', 'D')

Q-MAR определяет конец финансового года в марте:

In [317]: p = pd.Period('2011Q4', freq='Q-MAR')

In [318]: p.asfreq('D', 's')
Out[318]: Period('2011-01-01', 'D')

In [319]: p.asfreq('D', 'e')
Out[319]: Period('2011-03-31', 'D')

Преобразование между представлениями

Данные, помеченные временной меткой, можно преобразовать в данные PeriodIndex, используя to_period, и наоборот, используя to_timestamp:

In [320]: rng = pd.date_range('1/1/2012', periods=5, freq='M')

In [321]: ts = pd.Series(np.random.randn(len(rng)), index=rng)

In [322]: ts
Out[322]: 
2012-01-31    2.167674
2012-02-29   -1.505130
2012-03-31    1.005802
2012-04-30    0.481525
2012-05-31   -0.352151
Freq: M, dtype: float64

In [323]: ps = ts.to_period()

In [324]: ps
Out[324]: 
2012-01    2.167674
2012-02   -1.505130
2012-03    1.005802
2012-04    0.481525
2012-05   -0.352151
Freq: M, dtype: float64

In [325]: ps.to_timestamp()
Out[325]: 
2012-01-01    2.167674
2012-02-01   -1.505130
2012-03-01    1.005802
2012-04-01    0.481525
2012-05-01   -0.352151
Freq: MS, dtype: float64

Помните, что можно использовать «s» и «e», чтобы вернуть временные метки в начале или конце периода:

In [326]: ps.to_timestamp('D', how='s')
Out[326]: 
2012-01-01    2.167674
2012-02-01   -1.505130
2012-03-01    1.005802
2012-04-01    0.481525
2012-05-01   -0.352151
Freq: MS, dtype: float64

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

In [327]: prng = pd.period_range('1990Q1', '2000Q4', freq='Q-NOV')

In [328]: ts = pd.Series(np.random.randn(len(prng)), prng)

In [329]: ts.index = (prng.asfreq('M', 'e') + 1).asfreq('H', 's') + 9

In [330]: ts.head()
Out[330]: 
1990-03-01 09:00   -0.608988
1990-06-01 09:00    0.412294
1990-09-01 09:00   -0.715938
1990-12-01 09:00    1.297773
1991-03-01 09:00   -2.260765
Freq: H, dtype: float64

Представление диапазонов вне границ

Если у вас есть данные, выходящие за пределы Timestamp диапазонов, см. Ограничения временных меток, то вы можете использовать PeriodIndex и/или Series Periods для выполнения вычислений.

In [331]: span = pd.period_range('1215-01-01', '1381-01-01', freq='D')

In [332]: span
Out[332]: 
PeriodIndex(['1215-01-01', '1215-01-02', '1215-01-03', '1215-01-04',
             '1215-01-05', '1215-01-06', '1215-01-07', '1215-01-08',
             '1215-01-09', '1215-01-10',
             ...
             '1380-12-23', '1380-12-24', '1380-12-25', '1380-12-26',
             '1380-12-27', '1380-12-28', '1380-12-29', '1380-12-30',
             '1380-12-31', '1381-01-01'],
            dtype='period[D]', length=60632, freq='D')

Для преобразования из представления int64 YYYYMMDD.

In [333]: s = pd.Series([20121231, 20141130, 99991231])

In [334]: s
Out[334]: 
0    20121231
1    20141130
2    99991231
dtype: int64

In [335]: def conv(x):
   .....:     return pd.Period(year = x // 10000, month = x//100 % 100, day = x%100, freq='D')
   .....: 

In [336]: s.apply(conv)
Out[336]: 
0   2012-12-31
1   2014-11-30
2   9999-12-31
dtype: object

In [337]: s.apply(conv)[2]
Out[337]: Period('9999-12-31', 'D')

Эти значения легко преобразовать в PeriodIndex

In [338]: span = pd.PeriodIndex(s.apply(conv))

In [339]: span
Out[339]: PeriodIndex(['2012-12-31', '2014-11-30', '9999-12-31'], dtype='period[D]', freq='D')

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

Pandas предоставляет широкую поддержку работы с временными метками в разных часовых поясах с использованием библиотек pytz и dateutil. Поддержка dateutil появилась в версии 0.14.1 и в настоящее время поддерживается только для фиксированных смещений и зон tzfile. По умолчанию используется библиотека pytz. Поддержка dateutil предоставляется для совместимости с другими приложениями, например, если вы используете dateutil в других пакетах python.

Работа с часовыми поясами

По умолчанию объекты pandas не учитывают часовой пояс:

In [340]: rng = pd.date_range('3/6/2012 00:00', periods=15, freq='D')

In [341]: rng.tz is None
Out[341]: True

Для задания часового пояса можно использовать ключевое слово tz к date_range и другим функциям. Строки часовых поясов Dateutil отличаются от часовых поясов pytz тем, что начинаются с dateutil/.

  • В pytz вы можете найти список распространённых (и менее распространённых) часовых поясов, используя from pytz import common_timezones, all_timezones.
  • dateutil использует часовые пояса ОС, поэтому доступный фиксированный список отсутствует. Для распространённых зон имена совпадают с pytz.
# pytz
In [342]: rng_pytz = pd.date_range('3/6/2012 00:00', periods=10, freq='D',
   .....:                          tz='Europe/London')
   .....: 

In [343]: rng_pytz.tz
Out[343]: <DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD>

# dateutil
In [344]: rng_dateutil = pd.date_range('3/6/2012 00:00', periods=10, freq='D',
   .....:                              tz='dateutil/Europe/London')
   .....: 

In [345]: rng_dateutil.tz
Out[345]: tzfile('/usr/share/zoneinfo/Europe/London')

# dateutil - utc special case
In [346]: rng_utc = pd.date_range('3/6/2012 00:00', periods=10, freq='D',
   .....:                         tz=dateutil.tz.tzutc())
   .....: 

In [347]: rng_utc.tz
Out[347]: tzutc()

Обратите внимание, что часовой пояс UTC является особым случаем в dateutil и должен быть явно создан как экземпляр dateutil.tz.tzutc. Вы также можете сначала явно создать другие часовые пояса, что даёт вам больший контроль над используемым часовым поясом:

# pytz
In [348]: tz_pytz = pytz.timezone('Europe/London')

In [349]: rng_pytz = pd.date_range('3/6/2012 00:00', periods=10, freq='D',
   .....:                          tz=tz_pytz)
   .....: 

In [350]: rng_pytz.tz == tz_pytz
Out[350]: True

# dateutil
In [351]: tz_dateutil = dateutil.tz.gettz('Europe/London')

In [352]: rng_dateutil = pd.date_range('3/6/2012 00:00', periods=10, freq='D',
   .....:                              tz=tz_dateutil)
   .....: 

In [353]: rng_dateutil.tz == tz_dateutil
Out[353]: True

Временные метки, подобно объекту Python datetime.datetime, могут быть либо без часового пояса, либо с часовым поясом. Объекты временных рядов и DatetimeIndex без часового пояса могут быть локализованы с помощью tz_localize:

In [354]: ts = pd.Series(np.random.randn(len(rng)), rng)

In [355]: ts_utc = ts.tz_localize('UTC')

In [356]: ts_utc
Out[356]: 
2012-03-06 00:00:00+00:00    0.679135
2012-03-07 00:00:00+00:00    0.345668
2012-03-08 00:00:00+00:00   -1.143903
2012-03-09 00:00:00+00:00    0.487087
2012-03-10 00:00:00+00:00   -1.421073
2012-03-11 00:00:00+00:00   -0.327463
2012-03-12 00:00:00+00:00    0.169899
2012-03-13 00:00:00+00:00    0.867568
2012-03-14 00:00:00+00:00   -0.834122
2012-03-15 00:00:00+00:00   -1.698494
2012-03-16 00:00:00+00:00    0.974717
2012-03-17 00:00:00+00:00    0.966771
2012-03-18 00:00:00+00:00   -0.754168
2012-03-19 00:00:00+00:00   -1.434246
2012-03-20 00:00:00+00:00    0.848935
Freq: D, dtype: float64

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

In [357]: ts_utc.tz_convert('US/Eastern')
Out[357]: 
2012-03-05 19:00:00-05:00    0.679135
2012-03-06 19:00:00-05:00    0.345668
2012-03-07 19:00:00-05:00   -1.143903
2012-03-08 19:00:00-05:00    0.487087
2012-03-09 19:00:00-05:00   -1.421073
2012-03-10 19:00:00-05:00   -0.327463
2012-03-11 20:00:00-04:00    0.169899
2012-03-12 20:00:00-04:00    0.867568
2012-03-13 20:00:00-04:00   -0.834122
2012-03-14 20:00:00-04:00   -1.698494
2012-03-15 20:00:00-04:00    0.974717
2012-03-16 20:00:00-04:00    0.966771
2012-03-17 20:00:00-04:00   -0.754168
2012-03-18 20:00:00-04:00   -1.434246
2012-03-19 20:00:00-04:00    0.848935
Freq: D, dtype: float64

Предупреждение

Будьте осторожны при преобразованиях между библиотеками. Для некоторых зон pytz и dateutil имеют разные определения зоны. Это больше проблема для необычных часовых поясов, чем для «стандартных» часовых поясов, таких как US/Eastern.

Предупреждение

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

Предупреждение

Неправильно передавать часовой пояс непосредственно в конструктор datetime.datetime (например, datetime.datetime(2011, 1, 1, tz=timezone('US/Eastern'))). Вместо этого необходимо локализовать дату и время, используя метод localize для часового пояса.

Внутри все временные метки хранятся в формате UTC. Скалярные значения из DatetimeIndex с часовым поясом будут иметь свои поля (день, час, минута) локализованные в часовом поясе. Однако временные метки с одинаковым значением UTC по-прежнему считаются равными, даже если они находятся в разных часовых поясах:

In [358]: rng_eastern = rng_utc.tz_convert('US/Eastern')

In [359]: rng_berlin = rng_utc.tz_convert('Europe/Berlin')

In [360]: rng_eastern[5]
Out[360]: Timestamp('2012-03-10 19:00:00-0500', tz='US/Eastern', freq='D')

In [361]: rng_berlin[5]
Out[361]: Timestamp('2012-03-11 01:00:00+0100', tz='Europe/Berlin', freq='D')

In [362]: rng_eastern[5] == rng_berlin[5]
Out[362]: True

Как и Series, DataFrame, и DatetimeIndex, Timestamp``s can be converted to other time zones using ``tz_convert:

In [363]: rng_eastern[5]
Out[363]: Timestamp('2012-03-10 19:00:00-0500', tz='US/Eastern', freq='D')

In [364]: rng_berlin[5]
Out[364]: Timestamp('2012-03-11 01:00:00+0100', tz='Europe/Berlin', freq='D')

In [365]: rng_eastern[5].tz_convert('Europe/Berlin')
Out[365]: Timestamp('2012-03-11 01:00:00+0100', tz='Europe/Berlin')

Локализация функций Timestamp работает так же, как DatetimeIndex и Series:

In [366]: rng[5]
Out[366]: Timestamp('2012-03-11 00:00:00', freq='D')

In [367]: rng[5].tz_localize('Asia/Shanghai')
Out[367]: Timestamp('2012-03-11 00:00:00+0800', tz='Asia/Shanghai')

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

In [368]: eastern = ts_utc.tz_convert('US/Eastern')

In [369]: berlin = ts_utc.tz_convert('Europe/Berlin')

In [370]: result = eastern + berlin

In [371]: result
Out[371]: 
2012-03-06 00:00:00+00:00    1.358269
2012-03-07 00:00:00+00:00    0.691336
2012-03-08 00:00:00+00:00   -2.287805
2012-03-09 00:00:00+00:00    0.974174
2012-03-10 00:00:00+00:00   -2.842146
2012-03-11 00:00:00+00:00   -0.654926
2012-03-12 00:00:00+00:00    0.339798
2012-03-13 00:00:00+00:00    1.735136
2012-03-14 00:00:00+00:00   -1.668245
2012-03-15 00:00:00+00:00   -3.396988
2012-03-16 00:00:00+00:00    1.949435
2012-03-17 00:00:00+00:00    1.933541
2012-03-18 00:00:00+00:00   -1.508335
2012-03-19 00:00:00+00:00   -2.868493
2012-03-20 00:00:00+00:00    1.697870
Freq: D, dtype: float64

In [372]: result.index
Out[372]: 
DatetimeIndex(['2012-03-06', '2012-03-07', '2012-03-08', '2012-03-09',
               '2012-03-10', '2012-03-11', '2012-03-12', '2012-03-13',
               '2012-03-14', '2012-03-15', '2012-03-16', '2012-03-17',
               '2012-03-18', '2012-03-19', '2012-03-20'],
              dtype='datetime64[ns, UTC]', freq='D')

Чтобы удалить часовой пояс из tz-aware DatetimeIndex, используйте tz_localize(None) или tz_convert(None). tz_localize(None) удалит часовой пояс, сохраняя локальное представление времени. tz_convert(None) удалит часовой пояс после преобразования в UTC.

In [373]: didx = pd.DatetimeIndex(start='2014-08-01 09:00', freq='H', periods=10, tz='US/Eastern')

In [374]: didx
Out[374]: 
DatetimeIndex(['2014-08-01 09:00:00-04:00', '2014-08-01 10:00:00-04:00',
               '2014-08-01 11:00:00-04:00', '2014-08-01 12:00:00-04:00',
               '2014-08-01 13:00:00-04:00', '2014-08-01 14:00:00-04:00',
               '2014-08-01 15:00:00-04:00', '2014-08-01 16:00:00-04:00',
               '2014-08-01 17:00:00-04:00', '2014-08-01 18:00:00-04:00'],
              dtype='datetime64[ns, US/Eastern]', freq='H')

In [375]: didx.tz_localize(None)
Out[375]: 
DatetimeIndex(['2014-08-01 09:00:00', '2014-08-01 10:00:00',
               '2014-08-01 11:00:00', '2014-08-01 12:00:00',
               '2014-08-01 13:00:00', '2014-08-01 14:00:00',
               '2014-08-01 15:00:00', '2014-08-01 16:00:00',
               '2014-08-01 17:00:00', '2014-08-01 18:00:00'],
              dtype='datetime64[ns]', freq='H')

In [376]: didx.tz_convert(None)
Out[376]: 
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
               '2014-08-01 15:00:00', '2014-08-01 16:00:00',
               '2014-08-01 17:00:00', '2014-08-01 18:00:00',
               '2014-08-01 19:00:00', '2014-08-01 20:00:00',
               '2014-08-01 21:00:00', '2014-08-01 22:00:00'],
              dtype='datetime64[ns]', freq='H')

# tz_convert(None) is identical with tz_convert('UTC').tz_localize(None)
In [377]: didx.tz_convert('UCT').tz_localize(None)
Out[377]: 
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
               '2014-08-01 15:00:00', '2014-08-01 16:00:00',
               '2014-08-01 17:00:00', '2014-08-01 18:00:00',
               '2014-08-01 19:00:00', '2014-08-01 20:00:00',
               '2014-08-01 21:00:00', '2014-08-01 22:00:00'],
              dtype='datetime64[ns]', freq='H')

Неоднозначные времена при локализации

В некоторых случаях localize не может определить летнее/зимнее время, когда есть дубликаты. Это часто происходит при чтении файлов или записей базы данных, которые просто дублируют часы. Передача ambiguous='infer' (параметр infer_dst в предыдущих версиях) в tz_localize попытается определить правильное смещение. Пример сверху не сработает, так как он содержит неоднозначные времена, а снизу будет выведено правильное смещение.

In [378]: rng_hourly = pd.DatetimeIndex(['11/06/2011 00:00', '11/06/2011 01:00',
   .....:                                '11/06/2011 01:00', '11/06/2011 02:00',
   .....:                                '11/06/2011 03:00'])
   .....: 

Это не сработает, так как есть неоднозначные времена

In [2]: rng_hourly.tz_localize('US/Eastern')
AmbiguousTimeError: Cannot infer dst time from Timestamp('2011-11-06 01:00:00'), try using the 'ambiguous' argument

Определить неоднозначные времена

In [379]: rng_hourly_eastern = rng_hourly.tz_localize('US/Eastern', ambiguous='infer')

In [380]: rng_hourly_eastern.tolist()
Out[380]: 
[Timestamp('2011-11-06 00:00:00-0400', tz='US/Eastern'),
 Timestamp('2011-11-06 01:00:00-0400', tz='US/Eastern'),
 Timestamp('2011-11-06 01:00:00-0500', tz='US/Eastern'),
 Timestamp('2011-11-06 02:00:00-0500', tz='US/Eastern'),
 Timestamp('2011-11-06 03:00:00-0500', tz='US/Eastern')]

В дополнение к ‘infer’ поддерживается несколько других аргументов. Передача массивоподобного значения bool или 0/1, где True представляет часовой пояс летнего времени, а False — часовой пояс зимнего времени, позволяет различать более одного перехода в летнее время (например, если у вас есть несколько записей в базе данных, каждая со своим переходом в летнее время). Или передача ‘NaT’ заполнит переходные моменты значениями not-a-time. Эти методы доступны в конструкторе DatetimeIndex и tz_localize.

In [381]: rng_hourly_dst = np.array([1, 1, 0, 0, 0])

In [382]: rng_hourly.tz_localize('US/Eastern', ambiguous=rng_hourly_dst).tolist()
Out[382]: 
[Timestamp('2011-11-06 00:00:00-0400', tz='US/Eastern'),
 Timestamp('2011-11-06 01:00:00-0400', tz='US/Eastern'),
 Timestamp('2011-11-06 01:00:00-0500', tz='US/Eastern'),
 Timestamp('2011-11-06 02:00:00-0500', tz='US/Eastern'),
 Timestamp('2011-11-06 03:00:00-0500', tz='US/Eastern')]

In [383]: rng_hourly.tz_localize('US/Eastern', ambiguous='NaT').tolist()
Out[383]: 
[Timestamp('2011-11-06 00:00:00-0400', tz='US/Eastern'),
 NaT,
 NaT,
 Timestamp('2011-11-06 02:00:00-0500', tz='US/Eastern'),
 Timestamp('2011-11-06 03:00:00-0500', tz='US/Eastern')]

In [384]: didx = pd.DatetimeIndex(start='2014-08-01 09:00', freq='H', periods=10, tz='US/Eastern')

In [385]: didx
Out[385]: 
DatetimeIndex(['2014-08-01 09:00:00-04:00', '2014-08-01 10:00:00-04:00',
               '2014-08-01 11:00:00-04:00', '2014-08-01 12:00:00-04:00',
               '2014-08-01 13:00:00-04:00', '2014-08-01 14:00:00-04:00',
               '2014-08-01 15:00:00-04:00', '2014-08-01 16:00:00-04:00',
               '2014-08-01 17:00:00-04:00', '2014-08-01 18:00:00-04:00'],
              dtype='datetime64[ns, US/Eastern]', freq='H')

In [386]: didx.tz_localize(None)
Out[386]: 
DatetimeIndex(['2014-08-01 09:00:00', '2014-08-01 10:00:00',
               '2014-08-01 11:00:00', '2014-08-01 12:00:00',
               '2014-08-01 13:00:00', '2014-08-01 14:00:00',
               '2014-08-01 15:00:00', '2014-08-01 16:00:00',
               '2014-08-01 17:00:00', '2014-08-01 18:00:00'],
              dtype='datetime64[ns]', freq='H')

In [387]: didx.tz_convert(None)
Out[387]: 
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
               '2014-08-01 15:00:00', '2014-08-01 16:00:00',
               '2014-08-01 17:00:00', '2014-08-01 18:00:00',
               '2014-08-01 19:00:00', '2014-08-01 20:00:00',
               '2014-08-01 21:00:00', '2014-08-01 22:00:00'],
              dtype='datetime64[ns]', freq='H')

# tz_convert(None) is identical with tz_convert('UTC').tz_localize(None)
In [388]: didx.tz_convert('UCT').tz_localize(None)
Out[388]: 
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
               '2014-08-01 15:00:00', '2014-08-01 16:00:00',
               '2014-08-01 17:00:00', '2014-08-01 18:00:00',
               '2014-08-01 19:00:00', '2014-08-01 20:00:00',
               '2014-08-01 21:00:00', '2014-08-01 22:00:00'],
              dtype='datetime64[ns]', freq='H')

Типы данных с часовым поясом

Впервые добавлено в версии 0.17.0.

Series/DatetimeIndex с неявным значением часового пояса представлены типом данных datetime64[ns].

In [389]: s_naive = pd.Series(pd.date_range('20130101',periods=3))

In [390]: s_naive
Out[390]: 
0   2013-01-01
1   2013-01-02
2   2013-01-03
dtype: datetime64[ns]

Series/DatetimeIndex со явным значением часового пояса представлены типом данных datetime64[ns, tz].

In [391]: s_aware = pd.Series(pd.date_range('20130101',periods=3,tz='US/Eastern'))

In [392]: s_aware
Out[392]: 
0   2013-01-01 00:00:00-05:00
1   2013-01-02 00:00:00-05:00
2   2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern]

Оба этих Series могут быть обработаны с помощью доступара .dt, см. здесь.

Например, чтобы локализовать и преобразовать неявную временную метку в явную с часовым поясом.

In [393]: s_naive.dt.tz_localize('UTC').dt.tz_convert('US/Eastern')
Out[393]: 
0   2012-12-31 19:00:00-05:00
1   2013-01-01 19:00:00-05:00
2   2013-01-02 19:00:00-05:00
dtype: datetime64[ns, US/Eastern]

Кроме того, вы можете .astype(...) данные с явным и неявным часовыми поясами. Эта операция фактически локализует и преобразует неявную временную метку и преобразует явную.

# localize and convert a naive timezone
In [394]: s_naive.astype('datetime64[ns, US/Eastern]')
Out[394]: 
0   2012-12-31 19:00:00-05:00
1   2013-01-01 19:00:00-05:00
2   2013-01-02 19:00:00-05:00
dtype: datetime64[ns, US/Eastern]

# make an aware tz naive
In [395]: s_aware.astype('datetime64[ns]')
Out[395]: 
0   2013-01-01 05:00:00
1   2013-01-02 05:00:00
2   2013-01-03 05:00:00
dtype: datetime64[ns]

# convert to a new timezone
In [396]: s_aware.astype('datetime64[ns, CET]')
Out[396]: 
0   2013-01-01 06:00:00+01:00
1   2013-01-02 06:00:00+01:00
2   2013-01-03 06:00:00+01:00
dtype: datetime64[ns, CET]

Примечание

Использование аксессора .values для Series, возвращает массив numpy данных. Эти значения преобразуются в UTC, так как numpy в настоящее время не поддерживает часовые пояса (хотя он и отображает значения в местном часовом поясе!).

In [397]: s_naive.values
Out[397]: 
array(['2013-01-01T00:00:00.000000000', '2013-01-02T00:00:00.000000000',
       '2013-01-03T00:00:00.000000000'], dtype='datetime64[ns]')

In [398]: s_aware.values
Out[398]: 
array(['2013-01-01T05:00:00.000000000', '2013-01-02T05:00:00.000000000',
       '2013-01-03T05:00:00.000000000'], dtype='datetime64[ns]')

Обратите также внимание, что после преобразования в массив numpy эти значения потеряют информацию о часовом поясе.

In [399]: pd.Series(s_aware.values)
Out[399]: 
0   2013-01-01 05:00:00
1   2013-01-02 05:00:00
2   2013-01-03 05:00:00
dtype: datetime64[ns]

Однако, их можно легко преобразовать

In [400]: pd.Series(s_aware.values).dt.tz_localize('UTC').dt.tz_convert('US/Eastern')
Out[400]: 
0   2013-01-01 00:00:00-05:00
1   2013-01-02 00:00:00-05:00
2   2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern]

© 2008–2012, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/0.19.2/timeseries.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API