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