Calculs de séries temporelles avec Envision

Calculs de séries temporelles avec Envision











Accueil » Ressources » Ici

La plupart des activités d'un commerce peuvent être représentées précisément à l'aide de séries temporelles : historique des ventes, historique des commandes d'achats, historique des prix, etc. La manipulation de séries temporelles est donc essentielle pour les entreprises et Envision prend entièrement en charge de tels scénarios. Envision permet notamment d'agréger les données par jour, semaine ou mois. Envision facilite également les analyses plus complexes de séries temporelles comme les données retardées ou le calcul de moyennes mobiles. Ces fonctionnalités sont illustrées et expliquées dans le présent article.

Exemple

Une fois de plus nous utilisons notre échantillon de données pour illustrer les capacités d'Envision. Nous vous recommandons de lire Faire des calculs et Agréger des données avant le texte qui suit et porte sur une utilisation avancée d'Envision.

read "/sample/Lokad_Items.tsv"
read "/sample/Lokad_Orders.tsv" as Orders
read "/sample/Lokad_PurchaseOrders.tsv" as PO

show label "Time-series calculations with Envision" a1f1 tomato

end := max(date)

Week.sold := sum(Orders.NetAmount)
when date >= end - 52 * 7
show linechart "Weekly sales{$}" a2f4 tomato with
Week.sold as "This year"
Week.sold[-52] as "Last year"

Week.ma := sum(Orders.NetAmount / 4) over [-3 .. 0]
when date >= end - 52 * 7
show linechart "Weekly sales with 4 weeks moving average{$}" a5f7 tomato with
Week.ma as "This year"
Week.ma[-52] as "Last year"

Day.cashIn := sum(Orders.NetAmount)
Day.cashOut := sum(PO.NetAmount)
Day.balance := avg(Day.cashIn - Day.cashOut) over [-13 .. 0]
when date >= end - 6 * 7 & date < monday(end)
show linechart "Cash flow over the last 6 weeks{$}" a8f10 tomato with
Day.balance as "Balance"

PO.Price = PO.NetAmount / PO.Quantity
Orders.PurchasePrice = latest(PO.Price)
Orders.Cost = Orders.PurchasePrice * Orders.Quantity
Week.profitability := sum(Orders.NetAmount - Orders.Cost) / sum(Orders.Cost) or 1
when date >= monday(end) - 13 * 7 & date < monday(end)
show linechart "Profitability over the last 13 weeks{%}" a11f13 tomato with
Week.profitability

Une fois ce script exécuté avec l'échantillon de données, le tableau de bord ci-dessous s'affiche.

Image

Tables calendrier virtuelles

Puisque les agrégations par jour, par semaine et par mois sont monnaie courante, Envision a été conçu pour faciliter leur manipulation. Envision bénéficie en particulier de trois tables virtuelles appelées Day (jour), Week (semaine) et Month (mois). Ces tables sont désignées comme "virtuelles" car elles n'ont pas d'homologue sous forme de fichier tabulaire : elles existent uniquement pendant l'exécution des scripts. Le script ci-dessus exploite ces tables virtuelles pour afficher des graphiques linéaires. Voyons les lignes de code en question, copiées ci-dessous.

Week.sold := sum(Orders.NetAmount)
end := max(date)
when date >= end - 52 * 7
show linechart "Weekly sales{$}" a2f4 tomato with
Week.sold as "This year"
Week.sold[-52] as "Last year"

À la ligne 1, la somme du contenu de la table Orders est affectée à la table Week. Puisque l'affectation scalaire := est utilisée ici, une seule valeur est calculée pour chaque semaine. À la ligne 2, un filtre est défini pour exclure les données qui ont plus de 52 semaines. Enfin, des lignes 3 à 5, un graphique linéaire avec deux séries temporelles est défini. La deuxième série temporelle, Week.sold[-52], utilise un opérateur retard qui sera présenté dans la section suivante.

Ce script peut être facilement adapté à une agrégation journalière ou mensuelle. Il est possible, par exemple, d'ajouter les lignes ci-dessous à la fin du script :

Day.sold := sum(Orders.NetAmount)
show linechart "Daily sales{$}" a14f16 tomato with Day.sold
Month.sold := sum(Orders.NetAmount)
show linechart "Monthly sales{$}" a17f19 tomato with Month.sold

Ce code affiche deux graphiques linéaires supplémentaires, qui représentent des séries temporelles agrégées par jour et par mois. La variable Day.sold peut être interprétée comme le contenu de la colonne sold de la table Day mais également comme une série temporelle avec des intervalles égaux de "1 jour" — contrairement aux tables Envision classiques qui contiennent une colonne Date, comme la table Orders par exemple, et qui peuvent être interprétées comme des séries temporelles à intervalles irréguliers (lien en anglais).

Affichage de séries temporelles avec opérateur retard

Dans l'analyse des séries temporelles, l'opérateur retard est l'opérateur qui, à tout élément d'une série temporelle, associe l'observation précédente. Cet opérateur est utile pour comparer deux périodes distinctes. Envision permet l'utilisation d'un opérateur retard qui vise précisément ces situations. Reprenons le code présenté précédemment.

Week.sold := sum(Orders.NetAmount)
when date >= end - 52 * 7
show linechart "Weekly sales{$}" a2f4 tomato with
Week.sold as "This year"
Week.sold[-52] as "Last year"

Deux séries temporelles sont définies aux lignes 4 et 5. La première, Week.sold, correspond à l'agrégation initiale des ventes par semaine. Un suffixe supplémentaire, [-52], est présent dans la seconde. Il s'agit de l'opérateur retard en lui-même, qui a pour effet de ramener les données qui ont 52 semaines pour les afficher dans le graphique. Lorsque cet opérateur est utilisé avec la table Week (semaine), l'argument retard correspond à un nombre entier de semaines. De la même façon, l'argument correspond à un nombre de jours ou de mois lorsque l'opérateur est associé à la table Day ou Month, respectivement.

L'opérateur retard bénéficie de la coopération intelligente du filtre when, sans quoi toutes les données qui ont plus de 52 semaines seraient exclues et seuls des zéros seraient ramenées par l'opérateur retard — comme vous pouvez le constater dans le tableau de bord affiché plus haut, les données de l'année précédente sont bien présentes dans le graphique. Ce comportement est obtenu grâce la coopération, intégrée à Envision, entre le filtre when et l'opérateur retard.

Si vous passez votre souris sur la courbe d'un graphique dans un tableau de bord, vous verrez apparaître les dates et les valeurs. Sur le graphique affiché par le script présenté plus haut, la dates de la série temporelle "Last year" sont bien décalées d'un an par rapport à celles de la série temporelle "This year". Notez cependant que l'opérateur retard ne conserve pas vraiment les dates originales de la série temporelle. En réalité, Envision utilise la convention suivante : si l'opérateur retard est défini dans la déclaration du graphique alors, seulement dans ce cas, les dates originales sont conservées.

Modifions le script pour appliquer l'opérateur retard en dehors du graphique, en introduisant une variable nommée Week.lastYear.
Week.sold := sum(Orders.NetAmount)
Week.lastYear := Week.sold[-52]
when date >= end - 52 * 7
show linechart "Weekly sales{$}" a2f4 tomato with
Week.sold as "This year"
Week.lastYear as "Last year" // pb d'affichage de date

Si vous exécutez ce script et passez votre souris sur la courbe, vous devriez remarquer que ce sont les même dates qui sont indiquées pour les deux séries. En effet, dans ce cas, la sémantique est ambiguë quant à la date à afficher. Par exemple, la série Week.lastYear pourrait être interprétée comme une prévision d'une année sur l'autre et, dans ce cas, il serait normal d'afficher les mêmes dates pour les deux séries. Donc, si vous voulez conserver les dates originales dans votre graphique pour comparer des séries temporelles avec opérateur retard, ce dernier doit être défini dans l'instruction show.

Agréger des données sur une période donnée

Dans l'un de nos guides précédents, nous avons passé en revue l'agrégation de données. Avec les séries temporelles, un autre type d'agrégation est très utile : l'agrégation sur une période donnée. Le script présenté au début du présent article illustre comment calculer la moyenne mobile des ventes sur 4 semaines, grâce aux lignes copiées ci-dessous.

Week.ma := sum(Orders.NetAmount / 4) over [-3 .. 0]
when date >= end - 52 * 7
show linechart "Weekly sales with 4 weeks moving average{$}" a5f7 tomato with
Week.ma as "This year"
Week.ma[-52] as "Last year"

À la ligne 1, une agrégation avec l'agrégateur sum() est effectuée et elle contient une instruction qui commence par le mot-clé over. À la ligne 2, les données sont filtrées pour ne conserver que celles des dernières 52 semaines. Enfin, aux lignes 3 à 5, deux séries temporelles sont affichées dans le graphique. Elles sont toutes deux "lissées" car elles correspondent à des moyennes sur 4 semaines.

L'opérateur over est utilisé pour définir la période d'application et doit être rédigé de la façon suivante : [a .. b], a et b étant des nombres entiers, a étant inférieur ou égal à b. L'unité de a et b dépend de l'expression à gauche de l'affectation. Dans le cas présent, c'est la table Week (semaine) qui se trouve à gauche de l'affectation et donc -3 et 0 sont exprimés en semaines.

Astuce : entre la semaine de l'index -3 et celle de l'index 0, il y a bien 4 semaines et non pas 3 : -3, -2, -1 et 0.

L'option over peut être utilisée avec tous les agrégateurs. Lorsqu'elle est utilisée, la partie gauche de l'affectation est généralement une table calendrier virtuelle comme Day, Week ou Month. Mais ce n'est pas une obligation, toutes les tables indexées par date peuvent être utilisées. De plus, par définition, lorsque des tables calendrier sont utilisées, over [0 .. 0] aboutit au même résultat que l'agrégation par défaut :
Week.sold := sum(Orders.NetAmount)
Week.same := sum(Orders.NetAmount) over [0 .. 0] // même résultat !

Agrégations de séries temporelles plus complexes

La syntaxe d'Envision offre la possibilité d'effectuer des calculs de séries temporelles plus élaborées. Par exemple, il est possible de calculer une série temporelle, puis, d'effectuer plus de calculs sur cette série initiale, comme l'illustre le troisième bloc de code du script au début du présent article. Les lignes concernées sont copiées ci-dessous :

Day.cashIn := sum(Orders.NetAmount)
Day.cashOut := sum(PO.NetAmount)
Day.balance := avg(Day.cashIn - Day.cashOut) over [-13 .. 0]
when date >= monday(end) - 6 * 7 & date < monday(end)
show linechart "Cash flow over the last 6 weeks{$}" a8f10 tomato with
Day.balance as "Balance"

Aux lignes 1 et 2, les deux séries temporelles Day.cashIn et Day.cashOut représentent, respectivement, les totaux des ventes et des achats. À la ligne 3, la moyenne mobile sur 14 jours de la différence entre cashIn et cashOut est calculée dans la série temporelle Day.balance. À la ligne 4, un filtre avec deux conditions spécifiques est défini : les données ne doivent pas avoir plus de 7 semaines — à partir de lundi dernier — et les données ne doivent être plus récentes que celles de lundi dernier. Ici l'utilisation de la fonction monday() garantit qu'exactement 7 semaines complètes sont utilisées. Enfin, la série temporelle Day.balance est affichée seule dans le graphique défini aux lignes 5 et 6.

L'agrégation de la ligne 3 tire parti de l'option over, détaillée dans la section précédente. Ici, les deux séries temporelles contiennent des moyennes sur une période de 14 jours (entre l'index -13 et l'index 0, il y a bien 14 jours). Cette agrégation est quelque peu différente de celles que nous avons vues jusqu'ici puisque la table Day est présente à la fois à gauche et à droite de l'affectation, alors que les agrégations plus classiques qui n'ont pas de suffixe over agrègent généralement des données d'une table dans une autre.

Il est également possible de réagréger des séries temporelles basées sur des jours en séries temporelles basées sur des semaines, comme l'illustre le script ci-dessous avec le calcul de flux de trésorerie hebdomadaires plutôt que quotidiens.

Day.cashIn := sum(Orders.NetAmount)
Day.cashOut := sum(PO.NetAmount)
Week.balance := sum((Day.cashIn - Day.cashOut) / 2) over [-1 .. 0]
when date >= monday(end) - 6 * 7 & date < monday(end)
show linechart "Cash flow over the last 6 weeks{$}" a8f10 tomato with
Week.balance as "Balance"

Gestion de données associées à des événements

Les données associées à des événements correspondent à une certaine représentation de l'historique des données qui se concentre sur les "changements". Par exemple, au lieu de conserver l'historique des prix pour chaque jour, il est plus pratique de ne garder que les changements de prix : à chaque changement est associé une date et un nouveau prix, qui est considéré identique jusqu'au prochain changement. Avec Envision, il est possible de manipuler des historiques de données qui ne contiennent que des changements.

L'échantillon de données ne contient pas de table avec un historique de prix. Il est cependant possible de l'approximer à partir des achats enregistrés, en faisant l'hypothèse que le prix ne change pas entre deux transactions. C'est exactement ce dont il s'agit dans le dernier bloc de code du script, dont les lignes sont copiées ci-dessous.

PO.Price = PO.NetAmount / PO.Quantity
Orders.PurchasePrice = latest(PO.Price)
Orders.Cost = Orders.PurchasePrice * Orders.Quantity
Week.profitability := sum(Orders.NetAmount - Orders.Cost) / sum(Orders.Cost) or 1
when date >= monday(end) - 13 * 7 & date < monday(end)
show linechart "Profitability over the last 13 weeks{%}" a11f13 tomato with
Week.profitability

À la ligne 1, le prix d'achat par unité est calculé pour chaque ligne de la table PO à travers une opération qui repose sur un vecteur, comme d'habitude avec Envision. À la ligne 2, pour chaque ligne de la table Orders, la fonction latest recherche la valeur PO.Price à laquelle est associée la date la plus récente, tout en étant antérieure à celle de la ligne de la table Orders, puis copie cette valeur sur la gauche de l'affectation. Sur les lignes 3 et 4 se trouvent d'autres calculs qui utilisent des vecteurs et des agrégations. Enfin, à la ligne 5, un filtre limite le périmètre des opérations aux 13 dernières semaines complètes et, dans ce filtre, un graphique est affiché et exploite la série temporelle Week.profitability calculée à la ligne 4.

La fonction latest est l'ingrédient sur lequel repose tout le calcul en question. Cette fonction vise précisément à capter l'essence d'une suite d'événements dans laquelle une valeur est censée rester constante jusqu'à ce qu'elle soit écrasée par un nouvel événement. Il est également possible de "densifier" les prix d'achats, en calculant un prix pour chaque jour de l'historique :

Day.PurchasePrice = latest(PO.Price)

Dans la pratique, la fonction latest peut être utilisée dans de nombreux cas, comme les ruptures de stock, les promotions, les cycles de vie des produits, etc.