2017-09-04 9 views
1

J'ai une série temporelle dans un DataFrame Pandas. Les horodatages peuvent être inégaux (un toutes les 1-5 minutes), mais il y aura toujours un toutes les 5 minutes (horodateurs avec des minutes se terminant par 0,5,10,15,20,25,30,35,40,45,50 , 55).Python - Pandas moyens pondérés dans le temps, regroupés par intervalle de temps

Exemple:

2017-01-01 2:05:00 32.90 
2017-01-01 2:07:30 29.83 
2017-01-01 2:10:00 45.76 
2017-01-01 2:15:00 16.22 
2017-01-01 2:20:00 17.33 
2017-01-01 2:25:00 23.40 
2017-01-01 2:28:45 150.12 
2017-01-01 2:30:00 100.29 
2017-01-01 2:35:00 38.45 
2017-01-01 2:40:00 67.12 
2017-01-01 2:45:00 20.00 
2017-01-01 2:50:00 58.41 
2017-01-01 2:55:00 58.32 
2017-01-01 3:00:00 59.89 

Je veux obtenir la moyenne pondérée dans le temps des blocs de 15 minutes. Les lignes avec un horodatage qui se trouve directement sur une marque de 15 minutes (avec des horodatages minutes se terminant par 0,15,30,45) mettre fin à un intervalle, de sorte que le groupement est comme suit:

Group 1 (interval 2017-01-01 2:00:00): 
    2017-01-01 2:05:00 32.90 
    2017-01-01 2:07:30 29.83 
    2017-01-01 2:10:00 45.76 
    2017-01-01 2:15:00 16.22 

Group 2 (interval 2017-01-01 2:15:00): 
    2017-01-01 2:20:00 17.33 
    2017-01-01 2:25:00 23.40 
    2017-01-01 2:28:45 150.12 
    2017-01-01 2:30:00 100.29 

Group 3 (interval 2017-01-01 2:30:00): 
    2017-01-01 2:35:00 38.45 
    2017-01-01 2:40:00 67.12 
    2017-01-01 2:45:00 20.00 

Group 4 (interval 2017-01-01 2:45:00): 
    2017-01-01 2:50:00 58.41 
    2017-01-01 2:55:00 58.32 
    2017-01-01 3:00:00 59.89 

La moyenne doit être pondérée en fonction du temps, donc pas seulement une moyenne standard de toutes les valeurs d'un groupe. Par exemple, la moyenne pondérée dans le temps du groupe 2 n'est PAS 72.785, ce qui est la moyenne régulière des 4 valeurs. Au contraire, il devrait être:

(5 minutes/15 minutes) * 17.33 = 5.776667  ==> The 5 minutes is taken from the difference between this timestamp and the previous timestamp 
+(5 minutes/15 minutes) * 23.40 = 7.8 
+(3.75 minutes/15 minutes) * 150.12 = 37.53 
+(1.25 minutes/15 minutes) * 100.29 = 8.3575 

= **59.46417** 

idéalement En outre, les 15 minutes est paramétrés, car cela pourrait changer à l'avenir à 60 minutes (toutes les heures), mais je ne pense pas que ce soit un problème ici.

En outre, les performances sont très importantes dans ce domaine. Comme mon jeu de données aura environ 10k lignes, l'itération de chaque enregistrement un par un sera plutôt lente.

J'ai essayé de regarder dans la fonction df.rolling() de Pandas, mais je n'ai pas pu trouver comment l'appliquer directement à mon scénario spécifique.

Merci beaucoup pour votre aide!

MISE À JOUR 1:

Après solution brillante de Simon, je l'ai modifié un peu.

J'ai fait quelques modifications à elle pour l'adapter à mon cas particulier:

def func(df): 
    if df.size == 0: return 
    timestep = 15*60 
    indexes = df.index - (df.index[-1] - pd.Timedelta(seconds=timestep)) 
    seconds = indexes.seconds 
    weight = [seconds[n]/timestep if n == 0 else (seconds[n] - seconds[n - 1])/timestep 
      for n, k in enumerate(seconds)] 
    return np.sum(weight*df.values) 

Ceci pour faire face à peut-être des intervalles vides de 15 minutes (lignes manquantes dans le DB)

Répondre

3

Celui-ci était rusé. J'aimerais voir un autre intervenant le faire plus efficacement, car j'ai l'impression qu'il existe une meilleure façon de le faire. J'ai également sauté une partie, qui est en train de paramétrer la valeur de 15 minutes, mais je précise comment vous pourriez le faire dans le commentaire. Ceci est laissé comme un exercice pour le lecteur: D Il devrait être paramétré cependant, étant donné qu'il existe maintenant beaucoup de valeurs aléatoires '* 15' et '* 60' dispersées autour du lieu, ce qui semble maladroit. Je suis aussi fatigué, et ma femme veut regarder un film, donc je n'ai pas nettoyé mon code. C'est un peu brouillon, et devrait être écrit propre - ce qui peut ou ne peut pas être utile, selon si quelqu'un d'autre peut refaire tout cela dans 6 lignes de code. Si demain matin il est encore sans réponse, je reviendrai et ferai mieux.

Mise à jour meilleure solution 1

def func(df): 
    timestep = 15*60 
    seconds = (df.index.minute*60+df.index.second)-timestep 
    weight = [k/timestep if n == 0 else (seconds[n] - seconds[n - 1])/timestep 
       for n, k in enumerate(seconds)] 
    return np.sum(weight*df.values) 

df.resample('15min', closed='right').apply(func) 
+0

C'est génial! Merci beaucoup, c'est exactement ce dont j'avais besoin! Existe-t-il un moyen d'utiliser GroupBy() au lieu de Resample()? La raison en est que j'ai une autre colonne que je veux regrouper, que je n'ai pas incluse dans la question originale pour des raisons de simplicité. Je semble être la table à utiliser: df.groupby ([pd.TimeGrouper (freq = '15Min')]) Mais il ne semble pas y avoir un moyen de fermer le groupe avec le côté droit, comme le La fonction resample() a. –

+0

Donc, fondamentalement, j'ai les 4 colonnes suivantes dans ma table: « TIME | ZONE | PRICE1 | PRICE2 » Et je veux avoir une moyenne pondérée dans le temps par zone et par intervalle de 15 minutes pour chaque prix –

+0

je l'ai fait quelques tests supplémentaires avec plus de données et le tout est très lent; Peut-être que je ne suis pas habitué à la vitesse de Python. Pour traiter 1,6 million de lignes (en 530k groupes de ~ 3 lignes chacune), il a fallu environ 10 minutes. J'ai fait de même dans un programme C# (le code était beaucoup plus long car je devais itérer manuellement sur chaque ligne) et cela prenait moins de 10 secondes. –