数据清洗基础:重复值处理、格式统一、数据类型转换

数据清洗,说白了就是「擦干净黑板再写题」。原始组学数据里,重复、错位、格式混乱的情况太常见了。我刚开始做生物信息管线时,被一份基因组注释文件坑了三天——里面居然有 30% 的重复行。从那以后,清洗成了我管线里的头号步骤。

这一章我们聚焦三个最脏的环节:重复值处理、格式统一、数据类型转换。先把它们搞定,后续的特征工程才站得住。

数据清洗基础管线(第3章核心逻辑) 原始组学数据 重复值检测 去重 / 保留 格式统一 数据类型转换 干净数据 💡 为什么要按这个顺序? 先查重 → 再去重 → 再统一格式 → 最后转类型。顺序乱了会出问题: 比如,如果你先转了类型再去重,格式相同的重复行可能因为浮点精度差异而「漏网」。

3.1 重复值处理:别让数据「自我复制」

重复值是最常见的「脏东西」。它会让统计量失真,让模型看过的东西被反复加权。我个人习惯在处理管线里单独写一个 dedup 步骤,绝不含糊。

3.1.1 完全重复 vs 部分重复

  • 完全重复:整行一模一样。直接删,几乎没争议。
  • 部分重复:只有主键或关键列相同。你需要决定「保留哪一行」,或「合并信息」。
实践建议: 对于转录组表达数据,完全重复大概率是测序深度不均造成的拷贝。我会先保留第一次出现的行,再把重复行的计数加起来做一次归一化。这样既能消除重复,又不丢信息。

用 Python 处理重复值,pandas 是最顺手的选择:

import pandas as pd

# 加载原始表达矩阵
df = pd.read_csv("expression_matrix.csv", sep="\t")

# 查看重复行数量
print(f"重复行数(完全重复): {df.duplicated().sum()}")

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

# 按基因ID去重,保留均值(部分重复场景)
df_dedup = df.groupby("gene_id", as_index=False).mean()

3.1.2 避坑指南:别把「合法重复」删了

我曾经犯过一个低级错误。一份时间序列的蛋白质组数据里,同一个蛋白在不同时间点有不同丰度值。我直接用 drop_duplicates() 把时间戳给干掉了,结果把整个动态变化都删没了。

你应该这样思考:

  • 重复的是「实验批次」还是「测量值」?
  • 如果是批次,保留最新的。
  • 如果是生物学重复,取均值或中位数。
  • 如果是技术重复,先评估一致性,再决定聚合方式。

3.2 格式统一:让数据「说同一种语言」

格式不统一是另一个让人头疼的问题。同一个样本名,一个文件写 Sample_001,另一个写 sample-001,第三个写 S001。合并的时候直接崩。

3.2.1 命名规范:小写 + 下划线

我个人习惯:所有列名、样本名、分组名统一转为 小写,空格替换为下划线。这是个简单的规则,但能避免无数的匹配失败。

# 统一列名格式
df.columns = df.columns.str.lower().str.replace(" ", "_")

# 统一样本名格式
df["sample_id"] = df["sample_id"].str.lower().str.replace("-", "_")

3.2.2 日期与数值格式

日期格式的混乱我遇到太多了。有人用 2024-01-15,有人用 01/15/2024,还有人用 15-Jan-2024。你想想看,如果你不统一,时间序列怎么对齐?

我建议一步到位:

# 统一日期为 ISO 格式
df["date"] = pd.to_datetime(df["date"], errors="coerce").dt.strftime("%Y-%m-%d")

数值列也要小心。很多时候从实验仪器导出的文件里,数字里混了中文逗号、单位、甚至空格。

# 去除千分位逗号、单位后缀
df["intensity"] = df["intensity"].str.replace(",", "").str.replace(" ng/ml", "").astype(float)
⚠️ 注意: 使用 errors="coerce" 时,无法转换的值会变成 NaN。请务必检查转换后的缺失数量,如果太多,说明原始格式有大问题。

3.3 数据类型转换:让机器「读得懂」

数据类型不对,模型根本没法训练。绝大多数机器学习库只吃数值类型。类别、字符串、布尔值都需要转。

3.3.1 常见类型映射

原始类型 常见问题 转换目标 转换方法
整数列存为字符串 误含空格 int64 astype(int)
小数存为字符串 带单位 float64 pd.to_numeric()
分类变量 字符型 category astype('category')
布尔值 “Y/N”字符串 bool map({'Y':True, 'N':False})

3.3.2 代码实战:一条管线搞定

# 确认当前各列类型
print(df.dtypes)

# 批量转换数值列
num_cols = ["expression_value", "p_value", "fold_change"]
for col in num_cols:
    df[col] = pd.to_numeric(df[col], errors="coerce")

# 分类列转换
cat_cols = ["tissue_type", "condition"]
for col in cat_cols:
    df[col] = df[col].astype("category")

# 检查转换结果
print(df.dtypes)
核心原则: 所有列最终应该对应 pandas 的数据类型。不要留下 object 类型的列,除非它是真正的自由文本。

3.4 本章小结:干净数据是模型好坏的命门

重复值、格式、类型——这三个问题看似基础,但不处理干净,后面全白搭。我个人的准则是:数据清洗耗时不能少于整个管线开发的 30%。否则,你就是在劣质品上雕花。

现在,你的数据应该是:

  • 无完全重复行,部分重复已按规则聚合
  • 所有命名格式统一为小写+下划线
  • 所有类型都已转换为合适的数值或类别