J'ai une table comme celle-ci:
col_to_roll max_duration rolled_col
0 0 0 NaN
1 0 0 NaN
2 0 0 NaN
3 2000 12 NaN
4 0 0 NaN
5 0 0 NaN
6 700 8 NaN
7 0 0 2000.0
8 0 0 4000.0
9 530 2 6000.0
10 1000 5 8700.0
11 820 15 1400.0
12 0 0 2100.0
13 0 0 3330.0
14 200 3 2060.0
Pour chaque position de ligne i , je veux faire une somme glissante de col_to_roll entre les index i-7 et i-4 (tous deux inclus). La mise en garde est que je veux que les valeurs "plus loin dans le passé" soient comptées davantage, en fonction de la colonne max_duration (qui indique pour combien de pas temporels dans le futur cette valeur peut encore avoir un effet).
Il y a une limite supérieure qui correspond aux pas de temps restants à compter (min 1, max 4). Donc, si je suis sur la ligne numéro 7 en train de faire la somme min(max_duration[1],4) : la valeur de la ligne numéro 1 sera comptée min(max_duration[1],4) , la valeur de la ligne numéro 2 sera comptée min(max_duration[2],3) etc.
Je pourrais le faire à la manière de la force brute:
new_col = []
for i in range(7,len(ex)) :
rolled_val = sum([ex.iloc[j].col_to_roll*min(ex.iloc[j].max_duration , i-j+1-4) \
for j in range(i-7,i-3)])
new_col.append(rolled_val)
ex['rolled_col'] = [np.nan]*7+new_col
Ce qui aboutit aux résultats suivants de l'exemple ci-dessus:
import pandas as pd
values = [0,0,0,2000,0,0,700,0,0,530,1000,820,0,0,200]
durations = [0,0,0,12,0,0,8,0,0,2,5,15,0,0,3]
ex = pd.DataFrame({'col_to_roll' : values, 'max_duration': durations})
col_to_roll max_duration
0 0 0
1 0 0
2 0 0
3 2000 12
4 0 0
5 0 0
6 700 8
7 0 0
8 0 0
9 530 2
10 1000 5
11 820 15
12 0 0
13 0 0
14 200 3
Cela étant dit, j'apprécierais un moyen plus élégant (et plus important encore, plus efficace) d'obtenir ce résultat avec des pandas magiques.
3 Réponses :
Juste pour partager mes idées, cela peut être résolu en utilisant numpy sans boucle for
import numpy as np ex_len = ex.shape[0] inds = np.vstack([range(i-7,i-3) for i in range(7,ex_len)]) # part one col_to_roll = np.take(ex.col_to_roll.values,inds) # part two max_duration = np.take(ex.max_duration.values,inds) duration_to_compare = np.array([[i-j+1-4 for j in range(i-7,i-3)]for i in range(7,ex_len)]) min_mask = max_duration > duration_to_compare max_duration[min_mask] = duration_to_compare[min_mask] new_col = np.sum(col_to_roll*max_duration,axis=1) ex['rolled_col'] = np.concatenate(([np.nan]*7,new_col))
Voici mon humble idée d'une méthode élégante et efficace pour cette tâche. Pour ne pas réinventer la roue, installons pandarallel en invoquant pip install pandarallel . Je suis un fan du multitraitement, et cela devrait aider avec des données plus volumineuses.
INFO: Pandarallel will run on 8 workers.
INFO: Pandarallel will use Memory file system to transfer data between the main process and workers.
col_to_roll max_duration rolled_col
0 0 0 NaN
1 0 0 NaN
2 0 0 NaN
3 2000 12 NaN
4 0 0 NaN
5 0 0 NaN
6 700 8 NaN
7 0 0 2000.0
8 0 0 4000.0
9 530 2 6000.0
10 1000 5 8700.0
11 820 15 1400.0
12 0 0 2100.0
13 0 0 3330.0
14 200 3 2060.0
Production:
import pandas as pd
import numpy as np
from pandarallel import pandarallel
def rocknroll(index):
if index>=7:
a = ex['col_to_roll'].iloc[index-7:index-3]
b = map(min, ex['max_duration'].iloc[index-7:index-3], [4,3,2,1])
return sum(map(mul, a, b))
else:
return np.nan
pandarallel.initialize()
values = [0,0,0,2000,0,0,700,0,0,530,1000,820,0,0,200]
durations = [0,0,0,12,0,0,8,0,0,2,5,15,0,0,3]
ex = pd.DataFrame({'col_to_roll' : values, 'max_duration': durations})
ex['index_copy'] = list(range(0, len(ex)))
ex['rolled_col'] = ex['index_copy'].apply(rocknroll)
ex.drop(columns={'index_copy'}, inplace=True)
print(ex)
Vous trouverez ici de plus amples informations sur le bon fonctionnement élément par élément. Ajout élément par élément de 2 listes?
Vous pouvez utiliser pd.rolling() pour créer des fenêtres pd.rolling() en combinaison avec apply pour calculer la somme rolled_coll pour les fenêtres rolled_coll spécifiées
Calculez d' abord la taille de la fenêtre en utilisant la limite inférieure et supérieure (et ajoutez 1 pour inclure les deux indices). Cela vous permet de jouer avec différents intervalles de temps.
col_to_roll max_duration rolled_col 0 0 0 NaN 1 0 0 NaN 2 0 0 NaN 3 2000 12 NaN 4 0 0 NaN 5 0 0 NaN 6 700 8 NaN 7 0 0 2000.0 8 0 0 4000.0 9 530 2 6000.0 10 1000 5 8700.0 11 820 15 1400.0 12 0 0 2100.0 13 0 0 3330.0 14 200 3 2060.0
Deuxièmement définir la fonction à apply sur chaque fenêtre glissante. Dans votre cas, prenez le product de col_to_roll et la valeur minimale de max_duration et une liste de plage de 4 à 0 et additionnez toutes les valeurs dans la fenêtre glissante.
ex.assign(rolled_col=lambda x: x.rolling(window_size)
.apply(lambda x: calculate_rolled_count(x, ex))
.shift(-upper_bound)['max_duration'])
Enfin, attribuez une nouvelle colonne rolled_coll à votre dataframe d'origine et appliquez la fonction définie sur toutes les fenêtres déroulantes. Nous devons décaler les colonnes pour que la valeur corresponde à la ligne souhaitée (car la fenêtre déroulante définit par défaut les valeurs dans la limite droite de la fenêtre)
def calculate_rolled_count(series, ex):
index = series.index
min_values = np.minimum(ex.loc[index, 'max_duration'].values, list(range(4, 0, -1)))
return np.sum(ex.loc[index, 'col_to_roll'] * min_values)
Résultat
lower_bound = -7 upper_bound = -4 window_size = upper_bound - lower_bound + 1
Maintenant, je sais que pd.rolling () existe. Merci!