Как извлечь временные метки, когда, например, категориальный временной ряд pandas меняет состояние - PullRequest
1 голос
/ 17 апреля 2020

У меня недавно была проблема, когда временной ряд pandas содержал сигнал, который мог принимать несколько состояний, и меня интересовали начальная и конечная временные метки каждого состояния, чтобы я мог построить временные интервалы для каждого события. Входной сигнал представлял собой серию Pandas с индексом временной метки, и значения могли быть целыми числами (например, числовое представление категории) или NaN. Для NaN я мог бы предположить, что с момента последнего состояния (ffill это в основном исправило бы это состояние) изменений не было и что изменение состояния произошло именно тогда, когда оно было зарегистрировано (так что график на самом деле должен быть пошаговой диаграммой, а не линейно интерполируется, как показано ниже).

Поскольку временные интервалы определяются их временем начала и временем окончания, меня интересует метод, который может извлечь пары (start time, end time) для временных интервалов, показанных в нижней части цифра.

Illustration of input signal and desired result

Данные:

import pandas as pd

data = [2,2,2,1,2,np.nan,np.nan,1,3,3,1,1,np.nan,
        2,1,np.nan,3,3,3,2,3,np.nan,3,1,2,1,3,3,1,
        np.nan,1,1,2,1,3,1,2,np.nan,2,1]
s = pd.Series(data=data, index=pd.date_range(start='1/1/2020', freq='S', periods=40))

Ответы [ 2 ]

0 голосов
/ 17 апреля 2020

Вот немного более компактный метод. Мы создадим метку для каждой группы, а затем используем groupby, чтобы определить, где начинается эта группа. Чтобы сформировать эти группы ffill для работы с NaN, возьмите разницу и проверьте, где это не 0 (т. Е. Оно меняется на любое состояние). Совокупность этой логической серии образует группы. Поскольку следующая группа должна начинаться, когда предыдущая группа заканчивается, мы shift получаем значения времени окончания.

gps = s.ffill().diff().fillna(0).ne(0).cumsum()

df = s.reset_index().groupby(gps.to_numpy()).agg(start=('index', 'min'))
df['stop'] = df['start'].shift(-1)

Выходные данные

print(df.apply(lambda x: x.dt.strftime('%M:%S')))
## If you want a list of tuples:
# [tuple(zip(df['start'].dt.strftime('%M:%S'), df['stop'].dt.strftime('%M:%S')))]

    start   stop
0   00:00  00:03
1   00:03  00:04
2   00:04  00:07
3   00:07  00:08
4   00:08  00:10
5   00:10  00:13
6   00:13  00:14
7   00:14  00:16
8   00:16  00:19
9   00:19  00:20
10  00:20  00:23
11  00:23  00:24
12  00:24  00:25
13  00:25  00:26
14  00:26  00:28
15  00:28  00:32
16  00:32  00:33
17  00:33  00:34
18  00:34  00:35
19  00:35  00:36
20  00:36  00:39
21  00:39    NaT   # Drop the last row if you don't want this
0 голосов
/ 17 апреля 2020

Хорошо, так что это метод, который я придумал. Если у кого-то есть более эффективный или элегантный подход, пожалуйста, поделитесь.

import numpy as np
import pandas as pd

# Create the example Pandas Time Series
data = [2,2,2,1,2,np.nan,np.nan,1,3,3,1,1,np.nan,2,1,np.nan,3,3,3,2,3,np.nan,3,1,2,1,3,3,1,np.nan,1,1,2,1,3,1,2,np.nan,2,1]
dt = pd.date_range(start='1/1/2020', freq='S', periods=40)
s = pd.Series(data=data, index=dt)

# Drop NAN and calculate the state changes (not changing states returns 0)
s_diff = s.dropna().diff()

# Since 0 means no state change, remove them
s_diff = s_diff.replace(0,np.nan).dropna()

# Create a series that start with the time serie's initial condition, and then just the state change differences between the next states.
s_diff = pd.concat([s[:1], s_diff])

# We can now to a cumulative sum that starts on the initial value and adds the changes to find the actual states
s_states = s_diff.cumsum().astype(int)

# If the signal does not change in during the last timestamp, we need to ensure that we still get it.
s_states[s.index[-1]] = int(s[-1])

# Extract pairs of (start, end) timestamps for defining the timeslots. The .strftime method is only applied for readability. The following would probably be more useful:
# [(s_states.index[i], s_states.index[i+1] for i in range(len(s_states)-1)]
[(s_states.index[i].strftime('%M:%S'), s_states.index[i+1].strftime('%M:%S')) for i in range(len(s_states)-1)]
Out:
[('00:00', '00:03'),
 ('00:03', '00:04'),
 ('00:04', '00:07'),
 ('00:07', '00:08'),
 ('00:08', '00:10'),
 ('00:10', '00:13'),
 ('00:13', '00:14'),
 ('00:14', '00:16'),
 ('00:16', '00:19'),
 ('00:19', '00:20'),
 ('00:20', '00:23'),
 ('00:23', '00:24'),
 ('00:24', '00:25'),
 ('00:25', '00:26'),
 ('00:26', '00:28'),
 ('00:28', '00:32'),
 ('00:32', '00:33'),
 ('00:33', '00:34'),
 ('00:34', '00:35'),
 ('00:35', '00:36'),
 ('00:36', '00:39')]
...