按element和date分组归一化,确保6位小数和为1的实现方法
分组归一化后求和严格等于1的实现
需求:按element和date分组,对factor_a、factor_b字段做归一化处理并保留6位小数,且每组归一化后的数值和必须严格等于1。
原始数据与尝试代码
用户构建的DataFrame:
import pandas as pd data = {'element': ['A', 'A', 'A', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'C', 'C', 'C'], 'subelement': ['A1', 'A2', 'A3', 'B1','B2', 'B3','B4', 'B5','B5', 'B7', 'C1', 'C2', 'C3'], 'date': ['2022-06-01', '2022-06-01', '2022-06-01', '2022-06-01', '2022-06-01', '2022-06-01', '2022-06-01','2022-06-01', '2022-06-01', '2022-06-01','2022-07-01','2022-07-01','2022-07-01'], 'factor_a': [0.333333333333333, 0.333333333333333, 0.333333333333333, 0.142857142857143, 0.142857142857143, 0.142857142857143, 0.142857142857143, 0.142857142857143, 0.142857142857143, 0.142857142857143, 0.333333333333333, 0.333333333333333, 0.333333333333333], 'factor_b': [0.65, 0.35, 0, 0.5, 0, 0.5, 0, 0, 0, 0, 0.333333333333333, 0.333333333333333, 0.333333333333333] } df = pd.DataFrame(data)
用户尝试的代码存在误差问题:直接对归一化结果做round(6)会导致求和后出现0.999999或1.000001的偏差,无法严格等于1。
cols = df.columns[3:] groups = df.groupby(['element', 'date'])[cols] sum_ = groups.transform('sum') df[cols+'_norm'] = (df[cols] / sum_).round(6)
解决方案
核心思路:先计算保留6位小数的归一化初始值,再计算每组的求和误差,将误差调整到组内某一个元素上(这里选择组内最后一个元素,最小化对整体分布的影响),确保总和严格为1。
完整实现代码:
import pandas as pd # 构建DataFrame data = {'element': ['A', 'A', 'A', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'C', 'C', 'C'], 'subelement': ['A1', 'A2', 'A3', 'B1','B2', 'B3','B4', 'B5','B5', 'B7', 'C1', 'C2', 'C3'], 'date': ['2022-06-01', '2022-06-01', '2022-06-01', '2022-06-01', '2022-06-01', '2022-06-01', '2022-06-01','2022-06-01', '2022-06-01', '2022-06-01','2022-07-01','2022-07-01','2022-07-01'], 'factor_a': [0.333333333333333, 0.333333333333333, 0.333333333333333, 0.142857142857143, 0.142857142857143, 0.142857142857143, 0.142857142857143, 0.142857142857143, 0.142857142857143, 0.142857142857143, 0.333333333333333, 0.333333333333333, 0.333333333333333], 'factor_b': [0.65, 0.35, 0, 0.5, 0, 0.5, 0, 0, 0, 0, 0.333333333333333, 0.333333333333333, 0.333333333333333] } df = pd.DataFrame(data) cols = df.columns[3:] group_keys = ['element', 'date'] # 计算归一化后保留6位小数的初始值 sum_ = df.groupby(group_keys)[cols].transform('sum') df[cols + '_norm'] = (df[cols] / sum_).round(6) # 对每个归一化列进行误差修正 for col in cols: norm_col = f"{col}_norm" # 计算每组求和与1的误差 group_sum = df.groupby(group_keys)[norm_col].transform('sum') error = 1 - group_sum # 获取每组最后一行的索引,将误差加到该行对应列 last_row_idx = df.groupby(group_keys).tail(1).index df.loc[last_row_idx, norm_col] += error.loc[last_row_idx] # 再次保留6位小数,避免修正后出现多余小数位 df[norm_col] = df[norm_col].round(6) # 验证结果 print("factor_a_norm分组求和结果:") print(df.groupby(group_keys)['factor_a_norm'].sum()) print("\nfactor_b_norm分组求和结果:") print(df.groupby(group_keys)['factor_b_norm'].sum())
代码说明
- 先完成常规归一化并保留6位小数,得到初始归一化列。
- 对每个归一化列,计算每组求和与1的误差值。
- 将误差值加到每组最后一个元素上,再重新保留6位小数,确保总和严格为1。
- 可选优化:若不想调整最后一个元素,可选择组内数值最大的元素,进一步降低调整对数据分布的影响。
内容的提问来源于stack exchange,提问作者rfv18
相关产品推荐
相关产品推荐

