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

Объекты Timestamp и DatetimeIndex в pandas

DatetimeIndex связывает данные с моментами времени

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

Содержание:

  • Объект pandas.Timestamp() это pandas-эквивалент объекта datetime.datetime в python;
  • Объект pandas.DatetimeIndex представляет собой неизменяемый массив данных datetime64, подобный numpy.ndarray;
  • Функция pandas.to_datetime() преобразовывает различные данные, похожие на дату/время в объект pandas.Timestamp;
  • Функция pandas.date_range() создает DatetimeIndex на основе даты и времени;

pandas.Timestamp(ts_input=<object>, year=0, month=0, day=0, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, nanosecond=0, tz=None, unit=None, fold=None):

Объект pandas.Timestamp это pandas-эквивалент объекта datetime.datetime в python и в большинстве случаев взаимозаменяем с ним. Это тип, используемый для записей, составляющих DatetimeIndex, и других ориентированных на временные ряды структур данных.

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

  • ts_input - Значение, которое будет преобразовано во временную метку. Может быть объект похожий на datetime, строка похожая на дату, числа int или float.
  • year, month, day - целое число int (комментарии излишни)
  • hour, minute, second, microsecond - целое число int (комментарии излишни)
  • tzinfo - принимает datetime.tzinfo
  • nanosecond - целое число int (комментарии излишни)
  • tz - Часовой пояс, который будет иметь результирующий Timestamp. Может быть строкой str, объектами pytz.timezone или dateutil.tz.tzfile
  • unit - единица измерения, используемая для преобразования, если аргумент ts_input имеет тип int или float. Допустимыми значениями являются ‘D’, ‘h’, ‘m’, ‘s’, ‘ms’, ‘us’ и ‘ns’. Например, 's' означает секунды, а 'ms' - миллисекунды.

    Для входных данных float - результат будет сохранен в наносекундах, а unit будет установлен как 'ns'.

  • fold - из-за перехода на летнее время, время может совпадать дважды при переходе с летнего на зимнее время. Так вот этот аргумент описывает, соответствует ли значение даты/времени первому (0) или второму (1) времени, когда часы показывают неоднозначное время.

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

Две другие формы имитируют аргументы из datetime.datetime(). Они могут передаваться либо по позиции, либо по ключевому слову, но не по тому и другому вместе.

Объекты Series.dt и index.dt дают доступ к атрибутам и методам объекта Timestamp, хранящихся в Series/Index соответственно.

Примеры использования pandas.Timestamp():

Код ниже преобразует строку, похожую на дату/время:

>>> import pandas as pd
>>> pd.Timestamp('2024-01-01T12')
# Timestamp('2024-01-01 12:00:00')

Следующий вызов преобразует значение float, представляющее эпоху Unix в секундах:

>>> pd.Timestamp(1706275662, unit='s')
# Timestamp('2024-01-26 13:27:42')

Преобразуем значение int, представляющее эпоху Unix в секундах и для определенного часового пояса:

>>> pd.Timestamp(1706275662, unit='s', tz='Europe/Moscow')
# Timestamp('2024-01-26 16:27:42+0300', tz='Europe/Moscow')

Используем две другие формы соглашения о вызовах конструктора, которые имитируют API для объекта datetime.datetime():

>>> pd.Timestamp(2017, 1, 1, 12)
# Timestamp('2017-01-01 12:00:00')

>>> pd.Timestamp(year=2017, month=1, day=1, hour=12)
# Timestamp('2017-01-01 12:00:00')

pandas.DatetimeIndex(...):

Объект pandas.DatetimeIndex представляет собой неизменяемые данные datetime64, подобные numpy.ndarray. Внутренне представлен как int64 и может быть упакован в объекты Timestamp, которые являются подклассами datetime и содержат метаданные.

С версии psndas 2.0: различные числовые атрибуты даты/времени (день, месяц, год и т. д.) теперь имеют тип dtype: int32. Раньше у них был dtype: int64.

Объект DatetimeIndex.dt]Series.dt дает доступ к атрибутам и методам объекта Timestamp, который хранится в DatetimeIndex как элемент массива.

Здесь не будет описание аргументов конструктора класса DatetimeIndex, т.к. указание столбца с объектами Timestamp в качестве индекса Series/DataFrame автоматически конвертируется в DatetimeIndex.

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

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

>>> import pandas as pd
>>> import numpy as np
>>> import datetime
>>> start = datetime.datetime(2023, 1, 1)
>>> end = datetime.datetime(2024, 1, 1)
>>> rng = pd.date_range(start, end, freq="BME")
>>> ts = pd.Series(np.random.randn(len(rng)), index=rng)
>>> ts.index
# DatetimeIndex(['2023-01-31', '2023-02-28', '2023-03-31', '2023-04-28',
#                '2023-05-31', '2023-06-30', '2023-07-31', '2023-08-31',
#                '2023-09-29', '2023-10-31', '2023-11-30', '2023-12-29'],
#               dtype='datetime64[ns]', freq='BME')

>>> ts[:5].index
# DatetimeIndex(['2023-01-31', '2023-02-28', '2023-03-31', '2023-04-28',
#                '2023-05-31'],
#               dtype='datetime64[ns]', freq='BME')

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

Частичный срез данных DatetimeIndex

Строки с датой/временем могут быть переданы в качестве индексации, они преобразуются в метки даты/времени:

>>> ts["1/31/2023"]
# 1.1121734807631107

>>> ts[datetime.datetime(2023, 12, 25):]
# 2023-12-29    0.864635
# Freq: BME, dtype: float64

>>> ts["10/31/2023":"12/31/2023"]
# 2023-10-31    0.009955
# 2023-11-30   -0.645742
# 2023-12-29    0.864635
# Freq: BME, dtype: float64

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

>>> ts["2023"]
# 2023-01-31    1.112173
# 2023-02-28    0.878389
# 2023-03-31   -0.551219
# 2023-04-28    1.606600
# 2023-05-31    0.607183
# 2023-06-30    1.789773
# 2023-07-31    0.275235
# 2023-08-31    1.386999
# 2023-09-29   -0.599426
# 2023-10-31    0.009955
# 2023-11-30   -0.645742
# 2023-12-29    0.864635
# Freq: BME, dtype: float64

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

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

>>> dft
#                             A
# 2023-01-01 00:00:00  0.769829
# 2023-01-01 00:01:00 -0.666003
# 2023-01-01 00:02:00  0.625112
# 2023-01-01 00:03:00  0.843997
# 2023-01-01 00:04:00  0.196839
# ...                       ...
# 2023-03-11 10:35:00  0.905294
# 2023-03-11 10:36:00  0.330765
# 2023-03-11 10:37:00 -0.490381
# 2023-03-11 10:38:00  0.763424
# 2023-03-11 10:39:00 -1.347089
# 
# [100000 rows x 1 columns]

>>> dft.loc["2023"]
#                             A
# 2023-01-01 00:00:00  0.769829
# 2023-01-01 00:01:00 -0.666003
# 2023-01-01 00:02:00  0.625112
# 2023-01-01 00:03:00  0.843997
# 2023-01-01 00:04:00  0.196839
# ...                       ...
# 2023-03-11 10:35:00  0.905294
# 2023-03-11 10:36:00  0.330765
# 2023-03-11 10:37:00 -0.490381
# 2023-03-11 10:38:00  0.763424
# 2023-03-11 10:39:00 -1.347089
# 
# [100000 rows x 1 columns]

Срез начинается в самый первый день месяца и включает последнюю дату/время месяца:

>>> dft["2023-1":"2023-2"]
#                             A
# 2023-01-01 00:00:00  0.769829
# 2023-01-01 00:01:00 -0.666003
# 2023-01-01 00:02:00  0.625112
# 2023-01-01 00:03:00  0.843997
# 2023-01-01 00:04:00  0.196839
# ...                       ...
# 2023-02-28 23:55:00 -1.354526
# 2023-02-28 23:56:00  1.456907
# 2023-02-28 23:57:00  0.668234
# 2023-02-28 23:58:00  1.817265
# 2023-02-28 23:59:00 -0.101594
# 
# [84960 rows x 1 columns]

Укажем в качестве конца среза - время, которое включает все время последнего дня:

>>> dft["2023-1":"2023-2-28"]
#                             A
# 2023-01-01 00:00:00  0.769829
# 2023-01-01 00:01:00 -0.666003
# 2023-01-01 00:02:00  0.625112
# 2023-01-01 00:03:00  0.843997
# 2023-01-01 00:04:00  0.196839
# ...                       ...
# 2023-02-28 23:55:00 -1.354526
# 2023-02-28 23:56:00  1.456907
# 2023-02-28 23:57:00  0.668234
# 2023-02-28 23:58:00  1.817265
# 2023-02-28 23:59:00 -0.101594
# 
# [84960 rows x 1 columns]

В следующем примере укажем точное время конца среза (не совпадает с указанным выше):

>>> dft["2023-1":"2023-2-28 00:00:00"]
#                             A
# 2023-01-01 00:00:00  0.769829
# 2023-01-01 00:01:00 -0.666003
# 2023-01-01 00:02:00  0.625112
# 2023-01-01 00:03:00  0.843997
# 2023-01-01 00:04:00  0.196839
# ...                       ...
# 2023-02-27 23:56:00  0.112822
# 2023-02-27 23:57:00  0.248225
# 2023-02-27 23:58:00  0.317921
# 2023-02-27 23:59:00  0.478025
# 2023-02-28 00:00:00  0.636293
# 
# [83521 rows x 1 columns]

Выбор данных останавливаемся на включенной конечной точке, т.к. она является частью индекса:

>>> dft["2023-1-15":"2023-1-15 12:30:00"]
#                             A
# 2023-01-15 00:00:00 -1.098676
# 2023-01-15 00:01:00  0.123529
# 2023-01-15 00:02:00  0.749259
# 2023-01-15 00:03:00  1.322490
# 2023-01-15 00:04:00  0.339014
# ...                       ...
# 2023-01-15 12:26:00 -1.590723
# 2023-01-15 12:27:00  0.048567
# 2023-01-15 12:28:00  0.095829
# 2023-01-15 12:29:00 -0.202293
# 2023-01-15 12:30:00 -0.877787
# 
# [751 rows x 1 columns]

Частичная выбор данных DatetimeIndex также работает в DataFrame с a MultiIndex:

dft2 = pd.DataFrame(
    np.random.randn(20, 1),
    columns=["A"],
    index=pd.MultiIndex.from_product(
        [pd.date_range("20230101", periods=10, freq="12h"), ["a", "b"]]
    ),
)

>>> dft2
#                               A
# 2023-01-01 00:00:00 a  1.487131
#                     b  0.942668
# 2023-01-01 12:00:00 a  0.554916
#                     b -1.761021
# 2023-01-02 00:00:00 a  1.717367
#                     b -0.821675
# 2023-01-02 12:00:00 a  0.737373
#                     b  0.348934
# 2023-01-03 00:00:00 a  0.102362
#                     b -1.056135
# 2023-01-03 12:00:00 a  1.307184
#                     b -1.302244
# 2023-01-04 00:00:00 a  0.768927
#                     b  1.215484
# 2023-01-04 12:00:00 a  1.551029
#                     b  0.231425
# 2023-01-05 00:00:00 a  0.888348
#                     b  0.022093
# 2023-01-05 12:00:00 a -0.726614
#                     b -0.283663

>>> dft2.loc["2023-01-05"]
#                               A
# 2023-01-05 00:00:00 a  0.888348
#                     b  0.022093
# 2023-01-05 12:00:00 a -0.726614
#                     b -0.283663

>>> idx = pd.IndexSlice
>>> dft2 = dft2.swaplevel(0, 1).sort_index()
>>> dft2.loc[idx[:, "2023-01-05"], :]
#                               A
# a 2023-01-05 00:00:00  0.888348
#   2023-01-05 12:00:00 -0.726614
# b 2023-01-05 00:00:00  0.022093
#   2023-01-05 12:00:00 -0.283663

Срезы даты/времени и точное совпадения

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

Рассмотрим объект серии с индексом минимального разрешения:

series_minute = pd.Series(
    [1, 2, 3],
    pd.DatetimeIndex(
        ["2023-12-31 23:59:00", "2024-01-01 00:00:00", "2024-01-01 00:02:00"]
    ),
)

>>> series_minute.index.resolution
# 'minute'

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

>>> series_minute["2023-12-31 23"]
# 2023-12-31 23:59:00    1
# dtype: int64

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

>>> series_minute["2023-12-31 23:59"]
# 1
>>> series_minute["2023-12-31 23:59:00"]
# 1

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

series_second = pd.Series(
    [1, 2, 3],
    pd.DatetimeIndex(
        ["2023-12-31 23:59:59", "2024-01-01 00:00:00", "2024-01-01 00:00:01"]
    ),
)

>>> series_second.index.resolution
# 'second'

>>> series_second["2023-12-31 23:59"]
# 2023-12-31 23:59:59    1
# dtype: int64

Если строка с меткой даты/времени обрабатывается как фрагмент, то ее также можно использовать для индексации фрейма данных с помощью свойства DataFrame.loc[].

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

>>> dft_minute.loc["2023-12-31 23"]
#                      a  b
# 2023-12-31 23:59:00  1  4

Предупреждение: если строка с меткой даты/времени рассматривается как точное совпадение, то выбор DataFrame[...] будет осуществляться по столбцам, а не по строкам. Например, dft_minute['2023-12-31 23:59'] вызовет ошибку KeyError, т.к. '2023-12-31 23:59' имеет то же разрешение, что и индекс, а столбца с таким именем нет :

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

>>> dft_minute.loc["2023-12-31 23:59"]
# a    1
# b    4
# Name: 2023-12-31 23:59:00, dtype: int64

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

series_monthly = pd.Series(
    # обратите внимание, что `DatetimeIndex` 
    # строится с периодичностью - месяц
    [1, 2, 3], pd.DatetimeIndex(["2023-12", "2024-01", "2024-02"])
)

# а разрешение `DatetimeIndex` - 1 день
>>> series_monthly.index.resolution
# 'day'

# возвращает `Series`
>>> series_monthly["2023-12"]  
# 2023-12-01    1
# dtype: int64

Использование объектов datetime для выбора данных

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

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

>>> dft[datetime.datetime(2023, 1, 1): datetime.datetime(2023, 2, 28)]
#                             A
# 2023-01-01 00:00:00  0.769829
# 2023-01-01 00:01:00 -0.666003
# 2023-01-01 00:02:00  0.625112
# 2023-01-01 00:03:00  0.843997
# 2023-01-01 00:04:00  0.196839
# ...                       ...
# 2023-02-27 23:56:00  0.112822
# 2023-02-27 23:57:00  0.248225
# 2023-02-27 23:58:00  0.317921
# 2023-02-27 23:59:00  0.478025
# 2023-02-28 00:00:00  0.636293
# 
# [83521 rows x 1 columns]

Без каких-либо настроек по умолчанию.

>>> dft[
...     datetime.datetime(2023, 1, 1, 10, 12, 0): datetime.datetime(
...         2023, 2, 28, 10, 12, 0
...     )
... ]
#                             A
# 2023-01-01 10:12:00 -2.073523
# 2023-01-01 10:13:00  1.568231
# 2023-01-01 10:14:00  0.093409
# 2023-01-01 10:15:00 -1.062306
# 2023-01-01 10:16:00  0.823143
# ...                       ...
# 2023-02-28 10:08:00 -1.508005
# 2023-02-28 10:09:00  1.255278
# 2023-02-28 10:10:00 -1.354728
# 2023-02-28 10:11:00 -0.564761
# 2023-02-28 10:12:00 -0.923603
# 
# [83521 rows x 1 columns]

Усечение .truncate() и необычная индексация

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

>>> rng2 = pd.date_range("2023-01-01", "2024-01-01", freq="W")
>>> ts2 = pd.Series(np.random.randn(len(rng2)), index=rng2)
>>> ts2.truncate(before="2023-11", after="2023-12")
# 2023-11-05   -0.380798
# 2023-11-12    0.573212
# 2023-11-19   -0.375979
# 2023-11-26    0.970585
# Freq: W-SUN, dtype: float64

>>> ts2["2023-11":"2023-12"]
# 2023-11-05   -0.380798
# 2023-11-12    0.573212
# 2023-11-19   -0.375979
# 2023-11-26    0.970585
# 2023-12-03   -0.739985
# 2023-12-10   -0.616425
# 2023-12-17    0.858252
# 2023-12-24   -1.481935
# 2023-12-31   -1.929553
# Freq: W-SUN, dtype: float64

Даже сложная причудливая индексация, которая нарушает регулярность частоты DatetimeIndex, приведет к созданию DatetimeIndex, при этом частота будет потеряна:

>>> ts2.iloc[[0, 2, 6]].index
# DatetimeIndex(['2023-01-01', '2023-01-15', '2023-02-12'], dtype='datetime64[ns]', freq=None)