Thinking in DAX with PowerBI - 逻辑框架 - 数据结构
上接 逻辑框架 - 计算逻辑,接下来的问题是数据结构。
数据结构,顾名思义,就是数据摆放的状态。例如:数据按表,集合,列表等形式摆放。
另外,当数据结构是表的时候,还包括了表之间的关系。对于这点,我们再另外研究。
在 PowerBI DAX 中,为了简化,数据结构只有一种表面形态:表。那当需要按照不同逻辑结构思考问题的时候,如何从表的结构形态衍生出其他结构形态?
将表作为表
将表作为表,是很自然的。例如:
{1}
这就表示了一个表,在 PowerBI DAX 中创建后,得到:
这里并没有给定表的列名,系统会自动按Value
给出,当涉及多列时,例如:
{
(1,"Anna",23),
(2,"Tom",27)
}
得到:
这里并没有给定表的列名,系统会自动按Value
加上列序号给出。
表与值的转化
在参与运算时,若某个表中只有一行一列,则可以被作为值。
这里常用的一个 DAX 函数有:VALUES,这用来从一个表中提取一列(会自动非重复化),例如:
这里请注意两点:
1、度量值的定义是正确的;
2、度量值的使用结果也是符合预期的。
但如果使用多值的表列,会得到这样的结果:
这里也请注意两点:
1、度量值的定义是正确的;
2、度量值的使用结果是不符合预期的。
这是初学者常常出现的问题,从逻辑来看:
当VALUES
函数直接用于度量值时,DAX 引擎仅仅检查语法,是没有错误的;但只有在用户使用该度量值的运行时,才会真正计算,得到了含有多个值的结果(也是一个表),且提示用户:表中应该具有单个值。准确讲:只有当表中具有单个值的时候,才能转换成为值,进而显示。
说明
由于微软在界面设计上实在是,这种错误的提示,居然是大叉子,而且错误信息也让人心生恐惧,初学者往往认为自己犯了大错,或触发了软件的 BUG,而实际上,软件只是正确地运行用户的意图。用
VALUES
从表中取值,除非用户脑中想的就是这个。
初学者常常问到:那该怎么办?这个问题其实要问自己:当得到了一个列表后,要进一步做什么?是求和,还是显示出来,然后再用 DAX 函数来操作,确保度量值可以显示正确的结果。
将表作为列表
在某些场景,往往需要对一列元素进行操作,从逻辑结构上,这更符合将其看成是列表,例如:产品ID列表,客户ID列表,订单ID列表等,往往下一步就是对列表进行迭代,在迭代中做一些事情。在经典的 ABC 分析中,需要计算元素列表的积累 KPI 值,则有:
// 假设:
KPI = SUM( 'Order'[Sales] )
// 那么:
KPI.积累 =
VAR _value = [KPI] // 当前元素的 KPI
VAR _value_total = CALCULATE( [KPI], ALLSELECTED( ) ) // 全局元素的 KPI
// 构建视图层汇总表
VAR _v_table = CALCULATETABLE(
ADDCOLUMNS( VALUES( 'Product'[Product] ) , "@Value" , [KPI]),
ALLSELECTED( )
)
// 视图层计算
RETURN SUMX( _v_table , ( [@Value] >= _value ) * [@Value] ) / _value_total
其中:ADDCOLUMNS( VALUES( 'Product'[Product] ) , "@Value" , [KPI值])
就是将产品作为一个列表,再为列表中的每个产品分别计算[KPI]
。
注意
这里对于
[KPI]
的计算,会在迭代'Product'[Product]
的时候,发生上下文转换。即:正在计算的当前的产品所在行,会转换为对某个产品的筛选作用于整个数据模型进行对[KPI]
的计算。可以直观地想象成:在迭代每个产品时,在当前产品,向下捞取对应的订单计算。其中,“向下捞取对应的”这几个字就表现了上下文转换的逻辑动机,那就是:从宏观层面的某个元素,转换为对微观层面的整层筛选,实现了宏观迭代到微观筛选的转换。因此,上下文转换的本质其实正是:宏观迭代到微观筛选的转换。在数据模型中,很多计算的确是要建立在不同层面之间的,那么这种宏观迭代到微观筛选的转换便是在不同层面取数的核心逻辑。
将表作为集合
下面以留存客户数的计算为例,来体会集合的逻辑结构:
User.流失留存.留存客户数 =
//
// --------- A : X period ------------|----- B : y period ------|
// ↑d1 d2↑
// ↑
//
// 设置 x
VAR _month_number_x = 6
// 设置 y
VAR _month_number_y = 1
// 以下请勿修改
VAR _users_period_a =
CALCULATETABLE(
VALUES( 'Order'[CustomerID] ) ,
DATESINPERIOD(
'Calendar'[Date] ,
DATEADD( LASTDATE( 'Calendar'[Date] ) , - _month_number_y , MONTH ) ,
- _month_number_x ,
MONTH
)
)
VAR _users_period_b =
CALCULATETABLE(
VALUES( 'Order'[CustomerID] ) ,
DATESINPERIOD(
'Calendar'[Date] ,
LASTDATE( 'Calendar'[Date] ) ,
- _month_number_y ,
MONTH
)
)
RETURN COUNTROWS( INTERSECT( _users_period_a , _users_period_b ) )
留存用户数,在 DAX 种,通常指的是,对于指定的区间 [d1,d2],所在区间为 B,那么区间 A 的访问用户,在该 B 区间仍然访问的视为留存,其人数为留存数。
这里,有一个自然的逻辑:A 区间的用户集合,B 区间的用户集合,而 A,B 的交集正是留存的用户集合,其行数则为用户数。
这样,我们就把复杂的问题转换求两个集合,在这个案例中是两个用户 ID 的集合,因为用户 ID 代表了用户本身。
注意
上述内容来自 DAX 模板工具:DAX Pro,可以通过拖拽鼠标,快速创建度量值。您也可以理解学习上述 DAX 中的逻辑后在自己的模型中实验。
总结
在 DAX 中,常见的数据结构有四种:
作为表(Table)的表,常常与其他表通过关系构成更复杂的结构。(后续文章,我们进一步研究)
作为值(Value)的表,分析师必须确保在运行时可以的确只返回一个值。
作为列表(List)的表,通常会对一个列表进行迭代,而在迭代中往往要施加上下文转换来切换宏观和微观进行取数。
作为集合(Set)的表,通常会对两个集合做交,并,补的集合操作以便得到所需元素。
这就指导我们在面对一个问题时,我们要考虑表示该问题的数据结构应该强调的是什么,然后想办法来构建这些的数据结构,再基于数据结构来进行计算。
结合算法结构,我们不难看出:
计算方法,依赖于数据结构
计算方法,构建出数据结构
数据结构,为支撑计算方法
数据结构,由计算方法给出
他们之间有紧密的共生关系,这希望大家可以在实践中加以体会理解。
另外,本文揭示了上下文转换的本质:宏观微观切换。即从相对宏观层面的元素迭代,再切换到微观去筛选,俗称:向下捞取相关数据。
启发:DAX 是什么,并不是最重要的,重要的是如何设计简单的规则来完成数据建模中的必备逻辑,且规则最少,那么这些规则的本质应该是什么,这是穿透看似复杂的 DAX 回归简单的思路,故称:Thinking in DAX。
——
? 活动预告:
2020年10月22日 19点 直播:让 PowerBI DAX 回归简单 ?
报名方法:进入 让 PowerBI DAX 回归简单 - 在线直播活动 【阅读原文】。
让数据真正成为你的力量
Create value through simple and easy with fun by PowerBI
Excel BI | DAX Pro | DAX 权威指南 | 线下VIP学习
扫码与PBI精英一起学习,验证码:data2020
PowerBI MVP 带你正确而高效地学习 PowerBI
点击“阅读原文”,即刻开始
↙