使用 Envision 过滤数据


首页 » 资源 » 此处

数据过滤对于商业分析不可或缺。Envision 广泛支持数据过滤。过滤由 wherewhen 条件提供支持,这两个条件可以用于同时过滤多个大脚本块,或用于过滤单独的语句。本页将提供有关这些概念的实用说明。


脚本示例

我们先从样本数据集开始,从您的 Lokad 账户中的 /sample 路径应当可以访问该数据集。下面这段脚本的复杂程度一般,它说明了 Envision 中一些可用的过滤模式。建议您先阅读通过 ENVISION 执行计算,然后再来浏览本页中的信息。
read "/sample/Lokad_Items.tsv"
read "/sample/Lokad_Orders.tsv" as Orders

show label "Filtering and Aggregating with Envision" a1f1 tomato

oend := max(Orders.Date)

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

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

when date >= monday(oend) - 52 * 7 & date < monday(oend)
Week.sold := sum(Orders.NetAmount)
show linechart "Sold by week{$}" a6f7 tomato with Week.sold
建议您先将这段脚本复制-粘贴到您的 Lokad 账户,然后运行该脚本以便观察生成的仪表板。如果一切正常,应当可以看到如下所示的仪表板。

Image

过滤块

举个例子,在开始处理所有不早于去年的历史数据时,很有可能会涉及到许多计算。在这样的情况下,应对所有此类计算应用仅去年过滤器。基于此,使用 Envision 执行的数据过滤大多通过代码块进行。下面的脚本片段说明了两个嵌套的过滤块。第一个块从第 1 行以 when 条件开始,第二个块从第 3 行以 where 条件开始。
when date > oend - 365
LastYearQty = sum(Orders.Quantity)
where StockOnHand + StockOnOrder > LastYearQty
show table "Overstocked items, +1 year of stock" a2f3 tomato with
Id
Name
StockOnHand + StockOnOrder as "Stock"
LastYearQty
Envision 这种语言对每行起始处的空格很敏感。我们将一系列以同样多的空格开始的行称为代码块;代码块可以嵌套,即一个块可以包含另一个块。

如果这是您头一回碰到这种编程模式,可能会让您有点犯迷糊。但在实践中,Envision 脚本编辑器提供了众多内置支持:在包含过滤器条件的代码行行末按Enter键时,下一行会自动缩进两个空格。

开发人员备注:Envision 采用的空格模式与 Python 中的非常相似。这种方法与旨在提供简明语法来处理数据的 Envision 非常契合。由于Envision 中没有循环和分支,因此缩进程度鲜少超过 3 级,所以在实践中便于管理这些空格。

过滤块以条件作为开始(下文将进行更详细的说明)-对于每个表的每一行,它可能为 true,也可能为 false。在过滤块中,只有条件为 true 的行才会存在,其他所有行将被过滤。顾名思义,when 条件涉及临时过滤,它应用于所有按 Date 列索引的表。where 条件通常用于过滤项目,在过滤项目时,过滤器将应用于所有按 Id 列索引的表。

在 Envision 脚本中开始一个块后,所有满足该过滤器的表行将予以保留,否则将保持不变。并且每一段在该代码块之外编写的脚本片段都可以移到该代码块中。具体而言,即可以在一个块中定义另一个过滤器块。这种模式被称之为嵌套。在上例中,从第 3 行开始的 where 块嵌套在从第 1 行开始的 when 块中。

构成条件

所谓条件,即其求值结果可能为 true 或 false 的表达式。过滤的概念基于条件的构成,条件根据输入数据评估,且可以从中间计算获取结果。Envision 提供了构成各种条件的功能,如以下脚本所示:
when date >= monday(oend) - 52 * 7 & date < monday(oend)
  Week.sold := sum(Orders.NetAmount)
  show linechart "Sold by week{$}" a6f7 tomato with Week.sold
在这个代码段中,and 运算符指示表达式运算符的左右两边应都为 true。Envision 支持的逻辑运算符有:andornot。此外,使用数值运算符 ==(等于)、!=(不等于)、<=(小于等于)、>=(大于等于)、<(小于)、>(大于)可以比较数值。下面的代码段说明了这些运算符的组合和求值方式。
a := 1 > 10 // false
b := not a // true
c := a | b // true
d := a & b // false
e := 10 >= 3 | 5 > 7 // true
show table "Conditions" with a, b, c, d, e
当一个过滤块嵌套在另一个过滤块中时,其作用相当于对 and 运算符左右两边的两个条件使用 and 运算符。

在上例中,您可能会注意到其中包含 monday(end) 语法。这是对 monday 函数执行的函数调用。对于任何日期,此函数将返回不晚于(包含)作为参数传递的日期的最后一个星期一。因此,如果 Monday 被作为参数传递,该函数将返回相同的日期。

monday() 函数用于定义构成完整星期(而非部分星期)的时间窗口。实际上,在按星期执行聚合时,应只考虑完整的星期。否则,第一个数据点和最后一个数据点可能显示令人费解的低值,即使用的只是部分星期。

按日期过滤表

Envision 为针对临时条件对表进行过滤提供广泛支持。上面的脚本过滤的是 1 年之前的所有行。我们来更详细地了解一下相关代码段。
oend := max(Orders.Date)
when date > oend - 365
  LastYearQty = sum(Orders.Quantity)
Envision 在内部将日期作为自 2001 年 1 月 1 日算起的天数(整数)处理。此举具有诸多优点,包括能够对日期执行算术运算。譬如说,一个日期减去 7 可以获得上一周,减去 365 则可获得去年的大概日期。

date 关键字还有一种特殊行为。date 变量隐式指向所有包含 date 列的表。由于此处仅一个表(即 Orders 表),因此相当于我们编写了 Orders.Date > oend - 365。但是,date 语法不仅更简洁,而且可同时应用于所有相关表。如果我们不看销量,而要看采购量,可以这样编写脚本:
read "/sample/Lokad_PurchaseOrders.tsv" as PO // upload of PurchaseOrders

// snipped

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

过滤项目

除了按日期过滤行,Envision 还提供了过滤项目的功能,对于想排除特定产品、位置、类别等的情形而言,这又是另一个常见的需求。本页顶部的脚本说明了如何将范畴局限于最符合积压库存条件的项目。相关代码行如下:
where StockOnHand + StockOnOrder > LastYearQty
show table "Overstocked items, +1 year of stock" a2f3 tomato with
Id
Name
StockOnHand + StockOnOrder as "Stock"
LastYearQty
where 关键字后面跟有一个条件。该条件应用于三个隐式属于 Items 表的矢量。实际上,在变量名称中没有表名作为前缀的情况下,这是唯一可以指向的表。例如,在指向 Orders 表中的行时,需要使用 Orders.NetAmount,而不是只需要 NetAmount。这里的条件可以这样解读:只包含库存量+订单库存之和大于去年销售量的项目。

Items 表定义条件后,每个包含 Id(即用于指向遵循 Envision 约定的项目的标识符)列的表将同样被过滤。这种行为与之前介绍的临时过滤相同。实际上,在开始过滤项目时,保留不再附加到任何项目的销售订单行或采购订单行是没有意义的,因为这些项目已被过滤出去。

过滤项目影响新计算的矢量的定义范畴。我们可以用一小段脚本来说明这一点。
where StockOnHand > 5
  GreaterThanFive = "yes"
  show table "Hello" with Name, GreaterThanFive // CORRECT!
// 从这行开始跳出过滤块
show table "Hello" with Name, GreaterThanFive // WRONG!
第 5 行是不正确的,因为 GreaterThanFive 矢量仅针对 StockOnHand > 5 条件为 true 的行进行了定义。因此,尽管该矢量在块中进行了正确定义(因此可以使用该矢量,从第 3 行可以看出来),但并不能在该过滤块外部使用,因为有些值没有定义。通过确保该矢量针对所有项值进行正确定义可以解决这种问题,如下所示。
GreaterThanFive = "no"
where StockOnHand > 5
  GreaterThanFive = "yes"
  show table "Hello" with Name, GreaterThanFive // CORRECT!
// 从这行开始跳出过滤块
show table "Hello" with Name, GreaterThanFive // CORRECT!
这个代码段从第 1 行开始,针对所有项目正确定义了矢量 GreaterThanFive。在第 3 行针对项目子集修改了该定义。但是,这样的修改并不能改变这样一个事实: GreaterThanFive 矢量针对所有项目进显式定义,因此第 6 行的显示是正确的。

过滤任意表

虽然过滤日期和项目十分有用,但有时需要准确过滤某个特定表。此类任务也可以通过 where 关键字完成。我们来看一下以下代码行,了解 Envision 的这种功能。
where Orders.NetAmount > 1000
show table "Large transactions over $1000" a4f5 tomato with
Id
Name
Orders.Date
Orders.Quantity
Orders.NetAmount
Orders.Client
在这里,我们过滤了 Orders 表来排除所有金额在 $1000 以下的表行。未被过滤出去的行则通过 show table 显示在第 2 行和第 3 行。 本例说明了如何过滤单个表。该过滤器仅影响 Orders 表以及其他所有不受此类条件影响的表。

如果在过滤块中计算与 Orders 表相关的矢量,则对该矢量的访问仅限于该过滤块本身。我们已针对项目观察了这种行为,现在来了解它又是如何应用于任意表的。
where Orders.NetAmount > 1000
  Orders.LargeTxn = "yes"
  show table "Large transactions" with Name, Orders.LargeTxn // CORRECT!
// 块中最后一行
// 缩进减小,跳出此块
show table "Large transactions" with Name, Orders.LargeTxn // WRONG!
由于 Orders.LargeTxn 矢量没有针对 Orders 表的所有行进行定义,因此只有第 3 行正确,第 5 行不正确。与上一个例子一样,通过针对整个 Orders 表正确定义 LargeTxn 同样可以修复这段脚本。使用下面的脚本可以实现这个过程。
Orders.LargeTxn = "no"
where Orders.NetAmount > 1000
  Orders.LargeTxn = "yes"
  show table "Large transactions" with Name, Orders.LargeTxn // CORRECT!
// 块中最后一行
// 缩进减小,跳出此块
show table "Large transactions" with Name, Orders.LargeTxn // CORRECT!
根据经验法则,Envision 力求尽可能地允许块“泄漏”:在块中计算的矢量可以在该块外部使用,前提是这种使用不违反这样一种规则:矢量出现在赋值右边时,该矢量的所有值应显式定义。

用于过滤的语法糖

Envision“过滤和缩进”针对过滤使用的语法模式简洁易读,但在涉及到多个过滤器时,缩进可能有点难以解译。因此,Envision 提供了几种“语法糖”,也即只需较少缩进的备用语法。本节将对这些语法进行介绍。

使用多个过滤器时跳过缩进

如果是要单独使用过滤器,那么 Envision 只要求每个过滤器额外缩进一级。如果只对最内层的范围感兴趣,则只需缩进一级,如下所示:
// 每个 'where' 过滤器附带自身的缩进级别
where Orders.Quantity > 10
where StockOnHand < 100
show table "Filtered orders" with Orders.Quantity

// 但在使用多个过滤器时,只需缩进一级
where Orders.Quanty > 10
where StockOnHand < 100 // no ident here!
show table "Filtered orders" with Orders.Quantity
第二个代码块与第一个代码块的语义相同,但只需要缩进一级。其语法一般为:
where A
when B
where C
show table "Filtered by A, B and C" with X
//与以下代码块相同
where A
when B
where C
show table "Filtered by A, B and C" with X

利用关键字 and 合并过滤器

我们已看到,在 Envision 中,布尔运算符 AND 用符号 & 表示。但 Envision 也提供了关键字 and,该关键字的语义略有不同:
// 两个嵌套的 'where' 过滤器
where Orders.NetAmount > 1000 
where StockOnHand > 10
show table "Filtered transactions" with Name, Orders.Quantity

// 可以使用 'and' 重新编写为一个过滤器
where Orders.NetAmount > 1000 and StockOnHand > 10
show table "Filtered transactions" with Name, Orders.Quantity

and 关键字严格等同于嵌套的 where 过滤器。通过 and 关键字,可以按顺序引入多个过滤器且只需缩进一级。我们通常可以这样编写:
where A
where B
where C
// 片段

// 可以重写
where A and B and C
// 片段
实际上,使用 and 关键字可以合并多个不会进行单独使用的过滤器。

使用关键字 keep 无需缩进

常用的编码模式是在脚本起始处引入过滤器,以便限制对特定范围进行数据分析。虽然 Envision 的过滤器语法相对比较适合这种方案,但编写的整段 Envision 脚本最后会缩进一级或两级。关键字 keep 提供了一种消除缩进的方法:
// 'where' 过滤器引入一个缩进的块
where Orders.Quantity > 10
// 块开头
show table "Inside the filter" with sum(Orders.Quantity)
// 块末尾
show table "Outside the filter" with sum(Orders.Quantity)

// 但在使用 'keep' 时,过滤器不会缩进
keep where Orders.Quantity > 10
show table "Inside the filter" with sum(Orders.Quantity)
关键字 keep 应放在 wherewhen 前面,以指示使用此过滤器时不会缩进。在此范围结束前,过滤一直保持活动状态。
where A
keep where B
show table "Filtered by A and B" with X
// A 和 B 过滤器末尾
show table "Not filtered" with X
因此,如果 keep 放在脚本行中且没有缩进,那么在这段 Envision 脚本结束前都会应用此过滤器。

带后缀的串联过滤器

到目前为止,我们所观察到的所有过滤器都是作为过滤块编写的。但是,Envision 也支持备用语法,也即条件后缀,这种形式更紧凑。我们来回顾一下去年销售量的计算。
when date > oend - 365
  LastYearQty = sum(Orders.Quantity)
这段脚本可以重新编写为:
LastYearQty = sum(Orders.Quantity) when date > oend - 365
对于熟悉关系数据库的读者而言,这种语法可能与 SQL 中编写 where 条件的方式更为接近。在 Envision 中,这种语法主要作为语法糖,目的是避免在块中只写入一条语句时引入只有单独一行的块。wherewhen 在赋值右边均可作为“后缀”。

这些聚合器可以内联 whenwhere 条件进行过滤。此外 Envision 也提供了对条件添加 else 修饰符的功能。举个例子,以下的写法是错误的:
oend := max(Orders.Date)
Week.sold := sum(Orders.NetAmount) when date < monday(oend)
show linechart "Sold by week" with Week.sold // WRONG
Week.sold 未在整个范围内定义,因为已在上面的一行进行了过滤。但是,通过添加 else 选项,就可以在任意位置正确定义 Week.sold,我们可以这样编写:
oend := max(Orders.Date)
Week.sold := sum(Orders.NetAmount) when date < monday(oend) else 0
show linechart "Sold by week" with Week.sold // CORRECT