前言:今天在Tableau的交流群参与了一个小讨论,回顾一下案例还不错,我想结合阐述一下“选择计算字段的基本方法”。
时间仓促,简要阐述,欢迎留言。

问题背景:

群里有人问“Tableau有什么办法把1min的数据聚合成15min的数据吗?”
这个问题阐述有点模糊,详细说明一下:
  • 比如数据是每分钟生成一个数值,每分钟的的数据生成的折线过于密集了,希望更改维度的颗粒度;
  • 但是时间默认只有年、月、周、天、小时、分钟等固定颗粒度,小时太粗,分钟太细,希望把每小时再分为四等分,即以15min为间隔聚合度量。
截图如下,都是昵称,我也无暇隐藏了,见谅
WechatIMG386.jpeg
看一下提问者的截图如下:
mmexport15768473262785244116083052994828.jpg

1、直觉的解决方法

既然是要把分钟标记为0,15,30,45四个部分,直觉的解决方法就是根据分钟的大小做判断(if else)。如下:
mmexport15768473567147157063977813559962.jpg
虽然说直觉在大部分情况下是对的,但是对于新手而言,大部分情况下似乎是有瑕疵的。 

2、我的思考方法

正好我路上看到这里,脑海中想了一个图,我们是想把分钟切分为四个部分,相当于数据桶又不能用Tableau的“数据桶”,因为数据桶在Tableau是把连续的度量切分用的,而这里是时间——维度。
截屏2019-12-20下午10.57.12.png
上面的使用if else嵌套的方法,虽然可行,但显然效率太低,会给服务器沉重的压力——每一行都要做非常多的类型提取、转化、组合和判断。
这个问题之前有人问过我,不过当时是把1小时切分为2份,所以可以直接用上面的判断——只有两份使用的布尔运算,性能是最好的。这里要切分四分就不能用布尔运算,但是逻辑判断性能又太低。有没有更好的办法,比如用一次算数运算解决这个问题?
我突然想起来前几天在学习python写期货数据遇到的一个问题:
我的某个tushare接口一分钟不能提取120次,超过就会中断,为了避免中断,我设置了一个变量i,每循环一次+1,然后做“取整数计算”,当i//120>0时,设置time.sleep(60)休息60秒然后i重置为0继续!
这里能否用类似的思路,取整数运算?
似乎可行,我只需要让每个区间的分钟数值,先变成0,1,2,3,然后再改为0,15,30,45即可。我如今习惯了可视化更好的表述,如下:
截屏2019-12-20下午11.09.48.png
思路既然清晰,我就在群里说明了我的想法,期待大家的验证:
“大家好,数据很多,过多的if判断和嵌套会降低性能,不妨尝试用取整数计算。比如2//15=0,再*15,结果就是0/15/30/45四段。”
于是大家简化了上面的计算方法,如下:
:Users:wuyupeng:Downloads:WechatIMG383
看上去还不错!

2.5 取整运算的两个函数

——(博客和公众号好友Byte启发后补充)

上面提出取整运算后,对方直接使用了int函数,我看结果正确没有细想,感谢byte的提醒,其实在Tableau中还有一个函数,是专门用来做取整数运算的——ceiling

  • int是类型转化函数,把浮点改为整数,自动删除了后面所有的小数,因此是舍小数;
  • ceiling是算术函数,是把浮点进位到最近的整数,因此是进位;

Byte做日的数据桶,甚至使用了参数,这样可以根据参数设置数据桶的宽度,比如间隔是5,那么就是0-5号,5-10号,如果间隔是7,就是0-7号,7-14号。

但是如果我们把数据桶写0,5或者0,7,显然不如写5,10,或者7,14来的准确,因此ceiling函数就是更好的选择。

当然,如果你执着于int,那么就int之后加上1再计算吧。二者只是差一个单位,可以相互转化。

WechatIMG11

3、对时间的处理

上面的结果虽然不错,但是很明显没有充分利用Tableau中的日期函数,所以我又提出了新的完善建议:
“可以使用datetrunc函数,一次性提取hour之前的日期,然后再加上后面的分钟计算,这样可以进一步提高计算效率。try~”
于是又有了下面的更优雅的计算方法:
:Users:wuyupeng:Downloads:WechatIMG382
不过,我发现我没有说清楚我的想法,这不是我想要的。虽然可行,但是这个表达式把日期转化为字符串(str计算),然后又做了相加。对于日期而言,这样的计算不安全,也有些冗长。所以,我进一步说明了我的想法:
“外面的datetime函数可以尝试用dateadd来实现计算,datetrunc的日期结果add 日期的分段结果”
有必要我说明一下我的意思:datetrunc是截断,因此hour后面的部分都默认为0;而分钟已经通过“取整运算”返回为0/15/30/45四个结果,二者使用dateadd函数相加,就相当于一个日期加上了分钟部分。所以就有了下面的优雅表达式:
WechatIMG381.png
至此,最早的性能低下的if嵌套和字符串计算,就替换为了算数计算和日期计算,比较完美的解决了这个问题。
当然,这个表达式性能依然不高,因为每一行还是要做多次运算,才能返回一个结果,也许还有更好的办法,以后可以一起更新。

4、进一步的优化

正如上面byte所提示,如果领导的需求变化多,可以把数据桶的间隔改为参数,写入计算之中;在不需要更改参数的地方,只需要把参数控件隐藏即可。

一招致胜,四处享用。

 

5、选择运算的基本方法

我写这篇文章的重点是,面对此类问题,如何选择计算??
​Henry说程序员有几个境界:能实现、性能好、代码短、艺术性。四者兼之,自然是上等佳选。从性能的角度应该如何思考呢?
根据个人的经验,提几个角度:
A、选择计算方法时,布尔判断优先于算数计算,算数计算优先于(嵌套)逻辑判断。因此,当我只是区分小于30分钟和大于30分钟时,我用一个布尔判断来解决,但是当我要切分四阶段时,我选择用算数计算,只有二者都不能解决时,才考虑复杂逻辑判断。
处理逻辑问题,“没有if解决不了的,如果有就用两个”;;但是这是迫不得已的选择。即便使用if函数,也优先考虑iif、isnul、ifnull这样的简化函数,再使用​完整逻辑函数。
B、优先使用“原生性函数”。上面我们尽可能使用Tableau自带的日期函数,而非优先考虑字符串函数,原生函数一般都能更好的简化表达式,提高性能​。
C、聚合函数优先于行级别函数。行级别计算往往会导致性能问题,特别是每一行的计算都还要非常多次时。不过,所有的字符串函数、日期函数都是行级别函数,这个地方是没有简化空间的​;更多适用于算数计算等​地方。
如果还有加上一句话的话,
“条条大路通罗马,但总有最近的一条路。”


by喜乐君
Dec 20, 2019
Dec 23, 2019 升级,感谢byte