Cálculos de series de tiempo con Envision

Cálculos de series de tiempo











Inicio » Recursos » Aquí

La mayoría de las operaciones de negocios de cualquier comercio pueden representarse con precisión con las series de tiempo: ventas históricas, pedidos de compra históricos, precios históricos, etc. Debido a que trabajar con series de tiempo es de gran importancia para las empresas, Envision proporciona respaldo nativo extensivo para este tipo de escenarios. En particular, permite agregar datos por día, por semana y por mes. Además, Envision respalda análisis de series de tiempo más complejos, como datos atrasados o cálculos de medias móviles. Estas características se ilustran y documentan en esta página.

Un ejemplo ilustrativo

Una vez más, utilizamos nuestro conjunto de datos de ejemplo para ilustrar las capacidades de Envision. Debido a que esta página presenta un uso más avanzado de Envision, le sugerimos que lea Haciendo cálculos con Envision y Agregación de datos antes de proceder con esta página.
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
Después de ejecutar este script con el conjunto de datos de ejemplo, se obtiene el siguiente panel de información.

Image

Tablas de calendario virtuales

Debido a que las agregaciones diarias, semanales y mensuales se encuentran por todas partes, Envision ha sido diseñado para respaldar en forma nativa estos patrones de calendario periódicos. Más específicamente, Envision se beneficia de tres tablas virtuales llamadas Day, Week y Month respectivamente. Estas tablas se denominan "virtuales" porque no tienen archivos tabulados como contrapartida, o, en otras palabras, solo existen durante el tiempo de ejecución del script. El script anterior aprovecha esas tablas virtuales para mostrar los gráficos de líneas. Veamos las líneas relevantes copiadas nuevamente a continuación.
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"
En la línea 1, el contenido de la tabla Orders se suma en la tabla Week. Debido a que estamos utilizando la asignación escalar :=, se calcula un solo valor por semana. En la línea 2, se define un filtro para excluir datos que tengan más de 52 semanas de antigüedad. Por último, en las líneas de la 3 a la 5, se define un gráfico de líneas con dos series de tiempo. La segunda serie de tiempo Week.sold[-52] viene con un operador de retardo que veremos en la próxima sección. Este script se puede modificar fácilmente para una agregación diaria o mensual. Por ejemplo, es posible agregar las siguientes líneas al final del 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
Este bloque de código muestra otros dos gráficos de líneas que presentan series de tiempo agregadas a nivel diario y mensual respectivamente. La variable Day.sold puede interpretarse como el contenido de la columna llamada sold dentro de la tabla Day, pero también puede interpretarse como una serie de tiempo espaciada igualmente del período "1 día", en oposición a las tablas de Envision normales, que contienen una columna Date, como Orders, y que pueden interpretarse como series de tiempo irregularmente espaciada.

Visualización de series de tiempo retrasadas

En el análisis de series de tiempo, "retraso" hace referencia a la operación que consiste en mover temporalmente los elementos de una serie de tiempo. Uno de los objetivos principales del retraso de series de tiempo consiste en comparar dos períodos de tiempo distintos. Envision es compatible con un operador de retraso especialmente diseñado para gestionar tales situaciones. Volvamos a ver el fragmento de código anterior.
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"
En las líneas 4 5, se definen tenemos dos series de tiempo. La primera serie de tiempo Week.sold corresponde a las ventas originales agregadas semanalmente. La segunda serie viene con un sufijo adicional [-52]. Este sufijo es el operador de retraso. Significa que los datos que se remontan a 52 semanas atrás se adelantan y finalmente se muestran en el gráfico de líneas. Cuando se aplica el operador de retraso a la tabla Week, el argumento de retraso es un entero expresado en unidades semanales. De modo similar, las unidades aplicables son días y meses para las tablas Day y Month respectivamente.

El operador de retraso se beneficia del comportamiento cooperativo inteligente del operador de filtro when. De hecho, sin este comportamiento cooperativo, el filtro when ya hubiera excluido todos los datos de más de 52 semanas de antigüedad y, como consecuencia, el retraso de las series de tiempo de 52 semanas hubiera tenido como resultado el movimiento de ceros hacia delante. Aún así, como se ilustra en las capturas de pantalla del panel de información anterior, el operador de retraso está moviendo correctamente los datos reales de un año de antigüedad, y no solo los ceros. Este comportamiento se obtiene a través de la cooperación integrada entre el filtro when y el operador de retraso.

Si pasa el ratón sobre el gráfico de líneas dentro de su panel de información, notará que se muestran las fechas y los valores. En particular, con el script en la parte superior de esta página, las fechas reportadas para la serie de tiempo llamada "Last year" tienen de hecho un año menos con respecto a las fechas de la serie de tiempo llamada "This year". Sin embargo, es importante destacar que el operador de retraso en realidad no preserva las fechas originales de la serie de tiempo. En cambio, Envision utiliza simplemente una convención para que esto funcione: si el operador de retraso está definido dentro de la declaración de un gráfico de líneas, entonces, y solo entonces, se preservan las fechas originales.

Modifiquemos el script para aplicar el operador de retraso fuera del gráfico de líneas. Esto puede realizarse introduciendo una variable llamada 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" // problema de visualización de fecha
Si ejecuta este script modificado, y si pasa el ratón sobre los puntos de la serie de tiempo, debería notar que ambas series de tiempo se reportan con las mismas fechas. De hecho, en este caso, no hay semántica inequívoca para las fechas por mostrar. Por ejemplo, la serie de tiempo Week.lastYear podría haberse interpretado como un pronóstico año por año y, como resultado, tener las mismas fechas para las dos series de tiempo hubiera sido lo que estábamos buscando en este caso. En conclusión, si desea preservar las fechas originales en su gráfico de líneas para una comparación de las series de tiempo retrasadas una al lado de la otra, el operador de retraso debería definirse dentro de la instrucción show. Si ejecuta este script modificado, y si pasa el ratón sobre los puntos de la serie de tiempo, debería notar que ambas series de tiempo se reportan con las mismas fechas. De hecho, en este caso no existe una semática no ambigua para las fechas por mostrar. Por ejemplo, la serie de tiempo Week.lastYear podría haberse interpretado como un pronóstico año por año y, como resultado, tener las mismas fechas para las dos series de tiempo hubiera sido lo que estábamos buscando en este caso. En conclusión, si desea preservar las fechas originales en su gráfico de líneas para una comparación de las series de tiempo retrasadas una al lado de la otra, el operador de retraso debería definirse dentro de la instrucción show.

Agregación de datos a lo largo de una ventana de tiempo

En una de nuestras guías anteriores, hemos visto cómo agregar datos con Envision. Con las series de tiempo existe otro tipo de agregación que resulta muy deseable: la agregación a lo largo de una ventana de tiempo. El script al principio de esta página ilustra el modo de calcular una media móvil de las ventas a lo largo de 4 semanas. Las líneas de script pertinentes se copian nuevamente a continuación para que resulte más claro.
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"
En la línea 1, se lleva a cabo una agregación con el agregador sum(), y esta agregación viene con una instrucción que comienza con la palabra clave over al final. En la línea 2, los datos se filtran específicamente tomando las últimas 52 semanas de datos. Por último, entre las líneas 3 y 5, se muestran dos series de tiempo dentro del gráfico de líneas. Ambas series de tiempo están "suavizadas", ya que se ha hecho el promedio de las 4 semanas.

El operador over se utiliza para definir la ventana de tiempo aplicable, y se espera que se escriba [a .. b], donde a y b son enteros y a es menor o igual a b. La unidad utilizada para a y b depende de la expresión a la izquierda de la asignación. En este caso, tenemos la tabla Week a la izquierda de la asignación y, como resultado, -3 y 0 se expresan en semanas.

La solución de Lokad: entre la semana del índice -3 y la semana de índice 0, hay 4 semanas, no 3. Como resultado, tenemos las siguientes semanas de índices -3, -2, -1 y 0.
La opción over puede utilizarse con todos los agregadores. Cuando se utiliza esta opción, la parte izquierda de la asignación generalmente es una tabla de calendario virtual, como Day, Week y Month. Sin embargo, no es un requisito obligatorio y es posible utilizar cualquier tabla que esté indexada por una Date. Además, por definición, cuando se utilizan tablas de calendario, el uso de over [0 .. 0] da los mismos resultados que la agregación por defecto:
Week.sold := sum(Orders.NetAmount)
Week.same := sum(Orders.NetAmount) over [0 .. 0] // same result!

Agregaciones de series de tiempo más complejas

La sintaxis de Envision ofrece la posibilidad de realizar cálculos de serie de tiempo más complejos. Por ejemplo, es posible calcular series de tiempo y luego realizar más cálculos basados en esas series de tiempo iniciales. El tercer bloque de código del script al principio de esta página ilustra ese caso. Las líneas de script pertinentes se copian a continuación.
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"
En las líneas 1 y 2, las dos series de tiempo llamadas Day.cashIn y Day.cashOut se definen como los totales correspondientes de ventas y compras. En la línea 3, la serie de tiempo Day.balance se calcula como la media móvil a lo largo de 14 días de la diferencia cashIn menos cashOut. En la línea 4, se define un filtro con dos condiciones específicas: los datos no deberían tener más de 7 semanas de antigüedad contando a partir del último lunes, y los datos no deberían ser posteriores al último lunes. Aquí, el uso de la función monday() asegura que el alcance incluya exactamente 7 semanas completas. Por último, la serie de tiempo Day.balance se muestra como la única serie de tiempo del gráfico de líneas definido en las líneas 5 y 6.

La agregación que tiene lugar en la línea 3 aprovecha la opción over que hemos detallado en la sección anterior. Aquí, las dos series de tiempo se promedian a lo largo de una ventana de tiempo de 14 días; de hecho, desde el índice -13 hasta el índice 0 hay 14 índices distintos. Esta agregación es un tanto diferente de las agregaciones que hemos visto hasta ahora, porque la tabla Day aparece tanto a la izquierda como a la derecha de la asignación, mientras que las agregaciones más comunes que no tienen un sufijo over generalmente agregan datos de una tabla a otra.

Además, es posible volver a agregar las series de tiempo diarias en series de tiempo semanales. El script a continuación ilustra el modo en que se puede realizar para calcular un flujo de efectivo semanal en lugar de diario.
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"

Gestión de los datos impulsados por eventos

Los datos impulsados por eventos hacen referencia a una determinada representación de los datos históricos que se concentra en los "cambios". Por ejemplo, en lugar de tener todos los precios históricos para cada día del historial de datos, es mucho más práctico recopilar solo la lista de cambios de precio históricos: cada cambio de precio da un nuevo precio y una fecha, y se supone que cada nuevo precio se mantiene sin cambios hasta que se observa un nuevo cambio de precio. Envision respalda escenarios en los que los datos históricos se representan como una lista de cambios.

El conjunto de datos de ejemplo no incluye una tabla con precios históricos; no obstante, es posible aproximar estos datos observando las transacciones de compra pasadas, y suponiendo que los precios se mantienen sin cambios hasta que se observa la siguiente transacción. Esto es exactamente lo que sucede en el último bloque de script en el ejemplo al principio de la página. Observemos con más detenimiento las líneas pertinentes copiadas a continuación.
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
En la línea 1, el precio de compra por unidad se calcula para cada línea de la tabla PO a través de una operación vectorial en el estilo habitual de Envision. En la línea 2, se utiliza la función latest, y esta función tiene un comportamiento bastante específico: para cada línea de la tabla Orders —la tabla a la que se asigna—, la función latest busca la línea PO.Price más reciente disponible que no sea más reciente que la línea de Orders que se está considerando, y copia este valor en el lado izquierdo de la asignación. En las líneas 3 y 4, tenemos más cálculos con vectores y agregaciones. Por último, en la línea 5, se define un filtro para restringir el alcance de las operaciones a las últimas 13 semanas completas, y dentro de este bloque de filtro, se muestra un gráfico de líneas que aprovecha la serie de tiempo Week.profitability que se ha calculado anteriormente en la línea 4.

La función latest es el ingrediente específico que respalda el cálculo que se considera aquí. Esta función está pensada precisamente para capturar la semántica de una secuencia de eventos en la que se supone que un valor sea constante hasta que se ve sobrescrito por un nuevo evento. En particular, también es posible "densificar" los precios de compra, calculando un precio para cada día del historial con:
Day.PurchasePrice = latest(PO.Price)
En la práctica, la función latest se puede utilizar para gestionar muchas situaciones, como los desabastecimientos, las promociones, los ciclos de vida de producto. etc.