Thinking in DAX with PowerBI - 逻辑框架 - 计算逻辑
PowerBI 目前作为商业智能工具,其核心功能特性是:分析。我们会开设一个系列《Thinking in DAX》和大家一起从思想和计算的抽象层面来再次深入理解这个过程。此前,与此有关的内容,也会纳入进来。
要分析和处理一个问题,需要有解决它的逻辑框架,这涉及两个内容:
数据结构 - 数据以什么形式摆放
计算方法 - 如何基于数据摆放的结构进行计算
有过大学计算机相关背景的伙伴会非常清楚:数据结构和算法,是一个程序员(软件开发工程师)的绝对内功心法。
大家日常看到很多问题的解答,例如:如何做同比分析,如何做一个图,如果实现一个技巧,属于外显招式。
如果你看过《神雕侠侣》(古天乐版),应该记得一件事:
有了内功心法,不会招式,无法解决实际问题
有了招式技巧,不会心法,无法对战真正高手
在目前市面上讲解 DAX 的资料中,大部分讲的是招式以及招式的细节,学习招式的体会是:
一个技巧解决一个问题,爽。
但新的问题来了,如果不看老师怎么操作,自己还是不会。
没有举一反三的能力,无法应对复杂问题的处理。
创新性思维受到限制,因为没有系统性建立根基,没有思考的发力点。
那么,结合内功心法和外显招式,可以真正理解外显招式为什么是那样的。总之:
外显招式
好处:拿来就用
坏处:无法洞察本质,当人习惯了招式,很难去思考更本质的东西
内功心法
好处:抽象本质,可以帮助人们开发出更多技巧和模式
坏处:往往不能拿来就用
说白了,这特别像是:短期回报和长期回报的感觉。就如同短视频可以让人们在几秒high起来,但它毕竟是短视频。长视频需要人们付出更多时间去观看和思考,但可能将一个问题揭示得更加透彻。
逻辑框架
本文不准备展开讲逻辑框架,太抽象。但我们可以得到这样的共识,逻辑框架,这涉及两个内容:
数据结构 - 数据以什么形式摆放
计算方法 - 如何基于数据摆放的结构进行计算
数据结构和算法,在大学课程中,有两本厚厚的书与之对应,例如:
(这里只是示例,不推荐大家购买书籍学习,太绕远路。)
我们并不用按照本科甚至考研的要求来讲解算法和数据结构,但这里面最精华的思想我们可以提炼并与 DAX 结合,为大家呈现,让大家从一个更严谨更上帝视角来理解 DAX。
很多人会去使劲学习:筛选上下文和行上下文。在任何关于 DAX 的资料里,只会告诉你 DAX 中存在两个上下文:筛选上下文和行上下文,但没有任何资料讲它们为什么要存在。就好像是上帝,说要有光,于是就有了光,那为什么要有光呢?我们需要一个更本质的认知,沿着线索,找寻 DAX 被设计时经过的心路,这不但有趣,而且可以让你忘却 DAX 的表象,而逼近它的基因本质。
数据结构,之所以存在,就是为了基于它创建更优良的计算方法(算法);
计算方法,之所以存在,必须依赖于一个数据结构才能发挥作用。
这两者是共生共灭的。
感受 DAX 中的算法与数据结构
由于 DAX 的设计初衷是给商业分析师的,也就是业务人员,所以,我们不会把大家搞成程序员,但这丝毫不影响我们去理解思想。在 DAX 中,你其实已经用过了很多算法,你编写的任何 DAX 公式都是一个算法,都是一个计算方法,这些计算方法被定义成了一个核心部件,叫:度量值。
从这个意义上来说,度量值,是算法(计算方法)的定义。仅此而已。
你还记得这个折磨你的函数吗?CALCULATE,就是计算的意思。CALCULATE 从一定意义上也在揭示,它负责一个算法。
你觉得自己没有见过 DAX 中的数据结构吗?
数据结构,是数据摆放的形态。
DAX 中,的数据结构天然就是一个表。
你也许已经看过星型模型的说法,这是多个表所形成的数据结构。
你可能觉得没有什么新意。没错,在有的时候,我们不是按照表的思维。我来举两个例子。
视为列表,列表(List),强调的不是表,而是一个列,例如:VALUES( Product[SKU] )。往往下一步就是对列表的迭代。
视为集合,集合(Set),强调了不重复的无序元素,例如:VAR UsersLastYear = ... VAR UsersNow = ... 。往往下一步就是集合的交并补运算,如:RETURN INTERSECT( UsersLastYear , UsersNow ) ,这就是去年用户在现在的留存。
虽然我们眼睛看到的都是一个物理结构:表。仅仅只有这个结构,但它可以被理解成的数据结构包括但不限于:
值,一行一列的表。
列表,往往要施加迭代运算。
集合,往往要施加交集等运算。
那么现在,你应该可以感受到,很多时候往往你思考一个问题而不得解,是没有想好数据的结构。例如,要计算留存用户数的思路就是要使用集合的结构。
我们后续会计算展示不同数据结构的使用,但这些仅仅是《Thinking in DAX》的一个部分哦。
计算逻辑
这是本文的重点内容了。
在学习 DAX 之前,我们是否怀疑过一件事:DAX 的函数是有限的,那么对于任何一个复杂的业务问题,都可以用 DAX 求解吗?如果不能,那 DAX 的能力岂不是很有限吗?有限到什么程度呢?那 DAX 这么弱的话,是不是我还是去学其他的工具好了。
我们刚刚讲过解决任何问题,都需要逻辑框架,它包括:
数据结构
计算方法
经过科学家论证,如果某种计算方法能够充分提供三个计算逻辑,在理论上是可以表示任何计算方法的,这三个逻辑就是:
顺序逻辑
分支逻辑
循环逻辑
那么,问题来了,DAX 中有没有这几种逻辑的表达呢?如果没有或者缺失,那么 DAX 就很有限了;如果有,那么岂不是可以这么来思考问题了。
DAX 中的顺序逻辑
首先,我们要看懂什么是顺序逻辑,如下:
在 DAX 中,如何表示顺序逻辑呢?
有两种方法。
方法一,DAX 本身就是顺序逻辑。DAX 的函数是可以嵌套的,嵌套就是一种顺序逻辑,先执行内部函数,再执行包裹内部函数的外层函数,依次类推。
方法二,使用 VAR ... RTURN ... 结构。下面给出使用 VAR ... RTURN ... 结构的四种形态。
// 基本形态
VAR X = ...
VAR Y = ...
RETURN ...
// VAR 中带有 VAR ... RETURN ... 结构
VAR X =
VAR X1 = ...
VAR X2 = ...
RETURN ...
VAR Y = ...
RETURN ...
// RETURN 中带有 VAR ... RETURN ... 结构
VAR X = ...
VAR Y = ...
RETURN
VAR A = ...
VAR B = ...
RETURN ...
// VAR 和 RETURN 中分别带有 VAR ... RETURN ... 结构
VAR X =
VAR X1 = ...
VAR X2 = ...
RETURN ...
VAR Y = ...
RETURN
VAR A = ...
VAR B = ...
RETURN ...
很多小伙伴更喜欢使用 VAR ... RETURN ... 结构,就是因为当你习惯大脑用顺序思考问题时,自然用这种结构很贴合人的思考过程。
DAX 中的分支逻辑
首先,我们要看懂什么是顺序逻辑,如下:
在 DAX 中,如何表示分支逻辑呢?
你应该想到两个函数:IF 和 SWITCH。
本质上,SWITCH 只是 IF 的变体,因为,SWITCH 总可以表示成更复杂的 IF 结构。
请参考上图,注意其中的演化二字,虽然,编写的公式一样,但出现在人们大脑脑海的逻辑结构可能不同,可能是上面的样子,也可能是下面的样子。但我们建议你用下面的模式来思考,它可以应对任何情况。
对应于编写 DAX 公式,首先来看 IF 的双分支结构,如下:
IF( A = B , A , B )
很快地,我们就会遇到多个分支的情况,如下:
KPI =
// 通用类 KPI,具体 KPI 由用户选择决定
// KPI 的通用化,通常有切片器和它相配合
// 由 DAX Pro 生成,参考:www.excel120.com/daxpro/
SWITCH( SELECTEDVALUE( 'Option.KPI'[KPICode] ),
"Sales" , [KPI.销售额] ,
"Profit" , [KPI.利润] ,
"Volume" , [KPI.销量] ,
"Profit%" , [KPI.利润率] ,
[KPI.销售额] // 如果用户不选择任何 KPI 选项,所默认使用的 KPI。
)
这种形态还不够通用,当 SWITCH 要对比的条件不是 A = B 这种逻辑,而是例如:销售额 > 1000 这种更复杂的对比,就要使用更通用的结构如下:
Item.ABC.Color =
// 由 DAX Pro 生成,参考:www.excel120.com/daxpro/
SWITCH( TRUE() ,
[Item.ABC.Value%] <= MIN( 'Option.X'[Option.X] ) / 100 , "Green" ,
[Item.ABC.Value%] <= MAX( 'Option.X'[Option.X] ) / 100 , "Orange" ,
"Red"
)
这样的通用结构就可以确保,只要是一个分支逻辑不管是几个分支,都可以表达。
DAX 中的循环逻辑
首先,我们要看懂什么是顺序逻辑,也可以演变为迭代逻辑,如下:
对于循环结构,用代码表示,大概逻辑如下:
i = 1
for(i<=100){
...
i = i + 1;
}
其中,i 就是一个循环变量,我们不用理解编程的过程,但我们可以知道 i 就表示了循环的次数,也就是轮数,它可以用另一种等价结构表示,就成为了迭代,如下:
list = ...
foreach( line in list ){
...
}
迭代结构可以完全替换掉循环结构,而且有一个好处,这里并不需要一个所谓的循环变量 i。我们甚至可以这样写这个逻辑,如下:
Table = ...
ForEach( Table , fx( Row , ... ) )
其中,ForEach,是一个迭代函数,Table 是 ForEach 迭代的对象,该对象是一个列表,在迭代中,对于每次迭代的行 Row 施加一个函数 fx 去处理它。
如果你看懂了上述的过程,那恭喜你,你应该可以顿悟到底什么是行上下文了。
如果你说你没见过在 DAX 有 ForEach 这个函数,那没有问题,我们自己来设计一下:
FILTER( Table , ... ) = ForEach( Table , fx( Row , ... ) )
// 其中,
fx( Row , ... ) = IF ( ... = FALSE , RemoveRowFromTable( Table , Row ) )
而作为使用者,我们只需要使用:
FILTER( Table , ... )
我们永远不需要知道里面有个 Row 的处理过程,而这个 Row 的本质其实是:Table[i],也就是表的第 i 行,等价于开始的循环结构。
类似地,SUM,SUMX,MAX,MAXX,ADDCOLLUMN 等函数都内置了:ForEach( Table , fx( Row , ... ) )。
因此,我们可以彻底回答两个重要问题:
什么是行上下文?答案:ForEach( Table , fx( Row , ... ) ) 中正在被迭代的 Table 的第 i 行 Row。
为什么要有行上下文?答案:为了支持循环逻辑(迭代逻辑)的同时还不必考虑循环变量。
这样,我们不仅搞清楚了行上下文就是 DAX 为了实现迭代逻辑来创建的内部结构;还搞清楚了它存在的动机是完成循环(迭代)来实现大规模运算。
注意:上述的描述,在逻辑上是没有问题的,在 DAX 引擎的底层实现上,有更复杂的优化,但这根本不是业务分析师需要理解的,更不会影响我们用这里的逻辑来处理任何问题。正如每天生活在大地上,用牛顿运动定律就够了,非要说它不严谨,一定要用相对论算一辆车开起来的样子只是自寻烦恼。
如果您是一个业务分析师,根本看不懂上面写的是什么,也不要紧,您只需要明白一个重要的事情:
DAX 是支持循环逻辑的,这是构成解决任何问题的计算方法必备的顺序,分支,循环逻辑之一的最强大逻辑。
我们在面对数据分析时,往往都不是一条数据,而是成千上万条数据,因此,迭代逻辑是必须的。
那么,我们再来考大家一个问题:
SUM 中是否有迭代逻辑?
在 《DAX权威指南》4.8 中是这样写的:
Aggregators like SUM, MIN, and MAX only use the fi lter context, and they ignore the row context.
在线阅读版,参考:http://www.excel120.com/#/dax2/c4/c4-2
SUM,MIN,MAX 等聚合函数使用筛选上下文,忽略了行上下文。
从这里的学习可以发现,SUM 并不会忽略行上下文,而在 SUM 又构建了自己的行上下文体系,迭代发生在 SUM 中。
也就是说:
SUM( T[C] )
等价于
SUMX( T , T[C] )
那么对于:
SUMX( T , SUM( T[C] ) )
其中的 SUM( T[C] )
位于行上下文中,但 SUM 会产生新的迭代,进而创建自己的行上下文,如下:
SUMX( T , SUM( T[C] ) )
等价于
SUMX( T , SUMX( T , T[C] ) )
等价于
ForEach(
Table ,
Fx( RowX ,
// SUM 开始
ForEach(
Table,
Fx(
RowY ,
[C]
)
// SUM 执行完毕
)
)
因此,SUMX( T , SUM( T[C] ) )
中的 SUM 会迭代整个 T ,但外部的确有一个行上下文的。
可能《DAX权威指南》的作者希望读者更容易的记住这件事,用了忽略一词,于是很多小伙伴问过这个问题。
因此,SUM 中是有迭代逻辑的。
那么这个迭代逻辑怎么用于生产实践呢?
请这样思考问题:对 ... 进行迭代,在每步中,...。
这样的话语应该出现在你的大脑中。
修炼建议
理解了三个计算逻辑:顺序,分支,迭代。它分别对应了你的:小学,初中,高中。
小学三年级,学习了:150 - { 90 - [ 5 + ( 3 - 2 ) × 2 ] } 这就是嵌套公式
小学五年级,学习了:将上述算式分步,就是 VAR ... RETURN ...
初高中年级,学习了:y = ax² + bx + c ( a ≠ 0 ),a ≠ 0 或 = 0 就是分类讨论思想。
初高中年级,学习了:集合,数列,里面其实也蕴含了迭代的思想。
很多初学者,甚至是有一定学习经历的伙伴还是没有能真正驾驭 DAX,其实 DAX 根本不难,也根本不需要纠结于上下文啊上下文。
下面给出,正确思考问题的流程套路:
第一步:用顺序逻辑,建立解决问题的大框架。如:脑中暗暗想着第一大步做什么,第二大步做什么,就对了。
第二步:在顺序逻辑的框架里,进一步考虑细节。如:如果...怎么样,我就...怎么样,就对了。
第三步:在顺序逻辑的框架里,进一步考虑细节。如:迭代一个列表,在迭代的每步里,干...什么,就对了。
在上面的每一步的反复实践中,您会慢慢地:
在每一步的最终细节,使用 DAX 函数落地,具体可以参考 BI 佐罗的《DAX 36 个核心函数》。
在反复的重复中,这个思维模式会变成自然的习惯,从大脑进入身体内化成自然的身体反应。
接着,大脑思考业务问题,手中流淌出 DAX 公式,如是而已。
总结
本文提出了我们会陆续给出《Thinking in DAX》的系列。本文只是其中一篇而已。
本文提出了逻辑框架,并揭示了数据结构和计算方法在 DAX 的本质重要性。
本文详细阐述了计算方法中的三大逻辑以及在 DAX 中的实现并本质地揭示了行上下文的运行逻辑,最后给出了大家修炼 DAX 运算能力的建议。
如果您在学习 DAX 或解决业务问题中有什么实际通用问题,欢迎交流。
2020年10月 PowerBI VIP 实训 深圳 - 揭示全部 DAX 本质及应用技法。
让数据真正成为你的力量
Create value through simple and easy with fun by PowerBI
Excel BI | DAX Pro | DAX 权威指南 | 线下VIP学习
扫码与PBI精英一起学习,验证码:data2020
PowerBI MVP 带你正确而高效地学习 PowerBI
点击“阅读原文”,即刻开始
↙