Files
myquant/djapi/api/stock/smoothBrush.py
T
simonandClaude Opus 4.7 2f1d8b4d03 feat: djapi 数据源归一化 + bug 修复 + 废弃 getDivData_AK
- 新增 djapi/api/stock/data_source.py 统一数源入口 (Tushare 单例)
- 迁移 10 个模块至统一数据源入口
- 废弃 getDivData_AK.py
- 修复 getStockDiv2.py / smoothBrush.py 等模块
- indexDatas API 参数 tscode 类型修正 (股票→指数代码)
- views.py + urls.py 接口清理
- continuation.md 状态更新

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-16 15:01:04 +08:00

190 lines
7.1 KiB
Python

import pandas as pd
import numpy as np
def smooth_series_brush(series: pd.Series, window_size: int = 7, threshold_factor: float = 0.5, max_brush_length: int = 5) -> pd.Series:
"""
平滑处理pandas序列中的连续毛刺数据,使用前值或后值填充。
参数:
series (pd.Series): 输入的pandas序列。
window_size (int): 用于检测毛刺的滑动窗口大小。必须为奇数。默认为7。
threshold_factor (float): 判断毛刺的阈值因子。如果 abs(value - median) / median > threshold_factor,则认为是毛刺。默认为0.5 (50%)。
max_brush_length (int): 允许的最大连续毛刺长度。超过此长度的连续点将不被处理。默认为5。
返回:
pd.Series: 处理后的平滑序列。
"""
if not isinstance(series, pd.Series):
raise TypeError("输入必须是 pandas Series 对象。")
if window_size % 2 == 0:
raise ValueError("window_size 必须是奇数。")
# 创建副本以避免修改原始数据
smoothed_series = series.copy()
# 用于标记是否为毛刺的布尔序列
is_brush = pd.Series([False] * len(series), index=series.index)
half_window = window_size // 2
# --- 第一步:检测毛刺 ---
for i in range(len(series)):
start_idx = max(0, i - half_window)
end_idx = min(len(series), i + half_window + 1)
# 获取当前窗口数据
window_data = series.iloc[start_idx:end_idx]
if len(window_data) < 2:
continue
# 计算窗口中位数
window_median = window_data.median()
# 避免除以零
if window_median == 0:
continue
current_value = series.iloc[i]
# 计算偏差比例
deviation_ratio = abs(current_value - window_median) / abs(window_median)
# 如果偏差超过阈值,则标记为毛刺
if deviation_ratio > threshold_factor:
is_brush.iloc[i] = True
# --- 第二步:处理连续的毛刺段 ---
# 使用 cumsum 技巧识别连续毛刺段
brush_groups = (is_brush != is_brush.shift()).cumsum() * is_brush
# 遍历每个被标记为毛刺的组
for group_id in brush_groups[brush_groups != 0].unique():
if pd.isna(group_id):
continue
brush_indices = brush_groups[brush_groups == group_id].index
# 检查连续毛刺长度
if len(brush_indices) > max_brush_length:
print(f"警告: 发现长度为 {len(brush_indices)} 的连续毛刺段 (超过 max_brush_length={max_brush_length}),将不进行平滑处理。")
continue
# --- 平滑处理:使用前值或后值填充 ---
# 查找前一个非毛刺点
prev_valid_val = None
start_loc = series.index.get_loc(brush_indices[0])
for j in range(start_loc - 1, -1, -1):
if not is_brush.iloc[j]:
prev_valid_val = series.iloc[j]
break
# 查找后一个非毛刺点
next_valid_val = None
end_loc = series.index.get_loc(brush_indices[-1])
for j in range(end_loc + 1, len(series)):
if not is_brush.iloc[j]:
next_valid_val = series.iloc[j]
break
# 决定使用哪个值填充
if prev_valid_val is not None:
fill_value = prev_valid_val
elif next_valid_val is not None:
fill_value = next_valid_val
else:
print(f"警告: 毛刺段 {brush_indices} 无有效邻居,使用全局中位数填充。")
fill_value = series.median()
# 用 fill_value 填充整个毛刺段
for idx in brush_indices:
smoothed_series.loc[idx] = fill_value
return smoothed_series
def smooth_dataframe_brush(df: pd.DataFrame, target_columns: list, **kwargs) -> pd.DataFrame:
"""
对DataFrame中的指定列进行毛刺平滑处理。
参数:
df (pd.DataFrame): 输入的pandas DataFrame。
target_columns (list): 需要去毛刺处理的列名列表。
**kwargs: 传递给 smooth_series_brush 函数的参数 (如 window_size, threshold_factor, max_brush_length)。
返回:
pd.DataFrame: 处理后的DataFrame,指定列已平滑,其余列不变。
"""
if not isinstance(df, pd.DataFrame):
raise TypeError("输入必须是 pandas DataFrame 对象。")
# 创建副本以避免修改原始数据
result_df = df.copy()
# 检查目标列是否都存在于DataFrame中
missing_cols = [col for col in target_columns if col not in df.columns]
if missing_cols:
raise ValueError(f"以下列不在DataFrame中: {missing_cols}")
# 对每个目标列应用平滑函数
for col in target_columns:
print(f"正在处理列: {col}")
try:
# 应用去毛刺函数
result_df[col] = smooth_series_brush(df[col], **kwargs)
except Exception as e:
print(f"处理列 {col} 时出错: {e}")
# 可以选择保留原始数据或抛出异常
# 这里选择保留原始数据
continue
return result_df
# --- 示例 ---
if __name__ == "__main__":
# 1. 创建示例 DataFrame
dates = pd.date_range('2023-01-01', periods=20, freq='D')
# 需要处理的列
values_to_smooth = [10, 11, 10.5, 12, 11.8, 50, 12.1, 11.9, 10, 10.2,
9.8, 100, 105, 99, 10.1, 9.9, 10.3, 5, 10.2, 10.1]
# 不需要处理的列 (例如,另一个传感器数据)
other_data = np.random.randn(20).cumsum() + 100 # 累积和,模拟趋势
# 构建 DataFrame
df_original = pd.DataFrame({
'Date': dates,
'Sensor_A': values_to_smooth, # 需要去毛刺
'Sensor_B': other_data, # 不需要处理
'Other_Info': range(20) # 其他信息,不需要处理
})
# 设置日期为索引 (常见做法)
df_original.set_index('Date', inplace=True)
print("原始 DataFrame:")
print(df_original.head(10))
print("\n" + "="*50 + "\n")
# 2. 应用平滑函数
# 指定需要处理的列
columns_to_smooth = ['Sensor_A']
# 调用新函数
df_smoothed = smooth_dataframe_brush(
df_original,
target_columns=columns_to_smooth,
window_size=5,
threshold_factor=0.3,
max_brush_length=5
)
print("平滑后的 DataFrame:")
print(df_smoothed.head(10))
print("\n" + "="*50 + "\n")
# 3. 比较
comparison_df = df_original.copy()
comparison_df['Sensor_A_Smoothed'] = df_smoothed['Sensor_A']
comparison_df['Difference'] = comparison_df['Sensor_A'] - comparison_df['Sensor_A_Smoothed']
print("对比 (原始 Sensor_A vs 平滑后 vs 差异):")
print(comparison_df[['Sensor_A', 'Sensor_A_Smoothed', 'Difference']].head(10))