2014-12-23 14 views
13

Próbuję liczyć się kolejnych dni w danych powrotnej equity - więc jeśli dzień jest dodatnia i ujemna 1 wynosi 0, lista y=[0,0,1,1,1,0,0,1,0,1,1] powinien powrócić z=[0,0,1,2,3,0,0,1,0,1,2].Liczenie rzędu wartość dodatnia w Pythonie tablicy

Doszedłem do roztworu, który jest czysty pod względem liczby linii kodu, ale jest bardzo powolny:

import pandas 
y=pandas.Series([0,0,1,1,1,0,0,1,0,1,1]) 
def f(x): 
    return reduce(lambda a,b:reduce((a+b)*b,x) 
z=pandas.expanding_apply(y,f) 

zgaduję Ja zapętlenie całej listy y zbyt wiele razy. Czy istnieje przyjemny sposób Pythonic osiągnięcia tego, co chcę, podczas gdy tylko raz przechodzę przez dane? Mógłbym napisać pętlę samemu, ale zastanawiałem się, czy istnieje lepszy sposób.

Dzięki!

+0

Czy potrzebujesz wyłącznie rozwiązania dla pand? –

Odpowiedz

4

dlaczego obsesja na punkcie ultra-pythonic sposób robienia rzeczy? czytelność + efektywność atutów "leet hackerz style."

ja po prostu to zrób tak:

a = [0,0,1,1,1,0,0,1,0,1,1] 
b = [0,0,0,0,0,0,0,0,0,0,0] 

for i in range(len(a)): 
    if a[i] == 1: 
     b[i] = b[i-1] + 1 
    else: 
     b[i] = 0 
+0

Myślę, że pętla jest nadal najlepszą rzeczą ... Tx – alex314159

+3

Jest to powolne, dlaczego używać pętli, gdy mamy moc obliczeń równoległych przez numpy –

2

Czy to szybciej? Rozumie się samo przez y tylko jeden raz ...

y=[0,0,1,1,1,0,0,1,0,1,1] 

def f(y): 
    z = [] 
    i = 0 
    for e in y: 
     if e == 0: 
      i = 0 
      z.append(e) 
     else: 
      z.append(e + i) 
      i += 1 
    return z 

f(y) 
+0

Każde tego rodzaju rozwiązanie, napisane w natywnym python, jest wolne. Używanie numpy, scipy lub skompilowanego rozwiązania, takiego jak cython, numba, pythran, pypy itp., Lub pisanie własnych rozszerzeń C++ może czasami dać ci więcej niż 1000x przyspieszenia. – osa

49

Może się to wydawać trochę magiczne, ale faktycznie wykorzystuje pewne wspólne idiomów: od pandas nie ma jeszcze piękny natywne wsparcie dla ciągłej groupby, często można znaleźć się potrzebujące czegoś takiego.

>>> y * (y.groupby((y != y.shift()).cumsum()).cumcount() + 1) 
0  0 
1  0 
2  1 
3  2 
4  3 
5  0 
6  0 
7  1 
8  0 
9  1 
10 2 
dtype: int64 

pewnego wyjaśnienia: po pierwsze, możemy porównać y przeciwko przesuniętą wersją siebie znaleźć, gdy grupy sąsiadujących rozpocząć:

>>> y != y.shift() 
0  True 
1  False 
2  True 
3  False 
4  False 
5  True 
6  False 
7  True 
8  True 
9  True 
10 False 
dtype: bool 

Następnie (od fałszywego i prawdziwego == 0 == 1) możemy zastosować skumulowaną sumę, aby uzyskać numer do grup:

>>> (y != y.shift()).cumsum() 
0  1 
1  1 
2  2 
3  2 
4  2 
5  3 
6  3 
7  4 
8  5 
9  6 
10 6 
dtype: int32 

możemy użyć groupby i cumcount dostać nam całkowitą liczy się w każdej grupie:

>>> y.groupby((y != y.shift()).cumsum()).cumcount() 
0  0 
1  1 
2  0 
3  1 
4  2 
5  0 
6  1 
7  0 
8  0 
9  0 
10 1 
dtype: int64 

Dodaj jeden:

>>> y.groupby((y != y.shift()).cumsum()).cumcount() + 1 
0  1 
1  2 
2  1 
3  2 
4  3 
5  1 
6  2 
7  1 
8  1 
9  1 
10 2 
dtype: int64 

I wreszcie zera wartości, gdzie mieliśmy do zera na początku:

>>> y * (y.groupby((y != y.shift()).cumsum()).cumcount() + 1) 
0  0 
1  0 
2  1 
3  2 
4  3 
5  0 
6  0 
7  1 
8  0 
9  1 
10 2 
dtype: int64 
+1

@CodingOrange: jest to zwykle kompromis pomiędzy wielokrotnymi przebiegami a szybkimi operacjami. Python iteracja jest powolna, więc przy korzystaniu z Long Series, im więcej możesz sprowadzić do rodzimych 'pandas' ops, tym lepiej będziesz. I odwrotnie, jeśli seria jest tak mała, jak jest w tym przykładzie, będziesz tracić więcej czasu na narzut. Dokładnie ten sam problem pojawia się podczas pisania wektoryzowanego kodu 'numpy'. – DSM

+0

Myślę, że niektóre czasy byłyby dobre na dużym wejściu –

+0

Bardzo magiczne! Trochę zbyt skomplikowany dla mnie, ale bardzo interesujący, tx – alex314159

2

Utrzymanie rzeczy prostych, przy użyciu jednej tablicy, jednej pętli i jednej warunkowej.

a = [0,0,1,1,1,0,0,1,0,1,1] 

for i in range(1, len(a)): 
    if a[i] == 1: 
     a[i] += a[i - 1] 
+0

Przypuszczam, że pętla jest nadal najlepszą rzeczą ... Działa to (z wyjątkiem, że niszczy listę, ale łatwa do naprawienia) thx – alex314159

4

Jeśli coś jest jasne, jest "pythonic". Szczerze mówiąc, nie mogę nawet sprawić, żeby twoje oryginalne rozwiązanie działało. Ponadto, jeśli to działa, jestem ciekawy, czy jest szybszy niż pętla. Czy porównałeś?

Teraz, od kiedy zaczęliśmy omawiać efektywność, oto kilka spostrzeżeń.

Pętle w Pythonie są z natury powolne, bez względu na to, co robisz.Oczywiście, jeśli używasz pand, używasz również numpy pod spodem, z wszystkimi zaletami wydajności. Po prostu nie niszczyć ich za pomocą pętli. Nie wspominając o tym, że listy w Pythonie zajmują o wiele więcej pamięci niż mogłoby się wydawać; potencjalnie znacznie więcej niż 8 bytes * length, ponieważ każda liczba całkowita może być zawinięta w osobny obiekt i umieszczona w osobnym obszarze w pamięci, i wskazana przez wskaźnik z listy.

Wektoryzacja dostarczona przez numpy powinna wystarczyć, JEŚLI możesz znaleźć jakiś sposób wyrażenia tej funkcji bez pętli. W rzeczywistości zastanawiam się, czy istnieje sposób na reprezentowanie go za pomocą wyrażeń takich jak A+B*C. Jeśli możesz skonstruować tę funkcję poza funkcjami w Lapack, możesz nawet potencjalnie pokonać zwykły kod C++ skompilowany z optymalizacją.

Można również użyć jednego z opracowanych podejść, aby przyspieszyć pętle. Zobacz rozwiązanie z Numba na numpy tablicach poniżej. Inną opcją jest użycie PyPy, choć prawdopodobnie nie można go poprawnie połączyć z pandami.

In [140]: import pandas as pd 
In [141]: import numpy as np 
In [143]: a=np.random.randint(2,size=1000000) 

# Try the simple approach 
In [147]: def simple(L): 
       for i in range(len(L)): 
        if L[i]==1: 
         L[i] += L[i-1] 


In [148]: %time simple(L) 
CPU times: user 255 ms, sys: 20.8 ms, total: 275 ms 
Wall time: 248 ms 


# Just-In-Time compilation 
In[149]: from numba import jit 
@jit   
def faster(z): 
    prev=0 
    for i in range(len(z)): 
     cur=z[i] 
     if cur==0: 
      prev=0 
     else: 
      prev=prev+cur 
      z[i]=prev 

In [151]: %time faster(a) 
CPU times: user 51.9 ms, sys: 1.12 ms, total: 53 ms 
Wall time: 51.9 ms 


In [159]: list(L)==list(a) 
Out[159]: True 

W rzeczywistości większość czasu w powyższym przykładzie została wydana na kompilację Just-In-Time. Zamiast tego (pamiętaj, aby skopiować, ponieważ funkcja zmienia tablicę).

b=a.copy() 
In [38]: %time faster(b) 
CPU times: user 55.1 ms, sys: 1.56 ms, total: 56.7 ms 
Wall time: 56.3 ms 

In [39]: %time faster(c) 
CPU times: user 10.8 ms, sys: 42 µs, total: 10.9 ms 
Wall time: 10.9 ms 

Więc dla kolejnych wywołań mamy 25x-przyspieszenie w porównaniu ze zwykłą wersją. Proponuję przeczytać High Performance Python, jeśli chcesz dowiedzieć się więcej.

+0

Nie wiedziałem o numbie, wydaje się interesujące - tx! – alex314159

Powiązane problemy