Сообщить об ошибке.

Объекты Period и PeriodIndex модуля pandas

Представление данных при помощи временных периодов pandas

Во многих случаях более естественно связывать какие-то данные, с временным интервалом (промежутком времени). Для обычных промежутков времени pandas использует объекты Period и PeriodIndex для последовательностей промежутков времени (интервалов). В будущих выпусках > 2.2 появится улучшенная поддержка нерегулярных интервалов с произвольными начальной и конечной точками.

Содержание:

  • Объект pandas.Period представляет собой промежуток/период/интервал времени;
  • Объект pandas.PeriodIndex представляет собой неизменяемый numpy.ndarray(), содержащий значения, указывающие регулярные промежутки/периоды/интервалы времени.
  • Метод DataFrame.to_period преобразовывает индекс DataFrame/DatetimeIndex в PeriodIndex.
  • Функция pandas.period_range() создает PeriodIndex с фиксированной частотой по заданным параметрам.

pandas.Period(value=None, freq=None, ordinal=None, year=None, month=None, quarter=None, day=None, hour=None, minute=None, second=None):

Класс pandas.Period() представляет собой промежуток времени (например, день, месяц, квартал и т.д.).

Принимаемые аргументы:

  • value=None - период времени (например, ‘4Q2005’). Это ни начало, ни конец периода, а скорее сам весь период. Принимает следующие объекты: str, datetime, date или pandas.Timestamp.
  • freq=None - строка периода pandas или соответствующих объектов. Если значение равно datetime, то требуется частота.
  • ordinal=None - смещение от предшествовавшей григорианской эпохе.
  • year=None - год периода.
  • month=None - месяц периода.
  • quarter=None - квартал периода.
  • day=None - день периода.
  • hour=None - час периода.
  • minute=None - минута периода.
  • second=None - секунда периода.

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

>>> import pandas as pd
>>> pd.Period("2024", freq="Y")
# Period('2024', 'Y-DEC')
>>> pd.Period("2024-1-1", freq="D")
# Period('2024-01-01', 'D')
>>> pd.Period("2024-1-1 19:00", freq="h")
# Period('2024-01-01 19:00', 'h')
>>> pd.Period("2024-1-1 19:00", freq="5h")
# Period('2024-01-01 19:00', '5h')

Чтобы преобразовать представление YYYYMMDD на основе int64 в Period:

>>> s = pd.Series([20241231, 20191130, 20131231])
>>> s
# 0    20241231
# 1    20191130
# 2    20131231
# dtype: int64

# определим функцию преобразования
def conv(x):
    return pd.Period(year=x // 10000, month=x // 100 % 100, day=x % 100, freq="D")

>>> s.apply(conv)
# 0    2024-12-31
# 1    2019-11-30
# 2    2013-12-31
# dtype: period[D]

>>> s.apply(conv)[2]
# Period('2013-12-31', 'D')

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

>>> p = pd.Period("2024", freq="Y")
>>> p + 1
# Period('2025', 'A-DEC')
>>> p - 3
# Period('2021', 'A-DEC')
>>> p = pd.Period("2024-01", freq="2M")
>>> p + 2
# Period('2024-05', '2M')
>>> p - 1
# Period('2023-11', '2M')
>>> p == pd.Period("2024-01", freq="3M")
# False

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

>>> p = pd.Period("2014-07-01 09:00", freq="h")
>>> p + pd.offsets.Hour(2)
# Period('2014-07-01 11:00', 'H')
>>> import datetime
>>> p + datetime.timedelta(minutes=120)
# Period('2014-07-01 11:00', 'H')
>>> import numpy as np
>>> p + np.timedelta64(7200, "s")
# Period('2014-07-01 11:00', 'H')
>>> p + pd.offsets.Minute(5)
# Traceback (most recent call last):
# ...
# ... Input cannot be converted to Period(freq=H)

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

>>> pd.Period("2024", freq="Y") - pd.Period("2014", freq="Y")
# <10 * YearEnds: month=12>

Преобразования Period с помощью метода .asfreq()

Частоту pandas.Period можно преобразовать с помощью метода .asfreq(). Например, 2023 финансовый год, закончился в декабре:

>>> p = pd.Period("2023", freq="Y-DEC")
>>> p
# Period('2023', 'Y-DEC')

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

>>> p.asfreq("M", how="start")
# Period('2023-01', 'M')

# для удобства можно использовать сокращения:
# `s` - start
# `e`- end
# например, how="e"
>>> p.asfreq("M", how="e")
# Period('2023-12', 'M')

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

>>> p = pd.Period("2022-12", freq="M")
>>> p.asfreq("Y-NOV")
# Period('2023', 'Y-NOV')

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

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

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

>>> p = pd.Period("2024Q1", freq="Q-DEC")
>>> p.asfreq("D", "s")
# Period('2024-01-01', 'D')
>>> p.asfreq("D", "e")
# Period('2024-03-31', 'D')

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

>>> p = pd.Period("2024Q4", freq="Q-MAR")
>>> p.asfreq("D", "s")
# Period('2024-01-01', 'D')
>>> p.asfreq("D", "e")
# Period('2024-03-31', 'D')

pandas.PeriodIndex(data=None, ordinal=None, freq=None, dtype=None, copy=False, name=None, **fields):

Класс pandas.PeriodIndex() представляет собой неизменяемый numpy.ndarray(), содержащий порядковые значения, указывающие регулярные периоды времени. Индексные ключи привязаны к объектам Period, которые содержат метаданные (например, информацию о частоте).

Принимаемые аргументы:

  • data=None - необязательные данные, подобные периодам, для построения индекса.
  • ordinal=None -
  • freq=None - строка периода pandas.
  • dtype=None - строка или PeriodDtype,
  • copy=False - создает копию входного ndarray.
  • name=None - имя индекса

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

>>> import pandas as pd
>>> pd.PeriodIndex(["2023-1", "2023-2", "2023-3"], freq="M")
# PeriodIndex(['2023-01', '2023-02', '2023-03'], dtype='period[M]')

# для создания PeriodIndex из чисел, представляющих год, месяц, день и т.д. 
# с версии 2.2 используем метод `PeriodIndex.from_fields()`
>>> idx = pd.PeriodIndex.from_fields(year=[2023, 2024], quarter=[1, 3])
>>> idx
# PeriodIndex(['2023Q1', '2024Q3'], dtype='period[Q-DEC]')

# до версии 2.2 аргументы принимал сам конструктор `PeriodIndex`
>>> pd.PeriodIndex(year=[2023, 2024], quarter=[1, 3])
# PeriodIndex(['2023Q1', '2024Q3'], dtype='period[Q-DEC]')

Для преобразования представления YYYYMMDD на основе int64 в PeriodIndex необходимо выполнить следующие действия:

# допустим дана серия
>>> s = pd.Series([20241231, 20191130, 20131231])
>>> s
# 0    20241231
# 1    20191130
# 2    20131231
# dtype: int64

# определим функцию преобразования `int64` в `Period`
def conv(x):
    return pd.Period(year=x // 10000, month=x // 100 % 100, day=x % 100, freq="D")

# преобразуем в `PeriodIndex`
>>> span = pd.PeriodIndex(s.apply(conv))
>>> span
# PeriodIndex(['2024-12-31', '2019-11-30', '2013-12-31'], dtype='period[D]')

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

>>> prng = pd.period_range("1/1/2023", "1/1/2024", freq="M")
>>> prng
# PeriodIndex(['2023-01', '2023-02', '2023-03', '2023-04', '2023-05', '2023-06',
#              '2023-07', '2023-08', '2023-09', '2023-10', '2023-11', '2023-12',
#              '2024-01'],
#             dtype='period[M]')

При передаче умноженной частоты freq="3M" в pandas.period_range() выводится последовательность периодов с умноженным диапазоном.

>>> pd.period_range(start="2023-01", freq="3M", periods=4)
# PeriodIndex(['2023-01', '2023-04', '2023-07', '2023-10'], dtype='period[3M]')

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

>>> pd.period_range(
...     start=pd.Period("2024Q1", freq="Q"), end=pd.Period("2024Q2", freq="Q"), freq="M"
... )
# PeriodIndex(['2024-03', '2024-04', '2024-05', '2024-06'], dtype='period[M]')

Объект pandas.PeriodIndex удобно использовать для индексации объектов pandas:

>>> import numpy as np
>>> ps = pd.Series(np.random.randn(len(prng)), prng)
>>> ps
# 2023-01   -0.187288
# 2023-02    0.953165
# 2023-03    1.141206
# 2023-04   -1.619246
# 2023-05   -0.518793
# 2023-06   -2.186372
# 2023-07   -1.063065
# 2023-08   -0.880710
# 2023-09    1.914001
# 2023-10    0.549167
# 2023-11    0.557771
# 2023-12    0.310108
# 2024-01    0.113552
# Freq: M, dtype: float64

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

>>> idx = pd.period_range("2024-07-01 09:00", periods=5, freq="h")
>>> idx
# PeriodIndex(['2024-07-01 09:00', '2024-07-01 10:00', '2024-07-01 11:00',
#              '2024-07-01 12:00', '2024-07-01 13:00'],
#             dtype='period[h]')

>>> idx + pd.offsets.Hour(2)
# PeriodIndex(['2024-07-01 11:00', '2024-07-01 12:00', '2024-07-01 13:00',
#              '2024-07-01 14:00', '2024-07-01 15:00'],
#             dtype='period[h]')

>>> idx = pd.period_range("2024-07", periods=5, freq="M")
>>> idx
# PeriodIndex(['2024-07', '2024-08', '2024-09', '2024-10', '2024-11'], dtype='period[M]')

>>> idx + pd.offsets.MonthEnd(3)
# PeriodIndex(['2024-10', '2024-11', '2024-12', '2025-01', '2025-02'], dtype='period[M]')

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

>>> pi = pd.period_range("2024-01-01", periods=3, freq="M")
>>> pi
# PeriodIndex(['2024-01', '2024-02', '2024-03'], dtype='period[M]')
>>> pi.dtype
# period[M]

Тип периода можно использовать в методе PeriodIndex.astype(...), что позволяет изменить частоту PeriodIndex, и преобразовать pandas.DatetimeIndex в pandas.PeriodIndex, подобно методу DatetimeIndex.to_period():

# меняем ежемесячный период на ежедневный
>>> pi.astype("period[D]")
# PeriodIndex(['2024-01-31', '2024-02-29', '2024-03-31'], dtype='period[D]')

# конвертируем `PeriodIndex` в `DatetimeIndex`
>>> pi.astype("datetime64[ns]")
# DatetimeIndex(['2024-01-01', '2024-02-01', '2024-03-01'], dtype='datetime64[ns]', freq='MS')

# создадим `DatetimeIndex`
>>> dti = pd.date_range("2024-01-01", freq="ME", periods=3)
>>> dti
# DatetimeIndex(['2024-01-31', '2024-02-29', '2024-03-31'], dtype='datetime64[ns]', freq='ME')

# теперь конвертируем его в `PeriodIndex`
>>> dti.astype("period[M]")
# PeriodIndex(['2024-01', '2024-02', '2024-03'], dtype='period[M]')

Выбор данных/индексация с PeriodIndex

Объект PeriodIndex поддерживает срезы дат с немонотонными индексами.

Для выбора данных с PeriodIndex можно передавать даты и строки дат в Series и DataFrame так же, как и DatetimeIndex.

>>> ps["2024-01"]
# 0.11355172009101018

>>> import datetime
>>> ps[datetime.datetime(2023, 12, 25):]
# 2023-12    0.310108
# 2024-01    0.113552
# Freq: M, dtype: float64

>>> ps["10/31/2023":"12/31/2023"]
# 2023-10    0.549167
# 2023-11    0.557771
# 2023-12    0.310108
# Freq: M, dtype: float64

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

>>> ps["2023"]
# 2023-01   -0.187288
# 2023-02    0.953165
# 2023-03    1.141206
# 2023-04   -1.619246
# 2023-05   -0.518793
# 2023-06   -2.186372
# 2023-07   -1.063065
# 2023-08   -0.880710
# 2023-09    1.914001
# 2023-10    0.549167
# 2023-11    0.557771
# 2023-12    0.310108
# Freq: M, dtype: float64

# создадим новый `DataFrame`
dfp = pd.DataFrame(
    np.random.randn(600, 1),
    columns=["A"],
    index=pd.period_range("2024-01-01 9:00", periods=600, freq="min"),
)
>>> dfp
#                          A
# 2024-01-01 09:00  1.387812
# 2024-01-01 09:01  0.937993
# 2024-01-01 09:02  0.914350
# 2024-01-01 09:03 -2.258870
# 2024-01-01 09:04  1.173190
# ...                    ...
# 2024-01-01 18:55  0.314326
# 2024-01-01 18:56  1.475295
# 2024-01-01 18:57 -1.081923
# 2024-01-01 18:58 -1.080603
# 2024-01-01 18:59  0.394975
# 
# [600 rows x 1 columns]

>>> dfp.loc["2024-01-01 10h"]
#                          A
# 2024-01-01 10:00 -1.076145
# 2024-01-01 10:01 -0.754348
# 2024-01-01 10:02  1.073532
# 2024-01-01 10:03 -0.848630
# 2024-01-01 10:04 -2.349152
# ...
# 2024-01-01 10:55  0.027428
# 2024-01-01 10:56 -1.752014
# 2024-01-01 10:57  0.839968
# 2024-01-01 10:58  0.248728
# 2024-01-01 10:59  0.601582

Конечные точки будут включены в результат. В приведенном ниже примере данные фрагментируются с 10:00 до 11:59.

>>> dfp["2024-01-01 10h":"2024-01-01 11h"]
#                          A
# 2024-01-01 10:00 -1.076145
# 2024-01-01 10:01 -0.754348
# 2024-01-01 10:02  1.073532
# 2024-01-01 10:03 -0.848630
# 2024-01-01 10:04 -2.349152
# ...                    ...
# 2024-01-01 11:55  0.194616
# 2024-01-01 11:56 -1.916404
# 2024-01-01 11:57  1.751644
# 2024-01-01 11:58  1.221067
# 2024-01-01 11:59 -0.547866
# 
# [120 rows x 1 columns]

Индекс pandas.PeriodIndex как и объект Period поддерживает повторную выборку с помощью метода .asfreq().

Преобразование между DatetimeIndex и PeriodIndex

Данные с отметкой времени можно преобразовать в данные PeriodIndex с помощью методов Timestamp.to_period() и наоборот с помощью метода PeriodIndex.to_timestamp(freq=None, how='start'):

>>> rng = pd.date_range("1/1/2024", periods=5, freq="ME")
>>> ts = pd.Series(np.random.randn(len(rng)), index=rng)
>>> ts
# 2024-01-31    0.995308
# 2024-02-29   -0.040653
# 2024-03-31   -2.594716
# 2024-04-30    1.504875
# 2024-05-31    0.704241
# Freq: ME, dtype: float64

>>> ps = ts.to_period()
>>> ps
# 2024-01    0.995308
# 2024-02   -0.040653
# 2024-03   -2.594716
# 2024-04    1.504875
# 2024-05    0.704241
# Freq: M, dtype: float64

>>> ps.to_timestamp()
# 2024-01-01    0.995308
# 2024-02-01   -0.040653
# 2024-03-01   -2.594716
# 2024-04-01    1.504875
# 2024-05-01    0.704241
# Freq: MS, dtype: float64

Помним, что 's' и 'e' можно использовать для возврата временных меток в начале или конце периода:

>>> ps.to_timestamp("D", how="s")
# 2024-01-01    0.995308
# 2024-02-01   -0.040653
# 2024-03-01   -2.594716
# 2024-04-01    1.504875
# 2024-05-01    0.704241
# Freq: MS, dtype: float64

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

>>> prng = pd.period_range("2014Q1", "2024Q4", freq="Q-NOV")
>>> ts = pd.Series(np.random.randn(len(prng)), prng)
>>> ts.index = (prng.asfreq("M", "e") + 1).asfreq("h", "s") + 9
>>> ts.head()
# 2014-03-01 09:00    0.045042
# 2014-06-01 09:00    0.530235
# 2014-09-01 09:00   -0.598684
# 2014-12-01 09:00    0.611139
# 2015-03-01 09:00   -1.086792
# Freq: h, dtype: float64