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!