PAYATHON 2026

DolphinDB 如何高效计算面板数据的截面 z-score?

数据小许

可以使用 context by date 按交易日计算均值和标准差,再为当天每条股票记录生成 z-score。这样既能保留原表行数,也省去了逐日筛选和 unionAll 拼接。

直接替换 each 和 unionAll

保留示例中的表 t,将后续计算替换为:

final_result = select date, ticker, ret,
    (ret - mean(ret)) / std(ret) as zscore_ret
from t
context by date
order by date, ticker

mean(ret) 和 std(ret) 都在当前日期的分组内计算。每条记录的 ret 减去当天均值,再除以当天标准差,得到对应的 zscore_ret。

查询没有筛选或截取记录,示例中的 15 行输入仍会得到 15 行输出;date、ticker、ret 和计算结果逐行对应。末尾的 order by date, ticker 用于保持原代码要求的展示顺序。

DolphinDB 的 context by 可以在每组内返回与组内记录数相同的向量,适合分组计算后保留明细的场景。官方文档也给出了将组内聚合结果展开到明细行的示例。DolphinDB context by 文档

原实现为什么容易变慢

原代码先获取所有日期,再对每个日期执行:

sub_t = select * from t where date == d

随后为每组构造结果表,最后合并所有表。随着日期增加,查询调用、数据筛选、中间表分配和结果拼接的开销都会累积。对示例这样的普通内存表,逐日筛选还可能反复检查原表中的日期列。

原来的 zscore(sub_t.ret) 已经对一组收益率向量进行运算,因此不能将性能问题直接归结为“完全没有向量化”。从代码结构看,可以先消除外层逐日查询和拼接。

使用 context by 后,分组和组内计算由一条查询表达,交给查询引擎统一处理。这并不保证每个日期都会分配独立线程;实际并行程度和加速幅度仍取决于执行环境、数据存储方式及查询计划。

如何检查结果

先确认输出行数与输入一致,再检查每个日期标准化后的均值和标准差:

select mean(zscore_ret) as mean_z,
       std(zscore_ret) as std_z
from final_result
group by date

对于有效样本数至少为 2、且收益率标准差非零的日期,mean_z 应接近 0,std_z 应接近 1。浮点计算可能产生微小误差,不宜要求结果严格等于 0 或 1。

若要与原实现比较,先按相同的 date, ticker 顺序对齐,再比较 zscore_ret。如果真实数据中同一日期、同一股票存在多条记录,需要使用能够唯一标识原始记录的字段进行对齐。

标准差、缺失值与排序注意事项

std 使用样本标准差,计算时忽略 NULL,与你原来的公式口径一致。改用总体标准差会改变数值,应先确定业务需要哪种定义。DolphinDB std 文档

某个日期的收益率全部相同,或者有效样本不足时,无法按常规公式得到有意义的 z-score。应明确这些记录的处理规则,例如保留 NULL;将结果设为 0 则属于额外的业务约定。

context by date 保证这里的分组计算语义和行数要求,但不能据此假定输出顺序与输入完全一致。需要固定展示顺序时,保留 order by date, ticker。如果下游不要求排序,可以省去该步骤,避免额外开销。

比较新旧实现的耗时时,应使用同一份数据和相同的排序要求,并把最终结果拼接和排序计入原实现的总耗时。如果真实数据来自分布式表,还需结合分区布局检查读取与跨节点处理成本,不能仅凭这份内存表示例判断瓶颈。

备注:内容仅供参考。