2017-04-26 8 views
4

Ich möchte eine Rolling-Window-Berechnung in Pandas machen, die mit zwei Spalten gleichzeitig arbeiten müssen. Ich werde ein einfaches Beispiel, um das Problem klar zum Ausdruck bringen:Zugriff auf mehrere Spalten im fortlaufenden Operator?

import pandas as pd 

df = pd.DataFrame({ 
    'x': [1, 2, 3, 2, 1, 5, 4, 6, 7, 9], 
    'y': [4, 3, 4, 6, 5, 9, 1, 3, 1, 2] 
}) 

windowSize = 4 
result = [] 

for i in range(1, len(df)+1): 
    if i < windowSize: 
     result.append(None) 
    else: 
     x = df.x.iloc[i-windowSize:i] 
     y = df.y.iloc[i-windowSize:i] 
     m = y.mean() 
     r = sum(x[y > m])/sum(x[y <= m]) 
     result.append(r) 

print(result) 

Gibt es eine Möglichkeit, ohne in Pandas für Schleife um das Problem zu lösen? Jede Hilfe ist

geschätzt

Antwort

1

Hier ist ein vektorisiert Ansatz NumPy Tools -

windowSize = 4 
a = df.values 
X = strided_app(a[:,0],windowSize,1) 
Y = strided_app(a[:,1],windowSize,1) 
M = Y.mean(1) 
mask = Y>M[:,None] 
sums = np.einsum('ij,ij->i',X,mask) 
rest_sums = X.sum(1) - sums 
out = sums/rest_sums 

strided_app aus here genommen.

Runtime Test -

Approaches -

# @kazemakase's solution 
def rolling_window_sum(df, windowSize=4): 
    rw = rolling_window(df.values.T, windowSize) 
    m = np.mean(rw[1], axis=-1, keepdims=True) 
    a = np.sum(rw[0] * (rw[1] > m), axis=-1) 
    b = np.sum(rw[0] * (rw[1] <= m), axis=-1) 
    result = a/b 
    return result  

# Proposed in this post  
def strided_einsum(df, windowSize=4): 
    a = df.values 
    X = strided_app(a[:,0],windowSize,1) 
    Y = strided_app(a[:,1],windowSize,1) 
    M = Y.mean(1) 
    mask = Y>M[:,None] 
    sums = np.einsum('ij,ij->i',X,mask) 
    rest_sums = X.sum(1) - sums 
    out = sums/rest_sums 
    return out 

Timings -

In [46]: df = pd.DataFrame(np.random.randint(0,9,(1000000,2))) 

In [47]: %timeit rolling_window_sum(df) 
10 loops, best of 3: 90.4 ms per loop 

In [48]: %timeit strided_einsum(df) 
10 loops, best of 3: 62.2 ms per loop 

in mehr Leistung zu drücken, können wir den Y.mean(1) Teil berechnen, das ist im Grunde ein Fenster Summierung mit Scipy's 1D uniform filter . So könnte M alternativ berechnet für windowSize=4 als -

from scipy.ndimage.filters import uniform_filter1d as unif1d 

M = unif1d(a[:,1].astype(float),windowSize)[2:-1] 

Die Leistungssteigerungen sind signifikant -

In [65]: %timeit strided_einsum(df) 
10 loops, best of 3: 61.5 ms per loop 

In [66]: %timeit strided_einsum_unif_filter(df) 
10 loops, best of 3: 49.4 ms per loop 
2

Sie die rolling window trick for numpy arrays verwenden können und es auf dem Feld der Datenrahmen zugrunde liegen.

import pandas as pd 
import numpy as np 

def rolling_window(a, window): 
    shape = a.shape[:-1] + (a.shape[-1] - window + 1, window) 
    strides = a.strides + (a.strides[-1],) 
    return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides) 

df = pd.DataFrame({ 
    'x': [1, 2, 3, 2, 1, 5, 4, 6, 7, 9], 
    'y': [4, 3, 4, 6, 5, 9, 1, 3, 1, 2] 
}) 

windowSize = 4  

rw = rolling_window(df.values.T, windowSize) 
m = np.mean(rw[1], axis=-1, keepdims=True) 
a = np.sum(rw[0] * (rw[1] > m), axis=-1) 
b = np.sum(rw[0] * (rw[1] <= m), axis=-1) 
result = a/b 

Das Ergebnis fehlt die führenden None Werte, aber sie sollen (in Form von np.nan oder nach dem Umwandeln des Ergebnisses in eine Liste) anzuhängen, leicht sein.

Dies ist wahrscheinlich nicht das, was Sie suchen, arbeiten mit Pandas, aber es wird die Arbeit ohne Schleifen zu erledigen.

Verwandte Themen