Calcolare le serie temporali con Envision - Software di ottimizzazione delle scorte

Calcolare le serie temporali con Envision












Home » Risorse » Qui

La maggioranza delle operazioni di un'attività commerciale può essere rappresentata attraverso le serie temporali, o storiche (storico delle vendite, storico degli ordini di acquisto, dati storici sui prezzi, etc.). Le serie temporali sono di primaria importanza per qualsiasi azienda commerciale: per questo Envision consente di gestire le serie, aggregare i dati per giorno, settimana o mese e condurre analisi più complesse sui dati storici, come lag temporali e calcolo delle medie mobili. In questa pagina vedremo come sfruttare al meglio queste funzionalità.

Lo script: un esempio

Partiamo, come di consueto, dal campione dati Envision. Prima di continuare il nostro tutorial, che prevede un uso già avanzato di Envision, consigliamo, a chi non l'avesse già fatto, di leggere Eseguire calcoli e Aggregare i dati.
read "/sample/Lokad_Items.tsv"
read "/sample/Lokad_Orders.tsv" as O
read "/sample/Lokad_PurchaseOrders.tsv" as PO

show label "Time-series calculations" a1f1 tomato

end := max(date)
lastMon := monday(end)

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

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

Day.cashFlow := sum(O.NetAmount) - sum(PO.NetAmount)
Day.balance := avg(Day.cashFlow) over [-13 .. 0]
when date >= lastMon - 6 * 7 & date < lastMon
  show linechart "Cash flow over the last 6 weeks" a8f10 tomato unit:"$" with 
    Day.balance as "Balance"

PO.Price = PO.NetAmount / PO.Quantity
O.PurchasePrice = latest(PO.Price)
O.Cost = O.PurchasePrice * O.Quantity
O.Profit = O.NetAmount - O.Cost
Week.profitblty := sum(O.Profit) / sum(O.Cost) or 1
when date >= lastMon - 13 * 7 & date < lastMon
  show linechart "Profitability over the last 13 weeks" a11f13 tomato unit:"%" with 
    Week.profitblty
Eseguendo questo script con i dati campione, si ottiene un pannello di controllo come quello qui sotto.

Image

Tabelle calendario virtuali

Le aggregazioni giornaliere, settimanali e mensili sono imprescindibili per un'attività commerciale. Envision è stato pensato appositamente per supportare questi schemi periodici: più precisamente, abbiamo tre tabelle virtuali, Day (giorno), Week (settimana) e Month (mese). Chiamiamo le tabelle “virtuali” perché non hanno un file tabellare corrispondente, o, in altre parole, esistono solo per la durata dell'esecuzione dello script. Nello script sopra sfruttiamo le tabelle virtuali per visualizzare i grafici. Vediamo come, con l'aiuto delle righe di script corrispondenti.
Week.sold := sum(O.NetAmount)
end := max(date)
when date >= end - 52 * 7
  show linechart "Weekly sales" a2f4 tomato unit:"$" with 
    Week.sold as "This year"
    Week.sold[-52] as "Last year"
Alla riga 1, il contenuto della tabella O (ordini) viene sommato all'interno della tabella Week. Poiché usiamo un assegnamento scalare :=, calcoliamo un singolo valore per settimana. Alla riga 3, definiamo un filtro per escludere i dati che risalgono a più di 52 settimane fa. Tra le righe 5 e 6, infine, due serie temporali sono mostrate nel grafico. La seconda delle serie temporali Week.sold[-52] prevede un operatore lag, che prenderemo in esame più avanti. Questo script può essere facilmente modificato per ottenere un'aggregazione giornaliera o mensile. Possiamo ad esempio aggiungere queste righe alla fine dello script:
Day.sold := sum(O.NetAmount)
show linechart "Daily sales" a14f16 tomato unit:"$" with
  Day.sold
Month.sold := sum(O.NetAmount)
show linechart "Monthly sales" a17f19 tomato unit:"$" with
  Month.sold
Questo blocco di codice mostrerà altri due grafici, uno con le serie temporali su base giornaliera e l'altro con le serie temporali su base mensile. La variabile Day.sold (giorno, venduto) può essere vista come il contenuto della colonna sold (venduto) all'interno della tabella Day, oppure come una serie temporale a intervalli regolari di 1 giorno (in contrasto con le tabelle “normali” di Envision che contengono una colonna Date, data - come la tabella O - che possono essere interpretate come serie temporali a intervalli irregolari).

Serie temporali con lag

Nell'analisi delle serie temporali, il termine “lag” (o ritardo) indica l'operazione che consiste nello spostare nel tempo gli elementi di una serie temporale. Uno degli obiettivi più elementari di questo procedimento è quello di confrontare due periodi di tempo distinti. Envision supporta un operatore lag pensato appositamente per gestire situazioni di questo tipo. Rivediamo lo snippet precedente:
Week.sold := sum(O.NetAmount)
when date >= end - 52 * 7
  show linechart "Weekly sales" a2f4 tomato unit:"$" with 
    Week.sold as "This year"
    Week.sold[-52] as "Last year"
Alle righe 4 e 5 abbiamo due serie temporali: la prima (Week.sold) rappresenta le vendite aggregate a livello settimanale originali, mentre la seconda ha un suffisso aggiuntivo ([-52]). Questo suffisso è l'operatore lag. In questo caso, indica che i dati risalenti a più di 52 settimane fa vengono spostati in avanti e mostrati nel grafico. Quando applichiamo l'operatore lag alla tabella Week, l'argomento lag corrisponde a un numero intero di settimane. Per le tabelle Day e Month, l'argomento lag corrisponde rispettivamente a un numero di giorni o mesi.

L'operatore lag beneficia della cooperazione intelligente dell'operatore filtro when (quando). Senza questa cooperazione, il filtro when avrebbe già escluso i dati risalenti a più di 52 settimane fa e un lag di 52 settimane non avrebbe potuto far altro che mostrare degli zeri. Come vediamo nel pannello di controllo sopra, l'operatore lag sposta in avanti i dati reali dell'anno prima (e non degli zeri). Tutto questo è possibile grazie alla cooperazione tra operatore lag e filtro when.

Passando il puntatore del mouse sul grafico nel pannello di controllo, saranno visualizzati date e valori. In particolare, con lo script all'inizio di questa pagina, le date riportate per la serie temporale “Last year” (anno scorso) si differenziano di un anno rispetto a quelle della serie temporale “This year” (anno in corso). È anche importante notare che l'operatore lag non mantiene le date originali della serie temporale. A questo proposito, Envision usa un semplice “trucco”: se l'operatore lag è definito all'interno di una dichiarazione di grafico, allora, e solo allora, le date originali vengono mantenute.

Modifichiamo ora lo script per applicare l'operatore lag all'esterno del grafico, introducendo la variabile Week.lastYear (settimana, anno scorso).
Week.sold := sum(O.NetAmount)
Week.lastYear := Week.sold[-52]
when date >= end - 52 * 7
  show linechart "Weekly sales" a2f4 tomato unit:"$" with 
    Week.sold as "This year"
    Week.lastYear as "Last year"// problemi di visualizzazione delle date
Una volta eseguito questo script, spostando il cursore del mouse sui punti della serie temporale, si vedrà che entrambe le serie temporali riportano le stesse date. In questo caso, la semantica delle date da visualizzare è ambigua. Ad esempio, la serie Week.lastYear potrebbe essere interpretata come una previsione anno per anno e, in tal caso, visualizzare le stesse date per le stesse serie sarebbe l'opzione perfetta. Insomma, se vogliamo preservare le date originali nel grafico per un confronto tra serie temporali con lag temporale, allora dobbiamo definire l'operatore lag all'interno dell'istruzione show.

Aggregare i dati per un periodo specifico

In una delle nostre guide precedenti, abbiamo spiegato come aggregare i dati con Envision. Con le serie temporali, abbiamo la possibilità di effettuare un altro tipo di aggregazione, che può rivelarsi molto utile: l'aggregazione per un periodo di tempo specifico. Lo script all'inizio della pagina mostra come calcolare una media mobile delle vendite in 4 settimane. Riproponiamo qui sotto le righe di script corrispondenti:
Week.ma := sum(O.NetAmount / 4) over [-3 .. 0]
when date >= end - 52 * 7
  show linechart "Weekly sales with 4 weeks moving average" a5f7 tomato unit:"$" with 
    Week.ma as "This year"
    Week.ma[-52] as "Last year"
Alla riga 1, viene eseguita un'aggregazione con l'aggregatore sum(), a cui viene aggiunta alla fine un'istruzione introdotta dalla parola chiave over. Alla riga 2, i dati sono filtrati mantenendo le ultime 52 settimane di dati. Infine, alle righe 3, 4 e 5, all'interno del grafico sono mostrate due serie temporali. Entrambe le serie sono “livellate”, poiché sono considerate entrambe di 4 settimane in media.

L'operatore over è usato per definire il periodo di tempo applicabile e deve essere scritto come [a .. b], dove a e b sono numeri interi e a è minore o uguale di b. L'unità usata per a e b dipende dall'espressione a sinistra dell'assegnamento. In questo caso, a sinistra dell'assegnamento abbiamo la tabella Week, quindi -3 e 0 sono espressi in settimane.

Suggerimento: le settimane comprese tra -3 e 0 sono 4, non 3. Abbiamo quindi le settimane -3, -2, -1 e 0.
L'opzione over può essere usata con tutti gli aggregatori. Quando usiamo questa opzione, a sinistra dell'assegnamento abbiamo di solito una tabella Day, Week o Month, ma possiamo usare anche una qualsiasi altra tabella che contenga date. Tra l'altro, per definizione, over [0 .. 0] usato con una tabella calendario dà lo stesso risultato dell'aggregazione predefinita:
Week.sold := sum(O.NetAmount)
// stesso risultato!
Week.same := sum(O.NetAmount) over [0 .. 0] 

Aggregazioni complesse di serie temporali

La sintassi di Envision offre la possibilità di sfruttare le serie temporali per eseguire calcoli anche più elaborati di questi. Ad esempio, è possibile calcolare le serie temporali e, sulla base di queste, eseguire calcoli ulteriori. Un esempio è quello del terzo blocco di codice nello script in alto. Riprendiamo le righe di script qui sotto:
Day.cashFlow := sum(O.NetAmount) - sum(PO.NetAmount)
Day.balance := avg(Day.cashFlow) over [-13 .. 0]
when date >= monday(end) - 42 & date < monday(end)
  show linechart "Cash flow over the last 6 weeks" a8f10 tomato unit:"$" with 
    Day.balance as "Balance"
Alla riga 1, la serie temporale Day.cashFlow (giorno, entrate) è definita come la differenza tra le vendite totali e gli acquisti totali. Alla riga 2, la serie temporale Day.balance (giorno, saldo) è calcolata come media mobile su 14 giorni di Day.cashFlow. Alla riga 3, viene definito un filtro con due specifiche condizioni: i dati non devono risalire a più di 7 settimane fa (a partire dall'ultimo lunedì) e non devono essere più recenti dell'ultimo lunedì. Per essere sicuri che le settimane incluse nel calcolo siano esattamente 6 settimane intere, usiamo la funzione monday(). Infine, attraverso le righe 4 e 5, facciamo in modo che il grafico mostri solo la serie temporale Day.balance.

L'aggregazione alla riga 2 sfrutta l'opzione over che abbiamo visto nella sezione precedente. Qui, la serie temporale contiene i valori medi per un periodo di 14 giorni (infatti abbiamo 14 diversi valori dall'indice -13 all'indice 0). Questa aggregazione è leggermente diversa da quelle che abbiamo visto finora, perché la tabella Day appare sia a sinistra che a destra dell'assegnamento, mentre le aggregazioni più comuni, che non hanno un suffisso over, aggregano i dati da una tabella all'altra.

Possiamo anche riaggregare le serie temporali giornaliere in serie temporali settimanali. Lo script qui sotto illustra appunto come calcolare un flusso di cassa settimanale anziché giornaliero.
Day.cashFlow := sum(O.NetAmount) - sum(PO.NetAmount)
Week.balance := sum(Day.cashFlow / 2) over [-1 .. 0]
when date >= monday(end) - 42 & date < monday(end)
  show linechart "Cash flow over the last 6 weeks" a8f10 tomato unit:"$" with 
    Week.balance as "Balance"

Gestire dati associati a eventi

I dati associati a eventi sono dati storici focalizzati su “cambiamenti”. Facciamo un esempio: invece che registrare tutti i prezzi storici per ogni singolo giorno compreso nei dati storici, è molto più pratico raccogliere in una lista solo i cambiamenti di prezzo, annotando il nuovo prezzo e la data in cui è stato cambiato, e dando per scontato che il nuovo prezzo rimanga in vigore finché non viene indicato un altro prezzo. A questo proposito, Envision è in grado di gestire situazioni in cui i dati storici sono rappresentati da una lista di cambiamenti.

Nel nostro set di dati campione non abbiamo una tabella di dati storici sui prezzi. Possiamo, però, desumerla dalle transazioni di acquisto avvenute in passato, dando per scontato che i prezzi non abbiano subito variazioni fino alla transazione successiva. Questa è l'ipotesi che abbiamo fatto anche nel caso dello script in alto, nell'ultimo blocco in particolare. Riprendiamo le righe corrispondenti:
PO.Price = PO.NetAmount / PO.Quantity
O.PurchasePrice = latest(PO.Price)
O.Cost = O.PurchasePrice * O.Quantity
O.Profit = O.NetAmount - O.Cost
Week.profitblty := sum(O.Profit) / sum(O.Cost) or 1
when date >= lastMon - 13 * 7 & date < lastMon
  show linechart "Profitability over the last 13 weeks" a11f13 tomato unit:"%" with 
    Week.profitblty
Alla riga 1, il prezzo unitario di acquisto è calcolato per ogni riga della tabella PO (ordini di acquisto) attraverso una delle normali operazioni in vettori di Envision. Alla riga 2, è usata la funzione latest: per ogni riga della tabella O (la tabella a cui è stata associata), la funzione cerca la riga PO.Price (ordini di acquisto, prezzo) più recente tra quelle disponibili (non più recente della riga di O considerata) e ne copia il valore alla sinistra dell'assegnamento. Alle righe 3 e 4, abbiamo ulteriori calcoli con vettori e aggregazioni. Infine, alla riga 5, definiamo un filtro per restringere le operazioni alle ultime 13 settimane intere: all'interno di questo blocco, mostriamo un grafico con la serie temporale Week.profitability (settimana, redditività) calcolata in precedenza attraverso la riga 4.

La funzione latest è l'elemento specifico che supporta questo calcolo. La funzione ha lo scopo preciso di catturare la semantica di un flusso di eventi, dove un valore è considerato costante finché non è modificato dal sopraggiungere di un nuovo evento. In particolare, è anche possibile rendere più “densi” i prezzi di acquisto, calcolando un singolo prezzo per ogni giorno compreso nei dati storici:
Day.PurchasePrice = latest(PO.Price)
La funzione latest può essere quindi usata per gestire diverse situazioni, dalle rotture di stock alle promozioni, passando per il ciclo di vita del prodotto e molto altro.