时间序列预测方向一致性准确率函数封装及KeyError问题解决
时间序列预测方向一致性准确率函数封装及KeyError修复
需求说明
需要计算时间序列预测中真实值(y_true)与预测值(y_pred)的连续月份涨跌方向一致性比率,作为评估指标:
- 方向定义:当月值减上月值为正标记为上涨(1),负为下跌(-1),0为持平(0)
- 计算时排除y_true方向为null或0的行
样本数据
date y_true y_pred1 y_pred2 0 2017-1-31 6.42 -2.35 15.57 1 2017-2-28 -2.35 15.57 6.64 2 2017-3-31 15.57 6.64 7.61 3 2017-4-30 6.64 7.61 10.28 4 2017-5-31 7.61 7.61 6.34 5 2017-6-30 10.28 6.34 4.88 6 2017-7-31 6.34 4.88 7.91 7 2017-8-31 6.34 7.91 6.26 8 2017-9-30 7.91 6.26 11.51 9 2017-10-31 6.26 11.51 10.73 10 2017-11-30 11.51 10.73 10.65 11 2017-12-31 10.73 10.65 32.05
原计算逻辑及结果
以下代码可完成样本计算,得到结果0.2和0.4:
import numpy as np import pandas as pd def cal_arrays_direction(value): if value > 0: return 1 elif value < 0: return -1 elif value == 0: return 0 else: return np.NaN df['y_true_diff'] = df['y_true'].diff(1).map(cal_arrays_direction) df['y_pred1_diff'] = df['y_pred1'].diff(1).map(cal_arrays_direction) df['y_pred2_diff'] = df['y_pred2'].diff(1).map(cal_arrays_direction) df['y_true_y_pred1'] = np.where((df['y_true_diff'] == df['y_pred1_diff']), 1, 0) df['y_true_y_pred2'] = np.where((df['y_true_diff'] == df['y_pred2_diff']), 1, 0) dir_acc_y_true_pred1 = df['y_true_y_pred1'].value_counts()[1] / (df['y_true_diff'].value_counts()[-1] + df['y_true_diff'].value_counts()[1]) print(dir_acc_y_true_pred1) dir_acc_y_true_pred2 = df['y_true_y_pred2'].value_counts()[1] / (df['y_true_diff'].value_counts()[-1] + df['y_true_diff'].value_counts()[1]) print(dir_acc_y_true_pred2)
输出:
0.2 0.4
封装需求
需要将上述逻辑封装为类似sklearn中MSE、RMSE的可复用函数,函数签名如下:
def direction_consistency_acc(y_true, y_pred): ... return dir_acc_ratio
遇到的问题
封装后运行触发KeyError,错误信息如下:
Update 1
Traceback (most recent call last): File "C:\Users\LSTM\AppData\Local\Programs\Python\Python310\lib\site-packages\pandas\core\indexes\base.py", line 3803, in get_loc return self._engine.get_loc(casted_key) File "pandas\_libs\index.pyx", line 138, in pandas._libs.index.IndexEngine.get_loc File "pandas\_libs\index.pyx", line 165, in pandas._libs.index.IndexEngine.get_loc File "pandas\_libs\hashtable_class_helper.pxi", line 1577, in pandas._libs.hashtable.Float64HashTable.get_item File "pandas\_libs\hashtable_class_helper.pxi", line 1587, in pandas._libs.hashtable.Float64HashTable.get_item KeyError: 1.0 The above exception was the direct cause of the following exception: Traceback (most recent call last): File "..\code\stacked model_2022-11-08.py", line 353, in <module> run_model(df) File "..\code\stacked model_2022-11-08.py", line 258, in run_model out1 = direction_consistency_acc(preds['y_true'], preds[['y_pred1','y_pred2', File "..\code\stacked model_2022-11-08.py", line 245, in direction_consistency_acc dir_acc_y_true_pred = preds[f'y_true_{col}'].eq(1).sum() / (s[-1] + s[1]) File "C:\Users\LSTM\AppData\Local\Programs\Python\Python310\lib\site-packages\pandas\core\series.py", line 981, in __getitem__ return self._get_value(key) File "C:\Users\LSTM\AppData\Local\Programs\Python\Python310\lib\site-packages\pandas\core\series.py", line 1089, in _get_value loc = self.index.get_loc(label) File "C:\Users\LSTM\AppData\Local\Programs\Python\Python310\lib\site-packages\pandas\core\indexes\base.py", line 3805, in get_loc raise KeyError(key) from err KeyError: 1
Update 2
排查发现当y_true_diff仅存在-1.0时触发错误:
... 2021-05-31 -1.0 4 1.0 2 Name: y_true_diff, dtype: int64 2021-06-30 -1.0 5 1.0 1 Name: y_true_diff, dtype: int64 2021-07-31 -1.0 6 Name: y_true_diff, dtype: int64 Traceback (most recent call last): File "C:\Users\LSTM\AppData\Local\Programs\Python\Python310\lib\site-packages\pandas\core\indexes\base.py", line 3803, in get_loc return self._engine.get_loc(casted_key) File "pandas\_libs\index.pyx", line 138, in pandas._libs.index.IndexEngine.get_loc File "pandas\_libs\index.pyx", line 165, in pandas._libs.index.IndexEngine.get_loc File "pandas\_libs\hashtable_class_helper.pxi", line 1577, in pandas._libs.hashtable.Float64HashTable.get_item File "pandas\_libs\hashtable_class_helper.pxi", line 1587, in pandas._libs.hashtable.Float64HashTable.get_item KeyError: 1.0 The above exception was the direct cause of the following exception: Traceback (most recent call last): File "..\code\stacked model_2022-11-08.py", line 353, in <module> run_model(df) File "..\code\stacked model_2022-11-08.py", line 258, in run_model out1 = direction_consistency_acc(preds['y_true'], preds[['y_pred1','y_pred2', File "..\code\stacked model_2022-11-08.py", line 245, in direction_consistency_acc dir_acc_y_true_pred = preds[f'y_true_{col}'].eq(1).sum() / (s[-1] + s[1]) File "C:\Users\LSTM\AppData\Local\Programs\Python\Python310\lib\site-packages\pandas\core\series.py", line 981, in __getitem__ return self._get_value(key) File "C:\Users\LSTM\AppData\Local\Programs\Python\Python310\lib\site-packages\pandas\core\series.py", line 1089, in _get_value loc = self.index.get_loc(label) File "C:\Users\LSTM\AppData\Local\Programs\Python\Python310\lib\site-packages\pandas\core\indexes\base.py", line 3805, in get_loc raise KeyError(key) from err KeyError: 1
解决方案
问题根源
KeyError出现的原因是直接通过value_counts()[1]或value_counts()[-1]取值时,若该方向(上涨/下跌)不存在,就会触发键不存在的异常。比如当y_true只有下跌方向时,value_counts()中没有1这个键,导致报错。
修复后的函数
以下是封装好且修复异常的函数,支持单预测列和多预测列输入,同时处理边界情况:
import numpy as np import pandas as pd def direction_consistency_acc(y_true, y_pred): # 定义方向转换函数 def get_direction(series): diff = series.diff(1) return np.where(diff > 0, 1, np.where(diff < 0, -1, 0)) # 计算真实值方向 y_true_dir = get_direction(y_true) # 过滤掉真实值方向为0或NaN的索引 valid_idx = (y_true_dir != 0) & (~pd.isna(y_true_dir)) # 处理多预测列的情况 if isinstance(y_pred, pd.DataFrame): results = {} for col in y_pred.columns: y_pred_dir = get_direction(y_pred[col]) # 只保留有效索引的方向数据 true_valid = y_true_dir[valid_idx] pred_valid = y_pred_dir[valid_idx] # 计算一致的数量 match_count = (true_valid == pred_valid).sum() # 计算有效样本总数(真实值非0的数量) total_valid = len(true_valid) # 避免除以0 results[col] = match_count / total_valid if total_valid > 0 else 0.0 return results else: # 单预测列的情况 y_pred_dir = get_direction(y_pred) true_valid = y_true_dir[valid_idx] pred_valid = y_pred_dir[valid_idx] match_count = (true_valid == pred_valid).sum() total_valid = len(true_valid) return match_count / total_valid if total_valid > 0 else 0.0
函数说明
- 方向转换:使用
np.where替代map,更高效地生成方向标记 - 有效数据过滤:直接通过布尔索引过滤真实值方向为0或NaN的数据,避免后续无效计算
- 边界处理:
- 当真实值没有有效涨跌数据(total_valid=0)时,返回0.0避免除以0错误
- 处理多预测列输入时,返回各列对应的准确率字典
- 避免KeyError:不再依赖
value_counts()的键取值,直接通过布尔求和计算匹配数和有效总数
测试验证
用样本数据测试:
# 加载样本数据 df = pd.DataFrame({ 'date': ['2017-1-31', '2017-2-28', '2017-3-31', '2017-4-30', '2017-5-31', '2017-6-30', '2017-7-31', '2017-8-31', '2017-9-30', '2017-10-31', '2017-11-30', '2017-12-31'], 'y_true': [6.42, -2.35, 15.57, 6.64, 7.61, 10.28, 6.34, 6.34, 7.91, 6.26, 11.51, 10.73], 'y_pred1': [-2.35, 15.57, 6.64, 7.61, 7.61, 6.34, 4.88, 7.91, 6.26, 11.51, 10.73, 10.65], 'y_pred2': [15.57, 6.64, 7.61, 10.28, 6.34, 4.88, 7.91, 6.26, 11.51, 10.73, 10.65, 32.05] }) # 单预测列测试 print(direction_consistency_acc(df['y_true'], df['y_pred1'])) # 输出0.2 print(direction_consistency_acc(df['y_true'], df['y_pred2'])) # 输出0.4 # 多预测列测试 print(direction_consistency_acc(df['y_true'], df[['y_pred1', 'y_pred2']])) # 输出:{'y_pred1': 0.2, 'y_pred2': 0.4}
异常场景测试
测试只有下跌方向的情况:
# 构造只有下跌的真实值数据 test_true = pd.Series([10, 8, 6, 4, 2]) test_pred = pd.Series([9, 7, 5, 3, 1]) print(direction_consistency_acc(test_true, test_pred)) # 输出1.0,全部方向一致
内容的提问来源于stack exchange,提问作者ah bon
相关产品推荐
相关产品推荐

