在数据处理项目中,CSV往往是第一手资料的载体。它结构简单、兼容性强,但也正因为如此,原始CSV经常夹杂着空值、重复行、格式不一致、非法取值等问题。Python中的pandas库提供了完整的数据框操作能力,可以把这些杂乱的数据转换成规范、可分析的数据集。围绕CSV清洗,常见工作可以概括为读取检查、缺失值治理、重复值处理、字段规范化、异常过滤以及结果保存。
与手工修改表格相比,使用脚本清洗的优势在于过程可重复、规则可追踪、结果可验证。当下很多数据管道都会把CSV清洗作为前置步骤,先把脏数据转换为统一口径,再进行统计、建模或入库。理解这些常用操作,不仅能提高数据质量,也能减少后续分析阶段出现错误。
一、读取与初步检查:清洗CSV的第一步
CSV清洗并不是从修改数据开始,而是从正确读取数据开始。不同来源的CSV文件可能使用不同编码、不同分隔符,甚至存在表头缺失、字段类型识别异常等问题。pandas提供的read_csv方法可以指定编码、分隔符、表头、跳过行等参数,使读取过程更可控。读取完成后,通常需要查看前几行、字段类型和基本统计信息,以判断后续清洗重点。
初步检查的意义在于快速建立对数据的整体认知。例如,通过观察前几行可以发现字符串是否包含多余空格,通过字段类型可以发现数值列是否被误识别为文本,通过行数和列数可以判断数据规模是否符合预期。这个阶段的判断越充分,后续处理策略就越准确。
1. 基础读取示例
下面的示例使用内置字符串模拟CSV内容,并通过io.StringIO交给pandas读取。这样无需依赖外部文件,也能完整展示读取过程。实际项目中,只需把数据源替换为真实文件路径即可。
import pandas as pd import io # 使用字符串模拟CSV文件内容,便于完整运行示例 csv_text = """id,name,age,city,score 1, Alice ,20,Beijing,88 2,Bob,,Shanghai,70 3,Cathy,25,Guangzhou,65 """ # 通过StringIO将字符串转为类文件对象,再交给read_csv读取 df = pd.read_csv(io.StringIO(csv_text), encoding='utf-8', sep=',') # 查看前几行,确认数据是否成功加载 print(df.head())
在真实场景中,如果文件使用制表符分隔,可以把sep参数调整为对应符号;如果文件包含中文,需要根据来源选择合适编码。读取成功后,使用head查看样本数据,是快速确认数据结构的常用方式。
二、缺失值与重复值:保证数据完整性和唯一性
缺失值是CSV中最常见的问题之一。它可能来自采集失败、用户未填写、系统导出异常或字段本身不适用。处理缺失值之前,先要了解缺失值分布在哪些列、占比多少、是否与业务含义相关。盲目删除可能损失有效样本,盲目填充可能引入偏差。
重复值同样会影响统计结果。完全重复的行会导致指标虚高,关键字段重复则可能破坏主键唯一性。去重时需要根据业务规则选择保留第一条、最后一条,或者结合排序保留最合理的一条。pandas的drop_duplicates可以按整行或指定字段进行判断。
1. 缺失值识别与处理
通常先用isnull和sum统计每列缺失数量,再决定删除还是填充。对于缺失比例过高且业务价值有限的列,可以考虑删除;对于缺失较少且字段重要的列,可以采用均值、中位数、固定值或业务规则填充。
import pandas as pd
import io
csv_text = """id,name,age,city,score
1, Alice ,20,Beijing,88
2,Bob,,Shanghai,70
3,Cathy,25,,65
4,David,30,Shenzhen,
"""
df = pd.read_csv(io.StringIO(csv_text))
# 查看每列缺失值数量
missing_count = df.isnull().sum()
print(missing_count)
# 删除包含任意缺失值的行
df_drop_row = df.dropna(axis=0, how='any')
# 删除非空值数量低于阈值的列
threshold = int(len(df) * 0.5)
df_drop_col = df.dropna(axis=1, thresh=threshold)
# 对重要字段进行填充,避免直接删除造成样本损失
df_filled = df.copy()
df_filled['age'] = df_filled['age'].fillna(df_filled['age'].mean())
df_filled['city'] = df_filled['city'].fillna('未知')
df_filled['score'] = df_filled['score'].fillna(0)
print(df_filled)
上述代码展示了常见的缺失值处理路径。数值列使用均值填充只是其中一种策略,如果数据存在明显偏态,中位数往往更稳健;类别列则可以使用固定值或未知类别填充。无论采用哪种方式,都应保留处理规则,方便后续复查。
2. 重复值识别与去重
去重操作需要先明确重复的定义。如果所有字段都相同才算重复,可以直接按整行判断;如果同一个编号只能保留一条记录,则应按编号字段判断。下面的示例分别展示了这两种去重方式。
import pandas as pd import io csv_text = """id,name,age,city 1,Alice,20,Beijing 2,Bob,22,Shanghai 2,Bob,22,Shanghai 3,Cathy,25,Guangzhou """ df = pd.read_csv(io.StringIO(csv_text)) # 去除所有字段都重复的行,保留第一条 df_unique = df.drop_duplicates(keep='first') # 仅根据id字段判断重复,保留第一条 df_unique_id = df.drop_duplicates(subset=['id'], keep='first') print(df_unique) print(df_unique_id)
在实际清洗中,去重往往不是孤立步骤。它经常和排序、状态字段结合使用。例如,同一个编号可能存在多条记录,业务上希望保留状态最新的一条,这时就需要先排序再去重,以保证保留结果符合业务语义。
三、格式规范、异常过滤与结果保存:让数据进入可用状态
当数据不再缺失且没有明显重复后,下一步是统一字段格式。很多CSV看起来有数据,但字段内容并不符合分析要求。例如,姓名前后有空格,电话号码中包含分隔空格,数值列被保存为文本,时间列无法参与时间运算。这些问题不会立刻导致程序报错,却会在统计、分组和连接时产生隐患。
格式规范的目标是让每一列的数据类型和表达形式与业务含义一致。数值列应当是数值类型,文本列应当去除无意义空白,时间列应当转换为可计算的时间类型。完成规范化后,还需要基于业务规则过滤异常数据,例如年龄超出合理范围、分数为负数等。最后,将清洗后的数据保存为新的CSV文件,便于后续分析或入库。
1. 字符串、数值与时间字段规范化
字符串清洗常用str.strip去除首尾空白,用str.replace去除内部多余字符。数值转换常用to_numeric,它可以把无法转换的值标记为缺失值,避免脏数据直接混入计算。时间转换常用to_datetime,并可结合错误处理参数提高解析稳定性。
import pandas as pd
import io
csv_text = """id,name,phone,score,create_time
1, Alice ,138 0000 0000,88,
2,Bob,13900000000,70,
3,Cathy,13700000000,-5,
"""
df = pd.read_csv(io.StringIO(csv_text))
# 去除字符串首尾空白
df['name'] = df['name'].str.strip()
# 去除电话号码中的空格,使格式统一
df['phone'] = df['phone'].str.replace(' ', '', regex=False)
# 将分数转换为数值类型,无法转换的值变为缺失值
df['score'] = pd.to_numeric(df['score'], errors='coerce')
# 将时间字段统一转换为datetime类型,空值保持为缺失值
df['create_time'] = pd.to_datetime(df['create_time'], errors='coerce')
print(df.dtypes)
print(df)
在这段代码中,电话字段被去除空格,分数字段被转换为数值类型,时间字段被统一解析。对于无法解析的内容,使用errors='coerce'将其转为缺失值,比直接报错更适合批量清洗。这样处理后,字段类型会更接近后续分析需求。
2. 基于业务规则过滤异常值
异常值不一定都是错误数据,但超出业务合理范围的值通常需要过滤或单独标记。例如,年龄不应为负数,也不应超过常见人类年龄上限;分数通常不应为负数。通过条件筛选可以快速保留合理数据。
import pandas as pd import io csv_text = """id,age,score 1,20,88 2,-3,70 3,150,65 4,28,-5 """ df = pd.read_csv(io.StringIO(csv_text)) # 过滤年龄不在合理范围内的记录 df_normal = df[(df['age'] >= 0) & (df['age'] <= 120)] # 继续过滤分数为负数的记录 df_normal = df_normal[df_normal['score'] >= 0] print(df_normal)
条件过滤的关键是规则明确。最好把过滤条件写成可解释、可维护的表达式,而不是临时修改数据。如果某些异常值具有特殊业务含义,也可以新增标记列,将其保留并进入人工复核流程,而不是简单删除。
3. 保存清洗后的CSV
清洗完成后,通常需要将结果保存为新的CSV文件,避免覆盖原始数据。保存时可以指定编码,并选择不写入索引列,使文件更干净。下面的示例展示了保存操作。
import pandas as pd
import io
csv_text = """id,name,age,city
1,Alice,20,Beijing
2,Bob,22,Shanghai
3,Cathy,25,Guangzhou
"""
df = pd.read_csv(io.StringIO(csv_text))
# 保存清洗后的数据为新的CSV文件,不写入索引列
df.to_csv('cleaned_data.csv', index=False, encoding='utf-8')
print('清洗结果已保存')
保存不是简单导出,而是数据版本管理的一部分。建议原始文件保持只读,清洗结果单独命名,并把清洗脚本与规则一起保存。这样在数据更新或结果出现疑问时,可以快速重跑流程并追踪变化。
四、清洗思路总结与延伸建议
回顾整个流程,CSV清洗的核心并不在于某一个函数,而在于建立稳定的处理顺序:先读取并观察数据,再处理缺失和重复,然后规范字段格式,最后过滤异常并保存结果。这个顺序符合从整体到局部、从结构到内容的认知过程,也能减少误操作。
- 读取阶段重点关注编码、分隔符和字段类型。
- 缺失值处理需要结合比例、业务含义和统计影响。
- 去重需要先定义重复范围,再选择保留策略。
- 规范化操作要让数据类型与业务语义保持一致。
- 异常过滤应基于明确规则,必要时保留人工复核线索。
在更复杂的项目中,还可以进一步引入日志记录、规则配置和数据校验。例如,把每列的填充方式、去重字段、异常阈值写成配置项,让脚本适配多个CSV文件;也可以增加清洗前后的行数和缺失值统计,用于验证处理效果。掌握这些基础操作之后,可以逐步构建可复用的数据预处理管道,为后续分析和建模打下更可靠的基础。