Je voudrais obtenir des sous-ensembles de dataframe de manière «continue». J'ai essayé plusieurs choses sans succès, voici un exemple de ce que j'aimerais faire. Considérons dataframe.

df
     var1      var2
0    43         74
1    44         74
2    45         66
3    46        268
4    47         66

Je voudrais créer une nouvelle colonne avec la fonction suivante qui effectue une somme conditionnelle:

def func(x):
    tmp = (x["var1"] * (x["var2"] == 74)).sum()
    return tmp

Et l'appeler comme ça

df["newvar"] = df.rolling(2, min_periods=1).apply(func)

Cela signifierait que la fonction serait appliquée sur la base du dataframe, et non pour chaque ligne ou colonne

Il reviendrait

     var1      var2      newvar
0    43         74         43          # 43
1    44         74         87          # 43 * 1 + 44 * 1
2    45         66         44          # 44 * 1 + 45 * 0
3    46        268         0           # 45 * 0 + 46 * 0
4    47         66         0           # 46 * 0 + 47 * 0

Y a-t-il une manière pythonique de faire cela? Ceci n'est qu'un exemple mais la condition (toujours basée sur les valeurs de sous-dataframe dépend de plus de 2 colonnes.

7
user6903745 17 janv. 2017 à 18:39

2 réponses

Meilleure réponse

Commentaire mis à jour

@unutbu a publié une excellente réponse à une question très similaire ici mais elle apparaît que sa réponse est basée sur pd.rolling_apply qui passe l'index à la fonction. Je ne sais pas comment répliquer cela avec la méthode DataFrame.rolling.apply actuelle.

Réponse originale

Il semble que la variable passée à l'argument via la fonction apply est un tableau numpy de chaque colonne (un à la fois) et non un DataFrame, vous n'avez donc malheureusement pas accès à d'autres colonnes.

Mais ce que vous pouvez faire, c'est utiliser une logique booléenne pour créer temporairement une nouvelle colonne selon que var2 vaut 74 ou non, puis utiliser la méthode de roulement.

df['new_var'] = df.var2.eq(74).mul(df.var1).rolling(2, min_periods=1).sum()

   var1  var2  new_var
0    43    74     43.0
1    44    74     87.0
2    45    66     44.0
3    46   268      0.0
4    47    66      0.0

La colonne temporaire est basée sur la première moitié du code ci-dessus.

df.var2.eq(74).mul(df.var1)
# or equivalently with operators
# (df['var2'] == 74) * df['var1']

0    43
1    44
2     0
3     0
4     0

Recherche du type de variable passé à appliquer

Il est très important de savoir ce qui est réellement passé à la fonction apply et je ne me souviens pas toujours de ce qui est passé, donc si je ne suis pas sûr, j'imprimerai la variable avec son type afin que je sache quel objet je je traite. Consultez cet exemple avec votre DataFrame d'origine.

def foo(x):
    print(x)
    print(type(x))
    return x.sum()

df.rolling(2, min_periods=1).apply(foo)

Production

[ 43.]
<class 'numpy.ndarray'>
[ 43.  44.]
<class 'numpy.ndarray'>
[ 44.  45.]
<class 'numpy.ndarray'>
[ 45.  46.]
<class 'numpy.ndarray'>
[ 46.  47.]
<class 'numpy.ndarray'>
[ 74.]
<class 'numpy.ndarray'>
[ 74.  74.]
<class 'numpy.ndarray'>
[ 74.  66.]
<class 'numpy.ndarray'>
[  66.  268.]
<class 'numpy.ndarray'>
[ 268.   66.]
<class 'numpy.ndarray'>
4
Community 23 mai 2017 à 11:52

L'astuce consiste à définir une fonction qui a accès à l'ensemble de votre dataframe. Ensuite, vous faites un jet sur n'importe quelle colonne et appelez apply() en passant cette fonction. La fonction aura accès aux données de la fenêtre, qui sont un sous-ensemble de la colonne dataframe. À partir de ce sous-ensemble, vous pouvez extraire l'index que vous devriez consulter. (Cela suppose que votre index augmente strictement. Ainsi, l'index d'entier habituel fonctionnera, ainsi que la plupart des séries chronologiques.) Vous pouvez utiliser l'index pour accéder à l'ensemble de la trame de données avec toutes les colonnes.

def dataframe_roll(df):
    def my_fn(window_series):
        window_df = df[(df.index >= window_series.index[0]) & (df.index <= window_series.index[-1])]
        return window_df["col1"] + window_df["col2"]
    return my_fn

df["result"] = df["any_col"].rolling(24).apply(dataframe_roll(df), raw=False)
2
Alexei Andreev 29 juil. 2019 à 21:04