不可或缺的相邻引用

简介: 数据分析中常需进行跨行计算,如比上期、比同期或移动平均等。SQL虽有窗口函数,但代码繁琐且需重复定义排序;Python通过rolling或shift可实现相邻成员计算,但仍显冗长且语法不统一。SPL(esProc)提供简洁一致的相邻成员引用机制,如~[i]表示与当前成员距离为i的成员,支持复杂计算及分组场景,大幅简化代码逻辑。乾学院提供免费SPL,欢迎体验!

数据分析经常出现跨行计算,比如比上期、比同期、移动平均等等。针对有序数据集实现跨行计算,会涉及集合相邻成员引用的问题。

比如某商家某年 12 个月的销售额已经按月份次序准备好,现在要计算最大月增长额。

SQL 这样写:

with sales as (
    select column_value as amount, rownum as month
    from table(sys.odcinumberlist(123,345,321,345,546,542,874,234,543,983,434,897))
),
lagged_sales as (
    select amount, month, 
        lag(amount) over (order by month) as prev
    from sales
)
select max(amount - prev) as max
from lagged_sales;

窗口函数可以引用上月销售额,但要写子查询,代码很啰嗦。而且 SQL 基于无序集合,不能利用数据原有的顺序,每个窗口函数都要单独写 order by。

其实,数据本身总会有个次序,利用这个次序可以方便地表达计算需求。比如这里,数据若按月份有序,只要用当前成员减去上一个成员,差值就是月增长额。如果有一种相邻成员的引用语法,解决类似问题就很轻松了。

esProc SPL 就提供了集合相邻成员引用机制:

sales = [123, 345, 321, 345, 546, 542, 874, 234, 543, 983, 434, 897]
sales.(if(#>1,~-~[-1],0)).max()

~ 表示当前成员,# 是当前成员的序号,~[i] 表示和当前成员距离为 i 的成员,这里的 ~[-1] 表示前一个成员,也就是上月销售额。

实际上,if(#>1,~-~[-1],0)是一个 lambda 函数,外层的 sales.() 是循环函数。SPL 固定使用 ~,# 和 [] 这些符号来表示 lambda 函数的参数。

SPL 的 lambda 函数看起来就是一个普通表达式,语法比传统 lambda 更简洁,传入的参数却更丰富。

Python 的 Series 或 Dataframe 对象直接用 lambda 的话,只能传入当前行这一个参数,不支持序号和相邻引用的语法。要解决这个问题,必须先用 rolling 函数创建滑动窗口对象:

result = sales.rolling(window=2).apply(lambda x: x[1] - x[0], raw=True).max()

rolling 创建了大小为 2 的窗口,apply 函数操作会作用于 sales 序列中相邻的两个成员,可以利用 lambda 求这两个成员的差。

但 Python 多了一个创建窗口对象的步骤,略显啰嗦。而且 Python 的 lambda 函数是显示定义的,需要写 lambda 关键字,还要定义传入参数,不如 SPL 简洁。

Python 也可以换个思路:

sales=pd.Series([123, 345, 321, 345, 546, 542, 874, 234, 543, 983, 434, 897])
result = (sales-sales.shift(1)).max()

shift 函数把原序列向后移动一个位置生成新序列,再和原序列对位求差值。这样要多计算出一个序列对象,相比之下,还是 SPL 在原序列中引用相邻成员的代码更简单。

Python 还提供了 diff 函数,可以求相邻成员的差:

pd.Series([123, 345, 321, 345, 546, 542, 874, 234, 543, 983, 434, 897])
result = sales.diff().max()

diff 支持 periods 参数,能求当前成员与距离为 periods 的成员之差。diff 和类似的 pct_change 等函数都是固定计算,对于没有现成函数可用的计算,只能继续用 rolling 或 shift 来实现。

SPL 就完全没有问题,~[i] 在表达式中可以随意使用,能实现各种各样的相邻计算。

除了相邻成员外,我们还常常要引用相邻的集合。比如还是上面的数据,我们希望计算每个月和前两个月的销售额移动平均值。

SQL 这样写:

with sales as (
    select column_value as amount, rownum as month
    from table(sys.odcinumberlist(123,345,321,345,546,542,874,234,543,983,434,897))
)
select month,amount,
    avg(amount) over (order by month rows between 2 preceding and current row) as moving_average
from sales;

SQL 有相邻集合的概念,比如这三个相邻的销售额就组成了一个集合。但 SQL 无法保持住这个集合,要马上聚合,也就只能用 avg、sum、count 等固有聚合函数,如果情况再复杂些,SQL 就不能使用相邻集合计算了。

比如要判断每月和前两个月的销售额是否递增。简单思路是把这三个月销售额组成有序的小集合,再计算这个小集合是否递增。

SQL 没有判断递增的聚合函数,只能这样写:

with sales as (
    select column_value as amount, rownum as month
    from table(sys.odcinumberlist(123,345,321,345,546,542,874,234,543,983,434,897))
)
select month,amount,
    amount > lag(amount, 1) over (order by month)  and 
    lag(amount, 1) over (order by month) > lag(amount, 2) over (order by month)   
    as flag
from sales;

结果要用三个 lag 函数,而且,还要三个 order by month,很啰嗦。

SPL 把 ~[i] 扩展成 ~[a,b] 来表示相邻集合,无论是移动平均,还是递增判断,都很容易描述:

sales = [123, 345, 321, 345, 546, 542, 874, 234, 543, 983, 434, 897]
sales.(~[-2,0].avg())
sales.(~[-2,0].pselect(~<=~[-1])==null)

~[-2,0] 表示从上上成员开始,到当前成员组成的相邻集合。

想计算滑动平均,就对相邻集合使用 avg 函数。

要判断递增,就用位置计算函数 pselect 在相邻集合中查找,如果没有成员小于等于上一个成员,那就是递增了。pselect 参数中 ~、~[-1] 表示相邻集合的当前和上一个成员。

Python 也有相邻集合概念,和 SQL 类似,移动平均这类简单聚合可以轻松写出来:

sales = pd.Series([123, 345, 321, 345, 546, 542, 874, 234, 543, 983, 434, 897])
result = sales.rolling(window=3, min_periods=1).mean()

判断递增就麻烦很多,又要借助 lambda 函数了:

result = sales.rolling(window=3).apply(lambda x: (x[0] < x[1] and x[1] < x[2]), raw=True)

rolling 滑动窗口是固定大小的,很不灵活。比如希望窗口是从第一个成员到当前成员,就需要用另一个函数 expanding。如果希望窗口是从当前成员到最后一个成员,则需要反转序列。

而 SPL 使用统一的语法,~[,0] 表示第一个成员到当前成员,~[0,] 表示当前成员到最后一个成员。

对于常见的结构化计算,SPL 相邻引用语法做了简化,可直接使用字段名。比如 sales 表有字段 month、amount 和 rate,求每月 amount*rate 增长额的代码是这样:

sales.derive(amount*rate-amount[-1]*rate[-1]:increase)

~[-1].amount 直接写成 amount[-1],代码很简洁。

Python 没有为数据表而简化,表名会重复出现:

sales['increase']=(sales['amount']*sales['rate']).rolling(window=2).apply(lambda x:x[1] - x[0], raw=True)

SPL 相邻引用机制适用于所有集合,当然也包括分组后的子集合。比如 sales 表存储了各门店某年 12 个月的销售额,都是按月份有序的。计算各门店月增长额的代码是这样:

=sales.group(store).(~.derive(amount-amount[-1]:increase))
group 函数按照门店分组并保持分组子集,~ 就是当前子集合。针对子集的相邻引用语法和全集一致,两者计算过程也相同。

SQL 没有分组子集,要在窗口函数中增加 partition by,变得更复杂:

with monthly_growth as (
    select *,
        ((amount - lag(amount, 1) over (partition by store order by month)) / lag(amount, 1) over (partition by store order by month))  as increase
    from
        sales
)
select *
from monthly_growth
order by store, month;

Python 也有分组子集,用 rolling 配合 groupby 函数就可以:

sales['increase'] = sales.groupby('store')['amount'].rolling(window=2).apply(lambda x: x[1] - x[0], raw=True)

语法和不分组时基本一致,不过还要写繁琐的 lambda 函数。

小结一下:SQL 基于无序集合,经常需要用子查询和窗口函数来实现相邻成员之间的计算,代码比较啰嗦。Python 支持有序集合,实现相邻成员引用的优势要大得多,但仍有语法不一致问题,根据情况选择不同的函数,有时还要反转集合。SPL 在有序集合基础上提供相邻引用机制,相邻计算语法简洁、一致,学习和理解也最容易。
esProcSPL是免费的,欢迎前往乾学院下载试用~

相关文章
|
机器学习/深度学习 自然语言处理 数据处理
零样本学习的易懂解释
零样本学习是一种机器学习的方法,它的目标是在没有任何标记样本的情况下,通过学习从未见过的类别或任务。这意味着模型需要在没有任何先验知识的情况下进行学习和推理。
574 0
cesium添加实体不被地形遮挡的参数设置
disableDepthTestDistance:指定从相机到禁用深度测试的距离,关于深度测试我们将在后面的文章中介绍到,由于深度测试的存在,我们的对象很多时候会被地形挡住,如下:
2869 0
cesium添加实体不被地形遮挡的参数设置
|
Java Linux Maven
解决jdk17启动seata报错的问题
解决jdk17启动seata报错的问题
3877 1
解决jdk17启动seata报错的问题
|
5月前
|
存储 SQL Java
数据存储使用文件还是数据库,哪个更合适?
数据库和文件系统各有优劣:数据库读写性能较低、结构 rigid,但具备计算能力和数据一致性保障;文件系统灵活易管理、读写高效,但缺乏计算能力且无法保证一致性。针对仅需高效存储与灵活管理的场景,文件系统更优,但其计算短板可通过开源工具 SPL(Structured Process Language)弥补。SPL 提供独立计算语法及高性能文件格式(如集文件、组表),支持复杂计算与多源混合查询,甚至可替代数据仓库。此外,SPL 易集成、支持热切换,大幅提升开发运维效率,是后数据库时代文件存储的理想补充方案。
|
机器学习/深度学习 算法 计算机视觉
卷积神经网络(CNN)的工作原理深度解析
【6月更文挑战第14天】本文深度解析卷积神经网络(CNN)的工作原理。CNN由输入层、卷积层、激活函数、池化层、全连接层和输出层构成。卷积层通过滤波器提取特征,激活函数增加非线性,池化层降低维度。全连接层整合特征,输出层根据任务产生预测。CNN通过特征提取、整合、反向传播和优化进行学习。尽管存在计算量大、参数多等问题,但随着技术发展,CNN在计算机视觉领域的潜力将持续增长。
1053 3
|
9月前
|
数据可视化 数据挖掘 BI
报表工具怎么选?盘点2025年10个最好用的报表平台,建议收藏!
报表工具怎么选?盘点2025年10个最好用的报表平台,建议收藏!
|
10月前
|
存储 SQL 大数据
Python 在企业级应用中的两大硬伤
关系数据库和SQL在企业级应用中面临诸多挑战,如复杂SQL难以移植、数据库负担重、应用间强耦合等。Python虽是替代选择,但在大数据运算和版本管理方面存在不足。SPL(esProc Structured Programming Language)作为开源语言,专门针对结构化数据计算,解决了Python的这些硬伤。它提供高效的大数据运算能力、并行处理、高性能文件存储格式(如btx、ctx),以及一致的版本管理,确保企业级应用的稳定性和高性能。此外,SPL与Java无缝集成,适合现代J2EE体系应用,简化开发并提升性能。
|
11月前
|
关系型数据库 MySQL Linux
Linux下mysql数据库的导入与导出以及查看端口
本文详细介绍了在Linux下如何导入和导出MySQL数据库,以及查看MySQL运行端口的方法。通过这些操作,用户可以轻松进行数据库的备份与恢复,以及确认MySQL服务的运行状态和端口。掌握这些技能,对于日常数据库管理和维护非常重要。
498 8
|
10月前
|
存储 NoSQL Java
流计算需要框架吗?SPL 可能是更好的选择
流数据源的动态无界特性使得传统数据库技术难以直接处理,而Heron、Samza、Storm、Spark、Flink等计算框架在流计算领域取得了先发优势。然而,这些框架往往侧重于访问能力,计算能力不足,尤其在高级计算如流批混算、复杂计算和高性能计算方面表现欠佳。esProc SPL作为基于JVM的轻量级开源计算类库,专注于提升流计算的计算能力,支持丰富的流数据访问、灵活的集成接口和高效的内外存存储格式,具备强大的高级计算功能,能够简化业务逻辑开发并适应多样的应用场景。SPL通过专业的计算语言和结构化数据处理能力,为流计算提供了更优的解决方案。
|
安全 BI 数据安全/隐私保护
分享三款性价比超高的AD域管理工具
目前很多企业利用Active Directory (AD )来确保网络环境安全并维护更便捷的管理用户帐户。但还是有很多复杂的问题需要工具来辅助解决,在选择什么样的工具这个问题上,企业通常需要遵守严格的预算限制。这就是为什么我们将这个性价比超高的Active Directory管理工具介绍给大家的原因。
971 4
分享三款性价比超高的AD域管理工具