Spec-Zone.ru › pandas 1

Функциональность временных рядов/дат

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

Например, pandas поддерживает:

Парсинг информации о временных рядах из различных источников и форматов

In [1]: import datetime

In [2]: dti = pd.to_datetime(
   ...:     ["1/1/2018", np.datetime64("2018-01-01"), datetime.datetime(2018, 1, 1)]
   ...: )
   ...: 

In [3]: dti
Out[3]: DatetimeIndex(['2018-01-01', '2018-01-01', '2018-01-01'], dtype='datetime64[ns]', freq=None)

Генерация последовательностей дат и временных интервалов с фиксированной частотой

In [4]: dti = pd.date_range("2018-01-01", periods=3, freq="H")

In [5]: dti
Out[5]: 
DatetimeIndex(['2018-01-01 00:00:00', '2018-01-01 01:00:00',
               '2018-01-01 02:00:00'],
              dtype='datetime64[ns]', freq='H')

Обработка и преобразование дат и времени с информацией о часовом поясе

In [6]: dti = dti.tz_localize("UTC")

In [7]: dti
Out[7]: 
DatetimeIndex(['2018-01-01 00:00:00+00:00', '2018-01-01 01:00:00+00:00',
               '2018-01-01 02:00:00+00:00'],
              dtype='datetime64[ns, UTC]', freq='H')

In [8]: dti.tz_convert("US/Pacific")
Out[8]: 
DatetimeIndex(['2017-12-31 16:00:00-08:00', '2017-12-31 17:00:00-08:00',
               '2017-12-31 18:00:00-08:00'],
              dtype='datetime64[ns, US/Pacific]', freq='H')

Ресемплирование или преобразование временного ряда в определённую частоту

In [9]: idx = pd.date_range("2018-01-01", periods=5, freq="H")

In [10]: ts = pd.Series(range(len(idx)), index=idx)

In [11]: ts
Out[11]: 
2018-01-01 00:00:00    0
2018-01-01 01:00:00    1
2018-01-01 02:00:00    2
2018-01-01 03:00:00    3
2018-01-01 04:00:00    4
Freq: H, dtype: int64

In [12]: ts.resample("2H").mean()
Out[12]: 
2018-01-01 00:00:00    0.5
2018-01-01 02:00:00    2.5
2018-01-01 04:00:00    4.0
Freq: 2H, dtype: float64

Выполнение арифметических операций с датами и временем с абсолютными или относительными приращениями времени

In [13]: friday = pd.Timestamp("2018-01-05")

In [14]: friday.day_name()
Out[14]: 'Friday'

# Add 1 day
In [15]: saturday = friday + pd.Timedelta("1 day")

In [16]: saturday.day_name()
Out[16]: 'Saturday'

# Add 1 business day (Friday --> Monday)
In [17]: monday = friday + pd.offsets.BDay()

In [18]: monday.day_name()
Out[18]: 'Monday'

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

Обзор

pandas охватывает 4 общих понятия, относящиеся ко времени:

  1. Даты и время: Конкретная дата и время с поддержкой часового пояса. Аналогично datetime.datetime из стандартной библиотеки.

  2. Временные интервалы: Абсолютная продолжительность времени. Аналогично datetime.timedelta из стандартной библиотеки.

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

  4. Смещения дат: Относительная продолжительность времени, учитывающая календарные арифметические операции. Аналогично dateutil.relativedelta.relativedelta из пакета dateutil.

Концепция

Скалярный класс

Массивноый класс

Тип данных pandas

Основной метод создания

Даты и время

Timestamp

DatetimeIndex

datetime64[ns] или datetime64[ns, tz]

to_datetime или date_range

Временные интервалы

Timedelta

TimedeltaIndex

timedelta64[ns]

to_timedelta или timedelta_range

Временные отрезки

Period

PeriodIndex

period[freq]

Period или period_range

Смещения дат

DateOffset

None

None

DateOffset

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

In [19]: pd.Series(range(3), index=pd.date_range("2000", freq="D", periods=3))
Out[19]: 
2000-01-01    0
2000-01-02    1
2000-01-03    2
Freq: D, dtype: int64

Однако, Series и DataFrame также могут напрямую поддерживать временную составляющую как сами данные.

In [20]: pd.Series(pd.date_range("2000", freq="D", periods=3))
Out[20]: 
0   2000-01-01
1   2000-01-02
2   2000-01-03
dtype: datetime64[ns]

Series и DataFrame имеют расширенную поддержку типов данных и функциональность для datetime, timedelta и Period данных при передаче в эти конструкторы. DateOffset данные, однако, будут храниться как данные object.

In [21]: pd.Series(pd.period_range("1/1/2011", freq="M", periods=3))
Out[21]: 
0    2011-01
1    2011-02
2    2011-03
dtype: period[M]

In [22]: pd.Series([pd.DateOffset(1), pd.DateOffset(2)])
Out[22]: 
0         <DateOffset>
1    <2 * DateOffsets>
dtype: object

In [23]: pd.Series(pd.date_range("1/1/2011", freq="M", periods=3))
Out[23]: 
0   2011-01-31
1   2011-02-28
2   2011-03-31
dtype: datetime64[ns]

Наконец, pandas представляет нулевые даты, временные интервалы и временные отрезки как NaT, что полезно для представления пропущенных или нулевых значений дат и имеет аналогичное поведение, как np.nan для данных с плавающей точкой.

In [24]: pd.Timestamp(pd.NaT)
Out[24]: NaT

In [25]: pd.Timedelta(pd.NaT)
Out[25]: NaT

In [26]: pd.Period(pd.NaT)
Out[26]: NaT

# Equality acts as np.nan would
In [27]: pd.NaT == pd.NaT
Out[27]: False

Маркировка времени против временных отрезков

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

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

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

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

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

Например:

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

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

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

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

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

In [35]: type(ts.index)
Out[35]: pandas.core.indexes.datetimes.DatetimeIndex

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

In [37]: ts
Out[37]: 
2012-05-01    0.469112
2012-05-02   -0.282863
2012-05-03   -1.509059
dtype: float64

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

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

In [40]: type(ts.index)
Out[40]: pandas.core.indexes.period.PeriodIndex

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

In [42]: ts
Out[42]: 
2012-01   -1.135632
2012-02    1.212112
2012-03   -0.173215
Freq: M, dtype: float64

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

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

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

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

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

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

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

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

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

Как видно из примера выше, dayfirst не является строгой. Если дату невозможно разобрать, предполагая, что день идёт первым, она будет обработана так, как если бы dayfirst было False, а в случае разбора строк дат с разделителями (например, 31-12-2012) также будет выведено предупреждение.

Если вы передадите единственную строку функции to_datetime, она вернёт единственную Timestamp. Timestamp также может принимать строковый ввод, но не принимает параметры разбора строк, такие как dayfirst или format, поэтому используйте to_datetime если они необходимы.

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

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

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

In [49]: pd.DatetimeIndex(["2018-01-01", "2018-01-03", "2018-01-05"])
Out[49]: DatetimeIndex(['2018-01-01', '2018-01-03', '2018-01-05'], dtype='datetime64[ns]', freq=None)

Строка ‘infer’ может быть передана для установки частоты индекса как выведенной частоты при создании:

In [50]: pd.DatetimeIndex(["2018-01-01", "2018-01-03", "2018-01-05"], freq="infer")
Out[50]: DatetimeIndex(['2018-01-01', '2018-01-03', '2018-01-05'], dtype='datetime64[ns]', freq='2D')

Передача аргумента format

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

In [51]: pd.to_datetime("2010/11/12", format="%Y/%m/%d")
Out[51]: Timestamp('2010-11-12 00:00:00')

In [52]: pd.to_datetime("12-11-2010 00:00", format="%d-%m-%Y %H:%M")
Out[52]: Timestamp('2010-11-12 00:00:00')

Для получения дополнительной информации о доступных вариантах при указании параметра format, см. документацию Python по datetime.

Сборка datetime из нескольких столбцов DataFrame

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

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

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

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

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

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

  • обязательные: year, month, day

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

Недействительные данные

По умолчанию, errors='raise', при невозможности разбора возникает исключение:

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

Передайте errors='ignore' для возврата исходного ввода при невозможности разбора:

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

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

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

Маркеры времени эпохи

pandas поддерживает преобразование целых или чисел с плавающей запятой эпохи в Timestamp и DatetimeIndex. Единицей измерения по умолчанию являются наносекунды, так как именно так хранятся внутренние объекты Timestamp. Однако эпохи часто хранятся в другой единице unit, которую можно указать. Они вычисляются от начальной точки, заданной параметром origin.

In [58]: pd.to_datetime(
   ....:     [1349720105, 1349806505, 1349892905, 1349979305, 1350065705], unit="s"
   ....: )
   ....: 
Out[58]: 
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 [59]: pd.to_datetime(
   ....:     [1349720105100, 1349720105200, 1349720105300, 1349720105400, 1349720105500],
   ....:     unit="ms",
   ....: )
   ....: 
Out[59]: 
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)

Примечание

Параметр unit не использует те же строки, что и параметр format , о котором говорилось выше. Доступные единицы перечислены в документации к pandas.to_datetime().

Изменено в версии 1.0.0.

Создание Timestamp или DatetimeIndex с временной меткой эпохи с указанным аргументом tz вызовет ошибку ValueError. Если у вас есть эпохи в локальном времени в другой временной зоне, вы можете считать эпохи как временные метки без учета временной зоны, а затем локализовать их в соответствующей временной зоне:

In [60]: pd.Timestamp(1262347200000000000).tz_localize("US/Pacific")
Out[60]: Timestamp('2010-01-01 12:00:00-0800', tz='US/Pacific')

In [61]: pd.DatetimeIndex([1262347200000000000]).tz_localize("US/Pacific")
Out[61]: DatetimeIndex(['2010-01-01 12:00:00-08:00'], dtype='datetime64[ns, US/Pacific]', freq=None)

Примечание

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

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

Преобразование чисел с плавающей запятой эпохи может привести к неточным и неожиданным результатам. Python с плавающей запятой имеют около 15 десятичных знаков точности. Округление при преобразовании из числа с плавающей запятой в высокую точность Timestamp неизбежно. Единственный способ достижения точности — использование типов фиксированной ширины (например, int64).

In [62]: pd.to_datetime([1490195805.433, 1490195805.433502912], unit="s")
Out[62]: DatetimeIndex(['2017-03-22 15:16:45.433000088', '2017-03-22 15:16:45.433502913'], dtype='datetime64[ns]', freq=None)

In [63]: pd.to_datetime(1490195805433502912, unit="ns")
Out[63]: Timestamp('2017-03-22 15:16:45.433502912')

См. также

Использование параметра origin

Из меток времени в эпоху

Чтобы выполнить обратную операцию, а именно преобразовать Timestamp в ‘unix’ эпоху:

In [64]: stamps = pd.date_range("2012-10-08 18:15:05", periods=4, freq="D")

In [65]: stamps
Out[65]: 
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'],
              dtype='datetime64[ns]', freq='D')

Мы вычитаем эпоху (полночь 1 января 1970 года по UTC) и затем целочисленно делим на «единицу» (1 секунду).

In [66]: (stamps - pd.Timestamp("1970-01-01")) // pd.Timedelta("1s")
Out[66]: Int64Index([1349720105, 1349806505, 1349892905, 1349979305], dtype='int64')

Использование параметра origin

Используя параметр origin , можно указать альтернативную начальную точку для создания DatetimeIndex . Например, чтобы использовать 1960-01-01 в качестве даты начала:

In [67]: pd.to_datetime([1, 2, 3], unit="D", origin=pd.Timestamp("1960-01-01"))
Out[67]: DatetimeIndex(['1960-01-02', '1960-01-03', '1960-01-04'], dtype='datetime64[ns]', freq=None)

По умолчанию установлено значение origin='unix', которое по умолчанию является 1970-01-01 00:00:00. Часто называется ‘unix epoch’ или POSIX time.

In [68]: pd.to_datetime([1, 2, 3], unit="D")
Out[68]: DatetimeIndex(['1970-01-02', '1970-01-03', '1970-01-04'], dtype='datetime64[ns]', freq=None)

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

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

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

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

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

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

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

На практике это становится очень громоздким, потому что нам часто нужен очень длинный индекс с большим количеством меток времени. Если нам нужны метки времени с регулярной частотой, мы можем использовать функции date_range() и bdate_range() для создания DatetimeIndex . По умолчанию частота для date_range — это календарный день, а по умолчанию для bdate_range — рабочий день:

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

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

In [76]: index = pd.date_range(start, end)

In [77]: index
Out[77]: 
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 [78]: index = pd.bdate_range(start, end)

In [79]: index
Out[79]: 
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 , могут использовать различные псевдонимы частоты:

In [80]: pd.date_range(start, periods=1000, freq="M")
Out[80]: 
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-30',
               '2011-05-31', '2011-06-30', '2011-07-31', '2011-08-31',
               '2011-09-30', '2011-10-31',
               ...
               '2093-07-31', '2093-08-31', '2093-09-30', '2093-10-31',
               '2093-11-30', '2093-12-31', '2094-01-31', '2094-02-28',
               '2094-03-31', '2094-04-30'],
              dtype='datetime64[ns]', length=1000, freq='M')

In [81]: pd.bdate_range(start, periods=250, freq="BQS")
Out[81]: 
DatetimeIndex(['2011-01-03', '2011-04-01', '2011-07-01', '2011-10-03',
               '2012-01-02', '2012-04-02', '2012-07-02', '2012-10-01',
               '2013-01-01', '2013-04-01',
               ...
               '2071-01-01', '2071-04-01', '2071-07-01', '2071-10-01',
               '2072-01-01', '2072-04-01', '2072-07-01', '2072-10-03',
               '2073-01-02', '2073-04-03'],
              dtype='datetime64[ns]', length=250, freq='BQS-JAN')

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

In [82]: pd.date_range(start, end, freq="BM")
Out[82]: 
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 [83]: pd.date_range(start, end, freq="W")
Out[83]: 
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 [84]: pd.bdate_range(end=end, periods=20)
Out[84]: 
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 [85]: pd.bdate_range(start=start, periods=20)
Out[85]: 
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')

Указание start, end, и periods сгенерирует диапазон равномерно распределённых дат от start до end включительно с количеством элементов periods в результирующем DatetimeIndex:

In [86]: pd.date_range("2018-01-01", "2018-01-05", periods=5)
Out[86]: 
DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04',
               '2018-01-05'],
              dtype='datetime64[ns]', freq=None)

In [87]: pd.date_range("2018-01-01", "2018-01-05", periods=10)
Out[87]: 
DatetimeIndex(['2018-01-01 00:00:00', '2018-01-01 10:40:00',
               '2018-01-01 21:20:00', '2018-01-02 08:00:00',
               '2018-01-02 18:40:00', '2018-01-03 05:20:00',
               '2018-01-03 16:00:00', '2018-01-04 02:40:00',
               '2018-01-04 13:20:00', '2018-01-05 00:00:00'],
              dtype='datetime64[ns]', freq=None)

Пользовательские диапазоны частоты

bdate_range также может генерировать диапазон дат с пользовательской частотой, используя параметры weekmask и holidays. Эти параметры будут использованы только в случае передачи пользовательской строки частоты.

In [88]: weekmask = "Mon Wed Fri"

In [89]: holidays = [datetime.datetime(2011, 1, 5), datetime.datetime(2011, 3, 14)]

In [90]: pd.bdate_range(start, end, freq="C", weekmask=weekmask, holidays=holidays)
Out[90]: 
DatetimeIndex(['2011-01-03', '2011-01-07', '2011-01-10', '2011-01-12',
               '2011-01-14', '2011-01-17', '2011-01-19', '2011-01-21',
               '2011-01-24', '2011-01-26',
               ...
               '2011-12-09', '2011-12-12', '2011-12-14', '2011-12-16',
               '2011-12-19', '2011-12-21', '2011-12-23', '2011-12-26',
               '2011-12-28', '2011-12-30'],
              dtype='datetime64[ns]', length=154, freq='C')

In [91]: pd.bdate_range(start, end, freq="CBMS", weekmask=weekmask)
Out[91]: 
DatetimeIndex(['2011-01-03', '2011-02-02', '2011-03-02', '2011-04-01',
               '2011-05-02', '2011-06-01', '2011-07-01', '2011-08-01',
               '2011-09-02', '2011-10-03', '2011-11-02', '2011-12-02'],
              dtype='datetime64[ns]', freq='CBMS')

См. также

Пользовательские рабочие дни

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

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

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

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

См. также

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

Индексирование

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

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

  • Быстрое смещение с использованием метода shift для объектов pandas.

  • Объединение перекрывающихся объектов DatetimeIndex с той же частотой очень быстро (важно для быстрого выравнивания данных).

  • Быстрый доступ к полям дат через свойства, такие как year, month, и т. д.

  • Функции регуляризации, такие как snap, и очень быстрая логика asof.

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

См. также

Методы повторного индексирования

Примечание

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

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

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

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

In [96]: ts.index
Out[96]: 
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 [97]: ts[:5].index
Out[97]: 
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29',
               '2011-05-31'],
              dtype='datetime64[ns]', freq='BM')

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

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

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

In [99]: ts["1/31/2011"]
Out[99]: 0.11920871129693428

In [100]: ts[datetime.datetime(2011, 12, 25):]
Out[100]: 
2011-12-30    0.56702
Freq: BM, dtype: float64

In [101]: ts["10/31/2011":"12/31/2011"]
Out[101]: 
2011-10-31    0.271860
2011-11-30   -0.424972
2011-12-30    0.567020
Freq: BM, dtype: float64

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

In [102]: ts["2011"]
Out[102]: 
2011-01-31    0.119209
2011-02-28   -1.044236
2011-03-31   -0.861849
2011-04-29   -2.104569
2011-05-31   -0.494929
2011-06-30    1.071804
2011-07-29    0.721555
2011-08-31   -0.706771
2011-09-30   -1.039575
2011-10-31    0.271860
2011-11-30   -0.424972
2011-12-30    0.567020
Freq: BM, dtype: float64

In [103]: ts["2011-6"]
Out[103]: 
2011-06-30    1.071804
Freq: BM, dtype: float64

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

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

Индексирование DataFrame строк с одной строкой с помощью getitem (например, frame[dtstring]) устарело начиная с pandas 1.2.0 (в связи с неоднозначностью, является ли это индексированием строк или выбором столбца) и будет удалено в будущей версии. Эквивалент с .loc (например, frame.loc[dtstring]) по-прежнему поддерживается.

In [104]: dft = pd.DataFrame(
   .....:     np.random.randn(100000, 1),
   .....:     columns=["A"],
   .....:     index=pd.date_range("20130101", periods=100000, freq="T"),
   .....: )
   .....: 

In [105]: dft
Out[105]: 
                            A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-03-11 10:35:00 -0.747967
2013-03-11 10:36:00 -0.034523
2013-03-11 10:37:00 -0.201754
2013-03-11 10:38:00 -1.509067
2013-03-11 10:39:00 -1.693043

[100000 rows x 1 columns]

In [106]: dft.loc["2013"]
Out[106]: 
                            A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-03-11 10:35:00 -0.747967
2013-03-11 10:36:00 -0.034523
2013-03-11 10:37:00 -0.201754
2013-03-11 10:38:00 -1.509067
2013-03-11 10:39:00 -1.693043

[100000 rows x 1 columns]

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

In [107]: dft["2013-1":"2013-2"]
Out[107]: 
                            A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-02-28 23:55:00  0.850929
2013-02-28 23:56:00  0.976712
2013-02-28 23:57:00 -2.693884
2013-02-28 23:58:00 -1.575535
2013-02-28 23:59:00 -1.573517

[84960 rows x 1 columns]

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

In [108]: dft["2013-1":"2013-2-28"]
Out[108]: 
                            A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-02-28 23:55:00  0.850929
2013-02-28 23:56:00  0.976712
2013-02-28 23:57:00 -2.693884
2013-02-28 23:58:00 -1.575535
2013-02-28 23:59:00 -1.573517

[84960 rows x 1 columns]

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

In [109]: dft["2013-1":"2013-2-28 00:00:00"]
Out[109]: 
                            A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-02-27 23:56:00  1.197749
2013-02-27 23:57:00  0.720521
2013-02-27 23:58:00 -0.072718
2013-02-27 23:59:00 -0.681192
2013-02-28 00:00:00 -0.557501

[83521 rows x 1 columns]

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

In [110]: dft["2013-1-15":"2013-1-15 12:30:00"]
Out[110]: 
                            A
2013-01-15 00:00:00 -0.984810
2013-01-15 00:01:00  0.941451
2013-01-15 00:02:00  1.559365
2013-01-15 00:03:00  1.034374
2013-01-15 00:04:00 -1.480656
...                       ...
2013-01-15 12:26:00  0.371454
2013-01-15 12:27:00 -0.930806
2013-01-15 12:28:00 -0.069177
2013-01-15 12:29:00  0.066510
2013-01-15 12:30:00 -0.003945

[751 rows x 1 columns]

Индексирование по части строки DatetimeIndex также работает с DataFrame с MultiIndex:

In [111]: 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 [112]: dft2
Out[112]: 
                              A
2013-01-01 00:00:00 a -0.298694
                    b  0.823553
2013-01-01 12:00:00 a  0.943285
                    b -1.479399
2013-01-02 00:00:00 a -1.643342
...                         ...
2013-01-04 12:00:00 b  0.069036
2013-01-05 00:00:00 a  0.122297
                    b  1.422060
2013-01-05 12:00:00 a  0.370079
                    b  1.016331

[20 rows x 1 columns]

In [113]: dft2.loc["2013-01-05"]
Out[113]: 
                              A
2013-01-05 00:00:00 a  0.122297
                    b  1.422060
2013-01-05 12:00:00 a  0.370079
                    b  1.016331

In [114]: idx = pd.IndexSlice

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

In [116]: dft2.loc[idx[:, "2013-01-05"], :]
Out[116]: 
                              A
a 2013-01-05 00:00:00  0.122297
  2013-01-05 12:00:00  0.370079
b 2013-01-05 00:00:00  1.422060
  2013-01-05 12:00:00  1.016331

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

Срезы с индексированием по строкам также учитывают смещение UTC.

In [117]: df = pd.DataFrame([0], index=pd.DatetimeIndex(["2019-01-01"], tz="US/Pacific"))

In [118]: df
Out[118]: 
                           0
2019-01-01 00:00:00-08:00  0

In [119]: df["2019-01-01 12:00:00+04:00":"2019-01-01 13:00:00+04:00"]
Out[119]: 
                           0
2019-01-01 00:00:00-08:00  0

Срез против точного совпадения

Та же строка, используемая в качестве параметра индексирования, может обрабатываться либо как срез, либо как точное совпадение в зависимости от разрешения индекса. Если строка менее точна, чем индекс, она будет обрабатываться как срез, в противном случае как точное совпадение.

Рассмотрим объект Series с минутным разрешением индекса:

In [120]: series_minute = pd.Series(
   .....:     [1, 2, 3],
   .....:     pd.DatetimeIndex(
   .....:         ["2011-12-31 23:59:00", "2012-01-01 00:00:00", "2012-01-01 00:02:00"]
   .....:     ),
   .....: )
   .....: 

In [121]: series_minute.index.resolution
Out[121]: 'minute'

Строка отметки времени, менее точная, чем минута, дает объект Series.

In [122]: series_minute["2011-12-31 23"]
Out[122]: 
2011-12-31 23:59:00    1
dtype: int64

Строка отметки времени с минутным разрешением (или более точная) дает скаляр вместо этого, т. е. она не преобразуется в срез.

In [123]: series_minute["2011-12-31 23:59"]
Out[123]: 1

In [124]: series_minute["2011-12-31 23:59:00"]
Out[124]: 1

Если разрешение индекса — секунда, то отметка времени с точностью до минуты дает Series.

In [125]: series_second = pd.Series(
   .....:     [1, 2, 3],
   .....:     pd.DatetimeIndex(
   .....:         ["2011-12-31 23:59:59", "2012-01-01 00:00:00", "2012-01-01 00:00:01"]
   .....:     ),
   .....: )
   .....: 

In [126]: series_second.index.resolution
Out[126]: 'second'

In [127]: series_second["2011-12-31 23:59"]
Out[127]: 
2011-12-31 23:59:59    1
dtype: int64

Если строка отметки времени обрабатывается как срез, она может использоваться для индексирования DataFrame с .loc[] также.

In [128]: dft_minute = pd.DataFrame(
   .....:     {"a": [1, 2, 3], "b": [4, 5, 6]}, index=series_minute.index
   .....: )
   .....: 

In [129]: dft_minute.loc["2011-12-31 23"]
Out[129]: 
                     a  b
2011-12-31 23:59:00  1  4

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

Однако, если строка обрабатывается как точное совпадение, выбор в DataFrame’s [] будет по столбцам, а не по строкам, см. Основы индексирования. Например, dft_minute['2011-12-31 23:59'] вызовет KeyError, так как '2012-12-31 23:59' имеет такое же разрешение, что и индекс, и нет столбца с таким именем:

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

In [130]: dft_minute.loc["2011-12-31 23:59"]
Out[130]: 
a    1
b    4
Name: 2011-12-31 23:59:00, dtype: int64

Также обратите внимание, что разрешение DatetimeIndex не может быть менее точным, чем день.

In [131]: series_monthly = pd.Series(
   .....:     [1, 2, 3], pd.DatetimeIndex(["2011-12", "2012-01", "2012-02"])
   .....: )
   .....: 

In [132]: series_monthly.index.resolution
Out[132]: 'day'

In [133]: series_monthly["2011-12"]  # returns Series
Out[133]: 
2011-12-01    1
dtype: int64

Точное индексирование

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

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

In [134]: dft[datetime.datetime(2013, 1, 1): datetime.datetime(2013, 2, 28)]
Out[134]: 
                            A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-02-27 23:56:00  1.197749
2013-02-27 23:57:00  0.720521
2013-02-27 23:58:00 -0.072718
2013-02-27 23:59:00 -0.681192
2013-02-28 00:00:00 -0.557501

[83521 rows x 1 columns]

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

In [135]: dft[
   .....:     datetime.datetime(2013, 1, 1, 10, 12, 0): datetime.datetime(
   .....:         2013, 2, 28, 10, 12, 0
   .....:     )
   .....: ]
   .....: 
Out[135]: 
                            A
2013-01-01 10:12:00  0.565375
2013-01-01 10:13:00  0.068184
2013-01-01 10:14:00  0.788871
2013-01-01 10:15:00 -0.280343
2013-01-01 10:16:00  0.931536
...                       ...
2013-02-28 10:08:00  0.148098
2013-02-28 10:09:00 -0.388138
2013-02-28 10:10:00  0.139348
2013-02-28 10:11:00  0.085288
2013-02-28 10:12:00  0.950146

[83521 rows x 1 columns]

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

Предоставлена удобная функция truncate(), аналогичная срезу. Обратите внимание, что truncate предполагает значение 0 для любого неопределённого компонента даты в DatetimeIndex в отличие от среза, который возвращает любые частично совпадающие даты:

In [136]: rng2 = pd.date_range("2011-01-01", "2012-01-01", freq="W")

In [137]: ts2 = pd.Series(np.random.randn(len(rng2)), index=rng2)

In [138]: ts2.truncate(before="2011-11", after="2011-12")
Out[138]: 
2011-11-06    0.437823
2011-11-13   -0.293083
2011-11-20   -0.059881
2011-11-27    1.252450
Freq: W-SUN, dtype: float64

In [139]: ts2["2011-11":"2011-12"]
Out[139]: 
2011-11-06    0.437823
2011-11-13   -0.293083
2011-11-20   -0.059881
2011-11-27    1.252450
2011-12-04    0.046611
2011-12-11    0.059478
2011-12-18   -0.286539
2011-12-25    0.841669
Freq: W-SUN, dtype: float64

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

In [140]: ts2[[0, 2, 6]].index
Out[140]: DatetimeIndex(['2011-01-02', '2011-01-16', '2011-02-13'], dtype='datetime64[ns]', freq=None)

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

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

Свойство

Описание

year

Год даты и времени

month

Месяц даты и времени

day

День даты и времени

hour

Час даты и времени

minute

Минуты даты и времени

second

Секунды даты и времени

microsecond

Микросекунды даты и времени

nanosecond

Наносекунды даты и времени

date

Возвращает datetime.date (не содержит информацию о часовом поясе)

time

Возвращает datetime.time (не содержит информацию о часовом поясе)

timetz

Возвращает datetime.time как местное время с информацией о часовом поясе

dayofyear

Порядковый номер дня в году

day_of_year

Порядковый номер дня в году

weekofyear

Порядковый номер недели в году

week

Порядковый номер недели в году

dayofweek

Номер дня недели (понедельник=0, воскресенье=6)

day_of_week

Номер дня недели (понедельник=0, воскресенье=6)

weekday

Номер дня недели (понедельник=0, воскресенье=6)

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 accessor, как подробно описано в разделе о accessor .dt.

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

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

In [141]: idx = pd.date_range(start="2019-12-29", freq="D", periods=4)

In [142]: idx.isocalendar()
Out[142]: 
            year  week  day
2019-12-29  2019    52    7
2019-12-30  2020     1    1
2019-12-31  2020     1    2
2020-01-01  2020     1    3

In [143]: idx.to_series().dt.isocalendar()
Out[143]: 
            year  week  day
2019-12-29  2019    52    7
2019-12-30  2020     1    1
2019-12-31  2020     1    2
2020-01-01  2020     1    3

Объекты DateOffset

В предыдущих примерах использовались строки частоты (например, 'D'), чтобы указать частоту, которая определяла:

  • как даты и время в DatetimeIndex были размещены при использовании date_range()

  • частоту Period или PeriodIndex

Эти строки частоты соответствуют объекту DateOffset и его подклассам. DateOffset похож на Timedelta, который представляет длительность времени, но следует особым правилам календарной продолжительности. Например, Timedelta день всегда увеличивает datetimes на 24 часа, а DateOffset день увеличивает datetimes до того же времени следующего дня, независимо от того, представляет ли день 23, 24 или 25 часов из-за перехода на летнее время. Однако все подклассы DateOffset которые составляют час или меньше (Hour, Minute, Second, Milli, Micro, Nano ) ведут себя как Timedelta и учитывают абсолютное время.

Базовый DateOffset действует аналогично dateutil.relativedelta (документация relativedelta), который сдвигает дату и время на соответствующую календарную продолжительность, указанную. Арифметический оператор (+) может использоваться для выполнения сдвига.

# This particular day contains a day light savings time transition
In [144]: ts = pd.Timestamp("2016-10-30 00:00:00", tz="Europe/Helsinki")

# Respects absolute time
In [145]: ts + pd.Timedelta(days=1)
Out[145]: Timestamp('2016-10-30 23:00:00+0200', tz='Europe/Helsinki')

# Respects calendar time
In [146]: ts + pd.DateOffset(days=1)
Out[146]: Timestamp('2016-10-31 00:00:00+0200', tz='Europe/Helsinki')

In [147]: friday = pd.Timestamp("2018-01-05")

In [148]: friday.day_name()
Out[148]: 'Friday'

# Add 2 business days (Friday --> Tuesday)
In [149]: two_business_days = 2 * pd.offsets.BDay()

In [150]: friday + two_business_days
Out[150]: Timestamp('2018-01-09 00:00:00')

In [151]: (friday + two_business_days).day_name()
Out[151]: 'Tuesday'

У большинства DateOffsets есть связанные строки частот или псевдонимы сдвига, которые можно передавать в freq ключевые аргументы. Доступные сдвиги дат и соответствующие строки частот приведены ниже:

Сдвиг даты

Строка частоты

Описание

DateOffset

None

Общий класс смещения, по умолчанию абсолютные 24 часа

BDay или BusinessDay

'B'

рабочий день (будний день)

CDay или CustomBusinessDay

'C'

настраиваемый рабочий день

Week

'W'

одна неделя, необязательно привязанная к дню недели

WeekOfMonth

'WOM'

x-й день y-й недели каждого месяца

LastWeekOfMonth

'LWOM'

x-й день последней недели каждого месяца

MonthEnd

'M'

конец календарного месяца

MonthBegin

'MS'

начало календарного месяца

BMonthEnd или BusinessMonthEnd

'BM'

конец рабочего месяца

BMonthBegin или BusinessMonthBegin

'BMS'

начало рабочего месяца

CBMonthEnd или CustomBusinessMonthEnd

'CBM'

конец настраиваемого рабочего месяца

CBMonthBegin или CustomBusinessMonthBegin

'CBMS'

начало настраиваемого рабочего месяца

SemiMonthEnd

'SM'

15-е число (или другой день_месяца) и конец календарного месяца

SemiMonthBegin

'SMS'

15-е число (или другой день_месяца) и начало календарного месяца

QuarterEnd

'Q'

конец календарного квартала

QuarterBegin

'QS'

начало календарного квартала

BQuarterEnd

'BQ

конец рабочего квартала

BQuarterBegin

'BQS'

начало рабочего квартала

FY5253Quarter

'REQ'

квартал торгового года (с 52-53 неделями)

YearEnd

'A'

конец календарного года

YearBegin

'AS' или 'BYS'

начало календарного года

BYearEnd

'BA'

конец рабочего года

BYearBegin

'BAS'

начало рабочего года

FY5253

'RE'

год торгового года (с 52-53 неделями)

Easter

None

Пасха

BusinessHour

'BH'

рабочий час

CustomBusinessHour

'CBH'

настраиваемый рабочий час

Day

'D'

один абсолютный день

Hour

'H'

один час

Minute

'T' или 'min'

одна минута

Second

'S'

одна секунда

Milli

'L' или 'ms'

одна миллисекунда

Micro

'U' или 'us'

одна микросекунда

Nano

'N'

одна наносекунда

DateOffsets также имеют методы rollforward() и rollback() для смещения даты вперёд или назад соответственно до действительной даты смещения относительно смещения. Например, бизнес-смещения будут переносить даты, которые выпадают на выходные (суббота и воскресенье) на понедельник, так как бизнес-смещения действуют в будние дни.

In [152]: ts = pd.Timestamp("2018-01-06 00:00:00")

In [153]: ts.day_name()
Out[153]: 'Saturday'

# BusinessHour's valid offset dates are Monday through Friday
In [154]: offset = pd.offsets.BusinessHour(start="09:00")

# Bring the date to the closest offset date (Monday)
In [155]: offset.rollforward(ts)
Out[155]: Timestamp('2018-01-08 09:00:00')

# Date is brought to the closest offset date first and then the hour is added
In [156]: ts + offset
Out[156]: Timestamp('2018-01-08 10:00:00')

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

In [157]: ts = pd.Timestamp("2014-01-01 09:00")

In [158]: day = pd.offsets.Day()

In [159]: day + ts
Out[159]: Timestamp('2014-01-02 09:00:00')

In [160]: (day + ts).normalize()
Out[160]: Timestamp('2014-01-02 00:00:00')

In [161]: ts = pd.Timestamp("2014-01-01 22:00")

In [162]: hour = pd.offsets.Hour()

In [163]: hour + ts
Out[163]: Timestamp('2014-01-01 23:00:00')

In [164]: (hour + ts).normalize()
Out[164]: Timestamp('2014-01-01 00:00:00')

In [165]: (hour + pd.Timestamp("2014-01-01 23:30")).normalize()
Out[165]: Timestamp('2014-01-02 00:00:00')

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

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

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

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

In [168]: d + pd.offsets.Week()
Out[168]: Timestamp('2008-08-25 09:00:00')

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

In [170]: (d + pd.offsets.Week(weekday=4)).weekday()
Out[170]: 4

In [171]: d - pd.offsets.Week()
Out[171]: Timestamp('2008-08-11 09:00:00')

Вариант normalize будет эффективен для сложения и вычитания.

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

In [173]: d - pd.offsets.Week(normalize=True)
Out[173]: Timestamp('2008-08-11 00:00:00')

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

In [174]: d + pd.offsets.YearEnd()
Out[174]: Timestamp('2008-12-31 09:00:00')

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Настраиваемые рабочие дни

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

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

In [187]: weekmask_egypt = "Sun Mon Tue Wed Thu"

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

In [189]: bday_egypt = pd.offsets.CustomBusinessDay(
   .....:     holidays=holidays,
   .....:     weekmask=weekmask_egypt,
   .....: )
   .....: 

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

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

Давайте сопоставим с именами дней недели:

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

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

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

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

In [195]: bday_us = pd.offsets.CustomBusinessDay(calendar=USFederalHolidayCalendar())

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

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

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

In [198]: bmth_us = pd.offsets.CustomBusinessMonthBegin(calendar=USFederalHolidayCalendar())

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

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

# Define date index with custom offset
In [201]: pd.date_range(start="20100101", end="20120101", freq=bmth_us)
Out[201]: 
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 DateOffset. Важно отметить, что так как CustomBusinessDay является параметризованным типом, экземпляры CustomBusinessDay могут отличаться, и это не может быть обнаружено из строки частоты ‘C’. Поэтому пользователь должен убедиться, что строка частоты ‘C’ используется последовательно в приложении пользователя.

Рабочее время

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

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

In [202]: bh = pd.offsets.BusinessHour()

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

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

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

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

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

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

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

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

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

In [211]: bh = pd.offsets.BusinessHour(start="11:00", end=datetime.time(20, 0))

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

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

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

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

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

In [216]: bh = pd.offsets.BusinessHour(start="17:00", end="09:00")

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

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

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

# Although 2014-08-02 is Saturday,
# it is valid because it starts from 08-01 (Friday).
In [220]: pd.Timestamp("2014-08-02 04:00") + bh
Out[220]: 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 [221]: pd.Timestamp("2014-08-04 04:00") + bh
Out[221]: 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 [222]: pd.offsets.BusinessHour().rollback(pd.Timestamp("2014-08-02 15:00"))
Out[222]: Timestamp('2014-08-01 17:00:00')

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

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

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

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

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

Настраиваемое рабочее время

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

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

In [228]: bhour_us = pd.offsets.CustomBusinessHour(calendar=USFederalHolidayCalendar())

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

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

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

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

In [232]: bhour_mon = pd.offsets.CustomBusinessHour(start="10:00", weekmask="Tue Wed Thu Fri")

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

Псевдонимы сдвигов

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

Псевдоним

Описание

B

частота рабочих дней

C

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

D

частота календарных дней

W

частота недель

M

частота конца месяца

SM

частота полумесяца (15-е число и конец месяца)

BM

частота конца рабочего месяца

CBM

частота пользовательского конца рабочего месяца

MS

частота начала месяца

SMS

частота начала полумесяца (1-е и 15-е числа)

BMS

частота начала рабочего месяца

CBMS

частота пользовательского начала рабочего месяца

Q

частота конца квартала

BQ

частота конца квартала (рабочие дни)

QS

частота начала квартала

BQS

частота начала квартала (рабочие дни)

A, Y

частота конца года

BA, BY

частота конца рабочего года

AS, YS

частота начала года

BAS, BYS

частота начала рабочего года

BH

частота рабочего часа

H

частота часов

T, min

частота минут

S

частота секунд

L, ms

миллисекунды

U, us

микросекунды

N

наносекунды

Примечание

При использовании вышеперечисленных псевдонимов сдвигов следует учитывать, что такие функции, как date_range(), bdate_range(), будут возвращать только отметки времени, находящиеся в интервале, определенном start_date и end_date. Если start_date не соответствует частоте, возвращаемые отметки времени будут начинаться с ближайшей допустимой отметки времени, то же самое относится к end_date, возвращаемые отметки времени будут заканчиваться на предыдущей допустимой отметке времени.

Например, для сдвига MS, если start_date не является первым числом месяца, возвращаемые отметки времени будут начинаться с первого числа следующего месяца. Если end_date не является первым числом месяца, последней возвращаемой отметкой времени будет первое число соответствующего месяца.

In [234]: dates_lst_1 = pd.date_range("2020-01-06", "2020-04-03", freq="MS")

In [235]: dates_lst_1
Out[235]: DatetimeIndex(['2020-02-01', '2020-03-01', '2020-04-01'], dtype='datetime64[ns]', freq='MS')

In [236]: dates_lst_2 = pd.date_range("2020-01-01", "2020-04-01", freq="MS")

In [237]: dates_lst_2
Out[237]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01', '2020-04-01'], dtype='datetime64[ns]', freq='MS')

Из примера видно, что date_range() и bdate_range() вернут только допустимые отметки времени между start_date и end_date. Если эти отметки времени не являются допустимыми для заданной частоты, то будет произведен сдвиг к следующему значению для start_date (соответственно к предыдущему для end_date).

Объединение псевдонимов

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

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

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

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

In [240]: pd.date_range(start, periods=10, freq="2h20min")
Out[240]: 
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 [241]: pd.date_range(start, periods=10, freq="1D10U")
Out[241]: 
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

частота кварталов, конец года в феврале

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

Семантика привязанных сдвигов

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Правило

Описание

nearest_workday

переместить субботу на пятницу, а воскресенье на понедельник

sunday_to_monday

переместить воскресенье на следующий понедельник

next_monday_or_tuesday

переместить субботу на понедельник, а воскресенье/понедельник на вторник

previous_friday

переместить субботу и воскресенье на предыдущую пятницу

next_monday

переместить субботу и воскресенье на следующий понедельник

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

In [258]: from pandas.tseries.holiday import (
   .....:     Holiday,
   .....:     USMemorialDay,
   .....:     AbstractHolidayCalendar,
   .....:     nearest_workday,
   .....:     MO,
   .....: )
   .....: 

In [259]: class ExampleCalendar(AbstractHolidayCalendar):
   .....:     rules = [
   .....:         USMemorialDay,
   .....:         Holiday("July 4th", month=7, day=4, observance=nearest_workday),
   .....:         Holiday(
   .....:             "Columbus Day",
   .....:             month=10,
   .....:             day=1,
   .....:             offset=pd.DateOffset(weekday=MO(2)),
   .....:         ),
   .....:     ]
   .....: 

In [260]: cal = ExampleCalendar()

In [261]: cal.holidays(datetime.datetime(2012, 1, 1), datetime.datetime(2012, 12, 31))
Out[261]: DatetimeIndex(['2012-05-28', '2012-07-04', '2012-10-08'], dtype='datetime64[ns]', freq=None)
подсказка

weekday=MO(2) эквивалентно 2 * Week(weekday=2)

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

In [262]: pd.date_range(
   .....:     start="7/1/2012", end="7/10/2012", freq=pd.offsets.CDay(calendar=cal)
   .....: ).to_pydatetime()
   .....: 
Out[262]: 
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 [263]: offset = pd.offsets.CustomBusinessDay(calendar=cal)

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

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

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

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

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

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

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

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

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

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

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

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

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

In [274]: cal = get_calendar("ExampleCalendar")

In [275]: cal.rules
Out[275]: 
[Holiday: Memorial Day (month=5, day=31, offset=<DateOffset: weekday=MO(-1)>),
 Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7f27b75b8790>),
 Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO(+2)>)]

In [276]: new_cal = HolidayCalendarFactory("NewExampleCalendar", cal, USLaborDay)

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

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

Смещение / запаздывание

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

In [278]: ts = pd.Series(range(len(rng)), index=rng)

In [279]: ts = ts[:5]

In [280]: ts.shift(1)
Out[280]: 
2012-01-01    NaN
2012-01-02    0.0
2012-01-03    1.0
Freq: D, dtype: float64

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

Когда указано freq, метод shift изменяет все даты в индексе, а не меняет выравнивание данных и индекса:

In [281]: ts.shift(5, freq="D")
Out[281]: 
2012-01-06    0
2012-01-07    1
2012-01-08    2
Freq: D, dtype: int64

In [282]: ts.shift(5, freq=pd.offsets.BDay())
Out[282]: 
2012-01-06    0
2012-01-09    1
2012-01-10    2
dtype: int64

In [283]: ts.shift(5, freq="BM")
Out[283]: 
2012-05-31    0
2012-05-31    1
2012-05-31    2
dtype: int64

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

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

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

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

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

In [286]: ts
Out[286]: 
2010-01-01    1.494522
2010-01-06   -0.778425
2010-01-11   -0.253355
Freq: 3B, dtype: float64

In [287]: ts.asfreq(pd.offsets.BDay())
Out[287]: 
2010-01-01    1.494522
2010-01-04         NaN
2010-01-05         NaN
2010-01-06   -0.778425
2010-01-07         NaN
2010-01-08         NaN
2010-01-11   -0.253355
Freq: B, dtype: float64

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

In [288]: ts.asfreq(pd.offsets.BDay(), method="pad")
Out[288]: 
2010-01-01    1.494522
2010-01-04    1.494522
2010-01-05    1.494522
2010-01-06   -0.778425
2010-01-07   -0.778425
2010-01-08   -0.778425
2010-01-11   -0.253355
Freq: B, dtype: float64

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

Связанные с asfreq и reindex fillna(), который документирован в разделе пропущенные данные.

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

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

END_OF_DOCUMENT_MARKER

Ресемплирование

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

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

Метод resample() можно использовать непосредственно из объектов DataFrameGroupBy, см. документацию по группировке.

Основы

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

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

In [291]: ts.resample("5Min").sum()
Out[291]: 
2012-01-01    25103
Freq: 5T, dtype: int64

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

Любая функция, доступная через диспетчеризацию, доступна в качестве метода возвращаемого объекта, включая sum, mean, std, sem, max, min, median, first, last, ohlc:

In [292]: ts.resample("5Min").mean()
Out[292]: 
2012-01-01    251.03
Freq: 5T, dtype: float64

In [293]: ts.resample("5Min").ohlc()
Out[293]: 
            open  high  low  close
2012-01-01   308   460    9    205

In [294]: ts.resample("5Min").max()
Out[294]: 
2012-01-01    460
Freq: 5T, dtype: int64

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

In [295]: ts.resample("5Min", closed="right").mean()
Out[295]: 
2011-12-31 23:55:00    308.000000
2012-01-01 00:00:00    250.454545
Freq: 5T, dtype: float64

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

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

In [297]: ts.resample("5Min").mean()  # by default label='left'
Out[297]: 
2012-01-01    251.03
Freq: 5T, dtype: float64

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

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

Значения по умолчанию для label и closed равны ‘left’ для всех сдвигов частоты, кроме ‘M’, ‘A’, ‘Q’, ‘BM’, ‘BA’, ‘BQ’ и ‘W’, у которых значение по умолчанию равно ‘right’.

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

In [299]: s = pd.date_range("2000-01-01", "2000-01-05").to_series()

In [300]: s.iloc[2] = pd.NaT

In [301]: s.dt.day_name()
Out[301]: 
2000-01-01     Saturday
2000-01-02       Sunday
2000-01-03          NaN
2000-01-04      Tuesday
2000-01-05    Wednesday
Freq: D, dtype: object

# default: label='left', closed='left'
In [302]: s.resample("B").last().dt.day_name()
Out[302]: 
1999-12-31       Sunday
2000-01-03          NaN
2000-01-04      Tuesday
2000-01-05    Wednesday
Freq: B, dtype: object

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

In [303]: s.resample("B", label="right", closed="right").last().dt.day_name()
Out[303]: 
2000-01-03       Sunday
2000-01-04      Tuesday
2000-01-05    Wednesday
Freq: B, dtype: object

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

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

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

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

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

# from secondly to every 250 milliseconds
In [304]: ts[:2].resample("250L").asfreq()
Out[304]: 
2012-01-01 00:00:00.000    308.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    204.0
Freq: 250L, dtype: float64

In [305]: ts[:2].resample("250L").ffill()
Out[305]: 
2012-01-01 00:00:00.000    308
2012-01-01 00:00:00.250    308
2012-01-01 00:00:00.500    308
2012-01-01 00:00:00.750    308
2012-01-01 00:00:01.000    204
Freq: 250L, dtype: int64

In [306]: ts[:2].resample("250L").ffill(limit=2)
Out[306]: 
2012-01-01 00:00:00.000    308.0
2012-01-01 00:00:00.250    308.0
2012-01-01 00:00:00.500    308.0
2012-01-01 00:00:00.750      NaN
2012-01-01 00:00:01.000    204.0
Freq: 250L, dtype: float64

Ресемплирование разреженных данных

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

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

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

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

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

In [309]: ts.resample("3T").sum()
Out[309]: 
2014-01-01 00:00:00     0
2014-01-01 00:03:00     0
2014-01-01 00:06:00     0
2014-01-01 00:09:00     0
2014-01-01 00:12:00     0
                       ..
2014-04-09 23:48:00     0
2014-04-09 23:51:00     0
2014-04-09 23:54:00     0
2014-04-09 23:57:00     0
2014-04-10 00:00:00    99
Freq: 3T, Length: 47521, dtype: int64

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

In [310]: from functools import partial

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

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

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

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

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

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

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

In [315]: r = df.resample("3T")

In [316]: r.mean()
Out[316]: 
                            A         B         C
2012-01-01 00:00:00 -0.033823 -0.121514 -0.081447
2012-01-01 00:03:00  0.056909  0.146731 -0.024320
2012-01-01 00:06:00 -0.058837  0.047046 -0.052021
2012-01-01 00:09:00  0.063123 -0.026158 -0.066533
2012-01-01 00:12:00  0.186340 -0.003144  0.074752
2012-01-01 00:15:00 -0.085954 -0.016287 -0.050046

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

In [317]: r["A"].mean()
Out[317]: 
2012-01-01 00:00:00   -0.033823
2012-01-01 00:03:00    0.056909
2012-01-01 00:06:00   -0.058837
2012-01-01 00:09:00    0.063123
2012-01-01 00:12:00    0.186340
2012-01-01 00:15:00   -0.085954
Freq: 3T, Name: A, dtype: float64

In [318]: r[["A", "B"]].mean()
Out[318]: 
                            A         B
2012-01-01 00:00:00 -0.033823 -0.121514
2012-01-01 00:03:00  0.056909  0.146731
2012-01-01 00:06:00 -0.058837  0.047046
2012-01-01 00:09:00  0.063123 -0.026158
2012-01-01 00:12:00  0.186340 -0.003144
2012-01-01 00:15:00 -0.085954 -0.016287

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

In [319]: r["A"].agg([np.sum, np.mean, np.std])
Out[319]: 
                           sum      mean       std
2012-01-01 00:00:00  -6.088060 -0.033823  1.043263
2012-01-01 00:03:00  10.243678  0.056909  1.058534
2012-01-01 00:06:00 -10.590584 -0.058837  0.949264
2012-01-01 00:09:00  11.362228  0.063123  1.028096
2012-01-01 00:12:00  33.541257  0.186340  0.884586
2012-01-01 00:15:00  -8.595393 -0.085954  1.035476

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

In [320]: r.agg([np.sum, np.mean])
Out[320]: 
                             A            ...          C          
                           sum      mean  ...        sum      mean
2012-01-01 00:00:00  -6.088060 -0.033823  ... -14.660515 -0.081447
2012-01-01 00:03:00  10.243678  0.056909  ...  -4.377642 -0.024320
2012-01-01 00:06:00 -10.590584 -0.058837  ...  -9.363825 -0.052021
2012-01-01 00:09:00  11.362228  0.063123  ... -11.975895 -0.066533
2012-01-01 00:12:00  33.541257  0.186340  ...  13.455299  0.074752
2012-01-01 00:15:00  -8.595393 -0.085954  ...  -5.004580 -0.050046

[6 rows x 6 columns]

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

In [321]: r.agg({"A": np.sum, "B": lambda x: np.std(x, ddof=1)})
Out[321]: 
                             A         B
2012-01-01 00:00:00  -6.088060  1.001294
2012-01-01 00:03:00  10.243678  1.074597
2012-01-01 00:06:00 -10.590584  0.987309
2012-01-01 00:09:00  11.362228  0.944953
2012-01-01 00:12:00  33.541257  1.095025
2012-01-01 00:15:00  -8.595393  1.035312

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

In [322]: r.agg({"A": "sum", "B": "std"})
Out[322]: 
                             A         B
2012-01-01 00:00:00  -6.088060  1.001294
2012-01-01 00:03:00  10.243678  1.074597
2012-01-01 00:06:00 -10.590584  0.987309
2012-01-01 00:09:00  11.362228  0.944953
2012-01-01 00:12:00  33.541257  1.095025
2012-01-01 00:15:00  -8.595393  1.035312

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

In [323]: r.agg({"A": ["sum", "std"], "B": ["mean", "std"]})
Out[323]: 
                             A                   B          
                           sum       std      mean       std
2012-01-01 00:00:00  -6.088060  1.043263 -0.121514  1.001294
2012-01-01 00:03:00  10.243678  1.058534  0.146731  1.074597
2012-01-01 00:06:00 -10.590584  0.949264  0.047046  0.987309
2012-01-01 00:09:00  11.362228  1.028096 -0.026158  0.944953
2012-01-01 00:12:00  33.541257  0.884586 -0.003144  1.095025
2012-01-01 00:15:00  -8.595393  1.035476 -0.016287  1.035312

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

In [324]: 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 [325]: df
Out[325]: 
                   date  a
v d                       
1 2015-01-04 2015-01-04  0
2 2015-01-11 2015-01-11  1
3 2015-01-18 2015-01-18  2
4 2015-01-25 2015-01-25  3
5 2015-02-01 2015-02-01  4

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

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

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

Итерация по группам

Имея на руках объект Resampler , итерация по сгруппированным данным очень естественна и аналогична itertools.groupby():

In [328]: small = pd.Series(
   .....:     range(6),
   .....:     index=pd.to_datetime(
   .....:         [
   .....:             "2017-01-01T00:00:00",
   .....:             "2017-01-01T00:30:00",
   .....:             "2017-01-01T00:31:00",
   .....:             "2017-01-01T01:00:00",
   .....:             "2017-01-01T03:00:00",
   .....:             "2017-01-01T03:05:00",
   .....:         ]
   .....:     ),
   .....: )
   .....: 

In [329]: resampled = small.resample("H")

In [330]: for name, group in resampled:
   .....:     print("Group: ", name)
   .....:     print("-" * 27)
   .....:     print(group, end="\n\n")
   .....: 
Group:  2017-01-01 00:00:00
---------------------------
2017-01-01 00:00:00    0
2017-01-01 00:30:00    1
2017-01-01 00:31:00    2
dtype: int64

Group:  2017-01-01 01:00:00
---------------------------
2017-01-01 01:00:00    3
dtype: int64

Group:  2017-01-01 02:00:00
---------------------------
Series([], dtype: int64)

Group:  2017-01-01 03:00:00
---------------------------
2017-01-01 03:00:00    4
2017-01-01 03:05:00    5
dtype: int64

См. Итерация по группам или Resampler.__iter__ для более подробной информации.

Используйте origin или offset для изменения начала интервалов

В версии 1.1.0.

Интервалы группировки корректируются на основе начала дня точки отсчета временного ряда. Это хорошо работает с частотами, кратными дню (например, 30D ) или делящими день на равные части (например, 90s или 1min). Это может создавать несоответствия с некоторыми частотами, которые не соответствуют этим критериям. Чтобы изменить это поведение, вы можете указать фиксированную метку времени с аргументом origin.

Например:

In [331]: start, end = "2000-10-01 23:30:00", "2000-10-02 00:30:00"

In [332]: middle = "2000-10-02 00:00:00"

In [333]: rng = pd.date_range(start, end, freq="7min")

In [334]: ts = pd.Series(np.arange(len(rng)) * 3, index=rng)

In [335]: ts
Out[335]: 
2000-10-01 23:30:00     0
2000-10-01 23:37:00     3
2000-10-01 23:44:00     6
2000-10-01 23:51:00     9
2000-10-01 23:58:00    12
2000-10-02 00:05:00    15
2000-10-02 00:12:00    18
2000-10-02 00:19:00    21
2000-10-02 00:26:00    24
Freq: 7T, dtype: int64

Здесь мы видим, что при использовании origin с его значением по умолчанию ('start_day') результат после '2000-10-02 00:00:00' не идентичен в зависимости от начала временного ряда:

In [336]: ts.resample("17min", origin="start_day").sum()
Out[336]: 
2000-10-01 23:14:00     0
2000-10-01 23:31:00     9
2000-10-01 23:48:00    21
2000-10-02 00:05:00    54
2000-10-02 00:22:00    24
Freq: 17T, dtype: int64

In [337]: ts[middle:end].resample("17min", origin="start_day").sum()
Out[337]: 
2000-10-02 00:00:00    33
2000-10-02 00:17:00    45
Freq: 17T, dtype: int64

Здесь мы видим, что при установке origin на 'epoch', результат после '2000-10-02 00:00:00' идентичен вне зависимости от начала временного ряда:

In [338]: ts.resample("17min", origin="epoch").sum()
Out[338]: 
2000-10-01 23:18:00     0
2000-10-01 23:35:00    18
2000-10-01 23:52:00    27
2000-10-02 00:09:00    39
2000-10-02 00:26:00    24
Freq: 17T, dtype: int64

In [339]: ts[middle:end].resample("17min", origin="epoch").sum()
Out[339]: 
2000-10-01 23:52:00    15
2000-10-02 00:09:00    39
2000-10-02 00:26:00    24
Freq: 17T, dtype: int64

При необходимости вы можете использовать пользовательскую метку времени для origin:

In [340]: ts.resample("17min", origin="2001-01-01").sum()
Out[340]: 
2000-10-01 23:30:00     9
2000-10-01 23:47:00    21
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17T, dtype: int64

In [341]: ts[middle:end].resample("17min", origin=pd.Timestamp("2001-01-01")).sum()
Out[341]: 
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17T, dtype: int64

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

In [342]: ts.resample("17min", origin="start").sum()
Out[342]: 
2000-10-01 23:30:00     9
2000-10-01 23:47:00    21
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17T, dtype: int64

In [343]: ts.resample("17min", offset="23h30min").sum()
Out[343]: 
2000-10-01 23:30:00     9
2000-10-01 23:47:00    21
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17T, dtype: int64

Обратите внимание на использование 'start' для origin в последнем примере. В этом случае origin будет установлено на первое значение временного ряда.

Обратное ресемплирование

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

Вместо изменения начала бинов, иногда нам нужно исправить конец бинов, чтобы выполнить обратное ресемплирование с заданным freq. Обратное ресемплирование устанавливает closed в 'right' по умолчанию, так как последнее значение должно рассматриваться как граничная точка последнего бина.

Мы можем установить origin в 'end'. Значение для определенного индекса Timestamp представляет собой результат ресемплирования от текущего Timestamp минус freq до текущего Timestamp с правым включением.

In [344]: ts.resample('17min', origin='end').sum()
Out[344]: 
2000-10-01 23:35:00     0
2000-10-01 23:52:00    18
2000-10-02 00:09:00    27
2000-10-02 00:26:00    63
Freq: 17T, dtype: int64

Кроме того, в отличие от параметра 'start_day', поддерживается параметр end_day. Это установит начало в полдень потолка самого большого Timestamp.

In [345]: ts.resample('17min', origin='end_day').sum()
Out[345]: 
2000-10-01 23:38:00     3
2000-10-01 23:55:00    15
2000-10-02 00:12:00    45
2000-10-02 00:29:00    45
Freq: 17T, dtype: int64

Вышеупомянутый результат использует 2000-10-02 00:29:00 в качестве правой границы последнего бина, поскольку это используется в последующих вычислениях.

In [346]: ceil_mid = rng.max().ceil('D')

In [347]: freq = pd.offsets.Minute(17)

In [348]: bin_res = ceil_mid - freq * ((ceil_mid - rng.max()) // freq)

In [349]: bin_res
Out[349]: Timestamp('2000-10-02 00:29:00')

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

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

Период

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

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

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

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

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

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

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

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

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

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

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

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

In [360]: p == pd.Period("2012-01", freq="3M")
Out[360]: False

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

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

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

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

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

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

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

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

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

In [367]: pd.Period("2012", freq="A-DEC") - pd.Period("2002", freq="A-DEC")
Out[367]: <10 * YearEnds: month=12>

PeriodIndex и period_range

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

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

In [369]: prng
Out[369]: 
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]')

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

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

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

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

Если start или end являются объектами Period, они будут использованы в качестве опорных конечных точек для PeriodIndex с частотой, соответствующей частоте конструктора PeriodIndex.

In [372]: pd.period_range(
   .....:     start=pd.Period("2017Q1", freq="Q"), end=pd.Period("2017Q2", freq="Q"), freq="M"
   .....: )
   .....: 
Out[372]: PeriodIndex(['2017-03', '2017-04', '2017-05', '2017-06'], dtype='period[M]')

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

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

In [374]: ps
Out[374]: 
2011-01   -2.916901
2011-02    0.514474
2011-03    1.346470
2011-04    0.816397
2011-05    2.258648
2011-06    0.494789
2011-07    0.301239
2011-08    0.464776
2011-09   -1.393581
2011-10    0.056780
2011-11    0.197035
2011-12    2.261385
2012-01   -0.329583
Freq: M, dtype: float64

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

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

In [376]: idx
Out[376]: 
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]')

In [377]: idx + pd.offsets.Hour(2)
Out[377]: 
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]')

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

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

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

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

Типы данных Period

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

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

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

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

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

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

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

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

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

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

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

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

PeriodIndex теперь поддерживает частичное строковое срезы с немонотонными индексами.

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

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

In [389]: ps["2011-01"]
Out[389]: -2.9169013294054507

In [390]: ps[datetime.datetime(2011, 12, 25):]
Out[390]: 
2011-12    2.261385
2012-01   -0.329583
Freq: M, dtype: float64

In [391]: ps["10/31/2011":"12/31/2011"]
Out[391]: 
2011-10    0.056780
2011-11    0.197035
2011-12    2.261385
Freq: M, dtype: float64

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

In [392]: ps["2011"]
Out[392]: 
2011-01   -2.916901
2011-02    0.514474
2011-03    1.346470
2011-04    0.816397
2011-05    2.258648
2011-06    0.494789
2011-07    0.301239
2011-08    0.464776
2011-09   -1.393581
2011-10    0.056780
2011-11    0.197035
2011-12    2.261385
Freq: M, dtype: float64

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

In [394]: dfp
Out[394]: 
                         A
2013-01-01 09:00 -0.538468
2013-01-01 09:01 -1.365819
2013-01-01 09:02 -0.969051
2013-01-01 09:03 -0.331152
2013-01-01 09:04 -0.245334
...                    ...
2013-01-01 18:55  0.522460
2013-01-01 18:56  0.118710
2013-01-01 18:57  0.167517
2013-01-01 18:58  0.922883
2013-01-01 18:59  1.721104

[600 rows x 1 columns]

In [395]: dfp.loc["2013-01-01 10H"]
Out[395]: 
                         A
2013-01-01 10:00 -0.308975
2013-01-01 10:01  0.542520
2013-01-01 10:02  1.061068
2013-01-01 10:03  0.754005
2013-01-01 10:04  0.352933
...                    ...
2013-01-01 10:55 -0.865621
2013-01-01 10:56 -1.167818
2013-01-01 10:57 -2.081748
2013-01-01 10:58 -0.527146
2013-01-01 10:59  0.802298

[60 rows x 1 columns]

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

In [396]: dfp["2013-01-01 10H":"2013-01-01 11H"]
Out[396]: 
                         A
2013-01-01 10:00 -0.308975
2013-01-01 10:01  0.542520
2013-01-01 10:02  1.061068
2013-01-01 10:03  0.754005
2013-01-01 10:04  0.352933
...                    ...
2013-01-01 11:55 -0.590204
2013-01-01 11:56  1.539990
2013-01-01 11:57 -1.224826
2013-01-01 11:58  0.578798
2013-01-01 11:59 -0.685496

[120 rows x 1 columns]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Данные со временем отметки можно преобразовать в данные PeriodIndex с помощью to_period и наоборот с помощью to_timestamp:

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

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

In [413]: ts
Out[413]: 
2012-01-31    1.931253
2012-02-29   -0.184594
2012-03-31    0.249656
2012-04-30   -0.978151
2012-05-31   -0.873389
Freq: M, dtype: float64

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

In [415]: ps
Out[415]: 
2012-01    1.931253
2012-02   -0.184594
2012-03    0.249656
2012-04   -0.978151
2012-05   -0.873389
Freq: M, dtype: float64

In [416]: ps.to_timestamp()
Out[416]: 
2012-01-01    1.931253
2012-02-01   -0.184594
2012-03-01    0.249656
2012-04-01   -0.978151
2012-05-01   -0.873389
Freq: MS, dtype: float64

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

In [417]: ps.to_timestamp("D", how="s")
Out[417]: 
2012-01-01    1.931253
2012-02-01   -0.184594
2012-03-01    0.249656
2012-04-01   -0.978151
2012-05-01   -0.873389
Freq: MS, dtype: float64

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

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

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

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

In [421]: ts.head()
Out[421]: 
1990-03-01 09:00   -0.109291
1990-06-01 09:00   -0.637235
1990-09-01 09:00   -1.735925
1990-12-01 09:00    2.096946
1991-03-01 09:00   -1.039926
Freq: H, dtype: float64
END_OF_DOCUMENT_MARKER

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

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

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

In [423]: span
Out[423]: 
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)

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

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

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

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

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

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

Их легко можно преобразовать в PeriodIndex.

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

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

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

pandas предоставляет широкую поддержку работы со временными метками в разных часовых поясах, используя библиотеки pytz и dateutil или объекты datetime.timezone из стандартной библиотеки.

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

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

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

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

Чтобы локализовать эти даты в часовом поясе (назначить конкретный часовой пояс к дате без указания часового пояса), вы можете использовать метод tz_localize или ключевой аргумент tz в date_range(), Timestamp или DatetimeIndex. Вы можете передать объекты часового пояса pytz или dateutil или строки из базы данных часовых поясов Olson. Строки часовых поясов Olson по умолчанию возвращают объекты часового пояса pytz. Чтобы вернуть объекты часового пояса dateutil, добавьте dateutil/ перед строкой.

  • В pytz вы можете найти список распространённых (и менее распространённых) часовых поясов, используя from pytz import common_timezones, all_timezones.

  • dateutil использует часовые пояса ОС, поэтому нет фиксированного списка. Для распространённых поясов имена совпадают с именами pytz.

In [433]: import dateutil

# pytz
In [434]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D", tz="Europe/London")

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

# dateutil
In [436]: rng_dateutil = pd.date_range("3/6/2012 00:00", periods=3, freq="D")

In [437]: rng_dateutil = rng_dateutil.tz_localize("dateutil/Europe/London")

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

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

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

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

# datetime.timezone
In [441]: rng_utc = pd.date_range(
   .....:     "3/6/2012 00:00",
   .....:     periods=3,
   .....:     freq="D",
   .....:     tz=datetime.timezone.utc,
   .....: )
   .....: 

In [442]: rng_utc.tz
Out[442]: datetime.timezone.utc

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

In [443]: import pytz

# pytz
In [444]: tz_pytz = pytz.timezone("Europe/London")

In [445]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D")

In [446]: rng_pytz = rng_pytz.tz_localize(tz_pytz)

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

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

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

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

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

In [451]: rng_pytz.tz_convert("US/Eastern")
Out[451]: 
DatetimeIndex(['2012-03-05 19:00:00-05:00', '2012-03-06 19:00:00-05:00',
               '2012-03-07 19:00:00-05:00'],
              dtype='datetime64[ns, US/Eastern]', freq=None)

Примечание

При использовании часовых поясов pytz, DatetimeIndex будет создавать другой объект часового пояса, чем Timestamp для одного и того же входного значения часового пояса. DatetimeIndex может содержать коллекцию объектов Timestamp, которые могут иметь разные смещения UTC и не могут быть лаконично представлены одним экземпляром часового пояса pytz, в то время как один Timestamp представляет одну точку во времени со специфичным смещением UTC.

In [452]: dti = pd.date_range("2019-01-01", periods=3, freq="D", tz="US/Pacific")

In [453]: dti.tz
Out[453]: <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>

In [454]: ts = pd.Timestamp("2019-01-01", tz="US/Pacific")

In [455]: ts.tz
Out[455]: <DstTzInfo 'US/Pacific' PST-1 day, 16:00:00 STD>

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

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

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

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

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

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

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

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

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

Если вы используете даты после 2038-01-18, из-за текущих недостатков в базовых библиотеках, вызванных проблемой 2038 года, корректировки летнего времени (DST) для дат, учитывающих часовой пояс, не будут применяться. Когда и если базовые библиотеки будут исправлены, переходы летнего времени будут применяться.

Например, для двух дат, которые находятся в британском летнем времени (и, следовательно, обычно составляют GMT+1), оба следующих утверждения являются истинными:

In [456]: d_2037 = "2037-03-31T010101"

In [457]: d_2038 = "2038-03-31T010101"

In [458]: DST = "Europe/London"

In [459]: assert pd.Timestamp(d_2037, tz=DST) != pd.Timestamp(d_2037, tz="GMT")

In [460]: assert pd.Timestamp(d_2038, tz=DST) == pd.Timestamp(d_2038, tz="GMT")

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

In [461]: rng_eastern = rng_utc.tz_convert("US/Eastern")

In [462]: rng_berlin = rng_utc.tz_convert("Europe/Berlin")

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

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

In [465]: rng_eastern[2] == rng_berlin[2]
Out[465]: True

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

In [466]: ts_utc = pd.Series(range(3), pd.date_range("20130101", periods=3, tz="UTC"))

In [467]: eastern = ts_utc.tz_convert("US/Eastern")

In [468]: berlin = ts_utc.tz_convert("Europe/Berlin")

In [469]: result = eastern + berlin

In [470]: result
Out[470]: 
2013-01-01 00:00:00+00:00    0
2013-01-02 00:00:00+00:00    2
2013-01-03 00:00:00+00:00    4
Freq: D, dtype: int64

In [471]: result.index
Out[471]: 
DatetimeIndex(['2013-01-01 00:00:00+00:00', '2013-01-02 00:00:00+00:00',
               '2013-01-03 00:00:00+00:00'],
              dtype='datetime64[ns, UTC]', freq='D')

Чтобы удалить информацию о часовом поясе, используйте tz_localize(None) или tz_convert(None). tz_localize(None) удалит часовой пояс, получив локальное представление времени. tz_convert(None) удалит часовой пояс после преобразования в UTC время.

In [472]: didx = pd.date_range(start="2014-08-01 09:00", freq="H", periods=3, tz="US/Eastern")

In [473]: didx
Out[473]: 
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'],
              dtype='datetime64[ns, US/Eastern]', freq='H')

In [474]: didx.tz_localize(None)
Out[474]: 
DatetimeIndex(['2014-08-01 09:00:00', '2014-08-01 10:00:00',
               '2014-08-01 11:00:00'],
              dtype='datetime64[ns]', freq=None)

In [475]: didx.tz_convert(None)
Out[475]: 
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
               '2014-08-01 15:00:00'],
              dtype='datetime64[ns]', freq='H')

# tz_convert(None) is identical to tz_convert('UTC').tz_localize(None)
In [476]: didx.tz_convert("UTC").tz_localize(None)
Out[476]: 
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
               '2014-08-01 15:00:00'],
              dtype='datetime64[ns]', freq=None)

Складывание

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

Для неоднозначных моментов времени pandas поддерживает явное указание ключевого аргумента fold. Из-за перехода на летнее время, одно время на часах может возникнуть дважды при переходе от летнего к зимнему времени; fold описывает, соответствует ли datetime-подобный первый (0) или второй раз (1), когда время на часах попадает в неоднозначное время. Fold поддерживается только для построения из наивных datetime.datetime (см. документацию по datetime для получения подробной информации) или из Timestamp или для построения из компонентов (см. ниже). Поддерживаются только часовые пояса dateutil (см. документацию dateutil для методов dateutil которые обрабатывают неоднозначные даты и время) так как часовые пояса pytz не поддерживают fold (см. документацию pytz для получения подробной информации о том, как pytz обрабатывает неоднозначные даты и время). Для локализации неоднозначной даты и времени с pytz, пожалуйста, используйте Timestamp.tz_localize(). В общем случае, мы рекомендуем полагаться на Timestamp.tz_localize() при локализации неоднозначных дат и времени, если вам нужен прямой контроль над тем, как они обрабатываются.

In [477]: pd.Timestamp(
   .....:     datetime.datetime(2019, 10, 27, 1, 30, 0, 0),
   .....:     tz="dateutil/Europe/London",
   .....:     fold=0,
   .....: )
   .....: 
Out[477]: Timestamp('2019-10-27 01:30:00+0100', tz='dateutil//usr/share/zoneinfo/Europe/London')

In [478]: pd.Timestamp(
   .....:     year=2019,
   .....:     month=10,
   .....:     day=27,
   .....:     hour=1,
   .....:     minute=30,
   .....:     tz="dateutil/Europe/London",
   .....:     fold=1,
   .....: )
   .....: 
Out[478]: Timestamp('2019-10-27 01:30:00+0000', tz='dateutil//usr/share/zoneinfo/Europe/London')

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

tz_localize может не суметь определить смещение UTC временной метки, потому что летнее время (DST) в местном часовом поясе приводит к тому, что некоторые моменты времени происходят дважды в течение одного дня («часы отстают»). Доступны следующие варианты:

  • 'raise': Вызывает pytz.AmbiguousTimeError (по умолчанию).

  • 'infer': Попытка определить правильное смещение, основываясь на монотонности временных меток.

  • 'NaT': Заменяет неоднозначные моменты времени на NaT.

  • bool: True представляет время летнего времени (DST), False представляет время вне летнего времени (DST). Поддерживается массив-подобное значение bool для последовательности моментов времени.

In [479]: 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 01: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 [480]: rng_hourly.tz_localize("US/Eastern", ambiguous="infer")
Out[480]: 
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
               '2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
              dtype='datetime64[ns, US/Eastern]', freq=None)

In [481]: rng_hourly.tz_localize("US/Eastern", ambiguous="NaT")
Out[481]: 
DatetimeIndex(['2011-11-06 00:00:00-04:00', 'NaT', 'NaT',
               '2011-11-06 02:00:00-05:00'],
              dtype='datetime64[ns, US/Eastern]', freq=None)

In [482]: rng_hourly.tz_localize("US/Eastern", ambiguous=[True, True, False, False])
Out[482]: 
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
               '2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
              dtype='datetime64[ns, US/Eastern]', freq=None)

Несуществующие времена при локализации

Переход на летнее время может также сместить местное время вперед на 1 час, создавая несуществующие местные времена («часы переводятся вперед»). Поведение локализации временного ряда с несуществующими временами можно контролировать с помощью аргумента nonexistent. Доступны следующие варианты:

  • 'raise': Вызывает pytz.NonExistentTimeError (поведение по умолчанию)

  • 'NaT': Заменяет несуществующие времена на NaT

  • 'shift_forward': Перемещает несуществующие времена вперед к ближайшему реальному времени

  • 'shift_backward': Перемещает несуществующие времена назад к ближайшему реальному времени

  • объект timedelta: Перемещает несуществующие времена на продолжительность timedelta

In [483]: dti = pd.date_range(start="2015-03-29 02:30:00", periods=3, freq="H")

# 2:30 is a nonexistent time

Локализация несуществующих времен по умолчанию вызовет ошибку.

In [2]: dti.tz_localize('Europe/Warsaw')
NonExistentTimeError: 2015-03-29 02:30:00

Преобразуйте несуществующие времена в NaT или сместите эти времена.

In [484]: dti
Out[484]: 
DatetimeIndex(['2015-03-29 02:30:00', '2015-03-29 03:30:00',
               '2015-03-29 04:30:00'],
              dtype='datetime64[ns]', freq='H')

In [485]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_forward")
Out[485]: 
DatetimeIndex(['2015-03-29 03:00:00+02:00', '2015-03-29 03:30:00+02:00',
               '2015-03-29 04:30:00+02:00'],
              dtype='datetime64[ns, Europe/Warsaw]', freq=None)

In [486]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_backward")
Out[486]: 
DatetimeIndex(['2015-03-29 01:59:59.999999999+01:00',
                         '2015-03-29 03:30:00+02:00',
                         '2015-03-29 04:30:00+02:00'],
              dtype='datetime64[ns, Europe/Warsaw]', freq=None)

In [487]: dti.tz_localize("Europe/Warsaw", nonexistent=pd.Timedelta(1, unit="H"))
Out[487]: 
DatetimeIndex(['2015-03-29 03:30:00+02:00', '2015-03-29 03:30:00+02:00',
               '2015-03-29 04:30:00+02:00'],
              dtype='datetime64[ns, Europe/Warsaw]', freq=None)

In [488]: dti.tz_localize("Europe/Warsaw", nonexistent="NaT")
Out[488]: 
DatetimeIndex(['NaT', '2015-03-29 03:30:00+02:00',
               '2015-03-29 04:30:00+02:00'],
              dtype='datetime64[ns, Europe/Warsaw]', freq=None)

Операции с временными зонами Series

A Series со значениями naive временной зоны представлена с типом datetime64[ns].

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

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

A Series со значениями aware временной зоны представлена с типом datetime64[ns, tz], где tz — временная зона

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

In [492]: s_aware
Out[492]: 
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, см. раздел аксессоров dt.

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

In [493]: s_naive.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")
Out[493]: 
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. Этот метод может преобразовывать между различными типами данных с временными зонами aware.

# convert to a new time zone
In [494]: s_aware.astype("datetime64[ns, CET]")
Out[494]: 
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]

Примечание

Использование Series.to_numpy() на Series, возвращает массив NumPy данных. NumPy в настоящее время не поддерживает временные зоны (хотя он и выводит в локальной временной зоне!), поэтому для данных с временной зоной aware возвращается массив объектов Timestamp:

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

In [496]: s_aware.to_numpy()
Out[496]: 
array([Timestamp('2013-01-01 00:00:00-0500', tz='US/Eastern'),
       Timestamp('2013-01-02 00:00:00-0500', tz='US/Eastern'),
       Timestamp('2013-01-03 00:00:00-0500', tz='US/Eastern')],
      dtype=object)

Преобразованием в массив объектов Timestamp сохраняется информация о временной зоне. Например, при преобразовании обратно в Series:

In [497]: pd.Series(s_aware.to_numpy())
Out[497]: 
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]

Однако, если вам нужен фактический массив NumPy datetime64[ns] (со значениями, преобразованными в UTC), а не массив объектов, вы можете указать аргумент dtype:

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

© 2008–2022, 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/1.5.0/user_guide/timeseries.html

Spec-Zone.ru

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