2013-01-16 23 views
20

Mam pandas.DataFrame z pomiarami wykonanymi w kolejnych punktach w czasie. Wraz z każdym pomiarem obserwowany system posiadał odrębny stan w każdym punkcie czasowym. Dlatego DataFrame zawiera również kolumnę ze stanem systemu przy każdym pomiarze. Zmiany stanu są znacznie wolniejsze niż interwał pomiaru. W rezultacie, kolumna wskazuje stany może wyglądać następująco (index: stan):Wyszukiwanie kolejnych segmentów w ramce danych pandy

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 

Czy istnieje prosty sposób pobrać indeksy każdego segmentu kolejno równych państw. Oznacza to, że chciałbym uzyskać coś takiego:

[[1,2,3,4], [5,6,7,8], [9,10,11,12,13]] 

Wynik może być również w czymś innym niż zwykłe listy.

Jedyne rozwiązanie, o jakim mogłem myśleć, to ręczne powtarzanie wierszy, znajdowanie punktów zmiany segmentu i rekonstrukcja indeksów z tych punktów zmiany, ale mam nadzieję, że jest łatwiejsze rozwiązanie.

Odpowiedz

35

One-liner:

df.reset_index().groupby('A')['index'].apply(np.array) 

Kod na przykład:

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] 

Można również bezpośredni dostęp do informacji z obiektu 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) 

Aby zaradzić tej sytuacji, że DSM wymieniony mógłby zrobić coś takiego:

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 

Teraz GroupBy obie kolumny i zastosować funkcję lambda:

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] 
+12

Zakłada się, że wartości nie powtarzają się w nieciągłych segmentach - na przykład 'DataFrame ([3] * 4 + [4] * 4 + [1] * 4 + [3] * 4, kolumny = ['A "])" umieści dwie grupy po 3 w tej samej grupie. Możesz skanować te z przerwami, ale to tylko kolejna wersja oryginalnego problemu. Być może istnieje sposób na to, aby pandy "groupby" zachowywały się bardziej jak "itertools.groupby". – DSM

+0

Dzięki, twoje drugie rozwiązanie działa dobrze. Tak naprawdę mam sytuację opisaną przez DSM. – languitar

+0

Jak można to zrobić, jeśli chcesz chcieć grupować według jakiegoś odchylenia (np. Grupy zawierają wartości, gdzie wszystkie wartości mieszczą się w przedziale -1 -1 sąsiednich wartości w oryginalnym zbiorze) – ryanjdillon

15

Można użyć np.diff(), aby sprawdzić, gdzie segment zaczyna się/kończy i iterować na tych wynikach. Jest to bardzo proste rozwiązanie, więc prawdopodobnie nie najbardziej wydajne.

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 

Wyniki w:

[1 2 3 4 5] 
[ 6 7 8 9 10] 
[11 12 13 14] 
[15 16] 
[17 18 19] 
+0

Dzięki, rzeczywiście rozwiązanie przez Zelazny7 jest wygodniejsze, ponieważ lubię przechowywać segmenty w DataFrame i automatycznie to osiąga. – languitar

+0

Dzięki Rutger. 'np.diff()' to świetna propozycja tutaj. – ryanjdillon

Powiązane problemy