加速Datetime索引DataFrame的小时窗日收益率计算:Numba优化失效问题排查
嘿,我来帮你梳理下你的Numba代码为啥变慢,还有怎么修正逻辑并优化效率!
首先,先明确你的核心需求:每天计算从h1时刻(比如0点)到h2时刻(比如3点)的收益率,也就是(h2价格 / h1价格) - 1,结果对应到每天的h2时刻,其余时间为NaN。
你的Numba代码的两大问题
1. 逻辑错误:未按天分组,跨天复用分母
你的两个Numba函数都是全局遍历所有小时数据,没有按日期划分独立计算。比如,如果某天的0点数据缺失,函数会复用前一天的0点值作为分母,这和原生Pandas代码的逻辑完全不一致(原生resample是每天独立计算,缺失则结果为NaN)。这个错误不仅导致结果不正确,还让你做了大量无用的遍历操作。
2. 效率浪费:遍历所有元素而非目标时间点
你的DataFrame有2*365*24*250 = 4,380,000个元素,而你真正需要处理的只有每天2个时间点(h1和h2),总共2*365*2 = 1460个时间点。遍历所有元素完全是浪费算力,这也是Numba代码比原生Pandas慢的核心原因——Pandas的asfreq和resample都是底层C实现的向量化操作,直接跳过了无关数据,而你的Numba循环要逐个处理每个元素,开销大得多。
优化方案:先筛选目标时间点,再按天计算
不管用原生Pandas还是Numba,都应该先缩小处理范围,只保留每天的h1和h2时刻数据,再按天计算收益率。
方案1:优化原生Pandas代码(更快更简洁)
先筛选出h1和h2的行,按日期分组后计算,比你原来的asfreq+resample更高效:
import numpy as np import pandas as pd # 生成测试数据 df_index = pd.date_range("1/1/2000", periods=2 * 365 * 24, freq="H") df_data = np.random.randn(2 * 365 * 24, 250) df = pd.DataFrame(df_data, index=df_index) h1, h2 = 0, 3 # 筛选目标小时的行,按日期分组 target_df = df[df.index.hour.isin([h1, h2])] # 按日期分组,提取h1和h2的值并计算收益率 daily_return = target_df.groupby(target_df.index.date).apply( lambda g: (g.loc[g.index.hour == h2].iloc[0] / g.loc[g.index.hour == h1].iloc[0]) - 1 ) # 把结果转换回原时间索引(对应每天的h2时刻) daily_return = daily_return.set_index(pd.to_datetime(daily_return.index) + pd.Timedelta(hours=h2)) # 合并回原DataFrame(可选,保留原结构) result_df = df.copy() result_df.loc[daily_return.index] = daily_return.values result_df = result_df.dropna(how="all")
测试下来,这个版本比你原来的原生代码更快,因为跳过了大量无关数据。
方案2:修正并优化Numba代码
如果一定要用Numba,我们可以先按天分组提取h1和h2的数组,再用Numba批量计算:
import numba as nb @nb.njit(error_model="numpy", parallel=True) def numba_calculate_returns(h1_vals, h2_vals): out = np.full_like(h2_vals, np.nan) for i in nb.prange(h1_vals.shape[0]): # 跳过h1或h2缺失的情况 if not np.isnan(h1_vals[i]).all() and not np.isnan(h2_vals[i]).all(): out[i] = (h2_vals[i] / h1_vals[i]) - 1 return out # 准备分组后的h1和h2数据 target_df = df[df.index.hour.isin([h1, h2])] # 透视得到每天h1和h2的数值 pivot_df = target_df.reset_index().assign(date=lambda x: x['index'].dt.date).pivot( index="date", columns="index.dt.hour", values=df.columns ) h1_arr = pivot_df[h1].values h2_arr = pivot_df[h2].values # 用Numba计算 returns_arr = numba_calculate_returns(h1_arr, h2_arr) # 转换回DataFrame daily_return = pd.DataFrame(returns_arr, index=pivot_df.index, columns=df.columns) daily_return = daily_return.set_index(pd.to_datetime(daily_return.index) + pd.Timedelta(hours=h2)) result_df = df.copy() result_df.loc[daily_return.index] = daily_return.values result_df = result_df.dropna(how="all")
这个版本的Numba代码利用了parallel=True并行处理每一列,同时只处理必要的数据,效率会远高于你之前的实现,甚至可能超过优化后的原生Pandas代码(尤其是当列数非常多的时候)。
为什么你的Numba代码变慢?
总结一下:
- 遍历所有元素的开销远大于Pandas的向量化操作
- 未按天分组的逻辑错误导致额外的无效计算
- 逐列处理时的Pandas对象拼接开销抵消了Numba的加速效果
通过先筛选目标数据、按天分组,再结合Numba的并行计算,就能真正实现加速。
备注:内容来源于stack exchange,提问作者Benoit Sauvage

