2014-12-23 16 views
13

Hisse dönüş verilerinde art arda günler saymaya çalışıyorum - bu nedenle, pozitif bir gün 1 ve negatif 0 ise, y=[0,0,1,1,1,0,0,1,0,1,1] listesinin bir listesi z=[0,0,1,2,3,0,0,1,0,1,2]'u döndürmelidir.Python dizisinde ardışık pozitif değer sayma

Ben satır kod sayısı açısından düzgün bir çözüm gelmek, ama çok yavaş olduğunu ettik

:

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) 

Ben tüm liste y döngü ediyorum tahmin ediyorum bir çok sefer. Sadece bir kez veriyi kullanırken istediğimi elde etmenin güzel bir Pythonic yolu var mı? Kendime bir döngü yazabilirim ama daha iyi bir yol olup olmadığını merak edebilirim.

Teşekkürler!

+0

Kesinlikle bir panda çözümü ister misiniz? –

cevap

4

neden bir şey yapmanın ultra-pythonik yolu ile takıntı? okunabilirlik + verimlilik koz "leet hackerz tarzı."

Öyle gibi yapmak sadece ediyorum

:

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

Ben döngü hala en iyi şey sanırım ... Tx – alex314159

+3

Yavaş olsa da, numol –

2

daha hızlı bundan mı? Sadece bu biraz büyülü görünebilir

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

tarafından paralel hesaplama gücüne sahip olduğunda döngü kullanmak neden yerli python, bu tür herhangi bir çözüm, yavaştır. Sayısal, scipy veya cython, numba, pythran, pypy, vb. Gibi derlenmiş bir çözümü veya kendi C++ uzantınızı yazmak bazen 1000x hızdan daha fazlasını verebilir. – osa

49

... y tek seferde geçer, ama aslında bazı ortak deyimleri kullanır: pandas henüz bitişik groupby için güzel yerel destek olmadığı için, sık sık bulacaksınız Böyle bir şeye ihtiyacım var.

>>> 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 

Bazı açıklama: birincisi, biz bitişik gruplar başladığında bulmak başlı başına kaymış bir versiyonuna karşı y karşılaştırmak:

>>> 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 

Sonra (Yalancı == 0 ve True == beri

>>> (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 

Bizkullanabilirsiniz: 1) ve gruplar için bir numara almak için kümülatif toplamı uygulayabilirsiniz Bir tane ekle

>>> 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 

:ve cumcount her grupta yukarı sayma bize bir tamsayı olsun

>>> 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 

Ve nihayet başlamak için biz sıfır vardı değerleri sıfır:

>>> 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: Bu, çoklu geçişler ile hızlı işlemlerin arasındaki alışılmadık bir tradeoff. Python yinelemesi yavaştır, bu yüzden uzun Seriler kullanılırken, daha çok yerel pandalar için aşağı doğru itebilirsiniz, daha iyi olacaksınız. Tersine, eğer dizi örnekte olduğu kadar küçükse, ek yükte daha fazla zaman harcarsınız. "Numpy" kodunu yazarken tam olarak aynı sorun ortaya çıkıyor. – DSM

+0

Bazı girişlerin büyük girişlerde iyi olacağını düşünüyorum. –

+0

Çok büyülü! Benim için biraz fazla karmaşık ama çok ilginç, tx – alex314159

2

Bir diziyi, bir döngüyü ve bir koşulu kullanarak işleri basit tutmak.

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

Döngü hala en iyi şey sanırım ... Bu çalışır (hariç listeyi yok ama düzeltmek kolay) thx – alex314159

4

Net bir şey varsa, bu "pythonic" dir. Açıkçası, orijinal çözümünüzü bile yapamam. Ayrıca, eğer işe yararsa, bir döngüden daha hızlı olup olmadığını merak ediyorum. Karşılaştırtınız mı?

Şimdi, verimliliği tartışmaya başladığımızdan beri, bazı bakış açıları var.

Python'daki döngüler, ne yaparsanız yapın, doğal olarak yavaştır.Tabii ki, pandalar kullanıyorsanız, tüm performans avantajlarıyla birlikte, altta numpy kullanıyorsunuz. Sadece döngü yaparak onları yok etme. Bu Python listelerinin düşündüğünüzden çok daha fazla bellek aldığını söylemez; Potansiyel olarak çok daha fazla 8 bytes * length, çünkü her tam sayı ayrı bir nesneye sarılabilir ve bellekte ayrı bir alana yerleştirilebilir ve listeden bir işaretçi tarafından işaret edilir. numpy tarafından sağlanan Vectorization işlemi yeterli olmalıdır. Bu işlevi döngü olmadan ifade etmenin bir yolunu bulmanız yeterli olacaktır. Aslında, A+B*C gibi ifadeleri kullanarak bunu temsil etmenin bir yolu olup olmadığını merak ediyorum. Bu işlevi Lapack numaralı fonksiyondan yapılandırabiliyorsanız, potansiyel olarak optimizasyonla derlenen sıradan C++ kodunu bile geçebilirsiniz.

Döngülerinizi hızlandırmak için derlenmiş yaklaşımlardan birini de kullanabilirsiniz. Aşağıdaki numpy dizilerinde Numba ile bir çözüme bakın. Başka bir seçenek de muhtemelen pandalarla düzgün bir şekilde birleştirememenize rağmen PyPy'u kullanmaktır.

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 

Aslında, Just-In-Time derleme harcanmıştır Yukarıdaki ikinci örnekte çoğu zaman. Bunun yerine (işlev diziyi değiştirdikçe kopyalamayı unutmayın).

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 

Yani sonraki çağrılar için biz basit sürümüne göre bir 25x-hızlanma var. Daha fazla bilgi edinmek için High Performance Python numaralı telefonu okumanızı tavsiye ederim.

+0

numba hakkında bilmiyordum, ilginç görünüyor - tx! – alex314159