Во многих случаях данные, которые мы хотим проанализировать, имеют временную часть. Подумайте о таких данных, как дневная температура или осадки, цены на акции, данные о продажах, посещаемость студентов или события, такие как щелчок или просмотр в веб-приложении. В источниках данных нет недостатка, и новые источники постоянно добавляются. Поэтому большинству пользователей панд в какой-то момент необходимо будет ознакомиться с данными временных рядов.
Временной ряд - это просто пандыDataFrame
илиSeries
, который имеет временной индекс. Значения во временном ряду могут быть чем угодно, что может быть включено в контейнер, к ним просто обращаются со значениями даты или времени. В pandas контейнером временных рядов можно манипулировать разными способами, но в этом посте я сосредоточусь только на основах индексации. Понимание того, как работают индексы, важно для исследования данных и использования более продвинутых функций.
Индекс даты (DatetimeIndex
В пандах,DatetimeIndex
используется для пандSeries
иDataFrame
s обеспечивает индексирование, которое работает так же, как и другие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.