最近,我做了许多有关数据编程未来发展方面的探索。在日常工作中,我使用Python 的 pandas 和 R 的 dplyr。但我注意到了一些非常有趣的现象。最近我从事的一个项目需要进行大规模的散点图可视化,最多需要在浏览器上绘制10亿个点。在实现这个功能时,我意识到一些问题:

1. 在过去的几十年内,计算机的速度大幅提升;

2. 数据分析使用的语言开始掉队了;

3. 新的数据格式的出现,导致 Python、R和 JavaScript 之间的区别不是那么重要了;

4. 原本用于前端的JavaScript语言开始越来越多地用在后端,处理Python和R所从事的数据任务;

5. 这些趋势的确有点奇怪,但也许不是坏事。

我尝试过使用一种二进制的序列化格式代替JSON,我发现:

随着webgpu和新的二进制序列化格式(如Arrow)的出现,geojson蜗牛般的速度越来越难以忍受。越来越多的R和Python库变成了JS或WASM的封装。就像 2000 年前后它们封装 Java 一样,这个趋势很奇怪。

这里,我主要谈谈Python和R,因为这两种语言在数据编程领域中占据绝对优势。(“数据编程”指的是数据科学;由于我并不是数据科学家,我不确定用这个词来描述我的工作是否准确。)一些经济学界的老古董们依然在使用Stata,而激进的人使用Julia,但如果你想从事数据工作,那必然会用到Python或R。 在我们的工作中,数据处理程序最大的问题就在于,它们在CPU上运行,而且绝大多数仅能使用一个核心。因此速度永远是最头疼的问题。2011年,我第一次尝试Python,当时为了寻找GIL(全局解释器锁)的解决方案而抓狂,由于GIL的存在,程序无法在多核心上运行,甚至连多线程都不行。最近情况好了一些,至少线性代数方面的程序可以使用计算机的全部资源了。

JS/HTML是UI的底层语言,也是Python和R的底层语言

所有程序的图形界面都不约而同地转向了Web。如果晚几年才开始从事现在的工作,我甚至都不会意识到曾经还存在另一种解决方案。我从未真正使用过R的tcl/tk界面,但我知道它们存在。大约在2008年,JB Michel编写的第一版Google Ngram浏览器就是使用某个Python库编写的。当时这种用法非常正常。但在过去十年,凡是需要编写用户界面元素,比如“按钮”或“鼠标滑过事件”,那么毫无疑问,最容易的做法就是使用HTML的概念,而不是使用操作系统本身的概念。Martin Camacho在编写第一版Bookworm UI的时候就意识到需要一款JavaScript的图表库。现在,JavaScript的集成在数据编程领域越来越紧密了。我的许多同事和学生,都采用HTML/JS把他们的R包装成了漂亮的应用,给Google colab notebook添加了文本框、滑块等装饰,或者将R和Python代码发布到在线图书。

Jupyter notebook和RStudio IDE自身也在转变:它们只不过是一堆由看不见的JavaScript连在一起的Python代码。同样,这些平台或多或少都颠覆了过去的模型。我刚开始学习R时,还需要把代码从文本编辑器中复制粘贴到core R GUI中;我还尝试过emacs的ESS模式。但在今天,如果你需要反复检查某个数据框中的随机样本,重复运行某段代码,或者检查正则表达式是否能准确清理数据,你会选择notebook界面,就算你最终需要把代码打包成一个库,也会在开发中选择notebook。

而可视化方面也逐渐被JavaScript渗透。包括我在内的许多人都认为,在可视化pandas的数据框时,Altair要比matplotlib容易得多;而对于希望实现鼠标悬停提示功能的学生们,我也不再推荐ggplotly。没错,ggplot和matplotlib依然是在发表论文时的首选,但一旦涉及到Web上的交互式、响应式图表,我们希望图表能实现同样的功能。就像notebook界面中HTML的选择菜单和按钮担当的角色一样,在图表界面中,JS的图表库担当了此角色。

笔记本的GPU界面依然有待研究

首先声明,这一节的观点不保证完全正确。对于这些内容我并不是专家。但是这个领域的确不稳定,我相信有人和我有同样的经历。

近年来,GPU开始取代CPU。R/Python在GPU方面的接口很弱。Numba某种程度上可以使用;我也一直在研究gnumpy;我没有在R中特意使用过GPU,虽然有可能无意中用到过。在Python和R中,最简单的使用GPU的方法就是使用TensorFlow或Torch,即使不需要神经网络也可以。例如,在训练UMAP模型时,我会选择神经网络接口,尽管我更倾向于CPU接口。

大多数接口都依赖于CUDA来访问GPU。(我不确定的就是这里。)如果你要在这些平台上编程,就需要启动一台云服务器运行模型。CUDA的配置很繁琐,而且有可能你的机器上没有GPU。如果你想在云中运行一切也没问题,Google可以免费提供TPU。但仅仅是在几百万行数据上执行group-by、apply或summarize,使用TPU就是杀鸡用牛刀了。再说,尽管云计算比自己的笔记本便宜,但是云存储非常昂贵。为了运行RateMyProfessor的数据框,我每年都需要向Digital Ocean支付大约100美元;而为了保存HathiTrust所需的几个T的数据,我只能选择学校的集群和家里的一块12TB硬盘,否则就付不起账单了。

即使没有GPU,JavaScript也很快

我第一次在JavaScript中绘制图表时就被震惊了。ggplot仅仅绘制几千个点就要等很久,我早已习惯了。geopandas中的多边形操作很慢很昂贵,我也习惯了。而且我习惯了一边喝茶,一边等待加载geojson文件。

然而使用JavaScript,利用三角形生成随机多边形,然后绘制几百万个点,几乎一眨眼的功夫就能完成;接着甚至可以用regl实现动画,实现平滑缩放。例如,下面这个例子中,每张选票需要绘制5个点,你可以随意加载每个州。

随着我制作的这种可视化越来越多,我终于发现了原因。Apache Arrow能够暴露非常底层的数据模型,鼓励你从数据定义和底层的类型两方面进行思考。我在使用R的numpy时就习惯了这种思维范式,但在R中只做过一点点。但在现代JS中,二进制数组缓冲区是语言内置的。刚开始使用JS的时候,我以为它会很慢,但与其他动态强类型语言相比,Web开发者对于速度有更高的要求。Chrome内置的性能测试工具非常强大,而且Google在提高JS运行速度方面下了很大力气,因为他们的收益就来自于更好的Web体验。当然,许多网站依然很慢,因为他们使用了大量的React和没有任何用处的代码;而且DOM也确实很慢。但JavaScript本身非常快。

在刚开始教数字化人性课程时,我最不喜欢的一项工作就是帮助学生安装本地的Java环境,以运行主题模型最好的实现——Mallet。现在,我们通常使用gensim中更慢的实现,或者结构化主题模型等。但负责维护Mallet的David Mimno却说,JavaScript非常快。

尽管JavaScript作为语言的名声不太好,但从ES2015版本之后,使用JavaScript写程序的难度降低了很多。Map、集合、for...of...结构,所有这些语法都能正常使用(不会像我最初进行可视化时,由于单词统计程序的词汇表中包含了constructor一词,而不得不调试了好几个小时);而且还支持许多语法,如类、数组解构、箭头函数等,都比Python更好用。

JavaScript + WebGL非常快

既然JavaScript很快,那么WebGL就可以自由发挥了。想象一下,只需几毫秒就能绘制peano曲线中的两百万个点。你甚至可以重新生成每一帧。

而且WebGL与Apache Arrow一样都使用浮点数缓存,所以数据是从磁盘(或Web)直接复制到渲染器中,而不需要做JavaScript计算。这个操作很难,而且很容易出错。(我认为regl已经做了非常好的抽象,但我仍然会在本应创建一个缓冲区的时候,不小心为每一帧分配数千个缓冲区)。

至于在线制图方面,mapbox.gl或deck.gl中基于protobuffer的向量文件能够实现相似的功能。在处理制图数据时,一旦尝试过二进制数据的速度和压缩率,就很难再忍受基于JSON的格式的额外开销了。

WebGL的难度过大

我见识过了WebGL的速度。对于数组平滑、对每个点执行数值过滤、分组求和等任务,针对数据框中的每个元素并行执行关系代数操作,是完全可行的。

但除了在非常适合的情况下,否则我仍然不愿意使用WebGL,因为WebGL非常不擅长数值计算。除非非常有必要,否则我不会建议别人学习WebGL。属性缓冲区只能是浮点类型,因此你需要把所有整型转换成浮点类型才能传送数据。许多情况下,数据可以降低成半精度浮点,但双精度浮点数非常困难,以至于WebGL采用了一整套莫名其妙的结构,花费了巨大的代价来支持它。材质的支持在不同设备上的支持也不同(苹果的设备似乎有特殊的问题),所以据我所知,许多人都想出了各种退而求其次的方法。而一些数据编程必需的操作,如列表的索引查找、在数组上执行for循环等,是几乎做不到的。在WebGL中编写复杂的渲染器很有意思,但这样做就违背了整个系统的初衷。

WebGPU和WASM也许能改变一切

WASM和JavaScript虚拟机

最后这两个关键技术迟迟不见任何进展。Web Assembly(WASM)是一项优秀的技术。若干Rust的项目可以编译成WASM,在浏览器中实现更快的计算。(如果我晚几年学习一门新语言,那肯定是Rust,因为在编写webgl程序的过程中,我经常需要自己编写垃圾收集器,但作为一名使用高级语言的人,我学习的C语言还不够,并没有掌握足够的基础知识。)2000年前后,许多Python和R软件包都依赖于Java虚拟机;到了2010年,许多软件包开始依赖于C/C++。我认为,很快Python和R软件包就会开始依赖于JavaScript虚拟机。

WebGPU

最后一项技术一直没有任何进展,但可能是最重要的。WebGL正在逐渐死去,但大公司都在合力将WebGPU打造成在浏览器中使用GPU的下一代标准。它构建在已有设备的GPU接口上(如Vulkan和Metal),而这些已有接口都是我不愿意学习的。

WebGPU将会取代WebGL,在浏览器中快速渲染图形。但是,在WebGL中进行繁重计算的能力非常诱人,以至于一些激进的人已经开始这样做了。

我并没有仔细阅读过WebGPU的规格,但它的确支持采用更强壮的方式来处理任务。已经出现了一些使用WebGPU来处理线性代数的库(如BLAS)。可以想象,有了更多的数据类型支持,许多简单的group-by-filter-apply函数可以完全移到GPU上进行。

我于2004年开始学习R时,花费了许多时间尝试用SQL来保存在当时看来很多的数据——根据种族、学校、性别等分类的、几十年来的上百万条学生数据。现在已经完全可能将全部数据都放进GPU内存中进行处理了。一般而言,GPU内存(0.5~4GB)和通用内存(2~16GB)的差异并不是那么大,因此我们并不会发送过多的数据。数据分析和处理一般都可以并行化。

JS和WebGPU将通力合作

一旦这个组合可以成功运行,就会比Python/R更快、更方便,·而且在许多情况下不需要配置就可以使用。去年出现的Arquero库已经在Observable中实现了dplyr以及PandasAPI中最重要的部分,而且速度与python/R不相上下。如果使用集成更紧密的二进制文件,或使用不同的后端,它(或者类似的库)很容易成为所有学校数据科学课程的首选平台。即使做不到这一点,JavaScript在可视化速度(GPU集成)和界面(HTML5)方面的优势也意味着,我们可以将数据放在网站上进行探索。

一旦这些快速操作可以实现,解析CSV或JSON的额外开销,以及没有严格类型定义等缺点将会变得更明显。届时一定会出现一种更标准的二进制交换格式,就像现在的arrow、HDF5、ORC或profobuffer。

我们还会继续用R和Python吗?

使用R或Python的数据编程语言将会依赖于JavaScript。就像Python/R封装Altair、封装HTML点击事件,越来越多的包和模块将会在JS虚拟机上运行。人们也会关注有关V8引擎版本的问题。还会出现本应在任何地方都能运行的python代码,却无法在某些低端笔记本上运行的问题。

自2003年左右以来,我一直在从事某些编程工作,而且自2010年以来,我的大部分时间都在从事编程。在这期间内,我看到我们完全可以在开放式数据上编辑或运行数据分析。有些人只需要静态图表,而有些人想隐藏他们的数据。大多数人都不想调整设置。很多人都不太喜欢Javascript。但这是因为互联网创建于90年代,目的是共享学术作品,并允许读者对其进行编辑。大多数学术界和许多媒体都致力于单向信息流,而媒体喜欢追捧互联网,但看到如今这些变化,我感到特别兴奋,因为它们代表了一种可能性,也许Web真的能够兑现自己的诺言:用更简单的方式思考问题。