输入一份含缺失值、重复行、错误类型的临床数据表,用 Pandas 一步步清洗:dropna/fillna 处理缺失、drop_duplicates 去重、pd.to_numeric(errors='coerce') 把非数值转成 NaN、str.strip() 去掉基因名里的空格——这套清洗流程是下游统计分析的必备前处理,脏数据不清就会出现无法解释的报错或错误结论。最容易忽略的坑是 gene 名前后有空格导致 merge 不上,或者 count 列混了字符串导致统计失败。这一讲把生信数据清洗常用操作一次理清:缺失值、去重、类型转换、字符串清理、列操作、行过滤。
为什么必须先清洗
GEO 下载的临床表、测序公司发的样本表,十有八九有空格、缺失、类型错误。不清洗直接 merge,merge 不上;不处理缺失直接做统计,NaN 悄悄传播,结果全是 NaN。两分钟清洗,省去几小时排查。
核心代码速查
按「缺失值 / 去重 / 类型转换 / 字符串清理 / 列操作 / 行过滤」分组:
# Pandas 数据清洗速查 -- 逐行运行
import pandas as pd
import numpy as np
dirty = pd.read_csv("testdata/dirty.csv")
print("原始形状:", dirty.shape)
print("原始数据:\n", dirty)
# ---- 缺失值 ----
print("\n每列缺失数:\n", dirty.isnull().sum())
print("有缺失的行:\n", dirty[dirty.isnull().any(axis=1)])
df_drop = dirty.dropna()
print("\n删所有含缺失行后:", df_drop.shape)
df_drop_gene = dirty.dropna(subset=["gene"])
print("仅删 gene 缺失行:", df_drop_gene.shape)
dirty["count_filled_0"] = dirty["count"].fillna(0)
count_num = pd.to_numeric(dirty["count"], errors="coerce")
dirty["count_filled_mean"] = count_num.fillna(count_num.mean())
print("\n填 0:", dirty["count_filled_0"].tolist())
print("填均值:", dirty["count_filled_mean"].round(1).tolist())
# ---- 去重 ----
print("\n重复行数:", dirty.duplicated().sum())
df_dedup = dirty.drop_duplicates()
print("去重后:", df_dedup.shape)
df_dedup_gene = dirty.drop_duplicates(subset=["gene"])
print("按 gene 去重:", df_dedup_gene.shape)
# ---- 类型转换 ----
dirty["count_num"] = pd.to_numeric(dirty["count"], errors="coerce")
print("\n强转数值后:\n", dirty[["gene","count","count_num"]])
dirty["count_str"] = dirty["count"].astype(str)
print("转 str 示例:", dirty["count_str"].head(3).tolist())
# ---- 字符串清理 ----
dirty["gene_clean"] = dirty["gene"].str.strip()
dirty["gene_upper"] = dirty["gene"].str.strip().str.upper()
dirty["gene_under"] = dirty["gene"].str.strip().str.replace(" ", "_")
has_tp53 = dirty["gene"].str.strip().str.contains("TP53", na=False)
print("\n基因名清理:\n", dirty[["gene","gene_clean","gene_upper"]].head(5))
print("含 TP53:", has_tp53.tolist())
# ---- 列操作 ----
df_renamed = dirty.rename(columns={"useless_col": "tag"})
df_dropped = dirty.drop(columns=["useless_col", "count_filled_0", "count_filled_mean",
"count_num", "count_str", "gene_clean",
"gene_upper", "gene_under"])
print("\n重命名后列:", df_renamed.columns.tolist())
print("删列后列:", df_dropped.columns.tolist())
# ---- 过滤行 ----
dirty2 = dirty.copy()
dirty2["count_num"] = pd.to_numeric(dirty2["count"], errors="coerce")
df_high = dirty2[dirty2["count_num"] > 100]
df_query = dirty2.query("count_num > 100 and gene != 'NA'")
print("\ncount > 100 的行:", df_high.shape[0])
print("query 过滤结果:", df_query.shape[0])
print("\n✓ 清洗演示完成")
pd.to_numeric(errors='coerce') 把非数值变 NaN(而不是报错);str.strip() 去首尾空格;query('count > 100') 支持列名直接写条件,比布尔索引更易读。
示例的输出日志
用含脏数据的合成 CSV 演示全套清洗(重复行/缺失/非数值/前后空格):
原始形状: (8, 4)
原始数据:
gene count sample useless_col
0 TP53 128 s1 x
1 TP53 128 s1 x
2 KRAS NaN s2 y
3 MYC 234 s2 y
4 EGFR abc s3 z
5 BRCA1 312 s3 z
6 NaN 45 s4 w
7 CDH1 89 s4 w
每列缺失数:
gene 1
count 1
sample 0
useless_col 0
dtype: int64
有缺失的行:
gene count sample useless_col
2 KRAS NaN s2 y
6 NaN 45 s4 w
删所有含缺失行后: (6, 4)
仅删 gene 缺失行: (7, 4)
填 0: ['128', '128', 0, '234', 'abc', '312', '45', '89']
填均值: [128.0, 128.0, 156.0, 234.0, 156.0, 312.0, 45.0, 89.0]
重复行数: 1
去重后: (7, 6)
按 gene 去重: (7, 6)
强转数值后:
gene count count_num
0 TP53 128 128.0
1 TP53 128 128.0
2 KRAS NaN NaN
3 MYC 234 234.0
4 EGFR abc NaN
5 BRCA1 312 312.0
6 NaN 45 45.0
7 CDH1 89 89.0
转 str 示例: ['128', '128', 'nan']
基因名清理:
gene gene_clean gene_upper
0 TP53 TP53 TP53
1 TP53 TP53 TP53
2 KRAS KRAS KRAS
3 MYC MYC MYC
4 EGFR EGFR EGFR
含 TP53: [True, True, False, False, False, False, False, False]
重命名后列: ['gene', 'count', 'sample', 'tag', 'count_filled_0', 'count_filled_mean', 'count_num', 'count_str', 'gene_clean', 'gene_upper', 'gene_under']
删列后列: ['gene', 'count', 'sample']
count > 100 的行: 4
query 过滤结果: 4
✓ 清洗演示完成
OK
原始 8 行含 1 个重复和 1 个缺失;去重后 7 行;to_numeric 把 'abc' 转成 NaN;str.strip 把 ' MYC ' 变成 'MYC'——清洗后数据可以正常用于下游分析。
扩展:组合清洗流程
一份真实临床表的完整清洗流程(按需裁剪):
import pandas as pd
meta = pd.read_csv("clinical_meta.csv")
meta.columns = meta.columns.str.strip().str.lower().str.replace(" ", "_")
meta["sample_id"] = meta["sample_id"].str.strip()
meta = meta.drop_duplicates(subset=["sample_id"], keep="first")
for col in ["age", "os_months", "tumor_size"]:
if col in meta.columns:
meta[col] = pd.to_numeric(meta[col], errors="coerce")
for col in meta.select_dtypes(include="number").columns:
meta[col] = meta[col].fillna(meta[col].median())
meta = meta[meta["sample_id"].notna() & (meta["sample_id"] != "NA")]
print(f"清洗后: {meta.shape[0]} 个样本, {meta.shape[1]} 个变量")
meta.to_csv("clinical_meta_clean.csv", index=False)
列名规范化放第一步,避免后续操作因为大小写/空格不匹配报 KeyError;数值填中位数比均值更稳健(不受极端值影响)。
避坑指南
- gene 名前后有空格 merge 不上 → 先
df['gene'].str.strip() 清理 - dropna 删太多行 → 先
isnull().sum() 看哪列缺失多,只对关键列 dropna(subset=[...]) - fillna 用 method='ffill' 报警告 → pandas 2.x 改用
df.ffill() query 列名有空格/特殊字符 → 用反引号包住:query('列 名 > 100')
📦 完整代码 + 测试数据下载
百度网盘链接:https://pan.baidu.com/s/1pNwWP1Sg-JJitFP7inRMbw?pwd=p03c
提取码:p03c(代码已实测可直接运行,建议保存到自己网盘)
