打开网易新闻 查看精彩图片

200多个方法,15个核心算子,压缩比超过13:1。这不是某个程序员在炫技,是UC Berkeley的研究团队分析了100万个Jupyter笔记本后,给pandas做的一次"解剖手术"。

他们想知道:当你调用df.pivot()或df.melt()时,底层到底在发生什么?这些看起来毫不相关的API,有没有可能共享同一个"基因"?

一个没人回答过的问题:DataFrame到底是什么

一个没人回答过的问题:DataFrame到底是什么

听起来很基础,但直到2020年,学术界才有人给出正式定义。Petersohn等人在论文里把DataFrame写成一个四元组:(A, R, C, D)。A是数据数组,R是行标签,C是列标签,D是每列的数据类型域。

这个定义比"一张表"精确得多。它捕捉了DataFrame跟SQL表的本质区别:行和列都有序、都有标签、完全对称。你可以转置,可以把数据值提升为列标签,这些操作在关系数据库里根本不存在。

研究团队分析了100万个真实使用的notebook,发现85%以上的pandas操作都能被15个算子组合表达。fillna、isnull、str.upper、cummax——全是MAP的特例。sort_values、set_index、reset_index、merge、groupby、pivot——各自对应代数里的一个原语。

15个算子从哪来:SQL的遗产与DataFrame的独创

15个算子从哪来:SQL的遗产与DataFrame的独创

这15个算子可以分成三类。前9个直接来自关系代数:PROJECTION(选列)、SELECTION(选行)、RENAME(重命名)、JOIN(连接)、GROUPBY(分组)、AGGREGATE(聚合)、UNION(并集)、DIFFERENCE(差集)、CARTESIAN(笛卡尔积)。

WINDOW算子来自SQL的窗口函数扩展。真正有意思的是最后四个:TRANSPOSE(转置)、MAP(映射)、TOLABELS(值转标签)、FROMLABELS(标签转值)。

打开网易新闻 查看精彩图片

这四个算子是DataFrame独有的"超能力"。它们的存在,完全是因为DataFrame把行和列当作对称的一等公民。关系数据库里,元数据是元数据,数据是数据,井水不犯河水。但在pandas里,今天还是数据的东西,明天可能就是索引——这种流动性让数据分析变得灵活,也让优化变得困难。

MAP算子尤其值得多看一眼。它接收一个函数,独立作用于DataFrame的每个元素。听起来简单,但pandas里200多个方法中,大量都是MAP的"变装":apply、applymap、map、transform——本质上是同一个操作在不同上下文里的包装。

作者的不满足:15个还能不能再压缩

作者的不满足:15个还能不能再压缩

论文作者读到这儿时,盯上了那9个关系代数算子。PROJECTION、RENAME、GROUPBY、JOIN——这些感觉有亲缘关系。在数学的另一个分支里,有人已经研究过这类结构:范畴论(Category Theory)。

范畴论研究的是"结构之间的结构"。它的核心观察是:很多看起来不同的数学对象,共享相同的变换模式。比如集合与函数、向量空间与线性映射、数据类型与程序——都可以放进同一个框架。

作者发现,DataFrame的算子可以重新表述为范畴论中的"透镜"(Lens)和"光学器件"(Optics)。这是一种描述"部分更新"的数学工具:你有一个大图景,但只修改其中一小部分,同时保持与整体的联系。

用透镜重新建模后,15个算子开始显露出更深的共性。SELECTION和PROJECTION都是"限制"(Restriction)的实例。JOIN和GROUPBY都涉及"跨结构的重索引"。TRANSPOSE和TOLABELS/FROMLABELS则是"索引与数据的角色互换"——这在范畴论里对应着特定的伴随函子(Adjunction)。

从200到15再到更少:压缩的价值在哪

从200到15再到更少:压缩的价值在哪

这种压缩不是为了数学洁癖。Modin团队做这件事的原始动机很实际:他们要做pandas的分布式替代品。如果每个API都是手写实现,200个方法乘以分布式环境的复杂度,工程上完全不可行。

打开网易新闻 查看精彩图片

但如果有15个原语,每个原语实现一次分布式逻辑,其余200个方法自动继承——复杂度就从O(n)降到O(1)。实际上,Modin正是这样实现的:核心引擎处理15个算子,上层API保持与pandas完全兼容。

作者进一步追问:如果15个算子还有更深的统一性,能不能用更少的原语构建整个系统?范畴论给出的答案是肯定的。透镜组合(Lens Composition)提供了一种"搭积木"的方式:复杂操作由简单操作组合而成,每个简单操作的正确性可以独立验证。

这有点像编程语言里的"图灵完备":你用很少的原语(比如lambda演算里的函数抽象与应用),就能表达所有可计算函数。DataFrame的"代数完备"意味着:找到那组最小原语,你就能重建整个分析生态。

一个还在进行中的实验

一个还在进行中的实验

作者正在用自己的DataFrame库验证这个思路。范畴论的好处是"显然正确":如果操作符合透镜的数学定律,组合后的行为就是可预测的。坏处是"抽象税":你需要学习一套新的概念工具,才能理解为什么transpose和melt其实是同一枚硬币的两面。

目前的结果是乐观的。MAP、SELECTION、PROJECTION已经用透镜实现,性能与手写优化版本持平。JOIN和GROUPBY的透镜化实现正在调试——这两个算子涉及数据重分布,是分布式场景下的性能瓶颈。

作者提到一个有趣的观察:当用范畴论语义描述DataFrame时,pandas里一些"历史遗留"的设计决策突然有了解释。比如为什么index可以被赋值修改,为什么MultiIndex的层级关系如此别扭——这些都可以追溯到"索引作为数据"与"索引作为元数据"的张力。

如果这套框架最终跑通,我们可能会看到新一代DataFrame库的诞生:API可以像pandas一样友好,底层却像SQL优化器一样可分析、可优化。数学家提供的不是更快的代码,而是更清晰的边界——知道什么该做,什么代价太高,什么组合是安全的。

你现在写df.groupby()的时候,会好奇它底层对应15个算子中的哪一个吗?