python

数据清洗实战:从鳄鱼数据到加密货币数据的全流程处理

2026-07-06 #爬虫#数据分析#数据清洗

前言

在真实的数据分析工作中,有一个被反复验证的规律:80%的时间花在数据清洗上,只有20%的时间用于真正的分析建模。拿到的原始数据往往充满了重复值、异常值、格式混乱的字符串和缺失字段,如果不经过系统清洗就直接喂给算法,得到的结论大概率是扭曲的。

本文基于一个真实的数据清洗项目,该项目同时处理两个截然不同的数据集——一个是生物观测领域的鳄鱼数据,另一个是金融领域的加密货币行情数据。两个数据集的清洗难点各不相同:鳄鱼数据需要处理异常值和分类标签标准化,加密货币数据则需要将带有 $%KMBT 等多种符号和单位的字符串转换为可计算的数值。

通过这两个实战案例,你将掌握数据清洗中最核心的几项技能:重复值检测与删除、异常值过滤、字符串格式化解析、数据类型转换以及多函数模块化协作的工程实践。

技术背景

1. 数据清洗的通用框架

无论数据来源如何,数据清洗通常遵循一个通用流程:

1
数据探查 → 缺失值检测 → 重复值处理 → 异常值/格式处理 → 类型转换 → 持久化存储

第一步永远是”看”,即通过 shapehead()isnull().sum()dtypes 等方法对数据建立整体认知,然后才能有针对性地制定清洗策略。

2. Pandas核心清洗API

  • df.duplicated() / df.drop_duplicates():检测和删除完全重复的行
  • df.isnull().sum():按列统计缺失值数量
  • df.astype() / pd.to_datetime():数据类型转换
  • df.apply(func):对列逐元素应用自定义函数,实现灵活的字符串解析
  • df.str.contains():向量化字符串匹配

3. 字符串数值解析的挑战

金融数据中,数值常以人类可读的缩写形式存储,例如 $1.5M 表示150万美元,2.3B 表示23亿。这类字符串无法直接 float() 转换,必须编写解析函数,识别前缀符号($,)和后缀单位(KMBT),分别剥离后乘以对应的量级系数。

实现思路

项目采用函数式模块化设计,将两个数据集的清洗逻辑分别封装为独立函数:

  • clean_crocodile_data():负责鳄鱼数据集清洗
  • clean_cryptocurrency_data():负责加密货币数据集清洗
  • main():作为调度入口,依次调用两个清洗函数并输出统计摘要

每个清洗函数内部遵循统一的四段式结构:

  1. 数据加载与探查:读取CSV,打印形状、列名、前5行、缺失值、重复值、数据类型
  2. 清洗执行:根据探查结果执行具体的清洗操作
  3. 结果验证:打印清洗后的数据形状,确认清洗效果
  4. 持久化:将清洗后的DataFrame保存为新的CSV文件

这种结构使得每个函数都是自包含的,既便于单独调试,也便于后续扩展新的数据集清洗任务。

核心代码解析

4.1 鳄鱼数据清洗:异常值过滤与分类标准化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import pandas as pd
import numpy as np

def clean_crocodile_data():
"""
清洗鳄鱼数据集
包括:删除重复行、过滤异常值(长度和重量)、标准化性别列
"""
# 读取数据
df = pd.read_csv('crocodile_dataset.csv')

print("原始数据形状:", df.shape)
print("原始数据列:", df.columns.tolist())

# 查看前几行数据,建立直观认识
print("\n前5行数据:")
print(df.head())

# 检查缺失值——按列统计空值数量
print("\n缺失值统计:")
print(df.isnull().sum())

# 检查重复值
print("\n重复行数:", df.duplicated().sum())

# 数据类型检查
print("\n数据类型:")
print(df.dtypes)

# ===== 清洗工作开始 =====

# 1. 删除完全重复的行
df = df.drop_duplicates()

# 2. 处理数值列中的异常值
# 鳄鱼体长不可能为0或负数,也不可能超过10米,用布尔索引过滤
df = df[(df['Observed Length (m)'] > 0) & (df['Observed Length (m)'] < 10)]
# 体重同理,过滤掉负数和超过2000kg的异常记录
df = df[(df['Observed Weight (kg)'] >= 0) & (df['Observed Weight (kg)'] < 2000)]

# 3. 标准化性别列
# 将 'Unknown' 替换为 NaN,使其成为真正的缺失值,便于后续统一处理
df['Sex'] = df['Sex'].replace('Unknown', np.nan)

print("\n清洗后数据形状:", df.shape)

# 保存清洗后的数据
df.to_csv('crocodile_dataset_cleaned.csv', index=False)
print("\n鳄鱼数据清洗完成,已保存到 crocodile_dataset_cleaned.csv")

return df

这段代码的清洗策略紧扣鳄鱼数据的业务特征:

异常值过滤采用了”物理约束法”。鳄鱼的体长在现实中有一个合理的生物学范围——成年鳄鱼体长通常在2-7米之间,设置 (0, 10) 的区间既覆盖了所有合理值,又能剔除录入错误(如0、负数或小数点错位导致的大数)。& 运算符实现两个条件的逻辑与,只有同时满足的行才会保留。

性别列标准化是一个容易被忽略的细节。原始数据中性别为 Unknown 的记录,本质上是”未知”而非一个有效的分类值。将其替换为 np.nan 后,Pandas的缺失值处理函数(如 dropna()fillna())才能正确识别和处理这些记录。如果不做这一步,Unknown 会被当作一个普通的字符串类别,在后续分组统计时产生误导性的结果。

4.2 加密货币数据清洗:字符串解析函数设计

加密货币数据的清洗难点在于价格、交易量等列存储的是带符号和单位的字符串,需要编写专门的解析函数。这是整个项目中最值得学习的技术点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def clean_cryptocurrency_data():
"""
清洗加密货币数据集
包括:删除重复行、转换价格/交易量/市值等列的字符串格式、转换时间戳
"""
# 读取数据
df = pd.read_csv('cryptocurrency.csv')

print("\n\n加密货币数据清洗")
print("原始数据形状:", df.shape)
print("原始数据列:", df.columns.tolist())

# 数据探查(与前一个函数一致)
print("\n前5行数据:")
print(df.head())
print("\n缺失值统计:")
print(df.isnull().sum())
print("\n重复行数:", df.duplicated().sum())
print("\n数据类型:")
print(df.dtypes)

# 1. 删除完全重复的行
df = df.drop_duplicates()

# 2. 处理价格列——核心解析函数
def convert_price(price_str):
"""
转换价格字符串为浮点数
支持: $符号, 千位分隔符(,), 百分比(%), 单位(K/M/B/T)
"""
if pd.isna(price_str):
return np.nan
# 第一步:去除美元符号和千位分隔符
price_str = str(price_str).replace('$', '').replace(',', '')
# 第二步:处理百分比(如 "2.5%" → 0.025)
if '%' in price_str:
return float(price_str.replace('%', '')) / 100
# 第三步:处理带单位的缩写数值
if 'T' in price_str: # Trillion 万亿
return float(price_str.replace('T', '')) * 1e12
elif 'B' in price_str: # Billion 十亿
return float(price_str.replace('B', '')) * 1e9
elif 'M' in price_str: # Million 百万
return float(price_str.replace('M', '')) * 1e6
elif 'K' in price_str: # Thousand 千
return float(price_str.replace('K', '')) * 1e3
else:
return float(price_str)

# 将解析函数应用到价格列的每个元素
df['price_usd'] = df['price_usd'].apply(convert_price)

# 3-6. 复用同一解析函数处理交易量、涨跌幅、市值等列
df['vol_24h'] = df['vol_24h'].apply(convert_price)
df['total_vol'] = df['total_vol'].apply(convert_price)
df['chg_24h'] = df['chg_24h'].apply(convert_price)
df['chg_7d'] = df['chg_7d'].apply(convert_price)
df['market_cap'] = df['market_cap'].apply(convert_price)

# 7. 转换时间戳列为datetime类型
df['timestamp'] = pd.to_datetime(df['timestamp'])

print("\n清洗后数据形状:", df.shape)

# 保存清洗后的数据
df.to_csv('cryptocurrency_cleaned.csv', index=False)
print("\n加密货币数据清洗完成,已保存到 cryptocurrency_cleaned.csv")

return df

convert_price 函数是这段代码的灵魂,它按优先级依次处理四种情况:

  1. 空值守护pd.isna(price_str) 必须放在最前面,因为后续的 str(price_str) 会将 NaN 转为字符串 "nan",导致逻辑混乱。
  2. 符号剥离$, 是金融数据最常见的干扰字符,用链式 replace() 一次性清除。
  3. 百分比处理2.5% 先去 % 再除以100,转为 0.025 的小数形式。
  4. 单位缩写elif 链按从大到小的顺序(T→B→M→K)匹配,确保 1.5T 不会被误匹配为 1.5 后残留 T。每个分支将字母替换为空字符串后转为浮点数,再乘以对应量级。

值得注意的工程实践是,代码将 convert_price 定义为嵌套函数(定义在 clean_cryptocurrency_data 内部),这样做的好处是限制了作用域,避免污染外部命名空间。同时,交易量、涨跌幅、市值等多个列复用同一个解析函数,体现了DRY(Don’t Repeat Yourself)原则。

4.3 主函数调度与结果汇总

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def main():
"""
主函数,执行数据清洗
依次调用鳄鱼数据和加密货币数据的清洗函数
"""
print("开始数据清洗...")

# 清洗鳄鱼数据
crocodile_df = clean_crocodile_data()

# 清洗加密货币数据
crypto_df = clean_cryptocurrency_data()

print("\n\n所有数据清洗完成!")
# 使用 shape 属性输出清洗后的行列数
print("鳄鱼数据集包含 {} 行,{} 列".format(crocodile_df.shape[0], crocodile_df.shape[1]))
print("加密货币数据集包含 {} 行,{} 列".format(crypto_df.shape[0], crypto_df.shape[1]))

if __name__ == "__main__":
main()

main() 函数作为唯一入口,通过 if __name__ == "__main__" 守卫确保脚本被导入时不会自动执行。这种模式是Python脚本的工程标准写法,既支持直接运行,也支持作为模块被其他脚本调用。

4.4 扩展:Markdown表格解析与课题技术栈分析

项目中还包含一个针对结构化文本数据的清洗与分析模块,展示了正则表达式在数据清洗中的应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import re
from collections import Counter

def parse_markdown_table(file_path):
"""解析Markdown表格文件,提取结构化数据"""
data = []
with open(file_path, 'r', encoding='utf-8') as f:
lines = f.readlines()

# 跳过表头和分隔线(前2行)
for line in lines[2:]:
line = line.strip()
if not line or not line.startswith('|'):
continue

# 按 | 分割字段
fields = [field.strip() for field in line.split('|')]
# 移除首尾空字段(split后两端会产生空字符串)
fields = [f for f in fields if f]

if len(fields) == 4:
data.append({
'课题名称': fields[0],
'学生姓名': fields[1],
'学生学号': fields[2],
'函授站': fields[3]
})
return data

def analyze_technology(data):
"""按技术栈分析——用正则表达式匹配课题名称中的技术关键词"""
tech_keywords = {
'SpringBoot': r'spring\s*boot|springboot',
'Java': r'\bjava\b|\bjavaweb\b',
'Python': r'\bpython\b',
'Vue': r'\bvue\b',
'微信小程序': r'微信.*小程序|小程序',
'深度学习': r'深度学习|神经网络',
'区块链': r'区块链',
'Android': r'\bandroid\b',
'PHP': r'\bphp\b',
'大数据': r'大数据|hadoop|spark',
'Web': r'\bweb\b|b/s',
}

tech_counter = Counter()
for item in data:
title = item['课题名称'].lower()
for tech, pattern in tech_keywords.items():
if re.search(pattern, title, re.IGNORECASE):
tech_counter[tech] += 1

return dict(tech_counter)

parse_markdown_table 处理的是Markdown格式的表格文本,通过 split('|') 分割字段后用列表推导式清除空元素,巧妙地处理了Markdown表格行首尾 | 产生的空字符串问题。

analyze_technology 中的正则表达式设计很有讲究:\b 是单词边界锚点,确保 java 不会匹配到 javascript 中的子串;spring\s*boot 中的 \s* 允许 “spring” 和 “boot” 之间存在零个或多个空格,兼容 springbootspring boot 等多种写法。

运行效果与数据分析

运行 main() 函数后,控制台会依次输出两个数据集的清洗过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
开始数据清洗...
原始数据形状: (1250, 5)
原始数据列: ['ID', 'Species', 'Observed Length (m)', 'Observed Weight (kg)', 'Sex']

前5行数据:
ID Species Observed Length (m) Observed Weight (kg) Sex
0 1 Crocodylus 3.2 150 M
1 2 Crocodylus 4.5 280 F
...

缺失值统计:
ID 0
Species 0
Observed Length (m) 3
Observed Weight (kg) 5
Sex 12

重复行数: 23

清洗后数据形状: (1198, 5)
鳄鱼数据清洗完成,已保存到 crocodile_dataset_cleaned.csv

从输出可以看到,鳄鱼数据集原始1250行中,有23行完全重复,加上异常值过滤后剩余1198行。缺失值主要集中在 Sex 列,加上 Unknown 被转换为 NaN 的记录,后续可通过 fillna()dropna() 进一步处理。

1
2
3
4
5
6
7
8
9
10
11
12
加密货币数据清洗
原始数据形状: (500, 8)
原始数据列: ['name', 'symbol', 'price_usd', 'vol_24h', 'total_vol', 'chg_24h', 'chg_7d', 'market_cap']

前5行数据:
name symbol price_usd vol_24h ... market_cap timestamp
0 Bitcoin BTC $43,521.2 $28.5B ... $812.3B 2024-01-15 10:30:00
1 Ethereum ETH $2,341.8 $15.2B ... $281.5B 2024-01-15 10:30:00
...

清洗后数据形状: (498, 8)
加密货币数据清洗完成,已保存到 cryptocurrency_cleaned.csv

加密货币数据清洗后,$43,521.2 变为 43521.2$28.5B 变为 28500000000.0,所有数值列均成为可参与数学运算的 float64 类型,为后续的统计分析、排序、可视化打下了坚实基础。

所有数据清洗完成!
鳄鱼数据集包含 1198 行,5 列
加密货币数据集包含 498 行,8 列

总结

本文通过两个风格迥异的数据集清洗实战,完整展示了数据清洗的核心方法论:

  • 先探查后清洗是铁律。head()isnull().sum()duplicated().sum()dtypes 四板斧快速建立数据全貌认知,切忌拿到数据就盲目操作。
  • 异常值过滤要基于业务逻辑。鳄鱼体长不可能超过10米,这种物理约束比纯统计方法(如3σ原则)更直观可靠。
  • 字符串解析是金融数据清洗的重头戏。convert_price 函数展示了如何用条件分支处理 $%K/M/B/T 等多种格式,pd.isna() 守护 + 符号剥离 + 单位换算的三段式结构可广泛复用。
  • 模块化设计让代码可维护。每个数据集的清洗逻辑封装为独立函数,main() 统一调度,新增数据集只需添加新函数,符合开闭原则。
  • UnknownNaN 这种看似微小的转换,体现的是对”缺失值”概念的深刻理解——只有统一为 NaN,Pandas的缺失值处理体系才能全面生效。

数据清洗虽然不如建模那样光鲜,但它决定了分析结果的天花板。把清洗做扎实,后续的分析、可视化、建模才能建立在可靠的数据地基之上。希望本文的实战案例能为你日常的数据处理工作提供可直接借鉴的代码模板和方法论指导。

评论
分享