Индексация данных временных рядов в пандах

задняя часть

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

Временной ряд - это просто пандыDataFrameилиSeries, который имеет временной индекс. Значения во временном ряду могут быть чем угодно, что может быть включено в контейнер, к ним просто обращаются со значениями даты или времени. В pandas контейнером временных рядов можно манипулировать разными способами, но в этом посте я сосредоточусь только на основах индексации. Понимание того, как работают индексы, важно для исследования данных и использования более продвинутых функций.

Индекс даты (DatetimeIndex

В пандах,DatetimeIndexиспользуется для пандSeriesиDataFrames обеспечивает индексирование, которое работает так же, как и другиеIndexТот же тип, но предоставляет специальные функции для операций с временными рядами. Сначала мы познакомимся с другимиIndexОбщие черты типов, а затем поговорим об основах частичного индексирования строк.

Прежде чем мы начнем, есть одно предостережение. Ваш индекс должен быть отсортирован, иначе вы можете получить странные результаты.

пример

Чтобы показать, как работает эта функция, давайте создадим пример данных временного ряда с разным временным разрешением.

import pandas as pd
import numpy as np

import datetime

# this is an easy way to create a DatetimeIndex
# both dates are inclusive
d_range = pd.date_range("2021-01-01", "2021-01-20")

# this creates another DatetimeIndex, 10000 minutes long
m_range = pd.date_range("2021-01-01", periods=10000, freq="T")

# daily data in a Series
daily = pd.Series(np.random.rand(len(d_range)), index=d_range)
# minute data in a DataFrame
minute = pd.DataFrame(np.random.rand(len(m_range), 1),
                      columns=["value"],
                      index=m_range)

# time boundaries not on the minute boundary, add some random jitter
mr_range = m_range + pd.Series([pd.Timedelta(microseconds=1_000_000.0 * s)
                                for s in np.random.rand(len(m_range))]) 
# minute data in a DataFrame, but at a higher resolution
minute2 = pd.DataFrame(np.random.rand(len(mr_range), 1),
                       columns=["value"],
                       index=mr_range)
daily.head()
2021-01-01    0.293300
2021-01-02    0.921466
2021-01-03    0.040813
2021-01-04    0.107230
2021-01-05    0.201100
Freq: D, dtype: float64
minute.head()
                        value
2021-01-01 00:00:00  0.124186
2021-01-01 00:01:00  0.542545
2021-01-01 00:02:00  0.557347
2021-01-01 00:03:00  0.834881
2021-01-01 00:04:00  0.732195
minute2.head()
                               value
2021-01-01 00:00:00.641049  0.527961
2021-01-01 00:01:00.088244  0.142192
2021-01-01 00:02:00.976195  0.269042
2021-01-01 00:03:00.922019  0.509333
2021-01-01 00:04:00.452614  0.646703

разрешение

ОдинDatetimeIndexимеет разрешение, которое указываетIndexУровень, на котором индексируются данные. Три индекса, созданные выше, имеют разное разрешение. Это повлияет на то, как мы будем индексировать позже.

print("daily:", daily.index.resolution)
print("minute:", minute.index.resolution)
print("randomized minute:", minute2.index.resolution)
daily: day
minute: minute
randomized minute: microsecond

Типичная индексация

Прежде чем мы перейдем к некоторым «особым» способам для пандSeriesилиDataFrameиDatetimeIndexПрежде чем создавать индекс, давайте рассмотрим некоторые типичные функции индексирования.

Базовые знания

Я уже рассказывал об основах индексации, поэтому не буду вдаваться здесь в подробности. Однако важно осознавать, что,DatetimeIndex, работает так же, как и любой другой индекс в pandas, но с дополнительной функциональностью. Дополнительные функции могут быть более полезными и удобными, но поторопитесь, эти детали — следующий шаг). Если вы уже знакомы с основами индексации, вы можете пропустить это, пока не получите индекс частичной строки. Если вы не читали мою статью об индексировании, вам следует начать сбазовые знанияНачинать,ПотомНачните с этого.

использовать что-то вродеdatetimeпара объектовDatetimeIndexдля индексации будет использоватьсяточный индекс.

getitemон же оператор индексации массива ([])

когда используешьdatetimeПри индексировании подобных объектов нам необходимо соответствовать разрешению индекса.

Это выглядит довольно очевидным для наших ежедневных временных рядов.

daily[pd.Timestamp("2021-01-01")]
0.29330017699861666
try:
    minute[pd.Timestamp("2021-01-01 00:00:00")]
except KeyError as ke:
    print(ke)
Timestamp('2021-01-01 00:00:00')

этоKeyError,Так какDataFrame, используя один аргумент[]Оператор будет искать _столбец_, а не строку. в нашемDataFrame, у нас естьvalue, поэтому приведенный выше код ищет один столбец. Поскольку столбца с таким именем нет, существуетKeyError. Мы будем использовать другие методы дляDataFrameСтроки индексируются.

.ilocпоказатель

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

daily.iloc[0]
0.29330017699861666
minute.iloc[-1]
value    0.999354
Name: 2021-01-07 22:39:00, dtype: float64
minute2.iloc[4]
value    0.646703
Name: 2021-01-01 00:04:00.452614, dtype: float64

.locпоказатель

когда используешьdatetime-подобные объекты, вам нужно точное совпадение с одним индексом. Важно понимать, что когда вы делаетеdatetimeилиpd.Timestampобъект, все поля, которые вы явно не укажете, по умолчанию будут равны 0.

jan1 = datetime.datetime(2021, 1, 1)
daily.loc[jan1]
0.29330017699861666
minute.loc[jan1]  # the defaults for hour, minute, second make this work
value    0.124186
Name: 2021-01-01 00:00:00, dtype: float64
try:
    # we don't have that exact time, due to the jitter
    minute2.loc[jan1] 
except KeyError as ke:
    print("Missing in index: ", ke)
# but we do have a value on that day
# we could construct it manually to the microsecond if needed
jan1_ms = datetime.datetime(2021, 1, 1, 0, 0, 0, microsecond=minute2.index[0].microsecond)
minute2.loc[jan1_ms] 
Missing in index:  datetime.datetime(2021, 1, 1, 0, 0)
value    0.527961
Name: 2021-01-01 00:00:00.641049, dtype: float64

Фрагментация

Нарезка с целыми числами работает, как и ожидалось, вы можетеэто здесьПодробнее об обычных срезах. Но вот несколько примеров «обычного» слайсинга, который работает с оператором индексации массива ([]) или.ilocиндексаторы работают вместе.

daily[0:2] # first two, end is not inclusive
2021-01-01    0.293300
2021-01-02    0.921466
Freq: D, dtype: float64
minute[0:2] # same
                        value
2021-01-01 00:00:00  0.124186
2021-01-01 00:01:00  0.542545
minute2[1:5:2]  # every other
                               value
2021-01-01 00:01:00.088244  0.142192
2021-01-01 00:03:00.922019  0.509333
minute2.iloc[1:5:2] # works with the iloc indexer as well
                               value
2021-01-01 00:01:00.088244  0.142192
2021-01-01 00:03:00.922019  0.509333

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

daily[datetime.date(2021,1,1):datetime.date(2021, 1,3)] # end is inclusive
2021-01-01    0.293300
2021-01-02    0.921466
2021-01-03    0.040813
Freq: D, dtype: float64
minute[datetime.datetime(2021, 1, 1): datetime.datetime(2021, 1, 1, 0, 2, 0)]
                        value
2021-01-01 00:00:00  0.124186
2021-01-01 00:01:00  0.542545
2021-01-01 00:02:00  0.557347
minute2[datetime.datetime(2021, 1, 1): datetime.datetime(2021, 1, 1, 0, 2, 0)]
                               value
2021-01-01 00:00:00.641049  0.527961
2021-01-01 00:01:00.088244  0.142192

Это подразделение[]и.loc, но не включая.iloc,как и ожидалось. Помните,.ilocиндекс, используемый для целочисленных смещений.

minute2.loc[datetime.datetime(2021, 1, 1): datetime.datetime(2021, 1, 1, 0, 2, 0)]
                               value
2021-01-01 00:00:00.641049  0.527961
2021-01-01 00:01:00.088244  0.142192
try:
    # no! use integers with iloc
    minute2.iloc[datetime.datetime(2021, 1, 1): datetime.datetime(2021, 1, 1, 0, 2, 0)]
except TypeError as te:
    print(te)
cannot do positional indexing on DatetimeIndex with these indexers [2021-01-01 00:00:00] of type datetime

специальный индекс для строк

Теперь вещи становятся действительно интересными и полезными. Частичная индексация строк может быть очень полезна при работе с данными временных рядов и дешевле, чем использованиеdatetimeОбъекты доставляют гораздо меньше хлопот. Я знаю, что мы начали с объектов, но теперь вы видите, что строки очень полезны для интерактивного использования и исследования. Вы можете передать строку, которая может быть проанализирована как полная дата, и ее можно использовать для индексации.

daily["2021-01-04"]
0.10723013753233923
minute.loc["2021-01-01 00:03:00"]
value    0.834881
Name: 2021-01-01 00:03:00, dtype: float64

Строки также можно использовать для сегментирования.

minute.loc["2021-01-01 00:03:00":"2021-01-01 00:05:00"] # end is inclusive
                        value
2021-01-01 00:03:00  0.834881
2021-01-01 00:04:00  0.732195
2021-01-01 00:05:00  0.291089

индекс части строки

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

daily["2021"]    # all items match (since they were all in 2021)
daily["2021-01"] # this one as well (and only in January for our data)
2021-01-01    0.293300
2021-01-02    0.921466
2021-01-03    0.040813
2021-01-04    0.107230
2021-01-05    0.201100
2021-01-06    0.534822
2021-01-07    0.070303
2021-01-08    0.413683
2021-01-09    0.316605
2021-01-10    0.438853
2021-01-11    0.258554
2021-01-12    0.473523
2021-01-13    0.497695
2021-01-14    0.250582
2021-01-15    0.861521
2021-01-16    0.589558
2021-01-17    0.574399
2021-01-18    0.951196
2021-01-19    0.967695
2021-01-20    0.082931
Freq: D, dtype: float64

вы также можетеDataFrameделай так дальше.

minute["2021-01-01"]
<ipython-input-67-96027d36d9fe>:1: FutureWarning: Indexing a DataFrame with a datetimelike index using a single string to slice the rows, like `frame[string]`, is deprecated and will be removed in a future version. Use `frame.loc[string]` instead.
  minute["2021-01-01"]
                        value
2021-01-01 00:00:00  0.124186
2021-01-01 00:01:00  0.542545
2021-01-01 00:02:00  0.557347
2021-01-01 00:03:00  0.834881
2021-01-01 00:04:00  0.732195
...                       ...
2021-01-01 23:55:00  0.687931
2021-01-01 23:56:00  0.001978
2021-01-01 23:57:00  0.770587
2021-01-01 23:58:00  0.154300
2021-01-01 23:59:00  0.777973

[1440 rows x 1 columns]

Видите это предупреждение об устаревании? вы больше не должны использовать[]выполнятьDataFrameСтроковый индекс (как мы видели выше,[]следует использовать для доступа к столбцу, а не к строке). В зависимости от того, найдено ли значение в индексе, вы можете получить сообщение об ошибке или предупреждение. использовать.loc, так что вы можете избежать путаницы.

minute2.loc["2021-01-01"]
                               value
2021-01-01 00:00:00.641049  0.527961
2021-01-01 00:01:00.088244  0.142192
2021-01-01 00:02:00.976195  0.269042
2021-01-01 00:03:00.922019  0.509333
2021-01-01 00:04:00.452614  0.646703
...                              ...
2021-01-01 23:55:00.642728  0.749619
2021-01-01 23:56:00.238864  0.053027
2021-01-01 23:57:00.168598  0.598910
2021-01-01 23:58:00.103543  0.107069
2021-01-01 23:59:00.687053  0.941584

[1440 rows x 1 columns]

Если используется разбиение строки, конечная точка включает _все времена суток_.

minute2.loc["2021-01-01":"2021-01-02"]
                               value
2021-01-01 00:00:00.641049  0.527961
2021-01-01 00:01:00.088244  0.142192
2021-01-01 00:02:00.976195  0.269042
2021-01-01 00:03:00.922019  0.509333
2021-01-01 00:04:00.452614  0.646703
...                              ...
2021-01-02 23:55:00.604411  0.987777
2021-01-02 23:56:00.134674  0.159338
2021-01-02 23:57:00.508329  0.973378
2021-01-02 23:58:00.573397  0.223098
2021-01-02 23:59:00.751779  0.685637

[2880 rows x 1 columns]

Однако, если мы включим время, оно будет включать в себя неполные эпохи, а если указать микросекунды, то оно обрежет конечную точку до микросекунд.

minute2.loc["2021-01-01":"2021-01-02 13:32:01"]
                               value
2021-01-01 00:00:00.641049  0.527961
2021-01-01 00:01:00.088244  0.142192
2021-01-01 00:02:00.976195  0.269042
2021-01-01 00:03:00.922019  0.509333
2021-01-01 00:04:00.452614  0.646703
...                              ...
2021-01-02 13:28:00.925951  0.969213
2021-01-02 13:29:00.037827  0.758476
2021-01-02 13:30:00.309543  0.473163
2021-01-02 13:31:00.363813  0.846199
2021-01-02 13:32:00.867343  0.007899

[2253 rows x 1 columns]

Срез и точное совпадение

Наши три набора данных имеют разное разрешение в своих индексах: дни, минуты и микросекунды соответственно. Если мы передаем параметр строкового индекса, а разрешение строки не такое точное, как индекс, она будет рассматриваться как срез. Если он такой же или более точный, он считается точным совпадением. Давайте использовать наши микросекунды (minute2) и минут (minute) пример данных разрешения. Обратите внимание, что всякий раз, когда вы получаете срезDataFrame, возвращаемое значениеDataFrame. Когда это точное совпадение, этоSeries.

minute2.loc["2021-01-01"]          # slice - the entire day
minute2.loc["2021-01-01 00"]       # slice - the first hour of the day
minute2.loc["2021-01-01 00:00"]    # slice - the first minute of the day
minute2.loc["2021-01-01 00:00:00"] # slice - the first minute and second of the day
                               value
2021-01-01 00:00:00.641049  0.527961
print(str(minute2.index[0]))       # note the string representation include the full microseconds
minute2.loc[str(minute2.index[0])] # slice - this seems incorrect to me, should return Series not DataFrame
minute2.loc[minute2.index[0]]      # exact match
2021-01-01 00:00:00.641049
value    0.527961
Name: 2021-01-01 00:00:00.641049, dtype: float64
minute.loc["2021-01-01"]       # slice - the entire day
minute.loc["2021-01-01 00"]    # slice - the first hour of the day
minute.loc["2021-01-01 00:00"] # exact match
value    0.124186
Name: 2021-01-01 00:00:00, dtype: float64

Обратите внимание, что для совпадений строк с разрешением в микросекундах я не вижу точного совпадения (возвращаемое значениеSeries), но совпадение фрагмента (поскольку возвращаемое значение равноDataFrame). с минутным разрешениемDataFrame, он работает так, как я ожидал.

в виде

Один из способов справиться с проблемой такого типа — использоватьasof. Обычно, когда ваши данные случайны во времени или могут иметь пропущенные значения, лучше всего получить самые последние значения до определенного времени. Вы можете сделать это сами, но используйтеasof, который выглядит немного чище.

minute2.loc[:"2021-01-01 00:00:03"].iloc[-1]
# vs
minute2.asof("2021-01-01 00:00:03")
value    0.527961
Name: 2021-01-01 00:00:03, dtype: float64

обрезать

вы также можете использоватьtruncate, что немного похоже на сегментацию. вы указываетеbeforeилиafter(или оба), чтобы указать усечение данных. В отличие от срезов,truncateВключите все значения, которые частично соответствуют дате, и примите 0 для любого неопределенного значения даты.

minute2.truncate(after="2021-01-01 00:00:03")
                               value
2021-01-01 00:00:00.641049  0.527961

Суммировать

Теперь вы можете видеть, что данные временных рядов индексируются иначе, чем другие типы в pandas.Index, с некоторыми отличиями. Зная сегментацию временных рядов, вы можете быстро просматривать данные временных рядов и быстро входить в более сложный анализ временных рядов.

The postIndexing time series data in pandasappeared first onwrighters.io.