2016-05-27 37 views
12

mi hanno spinto ad utilizzare panda rolling funzione per eseguire un rotolamento di regressione a più fattori (Questa domanda è NON su rolling regressione a più fattori). Mi aspettavo che sarei stato in grado di usare apply dopo un df.rolling(2) e prendere il risultato pd.DataFrame estrarre il narray con .values ed eseguire la moltiplicazione della matrice richiesta. Non ha funzionato in questo modo.Perché panda rotolamento dimensione singola uso ndarray

Ecco cosa ho trovato:

import pandas as pd 
import numpy as np 

np.random.seed([3,1415]) 
df = pd.DataFrame(np.random.rand(5, 2).round(2), columns=['A', 'B']) 
X = np.random.rand(2, 1).round(2) 

Cosa fanno gli oggetti assomigliano:

print "\ndf = \n", df 
print "\nX = \n", X 
print "\ndf.shape =", df.shape, ", X.shape =", X.shape 

df = 
     A  B 
0 0.44 0.41 
1 0.46 0.47 
2 0.46 0.02 
3 0.85 0.82 
4 0.78 0.76 

X = 
[[ 0.93] 
[ 0.83]] 

df.shape = (5, 2) , X.shape = (2L, 1L) 

moltiplicazione di matrici si comporta normalmente:

df.values.dot(X) 

array([[ 0.7495], 
     [ 0.8179], 
     [ 0.4444], 
     [ 1.4711], 
     [ 1.3562]]) 

Utilizzando applicare per eseguire riga per dot fila il prodotto si comporta come previsto:

df.apply(lambda x: x.values.dot(X)[0], axis=1) 

0 0.7495 
1 0.8179 
2 0.4444 
3 1.4711 
4 1.3562 
dtype: float64 

Groupby -> Applica si comporta come ci si aspetterebbe:

df.groupby(level=0).apply(lambda x: x.values.dot(X)[0, 0]) 

0 0.7495 
1 0.8179 
2 0.4444 
3 1.4711 
4 1.3562 
dtype: float64 

Ma quando ho eseguito:

df.rolling(1).apply(lambda x: x.values.dot(X)) 

ottengo:

AttributeError: 'numpy.ndarray' object has no attribute 'values'

Ok, quindi è panda utilizzando direttamente ndarray all'interno dell'implementazione rolling. Posso gestirlo. Invece di usare .values per ottenere il ndarray, proviamo:

df.rolling(1).apply(lambda x: x.dot(X)) 

shapes (1,) and (2,1) not aligned: 1 (dim 0) != 2 (dim 0)

Aspetta! Che cosa?!

Così ho creato una funzione personalizzata per vedere cosa sta facendo il rotolamento.

def print_type_sum(x): 
    print type(x), x.shape 
    return x.sum() 

poi corse:

print df.rolling(1).apply(print_type_sum) 

<type 'numpy.ndarray'> (1L,) 
<type 'numpy.ndarray'> (1L,) 
<type 'numpy.ndarray'> (1L,) 
<type 'numpy.ndarray'> (1L,) 
<type 'numpy.ndarray'> (1L,) 
<type 'numpy.ndarray'> (1L,) 
<type 'numpy.ndarray'> (1L,) 
<type 'numpy.ndarray'> (1L,) 
<type 'numpy.ndarray'> (1L,) 
<type 'numpy.ndarray'> (1L,) 
     A  B 
0 0.44 0.41 
1 0.46 0.47 
2 0.46 0.02 
3 0.85 0.82 
4 0.78 0.76 

mio risultante pd.DataFrame è lo stesso, questo è un bene. Ma ha stampato 10 oggetti monodimensionali ndarray. Che dire rolling(2)

print df.rolling(2).apply(print_type_sum) 

<type 'numpy.ndarray'> (2L,) 
<type 'numpy.ndarray'> (2L,) 
<type 'numpy.ndarray'> (2L,) 
<type 'numpy.ndarray'> (2L,) 
<type 'numpy.ndarray'> (2L,) 
<type 'numpy.ndarray'> (2L,) 
<type 'numpy.ndarray'> (2L,) 
<type 'numpy.ndarray'> (2L,) 
     A  B 
0 NaN NaN 
1 0.90 0.88 
2 0.92 0.49 
3 1.31 0.84 
4 1.63 1.58 

Stessa cosa, si aspettano l'uscita ma è stampato 8 ndarray oggetti. rolling produce una singola dimensione ndarray di lunghezza window per ogni colonna rispetto a quello che mi aspettavo che era uno di forma (window, len(df.columns)).

La domanda è: perché?

Ora non ho un modo per eseguire facilmente una regressione a più fattori a rotazione.

+1

Si tratta di un [problema noto] (http: // stackoverflow.com/a/21026837/5276797). Di recente ho chiesto a Jeff, puoi leggere la sua risposta nei commenti! – IanS

+0

Qual è la soluzione allo stato dell'arte di Pandas 0.20? Sembra che siano stati fatti molti miglioramenti. L'obiettivo in OP è raggiungibile usando rolling(). Apply() direttamente? – Zhang18

risposta

4

Utilizzando la strides views concept on dataframe, ecco un approccio vettorializzare -

get_sliding_window(df, 2).dot(X) # window size = 2 

runtime di prova -

In [101]: df = pd.DataFrame(np.random.rand(5, 2).round(2), columns=['A', 'B']) 

In [102]: X = np.array([2, 3]) 

In [103]: rolled_df = roll(df, 2) 

In [104]: %timeit rolled_df.apply(lambda df: pd.Series(df.values.dot(X))) 
100 loops, best of 3: 5.51 ms per loop 

In [105]: %timeit get_sliding_window(df, 2).dot(X) 
10000 loops, best of 3: 43.7 µs per loop 

verificare i risultati -

In [106]: rolled_df.apply(lambda df: pd.Series(df.values.dot(X))) 
Out[106]: 
     0  1 
1 2.70 4.09 
2 4.09 2.52 
3 2.52 1.78 
4 1.78 3.50 

In [107]: get_sliding_window(df, 2).dot(X) 
Out[107]: 
array([[ 2.7 , 4.09], 
     [ 4.09, 2.52], 
     [ 2.52, 1.78], 
     [ 1.78, 3.5 ]]) 

miglioramento enorme lì, che sonosperando sarebbe rimanere visibile su array più grandi!

+0

viene visualizzato un errore di riferimento non risolto quando si tenta di utilizzare get_sliding_window – RaduS

+0

@RaduS Che cosa dice esattamente l'errore? – Divakar

6

Volevo condividere ciò che ho fatto per risolvere questo problema.

Dato un pd.DataFrame e una finestra, viene generato uno stack ndarray utilizzando np.dstack (see answer). Quindi lo converto in un pd.Panel e utilizzando pd.Panel.to_frame convertirlo in un pd.DataFrame. A questo punto, ho un pd.DataFrame che ha un livello aggiuntivo sul suo indice rispetto all'originale pd.DataFrame e il nuovo livello contiene informazioni su ogni periodo di rollup. Ad esempio, se la finestra di roll è 3, il nuovo livello di indice conterrà [0, 1, 2]. Un articolo per ogni periodo. Ora posso groupbylevel=0 e restituire l'oggetto groupby. Questo ora mi dà un oggetto che posso manipolare in modo molto più intuitivo.

funzione Roll

import pandas as pd 
import numpy as np 

def roll(df, w): 
    roll_array = np.dstack([df.values[i:i+w, :] for i in range(len(df.index) - w + 1)]).T 
    panel = pd.Panel(roll_array, 
        items=df.index[w-1:], 
        major_axis=df.columns, 
        minor_axis=pd.Index(range(w), name='roll')) 
    return panel.to_frame().unstack().T.groupby(level=0) 

Dimostrazione

np.random.seed([3,1415]) 
df = pd.DataFrame(np.random.rand(5, 2).round(2), columns=['A', 'B']) 

print df 

     A  B 
0 0.44 0.41 
1 0.46 0.47 
2 0.46 0.02 
3 0.85 0.82 
4 0.78 0.76 

Let sum

rolled_df = roll(df, 2) 

print rolled_df.sum() 

major  A  B 
1  0.90 0.88 
2  0.92 0.49 
3  1.31 0.84 
4  1.63 1.58 

a sbirciare sotto il cofano, possiamo vedere la stucture:

print rolled_df.apply(lambda x: x) 

major  A  B 
    roll    
1 0  0.44 0.41 
    1  0.46 0.47 
2 0  0.46 0.47 
    1  0.46 0.02 
3 0  0.46 0.02 
    1  0.85 0.82 
4 0  0.85 0.82 
    1  0.78 0.76 

Ma per quanto riguarda lo scopo per cui l'ho creato, la regressione a più fattori. Ma per ora mi accontento della moltiplicazione della matrice.

X = np.array([2, 3]) 

print rolled_df.apply(lambda df: pd.Series(df.values.dot(X))) 

     0  1 
1 2.11 2.33 
2 2.33 0.98 
3 0.98 4.16 
4 4.16 3.84 
+0

Questo è stato molto utile, grazie. Ho avuto un piccolo problema con i valori di nan, ma l'aggiornamento dell'ultima riga della funzione 'roll' per usare' .to_frame (filter_observations = False) 'ha risolto il problema. – user338714

+0

Questo è utile. Ma c'è modo di rendere la colonna 'roll' mantenere l'indice originale? per esempio. 0.46 0.47 è sempre associato a "1". Grazie. – iwbabn

1

apportate le seguenti modifiche alla risposta di cui sopra in quanto avevo bisogno di restituire l'intera finestra di laminazione come è fatto in pd.DataFrame.rolling()

def roll(df, w): 
    roll_array = np.dstack([df.values[i:i+w, :] for i in range(len(df.index) - w + 1)]).T 
    roll_array_full_window = np.vstack((np.empty((w-1 ,len(df.columns), w)), roll_array)) 
    panel = pd.Panel(roll_array_full_window, 
       items=df.index, 
       major_axis=df.columns, 
       minor_axis=pd.Index(range(w), name='roll')) 
    return panel.to_frame().unstack().T.groupby(level=0)