1
votes

Numpy: utiliser la vectorisation pour la boucle tout en faisant référence à la valeur de ligne précédente?

J'ai le dataframe suivant pour lequel je veux créer une colonne nommée 'Value' en utilisant numpy pour une boucle rapide et en même temps faire référence à la valeur de ligne précédente dans la même colonne.

for i in range(len(df)):
    if df.loc[i, "Is First?"] == "Yes":
        df.loc[i, "Value"] = df.loc[i, "Inbound"] + df.loc[i, "Outbound"]
    else:
        df.loc[i, "Value"] = df.loc[i, "Value"].shift(-1) + df.loc[i, "Outbound"]

XXX

La formule de la colonne Value dans le pseudocode est:

if ['Is First?'] = 'Yes' then [Value] = [Inbound] + [Outbound]
else [Value] = [Previous Value] - [Outbound]

La manière idéale de créer le La colonne de valeur en ce moment est de faire une boucle for et d'utiliser shift pour faire référence à la colonne précédente (que je ne suis pas en mesure de faire fonctionner). Mais comme je vais l'appliquer sur un ensemble de données géant, je souhaite utiliser la méthode de vectorisation numpy dessus.

  Product  Inbound  Outbound Is First?  Value
0       A      115        10       Yes    125
1       A      220        20        No    105
2       A      200        24        No     81
3       A      402        52        No     29
4       B      313        40       Yes    353
5       B      434        12        No    341
6       B      321        43        No    298
7       C      343        23       Yes    366
8       C      120        16        No    350


1 commentaires

un «oui» va-t-il toujours avec un autre nom de produit?


4 Réponses :


1
votes

Ce n'est pas une tâche anodine, la difficulté réside dans les Non consécutifs. Il est nécessaire de regrouper les non consécutifs, le code ci-dessous devrait faire,

col_sum = df.Inbound+df.Outbound

mask_no = df['Is First?'].eq('No')

mask_yes = df['Is First?'].eq('Yes')

consec_no = mask_yes.cumsum()

result = col_sum.groupby(consec_no).transform('first')-df['Outbound'].where(mask_no,0).groupby(consec_no).cumsum()


0 commentaires

1
votes

Utiliser :

df.loc[df['Is First?'].eq('Yes'),'Value']=df['Inbound']+df['Outbound']
df.loc[~df['Is First?'].eq('Yes'),'Value']=df['Value'].fillna(0).shift().cumsum()-df.loc[~df['Is First?'].eq('Yes'),'Outbound'].cumsum()


1 commentaires

C'est faux car le premier cum est calculé sur les groupes "Oui" .



2
votes

Aller simple :
Vous pouvez utiliser np.subtract.accumulate avec transform

df['value'] = (df.Inbound + df.Outbound).where(df['Is First?'].eq('Yes'))
s = df['Is First?'].eq('Yes').cumsum()
s1 = df.value.ffill() - df.Outbound.shift(-1).groupby(s).cumsum().shift()
df['value'] = df.value.fillna(s1)

Out[1671]:
  Product  Inbound  Outbound Is First?  value
0       A      115        10       Yes  125.0
1       A      220        20        No  105.0
2       A      200        24        No   81.0
3       A      402        52        No   29.0
4       B      313        40       Yes  353.0
5       B      434        12        No  341.0
6       B      321        43        No  298.0
7       C      343        23       Yes  366.0
8       C      120        16        No  350.0

Autre moyen :
Attribuez une valeur à Oui . Créez des s groupid à utiliser pour groupby. Groupby et décalez Sortant pour calculer le cumulé, et soustrayez-le de la valeur «Oui» de chaque groupe. Enfin, utilisez-le pour fillna.

s = df['Is First?'].eq('Yes').cumsum()
df['value'] = ((df.Inbound + df.Outbound).where(df['Is First?'].eq('Yes'), df.Outbound)
                                         .groupby(s)
                                         .transform(np.subtract.accumulate))

Out[1749]:
  Product  Inbound  Outbound Is First?  value
0       A      115        10       Yes    125
1       A      220        20        No    105
2       A      200        24        No     81
3       A      402        52        No     29
4       B      313        40       Yes    353
5       B      434        12        No    341
6       B      321        43        No    298
7       C      343        23       Yes    366
8       C      120        16        No    350


2 commentaires

Pourrait le rendre encore plus court si un nouveau produit correspond toujours à un Oui: df.loc [df ['Is First?']. Eq ('Yes'), 'Value'] = df.Inbound + df.Outbound df.loc [df ['Est le premier?']. eq ('Non'), 'Valeur'] = df.Value.ffill () - df.Outbound.shift (-1) .groupby (df.Product). c‌ umsum (). shift ()


ah, je vois ce que tu veux dire. J'ai envisagé df.Product pour groupby. Cependant, j'ai décidé de ne pas le faire parce que la logique d'OP ne le dit jamais. Sa logique ne mentionne que les valeurs de 'Is First?' , donc je dois créer des s à utiliser pour groupby.



1
votes

Code numpy annoté:

  Product  Inbound  Outbound Is First?  Value
0       A      115        10       Yes    125
1       A      220        20        No    105
2       A      200        24        No     81
3       A      402        52        No     29
4       B      313        40       Yes    353
5       B      434        12        No    341
6       B      321        43        No    298
7       C      343        23       Yes    366
8       C      120        16        No    350

Résultat:

## 1. line up values to sum

ob = -df["Outbound"].values
# get yes indices
fi, = np.where(df["Is First?"].values == "Yes")
# insert yes formula at yes positions
ob[fi] = df["Inbound"].values[fi] - ob[fi]

## 2. calculate block sums and subtract each from the
## first element of the **next** block

ob[fi[1:]] -= np.add.reduceat(ob,fi)[:-1]
# now simply taking the cumsum will reset after each block
df["Value"] = ob.cumsum()


0 commentaires