如何拟合多条收益率曲线并按曲线拆分pandas DataFrame
多收益率曲线拟合与数据集拆分方案
单轮RANSAC只能提取1条曲线,要提取散点中隐含的3-4条独立收益率曲线,直接用序贯RANSAC逻辑即可,不需要换复杂算法,在你现有代码基础上做迭代就能实现。
1. 多曲线独立拟合实现
核心逻辑是:拟合完1条曲线后,把该曲线对应的内点从样本池中移除,在剩余未分配的点里重复拟合过程,直到提取够目标数量的曲线,或者剩余样本量不足无法拟合为止。
几个关键调参注意点:
- 你原来设置的
residual_threshold=2 * np.std(y_ax)过大,会把其他曲线的点误判为当前曲线的内点,建议初始设为0.2-0.5(对应0.2-0.5个百分点的收益率差,匹配不同评级信用债的常规利差区间),再根据拟合效果微调 - 多项式阶数保持3阶即可,收益率曲线本身符合三次多项式形态,阶数过高会出现过拟合的波浪形异常
- 初始目标曲线数设为4,若某轮拟合出的曲线明显由噪声点构成,再把曲线数下调到3
完整拟合代码如下,可直接复用你已写的多项式回归类:
import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.linear_model import RANSACRegressor from sklearn.metrics import mean_squared_error # 原有多项式回归类无需修改 class PolynomialRegression(object): def __init__(self, degree=3, coeffs=None): self.degree = degree self.coeffs = coeffs def fit(self, X, y): self.coeffs = np.polyfit(X.ravel(), y, self.degree) def get_params(self, deep=False): return {'coeffs': self.coeffs} def set_params(self, coeffs=None, random_state=None): self.coeffs = coeffs def predict(self, X): poly_eqn = np.poly1d(self.coeffs) y_hat = poly_eqn(X.ravel()) return y_hat def score(self, X, y): return mean_squared_error(y, self.predict(X)) # 多曲线拟合参数配置 poly_degree = 3 target_curve_num = 4 residual_threshold = 0.3 # 单曲线内点残差阈值,可根据实际效果调整 min_sample_per_curve = 50 # 单条曲线最少样本数,低于该值停止拟合 fitted_models = [] unassigned_mask = np.ones(len(df_clean), dtype=bool) # 初始所有点标记为未分配 for i in range(target_curve_num): # 提取当前未分配的样本 x_unassign = df_clean.loc[unassigned_mask, 'YTM'].values y_unassign = df_clean.loc[unassigned_mask, 'YTW'].values # 剩余样本不足则终止拟合 if len(x_unassign) < min_sample_per_curve: break # 训练单条曲线的RANSAC模型 ransac = RANSACRegressor( PolynomialRegression(degree=poly_degree), residual_threshold=residual_threshold, random_state=0 ) ransac.fit(np.expand_dims(x_unassign, axis=1), y_unassign) # 将当前模型识别的内点映射回全量数据索引 inlier_local_mask = ransac.inlier_mask_ unassign_global_idx = np.where(unassigned_mask)[0] inlier_global_idx = unassign_global_idx[inlier_local_mask] # 保存模型,标记内点为已分配 fitted_models.append({ 'model': ransac, 'inlier_idx': inlier_global_idx }) unassigned_mask[inlier_global_idx] = False
2. 原始DataFrame拆分逻辑
不要直接用RANSAC输出的内点掩码拆分子集——RANSAC拟合时的内点判断只针对当前轮的剩余样本,容易漏判边界点。更稳妥的方式是:对每个样本点,计算它到所有拟合曲线的垂直残差(实际YTW与曲线预测YTW的差值绝对值),将点分配给残差最小的曲线;若最小残差超过阈值,可归为离群点单独存储。
对应代码如下:
# 生成用于曲线预测的X轴序列 x_pred = np.linspace(df_clean['YTM'].min(), df_clean['YTM'].max(), 1000) curve_predictions = [mod['model'].predict(np.expand_dims(x_pred, axis=1)) for mod in fitted_models] # 逐点计算到所有曲线的残差,完成样本分配 sample_assign = np.zeros(len(df_clean), dtype=int) outlier_threshold = 1.0 # 残差超过1个百分点判定为离群点 for idx, row in df_clean.iterrows(): x_val, y_real = row['YTM'], row['YTW'] res_list = [abs(y_real - mod['model'].predict(np.array([[x_val]]))[0]) for mod in fitted_models] min_res = min(res_list) if min_res > outlier_threshold: sample_assign[idx] = -1 # 离群点标记为-1 else: sample_assign[idx] = np.argmin(res_list) # 拆分得到各曲线对应的数据子集 df_subsets = [] for curve_id in range(len(fitted_models)): subset = df_clean[sample_assign == curve_id].copy() subset['curve_label'] = curve_id df_subsets.append(subset) # 单独存储离群点子集 df_outliers = df_clean[sample_assign == -1].copy() # 可视化验证拟合与拆分效果 colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728'] plt.figure(figsize=(12, 6)) for cid in range(len(fitted_models)): plt.scatter(df_subsets[cid]['YTM'], df_subsets[cid]['YTW'], s=10, color=colors[cid], label=f'曲线{cid+1}样本') plt.plot(x_pred, curve_predictions[cid], color=colors[cid], linewidth=3, label=f'拟合曲线{cid+1}') plt.scatter(df_outliers['YTM'], df_outliers['YTW'], s=10, c='gray', label='离群点') plt.xlabel('剩余到期期限(YTM)') plt.ylabel('最差收益率(YTW)') plt.legend() plt.show()
调参参考
- 若拟合时出现多条曲线重叠、单条曲线跨度过大,就把
residual_threshold调小 - 若某条明显存在的曲线没被拟合出来,要么把
residual_threshold适当调大,要么增加target_curve_num - 若拟合出的曲线出现不符合收益率常识的剧烈波动,就把多项式阶数下调到2阶
内容的提问来源于stack exchange,提问作者Egemen Candir
相关产品推荐
相关产品推荐

