ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

【FDE系列】阶段2:Day 34:数据清洗 — 把脏数据捋干净

【FDE系列】阶段2:Day 34:数据清洗 — 把脏数据捋干净 前言FDE系列内容总纲【大纲】FDE 前沿部署工程师学习系列教程-CSDN博客前置课程列表见文档结尾附录。阶段2·Day 34数据清洗 — 把脏数据捋干净FDE 学习系列教程 · 第二阶段 · 第 3 周 · Day 4预计时长2.5 小时 | 难度★★★☆☆ | 前置知识SQL 基础、Python 文件读写、logging 日志一句话目标识别四类常见脏数据用 Python SQL 完成去重、缺失值处理、格式对齐、类型转换和脱敏把一份脏 CSV 洗成标准结构化数据入库。‍‍ 开场FDE 最真实的工作场景来了兄弟前三天学的查询前提都是数据是干净的。但现实会狠狠给你上一课。客户发来一个 Excel 导出的 CSV你满怀期待地打开——设备名 温度 负责人 手机号 ← 问题 注塑机A1 65 李工 13812345678 注塑机A1 65 李工 13812345678 ← ① 跟上面完全重复 注塑机A2 八十二 王工 ← ② 温度是中文手机号缺失 注塑机a3 91 赵工 138-1234-5678 ← ③ 设备名小写、手机带横杠 注塑机 A3 91 赵工 13812345678 ← ④ 名字多空格和上一行其实重复 冲压机B1 75 李工 13812345678 冲压机B1 75 李工 13812345678 ← ⑤ 又一条完全重复 注塑机A4 赵工 13811112222 ← ⑥ 温度为空这就是江湖人称的脏数据Dirty Data。这种东西直接进数据库统计结果全错、JOIN 关联不上、报表数字打架。客户还会一脸无辜地问你我们的数据……挺整齐的呀今天你就学会怎么把它洗干净。这活儿不炫技但数据清洗往往占数据分析 60% 以上的时间是 FDE 的硬功夫。 一、先认清四类脏数据别上来就写代码先建立脏数据分类学。对症下药才不乱┌──────────────────────────────────────────────────────────────┐ │ 脏数据的四大门派 │ ├──────────────┬───────────────────────────────────────────────┤ │ ① 重复数据 │ 同一条记录出现多次 │ │ (Duplicate) │ 完全重复 / 实质重复大小写空格差异 │ │ │ 危害统计数量翻倍、SUM 偏大 │ ├──────────────┼───────────────────────────────────────────────┤ │ ② 缺失值 │ 该有的数据是空的 │ │ (Missing) │ 温度没填、手机号空、NULL │ │ │ 危害AVG 算错、JOIN 丢数据、程序报错 │ ├──────────────┼───────────────────────────────────────────────┤ │ ③ 格式不一致 │ 同一个意思写法五花八门 │ │ (Inconsistent)│ 82 / 八十二 / 约82度 │ │ │ 138-1234-5678 / 13812345678 │ │ │ 注塑机a3 / 注塑机 A3 / 注塑机A3 │ │ │ 危害去重失效、关联不上、无法计算 │ ├──────────────┼───────────────────────────────────────────────┤ │ ④ 类型/异常值│ 数字列混进文字、温度写成 9999传感器故障 │ │ (Wrong Type)│ 危害转 float 崩溃、统计被极端值带偏 │ └──────────────┴──────────────────────────────────────────────┘清洗总原则先标准化去空格、统一大小写再去重最后处理缺失和类型。顺序很重要——如果不先把注塑机a3和注塑机 A3标准化成同一个样子去重时根本认不出它俩是重复的。清洗流水线全景图脏 CSV ① 标准化 ② 去重 ③ 补缺/转类型 干净数据库 ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 重复行 │ │ 去空格 │ │ 完全重复 │ │ 温度→数字 │ │ │ │ 中文数字 │ ───► │ 统一大小写│ ───► │ 去掉 │ ─► │ 缺失→NULL │ ──► │ MySQL │ │ 空手机号 │ │ 去横杠 │ │ 实质重复 │ │ 手机号兜底 │ │ 干净表 │ │ 大小写乱 │ │ 去多余字符│ │ 去掉 │ │ 记录日志 │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ 每一步都打日志改了什么可追溯️ 二、准备脏数据在sql_practice文件夹新建dirty_data.csv逐字粘进去故意做脏device_name,temperature,owner_name,phone,type 注塑机A1,65,李工,13812345678,注塑 注塑机A1,65,李工,13812345678,注塑 注塑机A2,八十二,王工,,注塑 注塑机a3,91,赵工,138-1234-5678,注塑 注塑机 A3,91,赵工,13812345678,注塑 冲压机B1,75,李工,13812345678,冲压 冲压机B1,75,李工,13812345678,冲压 冲压机B2,88,陈工,13987654321,冲压 注塑机A4,,赵工,13811112222,注塑对照清单确认你能指出每行的毛病行毛病门派1-2完全重复① 重复3温度八十二、手机空③格式 ②缺失4设备名小写a3、手机带横杠③格式5设备名带空格和空格与第4行实质重复③格式 ①重复6-7完全重复① 重复9温度为空②缺失️ 三、写清洗脚本Python 标准库版这节也简单会的同学可以重点看清洗顺序和日志记录两个思路。新建06_clean.py 数据清洗实战从脏 CSV 到干净数据库 文件06_clean.py import sqlite3 import csv import logging # 配置日志时间 [级别] 信息 —— 清洗过程全程留痕 logging.basicConfig( levellogging.INFO, format%(asctime)s [%(levelname)s] %(message)s ) log logging.getLogger(__name__) # ① 读入脏 CSV def load_dirty_csv(path): with open(path, encodingutf-8) as f: return list(csv.DictReader(f)) # ② 逐行清洗先标准化再转类型 def clean_records(records): # 中文数字 → 阿拉伯数字 的映射表 cn_map {八十二: 82, 九十: 90, 九十一: 91} cleaned [] for i, r in enumerate(records): # —— 标准化去空格 —— r[device_name] r[device_name].strip() r[owner_name] r[owner_name].strip() # —— 标准化设备名英文统一大写 去掉内部空格 —— # 注塑机a3 → 注塑机A3注塑机 A3 → 注塑机A3 r[device_name] r[device_name].upper().replace( , ) # —— 温度中文数字转阿拉伯再转 float —— temp_str r[temperature].strip() if temp_str in cn_map: temp_str cn_map[temp_str] try: r[temperature] float(temp_str) except (ValueError, TypeError): # 转不了空字符串、奇怪文字就设为 None进库存 NULL r[temperature] None log.warning(f第 {i2} 行温度无法转换{temp_str}已设为 NULL) # —— 手机号去掉横杠和空格空的设 None —— phone r[phone].strip().replace(-, ).replace( , ) r[phone] phone if phone else None if not phone: log.warning(f第 {i2} 行手机号缺失已设为 NULL) r[type] r[type].strip() cleaned.append(r) return cleaned # ③ 去重标准化之后按 (设备名, 负责人) 判重 def deduplicate(records): seen set() unique [] for r in records: key (r[device_name], r[owner_name]) if key in seen: log.info(f去除重复行{r[device_name]} / {r[owner_name]}) continue seen.add(key) unique.append(r) return unique # ④ 存入干净的 SQLite 表 def save_to_db(records, db_pathfde_clean.db): conn sqlite3.connect(db_path) cur conn.cursor() cur.execute( CREATE TABLE IF NOT EXISTS devices_clean ( id INTEGER PRIMARY KEY AUTOINCREMENT, device_name TEXT NOT NULL, temperature REAL, owner_name TEXT NOT NULL, phone TEXT, type TEXT, imported_at TEXT DEFAULT (datetime(now,localtime)) ) ) for r in records: cur.execute( INSERT INTO devices_clean (device_name,temperature,owner_name,phone,type) VALUES (?,?,?,?,?) , (r[device_name], r[temperature], r[owner_name], r[phone], r[type])) conn.commit() cur.execute(SELECT COUNT(*) FROM devices_clean) count cur.fetchone()[0] conn.close() return count # 主流程按顺序串起来 raw load_dirty_csv(dirty_data.csv) log.info(f原始数据{len(raw)} 行) cleaned clean_records(raw) log.info(f标准化 类型转换完成{len(cleaned)} 行) unique deduplicate(cleaned) log.info(f去重后{len(unique)} 行删掉 {len(cleaned)-len(unique)} 条重复) count save_to_db(unique) log.info(f✅ 写入数据库{count} 条干净数据)运行并观察python 06_clean.py预期日志注意每一步的行数变化[INFO] 原始数据9 行 [WARNING] 第 4 行温度无法转换八十二已设为 NULL ← 实际已被中文映射转成82这行不会出现 [WARNING] 第 4 行手机号缺失已设为 NULL [INFO] 去除重复行注塑机A1 / 李工 [INFO] 去除重复行注塑机A3 / 赵工 [INFO] 去除重复行冲压机B1 / 李工 [INFO] 去重后6 行删掉 3 条重复 [WARNING] 第 10 行温度无法转换已设为 NULL [INFO] ✅ 写入数据库6 条干净数据⚠️ 小细节第 3 行温度是八十二会被cn_map先转成 82 再转 float所以不会报警告第 9 行温度是空字符串映射表没有float()报错才走 None 分支。可以在脚本里打断点或加 print 验证这个分支逻辑。清洗后的干净数据在 DBeaver 打开 fde_clean.db 查看device_name | temperature | owner_name | phone | type 注塑机A1 | 65.0 | 李工 | 13812345678 | 注塑 注塑机A2 | 82.0 | 王工 | NULL | 注塑 ← 八十二→82手机NULL 注塑机A3 | 91.0 | 赵工 | 13812345678 | 注塑 ← a3/A3 两种写法合并 冲压机B1 | 75.0 | 李工 | 13812345678 | 冲压 冲压机B2 | 88.0 | 陈工 | 13987654321 | 冲压 注塑机A4 | NULL | 赵工 | 13811112222 | 注塑 ← 空温度存为 NULL9 行脏数据 → 6 行干净数据。世界清净了。 四、SQL 清洗函数速查表清洗不一定要在 Python 里做很多标准化操作 SQL 也能直接干。这张表两边对照着用需求SQL 函数例子对应 Python去两端空格TRIM(x)TRIM(device_name).strip()转大写UPPER(x)UPPER(name).upper()转小写LOWER(x)LOWER(name).lower()替换字符REPLACE(x,a,b)REPLACE(phone,-,).replace(-,)取子串SUBSTR(x,起,长)SUBSTR(phone,1,3)切片s[:3]拼接x || y138 || ****a b/ f-stringNULL 兜底COALESCE(x,默认)COALESCE(temp,0)x or 默认类型转换CAST(x AS 类型)CAST(82 AS REAL)float(x)去重DISTINCTSELECT DISTINCT name...set()/drop_duplicatesCOALESCE 是清洗界的暖男COALESCE(字段, 默认值)意思是这个字段如果是 NULL就用默认值顶上可以串多个COALESCE(phone, 未登记, 未知)——第一个非 NULL 的胜出。比 MySQL 专属的IFNULL更通用SQLite/MySQL/PG 都支持。用 SQL 直接查干净结果不落库也能验证-- 同一份清洗逻辑用纯 SQL 表达针对已入库的原始表 SELECT DISTINCT UPPER(REPLACE(TRIM(device_name), , )) AS 标准设备名, CAST(REPLACE(temperature, 八十二, 82) AS REAL) AS 温度, TRIM(owner_name) AS 负责人, REPLACE(REPLACE(TRIM(phone), -, ), , ) AS 手机 FROM devices_raw;️ 五、数据脱敏把敏感信息打码清洗入库后数据还常常要给别人看做报表、发给外部、截图汇报。手机号、身份证号这类 PII个人身份信息必须脱敏——入库存全量展示打码。-- 手机号脱敏13812345678 → 138****5678 SELECT device_name AS 设备, owner_name AS 负责人, SUBSTR(phone, 1, 3) || **** || SUBSTR(phone, 8, 4) AS 脱敏手机, COALESCE(CAST(temperature AS TEXT), 缺失) AS 温度 FROM devices_clean ORDER BY device_name;结果设备 | 负责人 | 脱敏手机 | 温度 注塑机A1 | 李工 | 138****5678 | 65.0 注塑机A2 | 王工 | NULL | 缺失 注塑机A3 | 赵工 | 138****5678 | 91.0 冲压机B1 | 李工 | 138****5678 | 75.0 冲压机B2 | 陈工 | 139****4321 | 88.0 注塑机A4 | 赵工 | 138****2222 | 缺失拆解手机号脱敏公式13812345678 ├─┬─┤├────┤├─┬─┤ 前3位 打码4位 后4位 SUBSTR(...,1,3) **** SUBSTR(...,8,4) 138 **** 5678脱敏 vs 加密 别混淆脱敏不可逆地隐藏一部分138****5678用于展示看个大概但无法还原加密可逆有密钥能解开如 AES用于必须还原的存储场景报表展示用脱敏就够了别把完整手机号随手贴到群里或截图里。 六、❌ 常见翻车点翻车点后果正确姿势先去重后标准化a3 和 A3 认不出是同一条去重失效先 TRIM/UPPER 标准化再判重空字符串当正常值float()直接崩或统计出零个字符的怪结果显式判断空统一转 None/NULL用 None判断Python 里None要用is Noneif not phone:或x is None缺失值一律填 0温度 0 是真实可能的值会污染平均值数值缺失填 NULLAVG 自动忽略或业务默认值别想当然清洗不留日志出问题查不到改了啥、删了几条每步打日志原始多少、删掉多少、转换多少直接覆盖原始数据清洗规则错了原始数据毁了找不回脏数据只读结果写新表/新文件⚠️务必保留原始数据清洗永远在副本上做原始 CSV 和原始表绝不动。万一清洗逻辑有 bug你还能推倒重来。这是数据工作者的底线。 本课小结知识点一句话记住四类脏数据重复、缺失、格式不一致、类型/异常值清洗顺序先标准化 → 再去重 → 后补缺/转类型去空格Python.strip()/ SQLTRIM()统一大小写.upper()/UPPER()去字符.replace(-,)/REPLACE()中文数字映射字典替换后再转 float类型转换float()配 try/except失败设 None去重Python 用set存 keySQL 用DISTINCTNULL 兜底COALESCE(列, 默认值)脱敏SUBSTR取头尾 拼接打码展示用日志留痕每步记录行数变化和异常可追溯保护原始在副本上清洗原始数据只读不覆盖 核心认知数据清洗不是碰运气修修补补而是一条固定流水线——识别脏的类型、按正确顺序处理、每一步留日志、原始数据不动。把这套流程脚本化以后客户再来 100 份脏 CSV你改改配置就能批量洗。 课后练习练习 1给脏数据加两种新脏升级清洗脚本约 40 分钟在dirty_data.csv里追加两行注塑机A5,约82度,孙工,138 0000 5555, 注塑机a5,82,孙工,13800005555,新问题温度写成约82度需要从文字里提取数字提示用正则re.search(r\d, s)type列有空值提示空的填未知这两行清洗后应识别为同一台设备而只保留一条修改06_clean.py处理它们跑通后确认最终是 7 条干净数据。练习 2用 SQL DISTINCT 验证去重约 15 分钟把脏数据先导进一张devices_raw表用SELECT DISTINCT 标准化列...去重和 Python 脚本的结果对比确认条数和内容一致。练习 3身份证脱敏约 15 分钟写一条 SQL把身份证号110101199001011234脱敏成110101********1234保留前 6 位和后 4 位中间 8 位打星。提示SUBSTR(x,1,6)********SUBSTR(x,15,4)。 下节预告今天我们在轻量的 SQLite 里完成了清洗。但客户现场跑的是真正的MySQL。明天是本周收官干三件大事安装配置 MySQL把练习环境升级成企业级数据库用SQLAlchemy pandas在 Python 里优雅地读写数据库告别手写一堆 cursor把第 2 周的工单 API 从内存列表升级为 MySQL 持久化——服务重启数据再也不丢亲手体会分层架构带来的好处明天过后你就拥有一条完整的数据管道脏 CSV → pandas 清洗 → MySQL → FastAPI 查询。本周的压轴大戏明天见附录前置课程列表阶段一【FDE系列】阶段1Day 1AI 层级关系 — 四个嵌套的圈-CSDN博客【FDE系列】阶段1Day 2AI 三阶段发展史 — 会认 → 会判断 → 会创造-CSDN博客【FDE系列】阶段1Day 3符号 AI vs 机器学习 — 两条路线的本质区别-CSDN博客【FDE系列】阶段1Day 4Transformer 的历史意义 — 2017 年的分水岭-CSDN博客【FDE系列】阶段1Day 5本周复习与自测 — 检验你的 AI 认知地基-CSDN博客【FDE系列】阶段1Day 6Transformer 架构 — 一张图纸盖出千千万万栋楼-CSDN博客【FDE系列】阶段1Day 7LLM 本质 — 文字接龙机器-CSDN博客【FDE系列】阶段1Day 8Token — 模型眼中的最小单位-CSDN博客【FDE系列】阶段1Day 9AI 幻觉 — 为什么会一本正经地胡说八道-CSDN博客【FDE系列】阶段1Day 10上下文窗口 — 模型的记忆力上限 本周复习-CSDN博客【FDE系列】阶段1Day 11Prompt — 给模型立规矩-CSDN博客【FDE系列】阶段1Day 12Memory — 让模型记住上下文【FDE系列】阶段1Day 13RAG — 给模型配图书管理员-CSDN博客【FDE系列】阶段1Day 14Tool Use — 让模型动手操作-CSDN博客【FDE系列】阶段1Day 15MCP — 统一的工具接口标准 第三周复习-CSDN博客【FDE系列】阶段1Day 16什么是 FDE — 把 AI 变成客户结果的人-CSDN博客【FDE系列】阶段1Day 17FDE vs 传统实施 — 三大本质区别-CSDN博客【FDE系列】阶段1Day 18FDE 三重身份 C6 胜任力模型-CSDN博客【FDE系列】阶段1Day 19七阶段行动路径 行业经验的价值-CSDN博客【FDE系列】阶段1Day 20阶段总结与产出物 — 第一阶段收官-CSDN博客阶段二【FDE系列】阶段2Day 21Python 环境搭建 — 写出你的第一行代码-CSDN博客【FDE系列】阶段2Day 22变量、数据类型、条件判断 — Python 的“记忆“和“判断“-CSDN博客【FDE系列】阶段2Day 23循环与函数 — 让代码跑 100 遍、把逻辑打包复用-CSDN博客【FDE系列】阶段2Day 24数据结构 — 列表、字典、集合、元组-CSDN博客【FDE系列】阶段2Day 25文件读写与 JSON — 让程序连通外部数据第一周收官-CSDN博客【FDE系列】阶段2Day 26模块化编程 — 把代码拆成“抽屉柜“-CSDN博客【FDE系列】阶段2Day 27异常处理与日志 — 让程序“摔不烂、查得到“-CSDN博客【FDE系列】阶段2Day 28FastAPI 入门 — 把你的函数变成 API 服务-CSDN博客【FDE系列】阶段2Day 29FastAPI 进阶 — Pydantic 模型与完整 CRUD 实战-CSDN博客【FDE系列】阶段2Day 30生产代码规范 — 测试、类型注解、配置管理第二周收官-CSDN博客【FDE系列】阶段2Day 31SQL 基础 — 增删改查一把梭-CSDN博客【FDE系列】阶段2Day 32多表查询 — JOIN 与聚合-CSDN博客【FDE系列】阶段2Day 33进阶查询 — 窗口函数与 CTE-CSDN博客
返回列表