Je veux ffill et bfill une colonne spécifique après un groupby.

Ma solution fonctionne:

import numpy as np
import pandas as pd

df = pd.DataFrame({
    "A": [1, 1, 1, 1, 2, 2, 2, 2],
    "B": [np.nan, 'f1', 'b1', np.nan, np.nan, 'f2', 'b2', np.nan]
})
df['B'] = df.groupby('A')['B'].apply(lambda _: _.ffill().bfill())

Donc ça:

    A   B
0   1   NaN
1   1   f1
2   1   b1
3   1   NaN
4   2   NaN
5   2   f2
6   2   b2
7   2   NaN

Devient ceci:

    A   B
0   1   f1
1   1   f1
2   1   b1
3   1   b1
4   2   f2
5   2   f2
6   2   b2
7   2   b2

Notez que les séquences que je souhaite remplir et bfill seront toujours dans ce format (Nan, x, y, Nan)

Bien que cela fonctionne, il est extrêmement lent sur les grandes données.

Je recherche une optimisation pour rendre cela plus rapide (idéalement sans recourir à Dask ou au multitraitement), peut-être que je peux faire une optimisation Numpy?

Je n'ai pas eu beaucoup de chance en regardant d'autres réponses, comme celui-ci.

4
Andy 4 juin 2020 à 21:15

3 réponses

Meilleure réponse

Si vous voulez de la vitesse, éviter le groupby et utiliser numpy au lieu de pandas sont de bonnes règles à suivre. Ce n'est souvent pas possible, mais ici vous avez un cas particulier avec des données extrêmement régulières et tout ce dont vous avez besoin est un triplet d'indice de forme [start:end:stride]:

df.iloc[0::4,1] = df.iloc[1::4,1].values
df.iloc[3::4,1] = df.iloc[2::4,1].values

Explication: La plupart des gens savent que vous pouvez utiliser des indices de la forme [start:stop], mais vous pouvez également ajouter un argument stride facultatif. Donc la première ligne dit de remplacer les éléments 0,4,8, ... par les éléments 1,5,9, ... Les "valeurs" sont nécessaires pour supprimer l'indexation des pandas qui est en fait préjudiciable ici.

Cela devrait être un peu plus rapide simplement en évitant le groupby. Pour un peu plus de vitesse, vous pouvez afficher la colonne B dans numpy, travailler dans numpy (essentiellement le même code), puis réimporter vers les pandas:

arr = df.B.values
arr[0::4] = arr[1::4]  
arr[3::4] = arr[2::4]
df.B = arr

Une autre chose que vous pourriez faire si vous vouliez rester dans les pandas serait de désempiler, de copier des colonnes entières, puis de les réempiler. C'est essentiellement ce que fait le code ci-dessus de toute façon. Honnêtement, avec un problème aussi rectangulaire, toute approche de type tableau sera assez rapide.

3
JohnE 4 juin 2020 à 19:45

Si vos données sont vraiment bien structurées avec des groupes continus, vous pouvez éviter groupby en utilisant le paramètre limit dans ffill et bfill comme:

print (df['B'].ffill(limit=1).bfill(limit=1))
0    f1
1    f1
2    b1
3    b1
4    f2
5    f2
6    b2
7    b2
Name: B, dtype: object
2
Ben.T 4 juin 2020 à 19:16

Si votre format est préfixé en tant que (Nan, x, y, Nan), quand pouvez-vous le faire

df.B=df.groupby([df.A,df.index//2]).B.transform('first')
Out[169]: 
    B
0  f1
1  f1
2  b1
3  b1
4  f2
5  f2
6  b2
7  b2
1
YOBEN_S 4 juin 2020 à 18:28