14 votes

pourquoi pandas rolling utilise-t-il un tableau à dimension unique ?

J'ai été motivé pour utiliser les pandas rolling pour effectuer une régression multifactorielle glissante. PAS sur la régression multi-factorielle glissante). Je m'attendais à ce que je puisse utiliser apply après un df.rolling(2) et prendre le résultat pd.DataFrame extraire le ndarray avec .values et effectuer la multiplication matricielle requise. Ça ne s'est pas passé comme ça.

Voici ce que j'ai trouvé :

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)

A quoi ressemblent les objets :

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)

La multiplication matricielle se comporte normalement :

df.values.dot(X)

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

L'utilisation de apply pour effectuer le produit scalaire ligne par ligne se comporte comme prévu :

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 -> Apply se comporte comme je m'y attendais :

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

Mais quand je cours :

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

J'ai compris :

AttributeError : L'objet 'numpy.ndarray' ne possède pas d'attribut 'values'.

Ok, donc pandas utilise directement ndarray au sein de son rolling mise en œuvre. Je peux m'en occuper. Au lieu d'utiliser .values pour obtenir le ndarray essayons :

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

Les formes (1,) et (2,1) ne sont pas alignées : 1 (dim 0) != 2 (dim 0)

Attends ! Quoi ?!

J'ai donc créé une fonction personnalisée pour examiner ce que fait le roulement.

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

Puis a couru :

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

Mon résultat pd.DataFrame est le même, c'est bien. Mais ça a imprimé 10 dimensions uniques ndarray objets. Qu'en est-il 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

Même chose, je m'attendais à une sortie mais elle a imprimé 8 ndarray objets. rolling produit une image unidimensionnelle ndarray de longueur window pour chaque colonne, alors que je m'attendais à ce que ce soit une ndarray de la forme (window, len(df.columns)) .

La question est : pourquoi ?

Je n'ai maintenant aucun moyen de faire facilement une régression multi-factorielle.

11voto

piRSquared Points 159

Je voulais partager ce que j'ai fait pour contourner ce problème.

Étant donné un pd.DataFrame et une fenêtre, je génère une pile ndarray en utilisant np.dstack ( voir la réponse ). Je le convertis ensuite en un pd.Panel et en utilisant pd.Panel.to_frame le convertir en un pd.DataFrame . A ce stade, j'ai un pd.DataFrame qui a un niveau supplémentaire sur son indice par rapport à l'original pd.DataFrame et le nouveau niveau contient des informations sur chaque période laminée. Par exemple, si la fenêtre de roulement est 3, le nouveau niveau d'indice contiendra [0, 1, 2] . Un élément pour chaque période. Je peux maintenant groupby level=0 et retourner l'objet groupby. Cela me donne maintenant un objet que je peux manipuler de manière beaucoup plus intuitive.

Fonction de roulement

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)

Démonstration

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

Allons-y. 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

Pour jeter un coup d'œil sous le capot, nous pouvons voir 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

Mais qu'en est-il de l'objectif pour lequel je l'ai construit, la régression multi-factorielle glissante. Mais je vais me contenter d'une multiplication de matrice pour le moment.

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

7voto

Divakar Points 20144

Utilisation de la strides views concept on dataframe voici une approche vectorielle -

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

Test d'exécution -

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

Vérifier les résultats -

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 ]])

Une amélioration considérable, qui, je l'espère, restera perceptible sur des tableaux plus grands !

2voto

J'ai apporté les modifications suivantes à la réponse ci-dessus, car j'avais besoin de renvoyer la fenêtre de roulement entière comme cela est fait dans 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)

0voto

jorijnsmit Points 155

Depuis la version 0.23 de pandas, il est désormais possible de transmettre un fichier de type Series au lieu d'un ndarray à Rolling.apply() . Il suffit de mettre raw=False .

brut : bool, default None

False : transmet chaque ligne ou colonne comme une série à la fonction.

True o None la fonction passée recevra des objets ndarray à la place. Si vous appliquez simplement une fonction de réduction de NumPy, vous obtiendrez de bien meilleures performances. Le paramètre raw est obligatoire et affichera un FutureWarning s'il n'est pas passé. À l'avenir, la valeur par défaut de raw sera False.

Nouveau dans la version 0.23.0.

Comme indiqué, si vous n'avez besoin que d'une seule dimension, il est évidemment plus efficace de la transmettre brute. C'est probablement la réponse à votre question ; Rolling.apply() a été initialement construit pour passer un fichier ndarray uniquement parce que c'est le plus efficace.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X