Skip to main content

数据分析模式

YeongHer

随着各行业对数据分析需求的不断增加,SQL 作为一种古老的数据查询语言,时常与其它编程语言相结合(例如:ORM、模板语言和各类可编程DSL 等),但SQL 始终以字符串的形式存在,弱化了编程属性,使得单元测试、重用、重构和版本管理变得困难,系统工程化也就存在盲区。又因为SQL 的正确性除了语法和基础逻辑的正确, 还依赖最终输出的数据的正确性,更增加系统工程化的复杂度。本文将介绍一种新的方法,用于降低数据分析系统的复杂度,提升数据分析类产品的质量。

1 编程语言发展史

汇编语言: 用一些容易理解和记忆的字母、单词来代替一个特定的指令。通过这种方法,人们很容易去阅读和理解。

面向过程编程语言: 以数学的方法解决复杂的领域问题,问题被看作一系列需要完成的任务,函数则用于完成这些任务,解决问题的焦点集中于函数。

面向对象编程语言: 通过将数据(状态)和数据操作(行为)封装在一起,作为一个相互依存的整体,用来对现实领域问题进行抽象,同时以封装、 继承和多态的基本编程方法,使得系统更灵活、更容易扩展。

C 语言使得编程接近了自然语言的表达,Java 则屏蔽了C/C++ 语言中复杂且容易出错的指针,同时通过JVM 实现了跨平台(目前,已经可以通过LLVM 实现跨平台), Erlang/Scala/Go 则使得并行编程变得便捷。纵观编程语言的发展历史,虽然问题域越来越复杂,但编程语言则变得简单,使得程序员只需要面对实际的问题域, 尽可能忽略底层技术。

然而,SQL 自1986 年被标准化以来,只是不断的增加新特性,从未产生较大的变革,一直以字符串的形式附属在编程语言下。当下各行业对数据的依赖越来越强, 数据分析的复杂度来越来越高。整个行业依然以编程的形式适应着问题域的变化,本质上也是在隔靴搔痒,未能从源头解决问题,未能将底层技术细节对数据分析师 / 数据工程师屏蔽。

2 SQL 的底层编程技术

关联关系: SQL 中表与表之间的关联关系是查询的基础,用于描述领域实体间的逻辑关系,也是SQL 编程中频繁使用,但关联关系往往在关系建模时就已经确定的。 在不同场景的数据查询时,关联关系不会发生变化,然而,每次查询时,需要重复指定关联关系。

Chasm & Fan Trap:断层和扇形陷阱是关系型数据库出现后就存在的反直觉的设计,也是数据查询时无法避免问题。 通常这类错误的处理方法是子查询,也就是不断的数据变形(Data Reshaping),使得统计维度一致后再进行连接。同时,也导致SQL 的结构变得复杂、可读性差。

不同数据库SQL 方言: 各大数据库厂商基于自身的理论和技术,创造了多种不同形式的SQL 语法,但SQL 查询的本质并没有发生变化, 只是为个性化特性增加了一些函数,或一些语法上小变化。虽然大都数企业,数据库选型不经常变化,有时,随着业务不断发展,不得不重新选型。

数据分析模式: 数据分析方法经过数年的迭代,沉淀了大量通用的分析方法,暂且定义为数据分析模式,例如:同环比分析、留存分析等分析方法。 这类抽象化分析方法,完全可以进行封装和重用,但由于SQL 总是以字符串的形式存在,限制了扩展性。

跨表计算: 复杂业务中,统计指标通常涉及多张表的字段,有时还会以不同聚合函数聚合后进行算术运算。这类计算方法会产生大量子查询和重复的表连接,有时, 通用的维度表会被反复连接多次。

上述SQL 的底层技术都属于抽象的编程方法,也是数据库工程必须要掌握的基础技能,类似C 语言中的指针,Java 编程中的线程等抽象的编程概念。编程语言经过数十年的迭代, 除了提升工程化能力,也降低了编程的门槛,使得程序员的学习成本越来越低。但SQL 随着业务的发展变得越来越复杂,容易出错,且开发效率低下。

数据分析对于数据库而言,只是一个业务场景,数据库除了满足分析需求,同时还要满足数据写入和更新的需求,这类需求与业务系统关联的非常紧密。然而, 数据分析面向的是抽象化的关系模型,相比数据的写入和更新而言,受业务逻辑的影响相对较小。因此,基于数据分析场景设计一门高级语言就变得有价值, 屏蔽上述底层技术变得有价值,使得数据分析时,无需关注底层技术细节,聚焦于自身业务。

3 数据分析模式

细分: 零售或电商中(其它领域也有类似的场景),商品和客户的数量会比较多,无法实现单体分析,通常会基于某类规则对商品或客户进行有限的分类, 然后观察分类的数据表现,分类规则和参考值灵活多变。

多维度灵活组合: 维度是数据分析中基本的概念,是观察数据表现的一种视角,例如:以地区/商品类为视角,观察每个地区的客户重复购买情况,或者叠加时间维度, 观察每个地区、每个品类及每个月的数据表现,实时使用时,维度和聚合指标的组合非常多,不同的使用角色对维度的关注程度也不一样。维度的定义可能是原生属性, 例如:性别、颜色等,也有可能是派生属性,例如:年龄段(Age Group)、价格带(Price Range)等。

增长率: 增长率主要是基于时间维度,观察聚合指标的变化情况,有时也会组合高层次维度。部分领域还会出现基于时间维度上更多的计算方法,例如: 对聚合指标的移动平均、累加等分析方法,这类方法与 增长率 较为相似,不再赘述。

跨维度聚合计算: 跨维度聚合计算主要出现在占比分析的场景,例如:计算每个省份销售额占全国销售额的比例,有时也会出现多层次占比分析,例如: 同时观察省份占比和城市占省份的比例。

上述分析模式为抽象的分析方法,相比SQL 中的分析方法还是具体的多,更贴近实际领域。在不同领域中基于上述方法,还可以衍生出很多不同的分析实践,例如: 留存分析、同环比分析、RFM、复购分析等。

基于上述抽象的数据分析方法封装相应的函数或表达式,将会大大降低数据分析师的日常工作量,只需要设计指标涉及的字段和聚合方式即可, 不再关注具体的技术细节,也不再关注统计结果的准确性。

4 分析模式的形式化定义

所谓形式化定义是指将一个概念、概念体系或数学结构以明确、准确、符号化的方式表达出来,使其具有严密的逻辑和数学基础。这种定义通常采用数学符号、逻辑推理、 公理系统等形式工具,以确保对概念的描述是无歧义、一致的。

形式化定义的目的在于使概念或理论体系更具精确性和操作性。它可以避免歧义、主观性和语言的模糊性,使得人们能够以更系统和精准的方式进行研究、推理和应用。

4.1 细分

购物频次
// 分类规则为:近 3 个月的订单数量大于 5,且总销售额大于 100 的客户为 "高频次" 消费客户,其它规则依次类推。

SEGMENT(
CASE
WHEN COUNT(orders.order_id) > 5
AND SUM(order_details.quantity * order_details.unit_price) > 100 THEN '高频次'
WHEN COUNT(orders.order_id) BETWEEN 2 AND 5
AND SUM(order_details.quantity * order_details.unit_price) > 100 THEN '中频次'
WHEN COUNT(orders.order_id) = 1
AND SUM(order_details.quantity * order_details.unit_price) > 100 THEN '低频次'
ELSE '流失'
END,
customers.customer_id,
orders.order_date = LAST_MONTHS(3)
)
近30天有购
// 分类规则为:最近 30 天的订单数量大于 0的客户即为有 "购客户"

SEGMENT(
CASE
WHEN COUNT_IF(orders.order_date = LAST_DAYS(30)) > 0 THEN '是' ELSE '否'
END,
customers.customer_id
)

上述为两个比较简单的分类定义,其中 SEGMENTLAST_DAYSCOUNT_IF 函数均为Agile Query 定义的高级分析型函数,函数执行时, 会自动分离出内部的聚合函数,并拆分成多个不同的子查询,再依据定义进行连接,最终SQL 执行的结果如下:

3.2 多维度灵活组合

查看每个品类下各供应商的牛奶销售额
SELECT
categories.category_name,
suppliers.company_name,
SUM_IF(
products.product_name = '牛奶',
order_details.quantity * order_details.unit_price
)
FROM "17195123558281476869"
ORDER BY SUM_IF(
products.product_name = '牛奶',
order_details.quantity * order_details.unit_price
) DESC

3.3 增长率

销售额月增长率
GROWTH_OF(
SUM(order_details.quantity * order_details.unit_price),
orders.order_date
)
销售量季增长率
GROWTH_OF(
SUM(order_details.quantity),
orders.order_date,
'quarterly',
1
)
销售量同比
GROWTH_OF(
SUM(order_details.quantity),
orders.order_date,
'monthly',
12
)

3.4 跨维度聚合计算

销售额品类占比
SUM(order_details.quantity * order_details.unit_price) /
GROUP_SUM(
order_details.quantity * order_details.unit_price,
categories.category_name
) * 100

5 FlatQL 的由来

FlatQL 的初衷是为了解决多表分析中不同表之间的维度与聚合函数可以自由组合的问题,从而使得用户可以像传统BI工具一样通过简单的拖拽操作自由组合进行分析。 然而,随着实际业务的深入,发现在业务分析中存在许多共性的分析方法。这可能包括常见的维度组合、特定的聚合函数应用以及一些标准的业务场景。

这种发现推动了FlatQL进一步发展,使其能够更好地满足业务的通用需求。通过引入一些内置的分析方法、预定义的维度与聚合函数组合,FlatQL可以更快速地适应业务场景, 提供更便捷的分析体验。这种演进的过程中,FlatQL旨在既保持用户灵活组合的能力,又提供一些预设的共性分析方法,使其更贴近实际业务需求,提高分析效率和准确性。

通过类似于上述的函数封装和表达式设计,Agile Query逐渐朝着更贴近数据分析业务的方向发展,追求更高的易用性同时保持足够的灵活性。这意味着在Agile Query中, 用户可以通过简洁而强大的表达方式,更方便地执行常见的数据分析任务,同时仍然具备自由组合的灵活性,所有技术相关的细节均由FlatQL 编译器完成。