Spec-Zone.ru › pandas 2

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

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

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

Разбор данных временных рядов из различных источников и форматов

In [1]: import datetime

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

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

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

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

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

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

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

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

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

Передискретизацию временного ряда или преобразование его к заданной частоте

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

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

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

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

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

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

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

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

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

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

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

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

Обзор

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

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

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

  3. Временные интервалы: период времени, определяемый моментом времени и связанной с ним частотой.

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

Понятие

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

Класс массива

Тип данных pandas

Основной способ создания

Дата и время

Timestamp

DatetimeIndex

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

to_datetime или date_range

Временные дельты

Timedelta

TimedeltaIndex

timedelta64[ns]

to_timedelta или timedelta_range

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

Period

PeriodIndex

period[freq]

Period или period_range

Смещения дат

DateOffset

None

None

DateOffset

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

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

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

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

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

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

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

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

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

Данные с временными метками — это наиболее простой тип данных временных рядов, связывающий значения с конкретными моментами времени. Для объектов 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 объектов 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)

Примечание

Параметр 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 десятичных цифр. Округление при преобразовании чисел с плавающей запятой в 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')

См. также

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

Преобразование временных меток в эпоху

Чтобы выполнить обратную операцию и преобразовать 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 до 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 через [] будет выполняться по столбцам, а не по строкам; см. раздел Основы индексации. Например, 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]

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

Предоставляется вспомогательная функция 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.

Свойство

Описание

year

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

month

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

day

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

hour

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

minute

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

second

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

microsecond

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

nanosecond

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

date

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

time

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

timetz

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

dayofyear

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

day_of_year

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

weekofyear

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

week

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

dayofweek

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

day_of_week

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

weekday

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

quarter

Квартал даты: январь–март = 1, апрель–июнь = 2 и т. д.

days_in_month

Количество дней в месяце даты и времени

is_month_start

Логическое значение, указывающее, является ли дата первым днём месяца (определяется частотой)

is_month_end

Логическое значение, указывающее, является ли дата последним днём месяца (определяется частотой)

is_quarter_start

Логическое значение, указывающее, является ли дата первым днём квартала (определяется частотой)

is_quarter_end

Логическое значение, указывающее, является ли дата последним днём квартала (определяется частотой)

is_year_start

Логическое значение, указывающее, является ли дата первым днём года (определяется частотой)

is_year_end

Логическое значение, указывающее, является ли дата последним днём года (определяется частотой)

is_leap_year

Логическое значение, указывающее, относится ли дата к високосному году

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

Компоненты года, недели и дня по стандарту 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'):

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

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

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

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

# This particular day contains a day light savings time transition
In [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'

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

SemiMonthEnd

'SME'

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

SemiMonthBegin

'SMS'

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

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')

Параметр 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)

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

Класс 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’ в своём приложении.

Рабочий час

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

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

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

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

# 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]: <BusinessHour: bh=11:00-20:00>

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]: <BusinessHour: bh=17:00-09:00>

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) между 2014-08-01 17:00 и 2014-08-04 09:00 нет промежутка (0 минут).

# 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, описанное в следующем подразделе.

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

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

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

BYE

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

YS

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

BYS

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

h

почасовая частота

bh

частота рабочих часов

cbh

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

min

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

s

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

ms

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

us

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

ns

наносекунды

Устарело начиная с версии 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

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

ms

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

us

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

ns

наносекунды

Устарело начиная с версии 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 или в других видах анализа, требующих заранее заданного набора праздников. Класс AbstractHolidayCalendar содержит все необходимые методы для получения списка праздников; в конкретном классе календаря праздников нужно определить только rules. Кроме того, атрибуты класса start_date и end_date задают диапазон дат, для которого генерируются праздники. Чтобы диапазон применялся ко всем подклассам календаря, эти атрибуты следует переопределить в классе AbstractHolidayCalendar. USFederalHolidayCalendar — единственный существующий календарь; он служит главным образом примером для разработки других календарей.

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

Правило

Описание

nearest_workday

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

sunday_to_monday

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

next_monday_or_tuesday

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

previous_friday

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

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. Как и любое другое смещение, его можно использовать для создания 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/строки.

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=<DateOffset: weekday=MO(-1)>),
 Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7f9491497c70>),
 Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO(+2)>)]

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

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

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

Сдвиг / лагирование

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

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

In [280]: ts = ts[:5]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Преобразование в объекты datetime Python

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

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

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

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

Метод 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

Функция 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

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

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

При ресэмплировании данных типа period параметру 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

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

Метод resample() возвращает экземпляр pandas.api.typing.Resampler. Как и в API агрегирования, API groupby и API оконных операций, выборочное ресэмплирование можно выполнять с помощью 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

Аналогично, если ресэмплирование нужно выполнить по уровню типа datetime в 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 значение '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 устанавливается в 'right', поскольку последнее значение должно считаться граничной точкой последнего интервала.

Можно задать для origin значение 'end'. Значение для конкретного индекса Timestamp соответствует результату ресэмплирования от текущего 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')

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

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

Period

Объект 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 (интервалами) не допускаются.

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]')

Конструктор 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, 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

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

Тип данных period содержит атрибут 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]

Тип данных period можно использовать в .astype(...). Он позволяет изменить freq у PeriodIndex, например .asfreq(), и преобразовать DatetimeIndex в 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:00 последнего дня месяца, следующего за концом квартала:

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 типа 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)

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

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]: <DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD>

# 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 для того же входного часового пояса. Объект 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]: <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>

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

In [458]: ts.tz
Out[458]: <DstTzInfo 'US/Pacific' PST-1 day, 16:00:00 STD>

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

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

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

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

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

Для часовых поясов pytz передавать объект часового пояса непосредственно в конструктор datetime.datetime (например, datetime.datetime(2011, 1, 1, tzinfo=pytz.timezone('US/Eastern'))) некорректно. Вместо этого дату и время необходимо локализовать с помощью метода localize объекта часового пояса pytz.

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

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

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

Если вы используете даты после 2038-01-18, из-за текущих недостатков базовых библиотек, вызванных проблемой 2038 года, переходы на летнее время (DST) для дат с часовым поясом применяться не будут. Если и когда базовые библиотеки будут исправлены, переходы на летнее время будут применяться.

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

In [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 в разных часовых поясах возвращают Series в UTC, выравнивая данные по временным меткам 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)

Fold

Для неоднозначного времени pandas поддерживает явное указание аргумента fold, задаваемого только по ключевому слову. Из-за перехода на летнее время одни и те же показания часов могут возникать дважды при переходе с летнего времени на зимнее; fold указывает, соответствует ли объект даты и времени первому (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.<locals>.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.<locals>.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 и часовыми поясами

Объект Series со значениями без часового пояса представлен типом данных 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]

Объект Series со значениями, содержащими часовой пояс, представлен типом данных 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 пока не поддерживает часовые пояса (хотя вывод отображается в местном часовом поясе!), поэтому для данных с часовым поясом возвращается массив объектов Timestamp:

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)

Преобразование в массив объектов Timestamp сохраняет сведения о часовом поясе. Например, при обратном преобразовании в 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]')

© 2008–2011, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
© 2011–2025, Open source contributors
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/2.3.0/user_guide/timeseries.html

Spec-Zone.ru

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