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))