如何在Pandas中无循环实现带切片的两DataFrame运算?
如何在Pandas中无需循环实现动态截断DataFrame的标准化计算
问题描述
给定两个Pandas DataFrame:
import pandas as pd df1 = pd.DataFrame({'data': [0.4, 0.112, 0.7]}) df2 = pd.DataFrame({'data': [321, 3 , 1, 1, 2, 4, 5, 6, 7, 8, 9, 12, 0.4, 0.112]})
需要为df1的每个元素计算标准化值,规则为:
- df1最后一个元素(0.7):基于完整的df2计算
(值 - df2.mean()) / df2.std() - df1倒数第二个元素(0.112):基于去掉df2最后1个元素的子集计算
(值 - df2[:-1].mean()) / df2[:-1].std() - df1第一个元素(0.4):基于去掉df2最后2个元素的子集计算
(值 - df2[:-2].mean()) / df2[:-2].std()
通用逻辑为:对df1的第i个元素(0-based),使用df2的前 len(df2) - (len(df1)-1 -i) 个元素计算均值和标准差,再代入标准化公式。
无循环解决方案
利用Pandas的expanding()方法计算累积统计量,再通过索引匹配完成批量计算:
步骤1:计算df2的累积均值和标准差
expanding()会生成从第一个元素开始,逐步包含后续元素的累积统计序列,正好对应我们需要的不同长度的df2子集的统计量:
# 计算从第一个元素到第k个元素的累积均值(k从1到len(df2)) cum_mean = df2['data'].expanding().mean() # 计算累积标准差,保持和Pandas默认ddof=1一致 cum_std = df2['data'].expanding().std()
步骤2:匹配df1元素对应的累积统计量
df1有3个元素,我们需要从cum_mean和cum_std中提取对应前12、13、14个元素的统计值,对应索引为11、12、13(0-based):
n_df2 = len(df2) n_df1 = len(df1) # 生成需要提取的索引:从n_df2 - n_df1 到 n_df2 -1 target_indices = range(n_df2 - n_df1, n_df2)
步骤3:批量计算结果
直接对df1的data列和提取的统计量做矢量化运算:
df1['result'] = (df1['data'].values - cum_mean.iloc[target_indices].values) / cum_std.iloc[target_indices].values
完整代码
import pandas as pd df1 = pd.DataFrame({'data': [0.4, 0.112, 0.7]}) df2 = pd.DataFrame({'data': [321, 3 , 1, 1, 2, 4, 5, 6, 7, 8, 9, 12, 0.4, 0.112]}) # 计算累积统计量 cum_mean = df2['data'].expanding().mean() cum_std = df2['data'].expanding().std() # 匹配目标索引 n_df2 = len(df2) n_df1 = len(df1) target_indices = range(n_df2 - n_df1, n_df2) # 计算结果 df1['result'] = (df1['data'].values - cum_mean.iloc[target_indices].values) / cum_std.iloc[target_indices].values print(df1)
输出结果
data result 0 0.400 -0.317614 1 0.112 -0.317864 2 0.700 -0.317518
原理说明
expanding()方法是矢量化操作,内部用C实现,效率远高于Python循环- 提取索引时直接利用序列范围生成,避免循环遍历
- 最终的运算是基于NumPy数组的矢量化运算,完全规避了Python层面的循环迭代
内容的提问来源于stack exchange,提问作者Petar Ulev
相关产品推荐
相关产品推荐

