2016-01-05 11 views
8

desidero filtrare un numpyarray (o pandasDataFrame) in un modo che solo serie continua dello stesso valore con almeno window_size lunghezza viene mantenuta e quant'altro set a 0.panda filtraggio o array NumPy per serie continua di lunghezza finestra minima

ad esempio:

[1,1,1,0,0,1,1,1,1,0,0,1,0,0,0,1,1,1,0,1,1,1,1] 

dovrebbe diventare quando si utilizza una dimensione della finestra di 4

[0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1] 

Ho provato a utilizzare rolling_apply e scipy.ndimage.filtes.gerneric_filter ma a causa della natura delle funzioni di rolling del kernel, non penso che sia l'approccio giusto qui (e sono bloccato con esso al momento).

inserisco il mio tentativo qui comunque:

import numpy as np 
import pandas as pd 
import scipy 
#from scipy import ndimage 
df= pd.DataFrame({'x':np.array([1,1,1,0,0,1,1,1,1,0,0,1,0,0,0,1,1,1,0,1,1,1,1])}) 
df_alt = df.copy() 
def filter_df(df, colname, window_size): 
    rolling_func = lambda z: z.sum() >= window_size 
    df[colname] = pd.rolling_apply(df[colname], 
            window_size, 
            rolling_func, 
            min_periods=window_size/2, 
            center = True) 

def filter_alt(df, colname, window_size): 
    rolling_func = lambda z: z.sum() >= window_size 
    return scipy.ndimage.filters.generic_filter(df[colname].values, 
               rolling_func, 
               size = window_size,          
               origin = 0) 

window_size = 4 
filter_df(df, 'x', window_size) 
print df 
filter_alt(df_alt, 'x', window_size) 
+0

Come volete per il trattamento di una sequenza di stessi valori più lunga della dimensione della finestra? I valori sono sempre identici o possono differire per lo stesso array? – Stefan

+0

Mi piacerebbe tenerli come una serie di 1 anche. Come: [1,1,1,1,1] -> [1,1,1,1,1] – pho

risposta

6

Questo è fondamentalmente un image closing operation in image-processing per un caso 1D però. Tali operazioni potrebbero essere implementate con metodi di convoluzione. Ora, NumPy does support 1D convolution, quindi siamo fortunati!Così, per risolvere il nostro caso, sarebbe qualcosa di simile -

def conv_app(A, WSZ): 
    K = np.ones(WSZ,dtype=int) 
    L = WSZ-1 
    return (np.convolve(np.convolve(A,K)>=WSZ,K)[L:-L]>0).astype(int) 

Campione run -

In [581]: A 
Out[581]: array([1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1]) 

In [582]: conv_app(A,4) 
Out[582]: array([0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]) 

In [583]: A = np.append(1,A) # Append 1 and see what happens! 

In [584]: A 
Out[584]: array([1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1]) 

In [585]: conv_app(A,4) 
Out[585]: array([1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]) 

test runtime -

Questa sezione benchmark paio di altri approcci indicati per risolvere il postato domanda. Le loro definizioni sono elencati di seguito -

def groupby_app(A,WSZ): # @lambo477's solution 
    groups = itertools.groupby(A) 
    result = [] 
    for group in groups: 
     group_items = [item for item in group[1]] 
     group_length = len(group_items) 
     if group_length >= WSZ: 
      result.extend([item for item in group_items]) 
     else: 
      result.extend([0]*group_length) 
    return result 

def stride_tricks_app(arr, window): # @ajcr's solution 
    x = pd.rolling_min(arr, window) 
    x[:window-1] = 0 
    y = np.lib.stride_tricks.as_strided(x, (len(x)-window+1, window), (8, 8)) 
    y[y[:, -1] == 1] = 1 
    return x.astype(int)    

Tempi -

In [541]: A = np.random.randint(0,2,(100000)) 

In [542]: WSZ = 4 

In [543]: %timeit groupby_app(A,WSZ) 
10 loops, best of 3: 74.5 ms per loop 

In [544]: %timeit stride_tricks_app(A,WSZ) 
100 loops, best of 3: 3.35 ms per loop 

In [545]: %timeit conv_app(A,WSZ) 
100 loops, best of 3: 2.82 ms per loop 
+1

Avrei dovuto sapere che avresti trovato un modo veloce! Ho pensato brevemente alla convoluzione, ma non ho pensato di applicarlo due volte. Bella soluzione –

+1

Sì esattamente! Ben fatto e grazie mille Divakar! – pho

2

si potrebbe usare itertools.groupby come segue:

import itertools 
import numpy as np 

my_array = np.array([1,1,1,0,0,1,1,1,1,0,0,1,0,0,0,1,1,1,0,1,1,1,1]) 
window_size = 4 

groups = itertools.groupby(my_array) 

result = [] 
for group in groups: 
    group_items = [item for item in group[1]] 
    group_length = len(group_items) 
    if group_length >= window_size: 
     result.extend([item for item in group_items]) 
    else: 
     result.extend([0]*group_length) 

print(result) 

uscita

[0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1] 
+0

Questa è anche una buona soluzione e credo che sia leggermente più veloce di quella che ho fornito. – johnchase

+0

Grazie a lambo Ill test anche la tua soluzione e vedi quale funziona meglio. – pho

+1

itertools groupby è sorprendentemente veloce sui miei datafram piuttosto grandi. Ho testato 10000 iterazioni contro la soluzione johnchase e ha ottenuto una memoria e un runtime molto migliori. Quindi considererò questa la soluzione migliore. Anche se John sembra piuttosto carino ma manca di prestazioni. Grazie comunque! – pho

1

Ci possono benissimo essere una soluzione migliore , tuttavia penso che questo dovrebbe funzionare:

In [90]: x = np.array([1,1,1,0,0,1,1,1,1,0,0,1,0,1,2,1,4,4,4,4,4,0,1,1,1,1]) 

Ho incluso alcuni altri numeri nel caso in cui sia necessario il codice che conti per quello;

In [93]: y = np.split(x, np.where(np.diff(x) != 0)[0]+1) 
     z = [list(e) if len(e) >= 4 else [0]*len(e) for e in y] 
     result = np.array([item for sublist in z for item in sublist]) 

La prima riga qui sta dividendo la matrice originale in caratteri consecutivi, la seconda riga sostituisce qualsiasi elemento che contiene meno di 4 caratteri consecutivi con 0 e la riga finale appiattisce l'elenco di divisione.

In [96]: result 
Out[96]: array([0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]) 

La prima riga della soluzione fa anche ampio uso di un precedente SO answer

+0

Grazie per la risposta. Questo sembra abbastanza elegante, lo proverò sul mio set di dati e vedrò come si comporta. – pho

2

Ecco un approccio usando pd.rolling_min e falcata trucchi:

def func(arr, window): 
    x = pd.rolling_min(arr, window) 
    x[:window-1] = 0 
    y = np.lib.stride_tricks.as_strided(x, (len(x)-window+1, window), (8, 8)) 
    y[y[:, -1] == 1] = 1 
    return x.astype(int) 

Poi abbiamo:

>>> x = np.array([1,1,1,0,0,1,1,1,1,0,0,1,0,0,0,1,1,1,0,1,1,1,1]) 
>>> func(x, 4) 
array([0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]) 
>>> y = np.array([1,1,1,0,0,1,1,1,1,1,0,1,0,0,0,1,1,1,0,1,1,1,1]) # five 1s 
>>> func(y, 4) 
array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]) 

Su grandi array, questo approccio è abbastanza veloce (sul mio Sistema groupby è circa 20 volte più lento):

>>> x = np.random.randint(0, 2, size=1000000) 
>>> %timeit func(x, 4) 
10 loops, best of 3: 24.4 ms per loop 
+0

Sì, ajcr ho provato ed è stato il più lento. La soluzione itertools è di gran lunga la più veloce. Circa 13,9 ms per loop sul mio set di dati, Johns prende circa 153 ms per loop e il tuo quasi 3 secondi. Grazie. – pho

+0

@pho: ho sostituito 'rolling_apply' con' rolling_min' subito dopo che ho chiesto (avevo incollato la funzione sbagliata - scuse). Dovrebbe essere significativamente più veloce su qualsiasi set di dati più grande. –

+0

Ho anche avuto un errore nella mia funzione groupby modificata che spiega che è veloce. Lo esaminerò ancora una volta domani. Grazie ajcr – pho

1

Una variante più compatta sulla soluzione itertools.groupby:

window_size = 4 
groups = [list(g) for k, g in itertools.groupby(my_array)] 
filtered_array = [g if sum(g) >= window_size else [0]*len(g) for g in groups] 
[int(i) for sub in filtered_array for i in sub] 

[0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]