2017-05-04 3 views
4

kann ich um Anregung bitten, effizienter (und schneller) zu iterieren? hier ist das Problem, ich bin auf der Suche nach einem Weg Null nach unten in einem Pandas Datenrahmen innerhalb einer bestimmten Fenstergröße zu propagieren:auf der Suche nach einem effizienten Weg zur Iteration

import numpy as np 
import pandas as pd 

A = np.matrix([[ 0., 1., 1., 1., 1.], 
      [ 1., 0., 1., 1., 1.], 
      [ 1., 1., 0., 1., 1.], 
      [ 1., 1., 1., 0., 1.], 
      [ 1., 1., 1., 1., 0.], 
      [ 1., 1., 1., 1., 1.], 
      [ 1., 1., 1., 1., 1.], 
      [ 1., 1., 1., 1., 1.], 
      [ 1., 1., 1., 1., 0.], 
      [ 1., 1., 0., 1., 1.], 
      [ 1., 1., 1., 1., 1.], 
      [ 1., 1., 1., 1., 1.], 
      [ 1., 1., 1., 1., 1.], 
      [ 1., 1., 1., 1., 1.], 
      [ 1., 1., 1., 0., 1.], 
      [ 1., 1., 1., 1., 1.], 
      [ 1., 1., 0., 1., 1.], 
      [ 1., 1., 1., 1., 0.], 
      [ 1., 0., 1., 1., 1.], 
      [ 1., 1., 1., 1., 1.]]) 

df = pd.DataFrame(A) 

jetzt wollen wir durch Erhöhung der Fenster von 3 Zeilen à der Werte füllen aus die Spitze. jedes Fenster von 3 Zeilen beginnt am window_start, definiert als:

window_size = 3 
window_start = [i for i in range(0, df.shape[0]) 
       if i % window_size == 0] 
print(df) 
gf = df.copy() 
print('\n') 

nun den Datenrahmen zu machen, wo die Nullen von den vorhergehenden Zeilen oberhalb innerhalb dieses Fensters propagieren:

for i in window_start: 
for j in range(1, window_size): 
    try: gf.iloc[i + j] = gf.iloc[i + j - 1] * gf.iloc[i + j] 
    except: pass 

print(gf) 

Dieses letzte Bit ist ziemlich Ineffizient und zeitaufwendig für sehr große Datensätze, gibt es einen besseren Weg, dies zu tun?

Antwort

7

Sie sollten diese Aufgabe mit einem kumulativen Produkt innerhalb einer groupby erfüllen können.

df.groupby(np.arange(len(df)) // 3).cumprod() 

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

Wir können einen besseren Blick nehmen, von concat zu sehen, ob es tut, was wir wollen.

pd.concat([df.iloc[:6, :2], d1.iloc[:6, :2]], axis=1, keys=['Before', 'After']) 

    Before  After  
     0 1  0 1 
0 0.0 1.0 0.0 1.0 
1 1.0 0.0 0.0 0.0 
2 1.0 1.0 0.0 0.0 
3 1.0 1.0 1.0 1.0 
4 1.0 1.0 1.0 1.0 
5 1.0 1.0 1.0 1.0 

Mein nehmen auf einem numpy Ansatz
See @ Divakar-Lösung als ich einige Elemente seiner Funktion

def prop_zero(df, window_size=3): 
    a = df.values 
    W = window_size 
    m, n = a.shape 

    pad = np.zeros((W - m % W, n)) 
    b = np.vstack([a, pad]) 

    return pd.DataFrame(
     b.reshape(-1, W, n).cumprod(1).reshape(-1, n)[:m], 
     df.index, df.columns 
    ) 

prop_zero(df) 
+1

Beachten Sie, dass dieser Überlauf, wenn die Werte fehlschlagen, z.B '[1e200, 1e200, 0]', weil cumprod '[1e200, inf, nan]' erzeugt. ;-) #unlikelyfailuremodeoftheday – DSM

+0

Nice hashtag :-) – piRSquared

5

entlehnt könnten Sie ein groupby mit cummin tun:

In [46]: out = df.groupby(np.arange(len(df))//3).cummin() 

In [47]: df.head(6) 
Out[47]: 
    0 1 2 3 4 
0 0.0 1.0 1.0 1.0 1.0 
1 1.0 0.0 1.0 1.0 1.0 
2 1.0 1.0 0.0 1.0 1.0 
3 1.0 1.0 1.0 0.0 1.0 
4 1.0 1.0 1.0 1.0 0.0 
5 1.0 1.0 1.0 1.0 1.0 

In [48]: out.head(6) 
Out[48]: 
    0 1 2 3 4 
0 0.0 1.0 1.0 1.0 1.0 
1 0.0 0.0 1.0 1.0 1.0 
2 0.0 0.0 0.0 1.0 1.0 
3 1.0 1.0 1.0 0.0 1.0 
4 1.0 1.0 1.0 0.0 0.0 
5 1.0 1.0 1.0 0.0 0.0 

Dies setzt voraus, dass alle Werte liegen zwischen 0 und 1. Wenn Sie nicht-1-Werte haben, aber Sie wollen immer noch die Null-nach-Null-Verhalten, Sie so etwas wie

df.where(~(df == 0).groupby(np.arange(len(df))//3).cummax(), 0) 

tun konnte, was nicht so schön ist , wird aber nicht durch Werte wie 0,5 (wie cummin direkt auf die Werte angewendet wird) oder durch einen möglichen Überlauf (wie cumprod direkt auf die Werte anwenden) verwechselt.

+0

Das funktioniert so lange, wie die Werte 1 und 0 sind. Gute Antwort! – piRSquared

+0

@piRSquared: guter Punkt, ich sollte es allgemeiner machen .. – DSM

4

Hier ist ein NumPy-Ansatz, der die erste Achse aufteilt und uns ein 3D Array gibt, dann cumprod entlang der ersten Achse und dann wieder zurück zu 2D. Für die Fälle, in denen die Anzahl der Zeilen nicht durch die window_size teilbar ist, hätten wir übrig gebliebene Elemente, die nicht Teil der Umformung sind, und diese würden separat verarbeitet werden.

Somit würde die Umsetzung sein -

def numpy_cumprod(df, window_size=3): 
    a = df.values 
    W = window_size 
    m,n = a.shape 
    N = m//W 
    M = N*W 

    out0 = a[:M].reshape(-1,W,n).cumprod(1).reshape(-1,n) 
    out = np.vstack((out0, a[M:].cumprod(0))) 
    return pd.DataFrame(out) 

Probelauf -

In [279]: df 
Out[279]: 
    0 1 2 3 4 
0 2 2 2 0 1 
1 1 2 0 2 2 
2 1 1 0 0 1 
3 2 0 2 0 1 
4 0 0 0 1 0 
5 0 0 1 2 1 
6 1 1 0 0 1 
7 0 0 1 2 1 
8 2 2 2 1 1 
9 2 1 2 1 0 
10 1 1 1 1 2 
11 0 2 2 1 2 

In [280]: numpy_cumprod(df, window_size=3) 
Out[280]: 
    0 1 2 3 4 
0 2 2 2 0 1 
1 2 4 0 0 2 
2 2 4 0 0 2 
3 2 0 2 0 1 
4 0 0 0 0 0 
5 0 0 0 0 0 
6 1 1 0 0 1 
7 0 0 0 0 1 
8 0 0 0 0 1 
9 2 1 2 1 0 
10 2 1 2 1 0 
11 0 2 4 1 0 

Runtime-Test auf größere Datenmenge -

In [275]: df = pd.DataFrame(np.random.randint(0,3,(10000,5))) 

# @piRSquared's soln-1 using pandas groupby 
In [276]: %timeit df.groupby(np.arange(len(df)) // 3).cumprod() 
100 loops, best of 3: 2.49 ms per loop 

# @piRSquared's soln-2 using NumPy 
In [261]: %timeit prop_zero(df, window_size=3) 
1000 loops, best of 3: 285 µs per loop 

# Proposed in this post 
In [262]: %timeit numpy_cumprod(df, window_size=3) 
1000 loops, best of 3: 262 µs per loop 
+1

Das war, was ich hinzufügen wollte :-) Ich wollte am Ende eine aufgefüllte Reihe von Nullen anhängen und dann wieder entfernen. – piRSquared

+0

@piRSquared Schöne Ergänzung dort, fügte hinzu, dass in die Zeiten auch. Ziemlich schnell jetzt. – Divakar

Verwandte Themen