计算表:快捷的桌面计算表格

背景

Excel

Excel是事实上应用很广泛的桌面数据分析工具,超过许多专业的BI工具。

Excel具有简单易用、直观易懂等优势,非常适合没有程序设计能力和数学模型知识的普通业务人员。而且,特别地,Excel每一步动作后可以立即看到计算结果,从而再决定下一步动作,这种交互过程是业务人员分析数据的典型模式,不需要也不可能事先为分析目标建模。

但是,随着数据分析需求的复杂化,我们在使用Excel时也发现一些不足之处,通俗一些说,可以总结成四个与“多”相关的问题:

1、多行记录

Excel并没有明确的结构化记录概念,表格中每行对应一行记录,也就是单行记录。如果碰到多行记录,比如记录内容较多要占多行或有子记录时,编辑和运算操作都会非常繁琐。

2、多层表格

Excel提供了分组运算,但产生的有层次表格和初始表格并不一样,许多操作变得不可执行或执行方式不同,难以连续操作;分组还会带来上述的多行问题,涉及汇总值的公式在跨组复制时不能正确智能变迁而导致计算错误。

3、多表关联

Excel不算是基于关系代数的产品,对于数据分析中常见的多表关操作,Excel没有提供把两个表格之间的运算,只提供了Lookup等函数实现简单的单元格跨页引用,使用繁琐而且性能很差。

4、多值数据

显然Excel有集合的概念,可以针对区域内单元格做聚合运算,但Excel并没有显式的集合数据类型,无法存储集合数据和进行更丰富的集合运算。

我们会在后面的例子中具体阐述这些问题。

esCalc

针对Excel的不足,我们设计了新的数据处理模型来解决表格式数据的运算,并据此开发出新的电子表格软件,即esCalc。
事实上,esCalc 的数据与运算模型是从关系代数发展而来而不是基于Excel改进的,因而更接近于关系数据库。esCalc有明确的数据记录概念,并且提供了SQL中的绝大部分运算能力,如计算列、排序、过滤、分组、唯一值等,以及多表之间的连接、合并运算。由于采用了表格形式的交互操作界面,esCalc也可以理解成一种可视化的SQL计算器。

与大多数数据库前端软件采用字段名来引用数据项不同,esCalc继承了Excel的表格方式,使用单元格命名数据项和描述计算公式,并支持公式自动复制和智能变迁。这样将更为直观,有利于非技术人员掌握,而且可以方便地用于描述SQL不擅长的有序计算。

特别地,esCalc还在SQL基础上扩充了多层数据模型及相关的运算能力,这样可以支持有层次的主子表格,也使得分组后表格可以继续实施过滤、排序以及再分组和拆除分组等操作,涉及多层单元格的公式也能自动复制并正确变迁。

esCalc的设计目标是交互数据分析,功能涉及面不如Excel广泛,但在批量数据计算方面更为精细专业。esCalc没有打算也没有必要取代Excel,而是定位于和Excel配合使用,esCalc可以读取xls文件进行分析处理,也可以将运算结果写成xls格式供Excel进一步处理。不过,需要指出的是,esCalc并不是Excel上的插件,而是一个独立的应用程序。

在数据处理方面,与Excel相比,esCalc主要的功能缺失是没有提供VBA脚本和数据透视表。我们认为VBA难度较大,一般业务人员掌握不了;许多VBA脚本也是为了弥补Excel中不方便的功能,而esCalc已经提供了这些功能,对VBA脚本的需求程度就弱了许多。对于有程序设计能力的人员,实在需要脚本时,还可以使用esCalc的同族产品esProc,其中提供了比VBA强大得多的脚本计算能力。至于数据透视表,Excel已经做得很好,并没有明显的改进余地,esCalc能够生成xls格式的文件为Excel的透视表提供数据源就可以了。

下面通过一些例子详细解释Excel在数据分析和计算方面的上述几点不足,以及esCalc的解决方案。

结构

公式复制

Excel使用表格的行来表示记录,可以针对行执行过滤、排序等的操作,特别地,可以在行上增加计算列(由其它字段计算出来的新值),这就会面临公式复制的问题。

在记录上增加计算列会涉及所有记录(行),而由于Excel没有显式的记录概念,在某一行填写的计算公式需要手动复制到其它行上去。Excel很巧妙地采用了拖拽复制的方法,这在单行记录(即每条记录对应一行)时非常方便。

但有时候我们会面临复杂一些的情况,一条记录在表格中对应了不止一行,比如记录内容较多需要分两行填入,或者每条记录还有更下层的子记录(订单有订单明细)。这时候需要复制公式的单元格不再是连续的一片,无法采用拖拽复制的方法,手动单独复制的繁琐程度则可想而知。

esCalc则不同,一方面保留了Excel用单元格式命名数据项的直观方式,另一方面还引入了显式的记录概念,相当于整合了Excel和数据库前端软件的优点。在单元格填写的公式将被自动复制到同位格(即其它记录的该字段对应单元格)中,不需要专门的复制动作,多行记录和主子记录的情况都能正确地找到同位格。

如下面的订单数据表:

esCalc_homeintro_structure_1

在F1中,用公式={E2}.sum()计算出了订单总金额。在F2中输入公式=round(E2/F1,4),用当前订单的金额除以F1中的总金额,计算每笔订单的金额占比。同时,设定F2的显示格式为#0.00%,用百分比的格式显示。F2中的公式输入后,结果如下:

esCalc_homeintro_structure_2

查看F2的各个同位格可以发现,F2中输入公式后,在F3~F11这些同位格中都同时完成了计算。esCalc中单元格中的公式和显示格式能够正确地自动复制到了各个同位格中。

esCalc在处理多行记录时,可以和单行记录时同样方便地使用公式,如:

esCalc_homeintro_structure_3

表中存储了各类蔬菜水果的单价及购买数量,在D3中,用公式=floor(D2*B3,2)计算出购买Pineapple的价格,输入公式后,结果如下:

esCalc_homeintro_structure_4

公式输入后即被复制到了每种商品的对应格中,即D3的同位格中,分别计算出各类商品的价格。

数据编辑

Excel对于非单行记录的操作麻烦还体现在数据编辑中。

Excel没有把记录当作一个整体,插入、删除以及移动记录时都是针对表格的行列操作。这在单行记录时问题还不太大,而是多行记录和主子记录时,对行方面的操作就有些繁琐,而针对列方向插入和删除字段就几乎无法实施了。

由于Excel难以处理非单行记录,一般会在生成原始数据时就避开不处理,因此Excel用户会较少碰到这种情况,但实际业务中多层数据和数据项很多是客观存在的,并不是罕见现象。另外,我们在后面还会看到,分组运算后还会大量产生多层表格。

即使是单行记录,如果涉及到跨行计算表达式(如计算比上期和累积值)时,Excel在插入删除行时会导致公式的变迁错误,用粘贴实现记录移动也会出现这个问题,都需要手动修改或重新拖拽复制。而且,由于Excel不强调记录概念,没有设计针对记录的热键动作,这些操作也还是较为不便。

对于有记录概念的esCalc来讲,上述操作都很容易,而且提供了方便的热键以便快捷操作。记录(包括其子成员)可以一键整体插入删除和移动,变动后有跨行运算的计算公式仍然能保持正确,而列方向插入删除字段相当于改变数据结构,esCalc针对单元格的这类操作会自动复制到所有同位格上。

如下面的员工资料表格:

esCalc_homeintro_structure_5

其中,D3中的公式为=age(C3),它和同位格中的公式用来计算每位员工的年龄。同时,C2中用公式={B3}.count()计算出了各部门的员工总数,D2中用公式=round({D3}.avg(),1)计算出各部门员工的平均年龄。下面,需要删除员工数据中第1列中重复的部门字段,但是又不想影响部门行中已有的数据。为此,选中B3格,按Ctrl+Backspace删除A3及其同位格后,结果如下:

esCalc_homeintro_structure_6

所有同位行的结构变动会同时执行。此时C3及其同位格中的公式也会随着记录结构的改变而自动调整。如此时C3中的公式自动变为=age(B3)。

在esCalc中,也可以只调整汇总行的结构,如在上面的表格中,删除部门汇总行中空白的第2列。为此,可以选择C2格,按Ctrl+Backspace删除A2及其同位格后,结果如下:

esCalc_homeintro_structure_7

又如下面的会员管理表:

esCalc_homeintro_structure_8

在表格中,记录了每个月新增以及退出的会员数。在D8中填入==D7+B8-C8,根据上月会员数及本月的变化情况计算出本月会员总数。在这里使用两个等号开始的联动计算表达式,单元格中的数据会根据表格的变化自动调整。

表格中丢失了4,5月份的数据,在其中插入记录并输入数据后,表格如下:

esCalc_homeintro_structure_9

由于在esCalc中新数据同样存储在同位行中,在数据插入后,D列的计算仍然可以正确计算,会员统计会自动根据新的数据更新。如果是根据单元格的位置而不是单元格结构来处理公式的变化,则会在新加入行时产生错误公式。

非联动计算

Excel的计算单元格都是联动计算的,也就是说被引用格的值一旦发生变化,计算格会重新计算出新值,如果被引用格被删除了,计算格则会出现错误信息。

但在数据分析处理时更常见的情况是这样:我们获得计算列的值后,对原始被引用字段的值就不再关心而可以删除,或者改变原值后再用于别的计算列和上次计算值进行比较,这时上次计算值就不能跟随改变。比如原始数据中有人员的生日,而我们在后续分析过程中只需要某个时刻的年龄,这样计算出年龄后就可以把生日删除了,或者改变当前时刻数值重新计算另一时刻的年龄,但Excel却不容易实现这个需求。

esCalc 提供了联动和非联动两种方案,联动计算格和Excel一样,计算值会实时跟随被引用格值变化而变化;而非联动计算格则一旦计算完值后就和原始被引用格不再相关,原始格值改变或删除都不会影响这些非联动计算格的值。

事实上,在交互式数据分析过程中,非联动计算要远比联动计算更为常用。

如下面Alaska州的人口数据表:

esCalc_homeintro_structure_10

在C8及其同位格中,计算出了历次人口普查时Alaska州的人口增长率,如C8中的公式为=round((B8-B7)/B7,3),并设定了显示格式为#0.0%。现在,选择C2格,将数据按照人口增长率降序排序,结果如下:

esCalc_homeintro_structure_11

由于C列中的公式为单个等号开头的,C2及其同位格为非联动计算格,此时的格值会保持原值,而不会根据新的排序结果重新计算出错误的增长率数据。

分组

公式与变迁

前面已经提到过有主从关系的记录,更多的层次表格则是由分组运算造成的。

Excel的数据模型并不支持多层表格,虽然提供了分组运算,但被当成一种特殊动作,分组后的汇总层聚合运算要使用难以记忆的SUBTOTAL而不是平常使用SUM/COUNT等函数,否则将不能正确定位组内成员。

对于写在明细层单元格的计算公式,一方面如前所述,不能简单地用拖拽实现批量复制(被汇总层单元格分隔成若干不连续的片区),只能在跨组时手工复制;另一方面,如果其中涉及到了汇总层单元格或跨行的计算公式(如占比,比上期),按Excel的公式变迁规则,即使手工复制也无法保证正确变迁,还需要手工调整公式中被错误变迁的单元格。这些繁琐在组数较多的情况下完全无法接受。

对于单层表格,Excel提供了$符号可以引用汇总单元格,但多层表格就无能为力了。

esCalc则在数据模型上就支持多层表格,分组后的汇总层聚合运算仍然使用sum/count这些平常用到的函数。特别地,esCalc能区分单元格所在的层次,同时引用明细层(包括跨行)和汇总层单元格的计算公式在变迁时会被区别对待,复制到组内只变迁明细层单元格,跨组后才变迁汇总层单元格,用户只要直观地引用看到的单元格,无需用$符号自行区分(事实上,$符号只能区分一层也不够用),多个汇总层混合运算时都能正确变迁。

如在下面的网格中,计算每个月的平均温差:

esCalc_homeintro_group_1

esCalc中,相同类型的数据被存储在同位行中,当在E3中计算1月的平均温差时,其它各个月的对应同位格中,也会直接计算出各个月的温度差,不必复制公式,结果如下:

esCalc_homeintro_group_2

在上面的计算中,月份数据是子行,它们的主行是季度数据。子行中的计算不会影响到主行,同样,主行的数据也不会影响到子行,如在E2中填入公式={A3}.count()来计算各季度中的记录数,结果如下:

esCalc_homeintro_group_3

esCalc中的表达式可以根据结构关系变迁,而非按照单元格位置硬性变化,规则更为合理。

又如在气象数据表中计算降水量数据:

esCalc_homeintro_group_4

其中E2和E6计算出了本季度数据的平均降水量,如E2中的公式为={D3}.avg()。此时,需要计算各月降水量与本季度均值的差值,只需在E3中输入公式=D3-E2即可,结果如下:

esCalc_homeintro_group_5

在公式迁移时,会根据单元格所在的层次结构自动处理,如点击E6可以看到公式变迁为={D7}.avg(),即当季平均降水量,E8的公式变迁为=D8-E6,即本月降水量减去本季度汇总计算的平均值。可见在esCalc中,对于组内和组头的公式都可以正确迁移。

组后再运算

Excel把分组特殊化还体现在分组后的操作上。Excel分组后的表格不能再象普通单层表格那样随意地针对整个表格实施排序、过滤等操作。

比如我们希望将订单记录按销售员分组后按金额汇总排序以了解销售员的业绩排名,这就需要分组汇总后针对汇总值排序,在排序同时组内成员要一起跟着汇总值移动,这个操作在Excel中无法直接完成;再比如,我们要将金额在销售员汇总金额中占比小于1%的小订单删除掉再重新计算汇总,这需要在分组后计算占比,然后针对占比在所有组内同时执行过滤运算(这里还会用到前述的非联动计算),不支持多层表格运算的Excel也无法一次性完成这个动作,只能一个个分组分别处理。

esCalc中多层表格是常态,所有运算都能基于多层表格进行,完成上述运算没有什么难度。分组后针对汇总值排序,esCalc会将组成员连同汇总行一起移动,这里又再次体现了记录的概念(可以将一个分组连同其组成员看成一个条大记录);分组后针对明细行的过滤操作也会同时复制所有组,一次性完成组后过滤的功能。

如下面的订单统计表:

esCalc_homeintro_group_6

其中,F1中填入公式={E3}.sum()来计算所有订单的销售总额。F3中填入公式=round(E3/F1,4),用来计算各个订单的销售占比。

在esCalc中,分组计算后的表格是可以继续执行各类操作的,如执行过滤,将销售金额占比百分比小于1%的订单删除。选择F3执行过滤:

esCalc_homeintro_group_7

过滤后,结果如下:

esCalc_homeintro_group_8

层次编辑

Excel也没有在已分组表格的基础上插入和删除层次层次的功能,要改变现有分组结构要删除所有分组后重新再分组,这样我们汇总层已做的工作(计算过的单元格)就无法保留,甚至有时我们就是保留汇总层而不再需要明细层。

比如,在上例中删除小订单后的表格中需要再按订单中产品分组,查看每个销售员下这些非小订单中哪种产品的销售额更多,这需要在原有两层分组的表格中插入一层分组,汇总后再做组内排序;如果我们只关心这个分组汇总的结果,则需要把明细层数据都删除;这些操作在Excel中都不能直接完成,需要将中间结果复制出来到一个新表格继续操作,而由于分组导致的区域不连续,复制动作也不容易自动执行。

如在上面的表格再次按产品类型分组后,结果如下:

esCalc_homeintro_group_9

分组后的表格可以继续操作,如在上面的表格中,统计出出每位销售员的销售总额,在E2和E3中填入同样的表达式={E4}.sum(),结果如下:

esCalc_homeintro_group_10

F1,E2,E3这些格子中,填入的表达式相同,都是用来计算销售总额,但是由于在不同的层次以及不同的位置,也就计算出了不同的结果。

下面,选择E2执行降序排序,将数据按销售员的总销售额排序,结果如下:

esCalc_homeintro_group_11

在esCalc中,当分组行由于排序等操作移动时,分组中的子行也会随之一起移动。

esCalc实现这些操作则没有什么特殊性,层次是表格的一种属性,可以随意插入和删除,对行的动作会自动复制到所有的同位行(与同位格类似的概念)上,如删除明细行时会一次性删除掉所有明细行。

esCalc表格的数据模型包括层次结构,这样分组、拆组等动作就和过滤、排序一样是个普通运算,可以在同一表格内连续操作。用数值运算类比,在整数范围我们可以连续地执行加减乘运算,但不能随意做除法,因为其结果可能不再是整数,而如果我们将数值范围扩大到有理数,就可以把除法也加入到普通运算中,当然这时要重新定义加减乘运算在扩大范围后的规则。同样地,esCalc将表格模型扩展到多层结构,也要重新定义排序、过滤、计算列等操作(如要支持多层表达式的智能变迁),这样将使相关运算可以连续执行,数据交互分析得以持续地进行。

连接

连接是SQL中的重要运算。涉及到多个表的关联,如从代码获得属性(用产品代码获得产品产地、单价等),多个表格对齐(津贴表和考勤表按员工对齐)等,都需要用到连接运算。

Excel中使用Lookup函数族实现表间关联,功能相当于SQL中的左连接。SQL中还有内连接、右连接和全连接,其中内连接可以在左连接后再过滤实现,右连接是左连接的对称形式,改变关联方向即可,而全连接在Excel中则不能直接实现了。

Lookup函数最大的问题在于使用很繁琐,要指明关联列和范围以及获取列这好几项内容,每次只能获取一个关联列,要引用多列时就需要写多个同条件的Lookup,书写很麻烦,还会由于重复运算造成性能低下,Lookup函数还是一种遍历查找式方案,寻找关联数据的效率本身就非常低。

esCalc基于SQL模型发展,直接提供了包括内连接、左连接和全连接的完整运算,可以一次性获得关联表的多列,只要指定两表的关联单元格即可,操作要比Excel简单许多。比如将人员绩效表和考勤表连接起来,只要将设置好主格(即关联格),再复制考勤表中需要引用的单元格,用连接操作粘贴到绩效表即可完成。

如人员绩效表如下,其中A2及其同位格设置为了主格,存储员工编号:

esCalc_homeintro_join_1

员工考勤表如下,其中只存储了存在缺勤记录的员工,其中A2及其同位格为主格,同样存储着员工编号:

esCalc_homeintro_join_2

连接操作时,在第2个表考勤表中,选择B2,按Ctrl+C执行复制,然后在第1个表绩效表中,选择E2格,按Ctrl+Alt+J执行连接操作,选择左连接:

esCalc_homeintro_join_3

连接后,绩效表中结果如下:

esCalc_homeintro_join_4

esCalc的连接操作还支持多层表格。比如人员是按地区分组存储并记录考勤的,多层连接动作会先按分组对齐后再在组内寻找关联行,这样,即使不同地区下可能有同名人员也不会搞错,并且结果集仍然带有分组信息而不会打乱。

如值班表如下,其中主格为所在州及员工编号:

esCalc_homeintro_join_5

人员信息表如下,其中主格同样为所在州以及员工编号:

esCalc_homeintro_join_6

在人员信息表中,同时选择B3和C3,复制人员信息后,在值班表中选择C3,执行左连接,结果如下:

esCalc_homeintro_join_7

集合

集合是批量数据的基本概念。Excel可以对单元格集合进行汇总运算和关联运算(如Lookup),但Excel并没有集合数据类型,集合只在表达式计算的过程中存在,无法作为结果存储进单元格,也无法提供更丰富的集合运算,如集合的交并差以及查找定位等运算。

但是,集合是数据分析过程中很常见的数据类型。缺少集合会使许多分析运作很难甚至无法实施。这时候,在Excel中就常常必须求助于编写VBA代码来实现,难度很大而且非常麻烦。

esCalc有集合数据类型,并提供了丰富的集合运算函数及针对集合的操作,从而可以实施更复杂的数据分析过程。

列集合

下面的绩效表中列出每个员工在每月的绩效等级,现在需要找出连续三次获得过A的员工。

esCalc_homeintro_set_1

esCalc实现这个操作非常简单,只要针对任意数据行,比如第2行,执行过滤操作,过滤表达式写做[B2:M2].group@o().pselect(~(1)==”A” && ~.len()>=3)即可。[B2:M2]是第2行从B列到M列格值构成的集合,针对该集合做有序分组得到若干个绩效相同的连续组,再查找其中是否有绩效值为A且长度超过3的组,这里每一步都涉及到集合数据。

行集合

员工名册中有姓名重复的人,需要将这些多余的行删除,但不得改变原来次序。

esCalc_homeintro_set_2

同样地,针对数据行(仍用第2行)过滤,条件用{A2}.pos(A2)==#即可。{A2}表示由A2的同位格构成的集合,在该集合中寻找A2格值第一次出现的位置,如果和A2所在的位置不同,则说明是重复的多余行,可以删除。

扩展

希望将学生成绩表:

esCalc_homeintro_set_3

转置成:

esCalc_homeintro_set_4

这种将一行变成多行的动作可看成是分组运算的逆,Excel不借助VBA几乎无法实现这个目标,而在esCalc中只要几步操作即可完成:

在E列计算B/C/D列构成的集合,公式为E2=[B2:D2],E列存储的值即为集合。

esCalc_homeintro_set_5

针对E2执行扩展操作,将其变成多行。esCalc提供了针对集合的扩展操作。

esCalc_homeintro_set_6

在F列中填入科目名称,公式F2=[B2:D2]((#-1)%3+1)。从集合用序号中取出成员。

esCalc_homeintro_set_7

现在只要删除B/C/D列,调整E/F列次序并填上表头即可。