Поиск последовательных сегментов во фрейме данных панд
У меня есть pandas.DataFrame с измерениями, сделанными в последовательные моменты времени. Наряду с каждым измерением наблюдаемая система имела отличное состояние в каждый момент времени. Следовательно, DataFrame также содержит столбец с состоянием системы при каждом измерении. Изменения состояния намного медленнее, чем интервал измерения. В результате столбец с указанием состояний может выглядеть следующим образом (index: state):
1: 3
2: 3
3: 3
4: 3
5: 4
6: 4
7: 4
8: 4
9: 1
10: 1
11: 1
12: 1
13: 1
Есть ли простой способ получить индексы каждого сегмента последовательно равных состояний. Это означает, что я хотел бы получить что-то вроде этого:
[[1,2,3,4], [5,6,7,8], [9,10,11,12,13]]
Результат также может быть в чем-то отличном от простых списков.
Единственное решение, о котором я мог думать до сих пор, - это ручная итерация по строкам, поиск точек изменения сегмента и восстановление индексов по этим точкам изменения, но я надеюсь, что есть более простое решение.
3 ответа
Один лайнер:
df.reset_index().groupby('A')['index'].apply(np.array)
Код например:
In [1]: import numpy as np
In [2]: from pandas import *
In [3]: df = DataFrame([3]*4+[4]*4+[1]*4, columns=['A'])
In [4]: df
Out[4]:
A
0 3
1 3
2 3
3 3
4 4
5 4
6 4
7 4
8 1
9 1
10 1
11 1
In [5]: df.reset_index().groupby('A')['index'].apply(np.array)
Out[5]:
A
1 [8, 9, 10, 11]
3 [0, 1, 2, 3]
4 [4, 5, 6, 7]
Вы также можете напрямую получить доступ к информации из объекта groupby:
In [1]: grp = df.groupby('A')
In [2]: grp.indices
Out[2]:
{1L: array([ 8, 9, 10, 11], dtype=int64),
3L: array([0, 1, 2, 3], dtype=int64),
4L: array([4, 5, 6, 7], dtype=int64)}
In [3]: grp.indices[3]
Out[3]: array([0, 1, 2, 3], dtype=int64)
Чтобы разрешить ситуацию, о которой упоминал DSM, вы можете сделать что-то вроде:
In [1]: df['block'] = (df.A.shift(1) != df.A).astype(int).cumsum()
In [2]: df
Out[2]:
A block
0 3 1
1 3 1
2 3 1
3 3 1
4 4 2
5 4 2
6 4 2
7 4 2
8 1 3
9 1 3
10 1 3
11 1 3
12 3 4
13 3 4
14 3 4
15 3 4
Теперь сгруппируйте оба столбца и примените лямбда-функцию:
In [77]: df.reset_index().groupby(['A','block'])['index'].apply(np.array)
Out[77]:
A block
1 3 [8, 9, 10, 11]
3 1 [0, 1, 2, 3]
4 [12, 13, 14, 15]
4 2 [4, 5, 6, 7]
Вы можете использовать np.diff(), чтобы проверить, где начинается / заканчивается сегмент, и выполнить итерации по этим результатам. Это очень простое решение, поэтому, вероятно, не самое эффективное.
a = np.array([3,3,3,3,3,4,4,4,4,4,1,1,1,1,4,4,12,12,12])
prev = 0
splits = np.append(np.where(np.diff(a) != 0)[0],len(a)+1)+1
for split in splits:
print np.arange(1,a.size+1,1)[prev:split]
prev = split
Результаты в:
[1 2 3 4 5]
[ 6 7 8 9 10]
[11 12 13 14]
[15 16]
[17 18 19]
Вы можете использоватьitertools.groupby
:
from itertools import groupby
s = pd.Series([3, 3, 3, 4, 4, 4, 4, 1, 1, 1, 1, 1])
[list(i) for _, i in groupby(s)]
# or list(map(lambda x: list(x[1]), groupby(s)))
Выход:
[[3, 3, 3], [4, 4, 4, 4], [1, 1, 1, 1, 1]]