Функциональность временных рядов / работы с датами#
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 общих концепции, связанных со временем:
Дата и время: Конкретная дата и время с поддержкой часового пояса. Аналогично
datetime.datetimeиз стандартной библиотеки.Временные дельты: Абсолютная продолжительность времени. Аналогично
datetime.timedeltaиз стандартной библиотеки.Временные промежутки: Промежуток времени, определяемый точкой во времени и связанной с ней частотой.
Смещения дат: относительная продолжительность времени, учитывающая календарную арифметику. Аналогично
dateutil.relativedelta.relativedeltaизdateutilпакет.
Концепция |
Скалярный класс |
Класс Array |
Тип данных pandas |
Основной метод создания |
|---|---|---|---|---|
Даты и время |
|
|
|
|
Временные дельты |
|
|
|
|
Временные промежутки |
|
|
|
|
Смещения дат |
|
|
|
|
Для данных временных рядов принято представлять временную компоненту в индексе Series или DataFrame
так что манипуляции могут быть выполнены относительно временного элемента.
In [19]: pd.Series(range(3), index=pd.date_range("2000", freq="D", periods=3))
Out[19]:
2000-01-01 0
2000-01-02 1
2000-01-03 2
Freq: D, dtype: int64
Однако, Series и DataFrame может также напрямую поддерживать временную компоненту как сами данные.
In [20]: pd.Series(pd.date_range("2000", freq="D", periods=3))
Out[20]:
0 2000-01-01
1 2000-01-02
2 2000-01-03
dtype: datetime64[ns]
Series и DataFrame имеют расширенную поддержку типов данных и функциональность для datetime, timedelta
и Period данные при передаче в эти конструкторы. DateOffset
однако данные будут храниться как object data.
In [21]: pd.Series(pd.period_range("1/1/2011", freq="M", periods=3))
Out[21]:
0 2011-01
1 2011-02
2 2011-03
dtype: period[M]
In [22]: pd.Series([pd.DateOffset(1), pd.DateOffset(2)])
Out[22]:
0
1 <2 * DateOffsets>
dtype: object
In [23]: pd.Series(pd.date_range("1/1/2011", freq="ME", periods=3))
Out[23]:
0 2011-01-31
1 2011-02-28
2 2011-03-31
dtype: datetime64[ns]
Наконец, pandas представляет нулевые даты, временные интервалы и промежутки времени как NaT который
полезен для представления пропущенных или нулевых значений даты и ведет себя аналогично np.nan делает для данных с плавающей точкой.
In [24]: pd.Timestamp(pd.NaT)
Out[24]: NaT
In [25]: pd.Timedelta(pd.NaT)
Out[25]: NaT
In [26]: pd.Period(pd.NaT)
Out[26]: NaT
# Equality acts as np.nan would
In [27]: pd.NaT == pd.NaT
Out[27]: False
Временные метки vs. временные промежутки#
Данные с временными метками — это самый базовый тип временных рядов, который связывает значения с точками во времени. Для объектов pandas это означает использование точек во времени.
In [28]: import datetime
In [29]: pd.Timestamp(datetime.datetime(2012, 5, 1))
Out[29]: Timestamp('2012-05-01 00:00:00')
In [30]: pd.Timestamp("2012-05-01")
Out[30]: Timestamp('2012-05-01 00:00:00')
In [31]: pd.Timestamp(2012, 5, 1)
Out[31]: Timestamp('2012-05-01 00:00:00')
Однако во многих случаях более естественно связывать такие вещи, как переменные изменения,
с временным промежутком. Промежуток, представленный Period может быть
явно указан или выведен из формата строки даты и времени.
Например:
In [32]: pd.Period("2011-01")
Out[32]: Period('2011-01', 'M')
In [33]: pd.Period("2012-05", freq="D")
Out[33]: Period('2012-05-01', 'D')
Timestamp и Period может служить индексом. Списки
Timestamp и Period автоматически приводятся к DatetimeIndex
и PeriodIndex соответственно.
In [34]: dates = [
....: pd.Timestamp("2012-05-01"),
....: pd.Timestamp("2012-05-02"),
....: pd.Timestamp("2012-05-03"),
....: ]
....:
In [35]: ts = pd.Series(np.random.randn(3), dates)
In [36]: type(ts.index)
Out[36]: pandas.core.indexes.datetimes.DatetimeIndex
In [37]: ts.index
Out[37]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[ns]', freq=None)
In [38]: ts
Out[38]:
2012-05-01 0.469112
2012-05-02 -0.282863
2012-05-03 -1.509059
dtype: float64
In [39]: periods = [pd.Period("2012-01"), pd.Period("2012-02"), pd.Period("2012-03")]
In [40]: ts = pd.Series(np.random.randn(3), periods)
In [41]: type(ts.index)
Out[41]: pandas.core.indexes.period.PeriodIndex
In [42]: ts.index
Out[42]: PeriodIndex(['2012-01', '2012-02', '2012-03'], dtype='period[M]')
In [43]: ts
Out[43]:
2012-01 -1.135632
2012-02 1.212112
2012-03 -0.173215
Freq: M, dtype: float64
pandas позволяет захватывать оба представления и конвертировать между ними. Под капотом pandas представляет временные метки, используя экземпляры Timestamp и последовательности временных меток с использованием экземпляров
DatetimeIndex. Для регулярных временных интервалов pandas использует Period объекты для
скалярных значений и PeriodIndex для последовательностей интервалов. Лучшая поддержка нерегулярных интервалов с произвольными начальными и конечными точками появится в будущих выпусках.
Преобразование в метки времени#
Чтобы преобразовать Series или списокоподобный объект датоподобных объектов, например строк, эпох или их смеси, вы можете использовать to_datetime функция. При передаче
Series, это возвращает Series (с тем же индексом), в то время как список преобразуется в DatetimeIndex:
In [44]: pd.to_datetime(pd.Series(["Jul 31, 2009", "Jan 10, 2010", None]))
Out[44]:
0 2009-07-31
1 2010-01-10
2 NaT
dtype: datetime64[ns]
In [45]: pd.to_datetime(["2005/11/23", "2010/12/31"])
Out[45]: DatetimeIndex(['2005-11-23', '2010-12-31'], dtype='datetime64[ns]', freq=None)
Если вы используете даты, которые начинаются с дня (т.е. в европейском стиле),
вы можете передать dayfirst флаг:
In [46]: pd.to_datetime(["04-01-2012 10:00"], dayfirst=True)
Out[46]: DatetimeIndex(['2012-01-04 10:00:00'], dtype='datetime64[ns]', freq=None)
In [47]: pd.to_datetime(["04-14-2012 10:00"], dayfirst=True)
Out[47]: DatetimeIndex(['2012-04-14 10:00:00'], dtype='datetime64[ns]', freq=None)
Предупреждение
Вы видите в примере выше, что dayfirst не является строгим. Если дата
не может быть разобрана с днем первым, она будет разобрана как если бы
dayfirst были False и также будет выдано предупреждение.
Если вы передаете одну строку в to_datetime, он возвращает один Timestamp.
Timestamp также может принимать строковый ввод, но не принимает параметры парсинга строк, такие как dayfirst или format, поэтому используйте to_datetime если они требуются.
In [48]: pd.to_datetime("2010/11/12")
Out[48]: Timestamp('2010-11-12 00:00:00')
In [49]: pd.Timestamp("2010/11/12")
Out[49]: Timestamp('2010-11-12 00:00:00')
Вы также можете использовать DatetimeIndex конструктор напрямую:
In [50]: pd.DatetimeIndex(["2018-01-01", "2018-01-03", "2018-01-05"])
Out[50]: DatetimeIndex(['2018-01-01', '2018-01-03', '2018-01-05'], dtype='datetime64[ns]', freq=None)
Строку 'infer' можно передать, чтобы установить частоту индекса как выведенную частоту при создании:
In [51]: pd.DatetimeIndex(["2018-01-01", "2018-01-03", "2018-01-05"], freq="infer")
Out[51]: DatetimeIndex(['2018-01-01', '2018-01-03', '2018-01-05'], dtype='datetime64[ns]', freq='2D')
Предоставление аргумента формата#
В дополнение к обязательной строке даты и времени, format аргумент может быть передан для обеспечения конкретного разбора.
Это также может значительно ускорить преобразование.
In [52]: pd.to_datetime("2010/11/12", format="%Y/%m/%d")
Out[52]: Timestamp('2010-11-12 00:00:00')
In [53]: pd.to_datetime("12-11-2010 00:00", format="%d-%m-%Y %H:%M")
Out[53]: Timestamp('2010-11-12 00:00:00')
Для получения дополнительной информации о доступных вариантах при указании format
опция, см. Python документация по datetime.
Сборка даты и времени из нескольких столбцов DataFrame#
Вы также можете передать DataFrame целочисленных или строковых столбцов для сборки в Series of Timestamps.
In [54]: df = pd.DataFrame(
....: {"year": [2015, 2016], "month": [2, 3], "day": [4, 5], "hour": [2, 3]}
....: )
....:
In [55]: pd.to_datetime(df)
Out[55]:
0 2015-02-04 02:00:00
1 2016-03-05 03:00:00
dtype: datetime64[ns]
Вы можете передать только те столбцы, которые нужно собрать.
In [56]: pd.to_datetime(df[["year", "month", "day"]])
Out[56]:
0 2015-02-04
1 2016-03-05
dtype: datetime64[ns]
pd.to_datetime ищет стандартные обозначения компонентов даты и времени в названиях столбцов, включая:
обязательно:
year,month,dayнеобязательный:
hour,minute,second,millisecond,microsecond,nanosecond
Некорректные данные#
Поведение по умолчанию, errors='raise', это вызывать исключение при невозможности разбора:
In [57]: pd.to_datetime(['2009/07/31', 'asd'], errors='raise')
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[57], line 1
----> 1 pd.to_datetime(['2009/07/31', 'asd'], errors='raise')
File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:1104, in to_datetime(arg, errors, dayfirst, yearfirst, utc, format, exact, unit, infer_datetime_format, origin, cache)
1102 result = _convert_and_box_cache(argc, cache_array)
1103 else:
-> 1104 result = convert_listlike(argc, format)
1105 else:
1106 result = convert_listlike(np.array([arg]), format)[0]
File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:435, in _convert_listlike_datetimes(arg, format, name, utc, unit, errors, dayfirst, yearfirst, exact)
433 # `format` could be inferred, or user didn't ask for mixed-format parsing.
434 if format is not None and format != "mixed":
--> 435 return _array_strptime_with_fallback(arg, name, utc, format, exact, errors)
437 result, tz_parsed = objects_to_datetime64(
438 arg,
439 dayfirst=dayfirst,
(...)
443 allow_object=True,
444 )
446 if tz_parsed is not None:
447 # We can take a shortcut since the datetime64 numpy array
448 # is in UTC
File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:469, in _array_strptime_with_fallback(arg, name, utc, fmt, exact, errors)
458 def _array_strptime_with_fallback(
459 arg,
460 name,
(...)
464 errors: str,
465 ) -> Index:
466 """
467 Call array_strptime, with fallback behavior depending on 'errors'.
468 """
--> 469 result, tz_out = array_strptime(arg, fmt, exact=exact, errors=errors, utc=utc)
470 if tz_out is not None:
471 unit = np.datetime_data(result.dtype)[0]
File ~/work/pandas/pandas/pandas/_libs/tslibs/strptime.pyx:501, in pandas._libs.tslibs.strptime.array_strptime()
File ~/work/pandas/pandas/pandas/_libs/tslibs/strptime.pyx:451, in pandas._libs.tslibs.strptime.array_strptime()
File ~/work/pandas/pandas/pandas/_libs/tslibs/strptime.pyx:583, in pandas._libs.tslibs.strptime._parse_with_format()
ValueError: time data "asd" doesn't match format "%Y/%m/%d", at position 1. You might want to try:
- passing `format` if your strings have a consistent format;
- passing `format='ISO8601'` if your strings are all ISO8601 but not necessarily in exactly the same format;
- passing `format='mixed'`, and the format will be inferred for each element individually. You might want to use `dayfirst` alongside this.
Передать errors='coerce' для преобразования неразбираемых данных в NaT (не время):
In [58]: pd.to_datetime(["2009/07/31", "asd"], errors="coerce")
Out[58]: DatetimeIndex(['2009-07-31', 'NaT'], dtype='datetime64[ns]', freq=None)
Метки времени эпохи#
pandas поддерживает преобразование целочисленных или вещественных меток времени эпохи в Timestamp и
DatetimeIndex. Единица измерения по умолчанию — наносекунды, поскольку именно так Timestamp
объекты хранятся внутренне. Однако эпохи часто хранятся в другом unit
которые могут быть указаны. Они вычисляются из начальной точки, заданной
origin параметр.
In [59]: pd.to_datetime(
....: [1349720105, 1349806505, 1349892905, 1349979305, 1350065705], unit="s"
....: )
....:
Out[59]:
DatetimeIndex(['2012-10-08 18:15:05', '2012-10-09 18:15:05',
'2012-10-10 18:15:05', '2012-10-11 18:15:05',
'2012-10-12 18:15:05'],
dtype='datetime64[ns]', freq=None)
In [60]: pd.to_datetime(
....: [1349720105100, 1349720105200, 1349720105300, 1349720105400, 1349720105500],
....: unit="ms",
....: )
....:
Out[60]:
DatetimeIndex(['2012-10-08 18:15:05.100000', '2012-10-08 18:15:05.200000',
'2012-10-08 18:15:05.300000', '2012-10-08 18:15:05.400000',
'2012-10-08 18:15:05.500000'],
dtype='datetime64[ns]', freq=None)
Примечание
The unit параметр не использует те же строки, что и format параметр, который обсуждался выше).
Доступные единицы перечислены в документации для pandas.to_datetime().
Создание Timestamp или DatetimeIndex с меткой времени эпохи
с tz указанный аргумент вызовет ValueError. Если у вас есть эпохи в настенном времени в другом часовом поясе, вы можете прочитать эпохи как временные метки без часового пояса, а затем локализовать в соответствующий часовой пояс:
In [61]: pd.Timestamp(1262347200000000000).tz_localize("US/Pacific")
Out[61]: Timestamp('2010-01-01 12:00:00-0800', tz='US/Pacific')
In [62]: pd.DatetimeIndex([1262347200000000000]).tz_localize("US/Pacific")
Out[62]: DatetimeIndex(['2010-01-01 12:00:00-08:00'], dtype='datetime64[ns, US/Pacific]', freq=None)
Примечание
Времена эпохи будут округлены до ближайшей наносекунды.
Предупреждение
Преобразование эпох времени с плавающей точкой может привести к неточным и неожиданным результатам.
Числа с плавающей точкой Python имеют около 15 знаков точности в десятичной системе. Округление при преобразовании из float в высокую точность Timestamp неизбежно. Единственный способ достичь точной точности — использовать тип
фиксированной ширины (например, int64).
In [63]: pd.to_datetime([1490195805.433, 1490195805.433502912], unit="s")
Out[63]: DatetimeIndex(['2017-03-22 15:16:45.433000088', '2017-03-22 15:16:45.433502913'], dtype='datetime64[ns]', freq=None)
In [64]: pd.to_datetime(1490195805433502912, unit="ns")
Out[64]: Timestamp('2017-03-22 15:16:45.433502912')
Смотрите также
От временных меток к эпохе#
Чтобы выполнить обратную операцию к предыдущей, а именно преобразовать из Timestamp в 'unix' эпоху:
In [65]: stamps = pd.date_range("2012-10-08 18:15:05", periods=4, freq="D")
In [66]: stamps
Out[66]:
DatetimeIndex(['2012-10-08 18:15:05', '2012-10-09 18:15:05',
'2012-10-10 18:15:05', '2012-10-11 18:15:05'],
dtype='datetime64[ns]', freq='D')
Мы вычитаем эпоху (полночь 1 января 1970 года по UTC), а затем выполняем целочисленное деление на «единицу» (1 секунда).
In [67]: (stamps - pd.Timestamp("1970-01-01")) // pd.Timedelta("1s")
Out[67]: Index([1349720105, 1349806505, 1349892905, 1349979305], dtype='int64')
Используя origin параметр#
Используя origin параметр, можно указать альтернативную начальную точку для создания DatetimeIndex. Например, чтобы использовать 1960-01-01 в качестве начальной даты:
In [68]: pd.to_datetime([1, 2, 3], unit="D", origin=pd.Timestamp("1960-01-01"))
Out[68]: DatetimeIndex(['1960-01-02', '1960-01-03', '1960-01-04'], dtype='datetime64[ns]', freq=None)
Значение по умолчанию установлено на origin='unix', который по умолчанию равен 1970-01-01 00:00:00.
Часто называют 'эпохой Unix' или временем POSIX.
In [69]: pd.to_datetime([1, 2, 3], unit="D")
Out[69]: DatetimeIndex(['1970-01-02', '1970-01-03', '1970-01-04'], dtype='datetime64[ns]', freq=None)
Генерация диапазонов временных меток#
Чтобы сгенерировать индекс с метками времени, вы можете использовать либо DatetimeIndex или
Index конструктор и передать список объектов datetime:
In [70]: dates = [
....: datetime.datetime(2012, 5, 1),
....: datetime.datetime(2012, 5, 2),
....: datetime.datetime(2012, 5, 3),
....: ]
....:
# Note the frequency information
In [71]: index = pd.DatetimeIndex(dates)
In [72]: index
Out[72]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[ns]', freq=None)
# Automatically converted to DatetimeIndex
In [73]: index = pd.Index(dates)
In [74]: index
Out[74]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[ns]', freq=None)
На практике это становится очень громоздким, потому что нам часто нужен очень длинный индекс с большим количеством временных меток. Если нам нужны временные метки с регулярной частотой, мы можем использовать date_range() и bdate_range() функции
для создания DatetimeIndex. Частота по умолчанию для date_range является
календарный день в то время как значение по умолчанию для bdate_range является рабочий день:
In [75]: start = datetime.datetime(2011, 1, 1)
In [76]: end = datetime.datetime(2012, 1, 1)
In [77]: index = pd.date_range(start, end)
In [78]: index
Out[78]:
DatetimeIndex(['2011-01-01', '2011-01-02', '2011-01-03', '2011-01-04',
'2011-01-05', '2011-01-06', '2011-01-07', '2011-01-08',
'2011-01-09', '2011-01-10',
...
'2011-12-23', '2011-12-24', '2011-12-25', '2011-12-26',
'2011-12-27', '2011-12-28', '2011-12-29', '2011-12-30',
'2011-12-31', '2012-01-01'],
dtype='datetime64[ns]', length=366, freq='D')
In [79]: index = pd.bdate_range(start, end)
In [80]: index
Out[80]:
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
'2011-01-07', '2011-01-10', '2011-01-11', '2011-01-12',
'2011-01-13', '2011-01-14',
...
'2011-12-19', '2011-12-20', '2011-12-21', '2011-12-22',
'2011-12-23', '2011-12-26', '2011-12-27', '2011-12-28',
'2011-12-29', '2011-12-30'],
dtype='datetime64[ns]', length=260, freq='B')
Удобные функции, такие как date_range и bdate_range может использовать
разнообразные псевдонимы частоты:
In [81]: pd.date_range(start, periods=1000, freq="ME")
Out[81]:
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-30',
'2011-05-31', '2011-06-30', '2011-07-31', '2011-08-31',
'2011-09-30', '2011-10-31',
...
'2093-07-31', '2093-08-31', '2093-09-30', '2093-10-31',
'2093-11-30', '2093-12-31', '2094-01-31', '2094-02-28',
'2094-03-31', '2094-04-30'],
dtype='datetime64[ns]', length=1000, freq='ME')
In [82]: pd.bdate_range(start, periods=250, freq="BQS")
Out[82]:
DatetimeIndex(['2011-01-03', '2011-04-01', '2011-07-01', '2011-10-03',
'2012-01-02', '2012-04-02', '2012-07-02', '2012-10-01',
'2013-01-01', '2013-04-01',
...
'2071-01-01', '2071-04-01', '2071-07-01', '2071-10-01',
'2072-01-01', '2072-04-01', '2072-07-01', '2072-10-03',
'2073-01-02', '2073-04-03'],
dtype='datetime64[ns]', length=250, freq='BQS-JAN')
date_range и bdate_range упростить генерацию диапазона дат
с использованием различных комбинаций параметров, таких как start, end, periods,
и freq. Начальная и конечная даты строго включены, поэтому даты вне
указанного диапазона не будут сгенерированы:
In [83]: pd.date_range(start, end, freq="BME")
Out[83]:
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29',
'2011-05-31', '2011-06-30', '2011-07-29', '2011-08-31',
'2011-09-30', '2011-10-31', '2011-11-30', '2011-12-30'],
dtype='datetime64[ns]', freq='BME')
In [84]: pd.date_range(start, end, freq="W")
Out[84]:
DatetimeIndex(['2011-01-02', '2011-01-09', '2011-01-16', '2011-01-23',
'2011-01-30', '2011-02-06', '2011-02-13', '2011-02-20',
'2011-02-27', '2011-03-06', '2011-03-13', '2011-03-20',
'2011-03-27', '2011-04-03', '2011-04-10', '2011-04-17',
'2011-04-24', '2011-05-01', '2011-05-08', '2011-05-15',
'2011-05-22', '2011-05-29', '2011-06-05', '2011-06-12',
'2011-06-19', '2011-06-26', '2011-07-03', '2011-07-10',
'2011-07-17', '2011-07-24', '2011-07-31', '2011-08-07',
'2011-08-14', '2011-08-21', '2011-08-28', '2011-09-04',
'2011-09-11', '2011-09-18', '2011-09-25', '2011-10-02',
'2011-10-09', '2011-10-16', '2011-10-23', '2011-10-30',
'2011-11-06', '2011-11-13', '2011-11-20', '2011-11-27',
'2011-12-04', '2011-12-11', '2011-12-18', '2011-12-25',
'2012-01-01'],
dtype='datetime64[ns]', freq='W-SUN')
In [85]: pd.bdate_range(end=end, periods=20)
Out[85]:
DatetimeIndex(['2011-12-05', '2011-12-06', '2011-12-07', '2011-12-08',
'2011-12-09', '2011-12-12', '2011-12-13', '2011-12-14',
'2011-12-15', '2011-12-16', '2011-12-19', '2011-12-20',
'2011-12-21', '2011-12-22', '2011-12-23', '2011-12-26',
'2011-12-27', '2011-12-28', '2011-12-29', '2011-12-30'],
dtype='datetime64[ns]', freq='B')
In [86]: pd.bdate_range(start=start, periods=20)
Out[86]:
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
'2011-01-07', '2011-01-10', '2011-01-11', '2011-01-12',
'2011-01-13', '2011-01-14', '2011-01-17', '2011-01-18',
'2011-01-19', '2011-01-20', '2011-01-21', '2011-01-24',
'2011-01-25', '2011-01-26', '2011-01-27', '2011-01-28'],
dtype='datetime64[ns]', freq='B')
Указание start, end, и periods сгенерирует диапазон равномерно распределенных дат от start to end включительно, с periods количество элементов в
результирующем DatetimeIndex:
In [87]: pd.date_range("2018-01-01", "2018-01-05", periods=5)
Out[87]:
DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04',
'2018-01-05'],
dtype='datetime64[ns]', freq=None)
In [88]: pd.date_range("2018-01-01", "2018-01-05", periods=10)
Out[88]:
DatetimeIndex(['2018-01-01 00:00:00', '2018-01-01 10:40:00',
'2018-01-01 21:20:00', '2018-01-02 08:00:00',
'2018-01-02 18:40:00', '2018-01-03 05:20:00',
'2018-01-03 16:00:00', '2018-01-04 02:40:00',
'2018-01-04 13:20:00', '2018-01-05 00:00:00'],
dtype='datetime64[ns]', freq=None)
Пользовательские диапазоны частот#
bdate_range также может генерировать диапазон дат с пользовательской частотой, используя weekmask и holidays параметров. Эти параметры будут использоваться только
если передана пользовательская строка частоты.
In [89]: weekmask = "Mon Wed Fri"
In [90]: holidays = [datetime.datetime(2011, 1, 5), datetime.datetime(2011, 3, 14)]
In [91]: pd.bdate_range(start, end, freq="C", weekmask=weekmask, holidays=holidays)
Out[91]:
DatetimeIndex(['2011-01-03', '2011-01-07', '2011-01-10', '2011-01-12',
'2011-01-14', '2011-01-17', '2011-01-19', '2011-01-21',
'2011-01-24', '2011-01-26',
...
'2011-12-09', '2011-12-12', '2011-12-14', '2011-12-16',
'2011-12-19', '2011-12-21', '2011-12-23', '2011-12-26',
'2011-12-28', '2011-12-30'],
dtype='datetime64[ns]', length=154, freq='C')
In [92]: pd.bdate_range(start, end, freq="CBMS", weekmask=weekmask)
Out[92]:
DatetimeIndex(['2011-01-03', '2011-02-02', '2011-03-02', '2011-04-01',
'2011-05-02', '2011-06-01', '2011-07-01', '2011-08-01',
'2011-09-02', '2011-10-03', '2011-11-02', '2011-12-02'],
dtype='datetime64[ns]', freq='CBMS')
Смотрите также
Ограничения временных меток#
Пределы представления временных меток зависят от выбранного разрешения. Для наносекундного разрешения временной диапазон, который может быть представлен с использованием 64-битного целого числа, ограничен примерно 584 годами:
In [93]: pd.Timestamp.min
Out[93]: Timestamp('1677-09-21 00:12:43.145224193')
In [94]: pd.Timestamp.max
Out[94]: Timestamp('2262-04-11 23:47:16.854775807')
При выборе секундного разрешения доступный диапазон увеличивается до +/- 2.9e11 years.
Разные разрешения могут быть преобразованы друг в друга через as_unit.
Смотрите также
Индексирование#
Одно из основных применений для DatetimeIndex используется как индекс для объектов pandas.
DatetimeIndex класс содержит множество оптимизаций, связанных с временными рядами:
Большой диапазон дат для различных смещений предварительно вычислен и кэшируется под капотом, чтобы сделать генерацию последующих диапазонов дат очень быстрой (просто нужно взять срез).
Быстрое смещение с использованием
shiftметод для объектов pandas.Объединение перекрывающихся
DatetimeIndexобъектов с одинаковой частотой выполняется очень быстро (важно для быстрого выравнивания данных).Быстрый доступ к полям даты через свойства, такие как
year,month, и т.д.Функции регуляризации, такие как
snapи очень быстрыйasofлогика.
DatetimeIndex объекты обладают всей базовой функциональностью обычных Index
объекты и множество продвинутых методов, специфичных для временных рядов, для удобной
обработки частот.
Смотрите также
Примечание
Хотя pandas не заставляет вас иметь отсортированный индекс дат, некоторые из этих методов могут иметь неожиданное или некорректное поведение, если даты не отсортированы.
DatetimeIndex может использоваться как обычный индекс и предлагает всю его
интеллектуальную функциональность, такую как выбор, срез и т.д.
In [95]: rng = pd.date_range(start, end, freq="BME")
In [96]: ts = pd.Series(np.random.randn(len(rng)), index=rng)
In [97]: ts.index
Out[97]:
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29',
'2011-05-31', '2011-06-30', '2011-07-29', '2011-08-31',
'2011-09-30', '2011-10-31', '2011-11-30', '2011-12-30'],
dtype='datetime64[ns]', freq='BME')
In [98]: ts[:5].index
Out[98]:
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29',
'2011-05-31'],
dtype='datetime64[ns]', freq='BME')
In [99]: ts[::2].index
Out[99]:
DatetimeIndex(['2011-01-31', '2011-03-31', '2011-05-31', '2011-07-29',
'2011-09-30', '2011-11-30'],
dtype='datetime64[ns]', freq='2BME')
Частичная строковая индексация#
Даты и строки, которые преобразуются в метки времени, могут передаваться как параметры индексирования:
In [100]: ts["1/31/2011"]
Out[100]: 0.11920871129693428
In [101]: ts[datetime.datetime(2011, 12, 25):]
Out[101]:
2011-12-30 0.56702
Freq: BME, dtype: float64
In [102]: ts["10/31/2011":"12/31/2011"]
Out[102]:
2011-10-31 0.271860
2011-11-30 -0.424972
2011-12-30 0.567020
Freq: BME, dtype: float64
Для удобства доступа к более длинным временным рядам вы также можете передать год или год и месяц в виде строк:
In [103]: ts["2011"]
Out[103]:
2011-01-31 0.119209
2011-02-28 -1.044236
2011-03-31 -0.861849
2011-04-29 -2.104569
2011-05-31 -0.494929
2011-06-30 1.071804
2011-07-29 0.721555
2011-08-31 -0.706771
2011-09-30 -1.039575
2011-10-31 0.271860
2011-11-30 -0.424972
2011-12-30 0.567020
Freq: BME, dtype: float64
In [104]: ts["2011-6"]
Out[104]:
2011-06-30 1.071804
Freq: BME, dtype: float64
Этот тип среза будет работать на DataFrame с DatetimeIndex также. Поскольку
частичный выбор строк является формой среза по меткам, конечные точки будет включено. Это
будет включать совпадающие времена на включённой дате:
Предупреждение
Индексирование DataFrame строки с одиночный строка с getitem (например frame[dtstring])
устарело, начиная с pandas 1.2.0 (из-за неоднозначности, индексирует ли оно строки или выбирает столбец) и будет удалено в будущей версии. Эквивалент
с .loc (например, frame.loc[dtstring]) все еще поддерживается.
In [105]: dft = pd.DataFrame(
.....: np.random.randn(100000, 1),
.....: columns=["A"],
.....: index=pd.date_range("20130101", periods=100000, freq="min"),
.....: )
.....:
In [106]: dft
Out[106]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-03-11 10:35:00 -0.747967
2013-03-11 10:36:00 -0.034523
2013-03-11 10:37:00 -0.201754
2013-03-11 10:38:00 -1.509067
2013-03-11 10:39:00 -1.693043
[100000 rows x 1 columns]
In [107]: dft.loc["2013"]
Out[107]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-03-11 10:35:00 -0.747967
2013-03-11 10:36:00 -0.034523
2013-03-11 10:37:00 -0.201754
2013-03-11 10:38:00 -1.509067
2013-03-11 10:39:00 -1.693043
[100000 rows x 1 columns]
Это начинается с самого первого времени в месяце и включает последнюю дату и время для месяца:
In [108]: dft["2013-1":"2013-2"]
Out[108]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-02-28 23:55:00 0.850929
2013-02-28 23:56:00 0.976712
2013-02-28 23:57:00 -2.693884
2013-02-28 23:58:00 -1.575535
2013-02-28 23:59:00 -1.573517
[84960 rows x 1 columns]
Это указывает время остановки который включает все времена в последний день:
In [109]: dft["2013-1":"2013-2-28"]
Out[109]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-02-28 23:55:00 0.850929
2013-02-28 23:56:00 0.976712
2013-02-28 23:57:00 -2.693884
2013-02-28 23:58:00 -1.575535
2013-02-28 23:59:00 -1.573517
[84960 rows x 1 columns]
Это указывает точный время остановки (и не совпадает с вышеуказанным):
In [110]: dft["2013-1":"2013-2-28 00:00:00"]
Out[110]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-02-27 23:56:00 1.197749
2013-02-27 23:57:00 0.720521
2013-02-27 23:58:00 -0.072718
2013-02-27 23:59:00 -0.681192
2013-02-28 00:00:00 -0.557501
[83521 rows x 1 columns]
Мы останавливаемся на включенной конечной точке, так как она является частью индекса:
In [111]: dft["2013-1-15":"2013-1-15 12:30:00"]
Out[111]:
A
2013-01-15 00:00:00 -0.984810
2013-01-15 00:01:00 0.941451
2013-01-15 00:02:00 1.559365
2013-01-15 00:03:00 1.034374
2013-01-15 00:04:00 -1.480656
... ...
2013-01-15 12:26:00 0.371454
2013-01-15 12:27:00 -0.930806
2013-01-15 12:28:00 -0.069177
2013-01-15 12:29:00 0.066510
2013-01-15 12:30:00 -0.003945
[751 rows x 1 columns]
DatetimeIndex частичная строковая индексация также работает на DataFrame с MultiIndex:
In [112]: dft2 = pd.DataFrame(
.....: np.random.randn(20, 1),
.....: columns=["A"],
.....: index=pd.MultiIndex.from_product(
.....: [pd.date_range("20130101", periods=10, freq="12h"), ["a", "b"]]
.....: ),
.....: )
.....:
In [113]: dft2
Out[113]:
A
2013-01-01 00:00:00 a -0.298694
b 0.823553
2013-01-01 12:00:00 a 0.943285
b -1.479399
2013-01-02 00:00:00 a -1.643342
... ...
2013-01-04 12:00:00 b 0.069036
2013-01-05 00:00:00 a 0.122297
b 1.422060
2013-01-05 12:00:00 a 0.370079
b 1.016331
[20 rows x 1 columns]
In [114]: dft2.loc["2013-01-05"]
Out[114]:
A
2013-01-05 00:00:00 a 0.122297
b 1.422060
2013-01-05 12:00:00 a 0.370079
b 1.016331
In [115]: idx = pd.IndexSlice
In [116]: dft2 = dft2.swaplevel(0, 1).sort_index()
In [117]: dft2.loc[idx[:, "2013-01-05"], :]
Out[117]:
A
a 2013-01-05 00:00:00 0.122297
2013-01-05 12:00:00 0.370079
b 2013-01-05 00:00:00 1.422060
2013-01-05 12:00:00 1.016331
Срезы со строковой индексацией также учитывают смещение UTC.
In [118]: df = pd.DataFrame([0], index=pd.DatetimeIndex(["2019-01-01"], tz="US/Pacific"))
In [119]: df
Out[119]:
0
2019-01-01 00:00:00-08:00 0
In [120]: df["2019-01-01 12:00:00+04:00":"2019-01-01 13:00:00+04:00"]
Out[120]:
0
2019-01-01 00:00:00-08:00 0
Срез против точного совпадения#
Одна и та же строка, используемая как параметр индексации, может трактоваться либо как срез, либо как точное совпадение в зависимости от разрешения индекса. Если строка менее точна, чем индекс, она будет трактоваться как срез, иначе как точное совпадение.
Рассмотрим Series объект с индексом с минутным разрешением:
In [121]: series_minute = pd.Series(
.....: [1, 2, 3],
.....: pd.DatetimeIndex(
.....: ["2011-12-31 23:59:00", "2012-01-01 00:00:00", "2012-01-01 00:02:00"]
.....: ),
.....: )
.....:
In [122]: series_minute.index.resolution
Out[122]: 'minute'
Строка временной метки менее точная, чем минута, даёт Series объект.
In [123]: series_minute["2011-12-31 23"]
Out[123]:
2011-12-31 23:59:00 1
dtype: int64
Строка временной метки с минутным разрешением (или более точным) дает скаляр, т.е. не приводится к срезу.
In [124]: series_minute["2011-12-31 23:59"]
Out[124]: 1
In [125]: series_minute["2011-12-31 23:59:00"]
Out[125]: 1
Если разрешение индекса — секунды, то метка времени с точностью до минуты дает
Series.
In [126]: series_second = pd.Series(
.....: [1, 2, 3],
.....: pd.DatetimeIndex(
.....: ["2011-12-31 23:59:59", "2012-01-01 00:00:00", "2012-01-01 00:00:01"]
.....: ),
.....: )
.....:
In [127]: series_second.index.resolution
Out[127]: 'second'
In [128]: series_second["2011-12-31 23:59"]
Out[128]:
2011-12-31 23:59:59 1
dtype: int64
Если строка временной метки рассматривается как срез, она может использоваться для индексации DataFrame с .loc[] также.
In [129]: dft_minute = pd.DataFrame(
.....: {"a": [1, 2, 3], "b": [4, 5, 6]}, index=series_minute.index
.....: )
.....:
In [130]: dft_minute.loc["2011-12-31 23"]
Out[130]:
a b
2011-12-31 23:59:00 1 4
Предупреждение
Однако, если строка рассматривается как точное совпадение, выбор в DataFrame’s [] будет по столбцам, а не по строкам, см. Основы индексирования. Например dft_minute['2011-12-31 23:59'] вызовет исключение KeyError как '2012-12-31 23:59' имеет то же разрешение, что и индекс, и нет столбца с таким именем:
Для всегда иметь однозначный выбор, обрабатывается ли строка как срез или как одиночный выбор, используйте .loc.
In [131]: dft_minute.loc["2011-12-31 23:59"]
Out[131]:
a 1
b 4
Name: 2011-12-31 23:59:00, dtype: int64
Также обратите внимание, что DatetimeIndex разрешение не может быть менее точным, чем день.
In [132]: series_monthly = pd.Series(
.....: [1, 2, 3], pd.DatetimeIndex(["2011-12", "2012-01", "2012-02"])
.....: )
.....:
In [133]: series_monthly.index.resolution
Out[133]: 'day'
In [134]: series_monthly["2011-12"] # returns Series
Out[134]:
2011-12-01 1
dtype: int64
Точное индексирование#
Как обсуждалось в предыдущем разделе, индексация DatetimeIndex с частичной строкой зависит от «точности» периода, другими словами, насколько конкретен интервал по отношению к разрешению индекса. В отличие от этого, индексирование с Timestamp или datetime объектов точна, потому что объекты имеют точное значение. Они также следуют семантике включая обе конечные точки.
Эти Timestamp и datetime объекты имеют точные hours, minutes, и seconds, даже если они не были явно указаны (они 0).
In [135]: dft[datetime.datetime(2013, 1, 1): datetime.datetime(2013, 2, 28)]
Out[135]:
A
2013-01-01 00:00:00 0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00 0.113648
2013-01-01 00:04:00 -1.478427
... ...
2013-02-27 23:56:00 1.197749
2013-02-27 23:57:00 0.720521
2013-02-27 23:58:00 -0.072718
2013-02-27 23:59:00 -0.681192
2013-02-28 00:00:00 -0.557501
[83521 rows x 1 columns]
Без значений по умолчанию.
In [136]: dft[
.....: datetime.datetime(2013, 1, 1, 10, 12, 0): datetime.datetime(
.....: 2013, 2, 28, 10, 12, 0
.....: )
.....: ]
.....:
Out[136]:
A
2013-01-01 10:12:00 0.565375
2013-01-01 10:13:00 0.068184
2013-01-01 10:14:00 0.788871
2013-01-01 10:15:00 -0.280343
2013-01-01 10:16:00 0.931536
... ...
2013-02-28 10:08:00 0.148098
2013-02-28 10:09:00 -0.388138
2013-02-28 10:10:00 0.139348
2013-02-28 10:11:00 0.085288
2013-02-28 10:12:00 0.950146
[83521 rows x 1 columns]
Усечение и расширенная индексация#
A truncate() предоставлена удобная функция, аналогичная срезу. Обратите внимание, что truncate предполагает значение 0 для любого неуказанного компонента даты
в DatetimeIndex в отличие от среза, который возвращает любые частично совпадающие даты:
In [137]: rng2 = pd.date_range("2011-01-01", "2012-01-01", freq="W")
In [138]: ts2 = pd.Series(np.random.randn(len(rng2)), index=rng2)
In [139]: ts2.truncate(before="2011-11", after="2011-12")
Out[139]:
2011-11-06 0.437823
2011-11-13 -0.293083
2011-11-20 -0.059881
2011-11-27 1.252450
Freq: W-SUN, dtype: float64
In [140]: ts2["2011-11":"2011-12"]
Out[140]:
2011-11-06 0.437823
2011-11-13 -0.293083
2011-11-20 -0.059881
2011-11-27 1.252450
2011-12-04 0.046611
2011-12-11 0.059478
2011-12-18 -0.286539
2011-12-25 0.841669
Freq: W-SUN, dtype: float64
Даже сложное нестандартное индексирование, которое нарушает DatetimeIndex регулярность
частоты приведет к DatetimeIndex, хотя частота теряется:
In [141]: ts2.iloc[[0, 2, 6]].index
Out[141]: DatetimeIndex(['2011-01-02', '2011-01-16', '2011-02-13'], dtype='datetime64[ns]', freq=None)
Компоненты времени/даты#
Существует несколько свойств времени/даты, к которым можно получить доступ из Timestamp или коллекцию временных меток, как DatetimeIndex.
Свойство |
Описание |
|---|---|
год |
Год даты и времени |
month |
Месяц даты и времени |
day |
Дни даты и времени |
hour |
Час даты и времени |
минута |
Минуты даты и времени |
второй |
Секунды даты и времени |
микросекунда |
Микросекунды даты и времени |
наносекунда |
Наносекунды datetime |
дата |
Возвращает datetime.date (не содержит информации о часовом поясе) |
время |
Возвращает datetime.time (не содержит информацию о часовом поясе) |
timetz |
Возвращает datetime.time как локальное время с информацией о часовом поясе |
день года |
Порядковый день года |
день_года |
Порядковый день года |
weekofyear |
Порядковый номер недели в году |
неделя |
Порядковый номер недели в году |
dayofweek |
Номер дня недели, где понедельник=0, воскресенье=6 |
day_of_week |
Номер дня недели, где понедельник=0, воскресенье=6 |
день недели |
Номер дня недели, где понедельник=0, воскресенье=6 |
квартал |
Квартал даты: Янв-Мар = 1, Апр-Июн = 2 и т.д. |
days_in_month |
Количество дней в месяце даты и времени |
is_month_start |
Логический индикатор, является ли первый день месяца (определенный частотой) |
is_month_end |
Логический индикатор, является ли последний день месяца (определяется частотой) |
is_quarter_start |
Логический индикатор, является ли первый день квартала (определяется частотой) |
is_quarter_end |
Логический индикатор, является ли последний день квартала (определяется частотой) |
is_year_start |
Логический индикатор, является ли первый день года (определяется частотой) |
is_year_end |
Логический индикатор, является ли последний день года (определённый частотой) |
is_leap_year |
Логический индикатор, указывающий, принадлежит ли дата високосному году |
Кроме того, если у вас есть Series со значениями типа datetime, тогда вы можете
получить доступ к этим свойствам через .dt аксессор, как подробно описано в разделе
о .dt аксессоры.
Вы можете получить компоненты года, недели и дня ISO года из стандарта ISO 8601:
In [142]: idx = pd.date_range(start="2019-12-29", freq="D", periods=4)
In [143]: idx.isocalendar()
Out[143]:
year week day
2019-12-29 2019 52 7
2019-12-30 2020 1 1
2019-12-31 2020 1 2
2020-01-01 2020 1 3
In [144]: idx.to_series().dt.isocalendar()
Out[144]:
year week day
2019-12-29 2019 52 7
2019-12-30 2020 1 1
2019-12-31 2020 1 2
2020-01-01 2020 1 3
Объекты DateOffset#
В предыдущих примерах строки частоты (например, 'D') использовались для указания
частоты, которая определяла:
как даты и время в
DatetimeIndexбыли разделены пробелами при использованииdate_range()частота a
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 именованные аргументы. Доступные смещения дат и связанные строки частоты можно найти ниже:
Смещение даты |
Строка частоты |
Описание |
|---|---|---|
None |
Общий класс смещения, по умолчанию абсолютные 24 часа |
|
|
|
рабочий день (будний день) |
|
пользовательский рабочий день |
|
|
одна неделя, опционально привязанная к дню недели |
|
|
x-й день y-й недели каждого месяца |
|
|
x-й день последней недели каждого месяца |
|
|
конец календарного месяца |
|
|
начало календарного месяца |
|
|
конец рабочего месяца |
|
|
начало бизнес-месяца |
|
|
пользовательский конец бизнес-месяца |
|
|
custom business month begin |
|
|
15-е (или другой day_of_month) и конец календарного месяца |
|
|
15-е (или другой день месяца) и начало календарного месяца |
|
|
конец календарного квартала |
|
|
начало календарного квартала |
|
|
конец бизнес-квартала |
|
|
начало бизнес-квартала |
|
|
розничный (также известный как 52-53 недели) квартал |
|
|
конец календарного года |
|
|
начало календарного года |
|
|
конец бизнес-года |
|
|
начало рабочего года |
|
|
розничный (также 52-53 недели) год |
|
None |
Пасхальный праздник |
|
|
рабочий час |
|
|
пользовательский рабочий час |
|
|
один абсолютный день |
|
|
один час |
|
|
одна минута |
|
|
одна секунда |
|
|
одна миллисекунда |
|
|
одна микросекунда |
|
|
одна наносекунда |
DateOffsets дополнительно имеют rollforward() и rollback()
методы для перемещения даты вперед или назад соответственно к допустимой дате смещения относительно смещения. Например, бизнес-смещения будут переносить даты, которые выпадают на выходные (суббота и воскресенье), вперед на понедельник, поскольку бизнес-смещения работают по будням.
In [153]: ts = pd.Timestamp("2018-01-06 00:00:00")
In [154]: ts.day_name()
Out[154]: 'Saturday'
# BusinessHour's valid offset dates are Monday through Friday
In [155]: offset = pd.offsets.BusinessHour(start="09:00")
# Bring the date to the closest offset date (Monday)
In [156]: offset.rollforward(ts)
Out[156]: Timestamp('2018-01-08 09:00:00')
# Date is brought to the closest offset date first and then the hour is added
In [157]: ts + offset
Out[157]: Timestamp('2018-01-08 10:00:00')
Эти операции сохраняют информацию о времени (час, минута и т.д.) по умолчанию.
Чтобы сбросить время на полночь, используйте normalize() до или после применения операции (в зависимости от того, хотите ли вы включить информацию о времени в операцию).
In [158]: ts = pd.Timestamp("2014-01-01 09:00")
In [159]: day = pd.offsets.Day()
In [160]: day + ts
Out[160]: Timestamp('2014-01-02 09:00:00')
In [161]: (day + ts).normalize()
Out[161]: Timestamp('2014-01-02 00:00:00')
In [162]: ts = pd.Timestamp("2014-01-01 22:00")
In [163]: hour = pd.offsets.Hour()
In [164]: hour + ts
Out[164]: Timestamp('2014-01-01 23:00:00')
In [165]: (hour + ts).normalize()
Out[165]: Timestamp('2014-01-01 00:00:00')
In [166]: (hour + pd.Timestamp("2014-01-01 23:30")).normalize()
Out[166]: Timestamp('2014-01-02 00:00:00')
Параметрические смещения#
Некоторые из смещений могут быть "параметризованы" при создании для получения различного
поведения. Например, Week смещение для генерации еженедельных данных принимает
weekday параметр, который приводит к тому, что сгенерированные даты всегда попадают на определенный день недели:
In [167]: d = datetime.datetime(2008, 8, 18, 9, 0)
In [168]: d
Out[168]: datetime.datetime(2008, 8, 18, 9, 0)
In [169]: d + pd.offsets.Week()
Out[169]: Timestamp('2008-08-25 09:00:00')
In [170]: d + pd.offsets.Week(weekday=4)
Out[170]: Timestamp('2008-08-22 09:00:00')
In [171]: (d + pd.offsets.Week(weekday=4)).weekday()
Out[171]: 4
In [172]: d - pd.offsets.Week()
Out[172]: Timestamp('2008-08-11 09:00:00')
The normalize опция будет действовать для сложения и вычитания.
In [173]: d + pd.offsets.Week(normalize=True)
Out[173]: Timestamp('2008-08-25 00:00:00')
In [174]: d - pd.offsets.Week(normalize=True)
Out[174]: Timestamp('2008-08-11 00:00:00')
Другой пример — параметризация YearEnd с конкретным конечным месяцем:
In [175]: d + pd.offsets.YearEnd()
Out[175]: Timestamp('2008-12-31 09:00:00')
In [176]: d + pd.offsets.YearEnd(month=6)
Out[176]: Timestamp('2009-06-30 09:00:00')
Использование смещений с Series / DatetimeIndex#
Смещения могут использоваться с Series или DatetimeIndex чтобы
применить смещение к каждому элементу.
In [177]: rng = pd.date_range("2012-01-01", "2012-01-03")
In [178]: s = pd.Series(rng)
In [179]: rng
Out[179]: DatetimeIndex(['2012-01-01', '2012-01-02', '2012-01-03'], dtype='datetime64[ns]', freq='D')
In [180]: rng + pd.DateOffset(months=2)
Out[180]: DatetimeIndex(['2012-03-01', '2012-03-02', '2012-03-03'], dtype='datetime64[ns]', freq=None)
In [181]: s + pd.DateOffset(months=2)
Out[181]:
0 2012-03-01
1 2012-03-02
2 2012-03-03
dtype: datetime64[ns]
In [182]: s - pd.DateOffset(months=2)
Out[182]:
0 2011-11-01
1 2011-11-02
2 2011-11-03
dtype: datetime64[ns]
Если класс смещения напрямую соответствует Timedelta (Day, Hour,
Minute, Second, Micro, Milli, Nano) его можно использовать точно так же, как Timedelta - см.
Раздел Timedelta для дополнительных примеров.
In [183]: s - pd.offsets.Day(2)
Out[183]:
0 2011-12-30
1 2011-12-31
2 2012-01-01
dtype: datetime64[ns]
In [184]: td = s - pd.Series(pd.date_range("2011-12-29", "2011-12-31"))
In [185]: td
Out[185]:
0 3 days
1 3 days
2 3 days
dtype: timedelta64[ns]
In [186]: td + pd.offsets.Minute(15)
Out[186]:
0 3 days 00:15:00
1 3 days 00:15:00
2 3 days 00:15:00
dtype: timedelta64[ns]
Обратите внимание, что некоторые смещения (такие как BQuarterEnd) не имеют векторизованной реализации. Их все еще можно использовать, но они могут вычисляться значительно медленнее и покажут PerformanceWarning
In [187]: rng + pd.offsets.BQuarterEnd()
Out[187]: DatetimeIndex(['2012-03-30', '2012-03-30', '2012-03-30'], dtype='datetime64[ns]', freq=None)
Пользовательские рабочие дни#
The CDay или CustomBusinessDay класс предоставляет параметрический
BusinessDay класс, который можно использовать для создания пользовательских рабочих календарей,
учитывающих местные праздники и правила выходных дней.
В качестве интересного примера рассмотрим Египет, где выходными являются пятница и суббота.
In [188]: weekmask_egypt = "Sun Mon Tue Wed Thu"
# They also observe International Workers' Day so let's
# add that for a couple of years
In [189]: holidays = [
.....: "2012-05-01",
.....: datetime.datetime(2013, 5, 1),
.....: np.datetime64("2014-05-01"),
.....: ]
.....:
In [190]: bday_egypt = pd.offsets.CustomBusinessDay(
.....: holidays=holidays,
.....: weekmask=weekmask_egypt,
.....: )
.....:
In [191]: dt = datetime.datetime(2013, 4, 30)
In [192]: dt + 2 * bday_egypt
Out[192]: Timestamp('2013-05-05 00:00:00')
Давайте сопоставим с названиями дней недели:
In [193]: dts = pd.date_range(dt, periods=5, freq=bday_egypt)
In [194]: pd.Series(dts.weekday, dts).map(pd.Series("Mon Tue Wed Thu Fri Sat Sun".split()))
Out[194]:
2013-04-30 Tue
2013-05-02 Thu
2013-05-05 Sun
2013-05-06 Mon
2013-05-07 Tue
Freq: C, dtype: object
Календари праздников могут использоваться для предоставления списка праздников. См. календарь праздников раздел для получения дополнительной информации.
In [195]: from pandas.tseries.holiday import USFederalHolidayCalendar
In [196]: bday_us = pd.offsets.CustomBusinessDay(calendar=USFederalHolidayCalendar())
# Friday before MLK Day
In [197]: dt = datetime.datetime(2014, 1, 17)
# Tuesday after MLK Day (Monday is skipped because it's a holiday)
In [198]: dt + bday_us
Out[198]: Timestamp('2014-01-21 00:00:00')
Месячные смещения, учитывающие определенный календарь праздников, могут быть определены обычным способом.
In [199]: bmth_us = pd.offsets.CustomBusinessMonthBegin(calendar=USFederalHolidayCalendar())
# Skip new years
In [200]: dt = datetime.datetime(2013, 12, 17)
In [201]: dt + bmth_us
Out[201]: Timestamp('2014-01-02 00:00:00')
# Define date index with custom offset
In [202]: pd.date_range(start="20100101", end="20120101", freq=bmth_us)
Out[202]:
DatetimeIndex(['2010-01-04', '2010-02-01', '2010-03-01', '2010-04-01',
'2010-05-03', '2010-06-01', '2010-07-01', '2010-08-02',
'2010-09-01', '2010-10-01', '2010-11-01', '2010-12-01',
'2011-01-03', '2011-02-01', '2011-03-01', '2011-04-01',
'2011-05-02', '2011-06-01', '2011-07-01', '2011-08-01',
'2011-09-01', '2011-10-03', '2011-11-01', '2011-12-01'],
dtype='datetime64[ns]', freq='CBMS')
Примечание
Строка частоты 'C' используется для указания, что используется DateOffset CustomBusinessDay, важно отметить, что поскольку CustomBusinessDay является параметризованным типом, экземпляры CustomBusinessDay могут различаться, и это не обнаруживается по строке частоты 'C'. Поэтому пользователь должен убедиться, что строка частоты 'C' используется последовательно в приложении пользователя.
Рабочий час#
The BusinessHour класс предоставляет представление рабочих часов на BusinessDay,
позволяя использовать конкретные начальное и конечное время.
По умолчанию, BusinessHour использует 9:00 - 17:00 как рабочие часы.
Добавление BusinessHour будет увеличивать Timestamp почасовой частотой.
Если целевой Timestamp выходит за рамки рабочего времени, переместиться на следующий рабочий час, затем увеличить его. Если результат превышает конец рабочего времени, оставшиеся часы добавляются к следующему рабочему дню.
In [203]: bh = pd.offsets.BusinessHour()
In [204]: bh
Out[204]:
# 2014-08-01 is Friday
In [205]: pd.Timestamp("2014-08-01 10:00").weekday()
Out[205]: 4
In [206]: pd.Timestamp("2014-08-01 10:00") + bh
Out[206]: Timestamp('2014-08-01 11:00:00')
# Below example is the same as: pd.Timestamp('2014-08-01 09:00') + bh
In [207]: pd.Timestamp("2014-08-01 08:00") + bh
Out[207]: Timestamp('2014-08-01 10:00:00')
# If the results is on the end time, move to the next business day
In [208]: pd.Timestamp("2014-08-01 16:00") + bh
Out[208]: Timestamp('2014-08-04 09:00:00')
# Remainings are added to the next day
In [209]: pd.Timestamp("2014-08-01 16:30") + bh
Out[209]: Timestamp('2014-08-04 09:30:00')
# Adding 2 business hours
In [210]: pd.Timestamp("2014-08-01 10:00") + pd.offsets.BusinessHour(2)
Out[210]: Timestamp('2014-08-01 12:00:00')
# Subtracting 3 business hours
In [211]: pd.Timestamp("2014-08-01 10:00") + pd.offsets.BusinessHour(-3)
Out[211]: Timestamp('2014-07-31 15:00:00')
Вы также можете указать start и end время по ключевым словам. Аргумент должен быть str с hour:minute представление или datetime.time
экземпляра. Указание секунд, микросекунд и наносекунд в качестве рабочих часов
приводит к ValueError.
In [212]: bh = pd.offsets.BusinessHour(start="11:00", end=datetime.time(20, 0))
In [213]: bh
Out[213]:
In [214]: pd.Timestamp("2014-08-01 13:00") + bh
Out[214]: Timestamp('2014-08-01 14:00:00')
In [215]: pd.Timestamp("2014-08-01 09:00") + bh
Out[215]: Timestamp('2014-08-01 12:00:00')
In [216]: pd.Timestamp("2014-08-01 18:00") + bh
Out[216]: Timestamp('2014-08-01 19:00:00')
Передача start время позже end представляет полночный рабочий час. В этом случае рабочий час превышает полночь и перекрывает следующий день. Допустимые рабочие часы различаются в зависимости от того, начались ли они с допустимого BusinessDay.
In [217]: bh = pd.offsets.BusinessHour(start="17:00", end="09:00")
In [218]: bh
Out[218]:
In [219]: pd.Timestamp("2014-08-01 17:00") + bh
Out[219]: Timestamp('2014-08-01 18:00:00')
In [220]: pd.Timestamp("2014-08-01 23:00") + bh
Out[220]: Timestamp('2014-08-02 00:00:00')
# Although 2014-08-02 is Saturday,
# it is valid because it starts from 08-01 (Friday).
In [221]: pd.Timestamp("2014-08-02 04:00") + bh
Out[221]: Timestamp('2014-08-02 05:00:00')
# Although 2014-08-04 is Monday,
# it is out of business hours because it starts from 08-03 (Sunday).
In [222]: pd.Timestamp("2014-08-04 04:00") + bh
Out[222]: Timestamp('2014-08-04 18:00:00')
Применение BusinessHour.rollforward и rollback до времени вне рабочих часов приводит к
началу следующего рабочего часа или концу предыдущего дня. В отличие от других смещений, BusinessHour.rollforward
может выводить результаты, отличные от apply по определению.
Это связано с тем, что конец рабочего дня одного дня равен началу рабочего дня следующего дня. Например,
при стандартных рабочих часах (9:00 - 17:00) нет разрыва (0 минут) между 2014-08-01 17:00 и
2014-08-04 09:00.
# This adjusts a Timestamp to business hour edge
In [223]: pd.offsets.BusinessHour().rollback(pd.Timestamp("2014-08-02 15:00"))
Out[223]: Timestamp('2014-08-01 17:00:00')
In [224]: pd.offsets.BusinessHour().rollforward(pd.Timestamp("2014-08-02 15:00"))
Out[224]: Timestamp('2014-08-04 09:00:00')
# It is the same as BusinessHour() + pd.Timestamp('2014-08-01 17:00').
# And it is the same as BusinessHour() + pd.Timestamp('2014-08-04 09:00')
In [225]: pd.offsets.BusinessHour() + pd.Timestamp("2014-08-02 15:00")
Out[225]: Timestamp('2014-08-04 10:00:00')
# BusinessDay results (for reference)
In [226]: pd.offsets.BusinessHour().rollforward(pd.Timestamp("2014-08-02"))
Out[226]: Timestamp('2014-08-04 09:00:00')
# It is the same as BusinessDay() + pd.Timestamp('2014-08-01')
# The result is the same as rollworward because BusinessDay never overlap.
In [227]: pd.offsets.BusinessHour() + pd.Timestamp("2014-08-02")
Out[227]: Timestamp('2014-08-04 10:00:00')
BusinessHour считает субботу и воскресенье праздничными днями. Для использования произвольных праздников можно использовать CustomBusinessHour смещение, как объясняется в следующем подразделе.
Пользовательский рабочий час#
The CustomBusinessHour представляет собой смесь BusinessHour и CustomBusinessDay который позволяет указать произвольные праздничные дни. CustomBusinessHour работает так же, как BusinessHour за исключением того, что пропускает указанные пользовательские праздники.
In [228]: from pandas.tseries.holiday import USFederalHolidayCalendar
In [229]: bhour_us = pd.offsets.CustomBusinessHour(calendar=USFederalHolidayCalendar())
# Friday before MLK Day
In [230]: dt = datetime.datetime(2014, 1, 17, 15)
In [231]: dt + bhour_us
Out[231]: Timestamp('2014-01-17 16:00:00')
# Tuesday after MLK Day (Monday is skipped because it's a holiday)
In [232]: dt + bhour_us * 2
Out[232]: Timestamp('2014-01-21 09:00:00')
Вы можете использовать аргументы ключевых слов, поддерживаемые либо BusinessHour и CustomBusinessDay.
In [233]: bhour_mon = pd.offsets.CustomBusinessHour(start="10:00", weekmask="Tue Wed Thu Fri")
# Monday is skipped because it's a holiday, business hour starts from 10:00
In [234]: dt + bhour_mon * 2
Out[234]: Timestamp('2014-01-21 10:00:00')
Псевдонимы смещений#
Ряд строковых псевдонимов задан для полезных общих частот временных рядов. Мы будем ссылаться на эти псевдонимы как псевдонимы смещений.
Псевдоним |
Описание |
|---|---|
B |
частота рабочих дней |
C |
частота пользовательского рабочего дня |
D |
частота календарного дня |
W |
еженедельная частота |
ME |
месячная конечная частота |
SME |
полумесячная частота (15-е число и конец месяца) |
BME |
частота конца рабочего месяца |
CBME |
пользовательская частота конца рабочего месяца |
MS |
частотность начала месяца |
SMS |
полумесячная начальная частота (1-е и 15-е числа) |
BMS |
частота начала бизнес-месяца |
CBMS |
пользовательская частота начала бизнес-месяца |
QE |
квартальная конечная частота |
BQE |
частота окончания бизнес-квартала |
QS |
квартальная начальная частота |
BQS |
частота начала бизнес-квартала |
YE |
годовая частота окончания |
ПОКА |
частота окончания бизнес-года |
YS |
годовая начальная частота |
BYS |
частота начала бизнес-года |
h |
ежечасная частота |
bh |
частота рабочего часа |
cbh |
пользовательская частота рабочего часа |
min |
ежеминутная частота |
s |
частота секунды |
мс |
миллисекунды |
нас |
микросекунды |
нс |
наносекунды |
Устарело с версии 2.2.0: Псевдонимы H, BH, CBH, T, S, L, U, и N
устарели в пользу псевдонимов h, bh, cbh,
min, s, ms, us, и ns.
Примечание
При использовании псевдонимов смещений выше следует отметить, что функции такие как
date_range(),bdate_range()будет возвращать только временные метки, находящиеся в интервале, определенномstart_dateиend_date. Еслиstart_dateне соответствует частоте, возвращаемые временные метки начнутся со следующей допустимой временной метки, аналогично дляend_date, возвращаемые временные метки остановятся на предыдущей допустимой временной метке.
Например, для смещения MS, если start_date не является первым
числом месяца, возвращаемые метки времени будут начинаться с первого дня
следующего месяца. Если end_date не является первым днем месяца, последний возвращенный временной меткой будет первым днем соответствующего месяца.
In [235]: dates_lst_1 = pd.date_range("2020-01-06", "2020-04-03", freq="MS")
In [236]: dates_lst_1
Out[236]: DatetimeIndex(['2020-02-01', '2020-03-01', '2020-04-01'], dtype='datetime64[ns]', freq='MS')
In [237]: dates_lst_2 = pd.date_range("2020-01-01", "2020-04-01", freq="MS")
In [238]: dates_lst_2
Out[238]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01', '2020-04-01'], dtype='datetime64[ns]', freq='MS')
Мы можем видеть в примере выше date_range() и
bdate_range() будет возвращать только допустимые временные метки между
start_date и end_date. Если это недопустимые временные метки для заданной частоты, произойдёт переход к следующему значению для start_date
(соответственно предыдущий для end_date)
Псевдонимы периодов#
Ряд строковых псевдонимов задан для полезных общих частот временных рядов. Мы будем ссылаться на эти псевдонимы как псевдонимы периода.
Псевдоним |
Описание |
|---|---|
B |
частота рабочих дней |
D |
частота календарного дня |
W |
еженедельная частота |
M |
ежемесячная частота |
Q |
квартальная частота |
Y |
годовая частота |
h |
ежечасная частота |
min |
ежеминутная частота |
s |
частота секунды |
мс |
миллисекунды |
нас |
микросекунды |
нс |
наносекунды |
Устарело с версии 2.2.0: Псевдонимы A, H, T, S, L, U, и N устарели в пользу псевдонимов
Y, h, min, s, ms, us, и ns.
Объединение псевдонимов#
Как мы видели ранее, псевдоним и экземпляр смещения взаимозаменяемы в большинстве функций:
In [239]: pd.date_range(start, periods=5, freq="B")
Out[239]:
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
'2011-01-07'],
dtype='datetime64[ns]', freq='B')
In [240]: pd.date_range(start, periods=5, freq=pd.offsets.BDay())
Out[240]:
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
'2011-01-07'],
dtype='datetime64[ns]', freq='B')
Вы можете комбинировать дневные и внутридневные смещения:
In [241]: pd.date_range(start, periods=10, freq="2h20min")
Out[241]:
DatetimeIndex(['2011-01-01 00:00:00', '2011-01-01 02:20:00',
'2011-01-01 04:40:00', '2011-01-01 07:00:00',
'2011-01-01 09:20:00', '2011-01-01 11:40:00',
'2011-01-01 14:00:00', '2011-01-01 16:20:00',
'2011-01-01 18:40:00', '2011-01-01 21:00:00'],
dtype='datetime64[ns]', freq='140min')
In [242]: pd.date_range(start, periods=10, freq="1D10us")
Out[242]:
DatetimeIndex([ '2011-01-01 00:00:00', '2011-01-02 00:00:00.000010',
'2011-01-03 00:00:00.000020', '2011-01-04 00:00:00.000030',
'2011-01-05 00:00:00.000040', '2011-01-06 00:00:00.000050',
'2011-01-07 00:00:00.000060', '2011-01-08 00:00:00.000070',
'2011-01-09 00:00:00.000080', '2011-01-10 00:00:00.000090'],
dtype='datetime64[ns]', freq='86400000010us')
Закреплённые смещения#
Для некоторых частот вы можете указать суффикс привязки:
Псевдоним |
Описание |
|---|---|
W-SUN |
еженедельная частота (воскресенья). То же, что и 'W' |
W-MON |
еженедельная частота (понедельники) |
W-TUE |
еженедельная частота (по вторникам) |
W-WED |
еженедельная частота (по средам) |
W-THU |
еженедельная частота (по четвергам) |
W-FRI |
еженедельная частота (пятницы) |
W-SAT |
еженедельная частота (субботы) |
(B)Q(E)(S)-DEC |
квартальная частота, год заканчивается в декабре. То же, что и ‘QE’ |
(B)Q(E)(S)-JAN |
квартальная частота, год заканчивается в январе |
(B)Q(E)(S)-FEB |
квартальная частота, год заканчивается в феврале |
(B)Q(E)(S)-MAR |
квартальная частота, год заканчивается в марте |
(B)Q(E)(S)-APR |
квартальная частота, год заканчивается в апреле |
(B)Q(E)(S)-MAY |
квартальная частота, год заканчивается в мае |
(B)Q(E)(S)-JUN |
квартальная частота, год заканчивается в июне |
(B)Q(E)(S)-JUL |
квартальная частота, год заканчивается в июле |
(B)Q(E)(S)-AUG |
квартальная частота, год заканчивается в августе |
(B)Q(E)(S)-SEP |
квартальная частота, год заканчивается в сентябре |
(B)Q(E)(S)-OCT |
квартальная частота, год заканчивается в октябре |
(B)Q(E)(S)-NOV |
квартальная частота, год заканчивается в ноябре |
(B)Y(E)(S)-DEC |
годовая частота, привязанная к концу декабря. То же, что и 'YE' |
(B)Y(E)(S)-JAN |
годовая частота, привязанная к концу января |
(B)Y(E)(S)-FEB |
годовая частота, закрепленная в конце февраля |
(B)Y(E)(S)-MAR |
годовая частота, привязанная к концу марта |
(B)Y(E)(S)-APR |
годовая частота, закреплённая в конце апреля |
(B)Y(E)(S)-MAY |
годовая частота, привязанная к концу мая |
(B)Y(E)(S)-JUN |
годовая частота, закрепленная в конце июня |
(B)Y(E)(S)-JUL |
годовая частота, закреплённая в конце июля |
(B)Y(E)(S)-AUG |
годовая частота, закрепленная в конце августа |
(B)Y(E)(S)-SEP |
годовая частота, привязанная к концу сентября |
(B)Y(E)(S)-OCT |
годовая частота, закрепленная в конце октября |
(B)Y(E)(S)-NOV |
годовая частота, закрепленная в конце ноября |
Их можно использовать в качестве аргументов для date_range, bdate_range, конструкторы для DatetimeIndex, а также различные другие функции, связанные с временными рядами
в pandas.
Семантика закреплённого смещения#
Для тех смещений, которые привязаны к началу или концу определённой
частоты (MonthEnd, MonthBegin, WeekEnd, и т.д.), применяются следующие правила для скользящего вперёд и назад.
Когда n не равно 0, если заданная дата не находится на точке привязки, она привязывается к следующей (предыдущей)
точке привязки и перемещается |n|-1 дополнительные шаги вперед или назад.
In [243]: pd.Timestamp("2014-01-02") + pd.offsets.MonthBegin(n=1)
Out[243]: Timestamp('2014-02-01 00:00:00')
In [244]: pd.Timestamp("2014-01-02") + pd.offsets.MonthEnd(n=1)
Out[244]: Timestamp('2014-01-31 00:00:00')
In [245]: pd.Timestamp("2014-01-02") - pd.offsets.MonthBegin(n=1)
Out[245]: Timestamp('2014-01-01 00:00:00')
In [246]: pd.Timestamp("2014-01-02") - pd.offsets.MonthEnd(n=1)
Out[246]: Timestamp('2013-12-31 00:00:00')
In [247]: pd.Timestamp("2014-01-02") + pd.offsets.MonthBegin(n=4)
Out[247]: Timestamp('2014-05-01 00:00:00')
In [248]: pd.Timestamp("2014-01-02") - pd.offsets.MonthBegin(n=4)
Out[248]: Timestamp('2013-10-01 00:00:00')
Если заданная дата является на точке привязки, он перемещается |n| указывает вперед
или назад.
In [249]: pd.Timestamp("2014-01-01") + pd.offsets.MonthBegin(n=1)
Out[249]: Timestamp('2014-02-01 00:00:00')
In [250]: pd.Timestamp("2014-01-31") + pd.offsets.MonthEnd(n=1)
Out[250]: Timestamp('2014-02-28 00:00:00')
In [251]: pd.Timestamp("2014-01-01") - pd.offsets.MonthBegin(n=1)
Out[251]: Timestamp('2013-12-01 00:00:00')
In [252]: pd.Timestamp("2014-01-31") - pd.offsets.MonthEnd(n=1)
Out[252]: Timestamp('2013-12-31 00:00:00')
In [253]: pd.Timestamp("2014-01-01") + pd.offsets.MonthBegin(n=4)
Out[253]: Timestamp('2014-05-01 00:00:00')
In [254]: pd.Timestamp("2014-01-31") - pd.offsets.MonthBegin(n=4)
Out[254]: Timestamp('2013-10-01 00:00:00')
Для случая, когда n=0дата не перемещается, если находится на точке привязки, в противном случае она перекатывается вперед к следующей точке привязки.
In [255]: pd.Timestamp("2014-01-02") + pd.offsets.MonthBegin(n=0)
Out[255]: Timestamp('2014-02-01 00:00:00')
In [256]: pd.Timestamp("2014-01-02") + pd.offsets.MonthEnd(n=0)
Out[256]: Timestamp('2014-01-31 00:00:00')
In [257]: pd.Timestamp("2014-01-01") + pd.offsets.MonthBegin(n=0)
Out[257]: Timestamp('2014-01-01 00:00:00')
In [258]: pd.Timestamp("2014-01-31") + pd.offsets.MonthEnd(n=0)
Out[258]: Timestamp('2014-01-31 00:00:00')
Праздники / календари праздников#
Праздники и календари предоставляют простой способ определения правил праздников для использования с CustomBusinessDay или в другом анализе, требующем предопределённого
набора праздников. The AbstractHolidayCalendar класс предоставляет все необходимые
методы для возврата списка праздников и только rules должны быть определены
в конкретном классе календаря праздников. Кроме того, start_date и end_date
атрибуты класса определяют, в каком диапазоне дат генерируются праздники. Они
должны быть переопределены на AbstractHolidayCalendar класс, чтобы диапазон применялся ко всем подклассам календаря. USFederalHolidayCalendar является
единственным существующим календарём и в основном служит примером для разработки
других календарей.
Для праздников, которые выпадают на фиксированные даты (например, День памяти в США или 4 июля), правило соблюдения определяет, когда этот праздник отмечается, если он выпадает на выходные или другой нерабочий день. Определённые правила соблюдения:
Правило |
Описание |
|---|---|
nearest_workday |
переместить субботу на пятницу, а воскресенье на понедельник |
sunday_to_monday |
переместить воскресенье на следующий понедельник |
next_monday_or_tuesday |
переместить субботу на понедельник, а воскресенье/понедельник на вторник |
предыдущая_пятница |
переместить субботу и воскресенье на предыдущую пятницу” |
next_monday |
переместить субботу и воскресенье на следующий понедельник |
Пример того, как определяются праздники и календари праздников:
In [259]: from pandas.tseries.holiday import (
.....: Holiday,
.....: USMemorialDay,
.....: AbstractHolidayCalendar,
.....: nearest_workday,
.....: MO,
.....: )
.....:
In [260]: class ExampleCalendar(AbstractHolidayCalendar):
.....: rules = [
.....: USMemorialDay,
.....: Holiday("July 4th", month=7, day=4, observance=nearest_workday),
.....: Holiday(
.....: "Columbus Day",
.....: month=10,
.....: day=1,
.....: offset=pd.DateOffset(weekday=MO(2)),
.....: ),
.....: ]
.....:
In [261]: cal = ExampleCalendar()
In [262]: cal.holidays(datetime.datetime(2012, 1, 1), datetime.datetime(2012, 12, 31))
Out[262]: DatetimeIndex(['2012-05-28', '2012-07-04', '2012-10-08'], dtype='datetime64[ns]', freq=None)
- подсказка:
weekday=MO(2) такой же, как 2 * Week(weekday=2)
Используя этот календарь, создание индекса или выполнение арифметики смещений пропускает выходные и праздничные дни (например, День памяти/4 июля). Например, ниже определяется пользовательское смещение рабочего дня с использованием ExampleCalendar. Как и любой другой offset,
он может быть использован для создания DatetimeIndex или добавлен к datetime
или Timestamp объекты.
In [263]: pd.date_range(
.....: start="7/1/2012", end="7/10/2012", freq=pd.offsets.CDay(calendar=cal)
.....: ).to_pydatetime()
.....:
Out[263]:
array([datetime.datetime(2012, 7, 2, 0, 0),
datetime.datetime(2012, 7, 3, 0, 0),
datetime.datetime(2012, 7, 5, 0, 0),
datetime.datetime(2012, 7, 6, 0, 0),
datetime.datetime(2012, 7, 9, 0, 0),
datetime.datetime(2012, 7, 10, 0, 0)], dtype=object)
In [264]: offset = pd.offsets.CustomBusinessDay(calendar=cal)
In [265]: datetime.datetime(2012, 5, 25) + offset
Out[265]: Timestamp('2012-05-29 00:00:00')
In [266]: datetime.datetime(2012, 7, 3) + offset
Out[266]: Timestamp('2012-07-05 00:00:00')
In [267]: datetime.datetime(2012, 7, 3) + 2 * offset
Out[267]: Timestamp('2012-07-06 00:00:00')
In [268]: datetime.datetime(2012, 7, 6) + offset
Out[268]: Timestamp('2012-07-09 00:00:00')
Диапазоны определяются start_date и end_date атрибуты класса AbstractHolidayCalendar. Значения по умолчанию показаны ниже.
In [269]: AbstractHolidayCalendar.start_date
Out[269]: Timestamp('1970-01-01 00:00:00')
In [270]: AbstractHolidayCalendar.end_date
Out[270]: Timestamp('2200-12-31 00:00:00')
Эти даты могут быть перезаписаны путём установки атрибутов как datetime/Timestamp/string.
In [271]: AbstractHolidayCalendar.start_date = datetime.datetime(2012, 1, 1)
In [272]: AbstractHolidayCalendar.end_date = datetime.datetime(2012, 12, 31)
In [273]: cal.holidays()
Out[273]: DatetimeIndex(['2012-05-28', '2012-07-04', '2012-10-08'], dtype='datetime64[ns]', freq=None)
Каждый календарный класс доступен по имени с помощью get_calendar функция
которая возвращает экземпляр класса праздника. Любой импортированный класс календаря будет
автоматически доступен через эту функцию. Также, HolidayCalendarFactory
предоставляет простой интерфейс для создания календарей, которые являются комбинациями календарей
или календарей с дополнительными правилами.
In [274]: from pandas.tseries.holiday import get_calendar, HolidayCalendarFactory, USLaborDay
In [275]: cal = get_calendar("ExampleCalendar")
In [276]: cal.rules
Out[276]:
[Holiday: Memorial Day (month=5, day=31, offset=),
Holiday: July 4th (month=7, day=4, observance=),
Holiday: Columbus Day (month=10, day=1, offset=)]
In [277]: new_cal = HolidayCalendarFactory("NewExampleCalendar", cal, USLaborDay)
In [278]: new_cal.rules
Out[278]:
[Holiday: Labor Day (month=9, day=1, offset=),
Holiday: Memorial Day (month=5, day=31, offset=),
Holiday: July 4th (month=7, day=4, observance=),
Holiday: Columbus Day (month=10, day=1, offset=)]
Ресемплинг#
pandas имеет простую, мощную и эффективную функциональность для выполнения операций ресемплинга при преобразовании частоты (например, преобразование данных с секундной частотой в данные с 5-минутной частотой). Это очень распространено, но не ограничивается финансовыми приложениями.
resample() это группировка по времени, за которой следует метод редукции
в каждой из ее групп. См. некоторые примеры из кулинарной книги для
некоторых продвинутых стратегий.
The resample() метод может использоваться напрямую из DataFrameGroupBy объектов,
см. документация groupby.
Основы#
In [290]: rng = pd.date_range("1/1/2012", periods=100, freq="s")
In [291]: ts = pd.Series(np.random.randint(0, 500, len(rng)), index=rng)
In [292]: ts.resample("5Min").sum()
Out[292]:
2012-01-01 25103
Freq: 5min, dtype: int64
The resample функция очень гибкая и позволяет указать множество
различных параметров для управления преобразованием частоты и операцией
передискретизации.
Любой встроенный метод, доступный через GroupBy доступен как
метод возвращаемого объекта, включая sum, mean, std, sem,
max, min, median, first, last, ohlc:
In [293]: ts.resample("5Min").mean()
Out[293]:
2012-01-01 251.03
Freq: 5min, dtype: float64
In [294]: ts.resample("5Min").ohlc()
Out[294]:
open high low close
2012-01-01 308 460 9 205
In [295]: ts.resample("5Min").max()
Out[295]:
2012-01-01 460
Freq: 5min, dtype: int64
Для понижающей дискретизации, closed может быть установлено в 'left' или 'right' для указания, какой
конец интервала является закрытым:
In [296]: ts.resample("5Min", closed="right").mean()
Out[296]:
2011-12-31 23:55:00 308.000000
2012-01-01 00:00:00 250.454545
Freq: 5min, dtype: float64
In [297]: ts.resample("5Min", closed="left").mean()
Out[297]:
2012-01-01 251.03
Freq: 5min, dtype: float64
Параметры, такие как label используются для манипуляции результирующими метками.
label определяет, помечен ли результат началом или концом интервала.
In [298]: ts.resample("5Min").mean() # by default label='left'
Out[298]:
2012-01-01 251.03
Freq: 5min, dtype: float64
In [299]: ts.resample("5Min", label="left").mean()
Out[299]:
2012-01-01 251.03
Freq: 5min, dtype: float64
Предупреждение
Значения по умолчанию для label и closed является 'left' для всех частотных смещений, кроме 'ME', 'YE', 'QE', 'BME', 'BYE', 'BQE' и 'W', которые по умолчанию используют 'right'.
Это может непреднамеренно привести к опережающему просмотру, когда значение для более позднего времени переносится на предыдущее время, как в следующем примере с BusinessDay частота:
In [300]: s = pd.date_range("2000-01-01", "2000-01-05").to_series()
In [301]: s.iloc[2] = pd.NaT
In [302]: s.dt.day_name()
Out[302]:
2000-01-01 Saturday
2000-01-02 Sunday
2000-01-03 NaN
2000-01-04 Tuesday
2000-01-05 Wednesday
Freq: D, dtype: object
# default: label='left', closed='left'
In [303]: s.resample("B").last().dt.day_name()
Out[303]:
1999-12-31 Sunday
2000-01-03 NaN
2000-01-04 Tuesday
2000-01-05 Wednesday
Freq: B, dtype: object
Обратите внимание, как значение для воскресенья было оттянуто к предыдущей пятнице. Чтобы получить поведение, при котором значение для воскресенья переносится на понедельник, используйте вместо этого
In [304]: s.resample("B", label="right", closed="right").last().dt.day_name()
Out[304]:
2000-01-03 Sunday
2000-01-04 Tuesday
2000-01-05 Wednesday
2000-01-06 NaN
Freq: B, dtype: object
The axis параметр может быть установлен в 0 или 1 и позволяет передискретизировать указанную ось для DataFrame.
kind может быть установлено в 'timestamp' или 'period' для преобразования результирующего индекса в/из представлений временной метки и временного промежутка. По умолчанию resample
сохраняет входное представление.
convention может быть установлено в 'start' или 'end' при ресемплинге периодических данных
(подробности ниже). Определяет, как низкочастотные периоды преобразуются в высокочастотные.
Повышение частоты дискретизации#
Для повышающей дискретизации можно указать способ повышения и limit параметр для интерполяции по созданным пробелам:
# from secondly to every 250 milliseconds
In [305]: ts[:2].resample("250ms").asfreq()
Out[305]:
2012-01-01 00:00:00.000 308.0
2012-01-01 00:00:00.250 NaN
2012-01-01 00:00:00.500 NaN
2012-01-01 00:00:00.750 NaN
2012-01-01 00:00:01.000 204.0
Freq: 250ms, dtype: float64
In [306]: ts[:2].resample("250ms").ffill()
Out[306]:
2012-01-01 00:00:00.000 308
2012-01-01 00:00:00.250 308
2012-01-01 00:00:00.500 308
2012-01-01 00:00:00.750 308
2012-01-01 00:00:01.000 204
Freq: 250ms, dtype: int64
In [307]: ts[:2].resample("250ms").ffill(limit=2)
Out[307]:
2012-01-01 00:00:00.000 308.0
2012-01-01 00:00:00.250 308.0
2012-01-01 00:00:00.500 308.0
2012-01-01 00:00:00.750 NaN
2012-01-01 00:00:01.000 204.0
Freq: 250ms, dtype: float64
Разреженное передискретизирование#
Разреженные временные ряды — это те, где у вас гораздо меньше точек относительно
количества времени, которое вы хотите передискретизировать. Наивная передискретизация разреженного
ряда может потенциально генерировать много промежуточных значений. Когда вы не хотите
использовать метод для заполнения этих значений, например, fill_method является None, тогда
промежуточные значения будут заполнены NaN.
Поскольку resample является группировкой по времени, следующий метод эффективно передискретизирует только группы, которые не все NaN.
In [308]: rng = pd.date_range("2014-1-1", periods=100, freq="D") + pd.Timedelta("1s")
In [309]: ts = pd.Series(range(100), index=rng)
Если мы хотим передискретизировать до полного диапазона ряда:
In [310]: ts.resample("3min").sum()
Out[310]:
2014-01-01 00:00:00 0
2014-01-01 00:03:00 0
2014-01-01 00:06:00 0
2014-01-01 00:09:00 0
2014-01-01 00:12:00 0
..
2014-04-09 23:48:00 0
2014-04-09 23:51:00 0
2014-04-09 23:54:00 0
2014-04-09 23:57:00 0
2014-04-10 00:00:00 99
Freq: 3min, Length: 47521, dtype: int64
Вместо этого мы можем передискретизировать только те группы, где у нас есть точки, следующим образом:
In [311]: from functools import partial
In [312]: from pandas.tseries.frequencies import to_offset
In [313]: def round(t, freq):
.....: freq = to_offset(freq)
.....: td = pd.Timedelta(freq)
.....: return pd.Timestamp((t.value // td.value) * td.value)
.....:
In [314]: ts.groupby(partial(round, freq="3min")).sum()
Out[314]:
2014-01-01 0
2014-01-02 1
2014-01-03 2
2014-01-04 3
2014-01-05 4
..
2014-04-06 95
2014-04-07 96
2014-04-08 97
2014-04-09 98
2014-04-10 99
Length: 100, dtype: int64
Агрегация#
The resample() метод возвращает pandas.api.typing.Resampler экземпляр. Аналогично
API агрегации, API groupby,
и window API, a Resampler может быть выборочно передискретизирован.
Передискретизация DataFrame, по умолчанию действие будет применяться ко всем столбцам с той же функцией.
In [315]: df = pd.DataFrame(
.....: np.random.randn(1000, 3),
.....: index=pd.date_range("1/1/2012", freq="s", periods=1000),
.....: columns=["A", "B", "C"],
.....: )
.....:
In [316]: r = df.resample("3min")
In [317]: r.mean()
Out[317]:
A B C
2012-01-01 00:00:00 -0.033823 -0.121514 -0.081447
2012-01-01 00:03:00 0.056909 0.146731 -0.024320
2012-01-01 00:06:00 -0.058837 0.047046 -0.052021
2012-01-01 00:09:00 0.063123 -0.026158 -0.066533
2012-01-01 00:12:00 0.186340 -0.003144 0.074752
2012-01-01 00:15:00 -0.085954 -0.016287 -0.050046
Мы можем выбрать конкретный столбец или столбцы, используя стандартный getitem.
In [318]: r["A"].mean()
Out[318]:
2012-01-01 00:00:00 -0.033823
2012-01-01 00:03:00 0.056909
2012-01-01 00:06:00 -0.058837
2012-01-01 00:09:00 0.063123
2012-01-01 00:12:00 0.186340
2012-01-01 00:15:00 -0.085954
Freq: 3min, Name: A, dtype: float64
In [319]: r[["A", "B"]].mean()
Out[319]:
A B
2012-01-01 00:00:00 -0.033823 -0.121514
2012-01-01 00:03:00 0.056909 0.146731
2012-01-01 00:06:00 -0.058837 0.047046
2012-01-01 00:09:00 0.063123 -0.026158
2012-01-01 00:12:00 0.186340 -0.003144
2012-01-01 00:15:00 -0.085954 -0.016287
Вы можете передать список или словарь функций для агрегации, выводя DataFrame:
In [320]: r["A"].agg(["sum", "mean", "std"])
Out[320]:
sum mean std
2012-01-01 00:00:00 -6.088060 -0.033823 1.043263
2012-01-01 00:03:00 10.243678 0.056909 1.058534
2012-01-01 00:06:00 -10.590584 -0.058837 0.949264
2012-01-01 00:09:00 11.362228 0.063123 1.028096
2012-01-01 00:12:00 33.541257 0.186340 0.884586
2012-01-01 00:15:00 -8.595393 -0.085954 1.035476
На передискретизированном DataFrame, вы можете передать список функций для применения к каждому
столбцу, что создает агрегированный результат с иерархическим индексом:
In [321]: r.agg(["sum", "mean"])
Out[321]:
A ... C
sum mean ... sum mean
2012-01-01 00:00:00 -6.088060 -0.033823 ... -14.660515 -0.081447
2012-01-01 00:03:00 10.243678 0.056909 ... -4.377642 -0.024320
2012-01-01 00:06:00 -10.590584 -0.058837 ... -9.363825 -0.052021
2012-01-01 00:09:00 11.362228 0.063123 ... -11.975895 -0.066533
2012-01-01 00:12:00 33.541257 0.186340 ... 13.455299 0.074752
2012-01-01 00:15:00 -8.595393 -0.085954 ... -5.004580 -0.050046
[6 rows x 6 columns]
Передавая словарь в aggregate вы можете применить другую агрегацию к столбцам DataFrame:
In [322]: r.agg({"A": "sum", "B": lambda x: np.std(x, ddof=1)})
Out[322]:
A B
2012-01-01 00:00:00 -6.088060 1.001294
2012-01-01 00:03:00 10.243678 1.074597
2012-01-01 00:06:00 -10.590584 0.987309
2012-01-01 00:09:00 11.362228 0.944953
2012-01-01 00:12:00 33.541257 1.095025
2012-01-01 00:15:00 -8.595393 1.035312
Имена функций также могут быть строками. Чтобы строка была допустимой, она должна быть реализована в передискретизированном объекте:
In [323]: r.agg({"A": "sum", "B": "std"})
Out[323]:
A B
2012-01-01 00:00:00 -6.088060 1.001294
2012-01-01 00:03:00 10.243678 1.074597
2012-01-01 00:06:00 -10.590584 0.987309
2012-01-01 00:09:00 11.362228 0.944953
2012-01-01 00:12:00 33.541257 1.095025
2012-01-01 00:15:00 -8.595393 1.035312
Кроме того, вы также можете указать несколько агрегационных функций для каждого столбца отдельно.
In [324]: r.agg({"A": ["sum", "std"], "B": ["mean", "std"]})
Out[324]:
A B
sum std mean std
2012-01-01 00:00:00 -6.088060 1.043263 -0.121514 1.001294
2012-01-01 00:03:00 10.243678 1.058534 0.146731 1.074597
2012-01-01 00:06:00 -10.590584 0.949264 0.047046 0.987309
2012-01-01 00:09:00 11.362228 1.028096 -0.026158 0.944953
2012-01-01 00:12:00 33.541257 0.884586 -0.003144 1.095025
2012-01-01 00:15:00 -8.595393 1.035476 -0.016287 1.035312
Если DataFrame не имеет индекса типа datetime, но вместо этого вы хотите
пересэмплировать на основе столбца типа datetime во фрейме, его можно передать в
on ключевое слово.
In [325]: df = pd.DataFrame(
.....: {"date": pd.date_range("2015-01-01", freq="W", periods=5), "a": np.arange(5)},
.....: index=pd.MultiIndex.from_arrays(
.....: [[1, 2, 3, 4, 5], pd.date_range("2015-01-01", freq="W", periods=5)],
.....: names=["v", "d"],
.....: ),
.....: )
.....:
In [326]: df
Out[326]:
date a
v d
1 2015-01-04 2015-01-04 0
2 2015-01-11 2015-01-11 1
3 2015-01-18 2015-01-18 2
4 2015-01-25 2015-01-25 3
5 2015-02-01 2015-02-01 4
In [327]: df.resample("ME", on="date")[["a"]].sum()
Out[327]:
a
date
2015-01-31 6
2015-02-28 4
Аналогично, если вы вместо этого хотите передискретизировать по уровню, подобному дате и времени MultiIndex, его имя или местоположение может быть передано в
level ключевое слово.
In [328]: df.resample("ME", level="d")[["a"]].sum()
Out[328]:
a
d
2015-01-31 6
2015-02-28 4
Итерация по группам#
С Resampler объект в руках, итерация по сгруппированным данным очень естественна и работает аналогично itertools.groupby():
In [329]: small = pd.Series(
.....: range(6),
.....: index=pd.to_datetime(
.....: [
.....: "2017-01-01T00:00:00",
.....: "2017-01-01T00:30:00",
.....: "2017-01-01T00:31:00",
.....: "2017-01-01T01:00:00",
.....: "2017-01-01T03:00:00",
.....: "2017-01-01T03:05:00",
.....: ]
.....: ),
.....: )
.....:
In [330]: resampled = small.resample("h")
In [331]: for name, group in resampled:
.....: print("Group: ", name)
.....: print("-" * 27)
.....: print(group, end="\n\n")
.....:
Group: 2017-01-01 00:00:00
---------------------------
2017-01-01 00:00:00 0
2017-01-01 00:30:00 1
2017-01-01 00:31:00 2
dtype: int64
Group: 2017-01-01 01:00:00
---------------------------
2017-01-01 01:00:00 3
dtype: int64
Group: 2017-01-01 02:00:00
---------------------------
Series([], dtype: int64)
Group: 2017-01-01 03:00:00
---------------------------
2017-01-01 03:00:00 4
2017-01-01 03:05:00 5
dtype: int64
См. Итерация по группам или Resampler.__iter__ подробнее.
Используйте origin или offset для настройки начала бинов#
Бины группировки корректируются на основе начала дня начальной точки временного ряда. Это хорошо работает с частотами, кратными дню (например, 30D) или которые делят день равномерно (например 90s или 1min). Это может создавать несоответствия с некоторыми частотами, которые не соответствуют этому критерию. Чтобы изменить это поведение, вы можете указать фиксированный Timestamp с аргументом origin.
Например:
In [332]: start, end = "2000-10-01 23:30:00", "2000-10-02 00:30:00"
In [333]: middle = "2000-10-02 00:00:00"
In [334]: rng = pd.date_range(start, end, freq="7min")
In [335]: ts = pd.Series(np.arange(len(rng)) * 3, index=rng)
In [336]: ts
Out[336]:
2000-10-01 23:30:00 0
2000-10-01 23:37:00 3
2000-10-01 23:44:00 6
2000-10-01 23:51:00 9
2000-10-01 23:58:00 12
2000-10-02 00:05:00 15
2000-10-02 00:12:00 18
2000-10-02 00:19:00 21
2000-10-02 00:26:00 24
Freq: 7min, dtype: int64
Здесь мы видим, что при использовании origin с его значением по умолчанию ('start_day'), результат после '2000-10-02 00:00:00' не идентичны в зависимости от начала временного ряда:
In [337]: ts.resample("17min", origin="start_day").sum()
Out[337]:
2000-10-01 23:14:00 0
2000-10-01 23:31:00 9
2000-10-01 23:48:00 21
2000-10-02 00:05:00 54
2000-10-02 00:22:00 24
Freq: 17min, dtype: int64
In [338]: ts[middle:end].resample("17min", origin="start_day").sum()
Out[338]:
2000-10-02 00:00:00 33
2000-10-02 00:17:00 45
Freq: 17min, dtype: int64
Здесь мы видим, что при установке origin to 'epoch', результат после '2000-10-02 00:00:00' идентичны в зависимости от начала временного ряда:
In [339]: ts.resample("17min", origin="epoch").sum()
Out[339]:
2000-10-01 23:18:00 0
2000-10-01 23:35:00 18
2000-10-01 23:52:00 27
2000-10-02 00:09:00 39
2000-10-02 00:26:00 24
Freq: 17min, dtype: int64
In [340]: ts[middle:end].resample("17min", origin="epoch").sum()
Out[340]:
2000-10-01 23:52:00 15
2000-10-02 00:09:00 39
2000-10-02 00:26:00 24
Freq: 17min, dtype: int64
При необходимости вы можете использовать пользовательскую метку времени для origin:
In [341]: ts.resample("17min", origin="2001-01-01").sum()
Out[341]:
2000-10-01 23:30:00 9
2000-10-01 23:47:00 21
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
In [342]: ts[middle:end].resample("17min", origin=pd.Timestamp("2001-01-01")).sum()
Out[342]:
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
При необходимости вы можете просто настроить интервалы с помощью offset Timedelta, который будет добавлен к значению по умолчанию origin.
Эти два примера эквивалентны для этого временного ряда:
In [343]: ts.resample("17min", origin="start").sum()
Out[343]:
2000-10-01 23:30:00 9
2000-10-01 23:47:00 21
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
In [344]: ts.resample("17min", offset="23h30min").sum()
Out[344]:
2000-10-01 23:30:00 9
2000-10-01 23:47:00 21
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
Обратите внимание на использование 'start' для origin в последнем примере. В этом случае, origin будет установлено в первое значение временного ряда.
Обратная передискретизация#
Добавлено в версии 1.3.0.
Вместо корректировки начала интервалов иногда нам нужно зафиксировать конец интервалов, чтобы выполнить обратную передискретизацию с заданным freq. Обратная передискретизация устанавливает closed to 'right' по умолчанию, так как последнее значение должно рассматриваться как граничная точка для последнего бина.
Мы можем установить origin to 'end'. Значение для конкретного Timestamp index обозначает результат передискретизации из текущего Timestamp минус freq к текущему Timestamp с правой закрытой границей.
In [345]: ts.resample('17min', origin='end').sum()
Out[345]:
2000-10-01 23:35:00 0
2000-10-01 23:52:00 18
2000-10-02 00:09:00 27
2000-10-02 00:26:00 63
Freq: 17min, dtype: int64
Кроме того, в отличие от 'start_day' опция, end_day поддерживается. Это установит начало как полночь потолка наибольшего Timestamp.
In [346]: ts.resample('17min', origin='end_day').sum()
Out[346]:
2000-10-01 23:38:00 3
2000-10-01 23:55:00 15
2000-10-02 00:12:00 45
2000-10-02 00:29:00 45
Freq: 17min, dtype: int64
Приведенный выше результат использует 2000-10-02 00:29:00 как правый край последнего бина, поскольку следующее вычисление.
In [347]: ceil_mid = rng.max().ceil('D')
In [348]: freq = pd.offsets.Minute(17)
In [349]: bin_res = ceil_mid - freq * ((ceil_mid - rng.max()) // freq)
In [350]: bin_res
Out[350]: Timestamp('2000-10-02 00:29:00')
Представление временного интервала#
Регулярные интервалы времени представлены Period объекты в pandas, в то время как
последовательности Period объекты собираются в PeriodIndex, который может
быть создан с помощью удобной функции period_range.
Period#
A Period представляет промежуток времени (например, день, месяц, квартал и т.д.). Вы можете указать промежуток через freq ключевое слово с использованием псевдонима частоты, как показано ниже.
Поскольку freq представляет собой диапазон Period, он не может быть отрицательным, например '-3D'.
In [351]: pd.Period("2012", freq="Y-DEC")
Out[351]: Period('2012', 'Y-DEC')
In [352]: pd.Period("2012-1-1", freq="D")
Out[352]: Period('2012-01-01', 'D')
In [353]: pd.Period("2012-1-1 19:00", freq="h")
Out[353]: Period('2012-01-01 19:00', 'h')
In [354]: pd.Period("2012-1-1 19:00", freq="5h")
Out[354]: Period('2012-01-01 19:00', '5h')
Добавление и вычитание целых чисел из периодов сдвигает период на его собственную частоту. Арифметические операции не разрешены между Period с разными freq (span).
In [355]: p = pd.Period("2012", freq="Y-DEC")
In [356]: p + 1
Out[356]: Period('2013', 'Y-DEC')
In [357]: p - 3
Out[357]: Period('2009', 'Y-DEC')
In [358]: p = pd.Period("2012-01", freq="2M")
In [359]: p + 2
Out[359]: Period('2012-05', '2M')
In [360]: p - 1
Out[360]: Period('2011-11', '2M')
In [361]: p == pd.Period("2012-01", freq="3M")
Out[361]: False
Если Period частота ежедневная или выше (D, h, min, s, ms, us, и ns), offsets и timedelta-подобное может быть добавлено, если результат может иметь ту же частоту. В противном случае, ValueError будет вызвано исключение.
In [362]: p = pd.Period("2014-07-01 09:00", freq="h")
In [363]: p + pd.offsets.Hour(2)
Out[363]: Period('2014-07-01 11:00', 'h')
In [364]: p + datetime.timedelta(minutes=120)
Out[364]: Period('2014-07-01 11:00', 'h')
In [365]: p + np.timedelta64(7200, "s")
Out[365]: Period('2014-07-01 11:00', 'h')
In [366]: p + pd.offsets.Minute(5)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
File ~/work/pandas/pandas/pandas/_libs/tslibs/period.pyx:1824, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()
File ~/work/pandas/pandas/pandas/_libs/tslibs/timedeltas.pyx:278, in pandas._libs.tslibs.timedeltas.delta_to_nanoseconds()
File ~/work/pandas/pandas/pandas/_libs/tslibs/np_datetime.pyx:661, in pandas._libs.tslibs.np_datetime.convert_reso()
ValueError: Cannot losslessly convert units
The above exception was the direct cause of the following exception:
IncompatibleFrequency Traceback (most recent call last)
Cell In[366], line 1
----> 1 p + pd.offsets.Minute(5)
File ~/work/pandas/pandas/pandas/_libs/tslibs/period.pyx:1845, in pandas._libs.tslibs.period._Period.__add__()
File ~/work/pandas/pandas/pandas/_libs/tslibs/period.pyx:1826, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()
IncompatibleFrequency: Input cannot be converted to Period(freq=h)
Если Period имеет другие частоты, только те же offsets может быть добавлен. В противном случае, ValueError будет вызвано исключение.
In [367]: p = pd.Period("2014-07", freq="M")
In [368]: p + pd.offsets.MonthEnd(3)
Out[368]: Period('2014-10', 'M')
In [369]: p + pd.offsets.MonthBegin(3)
---------------------------------------------------------------------------
IncompatibleFrequency Traceback (most recent call last)
Cell In[369], line 1
----> 1 p + pd.offsets.MonthBegin(3)
File ~/work/pandas/pandas/pandas/_libs/tslibs/period.pyx:1847, in pandas._libs.tslibs.period._Period.__add__()
File ~/work/pandas/pandas/pandas/_libs/tslibs/period.pyx:1837, in pandas._libs.tslibs.period._Period._add_offset()
File ~/work/pandas/pandas/pandas/_libs/tslibs/period.pyx:1732, in pandas._libs.tslibs.period.PeriodMixin._require_matching_freq()
IncompatibleFrequency: Input has different freq=3M from Period(freq=M)
Взятие разности Period экземпляры с одинаковой частотой будут возвращать количество единиц частоты между ними:
In [370]: pd.Period("2012", freq="Y-DEC") - pd.Period("2002", freq="Y-DEC")
Out[370]: <10 * YearEnds: month=12>
PeriodIndex и period_range#
Регулярные последовательности Period объекты могут быть собраны в PeriodIndex,
который может быть создан с использованием period_range удобная функция:
In [371]: prng = pd.period_range("1/1/2011", "1/1/2012", freq="M")
In [372]: prng
Out[372]:
PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04', '2011-05', '2011-06',
'2011-07', '2011-08', '2011-09', '2011-10', '2011-11', '2011-12',
'2012-01'],
dtype='period[M]')
The PeriodIndex конструктор также может использоваться напрямую:
In [373]: pd.PeriodIndex(["2011-1", "2011-2", "2011-3"], freq="M")
Out[373]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]')
Передача умноженной частоты выводит последовательность Period который увеличил диапазон.
In [374]: pd.period_range(start="2014-01", freq="3M", periods=4)
Out[374]: PeriodIndex(['2014-01', '2014-04', '2014-07', '2014-10'], dtype='period[3M]')
Если start или end являются Period объектов, они будут использоваться в качестве конечных точек привязки для PeriodIndex с частотой, соответствующей
PeriodIndex конструктор.
In [375]: pd.period_range(
.....: start=pd.Period("2017Q1", freq="Q"), end=pd.Period("2017Q2", freq="Q"), freq="M"
.....: )
.....:
Out[375]: PeriodIndex(['2017-03', '2017-04', '2017-05', '2017-06'], dtype='period[M]')
Так же, как DatetimeIndex, a PeriodIndex также может использоваться для индексации объектов pandas:
In [376]: ps = pd.Series(np.random.randn(len(prng)), prng)
In [377]: ps
Out[377]:
2011-01 -2.916901
2011-02 0.514474
2011-03 1.346470
2011-04 0.816397
2011-05 2.258648
2011-06 0.494789
2011-07 0.301239
2011-08 0.464776
2011-09 -1.393581
2011-10 0.056780
2011-11 0.197035
2011-12 2.261385
2012-01 -0.329583
Freq: M, dtype: float64
PeriodIndex поддерживает сложение и вычитание с тем же правилом, что и Period.
In [378]: idx = pd.period_range("2014-07-01 09:00", periods=5, freq="h")
In [379]: idx
Out[379]:
PeriodIndex(['2014-07-01 09:00', '2014-07-01 10:00', '2014-07-01 11:00',
'2014-07-01 12:00', '2014-07-01 13:00'],
dtype='period[h]')
In [380]: idx + pd.offsets.Hour(2)
Out[380]:
PeriodIndex(['2014-07-01 11:00', '2014-07-01 12:00', '2014-07-01 13:00',
'2014-07-01 14:00', '2014-07-01 15:00'],
dtype='period[h]')
In [381]: idx = pd.period_range("2014-07", periods=5, freq="M")
In [382]: idx
Out[382]: PeriodIndex(['2014-07', '2014-08', '2014-09', '2014-10', '2014-11'], dtype='period[M]')
In [383]: idx + pd.offsets.MonthEnd(3)
Out[383]: PeriodIndex(['2014-10', '2014-11', '2014-12', '2015-01', '2015-02'], dtype='period[M]')
PeriodIndex имеет собственный тип данных с именем period, см. Типы данных Period.
Period dtypes#
PeriodIndex имеет пользовательский period dtype. Это расширенный тип данных pandas, аналогичный тип данных с учетом часового пояса (datetime64[ns, tz]).
The period dtype содержит freq атрибут и представлен с помощью
period[freq] как period[D] или period[M], используя строки частоты.
In [384]: pi = pd.period_range("2016-01-01", periods=3, freq="M")
In [385]: pi
Out[385]: PeriodIndex(['2016-01', '2016-02', '2016-03'], dtype='period[M]')
In [386]: pi.dtype
Out[386]: period[M]
The period dtype может использоваться в .astype(...)Это позволяет изменить
freq из PeriodIndex как .asfreq() и преобразовать
DatetimeIndex to PeriodIndex как to_period():
# change monthly freq to daily freq
In [387]: pi.astype("period[D]")
Out[387]: PeriodIndex(['2016-01-31', '2016-02-29', '2016-03-31'], dtype='period[D]')
# convert to DatetimeIndex
In [388]: pi.astype("datetime64[ns]")
Out[388]: DatetimeIndex(['2016-01-01', '2016-02-01', '2016-03-01'], dtype='datetime64[ns]', freq='MS')
# convert to PeriodIndex
In [389]: dti = pd.date_range("2011-01-01", freq="ME", periods=3)
In [390]: dti
Out[390]: DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31'], dtype='datetime64[ns]', freq='ME')
In [391]: dti.astype("period[M]")
Out[391]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]')
Частичная строковая индексация PeriodIndex#
PeriodIndex теперь поддерживает частичное строковое срезание с немонотонными индексами.
Вы можете передавать даты и строки в Series и DataFrame с PeriodIndex, таким же образом, как DatetimeIndex. Подробности см. в Частичная строковая индексация DatetimeIndex.
In [392]: ps["2011-01"]
Out[392]: -2.9169013294054507
In [393]: ps[datetime.datetime(2011, 12, 25):]
Out[393]:
2011-12 2.261385
2012-01 -0.329583
Freq: M, dtype: float64
In [394]: ps["10/31/2011":"12/31/2011"]
Out[394]:
2011-10 0.056780
2011-11 0.197035
2011-12 2.261385
Freq: M, dtype: float64
Передача строки, представляющей более низкую частоту, чем PeriodIndex возвращает частично срезанные данные.
In [395]: ps["2011"]
Out[395]:
2011-01 -2.916901
2011-02 0.514474
2011-03 1.346470
2011-04 0.816397
2011-05 2.258648
2011-06 0.494789
2011-07 0.301239
2011-08 0.464776
2011-09 -1.393581
2011-10 0.056780
2011-11 0.197035
2011-12 2.261385
Freq: M, dtype: float64
In [396]: dfp = pd.DataFrame(
.....: np.random.randn(600, 1),
.....: columns=["A"],
.....: index=pd.period_range("2013-01-01 9:00", periods=600, freq="min"),
.....: )
.....:
In [397]: dfp
Out[397]:
A
2013-01-01 09:00 -0.538468
2013-01-01 09:01 -1.365819
2013-01-01 09:02 -0.969051
2013-01-01 09:03 -0.331152
2013-01-01 09:04 -0.245334
... ...
2013-01-01 18:55 0.522460
2013-01-01 18:56 0.118710
2013-01-01 18:57 0.167517
2013-01-01 18:58 0.922883
2013-01-01 18:59 1.721104
[600 rows x 1 columns]
In [398]: dfp.loc["2013-01-01 10h"]
Out[398]:
A
2013-01-01 10:00 -0.308975
2013-01-01 10:01 0.542520
2013-01-01 10:02 1.061068
2013-01-01 10:03 0.754005
2013-01-01 10:04 0.352933
... ...
2013-01-01 10:55 -0.865621
2013-01-01 10:56 -1.167818
2013-01-01 10:57 -2.081748
2013-01-01 10:58 -0.527146
2013-01-01 10:59 0.802298
[60 rows x 1 columns]
Как и с DatetimeIndex, конечные точки будут включены в результат. Пример ниже срезает данные с 10:00 до 11:59.
In [399]: dfp["2013-01-01 10h":"2013-01-01 11h"]
Out[399]:
A
2013-01-01 10:00 -0.308975
2013-01-01 10:01 0.542520
2013-01-01 10:02 1.061068
2013-01-01 10:03 0.754005
2013-01-01 10:04 0.352933
... ...
2013-01-01 11:55 -0.590204
2013-01-01 11:56 1.539990
2013-01-01 11:57 -1.224826
2013-01-01 11:58 0.578798
2013-01-01 11:59 -0.685496
[120 rows x 1 columns]
Преобразование частоты и ресемплинг с PeriodIndex#
Частота Period и PeriodIndex может быть преобразован через asfreq
метод. Начнем с финансового года 2011, заканчивающегося в декабре:
In [400]: p = pd.Period("2011", freq="Y-DEC")
In [401]: p
Out[401]: Period('2011', 'Y-DEC')
Мы можем преобразовать его в месячную частоту. Используя how параметр позволяет указать, возвращать ли начальный или конечный месяц:
In [402]: p.asfreq("M", how="start")
Out[402]: Period('2011-01', 'M')
In [403]: p.asfreq("M", how="end")
Out[403]: Period('2011-12', 'M')
Для удобства предоставлены сокращения 's' и 'e':
In [404]: p.asfreq("M", "s")
Out[404]: Period('2011-01', 'M')
In [405]: p.asfreq("M", "e")
Out[405]: Period('2011-12', 'M')
Преобразование в "супер-период" (например, годовая частота является супер-периодом для квартальной частоты) автоматически возвращает супер-период, который включает входной период:
In [406]: p = pd.Period("2011-12", freq="M")
In [407]: p.asfreq("Y-NOV")
Out[407]: Period('2012', 'Y-NOV')
Обратите внимание, что поскольку мы преобразовали в годовую частоту, заканчивающую год в ноябре, месячный период декабря 2011 года фактически находится в периоде 2012 Y-NOV.
Преобразования периодов с закрепленными частотами особенно полезны для
работы с различными квартальными данными, распространенными в экономике, бизнесе и других
областях. Многие организации определяют кварталы относительно месяца, в котором их
финансовый год начинается и заканчивается. Таким образом, первый квартал 2011 года может начаться в 2010 году или
через несколько месяцев в 2011 году. С помощью закрепленных частот pandas работает со всеми квартальными
частотами Q-JAN через Q-DEC.
Q-DEC определить регулярные календарные кварталы:
In [408]: p = pd.Period("2012Q1", freq="Q-DEC")
In [409]: p.asfreq("D", "s")
Out[409]: Period('2012-01-01', 'D')
In [410]: p.asfreq("D", "e")
Out[410]: Period('2012-03-31', 'D')
Q-MAR определяет конец финансового года в марте:
In [411]: p = pd.Period("2011Q4", freq="Q-MAR")
In [412]: p.asfreq("D", "s")
Out[412]: Period('2011-01-01', 'D')
In [413]: p.asfreq("D", "e")
Out[413]: Period('2011-03-31', 'D')
Преобразование между представлениями#
Данные с временными метками могут быть преобразованы в данные с PeriodIndex с помощью to_period
и наоборот, используя to_timestamp:
In [414]: rng = pd.date_range("1/1/2012", periods=5, freq="ME")
In [415]: ts = pd.Series(np.random.randn(len(rng)), index=rng)
In [416]: ts
Out[416]:
2012-01-31 1.931253
2012-02-29 -0.184594
2012-03-31 0.249656
2012-04-30 -0.978151
2012-05-31 -0.873389
Freq: ME, dtype: float64
In [417]: ps = ts.to_period()
In [418]: ps
Out[418]:
2012-01 1.931253
2012-02 -0.184594
2012-03 0.249656
2012-04 -0.978151
2012-05 -0.873389
Freq: M, dtype: float64
In [419]: ps.to_timestamp()
Out[419]:
2012-01-01 1.931253
2012-02-01 -0.184594
2012-03-01 0.249656
2012-04-01 -0.978151
2012-05-01 -0.873389
Freq: MS, dtype: float64
Помните, что 's' и 'e' можно использовать для возврата временных меток в начале или конце периода:
In [420]: ps.to_timestamp("D", how="s")
Out[420]:
2012-01-01 1.931253
2012-02-01 -0.184594
2012-03-01 0.249656
2012-04-01 -0.978151
2012-05-01 -0.873389
Freq: MS, dtype: float64
Преобразование между периодом и меткой времени позволяет использовать некоторые удобные арифметические функции. В следующем примере мы преобразуем квартальную частоту с окончанием года в ноябре в 9 утра конца месяца, следующего за окончанием квартала:
In [421]: prng = pd.period_range("1990Q1", "2000Q4", freq="Q-NOV")
In [422]: ts = pd.Series(np.random.randn(len(prng)), prng)
In [423]: ts.index = (prng.asfreq("M", "e") + 1).asfreq("h", "s") + 9
In [424]: ts.head()
Out[424]:
1990-03-01 09:00 -0.109291
1990-06-01 09:00 -0.637235
1990-09-01 09:00 -1.735925
1990-12-01 09:00 2.096946
1991-03-01 09:00 -1.039926
Freq: h, dtype: float64
Представление выходящих за пределы диапазонов#
Если у вас есть данные, которые находятся за пределами Timestamp границы, см. Ограничения временных меток, тогда вы можете использовать PeriodIndex и/или Series of Periods для выполнения вычислений.
In [425]: span = pd.period_range("1215-01-01", "1381-01-01", freq="D")
In [426]: span
Out[426]:
PeriodIndex(['1215-01-01', '1215-01-02', '1215-01-03', '1215-01-04',
'1215-01-05', '1215-01-06', '1215-01-07', '1215-01-08',
'1215-01-09', '1215-01-10',
...
'1380-12-23', '1380-12-24', '1380-12-25', '1380-12-26',
'1380-12-27', '1380-12-28', '1380-12-29', '1380-12-30',
'1380-12-31', '1381-01-01'],
dtype='period[D]', length=60632)
Для преобразования из int64 представление на основе YYYYMMDD.
In [427]: s = pd.Series([20121231, 20141130, 99991231])
In [428]: s
Out[428]:
0 20121231
1 20141130
2 99991231
dtype: int64
In [429]: def conv(x):
.....: return pd.Period(year=x // 10000, month=x // 100 % 100, day=x % 100, freq="D")
.....:
In [430]: s.apply(conv)
Out[430]:
0 2012-12-31
1 2014-11-30
2 9999-12-31
dtype: period[D]
In [431]: s.apply(conv)[2]
Out[431]: Period('9999-12-31', 'D')
Их можно легко преобразовать в PeriodIndex:
In [432]: span = pd.PeriodIndex(s.apply(conv))
In [433]: span
Out[433]: PeriodIndex(['2012-12-31', '2014-11-30', '9999-12-31'], dtype='period[D]')
Обработка часовых поясов#
pandas предоставляет богатую поддержку работы с метками времени в разных часовых поясах с использованием pytz и dateutil библиотеки или datetime.timezone
объекты из стандартной библиотеки.
Работа с часовыми поясами#
По умолчанию объекты pandas не учитывают часовой пояс:
In [434]: rng = pd.date_range("3/6/2012 00:00", periods=15, freq="D")
In [435]: rng.tz is None
Out[435]: True
Чтобы локализовать эти даты в часовом поясе (назначить определенный часовой пояс наивной дате), вы можете использовать tz_localize метод или tz именованный аргумент в
date_range(), Timestamp, или DatetimeIndex. Вы можете либо передать pytz или dateutil объекты часовых поясов или строки базы данных часовых поясов Olson.
Строки часовых поясов Olson вернут pytz объекты часовых поясов по умолчанию.
Чтобы вернуть dateutil объекты часовых поясов, добавить dateutil/ перед строкой.
В
pytzвы можете найти список распространённых (и менее распространённых) часовых поясов, используяfrom pytz import common_timezones, all_timezones.dateutilиспользует часовые пояса ОС, поэтому фиксированного списка нет. Для распространенных зон названия совпадают сpytz.
In [436]: import dateutil
# pytz
In [437]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D", tz="Europe/London")
In [438]: rng_pytz.tz
Out[438]:
# dateutil
In [439]: rng_dateutil = pd.date_range("3/6/2012 00:00", periods=3, freq="D")
In [440]: rng_dateutil = rng_dateutil.tz_localize("dateutil/Europe/London")
In [441]: rng_dateutil.tz
Out[441]: tzfile('/usr/share/zoneinfo/Europe/London')
# dateutil - utc special case
In [442]: rng_utc = pd.date_range(
.....: "3/6/2012 00:00",
.....: periods=3,
.....: freq="D",
.....: tz=dateutil.tz.tzutc(),
.....: )
.....:
In [443]: rng_utc.tz
Out[443]: tzutc()
# datetime.timezone
In [444]: rng_utc = pd.date_range(
.....: "3/6/2012 00:00",
.....: periods=3,
.....: freq="D",
.....: tz=datetime.timezone.utc,
.....: )
.....:
In [445]: rng_utc.tz
Out[445]: datetime.timezone.utc
Обратите внимание, что UTC часовой пояс является особым случаем в dateutil и должен быть явно создан как экземпляр dateutil.tz.tzutc. Вы также можете сначала явно создать другие объекты
часовых поясов.
In [446]: import pytz
# pytz
In [447]: tz_pytz = pytz.timezone("Europe/London")
In [448]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D")
In [449]: rng_pytz = rng_pytz.tz_localize(tz_pytz)
In [450]: rng_pytz.tz == tz_pytz
Out[450]: True
# dateutil
In [451]: tz_dateutil = dateutil.tz.gettz("Europe/London")
In [452]: rng_dateutil = pd.date_range("3/6/2012 00:00", periods=3, freq="D", tz=tz_dateutil)
In [453]: rng_dateutil.tz == tz_dateutil
Out[453]: True
Чтобы преобразовать объект pandas с учётом часового пояса из одного часового пояса в другой, вы можете использовать tz_convert метод.
In [454]: rng_pytz.tz_convert("US/Eastern")
Out[454]:
DatetimeIndex(['2012-03-05 19:00:00-05:00', '2012-03-06 19:00:00-05:00',
'2012-03-07 19:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
Примечание
При использовании pytz часовые пояса, DatetimeIndex создаст другой
объект часового пояса, чем Timestamp для одного и того же часового пояса. A DatetimeIndex
может содержать коллекцию Timestamp объекты, которые могут иметь разные смещения UTC и не могут быть
кратко представлены одним pytz экземпляр часового пояса, в то время как один Timestamp
представляет один момент времени с определённым смещением UTC.
In [455]: dti = pd.date_range("2019-01-01", periods=3, freq="D", tz="US/Pacific")
In [456]: dti.tz
Out[456]:
In [457]: ts = pd.Timestamp("2019-01-01", tz="US/Pacific")
In [458]: ts.tz
Out[458]:
Предупреждение
Будьте осторожны с преобразованиями между библиотеками. Для некоторых часовых поясов, pytz и dateutil имеют разные определения зоны. Это больше проблема для нестандартных часовых поясов, чем для 'стандартных' зон, таких как US/Eastern.
Предупреждение
Учтите, что определение часового пояса в разных версиях библиотек часовых поясов может не считаться равным. Это может вызвать проблемы при работе с сохраненными данными, которые локализованы с использованием одной версии и обрабатываются с другой версией. Смотрите здесь для обработки такой ситуации.
Предупреждение
Для pytz часовые пояса, неправильно передавать объект часового пояса непосредственно в datetime.datetime конструктор
(например, datetime.datetime(2011, 1, 1, tzinfo=pytz.timezone('US/Eastern')).
Вместо этого дата и время должны быть локализованы с использованием localize метод
на pytz объект часового пояса.
Предупреждение
Учтите, что для времени в будущем корректное преобразование между часовыми поясами (и UTC) не может быть гарантировано любой библиотекой часовых поясов, потому что смещение часового пояса от UTC может быть изменено соответствующим правительством.
Предупреждение
Если вы используете даты после 2038-01-18, из-за текущих недостатков в базовых библиотеках, вызванных проблемой 2038 года, переходы на летнее время (DST) для дат с учётом часового пояса не будут применяться. Если и когда базовые библиотеки будут исправлены, переходы DST будут применены.
Например, для двух дат, которые находятся в британском летнем времени (и поэтому обычно GMT+1), оба следующих утверждения оцениваются как истинные:
In [459]: d_2037 = "2037-03-31T010101"
In [460]: d_2038 = "2038-03-31T010101"
In [461]: DST = "Europe/London"
In [462]: assert pd.Timestamp(d_2037, tz=DST) != pd.Timestamp(d_2037, tz="GMT")
In [463]: assert pd.Timestamp(d_2038, tz=DST) == pd.Timestamp(d_2038, tz="GMT")
Под капотом все временные метки хранятся в UTC. Значения из временной зоны с учетом
DatetimeIndex или Timestamp будут иметь свои поля (день, час, минута и т.д.) локализованными по часовому поясу. Однако временные метки с одинаковым значением UTC всё равно считаются равными, даже если они в разных часовых поясах:
In [464]: rng_eastern = rng_utc.tz_convert("US/Eastern")
In [465]: rng_berlin = rng_utc.tz_convert("Europe/Berlin")
In [466]: rng_eastern[2]
Out[466]: Timestamp('2012-03-07 19:00:00-0500', tz='US/Eastern')
In [467]: rng_berlin[2]
Out[467]: Timestamp('2012-03-08 01:00:00+0100', tz='Europe/Berlin')
In [468]: rng_eastern[2] == rng_berlin[2]
Out[468]: True
Операции между Series в разных часовых поясах даст UTC
Series, выравнивая данные по временным меткам UTC:
In [469]: ts_utc = pd.Series(range(3), pd.date_range("20130101", periods=3, tz="UTC"))
In [470]: eastern = ts_utc.tz_convert("US/Eastern")
In [471]: berlin = ts_utc.tz_convert("Europe/Berlin")
In [472]: result = eastern + berlin
In [473]: result
Out[473]:
2013-01-01 00:00:00+00:00 0
2013-01-02 00:00:00+00:00 2
2013-01-03 00:00:00+00:00 4
Freq: D, dtype: int64
In [474]: result.index
Out[474]:
DatetimeIndex(['2013-01-01 00:00:00+00:00', '2013-01-02 00:00:00+00:00',
'2013-01-03 00:00:00+00:00'],
dtype='datetime64[ns, UTC]', freq='D')
Чтобы удалить информацию о часовом поясе, используйте tz_localize(None) или tz_convert(None).
tz_localize(None) удалит часовой пояс, оставив локальное представление времени.
tz_convert(None) удалит часовой пояс после преобразования в UTC время.
In [475]: didx = pd.date_range(start="2014-08-01 09:00", freq="h", periods=3, tz="US/Eastern")
In [476]: didx
Out[476]:
DatetimeIndex(['2014-08-01 09:00:00-04:00', '2014-08-01 10:00:00-04:00',
'2014-08-01 11:00:00-04:00'],
dtype='datetime64[ns, US/Eastern]', freq='h')
In [477]: didx.tz_localize(None)
Out[477]:
DatetimeIndex(['2014-08-01 09:00:00', '2014-08-01 10:00:00',
'2014-08-01 11:00:00'],
dtype='datetime64[ns]', freq=None)
In [478]: didx.tz_convert(None)
Out[478]:
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
'2014-08-01 15:00:00'],
dtype='datetime64[ns]', freq='h')
# tz_convert(None) is identical to tz_convert('UTC').tz_localize(None)
In [479]: didx.tz_convert("UTC").tz_localize(None)
Out[479]:
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
'2014-08-01 15:00:00'],
dtype='datetime64[ns]', freq=None)
Свернуть#
Для неоднозначных времен pandas поддерживает явное указание аргумента fold (только ключевое слово).
Из-за перехода на летнее время одно настенное время может происходить дважды при переходе
с летнего на зимнее время; fold описывает, соответствует ли datetime-like
первому (0) или второму разу (1), когда настенные часы достигают неоднозначного времени.
Fold поддерживается только для создания из наивных datetime.datetime
(см. документация по datetime для подробностей) или из Timestamp
или для построения из компонентов (см. ниже). Только dateutil поддерживаются часовые пояса
(см. документация dateutil
для dateutil методы, которые работают с неоднозначными датами-временем) как pytz
часовые пояса не поддерживают fold (см. документация pytz
подробности о том, как pytz работает с неоднозначными датами и временем). Для локализации неоднозначной даты и времени
с pytz, пожалуйста, используйте Timestamp.tz_localize(). В целом, мы рекомендуем полагаться на Timestamp.tz_localize() при локализации неоднозначных дат и времени, если вам нужен прямой
контроль над тем, как они обрабатываются.
In [480]: pd.Timestamp(
.....: datetime.datetime(2019, 10, 27, 1, 30, 0, 0),
.....: tz="dateutil/Europe/London",
.....: fold=0,
.....: )
.....:
Out[480]: Timestamp('2019-10-27 01:30:00+0100', tz='dateutil//usr/share/zoneinfo/Europe/London')
In [481]: pd.Timestamp(
.....: year=2019,
.....: month=10,
.....: day=27,
.....: hour=1,
.....: minute=30,
.....: tz="dateutil/Europe/London",
.....: fold=1,
.....: )
.....:
Out[481]: Timestamp('2019-10-27 01:30:00+0000', tz='dateutil//usr/share/zoneinfo/Europe/London')
Неоднозначные времена при локализации#
tz_localize может не определить смещение UTC для метки времени,
потому что переход на летнее время (DST) в местном часовом поясе приводит к тому, что некоторые времена встречаются
дважды в течение одного дня («часы переводятся назад»). Доступны следующие опции:
'raise': Вызываетpytz.AmbiguousTimeError(поведение по умолчанию)'infer': Попытка определить правильное смещение на основе монотонности временных меток'NaT': Заменяет неоднозначные времена наNaTbool:Trueпредставляет время перехода на летнее время,Falseпредставляет время без перехода на летнее время. Массивоподобныйboolподдерживается для последовательности временных значений.
In [482]: rng_hourly = pd.DatetimeIndex(
.....: ["11/06/2011 00:00", "11/06/2011 01:00", "11/06/2011 01:00", "11/06/2011 02:00"]
.....: )
.....:
Это завершится ошибкой, так как есть неоднозначные временные значения ('11/06/2011 01:00')
In [483]: rng_hourly.tz_localize('US/Eastern')
---------------------------------------------------------------------------
AmbiguousTimeError Traceback (most recent call last)
Cell In[483], line 1
----> 1 rng_hourly.tz_localize('US/Eastern')
File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
286 @doc(DatetimeArray.tz_localize)
287 def tz_localize(
288 self,
(...)
291 nonexistent: TimeNonexistent = "raise",
292 ) -> Self:
--> 293 arr = self._data.tz_localize(tz, ambiguous, nonexistent)
294 return type(self)._simple_new(arr, name=self.name)
File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:81, in ravel_compat..method (self, *args, **kwargs)
78 @wraps(meth)
79 def method(self, *args, **kwargs):
80 if self.ndim == 1:
---> 81 return meth(self, *args, **kwargs)
83 flags = self._ndarray.flags
84 flat = self.ravel("K")
File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1090, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
1087 tz = timezones.maybe_get_tz(tz)
1088 # Convert to UTC
-> 1090 new_dates = tzconversion.tz_localize_to_utc(
1091 self.asi8,
1092 tz,
1093 ambiguous=ambiguous,
1094 nonexistent=nonexistent,
1095 creso=self._creso,
1096 )
1097 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
1098 dtype = tz_to_dtype(tz, unit=self.unit)
File ~/work/pandas/pandas/pandas/_libs/tslibs/tzconversion.pyx:371, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()
AmbiguousTimeError: Cannot infer dst time from 2011-11-06 01:00:00, try using the 'ambiguous' argument
Обработайте эти неоднозначные времена, указав следующее.
In [484]: rng_hourly.tz_localize("US/Eastern", ambiguous="infer")
Out[484]:
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
'2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
In [485]: rng_hourly.tz_localize("US/Eastern", ambiguous="NaT")
Out[485]:
DatetimeIndex(['2011-11-06 00:00:00-04:00', 'NaT', 'NaT',
'2011-11-06 02:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
In [486]: rng_hourly.tz_localize("US/Eastern", ambiguous=[True, True, False, False])
Out[486]:
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
'2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
Несуществующее время при локализации#
Переход на летнее время также может сдвинуть локальное время вперед на 1 час, создавая несуществующие
локальные времена ("часы переводятся вперед"). Поведение локализации временного ряда с несуществующими временами
может контролироваться параметром nonexistent аргумент. Доступны следующие опции:
'raise': Вызываетpytz.NonExistentTimeError(поведение по умолчанию)'NaT': Заменяет несуществующие времена наNaT'shift_forward': Сдвигает несуществующие времена вперед к ближайшему реальному времени'shift_backward': Сдвигает несуществующие времена назад к ближайшему реальному времениОбъект timedelta: Сдвигает несуществующие времена на длительность timedelta
In [487]: dti = pd.date_range(start="2015-03-29 02:30:00", periods=3, freq="h")
# 2:30 is a nonexistent time
Локализация несуществующих времён по умолчанию вызывает ошибку.
In [488]: dti.tz_localize('Europe/Warsaw')
---------------------------------------------------------------------------
NonExistentTimeError Traceback (most recent call last)
Cell In[488], line 1
----> 1 dti.tz_localize('Europe/Warsaw')
File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
286 @doc(DatetimeArray.tz_localize)
287 def tz_localize(
288 self,
(...)
291 nonexistent: TimeNonexistent = "raise",
292 ) -> Self:
--> 293 arr = self._data.tz_localize(tz, ambiguous, nonexistent)
294 return type(self)._simple_new(arr, name=self.name)
File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:81, in ravel_compat..method (self, *args, **kwargs)
78 @wraps(meth)
79 def method(self, *args, **kwargs):
80 if self.ndim == 1:
---> 81 return meth(self, *args, **kwargs)
83 flags = self._ndarray.flags
84 flat = self.ravel("K")
File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1090, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
1087 tz = timezones.maybe_get_tz(tz)
1088 # Convert to UTC
-> 1090 new_dates = tzconversion.tz_localize_to_utc(
1091 self.asi8,
1092 tz,
1093 ambiguous=ambiguous,
1094 nonexistent=nonexistent,
1095 creso=self._creso,
1096 )
1097 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
1098 dtype = tz_to_dtype(tz, unit=self.unit)
File ~/work/pandas/pandas/pandas/_libs/tslibs/tzconversion.pyx:431, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()
NonExistentTimeError: 2015-03-29 02:30:00
Преобразовать несуществующее время в NaT или сдвинуть времена.
In [489]: dti
Out[489]:
DatetimeIndex(['2015-03-29 02:30:00', '2015-03-29 03:30:00',
'2015-03-29 04:30:00'],
dtype='datetime64[ns]', freq='h')
In [490]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_forward")
Out[490]:
DatetimeIndex(['2015-03-29 03:00:00+02:00', '2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
In [491]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_backward")
Out[491]:
DatetimeIndex(['2015-03-29 01:59:59.999999999+01:00',
'2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
In [492]: dti.tz_localize("Europe/Warsaw", nonexistent=pd.Timedelta(1, unit="h"))
Out[492]:
DatetimeIndex(['2015-03-29 03:30:00+02:00', '2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
In [493]: dti.tz_localize("Europe/Warsaw", nonexistent="NaT")
Out[493]:
DatetimeIndex(['NaT', '2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
Операции с Series временных зон#
A Series с часовым поясом наивный значения
представлены с dtype datetime64[ns].
In [494]: s_naive = pd.Series(pd.date_range("20130101", periods=3))
In [495]: s_naive
Out[495]:
0 2013-01-01
1 2013-01-02
2 2013-01-03
dtype: datetime64[ns]
A Series с часовым поясом осведомленный значения
представлены с dtype datetime64[ns, tz] где tz это часовой пояс
In [496]: s_aware = pd.Series(pd.date_range("20130101", periods=3, tz="US/Eastern"))
In [497]: s_aware
Out[497]:
0 2013-01-01 00:00:00-05:00
1 2013-01-02 00:00:00-05:00
2 2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern]
Оба этих Series информация о часовом поясе
может быть изменена через .dt аксессор, см. раздел доступа dt.
Например, для локализации и преобразования наивной метки времени в осведомленную о часовом поясе.
In [498]: s_naive.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")
Out[498]:
0 2012-12-31 19:00:00-05:00
1 2013-01-01 19:00:00-05:00
2 2013-01-02 19:00:00-05:00
dtype: datetime64[ns, US/Eastern]
Информацию о часовом поясе также можно манипулировать с помощью astype метод.
Этот метод может преобразовывать между различными типами данных с учетом часовых поясов.
# convert to a new time zone
In [499]: s_aware.astype("datetime64[ns, CET]")
Out[499]:
0 2013-01-01 06:00:00+01:00
1 2013-01-02 06:00:00+01:00
2 2013-01-03 06:00:00+01:00
dtype: datetime64[ns, CET]
Примечание
Используя Series.to_numpy() на Series, возвращает массив NumPy данных.
NumPy в настоящее время не поддерживает часовые пояса (хотя это печать в локальном часовом поясе!),
поэтому возвращается массив объектов Timestamps для данных с учетом часового пояса:
In [500]: s_naive.to_numpy()
Out[500]:
array(['2013-01-01T00:00:00.000000000', '2013-01-02T00:00:00.000000000',
'2013-01-03T00:00:00.000000000'], dtype='datetime64[ns]')
In [501]: s_aware.to_numpy()
Out[501]:
array([Timestamp('2013-01-01 00:00:00-0500', tz='US/Eastern'),
Timestamp('2013-01-02 00:00:00-0500', tz='US/Eastern'),
Timestamp('2013-01-03 00:00:00-0500', tz='US/Eastern')],
dtype=object)
При преобразовании в массив объектов Timestamps сохраняется информация о часовом поясе. Например, при обратном преобразовании в Series:
In [502]: pd.Series(s_aware.to_numpy())
Out[502]:
0 2013-01-01 00:00:00-05:00
1 2013-01-02 00:00:00-05:00
2 2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern]
Однако, если вам нужен фактический NumPy datetime64[ns] массив (со значениями,
преобразованными в UTC) вместо массива объектов, вы можете указать
dtype аргумент:
In [503]: s_aware.to_numpy(dtype="datetime64[ns]")
Out[503]:
array(['2013-01-01T05:00:00.000000000', '2013-01-02T05:00:00.000000000',
'2013-01-03T05:00:00.000000000'], dtype='datetime64[ns]')