一、背景与问题 1.1 原有预处理能力 OpsMind 的数据预处理模块已具备以下能力:
功能
方法
说明
数据诊断
diagnose()
缺失率、重复行、类型异常检测
基础清洗
clean()
去重、类型修正、缺失值填充
衍生属性
generate_derived()
年龄段分箱、日期维度展开
LLM 建议
suggest_derived()
根据查询意图建议衍生规则
1.2 遇到的问题 在实际使用中,我们发现了一个常见的数据格式问题:宽表结构 。
┌─────────────────────────────────────────────────────────────────────────┐ │ 典型宽表问题 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 原始数据(宽表): │ │ ┌──────────┬──────┬──────┬──────┬──────┬──────┐ │ │ │ Unnamed │ 2020 │ 2021 │ 2022 │ 2023 │ 2024 │ │ │ ├──────────┼──────┼──────┼──────┼──────┼──────┤ │ │ │ 北京 │ 100 │ 120 │ 150 │ 180 │ 200 │ │ │ │ 上海 │ 200 │ 220 │ 250 │ 280 │ 300 │ │ │ │ 广东 │ 150 │ 170 │ 190 │ 210 │ 230 │ │ │ └──────────┴──────┴──────┴──────┴──────┴──────┘ │ │ │ │ 问题: │ │ 1. 年份作为列名,无法按时间维度分析 │ │ 2. Unnamed 列名无语义,难以理解 │ │ 3. 无法直接绘制趋势图(需要年份作为行) │ │ │ │ 期望格式(长表): │ │ ┌──────────┬──────┬──────┐ │ │ │ 省份 │ 年份 │ 值 │ │ │ ├──────────┼──────┼──────┤ │ │ │ 北京 │ 2020 │ 100 │ │ │ │ 北京 │ 2021 │ 120 │ │ │ │ ... │ ... │ ... │ │ │ └──────────┴──────┴──────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘
1.3 其他痛点
异常值干扰 :极端值影响统计分析和图表展示
表头行错位 :Excel 导出时表头前有说明行,导致列名解析错误
Unnamed 列 :导出数据丢失列名,难以理解数据语义
二、新增功能概览 2.1 功能矩阵
功能
方法
说明
新增
数据诊断
diagnose()
缺失率、重复行、类型异常
-
基础清洗
clean()
去重、类型修正、缺失值填充
-
宽表检测
detect_structure()
检测年份列、日期列、Unnamed列
✅
结构归一化
normalize()
宽表→长表、表头校正
✅
异常值处理
_handle_outliers()
IQR 方法截断异常值
✅
列名推断
_infer_column_name()
根据内容推断语义名称
✅
衍生属性
generate_derived()
年龄段、日期维度
-
LLM 建议
suggest_derived()
智能衍生建议
-
一站式预处理
prepare()
全流程整合
升级
2.2 预处理流程升级 升级前: ┌─────────┐ ┌─────────┐ ┌─────────────┐ │ clean │ → │ suggest │ → │ generate │ │ │ │ derived │ │ derived │ └─────────┘ └─────────┘ └─────────────┘ 升级后: ┌───────────┐ ┌─────────┐ ┌───────────┐ ┌─────────┐ ┌─────────────┐ │ normalize │ → │ clean │ → │ outliers │ → │ suggest │ → │ generate │ │ (新增) │ │ │ │ (新增) │ │ derived │ │ derived │ └───────────┘ └─────────┘ └───────────┘ └─────────┘ └─────────────┘ │ ├── detect_structure() 检测宽表 ├── melt_wide_table() 宽表转长表 └── 表头行校正
三、宽表结构检测 3.1 检测逻辑 def detect_structure (self, df: pd.DataFrame ) -> Dict [str , Any ]: """ 轻量级宽表结构检测(无 LLM 调用) Returns: { needs_attention: bool, # 是否需要用户关注 confidence: float, # 检测置信度 (0-1) format: str, # "wide_year" | "wide_date" | "normal" year_cols: List[str], # 年份列名列表 date_str_cols: List[str], # 日期字符串列名列表 text_cols: List[str], # 非时间文本列名 nan_cols: List[str], # 全 NaN 列名 unnamed_cols: List[str], # Unnamed 列名 inferred_names: Dict, # 推断的列名映射 suggested_config: Dict, # 建议的转换配置 } """
3.2 检测维度 ┌─────────────────────────────────────────────────────────────────────────┐ │ 宽表检测维度 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ 1. 年份列检测 │ │ │ │ 列名可直接转 int,且落在 [1900, 2100] │ │ │ │ 例:2020, 2021, 2022, 2023, 2024 │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ 2. 日期字符串列检测 │ │ │ │ 匹配正则:2023Q1, Jan-2023, 2024-01 等 │ │ │ │ 例:2020Q1, 2020Q2, 2021Q1, 2021Q2 │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ 3. Unnamed 列检测 │ │ │ │ 列名以 "Unnamed:" 开头,通常是导出时丢失的索引列 │ │ │ │ 例:Unnamed: 0, Unnamed: 1 │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ 4. 全 NaN 列检测 │ │ │ │ 整列都是空值,通常是导出时的空白列 │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ 5. 表头行错位检测 │ │ │ │ 大多数列名是 Unnamed,但首行数据看起来更像列名 │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘
3.3 置信度计算 confidence = 0.0 if n_time >= 3 : time_ratio = n_time / n_cols confidence += 0.5 if time_ratio > 0.7 else (0.3 if time_ratio > 0.4 else 0.1 ) if n_text_obj == 1 : confidence += 0.3 elif n_text_obj > 1 : confidence += 0.1 if len (df) < 200 : confidence += 0.2 confidence = min (confidence, 1.0 )
四、结构归一化 4.1 normalize() 方法 def normalize (self, df: pd.DataFrame, config: Optional [Dict ] = None ) -> Tuple [pd.DataFrame, Dict ]: """ 结构归一化 Args: df: 原始 DataFrame config: 用户确认的转换配置(来自 UI),None 表示自动无损清理 Returns: (df_out, report) 元组 """
4.2 自动无损清理 当 config=None 时,自动执行无损操作:
if config is None : detection = self .detect_structure(df) nan_cols = detection.get("nan_cols" , []) if nan_cols: df = df.drop(columns=nan_cols) inferred = detection.get("inferred_names" , {}) if inferred: df = df.rename(columns=inferred) return df, {"action" : "auto_clean" , "details" : [...]}
4.3 宽表转长表(melt) 当检测到宽表且用户确认后:
if action == "melt" : df = df.melt( id_vars=id_vars_final, value_vars=value_vars, var_name="年份" , value_name="值" , )
转换示例 :
宽表: ┌──────────┬──────┬──────┬──────┐ │ 省份 │ 2022 │ 2023 │ 2024 │ ├──────────┼──────┼──────┼──────┤ │ 北京 │ 100 │ 120 │ 150 │ │ 上海 │ 200 │ 220 │ 250 │ └──────────┴──────┴──────┴──────┘ ↓ melt 转换 ↓ 长表: ┌──────────┬──────┬──────┐ │ 省份 │ 年份 │ 值 │ ├──────────┼──────┼──────┤ │ 北京 │ 2022 │ 100 │ │ 北京 │ 2023 │ 120 │ │ 北京 │ 2024 │ 150 │ │ 上海 │ 2022 │ 200 │ │ 上海 │ 2023 │ 220 │ │ 上海 │ 2024 │ 250 │ └──────────┴──────┴──────┘
4.4 表头行校正 处理 Excel 导出时表头前有说明行的情况:
unnamed_ratio = sum (1 for c in df.columns if str (c).startswith("Unnamed:" )) / n_cols if unnamed_ratio > 0.4 : row0_str_count = sum (1 for v in df.iloc[0 ] if isinstance (v, str ) and v.strip()) if row0_str_count / n_cols > 0.25 : suggested_header_row = 0 if header_row is not None : new_cols = [str (v).strip() for v in df.iloc[header_row]] df = df.iloc[header_row + 1 :].reset_index(drop=True ) df.columns = new_cols
五、异常值处理 5.1 IQR 方法 使用四分位距(Interquartile Range)检测异常值:
def _handle_outliers (self, df: pd.DataFrame, strategy: str = "cap" ) -> pd.DataFrame: """ IQR 法检测并处理数值列中的异常值 Args: df: DataFrame strategy: "cap" 截断 | "drop" 删除 """ for col in numeric_cols: q1, q3 = series.quantile(0.25 ), series.quantile(0.75 ) iqr = q3 - q1 lower, upper = q1 - 1.5 * iqr, q3 + 1.5 * iqr if strategy == "cap" : df[col] = df[col].clip(lower=lower, upper=upper) elif strategy == "drop" : df = df[(df[col] >= lower) & (df[col] <= upper)]
5.2 异常值判定标准 ┌─────────────────────────────────────────────────────────────────────────┐ │ IQR 异常值判定 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 数据分布: │ │ │ │ ──────────┬──────────┬──────────┬──────────┬─────────── │ │ │ │ │ │ │ │ lower Q1 median Q3 upper │ │ │ │ │ │ │ │ Q1-1.5×IQR │ │ │ Q3+1.5×IQR │ │ │ │ │ │ │ │ ──────────┴──────────┴──────────┴──────────┴─────────── │ │ │ │ 正常范围:[Q1 - 1.5×IQR, Q3 + 1.5×IQR] │ │ 异常值:< lower 或 > upper │ │ │ │ 处理策略: │ │ - cap: 将异常值截断到边界值(保留数据行) │ │ - drop: 删除含异常值的行(减少数据量) │ │ │ └─────────────────────────────────────────────────────────────────────────┘
六、一站式预处理升级 6.1 prepare() 方法升级 def prepare ( self, df: pd.DataFrame, query: Optional [str ] = None , use_llm_suggestions: bool = True , table_config: Optional [Dict ] = None , ) -> tuple : """ 一站式预处理 流程: normalize → clean → outliers → suggest_derived → generate_derived Returns: (df_cleaned, cleaning_report) 元组 """
6.2 完整流程 def prepare (self, df, query, use_llm_suggestions, table_config ): self ._cleaning_report = {} df, norm_report = self .normalize(df, table_config) self ._cleaning_report["normalize" ] = norm_report df = self .clean(df) df = self ._handle_outliers(df, strategy="cap" ) suggestions = [] if use_llm_suggestions and self .client and query: suggestions = self .suggest_derived(df, query) df = self .generate_derived(df, suggestions=suggestions) return df, self ._cleaning_report
6.3 清洗报告示例 { "normalize" : { "action" : "melt" , "details" : [ "重设表头: 使用第 0 行作为列名" , "删除指定列: ['Unnamed: 5']" , "宽表转长表完成: 150行×3列" ] }, "clean" : { "duplicates_dropped" : 5 , "type_conversions" : ["'销售额' 转为数值型" ], "missing_filled" : ["'地区'(缺失率3.2%, 填充方式:众数('华东'))" ], "columns_dropped" : [], "shape_before" : "50行×6列" , "shape_after" : "45行×5列" }, "outliers" : [ "'销售额': 3个异常值已截断至 [1000, 50000]" ], "llm_suggestions_applied" : 2 }
七、实际应用场景 7.1 场景一:年度数据宽表 原始数据 :
┌──────────┬──────┬──────┬──────┬──────┬──────┐ │ Unnamed │ 2020 │ 2021 │ 2022 │ 2023 │ 2024 │ ├──────────┼──────┼──────┼──────┼──────┼──────┤ │ 北京 │ 100 │ 120 │ 150 │ 180 │ 200 │ │ 上海 │ 200 │ 220 │ 250 │ 280 │ 300 │ │ 广东 │ 150 │ 170 │ 190 │ 210 │ 230 │ └──────────┴──────┴──────┴──────┴──────┴──────┘
检测结果 :
{ "needs_attention" : True , "confidence" : 0.9 , "format" : "wide_year" , "year_cols" : ["2020" , "2021" , "2022" , "2023" , "2024" ], "unnamed_cols" : ["Unnamed: 0" ], "inferred_names" : {"Unnamed: 0" : "省份" }, "suggested_config" : { "action" : "melt" , "id_vars" : ["Unnamed: 0" ], "value_vars" : ["2020" , "2021" , "2022" , "2023" , "2024" ], "var_name" : "年份" , "value_name" : "值" , "rename_id_var" : "省份" } }
转换后 :
┌──────────┬──────┬──────┐ │ 省份 │ 年份 │ 值 │ ├──────────┼──────┼──────┤ │ 北京 │ 2020 │ 100 │ │ 北京 │ 2021 │ 120 │ │ ... │ ... │ ... │ └──────────┴──────┴──────┘
7.2 场景二:季度数据宽表 原始数据 :
┌──────────┬─────────┬─────────┬─────────┬─────────┐ │ 产品 │ 2023Q1 │ 2023Q2 │ 2023Q3 │ 2023Q4 │ ├──────────┼─────────┼─────────┼─────────┼─────────┤ │ A类 │ 1000 │ 1200 │ 1500 │ 1800 │ │ B类 │ 2000 │ 2200 │ 2500 │ 2800 │ └──────────┴─────────┴─────────┴─────────┴─────────┘
检测结果 :
{ "format" : "wide_date" , "date_str_cols" : ["2023Q1" , "2023Q2" , "2023Q3" , "2023Q4" ], "suggested_config" : { "action" : "melt" , "var_name" : "时间" , "value_name" : "值" } }
7.3 场景三:表头行错位 原始数据 :
┌──────────┬──────────┬──────────┬──────────┐ │ Unnamed │ Unnamed │ Unnamed │ Unnamed │ ├──────────┼──────────┼──────────┼──────────┤ │ 地区 │ 产品 │ 销售额 │ 利润 │ ← 真正的列名 ├──────────┼──────────┼──────────┼──────────┤ │ 华东 │ A类 │ 1000 │ 200 │ │ 华南 │ B类 │ 2000 │ 400 │ └──────────┴──────────┴──────────┴──────────┘
检测结果 :
{ "suggested_header_row" : 0 , }
校正后 :
┌──────────┬──────────┬──────────┬──────────┐ │ 地区 │ 产品 │ 销售额 │ 利润 │ ├──────────┼──────────┼──────────┼──────────┤ │ 华东 │ A类 │ 1000 │ 200 │ │ 华南 │ B类 │ 2000 │ 400 │ └──────────┴──────────┴──────────┴──────────┘
八、总结与展望 8.1 本次升级总结
方面
升级前
升级后
宽表处理
❌ 不支持
✅ 自动检测 + 智能转换
异常值
❌ 不处理
✅ IQR 检测 + 截断
列名推断
❌ 无
✅ 根据内容推断语义名
表头校正
❌ 无
✅ 自动检测错位表头
流程完整性
3 步
5 步
8.2 技术亮点
零 LLM 调用检测 :detect_structure() 纯规则检测,无延迟
置信度评估 :量化检测结果可信度,辅助用户决策
无损优先 :自动清理只执行无损操作,有损操作需用户确认
完整报告 :每步操作都有详细日志,便于追溯
8.3 后续规划
功能
说明
优先级
多级宽表支持
支持多个维度列的宽表转换
🟡 中
自定义分箱规则
用户可配置分箱边界
🟡 中
数据质量评分
综合评估数据质量
🟢 低
增量预处理
支持数据追加时的增量处理
🟢 低
本次升级让 OpsMind 的数据预处理能力从”简单清洗”进化为”智能结构化”,能够自动识别和处理复杂的数据格式问题,大大提升了数据分析的效率和准确性。
本文由 OpsMind 技术团队撰写,转载请注明出处。