Filtrare i dati con Envision - Software di ottimizzazione delle scorte

Filtrare i dati con Envision












Home » Risorse » Qui

Filtrare i dati è un'operazione fondamentale per le analisi logistiche. Envision offre tutto il supporto necessario per farlo: è possibile filtrare sia grandi blocchi di script che istruzioni singole, con l'aiuto delle condizioni where (dove) e when (quando). In questa sezione spieghiamo come fare.


Lo script: un esempio

Come al solito, partiamo dal nostro campione dati, che dovrebbe essere accessibile dal percorso /sample nell'account Lokad. Lo script qui sotto è mediamente complesso e illustra alcune delle opzioni di filtro disponibili in Envision. A chi non l'avesse già fatto, consigliamo di leggere la sezione Eseguire calcoli con Envision prima di passare a questo tutorial.
read "/sample/Lokad_Items.tsv"
read "/sample/Lokad_Orders.tsv" as O

show label "Filtering data" a1f1 tomato

oend := max(O.Date)

when date > oend - 365
  LastYearQty = sum(O.Quantity)
  where StockOnHand + StockOnOrder > LastYearQty
    show table "Overstocked items, +1 year of stock" a2f3 tomato with 
      Id
      Name
      StockOnHand + StockOnOrder as "Stock"
      LastYearQty

  where O.NetAmount > 1000
    show table "Large transactions over $1000" a4f5 tomato with 
      Id
      Name 
      O.Date 
      O.Quantity
      O.NetAmount
      O.Client

lastDay := monday(oend)
firstDay := lastDay - 52 * 7
when date >= firstDay & date < lastDay
  Week.sold := sum(O.NetAmount)
  show linechart "Sold by week" a6f7 tomato unit:"$" with
    Week.sold
Copiando ed eseguendo lo script qui sopra nell'account Lokad, è possibile visualizzare un pannello di controllo come quello qui sotto.

Image

Filtrare blocchi di script

Quando si comincia a lavorare su dati storici relativamente recenti, non più vecchi di un anno, i calcoli da eseguire sono spesso molto numerosi. In questi casi, il filtro più adatto da applicare a tutti i calcoli è solo scorso anno. Per questi motivi, con Envision i dati vengono filtrati soprattutto per blocchi di script. Il frammento di script qui sotto illustra due blocchi filtrati annidati: il primo blocco inizia alla riga 1 con la condizione when, il secondo blocco inizia alla riga 3 con una condizione where.
when date > oend - 365
  LastYearQty = sum(O.Quantity)
  where StockOnHand + StockOnOrder > LastYearQty
    show table "Overstocked items, +1 year of stock" a2f3 tomato with
      Id
      Name
      StockOnHand + StockOnOrder as "Stock"
      LastYearQty
Envision è un linguaggio sensibile agli spazi bianchi lasciati all'inizio di ogni riga. Chiamiamo blocco di codice una sequenza di righe che iniziano con lo stesso numero di spazi. I blocchi possono anche essere annidati, ossia trovarsi l'uno all'interno dell'altro.

Un meccanismo simile può essere disorientante per chi lo utilizza per la prima volta. Non c'è nulla di cui preoccuparsi, però, perché l'editor di script Envision offre tutto il supporto necessario: cliccando su “Invio” alla fine di una riga di script che contiene una condizione filtro, la riga successiva conterrà già un rientro automatico di due spazi.

Nota dello sviluppatore: Envision adotta un sistema di spaziatura simile a quello di Python, che ben si adatta a una sintassi concisa per la gestione dei dati, come appunto quella di Envision. Data l'assenza di loop e salti, il rientro raramente si spinge oltre i 3 livelli, rendendo la spaziatura molto semplice da gestire.

Un blocco filtrato inizia con una condizione (che vedremo nel dettaglio più avanti) che può essere vera o falsa per ogni riga di ogni tabella. All'interno del blocco sono conservate soltanto le righe per cui la condizione è vera, mentre tutte le altre vengono filtrate. Come suggerisce il nome, la condizione when implica un filtro temporale e può essere applicata a tutte le tabelle indicizzate con una colonna Date (data). La condizione where, invece, è usata solitamente per filtrare gli articoli ed è applicabile a tutte le tabelle indicizzate con una colonna Id (identificativo dell'articolo).

Una volta iniziato il blocco di codice, vengono conservate solo le righe della tabella che soddisfano la condizione posta dal filtro. Tutto il resto rimane uguale. Rimane anche la possibilità di spostare all'interno del blocco qualsiasi elemento scritto all'esterno del blocco. Più precisamente, è possibile definire un altro blocco filtrato all'interno del primo blocco, secondo la tecnica dell'annidamento (nesting in inglese). Nell'esempio sopra, il blocco where, che inizia alla riga 3, è annidato all'interno del blocco when, che inizia alla riga 1.

Creare una condizione

Una condizione è un'espressione che può essere valutata come vera o falsa. Per filtrare i dati bisogna prima di tutto creare delle condizioni da valutare con i dati di input ed eventualmente con i risultati dei calcoli intermedi. Envision offre la possibilità di creare condizioni complesse, come illustra lo script qui sotto:
lastDay := monday(oend)
firstDay := lastDay - 52 * 7
when date >= firstDay & date < lastDay
  Week.sold := sum(O.NetAmount)
  show linechart "Sold by week" a6f7 tomato unit:"$" with
    Week.sold
In questo snippet, l'operatore and (e) indica che entrambe le espressioni, sia quella a sinistra che quella a destra dell'operatore, devono essere vere. Envision supporta gli operatori logici and, or (o) e not (non), oltre agli operatori numerici == (uguaglianza), != (diseguaglianza), <= (minore o uguale), >= (maggiore o uguale), < (minore), > (maggiore). Nello snippet qui sotto vediamo come gli operatori possono essere combinati e valutati.
a := 1 > 10 // falso
b := not a // vero
c := a | b // vero
d := a & b // falso
e := 10 >= 3 | 5 > 7 // vero
show table "Conditions" with a, b, c, d, e
Quando un blocco filtrato è annidato all'interno di un altro blocco, è come se le due condizioni fossero una a destra e l'altra a sinistra dell'operatore and.

Nell'esempio sopra, è da notare la sintassi monday(oend) (lunedì (fine)): si tratta di una chiamata della funzione monday (lunedì). Per ogni data, questa funzione torna all'ultimo lunedì prima della data indicata come argomento (inclusa). Quindi, se come argomento abbiamo un lunedì, la funzione rimanderà alla stessa data.

La funzione monday() è usata per definire una finestra temporale composta da settimane intere, invece che parziali. Infatti, quando aggreghiamo i dati su base settimanale, dobbiamo considerare solo le settimane intere, altrimenti corriamo il rischio che la prima e l'ultima data corrispondano a valori estremamente bassi, considerando che riflettono una settimana parziale.

Filtrare le tabelle per data

Envision offre il supporto necessario per filtrare le tabelle in base a condizioni temporali. Lo script precedente filtra tutte le righe che risalgono a più di 1 anno prima. Vediamo meglio la parte di script corrispondente.
oend := max(O.Date)
when date > oend - 365
  LastYearQty = sum(O.Quantity)
Envision tratta le date come un numero intero di giorni a partire dal 1° gennaio 2001. Questo meccanismo offre numerosi vantaggi, tra cui la possibilità di eseguire operazioni aritmetiche con le date: ad esempio, se sottraiamo 7 da una data avremo la settimana precedente, o se sottraiamo 365 avremo una data (approssimativa) dell'anno precedente.

La parola chiave date ha un comportamento particolare: implicitamente, la variabile date si riferisce a tutte le tabelle che contengono una colonna Date. Poiché in questo caso abbiamo una sola tabella (O, la tabella degli ordini), è come se avessimo scritto O.Date > oend - 365. La sintassi date non solo è più concisa, ma considera tutte le tabelle insieme. Se, invece di cercare le quantità vendute, avessimo voluto visualizzare le quantità acquistate, allora avremmo dovuto scrivere:
// caricamento degli ordini di acquisto
read "/sample/Lokad_PurchaseOrders.tsv" as PO

// ...

when date > oend - 365
  LastYearQty = sum(PO.Quantity)

Filtrare gli articoli

Oltre che filtrare le righe per data, Envision offre anche la possibilità di filtrare gli articoli, rispondendo così all'esigenza di escludere prodotti, posizioni, categorie, etc. Lo script all'inizio di questa pagina illustra come restringere il campo agli articoli che possiamo definire come stock morto. Ripetiamo qui sotto le righe di script corrispondenti.
  where StockOnHand + StockOnOrder > LastYearQty
    show table "Overstocked items, +1 year of stock" a2f3 tomato with
      Id
      Name
      StockOnHand + StockOnOrder as "Stock"
      LastYearQty
La parola chiave where è seguita da una condizione, applicata a tre vettori che appartengono, implicitamente, alla tabella Items (articoli): questa è infatti l'unica tabella per cui possiamo evitare di inserire un prefisso con il nome della tabella nel nome della variabile (quando ci riferiamo alle righe della tabella degli ordini O, infatti, dobbiamo usare il nome O.NetAmount, non possiamo limitarci a un semplice NetAmount). La condizione nello script qui sopra è quella di includere solo gli articoli per cui la somma delle scorte disponibili e delle scorte già ordinate è superiore al numero di unità vendute nell'ultimo anno.

Una volta definita una condizione per la tabella Items, ogni tabella che include una colonna Id (identificativo dell'articolo, secondo le convenzioni di Envision) viene filtrata allo stesso modo, secondo un procedimento identico a quello che abbiamo visto precedentemente per il filtro temporale. Non appena inizieremo a filtrare gli articoli, infatti, non avrà molto senso visualizzare tutti gli ordini di acquisto o di vendita, compresi quelli corrispondenti agli articoli scartati con il filtro.

Filtrare gli articoli significa anche modificare il campo d'azione dei vettori appena calcolati. Vediamo perché, con l'aiuto dello script qui sotto.
where StockOnHand > 5
  GreaterThanFive = "yes"
  show table "Hello" 
  with Name
  GreaterThanFive // GIUSTO!

// il blocco filtro termina qui
show table "Hello" 
  with Name
  GreaterThanFive // SBAGLIATO!
La riga 10 non è corretta, poiché il vettore GreaterThanFive (maggiore di cinque) è stato definito solo per le righe per cui è vera la condizione StockOnHand > 5 (scorte disponibili superiori a 5). Così, se il vettore è definito correttamente all'interno del blocco (e quindi può essere usato come nella riga 5), lo stesso vettore non può essere usato al di fuori del blocco filtrato, perché altrimenti alcuni valori non verrebbero definiti. Possiamo ovviare al problema assicurandoci che il vettore sia definito correttamente per tutti i valori degli articoli, come nell'esempio qui sotto.
GreaterThanFive = "no"

where StockOnHand > 5
  GreaterThanFive = "yes"
  show table "Hello" with
    Name
    GreaterThanFive // GIUSTO!

// il blocco filtro termina qui
show table "Hello" with
  Name
  GreaterThanFive // GIUSTO!
Questo snippet inizia, alla riga 1, con una definizione corretta del vettore GreaterThanFive per tutti gli articoli. La definizione viene modificata alla riga 4 per un sottoinsieme di articoli. Questa modifica, tuttavia, non cambia il fatto che il vettore GreaterThanFive sia definito esplicitamente per tutti gli articoli. Di conseguenza, il contenuto della riga 12 è ora corretto.

Filtrare una tabella specifica

Filtrare i dati per articolo o per data è molto utile. A volte, però, capita di dover filtrare una specifica tabella. Con Envision, possiamo filtrare una tabella con la parola chiave where, come nelle righe di script qui sotto.
  where O.NetAmount > 1000
    show table "Large transactions over $1000" a4f5 tomato with
      Id
      Name
      O.Date
      O.Quantity
      O.NetAmount
      O.Client
Nel nostro caso, vogliamo filtrare la tabella O per escludere tutte le righe con un valore inferiore a $1000. Le righe non filtrate sono mostrate attraverso show table alle righe 2 e 3. L'esempio mostra come filtrare un'unica tabella, scremando i risultati della tabella O, ma lasciando invariate tutte le altre tabelle.

Se un vettore associato alla tabella O viene calcolato all'interno di un blocco filtrato, l'accesso a questo vettore sarà ristretto al blocco. Abbiamo già visto un comportamento simile nel filtro per articolo, vediamo ora come applicare il meccanismo anche al filtro per tabella.
where O.NetAmount > 1000
  O.LargeTxn = "yes"
  show table "Large transactions" 
  with Name
  O.LargeTxn // GIUSTO!
// ultima riga del blocco
// rientro diminuito, il blocco è terminato
show table "Large transactions" 
  with Name
  O.LargeTxn // SBAGLIATO!
Poiché il vettore O.LargeTxn (ordini, superiore a 1000) non è definito per tutte le righe della tabella O, solo la riga 5 risulta corretta, mentre la riga 10 non lo è. Come nell'esempio precedente, possiamo risolvere il problema definendo correttamente un valore LargeTxn per l'intera tabella O, come nello script qui sotto.
O.LargeTxn = "no"
where O.NetAmount > 1000
  O.LargeTxn = "yes"
  show table "Large transactions"
  with Name
  O.LargeTxn // GIUSTO!
// ultima riga del blocco
// rientro diminuito, il blocco è terminato
show table "Large transactions"
  with Name
  O.LargeTxn // GIUSTO!
In linea di massima, Envision cerca di rendere i blocchi il più fluidi possibile: un vettore calcolato all'interno di un blocco può essere usato all'esterno del blocco, a condizione di non violare la regola per cui tutti i valori del vettore devono essere definiti esplicitamente quando il vettore appare a destra di un assegnamento.

Filtrare con gli zuccheri sintattici

Lo schema sintattico usato da Envision per filtrare i dati, basato sul rientro, è conciso e leggibile. Tuttavia, quando sono usati più filtri, il rientro diventa un parametro difficile da seguire. Envision offre allora degli zuccheri sintattici, ossia una sintassi alternativa che richiede un rientro minore. In questa sezione ne analizzeremo in dettaglio il funzionamento.

Rientri e filtri multipli

Envision richiede un solo livello supplementare di rientro per filtro, a patto però che i filtri siano usati separatamente. Se ci interessa solo l'ultimo filtro, allora possiamo usare un solo livello di rientro, come si vede nell'esempio seguente:
// ogni filtro "where" 
// ha il proprio rientro
where O.Quantity > 10
  where StockOnHand < 100
    show table "Filtered orders" with
     O.Quantity

// se sono usati più filtri, 
// è necessario un solo rientro
where O.Quantity > 10
where StockOnHand < 100 // non c'è rientro!
  show table "Filtered orders" with O.Quantity
Il secondo blocco segue la stessa semantica del primo, ma richiede un solo rientro. Più in generale, la sintassi è la seguente:
where A
  when B
    where C
      show table "Filtered by A, B and C" with X
// uguale a
where A
when B
where C
  show table "Filtered by A, B and C" with X

Unire i filtri con la parola chiave and

Abbiamo già visto come, in Envision, l'operatore booleano AND è rappresentato dal simbolo &. Envision offre però anche una parola chiave and, a cui corrisponde una semantica leggermente diversa:
// i due filtri annidati "where"
where O.NetAmount > 1000 
  where StockOnHand > 10
    show table "Filtered transactions" with
      Name
      O.Quantity

// possono essere riscritti come filtro singolo con la parola chiave "and"
where O.NetAmount > 1000 and StockOnHand > 10
  show table "Filtered transactions" with 
    Name
    O.Quantity
La parola chiave and equivale all'annidare i due filtri where: può essere quindi utilizzata per introdurre più filtri in sequenza, con un solo livello di rientro. Più in generale, avremo:
where A
  where B
   where C
     // ...

// può essere riscritto come
where A and B and C
  // ...
Insomma, la parola chiave and offre la possibilità di unire più filtri che vogliamo utilizzare l'uno in aggiunta all'altro.

Evitare il rientro con la parola chiave keep

Uno schema frequente consiste nell'introdurre i filtri all'inizio dello script per restringere l'analisi dei dati a un campo specifico. La sintassi filtro di Envision gestisce in modo adeguato questi scenari, ma tutto lo script Envision finisce poi con uno o due livelli di rientro. A questo proposito, la parola chiave keep (mantieni) permette di rimuovere tutti i rientri:
// il filtro "where" introduce un blocco rientrato
where O.Quantity > 10
  // inizio del blocco
  show table "Inside the filter" with
    sum(O.Quantity)
  // fine del blocco
  show table "Outside the filter" with
   sum(O.Quantity)

// se invece usiamo "keep", 
// il filtro è applicato senza rientro
keep where O.Quantity > 10
show table "Inside the filter" with
  sum(O.Quantity)
La parola chiave keep deve essere posta prima dei filtri where o when, a indicare che il filtro non presenta un rientro. Il filtro resta attivo per tutto il blocco.
where A
  keep where B
  show table "Filtered by A and B" with X
  // fine dei filtri A e B
show table "Not filtered" with X
Quindi, se keep viene posto in una riga di script senza rientro, il filtro viene applicato fino alla fine dello script.

Filtrare con i suffissi interni alla riga

Finora abbiamo visto come le condizioni fossero scritte in blocchi. Envision offre anche un'altra sintassi, più compatta: i suffissi di condizione. Torniamo al calcolo delle quantità vendute nell'ultimo anno.
when date > oend - 365
  LastYearQty = sum(O.Quantity)
Lo script può essere riscritto in questo modo:
LastYearQty = sum(O.Quantity) when date > oend - 365
A chi ha familiarità con i database relazionali questa sintassi sembrerà forse simile alle condizioni where di SQL. All'interno di Envision, questa sintassi è prima di tutto uno zucchero sintattico che evita di introdurre blocchi di una sola riga quando l'istruzione da inserire nel blocco è una sola: sia la condizione where che la condizione when, infatti, possono essere "ridotte a un suffisso" da aggiungere all'estrema destra dell'assegnamento.

Gli aggregatori possono essere filtrati all'interno della riga con le condizioni when o where. Envision offre inoltre la possibilità di aggiungere un modificatore else (altro da) alla condizione. Ad esempio, non è corretto scrivere:
oend := max(O.Date)
lastDay := monday(oend)
Week.sold := sum(O.NetAmount) when date < lastDay
show linechart "Sold by week" with
  Week.sold // SBAGLIATO
perché Week.sold è stato filtrato alla riga precedente e non è quindi definito interamente. Aggiungendo però l'opzione else, definiamo correttamente Week.sold in tutto lo script:
oend := max(O.Date)
lastDay := monday(oend)
Week.sold := sum(O.NetAmount) when date < lastDay else 0
show linechart "Sold by week" with
  Week.sold // GIUSTO