[python] Moyenne exponentielle weekly optimisation

a marqué ce sujet comme résolu.

Bonjour,

J’ai un datframe avec un index qui est de type datetime. Les dates sont daily mais il y a des dates manquantes.

Je cherche à créer une fonction qui en entrée cette datframe, une window et qui sort la moyenne exponentielle weekly de cette time serie.

La moyenne exponentielle weekly à la date dd est définie comme suit: Je prends la date dd et les window1window-1 dernier jours des semaines avant dd et je prend la moyenne exponentielle de ça.

Prendre le dernier jour de chaque semaine dans ma time serie est un peu tricky, et au final j’ai obtenu la fonction suivante (j’ai essayé de commenter pour que tout soit assez clair). J’aimerais bien optimiser cette fonction mais je ne sais pas comment faire? J’ai l’impression de faire du "bidouillage", est ce que vous avez des idées pour rendre cette fonction plus optimale?

def weekly_exponential_moving_average(data: pd.DataFrame, window: int, column: Optional[str] = 'Close_Price') -> pd.Series:
    """
    Calculate the weekly exponential moving average of a specified column in a pandas DataFrame.

    Parameters:
    data (pd.DataFrame): The input data as a pandas DataFrame. The DataFrame should have a datetime index.
    window (int): The window for the exponential moving average, i.e., the number of weeks to include in each average.
    column (Optional[str]): The column in the DataFrame to calculate the moving average for. Defaults to 'Close_Price'.

    Returns:
    pd.Series: A pandas Series containing the weekly exponential moving average values.
    """
    
    if column not in data.columns:
        raise ValueError(f"The specified column '{column}' does not exist in the input DataFrame.")
    
    # Getting the last entry for each week
    last_days = data.groupby([data.index.year, data.index.isocalendar().week]).apply(lambda x: x.index.max())

    # Function to calculate the combined weekly data, including the current day's entry
    def get_weekly_data_for_day(day):
        relevant_dates = [day] + list(last_days[last_days < day][-window+1:])
        return data.loc[relevant_dates, column]

    # Calculate weekly EMA for each day
    def compute_ema_for_day(day):
        weekly_data = get_weekly_data_for_day(day)
        return weekly_data.ewm(span=window, adjust=False).mean().iloc[-1]

    weekly_ema = data.index.to_series().apply(compute_ema_for_day)
    
    return weekly_ema

Merci beaucoup!

+0 -0

Salut,

Ce que tu essaies de faire est pas super-clair.

Le nombre window semble avoir deux rôles : définir le coefficient de lissage de la moyenne exponentielle, et définir le nombre de semaines sur lequel effectuer le lissage. Ça me parait plutôt bizarre, tout l’intérêt de la moyenne exponentielle est qu’elle "oublie" d’elle-même les vieilles valeurs en leur donnant un poids minuscule, donc il n’y a pas besoin de s’embêter à prendre manuellement quelque semaines à la fin de la série pour avoir une valeur contrôlée principalement par ces dernières semaines. La vitesse "d’oubli" des vieilles valeurs est contrôlée par le coefficient de lissage (ici déterminé par le paramètres span de ewn).

De façon plus large, ce que tu veux calculer exactement n’est absolument pas clair (ce qui est plutôt gênant vu que c’est le cœur du sujet…) Tu parles d’avoir des données journalières, et de vouloir "la moyenne exponentielle weekly". Juste ça, ça me donne envie d’écrire juste un truc du genre

df.ewm(halflife="1 week").mean()

df est la série indexée par des dates dont on veut la moyenne exponentielle glissante. Et c’est tout, pas de bidouillage qui me semble aller complètement à contre-courant de comment la moyenne exponentielle est définie et s’utilise classiquement. Le reste des explications me laissent penser soit que ton problème est beaucoup plus subtil, soit que tu te compliques énormément la vie pour rien.

+0 -0

Hello adri1,

Merci pour ta réponse!

J’essaie de calculer la moyenne weekly de ma time série. Le problème de la moyenne weekly c’est que celle-ci ne donne qu’une valeur hebdomadaire. Je veux obtenir une valeur journalière. Pour cela ce que je fais c’est que pour le jour dd, je considère une nouvelle time série sds_d qui s’arrête au jour dd, je calcule sur sds_d la moyenne exponentielle weekly wdw_d et je donne au jour dd la dernière valeur de wdw_d ( qui est donc wd[1]w_d[-1]).

J’espère que c’est plus clair maintenant :lol:

Le code du dessus n’effectue pas cela donc j’ai modifié mon code et j’obtiens:

def weekly_exponential_moving_average(data: pd.DataFrame, window: int, column: Optional[str] = 'Close_Price') -> pd.Series:
    """
    Calculate the weekly exponential moving average of a specified column in a pandas DataFrame.

    Parameters:
    data (pd.DataFrame): The input data as a pandas DataFrame. The DataFrame should have a datetime index.
    window (int): The window for the exponential moving average, i.e., the number of weeks to include in each average.
    column (Optional[str]): The column in the DataFrame to calculate the moving average for. Defaults to 'Close_Price'.

    Returns:
    pd.Series: A pandas Series containing the weekly exponential moving average values.
    """
    
    if column not in data.columns:
        raise ValueError(f"The specified column '{column}' does not exist in the input DataFrame.")
    
    def compute_ema_for_day(day):
        day_before_d = data[data.index <= day][column]
        weekly_data = day_before_d.resample('W').last()
        return weekly_data.ewm(span=window, adjust=False).mean().iloc[-1]

    weekly_ema = data.index.to_series().apply(compute_ema_for_day)
    
    return weekly_ema

Le problème c’est que ce code prend énormément de temps à s’executer vu qu’il calcul à chaque fois la ewm pour toute la time série jusqu’à dd. Je me demande si il n’y pas moyen d’optimiser ça en utilisant du sucre syntaxique Python. J’imagine que c’est possible de faire une boucle for sinon… mais j’aimerais bien qu’il y est une méthode un plus succinte.

C’est toujours pas clair parce que tu es un peu passé à côté de la question centrale que j’ai essayé de poser, qui est de savoir ce que tu entends par "moyenne weekly". Il y a deux façons courantes d’interpréter ça :

  • tu veux calculer une moyenne pour chaque semaine, indépendement de ce qui se passe les autres semaines. C’est rarement intéressant quand on parle de moyenne exponentielle, et comme tu dis que tu veux obtenir une valeur journalière, ça me confirme dans mon intuition que ce n’est pas ce qui t’intéresse.
  • tu veux calculer une moyenne exponentielle avec un coefficient de lissage qui correspond à un temps caractéristique (demi-vie ou ee-folding, peu importe) d’une semaine.

J’ai l’impression que tu es dans le second cas, et dans ce cas encore une fois je me demande bien pourquoi diable tu t’embêtes à faire autre chose que juste

data[column].ewm(halflife=pd.Timedelta("7 days"), times=pd.DatetimeIndex(data.index)).mean()

ça retourne déjà une moyenne glissante calculée pour chaque jour.

+0 -0

Hello!

Je ne veux pas calculer une moyenne glissante sur 7 jours. Je veux calculer une moyenne glissante weekly.

Prenons un exemple pour que ça soit plus clair:

Supposons que j’ai en index : [Samedi, Lundi, Mardi, Mercredi, Jeudi, Lundi, Mardi, Lundi] et en données de prix j’ai [1, 2,3, 4, 5, 6, 7, 8]. Alors maintenant je veux calculer la moyenne exponentielle sur 2 semaines, j’obtiens:

  • pour Samedi ça ne bouge pas cest 11 vu que je n’ai pas de valeurs avant
  • pour le Lundi ensuite ça va être la moyenne exponentielle entre 1 et 2
  • Pour Mardi ça va être entre la moyenne exp du dernier jour de la semaine d’avant et Mardi donc Samedi et Mardi
  • pour Mercredi ça va être entre la moyenne exp du dernier jour de la semaine d’avant et Mercredi donc Samedi et Mercredi
  • pour Jeudi ça va être entre la moyenne exp du dernier jour de la semaine d’avant et Jeudi donc Samedi et Jeudi
  • pour Lundi ça va être entre la moyenne exp du dernier jour de la semaine d’avant et Lundi donc Jeudi et Lundi

donc le code suivant fonctionne très bien:

    def compute_ema_for_day(day):
        day_before_d = data[data.index <= day][column]
        weekly_data = day_before_d.resample('W').last()
        return weekly_data.ewm(span=window, adjust=False).mean().iloc[-1]

    weekly_ema = data.index.to_series().apply(compute_ema_for_day)
    
    return weekly_ema

Je me demande juste si il n’y a pas moyen de l’améliorer.

C’est rudement biscornu comme définition… :-° C’est trop éloigné de quoique ce soit de standard pour qu’il y ait une fonction toute faite qui existe. Et ça veut dire que les performances ne seront jamais très bonnes si tu te limites à du Python pur…

Cela dit, je pense qu’en exploitant la définition récursive de la moyenne exponentielle, tu peux peut être arriver à quelque chose de plus rapide que ta solution. Tu pourrais commencer par calculer le numéro de semaine (en partant de 0) pour chaque entrée de ta table, puis créer une liste dont le but sera de mapper ces numéros à la dernière moyenne connue. Ensuite, pour chaque jour de la table, tu peux aller chercher la moyenne de la semaine précédente dans cette liste, t’en servir pour calculer la moyenne pour le jour courant, puis mettre à jour la liste. Ça donne une implémentation en O(n)O(n), probablement bien meilleure que le O(n2)O(n^2) que tu as actuellement.

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte