2017-09-15 4 views
2

j'ai une trame de données de pandas de mesures et de poids correspondants:Lissage une série de valeurs pondérées en numpy/pandas

df = pd.DataFrame({'x': np.random.randn(1000), 'w': np.random.rand(1000)}) 

Je souhaite lisser les valeurs de mesure (x) tout en prenant l'élément sage poids (w) en compte. Ceci est indépendant des poids de la fenêtre coulissante, que je voudrais aussi appliquer (par exemple une fenêtre triangulaire, ou quelque chose de colombophile). Ainsi, pour calculer la valeur lissée dans chaque fenêtre, la fonction doit pondérer les éléments tranchés de x non seulement par la fonction de fenêtre (par exemple triangle), mais aussi par les éléments correspondants dans w.

Pour autant que je peux dire, pd.rolling_apply ne sera pas le faire, car elle applique la fonction donnée sur x et w séparément. De même, pd.rolling_window ne prend pas en compte les poids élément par élément du DataFrame source; la fenêtre pondérée (par exemple «triangle») peut être définie par l'utilisateur, mais elle est fixée à l'avant.

Voici mon implémentation ish lente:

def rolling_weighted_triangle(x, w, window_size): 
    """Smooth with triangle window, also using per-element weights.""" 
    # Simplify slicing 
    wing = window_size // 2 

    # Pad both arrays with mirror-image values at edges 
    xp = np.r_[x[wing-1::-1], x, x[:-wing-1:-1]] 
    wp = np.r_[w[wing-1::-1], w, w[:-wing-1:-1]] 

    # Generate a (triangular) window of weights to slide 
    incr = 1./(wing + 1) 
    ramp = np.arange(incr, 1, incr) 
    triangle = np.r_[ramp, 1.0, ramp[::-1]] 

    # Apply both sets of weights over each window 
    slices = (slice(i - wing, i + wing + 1) for i in xrange(wing, len(x) + wing)) 
    out = (np.average(xp[slc], weights=triangle * wp[slc]) for slc in slices) 
    return np.fromiter(out, x.dtype) 

Comment puis-je accélérer ce avec numpy/scipy/pandas géants?

La trame de données peut déjà prendre une partie non triviale de la RAM (lignes 10k à 200M), par ex. l'allocation d'un tableau 2D de poids de fenêtre par élément à l'avant est trop importante. J'essaie de minimiser l'utilisation de tableaux temporaires, peut-être en utilisant np.lib.stride_tricks.as_strided et np.apply_along_axis ou np.convolve, mais n'ont rien trouvé pour répliquer complètement ce qui précède.

est ici l'équivalent d'une fenêtre uniforme, au lieu d'un triangle (en utilisant le get_sliding_window trick from here) - proche, mais pas tout à fait:

def get_sliding_window(a, width): 
    """Sliding window over a 2D array. 

    Source: https://stackoverflow.com/questions/37447347/dataframe-representation-of-a-rolling-window/41406783#41406783 
    """ 
    # NB: a = df.values or np.vstack([x, y]).T 
    s0, s1 = a.strides 
    m, n = a.shape 
    return as_strided(a, 
        shape=(m-width+1, width, n), 
        strides=(s0, s0, s1)) 


def rolling_weighted_average(x, w, window_size): 
    """Rolling weighted average with a uniform 'boxcar' window.""" 
    wing = window_size // 2 
    window_size = 2 * wing + 1 
    xp = np.r_[x[wing-1::-1], x, x[:-wing-1:-1]] 
    wp = np.r_[w[wing-1::-1], w, w[:-wing-1:-1]] 
    x_w = np.vstack([xp, wp]).T 
    wins = get_sliding_window(x_w, window_size) 
    # TODO - apply triangle window weights - multiply over wins[,:,1]? 
    result = np.average(wins[:,:,0], axis=1, weights=wins[:,:,1]) 
    return result 
+0

N'est-ce pas équivalent à l'application de la fenêtre sur 'w * x'? Peut-être que vous pouvez générer cette colonne en premier? – VBB

+0

Cela ne semble pas être le cas. La moyenne dans une tranche de fenêtre donnée n'est pas nécessairement 0. –

Répondre

1

Vous pouvez simplement utiliser convolution là, comme si -

def rolling_weighted_triangle_conv(x, w, window_size): 
    """Smooth with triangle window, also using per-element weights.""" 
    # Simplify slicing 
    wing = window_size // 2 

    # Pad both arrays with mirror-image values at edges 
    xp = np.concatenate((x[wing-1::-1], x, x[:-wing-1:-1])) 
    wp = np.concatenate((w[wing-1::-1], w, w[:-wing-1:-1])) 

    # Generate a (triangular) window of weights to slide 
    incr = 1./(wing + 1) 
    ramp = np.arange(incr, 1, incr) 
    triangle = np.r_[ramp, 1.0, ramp[::-1]] 

    D = np.convolve(wp*xp, triangle)[window_size-1:-window_size+1] 
    N = np.convolve(wp, triangle)[window_size-1:-window_size+1]  
    return D/N 

test Runtime

In [265]: x = np.random.randn(1000) 
    ...: w = np.random.rand(1000) 
    ...: WSZ = 7 
    ...: 

In [266]: out1 = rolling_weighted_triangle(x, w, window_size=WSZ) 
    ...: out2 = rolling_weighted_triangle_conv(x, w, window_size=WSZ) 
    ...: print(np.allclose(out1, out2)) 
    ...: 
True 

In [267]: %timeit rolling_weighted_triangle(x, w, window_size=WSZ) 
    ...: %timeit rolling_weighted_triangle_conv(x, w, window_size=WSZ) 
    ...: 
100 loops, best of 3: 10.2 ms per loop 
10000 loops, best of 3: 32.9 µs per loop 

300x+ accélérer là-bas!

+0

Fantastique. Cette approche permet également de brancher une autre forme de fenêtre comme Kaiser à la place du triangle. –

+0

@EricTalevich Yup! Tout type de poids est connectable ici. – Divakar