tableau参数_Tableau 数学驱动艺术

TLDR:非 Tableau 的用户可以跳过了。本篇所分享的奇技淫巧,在实际工作中甚少用到,请谨慎选择学习。

00c6334b50519b4de42859ba694de632.png

Tableau 的定位是 BI ,并非生成式艺术(Generative Art)。如果感兴趣生成式艺术,可以尝试 Processing 等软件。近期出道的纯 Web 软件 morph.graphics ,也值得一试。用 Tableau 来做图形艺术,一是大材小用,二是坦白讲非常麻烦,甚至不及直接用 D3.js 等写代码方便。

既然大费周章搞定了 Tableau,必须取个酷酷的名字:数学驱动艺术(Math Driven Art)。为什么不是 “data-driven” 呢?其实上面的这个图形,输入只有两个数据点,图形完全靠公式生成,当然 “math-driven” 更达意。

Let's hack。

前导知识

•熟悉 Tableau 基本概念和界面

•清晰维度和度量

•了解图表(Chart)背后的数据透视表(Pivot Table)

•了解基本的公式

•了解基本的表格计算

•(optional)了解分桶变量

•(optional)了解座标系、函数、曲线参数化等

第一个图

先来尝试画一个抛物线。其实原理比较简单:曲线只有一个自由度,所以可以用一个参数刻画,通常记为 t。比如下面的抛物线可以参数化为:

•x = t - 0.5 (移动到中心位置)

•y = x^2

以下均约定 t in [0,1]。t=0 就是曲线开始的位置。t=1 就是曲线结束的位置。

974a097312f61b4e1971b74b26d5fb25.png

所以聪明的读者已经想到,做这个图最简单的方法,就是输入数据集,里面包含所有的 t 值即可。这里有几点不方便:

•当我们寻求的颗粒度(granularity)更细的时候,输入数据很大

•颗粒度由输入数据决定,不能灵活调整

数据分桶

那么第一个 trick 就来了。假设我们需要 t in [0, 1],以 0.01 为步长,总共打 101 个点。为了达成这个目标,我们的输入数据只需要两个点,即下面这个我叫做  Universal 的表格。

d98284346e8f2572d4ea88cb34751a4f.png

这两个数据点,一个的 value 为 0 ,一个 value 为 1,分别代表了起点和终点。为了得到中间的各个点,我们只需要创建分桶(bin)即可。比如下面以 0.001 为步长,则在 0 和 1 之间创建了 1000 个区间(总共 1001)个点。

8097b52fd1e7dceec17b550f16135751.png

这个新的变量,我们叫做 steps,以它作为维度,则相当于按照步长生成了所有参数化的点位。再根据参数化公式,把曲线计算出来即可。预想中,steps 已经包含了我们需要的 1001 个点,那直接用 steps 作上述公式中的 t,是否就可以搞定?

这个尝试失败。原因是分桶变量不能直接用于计算。这个是 Tableau 内部实现的限制,我们只能想办法去绕过。

4293a9f4150d84d2a0b5af200c3954e4.png

既然分桶变量可以作为维度(dimension),那当分组(partition)确定后,这些生成的中间数据点,就会默认获得一个顺序。这个顺序,可以用内建的 INDEX() 函数来取得,取值分别是 1..1001,这样推导出 t 也就很容易了:

•t = (INDEX()-1)/1000

接着可以用 t 计算出 x 和 y,感觉一个曲线就完成了。

接着遇到一个新的问题,这时候的图如下,视觉上看只有一个点:

173a583db5de219adcd8a4f98b2756e6.png

这是因为 x 和 y 的计算,并没有按照我们的刚才创建的 steps 进行。默认的计算方式如下图,是按照表格进行。更准确地说,在当前视图下,对应的数据透视表,没有维度,所有数据点都聚合到一个组里,当然在组里的下标就是 1。按照我们的公式 t = 1 / 1000 - 0.5 = 0.499,这样就得到了上图中的这一个点。修复方法也很简单:

2473f50a694ae7d2ee9f6a51c6252401.png

图:默认的计算方法是 Table (across) ,换成 steps

这样就可以得到开头的那个抛物线了:

974a097312f61b4e1971b74b26d5fb25.png

缺省数据

如果你是跟随上面的步骤做到这里,有可能还不会得到抛物线,大概率屏幕上是两个点。这是因为我们输入的数据只有两个点。虽然做了分桶,但是中间的桶都没有数据点存在,于是 Tableau 把他们忽略了。

这个的解决方法,是先把 steps 拖到 columns 或 rows,通过下拉菜单,选择 “show missing values”。做了这个选择后,可以看到除了 0 和 1,中间的值也都出现了。

586d118cc05bee9fdceeef7194503dbe.png

这时候,再把 steps 拖到 details 备用,并且拖入 x 和 y 即可。

复杂曲线

通过上面的方法,作出下面这些图并不难:

ba63f8928c1c8467c2038b29a2b833b2.png

图:Sigmoid

06dfd70b85f547c94cfb47394ee66f82.png

图:心型曲线

原则上,只要找到参数化的公式,就可以做出任何曲线。这在 Wolfram Alpha 上面可以搜到一大堆。有一些公式使用的是极座标,需要自己转化成直角座标。

88a4f51594933b25f69d3e919fb5750a.png

曲线路径

这里有一个常见问题。比如,明明画的是曲线,为什么 Tableau 会把中间的内容给填充上?

2d3d96a7d47a803decabd12f442ffb68.png

只需要把 steps,拖入 path (路径)即可。

06dfd70b85f547c94cfb47394ee66f82.png

感兴趣的读者可以把前面做成功的抛物线 x/y 对调一下,会发现类似的问题。通过设置路径可以解决。

7bd4e31b67d93d187a2d4a4000737439.png

图:抛物线,设置路径前

2ee6c9659424c9910f8f88a3ba6c9608.png

图:抛物线,设置路径后

这里的区别在于,设置路径前,Tableau 按照 x 和 y 的大小顺序来绘图,所以中间会有很多交叉。通过路径(path),可以指定绘图的顺序。

曲线簇 - I

做到这里,还没有太多艺术的感觉。毕竟一个曲线是单调的。如果能够系统地生成一簇曲线,并且通过规则,来改变他们的视觉效果,那就有点艺术的感觉了。比如下面这个哑铃图:

7b01f341d93734771b6a77205dfe3110.png

本质上,这是一簇曲线叠加在一起的效果。配置如下:

•Steps: 步长 0.0001 (10,000 个点)

•bunch = int(INDEX() / 1000)

•index = INDEX() % 1000

•t = [index] / 1000 * 12 - 6

•x = [t]

•y = 1 / (1+EXP(-[t])) -0.5

•y2 = [y] * ([bunch]/10 - 0.45)

原理和单条曲线是类似的。我们这里通过 steps 生成了 1 万个 数据点,总共有 10 条曲线,每条曲线 1000 个点。所以通过整除和余数计算,我们得到 bunch 和 index 两个量。bunch 是 0..9 之间,代表了曲线簇中的位置;index 是 0..1000 之间,代表了在一条曲线内部的顺序。

于是我们得到了一个通用的套路:

•通过 index 计算出单条曲线自身的形状

•通过 bunch 对不同的曲线进行系统化的变换。

比如在上例中,y 其实是代表了单条曲线参数化的结果,高亮的 y2 则是对 y 做一个拉伸变换,于是不同高度的 Sigmoid 就生成了。

为了看得更清楚一些,可以把 bunch 拖到 pages 里面,并显示历史轨迹。如下清晰可见各条曲线。

4f4ed5662a6d2ad4858907fe413b0c5f.png

如果我们不希望使用填充的效果,也不想用 pages 来做动画,则可以把 steps 拉到路径中,得到这个曲线的真身,如下图:

1b15bda098cfd424cf9d38a2b40be98b.png

虽然各条曲线清晰可见,但这个视觉比较差强人意。原因是我们通过整除和余数来模拟不同曲线。但在 Tableau 看来,自始至终只有一条曲线,所以前后两个曲线(bunch)之间,产生了图中锯齿状的连结。

曲线簇 - II

恭喜你,如果看到这里,很快就可以做出题图的效果了。不过,最接近山顶这段路,会非常陡峭。请耐心阅读和思考。

841557c1dd2d0a1407a362e80b16ac2f.png

法 1 的问题在于,只有一条曲线。所以自然的解法就是尝试多条曲线。在 Tableau 里面,我们可以加入一个新的维度,使得每个组代表一条曲线(bunch)。组内则沿用 steps 画出基本路径,组外通过 bunch 的参数,进行平面变换。这个思路很简单,但实际操作中,会遇到的问题是:

•输入数据中,如何高效地生成 “簇”。这里会使用到叉乘,在数据库领域又叫做 cross join。

•如何生成可用于计算的 bunch?这里要注意的问题是,因为其他的变量,如 t、x、y 是基于 table calculation 导出的,所以他们的计算是针对内建的 pivot table 进行。如果最终的公式中要使用 bunch,那么 bunch 自己也必须是一个 table calculation。输入数据中的量,可以作为计算 bunch 使用的依赖数据,但 bunch 自己需要是依靠 table calculation 生成。

•当最终的 x、y 包含基础图形的曲线(依赖 t),又同时包含 bunch 指导的变换时,就产生了两个依赖的 table calculation,他们需要使用不同的 scope 来计算。这是整个实现比较复杂地方,读者可以进一步搜寻 Nested Table Calculation。

那么下面我们一步一步来实现。

前面我们用到了 Universal 的这个表,只有两个数据点,通过分桶来生成中间点(Densification)。现在为了生成系列曲线,我们需要另一张表,叫做 Repeat。Repeat 的作用,顾名思义,就是重复的意思。我们想重复多少次,就在 Repeat 里面添加多少行。

35b6bfcfd37a614941018c87811626aa.png

图:Repeat。ID 默认都写 0,与 Universal 表中的 ID 是一样。这个域后续用于叉乘。Layer 则是对重复的编号,不一定要是连续的整数,只要能排序就可以。推荐使用连续整数,方便后续操作。

想要重复多少次,Repeat 表就包含多少行。推荐这个表里的行数多一些,在 Tableau 输入数据的时候过滤即可,这样不用重复生成数据集。熟悉命令行的同学,可以通过下面的代码片段,自动生成 1-100 之间的整数。

d1bbddfa8c9e12ca8a983138ad9af2ca.png

图:命令行工具

现在我们把 Universal 和 Repeat 给 JOIN 起来,选择 ID 列作为条件。注意,两个表的 ID 都是常数,所以这里本质上进行了一个叉乘(cross product)。形象一点地说,就是左边表的每一行,都会匹配右边表的每一行。在我们的案例中,Universal 有两个点,Repeat 100 次,那么叉乘的结果就是 200 行。

d9cbeeba40b88d623113989e4178a561.png

可以在数据源的位置进行过滤,进行稍微少量的重复。

e1ae466f93aa6eb5750beddeebecd4a0.png

我们重复前面单条曲线的操作,并把 Layer 加入作为维度。得到下图:

4e5bc8a41da8474176a44f38e07da401.png

这时候,已经有多条曲线了,但他们是重叠的。我们稍微改下 y 的公式:

7a1607a31eb2af7935013699d66ced5e.png

这是一个简单的平移操作。

47edebf66f293cc91cbfd4b6313b72c7.png

这就是多条曲线的原理。这里的 [bunch] 在实现上并不是很直接。我们一步一步来看。

首先需要这个 db_index。通过 RUNNING_SUM,我们可以得到一个 table calculation,而它的结果,恰好就是该行在数据源中的行号。

681d919873b9cb2234128223e8558f5f.png

那这个 db_index ,在视图中具体的值是什么呢?这里有点烧脑的细节问题。我们先打一张表,用 Layer 和 steps 作为维度,来看 db_index 的值。

30230f0b00ff72b3b10743011a7fe24e.png

图:起头的部分

3e4f5dcbe209c8eb454988b8ed55d852.png

图:结尾的部分

大家可以看到,db_index 只在整数的时候变化一下,中间全部都是沿用前值。这是因为, steps 是通过分桶功能生成的,中间的这些数据点并不存在。所以,0 的 db_index 为 1,3, 5,7, … 而 1 的 db_index 为 2,4,6,8,… 其中, (1, 2) 行为 repeat 1,(3, 4) 行为 repeat 2,… 所以,[1, 2) 区间中的桶,全部采用了 1,在 2 的时候,突然变为 2,紧接着跳为 3,并持续到 [3, 4),…

上面有点绕口令,但我们举个例子就明白。如果直接用 db_index 去做平移,会是下图的效果:

396a44a6e62990208cb35e4e4dd092ae.png

右边这个竖线,就是最后跳跃那一下的结果。

修复只需要做一个简单的分支就可以了。

223c72ca341751d3579af9b52501ef82.png

这样得到的 bunch,就是非常干净的。大家可以用前面看 db_index 的方法,检查一下,在同一个 layer 里面的点,他们的 bunch 也是相同的值。同时,这个 bunch 是 table calculation,可以和前面的 t、x、y 等合起来使用。

如果做了这些步骤外,得到的是一个心,而不是多个心,那么还有一个细节需要注意,就是 Nested Table Calculation。

我们来看下面的公式:

7a1607a31eb2af7935013699d66ced5e.png

其中 r 和 t 来自单条曲线参数化的部分,它们应该 只沿着 steps 计算。而 bunch 是指定曲线簇中的下标,通过前面展示的 pivot table,我们知道它按照 table cross 计算出的值,已经是需要的了。所以,bunch 的计算方向,实际上是 。当 x 或 y 同时依赖这两部分的时候,子计算的方向就出现了不同。这是 Nested Table Calculation。

现在右键编辑 x 或者 y 的 table calculation,可以找到下面两个配置:

e4b9b1d3642954fdf3d59bc6dccd83b7.png

如图修改后,就得到了下面的一组心型。

47edebf66f293cc91cbfd4b6313b72c7.png

恭喜你,核心已经掌握,可以开始耍花了。

心型变种

我们的心型曲线是用极座标写的,所以改一下 r,可以对图形进行伸缩。比如  r = bunch * 0.1。具体的比例,大家可以自己试试。

4a96d524b6679ae93fb1d63dd696db05.png

除了伸缩之外,在 y 轴上做个平移。公式和结果如下。

5fe02e71ccfa1d0ee4ca0020e6b153ff.png

看起来有点呆板,可以反方向移动,这个效果还可以:

841557c1dd2d0a1407a362e80b16ac2f.png

除了平移,还可以旋转。需要使用到旋转矩阵:

0dceae32d889a31a79a7c1140ae29777.png

图:维基百科

旋转角度的定义和相关公式如下:

be4301de62bf0fc56e4bd285ddccb318.png

这个版本中,我们只保留 4 个心型,每个旋转 90 度。结果图案有点窗花的感觉。

00c6334b50519b4de42859ba694de632.png

为了让大家看清楚一些旋转的是什么,我们高亮其中一个。

dabe553516b883659df9fa093faa6134.png

在这个公式里面,原点 (0, 0) 是两半心交汇的地方。旋转矩阵是按照原点进行旋转,所以得到了上面的图案。

我们可以看到,心的尖峰在大概 y=-4 的位置。所以只需要稍作改动,把单元图形上移 4,就可以形成围绕尖峰旋转的图案。

公式和结果如下:

d7ab81844d32c57aebefdbfbcccf4c27.png

玩玩心跳

最后来一个综合练习。用 Layer 做 pages,然后用 bunch 生成周期性的伸缩尺度,再回到数据源,把之前过滤掉的 Layer 放回来。就得到了下图(可能需要点击 gif 播放)。

66a9fe20a7c1b4903ad8983ce3f080e2.gif

感兴趣的同学也可以到 Tableau Public 上,下载这个 workbook。在网页上没有加速播放的选项,所以看起来有点迟钝。

c219eff27be9de447fcc670c826cbae4.png

https://public.tableau.com/shared/397PN9YHZ?:display_count=y&:origin=viz_share_link

总结

本篇探索 Tableau 中的数学驱动艺术(Math Driven Graphics),大体上分成两步:

•对单条曲线通过参数刻画

•根据曲线在数据集中的座标,进行二维变化。除了平移和旋转外,还可以根据曲线簇的位置来着色,或者形成动画。

其中的知识点包括:

•使用分桶变量将数据密化(densification)

•分桶变量需要明确指定 show missing values

•查找曲线参数化的公式

•通过分桶变量绘制基础曲线

•极座标和直角座标的转换

•使用 Path 模块,按顺序绘制曲线

•使用 running sum,生成按数据源顺序走动的下标,得到 bunch

•按照 bunch,对曲线进行变换

•设置正确的 table calculation 方向(along XXX for each XXX)

虽然本篇的技巧在实际 BI 中不会用到,但所涉及的知识点,对于充分理解 Tableau calculation 的内在逻辑,是非常有帮助的。

遇到疑难的朋友,欢迎到读者群提问。

数据科学 | 数字广告 | 未来主义

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值