38 votes

Comment fonctionnent les objets roulants pandas?

Edit: j'ai condensé cette question, étant donné que c'était probablement trop impliqué pour commencer. La viande de la question est en gras ci-dessous.

J'aimerais en savoir plus à propos de l'objet qui est en fait créé lors de l'utilisation d' DataFrame.rolling ou Series.rolling:

print(type(df.rolling))
<class 'pandas.core.window.Rolling'>

Un peu de contexte: considérer l'oft alternative, avec np.as_strided. Cet extrait de code lui-même n'est pas important, mais le résultat est mon point de référence en posant cette question.

def rwindows(a, window):
    if a.ndim == 1:
        a = a.reshape(-1, 1)
    shape = a.shape[0] - window + 1, window, a.shape[-1]
    strides = (a.strides[0],) + a.strides
    windows = np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)
    return np.squeeze(windows)

Ici, rwindows va prendre un 1d ou 2d ndarray et de construire des rolling "blocs" égale à celle indiquée la taille de la fenêtre (comme ci-dessous). Comment un .rolling objet de comparer le ndarray de sortie ci-dessous? Est-il un itérateur, avec certains attributs stockés pour chaque bloc? Ou tout autre chose? J'ai essayé de jouer avec la saisie semi-automatique sur l'objet avec des attributs/méthodes telles que l' __dict__ et _get_index() et ils ne sont pas à me dire beaucoup. J'ai aussi vu un _create_blocks méthode dans les pandas--qu'il ne ressemble en rien à l' strided méthode?

# as_strided version

a = np.arange(5)
print(rwindows(a, 3))           # 1d input
[[0 1 2]
 [1 2 3]
 [2 3 4]]

b = np.arange(10).reshape(5,2)
print(rwindows(b, 4))           # 2d input
[[[0 1]
  [2 3]
  [4 5]
  [6 7]]

 [[2 3]
  [4 5]
  [6 7]
  [8 9]]]

La partie 2, un crédit supplémentaire

À l'aide de la NumPy approche ci-dessus (MCO mise en œuvre ici) est rendue nécessaire par le fait que l' func dans les pandas.de base.de la fenêtre.Le roulement.appliquer doit

produire une valeur unique à partir d'un ndarray d'entrée *args et **kwargs sont passé à la fonction

Donc l'argument ne peut pas être un autre objet roulant. I. e.

def prod(a, b):
    return a * b
df.rolling(3).apply(prod, args=((df + 2).rolling(3),))
-----------------------------------------------------------------------
...
TypeError: unsupported operand type(s) for *: 'float' and 'Rolling'

Donc c'est vraiment, d'où ma question ci-dessus les tiges. Pourquoi est-ce que la fonction doit utiliser un tableau NumPy et de produire une valeur scalaire unique, et qu'est-ce que avez à faire avec la mise en page d'un .rolling objet?

45voto

Je vous suggère de regarder le code source afin d'obtenir dans le nitty gritty de ce roulement n'. En particulier, je vous suggère d'avoir un coup d'oeil à l' rolling fonctions generic.py et window.py. De là, vous pouvez jeter un oeil à l' Window de la classe qui est utilisée si vous spécifiez un type de fenêtre ou de la valeur par défaut Rolling classe. Le dernier hérite _Rolling_and_Expanding et, finalement, _Rolling et _Window.

Cela dit, je vais donner à mes deux cents: les Pandas de " l'ensemble du mécanisme de rotation, s'appuie sur la fonction numpy apply_along_axis. En particulier, il est utilisé ici dans les pandas. Il est utilisé en conjonction avec l' windows.pyx cython module. En va de votre série, sort de l'ensemble roulant fenêtre. Pour les fonctions d'agrégation, il les gère pour vous de manière efficace, mais pour ceux personnalisées (à l'aide d' apply()), il utilise un roll_generic() en windows.pyx.

Les rolling fonction dans les pandas fonctionne sur les pandas trame de données des colonnes de façon indépendante. Ce n'est pas un python itérateur, et n'est pas chargé, de sens, rien n'est calculé jusqu'à ce que vous appliquer une fonction d'agrégation à elle. Les fonctions qui s'appliquent en fait l'roulant fenêtre de données ne sont pas utilisés jusqu'à ce que juste avant une agrégation est faite.

Une source de confusion et peut-être que vous êtes la pensée de l'objet roulant comme un dataframe. (Vous avez nommé les rolling objet df dans votre dernier extrait de code). Il est vraiment pas. C'est un objet qui peut produire des dataframes en appliquant des agrégations sur la fenêtre de la logique qu'elle abrite.

Le lambda que vous fournissez est appliquée pour chaque cellule de votre nouveau dataframe. Il faut une fenêtre vers l'arrière (le long de chaque colonne) dans votre ancien dataframe, et il agrège à une seule cellule dans la nouvelle dataframe. L'agrégation peut être des choses comme sum, mean, quelque chose de personnalisé que vous avez fait, etc., sur certains la taille de la fenêtre, dire 3. Voici quelques exemples:

a = np.arange(5)
df = pd.DataFrame(a, columns=['a'])
df.rolling(3).mean().dropna()

... qui peut aussi être fait par:

df.rolling(3).apply(np.mean).dropna()

... et produit:

     a
2  3.0
3  6.0
4  9.0

(La première colonne est la valeur de l'indice et peut être ignoré ici, et pour les exemples).

Remarquez comment nous avons fourni un existant numpy la fonction d'agrégation. C'est l'idée. Nous sommes censés être en mesure de fournir tout ce qu'on veut tant qu'il se conforme à ce que des fonctions d'agrégation faire, c'est à dire, prendre un vecteur de valeurs et de produire une seule valeur de celui-ci. En voici une autre où l'on créer une fonction d'agrégation, dans ce cas, la norme L2 de la fenêtre:

df.rolling(3).apply(lambda x: np.sqrt(x.dot(x))).dropna()

si vous n'êtes pas familier avec les lambda fonctions c'est la même chose que:

def euclidean_dist(x):
    return np.sqrt(x.dot(x))

df.rolling(3).apply(euclidean_dist).dropna()

... rendement:

          a
2  2.236068
3  3.741657
4  5.385165

Juste pour être sûr, nous pouvons vérifier manuellement que np.sqrt(0**2 + 1**2 + 2**2) est en effet 2.236068.

[Vos originaux de modifier, dans le] dernier extrait de code, votre code est probablement à défaut tôt que prévu. Il est en panne avant l'invocation de l' df.apply(...) Vous essayez d'ajouter un roulement objet nommé df le nombre 2 avant de passer à l' df.apply(...). Les rolling objet n'est pas quelque chose que vous faites des opérations. La fonction d'agrégation que vous avez fourni n'est pas conforme à une fonction d'agrégation en général. L' a une liste avec les valeurs d'une fenêtre, b serait une constante paramètre supplémentaire que vous transmettez. Il peut être un roulement objet si vous voulez, mais il ne serait généralement pas être quelque chose que vous aimeriez faire. Pour rendre cela plus clair, ici est quelque chose qui est similaire à ce que vous faisiez dans votre edition originale mais qui fonctionne:

a = np.arange(8)
df = pd.DataFrame(a, columns=['a'])
n = 4
rol = df.rolling(n)

def prod(window_list, constant_rol):
    return window_list.dot(constant_rol.sum().dropna().head(n))

rol.apply(prod, args=(rol,)).dropna()

# [92.0, 140.0, 188.0, 236.0, 284.0]

C'est un exemple artificiel, mais je vais les montrer à faire le point que vous pouvez passer dans tout ce que vous voulez comme une constante, même les rolling objet que vous êtes en utilisant lui-même. La partie dynamique est le premier argument a dans votre cas ou window_list dans mon cas. Tous les windows, sous la forme de listes individuelles, sont passés dans cette fonction, un par un.

Basé sur votre suivi des commentaires, cela pourrait être ce que vous cherchez:

import numpy as np
import pandas as pd

n = 3
a = np.arange(5)
df = pd.DataFrame(a, columns=['a'])

def keep(window, windows):
    windows.append(window.copy())
    return window[-1]

windows = list()
df['a'].rolling(n).apply(keep, args=(windows,))
df = df.tail(n)
df['a_window'] = windows

qui ajoute des matrices/vecteurs pour chaque bloc de roulement produisant ainsi:

   a         a_window
2  2  [0.0, 1.0, 2.0]
3  3  [1.0, 2.0, 3.0]
4  4  [2.0, 3.0, 4.0]

Notez que cela ne fonctionne que si vous le faites sur une colonne à la fois. Si vous voulez faire un peu de maths sur la fenêtre, avant de les stocker à l'écart en keep c'est très bien aussi.

Cela dit, sans plus de commentaires sur exactement ce que vous essayez d'atteindre, il est difficile de construire un exemple qui répond à vos besoins.

Si votre but ultime est de créer un dataframe de retard variables, alors j'irais pour l'utilisation réelle des colonnes à l'aide de shift():

import numpy as np
import pandas as pd

a = np.arange(5)

df = pd.DataFrame(a, columns=['a'])
for i in range(1,3):
    df['a-%s' % i] = df['a'].shift(i)

df.dropna()

... donnant:

   a  a-1  a-2
2  2  1.0  0.0
3  3  2.0  1.0
4  4  3.0  2.0

(Il pourrait y avoir une plus belle façon de le faire, mais il fait le travail.)

Concernant votre variable b lors de votre premier extrait de code, n'oubliez pas DataFrames dans les pandas ne sont généralement pas traités comme des tenseurs de l'arbitraire dimensions/objet. Vous pouvez probablement des trucs ce que vous voulez, mais en fin de compte les chaînes, le temps des objets, des entiers et des flottants est ce qui est prévu. Qui pourraient être les raisons les concepteurs de pandas n'ai pas pris la peine avec permettant de rouler agrégation de non-valeurs scalaires. Il n'a même pas sembler comme une simple chaîne de caractères est autorisé en sortie de la fonction d'agrégation.

De toute façon, j'espère que cela répond à certaines de vos questions. Si non, laissez-moi savoir, et je vais essayer de vous aider dans les commentaires, ou une mise à jour.


Note finale sur l' _create_blocks() fonction des objets roulants.

L' _create_blocks() fonction gère la réindexation et binning lorsque vous utilisez l' freq argument de l' rolling.

Si vous utilisez freq avec, disons, des semaines, tels que freq=W:

import pandas as pd

a = np.arange(50)
df = pd.DataFrame(a, columns=['a'])
df.index = pd.to_datetime('2016-01-01') + pd.to_timedelta(df['a'], 'D')
blocks, obj, index = df.rolling(4, freq='W')._create_blocks(how=None)
for b in blocks:
    print(b)

... alors nous obtenons la mise à la poubelle (pas de roulement) de données d'origine à la semaine:

               a
a               
2016-01-03   2.0
2016-01-10   9.0
2016-01-17  16.0
2016-01-24  23.0
2016-01-31  30.0
2016-02-07  37.0
2016-02-14  44.0
2016-02-21   NaN

Notez que ce n'est pas la sortie de l'ensemble roulant. C'est tout simplement la nouvelle blocs, il fonctionne sur. Après cette. Nous faisons une agrégation comme sum et obtenez:

                a
a                
2016-01-03    NaN
2016-01-10    NaN
2016-01-17    NaN
2016-01-24   50.0
2016-01-31   78.0
2016-02-07  106.0
2016-02-14  134.0
2016-02-21    NaN

... qui vérifie avec un test de sommation: 50 = 2 + 9 + 16 + 23.

Si vous n'utilisez pas freq comme argument, elle renvoie simplement les données d'origine de la structure:

import pandas as pd
a = np.arange(5)
df = pd.DataFrame(a, columns=['a'])
blocks, obj, index = df.rolling(3)._create_blocks(how=None)

for b in blocks:
    print(b)

... qui produit ...

            a
a            
2016-01-01  0
2016-01-02  1
2016-01-03  2
2016-01-04  3
2016-01-05  4

... et est utilisé pour le déploiement de la fenêtre de l'agrégation.

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