前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PowerBI 全网首发原生平滑曲线 - 通用模板及应用

PowerBI 全网首发原生平滑曲线 - 通用模板及应用

作者头像
BI佐罗
发布2021-02-08 21:08:16
8800
发布2021-02-08 21:08:16
举报
文章被收录于专栏:PowerBI战友联盟PowerBI战友联盟

书接上回,我们研究了折线如何变成曲线,如下:

并得到了最终的终极方案:

它满足了:

  • 足够简单
  • 连续光滑
  • 性能够好

那么问题来了:

  • 要单独构建两个辅助表
  • 是否可以让 X 轴不显示序号而是真实信息

也就是通用化问题。

答案是非常肯定的。

效果

我们都知道,在给出年月计算新老客户以及活跃用户数是相对比较复杂的计算,我们来看看最终效果:

这个效果完全满足了需求。

请观察几个点:

  • 2017年01月,活跃用户由纯新用户供应49人,老用户0人。
  • 2017年11月,活跃用户50人,分别对应新用户21人与老用户29人。
  • 2019年05月,活跃用户100人,分别对应新用户13人与老用户87人。
  • 2019年07月,活跃用户35人,新用户0人,完全由老用户供应。

整套曲线的显示很完美。

再观赏坐标轴,图例,曲线颜色,标签,标签背景颜色都完美配合。

这的确实现了想要的一切,这套曲线是连续光滑的,避免了折线图的生硬。

如果是折线图,会是这样:

不对比不知道,一对比,就看出平滑曲线的优雅了。

上文有伙伴留言:

  • Excel 里点一下就好了
  • Tableau 里点一下就好了

没有错。

这两句话是用来怼BI佐罗好还是用来怼PowerBI产品组好呢?

明明是 PowerBI 产品组应该做的事,偏偏不做,把时间用来研究 PowerBI 的logo不叫logo叫Icon这种问题,而 BI佐罗 只能通过这么间接的方法来弥补了 PowerBI 的硬伤,反而捞不到什么好。一定要注意:这是 PowerBI 的问题。 那么我们这么做有意义吗?有的。 处于学习 PowerBI 尤其是 DAX 的阶段,这样的问题的解决可以极度提升个人的 DAX 以及 PowerBI 能力,而不是曲线问题本身。 很多人学习 PowerBI 以及学习 DAX,可以在这些问题中得以思考并实现是很有价值的。

最重要的是,本文要从根本上给出通用方法。

通用实现

一个问题的解决,并不是最难的,最难的在于:

  • 通用化和可扩展化,适用于所有场景
  • 以更高的性能运行与折线图不应该有任何性能差异

这两点居然被完美地解决了。

首先,来看方法的通用化。

如果我们的 X 轴是 0,1,2,...,N,那么,我们只需要这么做:

将 X 轴演化为:0.01,0.02,....1.00,1.01,1.02,...,2.00,2.01,2.02,...,3.00,4.00,...,N.00 即可。这样 X 轴就以10倍或者20倍拉伸除了更多的点。我们只要在这些点计算出值,并用纯折线图连接,由于点很多,看着就是平滑的曲线了。

但是,如果我们的 X 轴是年,月,甚至是年,月的层级怎么办?

如果您学习过《BI真经》就可以知道我们可以创建一个定制 X 轴,并保留年月层级同时做到扩大20倍,如下所示:

可以看出:

  • 2017-01 的 X 是 0,而有 0.00 到 0.95 间隔 0.05 步长的 20 个点
  • X 和 X' 与该维度融合,无需另外制作

将这两个特定整合到一起真的太强大了,也就意味着:

当且仅当需要为某个折线图定制它的光滑曲线版本时,才进行仅一次维度定制即可。

接着,维度定制的方法存在通用性吗?

答案是肯定的。其通用算法如下:

代码语言:javascript
复制
Smooth.X.CRM.Date = 

VAR _base = 

    VAR _last = EOMONTH( [Start:Date.LastDate.All] , -1 )
    RETURN 
        CALCULATETABLE(
            SUMMARIZE( 'Calendar' , [YearNumber] , [MonthNumber] ),
            'Calendar'[Date] <= _last
        )

VAR _base_indexKey =
    ADDCOLUMNS(
        _base,
        "@IndexKey" , [YearNumber] * 100 + [MonthNumber]
    )

// no need to change the DAX below:

VAR _index_temp = SELECTCOLUMNS( _base_indexKey , "@IndexKey" , [@IndexKey] )

VAR _base_index = SUBSTITUTEWITHINDEX( _base_indexKey , "X" , _index_temp , [@IndexKey] , ASC )

VAR _table_ex = 
    FILTER(
        GENERATEALL( _base_index , SELECTCOLUMNS( GENERATESERIES( 0 , 19 ) , "X'" , [x] + [Value] / 20 ) ) ,
        [X'] <= MAXX( _base_index , [X] )
    )

RETURN _table_ex

框架思路如下:

  • 准备基本维度
  • 将基本维度扩展出通用索引 X 以及 X'

其中,第一步需要编写,而第二步已经被模板化,无需做任何修改。

需要修改的 DAX 仅仅为:

代码语言:javascript
复制
VAR _base = 

    VAR _last = EOMONTH( [Start:Date.LastDate.All] , -1 )
    RETURN 
        CALCULATETABLE(
            SUMMARIZE( 'Calendar' , [YearNumber] , [MonthNumber] ),
            'Calendar'[Date] <= _last
        )

VAR _base_indexKey =
    ADDCOLUMNS(
        _base,
        "@IndexKey" , [YearNumber] * 100 + [MonthNumber]
    )

也就是构建了一个在合理时间范围内的 年 月 表而已,这也是本来就必须要做的。

而将基本维度扩展出通用索引 X 以及 X' 的 DAX 无需做任何修改,已经神奇地做了通用实现。

没有《BI真经》作为基础,看不懂是很正常的,请学习《BI真经》。

那么,现在维度已经好了。

再来看度量值如何做才能:

  • 通用化
  • 高性能

这里我们借助《BI真经》给出的通用视图性能提升模式,给出通用化实现,如下:

代码语言:javascript
复制
CRM.User.New.Smooth = 

VAR _x_curr = SELECTEDVALUE( 'Smooth.X.CRM.Date'[X] ) // 指定表
VAR _dx = SELECTEDVALUE( 'Smooth.X.CRM.Date'[X'] ) - SELECTEDVALUE( 'Smooth.X.CRM.Date'[X] ) // 指定表
VAR _view_table =

CALCULATETABLE(

    ADDCOLUMNS(
        SUMMARIZE( 'Smooth.X.CRM.Date' , [X] , [YearNumber] , [MonthNumber] ) , // 新维度
        "Y" , 
        CALCULATE( 
            [CRM.User.New] , // 原始度量值
            TREATAS( 
                { ( [YearNumber] , [MonthNumber] ) } , // 新维度
                'Calendar'[YearNumber] , 'Calendar'[MonthNumber] // 原始维度
            ) 
        )
    )

    ,

    ALLSELECTED( )

)

// ------以下模板无需修改----------

VAR _y_min = MAXX( FILTER( _view_table , [X] = MINX( _view_table , [X] ) ) , [Y] )
VAR _y_max = MAXX( FILTER( _view_table , [X] = MAXX( _view_table , [X] ) ) , [Y] )

VAR _y0 = COALESCE( MAXX( FILTER( _view_table , [X] = _x_curr - 1 ) , [Y] ) , _y_min )
VAR _y1 = MAXX( FILTER( _view_table , [X] = _x_curr + 0 ) , [Y] )
VAR _y2 = MAXX( FILTER( _view_table , [X] = _x_curr + 1 ) , [Y] )
VAR _y3 = COALESCE( MAXX( FILTER( _view_table , [X] = _x_curr + 2 ) , [Y] ) , _y_max )

RETURN

    VAR _u1 = _dx
    VAR _u2 = _dx * _dx
    VAR _a0 = -0.5 * _y0 + 1.5 * _y1 - 1.5 * _y2 + 0.5 * _y3 
    VAR _a1 = _y0 - 2.5 * _y1 + 2 * _y2 - 0.5 * _y3
    VAR _a2 = -0.5 * _y0 + 0.5 * _y2
    VAR _a3 = _y1

    RETURN _a0 * _u1 * _u2 + _a1 * _u2 + _a2 * _u1 + _a3

本来是复杂的度量值逻辑计算,在这里也做到了:

  • 高度通用化,仅仅格式化的修改几个位置
  • 用了视图层模式,以及对该问题的定制优化,性能非常快

这样,就完美地解决了度量值计算的问题。

理解视图层通用模式需要学习《BI真经》。

总结

本文给出了折线图的平滑曲线版本的完美通用实现以及所有的 DAX 细节。需要《BI真经》作为基础方能领悟其中的各种妙处。

从本文中不难看出在解决问题到通用化的过程中的通用模式,也就是模式的模式:

  • 问题到模板
  • 维度的通用化构建方法
    • 维度的可变部分,用套路改有限的参数
    • 维度的不变部分,模板化实现,永不修改
  • 度量值的通用化头肩方法
    • 度量值的可变部分,用套路改有限的参数
    • 度量值的不变部分,模板化实现,永不修改

如果你学习过《BI真经》的《PBI高级》,你就可以看出,这里严格遵守了 OCP 原则(开放闭合原则)给出了稳定需求和可变需求的分离,让实现具备了通用性。

在选择10个点,20个点还是更多点作为插值元素方面以及索引从 0 开始而不是从 1 开始等很多细节都经过了极为巧妙地推演,读者可以自行研究,此处就不再赘述。

再来回顾最终的结果:

致敬:大学本科二年级《数学分析》第四章 - 函数的连续,曲线的光滑。

既然可以用于这个新老用户访问趋势图,本方案便可以用于任何曲线场景,完美通用实现。

缺乏 DAX 基础和数学基础的伙伴认为本文比较复杂也很正常,但在实际应用层面,仅仅是改几个参数的问题,已经可以无脑复制,但问题是如果您没有《BI真经》的学习,无脑复制也是有难度的。

最后,强烈建议有缘的你,如果正在学习 PowerBI 请学习《BI真经》,它可以推演出所有在 PowerBI 中需要的内容。

在订阅了BI佐罗讲授的《BI真经》之《BI进行时》课程区,除了可以下载本文案例,还可以观看视频讲解。

在视频讲解中,会更加详尽地介绍一些细节,连前几个视频将于 2 月初统一更新。

PowerBI 全网首发原生平滑曲线 - 原理及实现

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-01-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PowerBI战友联盟 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 效果
  • 通用实现
  • 总结
相关产品与服务
腾讯云 BI
腾讯云 BI(Business Intelligence,BI)提供从数据源接入、数据建模到数据可视化分析全流程的BI能力,帮助经营者快速获取决策数据依据。系统采用敏捷自助式设计,使用者仅需通过简单拖拽即可完成原本复杂的报表开发过程,并支持报表的分享、推送等企业协作场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档