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

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 пакет.

Концепция

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

Класс Array

Тип данных 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 data.

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         
1    <2 * DateOffsets>
dtype: object

In [23]: pd.Series(pd.date_range("1/1/2011", freq="ME", 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

Временные метки vs. временные промежутки#

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

In [28]: import datetime

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

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

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

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

Например:

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

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

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

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

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

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

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

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

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

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

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

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

In [43]: ts
Out[43]: 
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 [44]: pd.to_datetime(pd.Series(["Jul 31, 2009", "Jan 10, 2010", None]))
Out[44]: 
0   2009-07-31
1   2010-01-10
2          NaT
dtype: datetime64[ns]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Сборка даты и времени из нескольких столбцов DataFrame#

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

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

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

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

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

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

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

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

Некорректные данные#

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

In [57]: pd.to_datetime(['2009/07/31', 'asd'], errors='raise')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[57], line 1
----> 1 pd.to_datetime(['2009/07/31', 'asd'], errors='raise')

File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:1104, in to_datetime(arg, errors, dayfirst, yearfirst, utc, format, exact, unit, infer_datetime_format, origin, cache)
   1102         result = _convert_and_box_cache(argc, cache_array)
   1103     else:
-> 1104         result = convert_listlike(argc, format)
   1105 else:
   1106     result = convert_listlike(np.array([arg]), format)[0]

File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:435, in _convert_listlike_datetimes(arg, format, name, utc, unit, errors, dayfirst, yearfirst, exact)
    433 # `format` could be inferred, or user didn't ask for mixed-format parsing.
    434 if format is not None and format != "mixed":
--> 435     return _array_strptime_with_fallback(arg, name, utc, format, exact, errors)
    437 result, tz_parsed = objects_to_datetime64(
    438     arg,
    439     dayfirst=dayfirst,
   (...)
    443     allow_object=True,
    444 )
    446 if tz_parsed is not None:
    447     # We can take a shortcut since the datetime64 numpy array
    448     # is in UTC

File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:469, in _array_strptime_with_fallback(arg, name, utc, fmt, exact, errors)
    458 def _array_strptime_with_fallback(
    459     arg,
    460     name,
   (...)
    464     errors: str,
    465 ) -> Index:
    466     """
    467     Call array_strptime, with fallback behavior depending on 'errors'.
    468     """
--> 469     result, tz_out = array_strptime(arg, fmt, exact=exact, errors=errors, utc=utc)
    470     if tz_out is not None:
    471         unit = np.datetime_data(result.dtype)[0]

File ~/work/pandas/pandas/pandas/_libs/tslibs/strptime.pyx:501, in pandas._libs.tslibs.strptime.array_strptime()

File ~/work/pandas/pandas/pandas/_libs/tslibs/strptime.pyx:451, in pandas._libs.tslibs.strptime.array_strptime()

File ~/work/pandas/pandas/pandas/_libs/tslibs/strptime.pyx:583, in pandas._libs.tslibs.strptime._parse_with_format()

ValueError: time data "asd" doesn't match format "%Y/%m/%d", at position 1. You might want to try:
    - passing `format` if your strings have a consistent format;
    - passing `format='ISO8601'` if your strings are all ISO8601 but not necessarily in exactly the same format;
    - passing `format='mixed'`, and the format will be inferred for each element individually. You might want to use `dayfirst` alongside this.

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

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

Метки времени эпохи#

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

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

Примечание

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

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

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

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

Примечание

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

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

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

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

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

От временных меток к эпохе#

Чтобы выполнить обратную операцию к предыдущей, а именно преобразовать из Timestamp в 'unix' эпоху:

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

In [66]: stamps
Out[66]: 
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 [67]: (stamps - pd.Timestamp("1970-01-01")) // pd.Timedelta("1s")
Out[67]: Index([1349720105, 1349806505, 1349892905, 1349979305], dtype='int64')

Используя origin параметр#

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

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

Значение по умолчанию установлено на origin='unix', который по умолчанию равен 1970-01-01 00:00:00. Часто называют 'эпохой Unix' или временем POSIX.

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

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

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

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

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

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

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

In [74]: index
Out[74]: 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 [75]: start = datetime.datetime(2011, 1, 1)

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

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

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

In [80]: index
Out[80]: 
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 [81]: pd.date_range(start, periods=1000, freq="ME")
Out[81]: 
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='ME')

In [82]: pd.bdate_range(start, periods=250, freq="BQS")
Out[82]: 
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 [83]: pd.date_range(start, end, freq="BME")
Out[83]: 
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='BME')

In [84]: pd.date_range(start, end, freq="W")
Out[84]: 
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 [85]: pd.bdate_range(end=end, periods=20)
Out[85]: 
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 [86]: pd.bdate_range(start=start, periods=20)
Out[86]: 
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 to end включительно, с periods количество элементов в результирующем DatetimeIndex:

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

In [88]: pd.date_range("2018-01-01", "2018-01-05", periods=10)
Out[88]: 
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 [89]: weekmask = "Mon Wed Fri"

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

In [91]: pd.bdate_range(start, end, freq="C", weekmask=weekmask, holidays=holidays)
Out[91]: 
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 [92]: pd.bdate_range(start, end, freq="CBMS", weekmask=weekmask)
Out[92]: 
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')

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

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

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

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

При выборе секундного разрешения доступный диапазон увеличивается до +/- 2.9e11 years. Разные разрешения могут быть преобразованы друг в друга через as_unit.

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

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

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

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

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

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

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

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

Смотрите также

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

Примечание

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

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

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

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

In [97]: ts.index
Out[97]: 
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='BME')

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

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

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

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

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

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

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

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

In [103]: ts["2011"]
Out[103]: 
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: BME, dtype: float64

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

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

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

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

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

In [106]: dft
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.loc["2013"]
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-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 [108]: dft["2013-1":"2013-2"]
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"]
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-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 [110]: dft["2013-1":"2013-2-28 00:00:00"]
Out[110]: 
                            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 [111]: dft["2013-1-15":"2013-1-15 12:30:00"]
Out[111]: 
                            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 [112]: 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 [113]: dft2
Out[113]: 
                              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 [114]: dft2.loc["2013-01-05"]
Out[114]: 
                              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 [115]: idx = pd.IndexSlice

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

In [117]: dft2.loc[idx[:, "2013-01-05"], :]
Out[117]: 
                              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

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

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

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

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

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

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

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

In [121]: 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 [122]: series_minute.index.resolution
Out[122]: 'minute'

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

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

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

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

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

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

In [126]: 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 [127]: series_second.index.resolution
Out[127]: 'second'

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

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

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

In [130]: dft_minute.loc["2011-12-31 23"]
Out[130]: 
                     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 [131]: dft_minute.loc["2011-12-31 23:59"]
Out[131]: 
a    1
b    4
Name: 2011-12-31 23:59:00, dtype: int64

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

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

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

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

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

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

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

In [135]: dft[datetime.datetime(2013, 1, 1): datetime.datetime(2013, 2, 28)]
Out[135]: 
                            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 [136]: dft[
   .....:     datetime.datetime(2013, 1, 1, 10, 12, 0): datetime.datetime(
   .....:         2013, 2, 28, 10, 12, 0
   .....:     )
   .....: ]
   .....: 
Out[136]: 
                            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]

Усечение и расширенная индексация#

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

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

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

In [139]: ts2.truncate(before="2011-11", after="2011-12")
Out[139]: 
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 [140]: ts2["2011-11":"2011-12"]
Out[140]: 
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 [141]: ts2.iloc[[0, 2, 6]].index
Out[141]: DatetimeIndex(['2011-01-02', '2011-01-16', '2011-02-13'], dtype='datetime64[ns]', freq=None)

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

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

Свойство

Описание

год

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

month

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

day

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

hour

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

минута

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

второй

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

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

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

наносекунда

Наносекунды datetime

дата

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

время

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

timetz

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

день года

Порядковый день года

день_года

Порядковый день года

weekofyear

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

неделя

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

dayofweek

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

day_of_week

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

день недели

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

квартал

Квартал даты: Янв-Мар = 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 со значениями типа datetime, тогда вы можете получить доступ к этим свойствам через .dt аксессор, как подробно описано в разделе о .dt аксессоры.

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

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

In [143]: idx.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

In [144]: idx.to_series().dt.isocalendar()
Out[144]: 
            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') использовались для указания частоты, которая определяла:

Эти строки частоты соответствуют 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 [145]: ts = pd.Timestamp("2016-10-30 00:00:00", tz="Europe/Helsinki")

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

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

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

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

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

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

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

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

Смещение даты

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

Описание

DateOffset

None

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

BDay или BusinessDay

'B'

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

CDay или CustomBusinessDay

'C'

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

Week

'W'

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

WeekOfMonth

'WOM'

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

LastWeekOfMonth

'LWOM'

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

MonthEnd

'ME'

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

MonthBegin

'MS'

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

BMonthEnd или BusinessMonthEnd

'BME'

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

BMonthBegin или BusinessMonthBegin

'BMS'

начало бизнес-месяца

CBMonthEnd или CustomBusinessMonthEnd

'CBME'

пользовательский конец бизнес-месяца

CBMonthBegin или CustomBusinessMonthBegin

'CBMS'

custom business month begin

SemiMonthEnd

'SME'

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

SemiMonthBegin

'SMS'

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

QuarterEnd

'QE'

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

QuarterBegin

'QS'

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

BQuarterEnd

'BQE

конец бизнес-квартала

BQuarterBegin

'BQS'

начало бизнес-квартала

FY5253Quarter

'REQ'

розничный (также известный как 52-53 недели) квартал

YearEnd

'YE'

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

YearBegin

'YS' или 'BYS'

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

BYearEnd

'BYE'

конец бизнес-года

BYearBegin

'BYS'

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

FY5253

'RE'

розничный (также 52-53 недели) год

Easter

None

Пасхальный праздник

BusinessHour

'bh'

рабочий час

CustomBusinessHour

'cbh'

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

Day

'D'

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

Hour

'h'

один час

Minute

'min'

одна минута

Second

's'

одна секунда

Milli

'ms'

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

Micro

'us'

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

Nano

'ns'

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

In [182]: s - pd.DateOffset(months=2)
Out[182]: 
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 [183]: s - pd.offsets.Day(2)
Out[183]: 
0   2011-12-30
1   2011-12-31
2   2012-01-01
dtype: datetime64[ns]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

In [194]: pd.Series(dts.weekday, dts).map(pd.Series("Mon Tue Wed Thu Fri Sat Sun".split()))
Out[194]: 
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 [195]: from pandas.tseries.holiday import USFederalHolidayCalendar

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

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

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

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

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

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

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

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

Рабочий час#

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

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

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

In [204]: bh
Out[204]: 

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

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

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

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

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

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

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

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

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

In [213]: bh
Out[213]: 

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

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

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

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

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

In [218]: bh
Out[218]: 

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

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

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

In [224]: pd.offsets.BusinessHour().rollforward(pd.Timestamp("2014-08-02 15:00"))
Out[224]: 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 [225]: pd.offsets.BusinessHour() + pd.Timestamp("2014-08-02 15:00")
Out[225]: Timestamp('2014-08-04 10:00:00')

# BusinessDay results (for reference)
In [226]: pd.offsets.BusinessHour().rollforward(pd.Timestamp("2014-08-02"))
Out[226]: 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 [227]: pd.offsets.BusinessHour() + pd.Timestamp("2014-08-02")
Out[227]: Timestamp('2014-08-04 10:00:00')

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

Пользовательский рабочий час#

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

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

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

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

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

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

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

In [233]: 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 [234]: dt + bhour_mon * 2
Out[234]: Timestamp('2014-01-21 10:00:00')

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

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

Псевдоним

Описание

B

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

C

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

D

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

W

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

ME

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

SME

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

BME

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

CBME

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

MS

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

SMS

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

BMS

частота начала бизнес-месяца

CBMS

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

QE

квартальная конечная частота

BQE

частота окончания бизнес-квартала

QS

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

BQS

частота начала бизнес-квартала

YE

годовая частота окончания

ПОКА

частота окончания бизнес-года

YS

годовая начальная частота

BYS

частота начала бизнес-года

h

ежечасная частота

bh

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

cbh

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

min

ежеминутная частота

s

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

мс

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

нас

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

нс

наносекунды

Устарело с версии 2.2.0: Псевдонимы H, BH, CBH, T, S, L, U, и N устарели в пользу псевдонимов h, bh, cbh, min, s, ms, us, и ns.

Примечание

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

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

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

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

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

In [238]: dates_lst_2
Out[238]: 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)

Псевдонимы периодов#

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

Псевдоним

Описание

B

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

D

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

W

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

M

ежемесячная частота

Q

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

Y

годовая частота

h

ежечасная частота

min

ежеминутная частота

s

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

мс

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

нас

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

нс

наносекунды

Устарело с версии 2.2.0: Псевдонимы A, H, T, S, L, U, и N устарели в пользу псевдонимов Y, h, min, s, ms, us, и ns.

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

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

In [239]: pd.date_range(start, periods=5, freq="B")
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=5, freq=pd.offsets.BDay())
Out[240]: 
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
               '2011-01-07'],
              dtype='datetime64[ns]', freq='B')

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

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

In [242]: pd.date_range(start, periods=10, freq="1D10us")
Out[242]: 
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='86400000010us')

Закреплённые смещения#

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

Псевдоним

Описание

W-SUN

еженедельная частота (воскресенья). То же, что и 'W'

W-MON

еженедельная частота (понедельники)

W-TUE

еженедельная частота (по вторникам)

W-WED

еженедельная частота (по средам)

W-THU

еженедельная частота (по четвергам)

W-FRI

еженедельная частота (пятницы)

W-SAT

еженедельная частота (субботы)

(B)Q(E)(S)-DEC

квартальная частота, год заканчивается в декабре. То же, что и ‘QE’

(B)Q(E)(S)-JAN

квартальная частота, год заканчивается в январе

(B)Q(E)(S)-FEB

квартальная частота, год заканчивается в феврале

(B)Q(E)(S)-MAR

квартальная частота, год заканчивается в марте

(B)Q(E)(S)-APR

квартальная частота, год заканчивается в апреле

(B)Q(E)(S)-MAY

квартальная частота, год заканчивается в мае

(B)Q(E)(S)-JUN

квартальная частота, год заканчивается в июне

(B)Q(E)(S)-JUL

квартальная частота, год заканчивается в июле

(B)Q(E)(S)-AUG

квартальная частота, год заканчивается в августе

(B)Q(E)(S)-SEP

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

(B)Q(E)(S)-OCT

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

(B)Q(E)(S)-NOV

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

(B)Y(E)(S)-DEC

годовая частота, привязанная к концу декабря. То же, что и 'YE'

(B)Y(E)(S)-JAN

годовая частота, привязанная к концу января

(B)Y(E)(S)-FEB

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

(B)Y(E)(S)-MAR

годовая частота, привязанная к концу марта

(B)Y(E)(S)-APR

годовая частота, закреплённая в конце апреля

(B)Y(E)(S)-MAY

годовая частота, привязанная к концу мая

(B)Y(E)(S)-JUN

годовая частота, закрепленная в конце июня

(B)Y(E)(S)-JUL

годовая частота, закреплённая в конце июля

(B)Y(E)(S)-AUG

годовая частота, закрепленная в конце августа

(B)Y(E)(S)-SEP

годовая частота, привязанная к концу сентября

(B)Y(E)(S)-OCT

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

(B)Y(E)(S)-NOV

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

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

Семантика закреплённого смещения#

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Правило

Описание

nearest_workday

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

sunday_to_monday

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

next_monday_or_tuesday

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

предыдущая_пятница

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

next_monday

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

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

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

In [260]: 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 [261]: cal = ExampleCalendar()

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

weekday=MO(2) такой же, как 2 * Week(weekday=2)

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

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

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

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

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

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

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

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

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

Эти даты могут быть перезаписаны путём установки атрибутов как datetime/Timestamp/string.

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

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

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

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

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

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

In [276]: cal.rules
Out[276]: 
[Holiday: Memorial Day (month=5, day=31, offset=),
 Holiday: July 4th (month=7, day=4, observance=),
 Holiday: Columbus Day (month=10, day=1, offset=)]

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

In [278]: new_cal.rules
Out[278]: 
[Holiday: Labor Day (month=9, day=1, offset=),
 Holiday: Memorial Day (month=5, day=31, offset=),
 Holiday: July 4th (month=7, day=4, observance=),
 Holiday: Columbus Day (month=10, day=1, offset=)]

Ресемплинг#

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

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

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

Основы#

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

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

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

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

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

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

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

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

Для понижающей дискретизации, closed может быть установлено в 'left' или 'right' для указания, какой конец интервала является закрытым:

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

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

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

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

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

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

Значения по умолчанию для label и closed является 'left' для всех частотных смещений, кроме 'ME', 'YE', 'QE', 'BME', 'BYE', 'BQE' и 'W', которые по умолчанию используют 'right'.

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

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

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

In [302]: s.dt.day_name()
Out[302]: 
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 [303]: s.resample("B").last().dt.day_name()
Out[303]: 
1999-12-31       Sunday
2000-01-03          NaN
2000-01-04      Tuesday
2000-01-05    Wednesday
Freq: B, dtype: object

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

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

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

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

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

Повышение частоты дискретизации#

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

# from secondly to every 250 milliseconds
In [305]: ts[:2].resample("250ms").asfreq()
Out[305]: 
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: 250ms, dtype: float64

In [306]: ts[:2].resample("250ms").ffill()
Out[306]: 
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: 250ms, dtype: int64

In [307]: ts[:2].resample("250ms").ffill(limit=2)
Out[307]: 
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: 250ms, dtype: float64

Разреженное передискретизирование#

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

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

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

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

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

In [310]: ts.resample("3min").sum()
Out[310]: 
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: 3min, Length: 47521, dtype: int64

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

In [311]: from functools import partial

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

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

In [314]: ts.groupby(partial(round, freq="3min")).sum()
Out[314]: 
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

Агрегация#

The resample() метод возвращает pandas.api.typing.Resampler экземпляр. Аналогично API агрегации, API groupby, и window API, a Resampler может быть выборочно передискретизирован.

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

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

In [316]: r = df.resample("3min")

In [317]: r.mean()
Out[317]: 
                            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

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

In [318]: r["A"].mean()
Out[318]: 
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: 3min, Name: A, dtype: float64

In [319]: r[["A", "B"]].mean()
Out[319]: 
                            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 [320]: r["A"].agg(["sum", "mean", "std"])
Out[320]: 
                           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 [321]: r.agg(["sum", "mean"])
Out[321]: 
                             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 [322]: r.agg({"A": "sum", "B": lambda x: np.std(x, ddof=1)})
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", "B": "std"})
Out[323]: 
                             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 [324]: r.agg({"A": ["sum", "std"], "B": ["mean", "std"]})
Out[324]: 
                             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 не имеет индекса типа datetime, но вместо этого вы хотите пересэмплировать на основе столбца типа datetime во фрейме, его можно передать в on ключевое слово.

In [325]: 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 [326]: df
Out[326]: 
                   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 [327]: df.resample("ME", on="date")[["a"]].sum()
Out[327]: 
            a
date         
2015-01-31  6
2015-02-28  4

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

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

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

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

In [329]: 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 [330]: resampled = small.resample("h")

In [331]: 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 для настройки начала бинов#

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

Например:

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

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

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

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

In [336]: ts
Out[336]: 
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: 7min, dtype: int64

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

In [337]: ts.resample("17min", origin="start_day").sum()
Out[337]: 
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: 17min, dtype: int64

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

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

In [339]: ts.resample("17min", origin="epoch").sum()
Out[339]: 
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: 17min, dtype: int64

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

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

In [341]: ts.resample("17min", origin="2001-01-01").sum()
Out[341]: 
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: 17min, dtype: int64

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

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

In [343]: ts.resample("17min", origin="start").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: 17min, dtype: int64

In [344]: ts.resample("17min", offset="23h30min").sum()
Out[344]: 
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: 17min, dtype: int64

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

Обратная передискретизация#

Добавлено в версии 1.3.0.

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

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

In [345]: ts.resample('17min', origin='end').sum()
Out[345]: 
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: 17min, dtype: int64

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

In [346]: ts.resample('17min', origin='end_day').sum()
Out[346]: 
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: 17min, dtype: int64

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

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

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

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

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

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

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

Period#

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

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

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

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

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

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

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

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

In [357]: p - 3
Out[357]: Period('2009', 'Y-DEC')

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

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

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

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

Если Period частота ежедневная или выше (D, h, min, s, ms, us, и ns), offsets и timedelta-подобное может быть добавлено, если результат может иметь ту же частоту. В противном случае, ValueError будет вызвано исключение.

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

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

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

In [365]: p + np.timedelta64(7200, "s")
Out[365]: Period('2014-07-01 11:00', 'h')
In [366]: p + pd.offsets.Minute(5)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/work/pandas/pandas/pandas/_libs/tslibs/period.pyx:1824, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()

File ~/work/pandas/pandas/pandas/_libs/tslibs/timedeltas.pyx:278, in pandas._libs.tslibs.timedeltas.delta_to_nanoseconds()

File ~/work/pandas/pandas/pandas/_libs/tslibs/np_datetime.pyx:661, in pandas._libs.tslibs.np_datetime.convert_reso()

ValueError: Cannot losslessly convert units

The above exception was the direct cause of the following exception:

IncompatibleFrequency                     Traceback (most recent call last)
Cell In[366], line 1
----> 1 p + pd.offsets.Minute(5)

File ~/work/pandas/pandas/pandas/_libs/tslibs/period.pyx:1845, in pandas._libs.tslibs.period._Period.__add__()

File ~/work/pandas/pandas/pandas/_libs/tslibs/period.pyx:1826, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()

IncompatibleFrequency: Input cannot be converted to Period(freq=h)

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

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

In [368]: p + pd.offsets.MonthEnd(3)
Out[368]: Period('2014-10', 'M')
In [369]: p + pd.offsets.MonthBegin(3)
---------------------------------------------------------------------------
IncompatibleFrequency                     Traceback (most recent call last)
Cell In[369], line 1
----> 1 p + pd.offsets.MonthBegin(3)

File ~/work/pandas/pandas/pandas/_libs/tslibs/period.pyx:1847, in pandas._libs.tslibs.period._Period.__add__()

File ~/work/pandas/pandas/pandas/_libs/tslibs/period.pyx:1837, in pandas._libs.tslibs.period._Period._add_offset()

File ~/work/pandas/pandas/pandas/_libs/tslibs/period.pyx:1732, in pandas._libs.tslibs.period.PeriodMixin._require_matching_freq()

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

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

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

PeriodIndex и period_range#

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

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

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

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

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

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

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

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

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

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

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

In [377]: ps
Out[377]: 
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 [378]: idx = pd.period_range("2014-07-01 09:00", periods=5, freq="h")

In [379]: idx
Out[379]: 
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 [380]: idx + pd.offsets.Hour(2)
Out[380]: 
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 [381]: idx = pd.period_range("2014-07", periods=5, freq="M")

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

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

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

Period dtypes#

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

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

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

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

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

The period dtype может использоваться в .astype(...)Это позволяет изменить freq из PeriodIndex как .asfreq() и преобразовать DatetimeIndex to PeriodIndex как to_period():

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

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

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

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

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

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

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

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

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

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

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

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

In [395]: ps["2011"]
Out[395]: 
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 [396]: dfp = pd.DataFrame(
   .....:     np.random.randn(600, 1),
   .....:     columns=["A"],
   .....:     index=pd.period_range("2013-01-01 9:00", periods=600, freq="min"),
   .....: )
   .....: 

In [397]: dfp
Out[397]: 
                         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 [398]: dfp.loc["2013-01-01 10h"]
Out[398]: 
                         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 [399]: dfp["2013-01-01 10h":"2013-01-01 11h"]
Out[399]: 
                         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 [400]: p = pd.Period("2011", freq="Y-DEC")

In [401]: p
Out[401]: Period('2011', 'Y-DEC')

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

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

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

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

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

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

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

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

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

Обратите внимание, что поскольку мы преобразовали в годовую частоту, заканчивающую год в ноябре, месячный период декабря 2011 года фактически находится в периоде 2012 Y-NOV.

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

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

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

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

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

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

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

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

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

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

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

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

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

In [416]: ts
Out[416]: 
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: ME, dtype: float64

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

In [418]: ps
Out[418]: 
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 [419]: ps.to_timestamp()
Out[419]: 
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 [420]: ps.to_timestamp("D", how="s")
Out[420]: 
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 утра конца месяца, следующего за окончанием квартала:

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

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

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

In [424]: ts.head()
Out[424]: 
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

Представление выходящих за пределы диапазонов#

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

In [435]: rng.tz is None
Out[435]: 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 [436]: import dateutil

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

In [438]: rng_pytz.tz
Out[438]: 

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

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

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

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

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

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

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

In [446]: import pytz

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

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

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

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

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

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

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

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

In [454]: rng_pytz.tz_convert("US/Eastern")
Out[454]: 
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 для одного и того же часового пояса. A DatetimeIndex может содержать коллекцию Timestamp объекты, которые могут иметь разные смещения UTC и не могут быть кратко представлены одним pytz экземпляр часового пояса, в то время как один Timestamp представляет один момент времени с определённым смещением UTC.

In [455]: dti = pd.date_range("2019-01-01", periods=3, freq="D", tz="US/Pacific")

In [456]: dti.tz
Out[456]: 

In [457]: ts = pd.Timestamp("2019-01-01", tz="US/Pacific")

In [458]: ts.tz
Out[458]: 

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

Будьте осторожны с преобразованиями между библиотеками. Для некоторых часовых поясов, 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) для дат с учётом часового пояса не будут применяться. Если и когда базовые библиотеки будут исправлены, переходы DST будут применены.

Например, для двух дат, которые находятся в британском летнем времени (и поэтому обычно GMT+1), оба следующих утверждения оцениваются как истинные:

In [459]: d_2037 = "2037-03-31T010101"

In [460]: d_2038 = "2038-03-31T010101"

In [461]: DST = "Europe/London"

In [462]: assert pd.Timestamp(d_2037, tz=DST) != pd.Timestamp(d_2037, tz="GMT")

In [463]: assert pd.Timestamp(d_2038, tz=DST) == pd.Timestamp(d_2038, tz="GMT")

Под капотом все временные метки хранятся в UTC. Значения из временной зоны с учетом DatetimeIndex или Timestamp будут иметь свои поля (день, час, минута и т.д.) локализованными по часовому поясу. Однако временные метки с одинаковым значением UTC всё равно считаются равными, даже если они в разных часовых поясах:

In [464]: rng_eastern = rng_utc.tz_convert("US/Eastern")

In [465]: rng_berlin = rng_utc.tz_convert("Europe/Berlin")

In [466]: rng_eastern[2]
Out[466]: Timestamp('2012-03-07 19:00:00-0500', tz='US/Eastern')

In [467]: rng_berlin[2]
Out[467]: Timestamp('2012-03-08 01:00:00+0100', tz='Europe/Berlin')

In [468]: rng_eastern[2] == rng_berlin[2]
Out[468]: True

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

In [469]: ts_utc = pd.Series(range(3), pd.date_range("20130101", periods=3, tz="UTC"))

In [470]: eastern = ts_utc.tz_convert("US/Eastern")

In [471]: berlin = ts_utc.tz_convert("Europe/Berlin")

In [472]: result = eastern + berlin

In [473]: result
Out[473]: 
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 [474]: result.index
Out[474]: 
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 [475]: didx = pd.date_range(start="2014-08-01 09:00", freq="h", periods=3, tz="US/Eastern")

In [476]: didx
Out[476]: 
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 [477]: didx.tz_localize(None)
Out[477]: 
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 [478]: didx.tz_convert(None)
Out[478]: 
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 [479]: didx.tz_convert("UTC").tz_localize(None)
Out[479]: 
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
               '2014-08-01 15:00:00'],
              dtype='datetime64[ns]', freq=None)

Свернуть#

Для неоднозначных времен pandas поддерживает явное указание аргумента fold (только ключевое слово). Из-за перехода на летнее время одно настенное время может происходить дважды при переходе с летнего на зимнее время; fold описывает, соответствует ли datetime-like первому (0) или второму разу (1), когда настенные часы достигают неоднозначного времени. Fold поддерживается только для создания из наивных datetime.datetime (см. документация по datetime для подробностей) или из Timestamp или для построения из компонентов (см. ниже). Только dateutil поддерживаются часовые пояса (см. документация dateutil для dateutil методы, которые работают с неоднозначными датами-временем) как pytz часовые пояса не поддерживают fold (см. документация pytz подробности о том, как pytz работает с неоднозначными датами и временем). Для локализации неоднозначной даты и времени с pytz, пожалуйста, используйте Timestamp.tz_localize(). В целом, мы рекомендуем полагаться на Timestamp.tz_localize() при локализации неоднозначных дат и времени, если вам нужен прямой контроль над тем, как они обрабатываются.

In [480]: pd.Timestamp(
   .....:     datetime.datetime(2019, 10, 27, 1, 30, 0, 0),
   .....:     tz="dateutil/Europe/London",
   .....:     fold=0,
   .....: )
   .....: 
Out[480]: Timestamp('2019-10-27 01:30:00+0100', tz='dateutil//usr/share/zoneinfo/Europe/London')

In [481]: pd.Timestamp(
   .....:     year=2019,
   .....:     month=10,
   .....:     day=27,
   .....:     hour=1,
   .....:     minute=30,
   .....:     tz="dateutil/Europe/London",
   .....:     fold=1,
   .....: )
   .....: 
Out[481]: 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 представляет время перехода на летнее время, False представляет время без перехода на летнее время. Массивоподобный bool поддерживается для последовательности временных значений.

In [482]: 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 [483]: rng_hourly.tz_localize('US/Eastern')
---------------------------------------------------------------------------
AmbiguousTimeError                        Traceback (most recent call last)
Cell In[483], line 1
----> 1 rng_hourly.tz_localize('US/Eastern')

File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
    286 @doc(DatetimeArray.tz_localize)
    287 def tz_localize(
    288     self,
   (...)
    291     nonexistent: TimeNonexistent = "raise",
    292 ) -> Self:
--> 293     arr = self._data.tz_localize(tz, ambiguous, nonexistent)
    294     return type(self)._simple_new(arr, name=self.name)

File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:81, in ravel_compat..method(self, *args, **kwargs)
     78 @wraps(meth)
     79 def method(self, *args, **kwargs):
     80     if self.ndim == 1:
---> 81         return meth(self, *args, **kwargs)
     83     flags = self._ndarray.flags
     84     flat = self.ravel("K")

File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1090, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
   1087     tz = timezones.maybe_get_tz(tz)
   1088     # Convert to UTC
-> 1090     new_dates = tzconversion.tz_localize_to_utc(
   1091         self.asi8,
   1092         tz,
   1093         ambiguous=ambiguous,
   1094         nonexistent=nonexistent,
   1095         creso=self._creso,
   1096     )
   1097 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
   1098 dtype = tz_to_dtype(tz, unit=self.unit)

File ~/work/pandas/pandas/pandas/_libs/tslibs/tzconversion.pyx:371, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()

AmbiguousTimeError: Cannot infer dst time from 2011-11-06 01:00:00, try using the 'ambiguous' argument

Обработайте эти неоднозначные времена, указав следующее.

In [484]: rng_hourly.tz_localize("US/Eastern", ambiguous="infer")
Out[484]: 
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 [485]: rng_hourly.tz_localize("US/Eastern", ambiguous="NaT")
Out[485]: 
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 [486]: rng_hourly.tz_localize("US/Eastern", ambiguous=[True, True, False, False])
Out[486]: 
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 [487]: dti = pd.date_range(start="2015-03-29 02:30:00", periods=3, freq="h")

# 2:30 is a nonexistent time

Локализация несуществующих времён по умолчанию вызывает ошибку.

In [488]: dti.tz_localize('Europe/Warsaw')
---------------------------------------------------------------------------
NonExistentTimeError                      Traceback (most recent call last)
Cell In[488], line 1
----> 1 dti.tz_localize('Europe/Warsaw')

File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
    286 @doc(DatetimeArray.tz_localize)
    287 def tz_localize(
    288     self,
   (...)
    291     nonexistent: TimeNonexistent = "raise",
    292 ) -> Self:
--> 293     arr = self._data.tz_localize(tz, ambiguous, nonexistent)
    294     return type(self)._simple_new(arr, name=self.name)

File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:81, in ravel_compat..method(self, *args, **kwargs)
     78 @wraps(meth)
     79 def method(self, *args, **kwargs):
     80     if self.ndim == 1:
---> 81         return meth(self, *args, **kwargs)
     83     flags = self._ndarray.flags
     84     flat = self.ravel("K")

File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1090, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
   1087     tz = timezones.maybe_get_tz(tz)
   1088     # Convert to UTC
-> 1090     new_dates = tzconversion.tz_localize_to_utc(
   1091         self.asi8,
   1092         tz,
   1093         ambiguous=ambiguous,
   1094         nonexistent=nonexistent,
   1095         creso=self._creso,
   1096     )
   1097 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
   1098 dtype = tz_to_dtype(tz, unit=self.unit)

File ~/work/pandas/pandas/pandas/_libs/tslibs/tzconversion.pyx:431, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()

NonExistentTimeError: 2015-03-29 02:30:00

Преобразовать несуществующее время в NaT или сдвинуть времена.

In [489]: dti
Out[489]: 
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 [490]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_forward")
Out[490]: 
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 [491]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_backward")
Out[491]: 
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 [492]: dti.tz_localize("Europe/Warsaw", nonexistent=pd.Timedelta(1, unit="h"))
Out[492]: 
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 [493]: dti.tz_localize("Europe/Warsaw", nonexistent="NaT")
Out[493]: 
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 с часовым поясом наивный значения представлены с dtype datetime64[ns].

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

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

A Series с часовым поясом осведомленный значения представлены с dtype datetime64[ns, tz] где tz это часовой пояс

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

In [497]: s_aware
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]

Оба этих Series информация о часовом поясе может быть изменена через .dt аксессор, см. раздел доступа dt.

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

In [498]: s_naive.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")
Out[498]: 
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 метод. Этот метод может преобразовывать между различными типами данных с учетом часовых поясов.

# convert to a new time zone
In [499]: s_aware.astype("datetime64[ns, CET]")
Out[499]: 
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 в настоящее время не поддерживает часовые пояса (хотя это печать в локальном часовом поясе!), поэтому возвращается массив объектов Timestamps для данных с учетом часового пояса:

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

In [501]: s_aware.to_numpy()
Out[501]: 
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)

При преобразовании в массив объектов Timestamps сохраняется информация о часовом поясе. Например, при обратном преобразовании в Series:

In [502]: pd.Series(s_aware.to_numpy())
Out[502]: 
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 [503]: s_aware.to_numpy(dtype="datetime64[ns]")
Out[503]: 
array(['2013-01-01T05:00:00.000000000', '2013-01-02T05:00:00.000000000',
       '2013-01-03T05:00:00.000000000'], dtype='datetime64[ns]')