1. 项目概述:为什么数据清洗不是“脏活”,而是数据工作的命脉

你刚拿到一份来自业务部门的Excel表格,标题叫《2024Q2用户行为汇总》,点开第一眼:A列是“用户ID”,但第17行突然变成“———”;B列“注册时间”里混着“2024-03-15”、“15/03/2024”、“Mar 15, 2024”三种格式,还有两行写着“待补录”;C列“消费金额”本该是数字,却出现了“¥899.00”“899元”“899.00(含税)”;D列“城市”里,“北京”“北京市”“beijing”“BJ”并存;更别提整张表里零星散落的空行、重复记录、以及最后一行赫然写着“以上数据截止至2024-06-30,如有疑问请联系王经理”。这不是个例,这是每天早上九点你打开Jupyter Notebook时最真实的开场。 Common Data Cleaning Tasks in Everyday Work of a Data Scientist/Analyst in Python ——这个标题看似平淡,实则浓缩了数据从业者80%的日常真实工作量。它不是模型训练前的“准备步骤”,而是贯穿整个分析生命周期的呼吸系统:数据不干净,再漂亮的可视化也是沙上筑塔,再前沿的算法也会输出垃圾结论。我带过三届实习生,几乎所有人第一次独立跑通一个完整分析流程,卡在清洗环节的时间平均是建模+解读的2.3倍。这不是因为技术门槛高,而是因为清洗没有标准答案——它高度依赖业务语境、数据来源逻辑和人的判断力。比如,“用户ID为空”是该删掉,还是该用规则反推?“消费金额为负数”是退款记录,还是录入错误?这些决策背后,是财务口径、产品逻辑、风控规则的综合映射。所以,本文不讲“pandas.fillna()怎么用”,而是带你拆解真实工单里高频出现的12类清洗场景,还原每一步操作背后的业务思考链路、参数选择依据、以及我踩过的那些让日报延迟两小时的坑。适合刚转行的数据新人、想摆脱“取数工具人”标签的分析师,以及需要给团队制定清洗SOP的Tech Lead——因为最终交付给老板的,从来不是一段能跑通的代码,而是一份经得起业务方当面质疑的、有据可查的数据资产。

2. 核心清洗任务全景图:从“看到问题”到“定义规则”的思维跃迁

2.1 为什么不能直接套用“清洗模板”?——数据质量的四维坐标系

很多新手会搜索“pandas数据清洗大全”,下载一个包含20个函数的notebook,然后机械地执行:缺失值填充→去重→类型转换→标准化。结果跑完发现,销售漏掉了3个重点城市的订单,用户分群时把VIP客户误判为新客。问题出在哪?在于跳过了最关键的一步: 定义数据质量维度 。在真实工作中,我们用四个相互制约的维度来评估一条记录是否“可用”,缺一不可:

  • 完整性(Completeness) :该有的字段是否都有值?例如,用户注册流程中,“手机号”和“验证码”必须同时存在才构成有效注册事件,单独填了手机号但没验证,这条记录在风控场景下就是不完整的,不能参与活跃度计算。

  • 一致性(Consistency) :同一概念在不同表、不同时间、不同来源中表达是否统一?比如CRM系统里“客户等级”是“钻石/黄金/白银”,而订单系统里是“Level1/Level2/Level3”,清洗时必须建立双向映射表,而不是简单用字符串替换。

  • 准确性(Accuracy) :数值或分类是否符合业务事实?“用户年龄=200岁”显然不准,但“订单金额=0.0001元”可能准确——它是某次积分抵扣后的实际支付额。准确性必须结合业务规则库校验,而非仅靠统计阈值。

  • 时效性(Timeliness) :数据是否在要求的时间窗口内更新?一份标注“T+1”的日活报表,如果凌晨3点还没入库,那当天的分析结论就失去决策价值。清洗脚本必须内置时效性检查,超时自动告警而非强行处理。

这四个维度像一张网,任何清洗动作都必须回答:“这步操作提升了哪个维度?是否会损害另一个维度?”例如,用众数填充“城市”字段,提升了完整性,但若众数是“北京”,而实际缺失的是“乌鲁木齐”用户,就损害了准确性。真正的清洗高手,脑子里永远有这张四维坐标系,而不是函数手册。

2.2 高频任务TOP12:按发生频率与破坏力排序的真实战场

基于我过去三年审阅的137份生产环境清洗脚本(覆盖电商、金融、SaaS、教育四类行业),整理出12类最高频、且一旦出错影响最大的清洗任务。注意,这里的排序不是按技术难度,而是按 对下游分析结果的污染概率

排名 任务名称 典型表现 业务影响案例 清洗核心难点
1 混合数据类型的数值列 “金额”列含“¥1299”“1299.00元”“1299(促销价)”“NULL”“-” 财务对账时,因未识别“-”为0,导致月度GMV虚高1.2% 正则提取需兼顾多国货币符号、单位、括号修饰
2 日期格式碎片化 同一“下单时间”列存在“2024-03-15”“15/03/2024”“Mar 15, 2024”“20240315”“空白” 用户生命周期分析中,因“20240315”被解析为2024年1月1日,导致新客留存率计算失真 多格式共存时, pd.to_datetime() infer_datetime_format 易误判
3 语义重复的分类值 “省份”列含“广东”“广东省”“guangdong”“GD”“粤” 地域运营策略中,将“粤”和“广东”视为不同省份,导致广东资源投放被拆成两份 需构建业务词典,而非简单lower()或strip()
4 隐式缺失值编码 “用户状态”列用“-”“N/A”“0”“NULL”“”表示未激活,“1”表示已激活 风控模型将“0”当作有效状态参与训练,误判大量沉默用户为高风险 必须区分“缺失”与“有效值0”,需业务方确认编码规则
5 跨表主键不一致 用户表主键为“user_id”,订单表外键为“uid”,且订单表中“uid”有“U12345”“u12345” 用户行为路径分析时,大小写不敏感匹配失败,丢失37%的订单关联关系 主键清洗必须在JOIN前完成,且需统一大小写+去空格+校验长度
6 异常值的业务合理性判断 “单日登录次数”均值为5,但存在“9999”“-1”“999999999” 活跃度模型将测试账号(9999次)当作真实用户,扭曲DAU分布 不能只用IQR或Z-score,需结合账号类型、设备指纹等上下文
7 结构化文本中的嵌套信息 “地址”列含“北京市朝阳区建国路8号SOHO现代城A座1001室” 地域分析需精确到区级,但直接切分“朝阳区”会漏掉“朝阳”“朝阳区”“Chaoyang District” 需调用地理编码API或预置行政区划知识图谱
8 重复记录的业务去重逻辑 同一订单在支付成功、物流发货、签收完成三个节点各生成一条记录 GMV统计重复计算,导致营收目标达成率虚高200% 去重键不能只用订单号,需结合事件类型、时间戳、状态码
9 多源数据的字段对齐 A表“产品ID”为“P1001”,B表“商品编码”为“1001”,C表“SKU”为“PROD-1001” 商品销量归因分析时,因ID未对齐,无法合并三方数据源 需建立主数据管理(MDM)映射表,而非临时映射
10 时间序列的断点修复 IoT设备上报数据,因网络问题,连续2小时无数据,但前后时间戳跳跃 设备故障预测模型因输入序列断裂,误判为正常停机 插值需区分“设备关机”与“数据丢失”,前者不应插值
11 文本字段的噪声过滤 “用户反馈”列含“!!!差评!!!”“ <script></script> ”“[图片]” NLP情感分析时,HTML标签被当作词汇,导致负面情绪误判 过滤需保留业务相关标点(如“!”表强烈情绪),去除技术噪声
12 权限控制导致的字段截断 API返回的“用户全名”在测试环境为“张三”,生产环境因GDPR被截断为“张*” 用户画像构建时,姓名字段在不同环境长度不一致,导致特征工程失败 清洗脚本需适配多环境配置,而非硬编码规则

这份清单的价值,不在于告诉你“怎么做”,而在于帮你建立 问题敏感度 。当你下次看到一个新数据集,第一反应不该是“先fillna()”,而是快速扫描:它属于哪几类?排名前三的破坏力任务是否存在?这能帮你把80%的精力聚焦在真正致命的问题上。

2.3 清洗策略的黄金三角:规则驱动、统计驱动、业务驱动

面对同一份脏数据,不同角色会给出不同清洗方案,这恰恰揭示了清洗的本质——它是技术、统计与业务的三角博弈。我见过太多因“纯技术思维”翻车的案例:一位算法工程师用KNN填充用户收入,结果把应届生的“0”收入(实习期)全部填成了行业平均35万,导致薪酬分析报告被HRD当场否决。正确的策略必须平衡三方:

  • 规则驱动(Rule-based) :适用于有明确业务定义的场景。例如,“订单状态”字段,业务方明确定义:“0=待支付,1=已支付,2=已发货,3=已完成,-1=已取消”。清洗时必须严格按此映射,任何统计推断都是越界。规则驱动的优势是 可解释、可审计、零争议 ,缺点是维护成本高,需随业务变更同步更新。

  • 统计驱动(Statistical) :适用于无明确规则、但分布稳定的场景。例如,“用户单次访问时长”,历史数据显示95%集中在1-120秒,那么>300秒的记录大概率是埋点错误。此时用IQR(四分位距)识别异常值比拍脑袋定阈值更可靠。统计驱动的优势是 适应性强、自动化程度高 ,缺点是黑箱,业务方难以理解为何“150秒”被判定为异常。

  • 业务驱动(Business-driven) :这是最高阶的策略,它不直接清洗数据,而是清洗 数据产生逻辑 。例如,发现“优惠券使用率”突降50%,技术排查无异常,最终发现是市场部把“满100减10”改成了“满200减10”,规则变了,但数据口径没同步。此时清洗动作是:在数据字典中标注该指标的计算逻辑变更时间点,并为历史数据打上“规则版本”标签。业务驱动的优势是 根治问题、避免重复劳动 ,缺点是需要深度介入业务流程,非纯技术岗能轻易推动。

在实战中,我坚持“80%规则驱动 + 15%统计驱动 + 5%业务驱动”的配比。新手常犯的错误是过度依赖统计驱动,试图用算法解决所有问题;而资深者会花30%时间与业务方对齐规则,确保清洗逻辑本身就在正确轨道上。

3. 核心清洗任务深度拆解:手把手还原12个高频场景的完整决策链

3.1 混合数据类型的数值列:从“¥1299”到纯数字的精准萃取

场景还原 :电商后台导出的“成交金额”列,原始数据如下:

['¥1299.00', '1299元', '1299.00(含税)', '¥89.9', '89.90', 'NULL', '-', '1299.0000', '1,299.00']

直接 pd.to_numeric(df['amount'], errors='coerce') 会把所有含文字的行转为NaN,损失率达70%。但业务方强调:这些“带符号”记录都是真实有效订单,必须保留。

我的决策链

  1. 第一步:识别所有干扰模式
    人工抽样1000条,归纳出6类干扰:

    • 货币符号前置(¥、$、€)
    • 单位后缀(元、USD、CNY)
    • 括号修饰((含税)、(促销价)、(赠品))
    • 千分位逗号(1,299.00)
    • 隐式缺失('NULL'、'-'、'')
    • 纯数字(含小数、整数、科学计数法)
  2. 第二步:设计正则提取规则
    关键洞察: 所有有效金额的数字部分都连续存在,且小数点最多出现一次 。因此,正则目标不是“删除所有非数字”,而是“捕获最长的、符合金额格式的数字子串”。我采用两阶段正则:

    # 第一阶段:剥离确定的干扰前缀/后缀
    df['cleaned'] = df['amount'].str.replace(r'^[¥$€]+|[元USD]+$', '', regex=True)
    # 第二阶段:提取核心数字(支持千分位和小数)
    pattern = r'(\d{1,3}(?:,\d{3})*(?:\.\d+)?|\d+(?:\.\d+)?)'
    df['numeric_str'] = df['cleaned'].str.extract(pattern, expand=False)
    

    提示: (?:,\d{3})* 匹配零次或多次“逗号+三位数字”, (?:\.\d+)? 匹配可选的小数部分, ? 表示前面的组可选,避免匹配到“1299(含税)”中的“1299”。

  3. 第三步:处理千分位与类型转换
    df['numeric_str'] 中仍有“1,299.00”,需移除逗号再转浮点:

    df['amount_clean'] = pd.to_numeric(
        df['numeric_str'].str.replace(',', ''), 
        errors='coerce'
    )
    
  4. 第四步:隐式缺失值的业务映射
    对于 'NULL' '-' ,不能简单填0(会扭曲GMV均值)。我创建映射字典:

    missing_map = {'NULL': np.nan, '-': np.nan, '': np.nan}
    df['amount_clean'] = df['amount'].map(missing_map).fillna(df['amount_clean'])
    

    最终,有效数据保留率从30%提升至98.7%,且所有NaN均可追溯到原始值,满足审计要求。

实操心得

  • 切忌用 str.replace(r'[^0-9.]', '') 粗暴删除,它会把“-1299”变成“1299”,把“1299.00(赠品)”变成“1299.00赠品”,后者仍无法转数字。
  • 千分位逗号必须在 to_numeric 前移除,否则 to_numeric('1,299.00') 会返回NaN。
  • 我在脚本开头固定添加 # [RULE] 金额清洗:2024-06-15 业务确认"-"和"NULL"代表数据缺失,不参与统计 ,确保半年后有人接手时知道依据。

3.2 日期格式碎片化:让“Mar 15, 2024”和“20240315”和平共处

场景还原 :某SaaS公司客户导入的“签约日期”列,包含7种格式:

['2024-03-15', '15/03/2024', 'Mar 15, 2024', '20240315', '2024/03/15', '15-Mar-2024', '']

pd.to_datetime(df['date'], infer_datetime_format=True) 在遇到“15/03/2024”时,会默认按美式格式解析为2024年15月3日(报错),而 infer_datetime_format=False 又太慢。

我的决策链

  1. 第一步:按格式复杂度分层处理
    将7种格式分为三类:

    • 标准ISO格式 2024-03-15 , 2024/03/15 ):直接 to_datetime ,最快。
    • 固定长度数字格式 20240315 ):用 format='%Y%m%d' 精确解析,避免歧义。
    • 文本月份格式 Mar 15, 2024 , 15-Mar-2024 ):需指定 format ,且要兼容英文缩写。
  2. 第二步:构建多格式解析器
    使用 dateutil.parser.parse 虽灵活但慢,我改用 try-except 链式解析,优先尝试快的:

    def parse_date(date_str):
        if not isinstance(date_str, str) or not date_str.strip():
            return pd.NaT
        # 1. 尝试ISO格式(最快)
        try:
            return pd.to_datetime(date_str, format='%Y-%m-%d')
        except:
            pass
        # 2. 尝试数字格式
        try:
            return pd.to_datetime(date_str, format='%Y%m%d')
        except:
            pass
        # 3. 尝试英文月份格式(显式指定format,比infer快10倍)
        try:
            # 支持 "Mar 15, 2024" 和 "15-Mar-2024"
            for fmt in ['%b %d, %Y', '%d-%b-%Y', '%d/%m/%Y']:
                try:
                    return pd.to_datetime(date_str, format=fmt)
                except:
                    continue
        except:
            pass
        return pd.NaT
    
    df['date_clean'] = df['date'].apply(parse_date)
    
  3. 第三步:处理歧义格式“15/03/2024”
    这是最大雷区。业务方确认:所有客户数据均为 日/月/年 格式(非美式月/日/年)。因此,我强制指定 dayfirst=True

    # 在链式解析最后补充
    try:
        return pd.to_datetime(date_str, dayfirst=True)
    except:
        pass
    
  4. 第四步:验证与修复
    解析后,检查 date_clean 中是否存在 NaT ,对剩余 NaT 进行人工复核。我发现“2024/03/15”被正确解析,但“15/03/2024”在 dayfirst=True 下也正确,而“03/15/2024”(美式)极少出现,可忽略。

实操心得

  • infer_datetime_format=True 在混合格式下极易误判,宁可多写几行 try-except ,也要保证精度。
  • 英文月份缩写( %b )默认支持 Jan - Dec ,无需额外映射。
  • 我在脚本中固定添加 # [BUSINESS] 所有日期均为日/月/年格式,由客户成功团队2024-06-10书面确认 ,避免后续争议。
  • 性能对比:链式解析耗时1.2秒(10万行), infer=True 耗时3.8秒且错误率23%。

3.3 语义重复的分类值:让“粤”“广东”“guangdong”指向同一个地理实体

场景还原 :某地图App的“用户常驻省份”列,包含:

['广东', '广东省', 'guangdong', 'GD', '粤', '广东 ', '  广东  ', 'GDSHENG']

df['province'].str.lower().str.strip() 只能解决大小写和空格,对“粤”“GD”无效,而 replace({'粤':'广东', 'GD':'广东'}) 又太脆弱,新增“GDSHENG”就得改代码。

我的决策链

  1. 第一步:构建业务词典(非技术词典)
    不是简单映射,而是按 业务实体层级 组织:

    • 标准名 :国家规定的正式名称(“广东省”)
    • 简称 :政府公文常用缩写(“广东”)
    • 拼音 pypinyin 生成的标准拼音(“guangdong”)
    • 代码 :国家标准GB/T 2260中的两位字母码(“GD”)
    • 古称/别称 :地方文化中通用的称呼(“粤”)
      词典结构为 {标准名: [简称, 拼音, 代码, 古称]} ,例如: {"广东省": ["广东", "guangdong", "GD", "粤"]}
  2. 第二步:逆向映射生成清洗字典
    将词典扁平化为 {模糊值: 标准名}

    province_dict = {}
    for std_name, variants in province_mapping.items():
        for v in variants:
            # 清洗变体:去空格、转小写、去标点
            clean_v = re.sub(r'[^\w]', '', v.strip().lower())
            province_dict[clean_v] = std_name
    # 结果:{'guangdong': '广东省', 'gd': '广东省', '粤': '广东省', '广东': '广东省'}
    
  3. 第三步:应用映射并处理未知值

    df['province_clean'] = df['province'].str.strip().str.lower().str.replace(r'[^\w]', '')
    df['province_clean'] = df['province_clean'].map(province_dict).fillna(df['province_clean'])
    # 对仍为原值的,标记为"UNKNOWN",供人工审核
    df.loc[df['province_clean'].isin(df['province']), 'province_clean'] = 'UNKNOWN'
    
  4. 第四步:动态扩展机制
    词典存为JSON文件,脚本启动时加载。当发现新变体(如“GDSHENG”),脚本自动记录到 unknown_provinces.log ,并邮件通知数据负责人更新词典。

实操心得

  • 绝对不要用 fuzzywuzzy 做实时模糊匹配,10万行数据耗时分钟级,且阈值难调(“广西”和“广东”相似度高达85%)。
  • 词典必须由业务方(如区域运营总监)签字确认,而非数据团队自定义。
  • 我在词典JSON中为每个条目添加 "source": "GB/T 2260-2007" "source": "客户成功团队2024-06-10会议纪要" ,确保可追溯。
  • 实测:词典映射耗时0.08秒(10万行),准确率100%,而模糊匹配耗时47秒,准确率仅63%。

3.4 隐式缺失值编码:区分“没填”和“填了0”

场景还原 :信贷系统的“历史逾期次数”列,原始值:

['0', '1', '2', 'NULL', '-', '0次', '无', '']

df['overdue'].fillna(0) 会把“NULL”和“-”变成0,但业务方强调:“0”表示用户信用极好,从未逾期;“NULL”表示该用户是新客,征信报告尚未拉取,其风险未知,不能参与逾期率计算。

我的决策链

  1. 第一步:穷举所有隐式缺失编码
    与风控团队逐条确认:

    • 'NULL' , '-' , '' , 'N/A' , '未知' 真缺失(Unknown)
    • '0' , '0次' , '无' 有效值0(Zero)
    • '1' , '2' 有效值(Positive)
  2. 第二步:创建三态标记列
    不直接修改原列,而是新增 overdue_status 列,标记每条记录的状态:

    def get_overdue_status(x):
        x = str(x).strip()
        if x in ['NULL', '-', '', 'N/A', '未知']:
            return 'UNKNOWN'
        elif x in ['0', '0次', '无']:
            return 'ZERO'
        elif x.isdigit() and int(x) > 0:
            return 'POSITIVE'
        else:
            return 'INVALID'  # 触发告警
    
    df['overdue_status'] = df['overdue'].apply(get_overdue_status)
    
  3. 第三步:按状态分流处理

    • UNKNOWN :在后续分析中 query("overdue_status != 'UNKNOWN'") 过滤,或用 np.nan 占位。
    • ZERO :转为整数0,参与计算。
    • POSITIVE :转为整数。
    • INVALID :记录到 error_log.csv ,人工介入。
  4. 第四步:固化业务规则
    get_overdue_status 函数封装为 data_quality_rules.py 模块,所有新项目必须导入此模块,确保规则复用。

实操心得

  • 永远不要在清洗脚本中直接 fillna(0) ,除非业务方白纸黑字写明“所有缺失即0”。
  • 三态标记(UNKNOWN/ZERO/POSITIVE)比二态(MISSING/VALID)更能反映业务现实。
  • 我在脚本中添加 # [AUDIT] 2024-06-12 风控总监李XX邮件确认:'NULL'代表征信未拉取,不得参与模型训练
  • 新人常犯错误:把 '0次' str.isdigit() 判为False,从而归入 INVALID ,其实应先 re.sub(r'[^0-9]', '', x) 提取数字。

3.5 跨表主键不一致:让“U12345”和“u12345”在JOIN前握手言和

场景还原 :用户表 users.csv 主键为 user_id (值如 U12345 ),订单表 orders.csv 外键为 uid (值如 u12345 ),且 uid 中混有空格: ' u12345 '

我的决策链

  1. 第一步:主键清洗必须在JOIN前完成
    错误做法: pd.merge(users, orders, left_on='user_id', right_on='uid', how='left') ,再清洗。
    正确做法:分别清洗两表主键,再JOIN。因为JOIN后字段混合,清洗逻辑混乱。

  2. 第二步:定义主键清洗黄金法则

    • 统一大小写 upper() lower() ,选其一并全局遵守。
    • 去除首尾空格 .strip()
    • 校验长度与格式 :如 user_id 应为 U +5位数字,用正则 ^U\d{5}$ 校验。
    • 拒绝非法值 :不满足格式的,设为 np.nan ,JOIN时自动丢弃。
  3. 第三步:实施清洗

    # 用户表
    users['user_id_clean'] = users['user_id'].str.strip().str.upper()
    users = users[users['user_id_clean'].str.match(r'^U\d{5}$')].copy()
    
    # 订单表
    orders['uid_clean'] = orders['uid'].str.strip().str.upper()
    orders = orders[orders['uid_clean'].str.match(r'^U\d{5}$')].copy()
    
    # JOIN
    merged = users.merge(orders, left_on='user_id_clean', right_on='uid_clean', how='inner')
    
  4. 第四步:监控主键健康度
    在清洗后添加质量检查:

    print(f"用户表主键合规率: {len(users)/len(original_users):.2%}")
    print(f"订单表主键合规率: {len(orders)/len(original_orders):.2%}")
    if len(merged) < 0.95 * len(orders):
        send_alert("主键匹配率低于95%,请检查数据源")
    

实操心得

  • 主键清洗是唯一允许“暴力丢弃”的环节,因为不合规的主键无法建立可信关联。
  • 大小写统一选 upper() ,因为 U12345 u12345 更常见于企业系统。
  • 我在ETL流程中,主键清洗是第一个独立步骤,输出 users_clean.csv orders_clean.csv ,供其他团队复用。
  • 曾因忘记 strip() ,导致 ' u12345 ' 'U12345' 无法匹配,丢失23%订单,教训深刻。

4. 工程化实践:如何让清洗脚本从“能跑通”升级为“可交付、可审计、可演进”

4.1 清洗脚本的四大交付物:超越代码的完整资产包

一份合格的清洗脚本,绝不是 .py 文件加 .ipynb 。我要求团队交付以下四大资产,缺一不可:

  1. 可执行脚本( clean_data.py

    • 必须支持命令行参数: python clean_data.py --input data/raw/ --output data/clean/ --config config/prod.yaml
    • 内置日志:记录每步处理行数、耗时、异常数,输出到 logs/clean_20240615.log
    • 错误处理: try-except 捕获具体异常,而非 except Exception ,并打印原始值。
  2. 配置文件( config/prod.yaml

    • 分离规则与代码: missing_values: ["NULL", "-", "N/A"] date_formats: ["%Y-%m-%d", "%Y%m%d"]
    • 环境隔离: prod staging dev 配置独立,避免测试环境规则污染生产。
  3. 数据字典( docs/data_dictionary.md

    • 每个字段注明:原始名、清洗后名、清洗规则(如“金额:正则提取数字,千分位逗号移除”)、业务含义、空值含义。
    • 版本化:每次规则变更,更新 v1.2 -> v1.3 ,并说明变更原因(如“2024-06-10 新增‘GDSHENG’映射”)。
  4. 质量报告( reports/quality_report_20240615.html

    • 自动生成:缺失率热力图、异常值分布、主键匹配率、清洗前后行数对比。
    • 可视化:用 plotly 生成交互图表,支持钻取到具体问题行。

提示:没有质量报告的清洗,等于没有清洗。老板不会看代码,但会看“清洗后数据缺失率从12%降至0.3%”的图表。

4.2 避坑指南:那些让日报延迟两小时的“小问题”

  • 坑1: inplace=True 的幻觉
    df.dropna(inplace=True) 看似省事,但调试时无法查看中间状态。我坚持 df = df.dropna() ,并在关键步骤后加 print(f"dropna后剩余{len(df)}行")

  • 坑2: pd.concat 的索引灾难
    pd.concat([df1, df2]) 默认保留原索引,导致合并后索引重复(如 [0,1,2,0,1,2] ),后续 df.iloc[3] 取到 df2 的第0行。必须加 ignore_index=True

  • 坑3: str.contains() 的正则陷阱
    df['col'].str.contains('123') 会把 '1234' 也匹配到。应加 regex=False '123\b' \b 表示单词边界)。

  • 坑4:时区的隐形杀手
    pd.to_datetime('2024-03-15') 默认为本地时区,而`pd.to

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐