Thinking in DAX with PowerBI - 逻辑框架 - 计算逻辑

时间:2022-07-27
本文章向大家介绍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 或解决业务问题中有什么实际通用问题,欢迎交流。