03 · Pandas 数据处理与清洗
本章目标
熟练使用 Pandas 完成数据读取、清洗、筛选、分组聚合、合并连接与透视表。GEO 表达矩阵、中药成分-靶点表、临床数据表,都靠 Pandas 处理。
本章路线
0. 小白导读:Pandas 是什么?先用什么比喻听懂它?
1. Pandas 是什么
Pandas 是 Python 最核心的数据分析库,提供表格型数据(DataFrame)——类似 Excel 表格,但比 Excel 强大得多。
为什么科研必须用 Pandas?
- 一键读取 CSV / Excel / TSV 等格式
- 灵活的缺失值处理、数据清洗
- 按分组计算统计量(groupby 是科研高频操作)
- 两个表按条件合并(merge,类似 Excel 的 VLOOKUP)
📌要点
2026 年版本说明:pandas 3.0(2026 年 1 月发布)引入了重要变化——Copy-on-Write 默认开启(修改 DataFrame 时不再意外改动原数据);字符串列默认使用 PyArrow 类型。本书代码基于 pandas 3.x,但大多数操作在 pandas 2.x 上也兼容。
import pandas as pd import numpy as np
2. 数据结构:Series 与 DataFrame
2.1 Series(一维带标签数组)
# 从列表创建 Series s = pd.Series([3.2, 5.8, 1.5], index=["丹参", "川芎", "黄芪"]) print(s) # 丹参 3.2 # 川芎 5.8 # 黄芪 1.5 # dtype: float64 # 访问 print(s["丹参"]) # 3.2 print(s.values) # [3.2 5.8 1.5] print(s.index) # Index(['丹参', '川芎', '黄芪'], dtype='object')
2.2 DataFrame(二维表格,核心)
# 从字典创建(每个键是一列) df = pd.DataFrame({ "sample": ["S001", "S002", "S003"], "group": ["control", "model", "model"], "value": [3.2, 5.8, 1.5] }) print(df) # sample group value # 0 S001 control 3.2 # 1 S002 model 5.8 # 2 S003 model 1.5
3. 数据读写
3.1 CSV 文件
# 读取 df = pd.read_csv("data.csv") df = pd.read_csv("data.csv", encoding="utf-8") # 指定编码 df = pd.read_csv("data.csv", index_col=0) # 指定第0列为行索引 df = pd.read_csv("data.csv", na_values=["NA", "N/A"]) # 指定缺失值标记 # 写入 df.to_csv("output.csv", index=False, encoding="utf-8") # 不写行索引
3.2 Excel 文件
# 读取(需要安装 openpyxl 或 xlrd) df = pd.read_excel("data.xlsx", sheet_name="Sheet1") # 写入 df.to_excel("output.xlsx", sheet_name="结果", index=False)
3.3 其他常见格式
# TSV(制表符分隔) pd.read_table("data.tsv", sep="\t") # 直接读取剪贴板(从 Excel 复制后,Ctrl+C,然后: df = pd.read_clipboard()
4. 数据查看与描述
df = pd.read_csv("data.csv") df.head() # 前5行 df.tail() # 后5行 df.head(10) # 前10行 df.info() # 列名、非空计数、数据类型(科研最常用!) df.describe() # 数值列的统计摘要(均值、标准差、四分位数) df.shape # (行数, 列数) df.columns # 列名列表 df.dtypes # 每列类型
💡提示
拿到数据的第一步:永远先 df.head() + df.info(),了解数据长什么样、有没有缺失值、类型对不对。
5. 选择与过滤
5.1 列选择
# 选单列 → Series df["group"] df.group # 选多列 → DataFrame df[["sample", "value"]]
5.2 行选择
# 按位置(行号) df.iloc[0] # 第1行 df.iloc[0:3] # 第1-3行(左闭右开) df.iloc[[0, 2, 3]] # 第1、3、4行 # 按标签(行索引名) df.loc[0] # 索引为0的行 df.loc[0:2] # 索引0到2的行(loc 是闭区间!)
⚠注意
️ iloc 是左闭右开,loc 是闭区间。这是高频混淆点。
5.3 布尔索引(科研最高频操作)
# 筛选 model 组的数据 df[df["group"] == "model"] # 筛选 value > 3 的数据 df[df["value"] > 3] # 组合条件(用 & | ~,不是 and or not) df[(df["group"] == "model") & (df["value"] > 3)] df[df["group"].isin(["model", "control"])] # 属于某集合
5.4 query 方法(简洁写法)
# 等价于上面的组合条件 df.query("group == 'model' and value > 3")
6. 数据清洗
6.1 缺失值处理
# 检测缺失 df.isnull().sum() # 每列缺失计数 df.isna().sum() # 同上 # 删除缺失 df.dropna() # 删除有缺失的行 df.dropna(axis=1) # 删除有缺失的列 df.dropna(subset=["value"]) # 只在指定列检查缺失 # 填充缺失 df.fillna(0) # 用0填充 df.fillna(df["value"].mean()) # 用均值填充 df["value"].fillna(method="ffill") # 用前一个值填充
6.2 重复值处理
df.duplicated() # 判断重复行 df.drop_duplicates() # 删除重复行 df.drop_duplicates(subset=["sample"]) # 按指定列去重
6.3 类型转换
# 转数值 df["value"] = pd.to_numeric(df["value"], errors="coerce") # 转日期 df["date"] = pd.to_datetime(df["date"], format="%Y-%m-%d") # 转字符串 df["sample"] = df["sample"].astype(str)
💡提示
errors="coerce" 表示遇到无法转换的值设为 NaN,比直接报错更友好。
6.4 字符串处理
df["gene"].str.strip() # 去首尾空白 df["gene"].str.replace(" ", "") # 去掉空格 df["gene"].str.upper() # 转大写 df["gene"].str.contains("TP53") # 是否包含 df["gene"].str.split("_") # 拆分 df["gene"].str.strip().str.upper() # 链式调用
💡提示
科研中清洗基因名、样本名时,strip + upper + replace 是每日必用组合。
7. 排序与排名
# 按值排序 df.sort_values("value") # 升序 df.sort_values("value", ascending=False) # 降序 df.sort_values(["group", "value"], ascending=[True, False]) # 多列排序 # 按索引排序 df.sort_index() # 排名 df["rank"] = df["value"].rank() # 默认升序排名 df["rank"] = df["value"].rank(ascending=False) # 降序排名
8. 分组聚合(科研最高频操作之一)
8.1 groupby 基本用法
# 按 group 分组,计算每组的均值 df.groupby("group")["value"].mean() # 分组后求多个统计量 df.groupby("group")["value"].agg(["mean", "std", "count"]) # 分组后多列多统计量 df.groupby("group").agg({ "value": ["mean", "std"], "age": "mean" })
8.2 transform:保持原行数的分组计算
transform 返回与原 DataFrame 等长的结果,常用于添加一列"每组的均值"。
# 每行减去该组均值(组内中心化) df["group_mean"] = df.groupby("group")["value"].transform("mean") df["centered"] = df["value"] - df["group_mean"]
8.3 apply:自定义分组操作
# 对每组应用自定义函数 def zscore_group(x): return (x - x.mean()) / x.std() df["zscore"] = df.groupby("group")["value"].transform(zscore_group)
9. 合并连接
9.1 merge(类似 SQL JOIN 或 Excel VLOOKUP)
# 两个表按公共列合并 targets = pd.DataFrame({ "gene": ["TP53", "AKT1", "EGFR"], "uniprot": ["P04637", "P31749", "P00533"] }) expr = pd.DataFrame({ "gene": ["TP53", "AKT1", "TP53", "EGFR"], "value": [3.2, 5.8, 4.1, 2.5] }) # 内连接(默认) merged = pd.merge(expr, targets, on="gene", how="inner") # 左连接(保留左边所有行) pd.merge(expr, targets, on="gene", how="left") # 右连接 pd.merge(expr, targets, on="gene", how="right") # 外连接(保留两边所有行) pd.merge(expr, targets, on="gene", how="outer")
9.2 concat(纵向/横向拼接)
# 纵向拼接(行方向) df1 = pd.DataFrame({"A": [1, 2], "B": [3, 4]}) df2 = pd.DataFrame({"A": [5, 6], "B": [7, 8]}) pd.concat([df1, df2], axis=0) # 默认,追加行 # 横向拼接(列方向) pd.concat([df1, df2], axis=1) # 列方向拼接
9.3 join(按索引合并)
# 按索引合并两个 DataFrame df1.join(df2, lsuffix="_left", rsuffix="_right")
10. 透视表与交叉表
10.1 pivot_table(科研高频)
# 生成透视表:行为 group,列为 drug,值为 value 的均值 pd.pivot_table(df, values="value", index="group", columns="drug", aggfunc="mean", fill_value=0)
10.2 crosstab(交叉表,统计频数)
# 两个分类变量的频数统计 pd.crosstab(df["group"], df["drug"])
11. 描述统计与相关性
# 描述统计 df["value"].mean() # 均值 df["value"].median() # 中位数 df["value"].std() # 标准差 df["value"].min() / .max() # 最小值 / 最大值 df["value"].quantile([0.25, 0.5, 0.75]) # 四分位数 # 频数统计 df["group"].value_counts() # 分组频数 df["group"].value_counts(normalize=True) # 百分比 # 相关性矩阵 df[["value1", "value2", "value3"]].corr() # Pearson 相关 df[["value1", "value2"]].corr(method="spearman") # Spearman 相关
12. 补充:科研高频操作
12.1 时间序列处理
# 转日期 df["date"] = pd.to_datetime(df["date"]) # 按时间重采样 df.set_index("date")["value"].resample("D").mean() # 日均值 df.set_index("date")["value"].resample("W").mean() # 周均值 df.set_index("date")["value"].resample("ME").mean() # 月均值 # 滚动窗口 df["rolling_mean"] = df["value"].rolling(window=3).mean() # 3点滑动平均 # 时间差 df["time_diff"] = df["date"].diff() # 相邻时间差
12.2 apply / map / applymap
# apply:对整个 Series 或 DataFrame 应用函数 df["value_sq"] = df["value"].apply(lambda x: x ** 2) # map:对 Series 每个值做映射 df["group_num"] = df["group"].map({"control": 0, "model": 1}) # 对 DataFrame 每个元素应用函数(不常用) df.select_dtypes(include="number").applymap(lambda x: round(x, 2))
12.3 大数据分块读取
# 分块读取 GB 级文件(节省内存) chunks = pd.read_csv("big_data.csv", chunksize=10000) results = [] for chunk in chunks: # 处理每块数据 results.append(chunk.groupby("gene")["value"].mean()) result = pd.concat(results)
12.4 快速绘图
import matplotlib.pyplot as plt df["value"].plot(kind="hist", bins=20) # 直方图 df.groupby("group")["value"].mean().plot(kind="bar") # 柱状图 df.plot(kind="scatter", x="value1", y="value2") # 散点图 plt.show() # 箱线图 df.boxplot(column="value", by="group")
12.5 数据标准化
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() df["value_z"] = scaler.fit_transform(df[["value"]]) # 注意:需要二维输入
13. 常见坑
| 坑 | 说明 | 正确做法 |
|---|---|---|
iloc 与 loc 混淆 |
前者左闭右开,后者闭区间 | 记清楚:i=位置,l=标签 |
| 修改视图触发警告 | pandas 3.x 的 Copy-on-Write | 显式 .copy() 再修改 |
| 链式赋值 | df[df.a>0]['b']=1 可能无效 |
用 .loc[条件, 列] = 值 |
| 读 CSV 中文乱码 | 默认编码问题 | encoding="utf-8" |
inplace=True |
在 pandas 3.x 中逐步淘汰 | 用 df = df.dropna() |
忘记 errors="coerce" |
类型转换报错 | pd.to_numeric(..., errors="coerce") |
| groupby 后忘记 reset_index | 分组键还在索引里 | 链式 .reset_index() |
14. 练习与交付物
交付物(必做)
❞笔记
科研数据清洗报告:选取公开科研数据(如 GEO 表达矩阵或中药成分-靶点表),完成以下步骤:
- 读取:用
pd.read_csv读取数据,指定编码。 - 查看:
head()、info()、describe(),记录原始数据概况。 - 清洗:
- 处理缺失值(
fillna或dropna) - 删除重复行 - 类型转换(pd.to_numeric) - 字符串清洗(strip/upper/replace) - 筛选:用布尔索引筛选出满足条件的数据。
- 分组统计:按分组计算均值、标准差。
- 输出:清洗前后对比说明 + 干净数据 CSV。
进阶练习(用真实数据练手)
- 从 GEO 数据库下载一个表达矩阵 CSV(如 GSE 系列),用 Pandas 读取并做初步探索。
- 对两个表做
merge合并(如基因表达表 + 基因注释表)。 - 生成一个透视表(行为样本分组,列为基因名,值为表达量均值)。
- 用
groupby + transform对每组表达式做组内标准化。
15. 速查表
| 需求 | 代码 |
|---|---|
| 读 CSV | pd.read_csv("path", encoding="utf-8") |
| 写 CSV | df.to_csv("path", index=False) |
| 快速查看 | df.head() / df.info() / df.describe() |
| 选列 | df["col"] / df[["col1","col2"]] |
| 选行 | df.iloc[0:3] / df.loc[0:2] |
| 布尔筛选 | df[df["col"] > 3] |
| 组合条件 | df[(条件1) & (条件2)] |
| 缺失处理 | df.isnull().sum() / df.fillna(0) / df.dropna() |
| 分组聚合 | df.groupby("group")["value"].mean() |
| 分组多统计 | df.groupby("group")["value"].agg(["mean","std"]) |
| 合并 | pd.merge(df1, df2, on="key", how="left") |
| 透视表 | pd.pivot_table(df, values="v", index="i", columns="c") |
| 排序 | df.sort_values("col", ascending=False) |
| 类型转换 | pd.to_numeric(df["col"], errors="coerce") |
| 时间序列 | pd.to_datetime(df["date"]) |
上一章:02 · NumPy 数值计算基础 | 下一章:04 · 包管理与项目版本管理