Подтвердить что ты не робот

Поиск последовательных сегментов в кадре данных pandas

У меня есть 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]]

Результат может также быть в чем-то отличном от простых списков.

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

4b9b3361

Ответ 1

Однострочник:

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]

Ответ 2

Вы можете использовать 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]