Python 数据处理进阶:Pandas / Polars 生信实战教程¶
一句话说明¶
Pandas 是 Python 最常用的表格数据处理库,Polars 是它的高性能替代;掌握这两个工具,你就能用 Python 完成 R 里 dplyr 能做的一切数据清洗和统计汇总工作。
为什么要学¶
| 场景 | 没有 Pandas | 有 Pandas |
|---|---|---|
| 合并 200 个样本的 Kraken2 丰度表 | 手写循环逐行解析 TSV,容易出错 | pd.concat() 一行搞定 |
| 按分组(健康 vs 糖尿病)算平均丰度 | 嵌套字典 + for 循环 | df.groupby("group").mean() |
| 找缺失值并填充 | 自己写 if/else 判断 | df.fillna(0) |
| 处理百万行大文件 | 内存爆掉 | chunksize 分块读取 |
面试高频点:生信岗位笔试/机试常考 Pandas 操作(groupby、merge、apply),手写能力很重要。
高频操作详解¶
1. 读写 CSV / Excel / TSV¶
import pandas as pd
# === 读取 CSV(逗号分隔)===
# 最基础的读法,第一行自动当列名
df_csv = pd.read_csv("sample_metadata.csv")
# === 读取 TSV(制表符分隔,生信最常见)===
# Kraken2、MetaPhlAn 输出都是 TSV
df_tsv = pd.read_csv("species_abundance.tsv", sep="\t")
# === 读取 Excel ===
# 需要安装 openpyxl:pip install openpyxl
df_excel = pd.read_excel("metadata.xlsx", sheet_name="Sheet1")
# === 只读前 100 行(快速预览大文件)===
df_peek = pd.read_csv("huge_file.tsv", sep="\t", nrows=100)
# === 指定列的数据类型(节省内存)===
df_typed = pd.read_csv("abundance.tsv", sep="\t", dtype={
"sample_id": str, # 样本ID强制为字符串,防止纯数字ID被识别为int
"read_count": "int32", # 用32位整数代替默认64位,省一半内存
})
# === 写出到文件 ===
df_tsv.to_csv("output.tsv", sep="\t", index=False) # index=False 不写行号
df_tsv.to_excel("output.xlsx", index=False) # 写 Excel
2. 筛选过滤¶
import pandas as pd
# 构造示例:样本元数据表
metadata = pd.DataFrame({
"sample_id": ["S01", "S02", "S03", "S04", "S05"],
"group": ["T2D", "Healthy", "T2D", "Healthy", "T2D"],
"age": [55, 30, 62, 28, 45],
"bmi": [28.5, 22.1, 31.0, 21.5, 27.8],
})
# === 单条件筛选:只要糖尿病组 ===
t2d_samples = metadata[metadata["group"] == "T2D"]
# 白话:方括号里放布尔条件,True 的行留下
# === 多条件筛选:糖尿病组 且 年龄>50 ===
# 注意:多条件必须用 & (且) 或 | (或),不能用 and/or
# 每个条件要加小括号
t2d_old = metadata[(metadata["group"] == "T2D") & (metadata["age"] > 50)]
# === isin:筛选多个值 ===
# 比如只要 S01 和 S03 两个样本
subset = metadata[metadata["sample_id"].isin(["S01", "S03"])]
# === query 方法(更像 SQL 的写法)===
result = metadata.query("group == 'T2D' and bmi > 28")
# === 按列值排序 ===
sorted_df = metadata.sort_values("age", ascending=False) # 年龄从大到小
3. 分组聚合 groupby¶
import pandas as pd
# 构造示例:物种丰度表(长格式)
abundance = pd.DataFrame({
"sample_id": ["S01", "S01", "S02", "S02", "S03", "S03"],
"species": ["E.coli", "B.fragilis", "E.coli", "B.fragilis", "E.coli", "B.fragilis"],
"group": ["T2D", "T2D", "Healthy", "Healthy", "T2D", "T2D"],
"relative_abundance": [0.15, 0.08, 0.05, 0.12, 0.20, 0.06],
})
# === 按疾病分组,算每组的平均丰度 ===
group_mean = abundance.groupby("group")["relative_abundance"].mean()
# 结果:Healthy 0.085, T2D 0.1225
# 白话:先按 group 列分堆,再对 relative_abundance 列求均值
# === 按疾病+物种交叉分组 ===
cross_mean = abundance.groupby(["group", "species"])["relative_abundance"].mean()
# === 同时算多个统计量 ===
stats = abundance.groupby("group")["relative_abundance"].agg(["mean", "std", "count"])
# 结果是一个表,列为 mean / std / count
# === 自定义聚合函数 ===
# 比如算每组丰度的变异系数 CV = std / mean
cv = abundance.groupby("group")["relative_abundance"].agg(
lambda x: x.std() / x.mean() # lambda 匿名函数
)
4. 合并 merge / concat¶
import pandas as pd
# === 示例数据 ===
# 表1:样本元数据
metadata = pd.DataFrame({
"sample_id": ["S01", "S02", "S03"],
"group": ["T2D", "Healthy", "T2D"],
})
# 表2:物种丰度(宽格式,每列一个样本)
abundance = pd.DataFrame({
"species": ["E.coli", "B.fragilis", "F.prausnitzii"],
"S01": [1500, 800, 200],
"S02": [500, 1200, 3000],
"S03": [2000, 600, 150],
})
# === merge:按共同列合并(类似 SQL 的 JOIN)===
# 把元数据和丰度表通过 sample_id 连起来
# 先把丰度表从宽格式转为长格式
abundance_long = abundance.melt(
id_vars="species", # 保持不动的列
var_name="sample_id", # 原来的列名变成这一列的值
value_name="read_count", # 原来的数值放这一列
)
# 现在可以 merge 了
merged = abundance_long.merge(metadata, on="sample_id", how="left")
# how="left":以左表为基准,左表全保留,右表匹配不上的填 NaN
# how="inner":只保留两边都有的(默认)
# how="outer":全保留,匹配不上的填 NaN
# === concat:纵向拼接(上下叠加)===
# 合并多个样本的 Kraken2 输出
df1 = pd.DataFrame({"species": ["E.coli"], "count": [100], "sample": ["S01"]})
df2 = pd.DataFrame({"species": ["E.coli"], "count": [200], "sample": ["S02"]})
combined = pd.concat([df1, df2], ignore_index=True)
# ignore_index=True:重置行号,不然会有重复的 0, 0
# === concat:横向拼接(左右并排)===
left = pd.DataFrame({"A": [1, 2]})
right = pd.DataFrame({"B": [3, 4]})
side_by_side = pd.concat([left, right], axis=1) # axis=1 表示按列拼
5. 数据透视 pivot¶
import pandas as pd
# 长格式丰度表 → 宽格式丰度矩阵(生信最常用的转换)
abundance_long = pd.DataFrame({
"sample_id": ["S01", "S01", "S02", "S02"],
"species": ["E.coli", "B.fragilis", "E.coli", "B.fragilis"],
"read_count": [1500, 800, 500, 1200],
})
# === pivot:长转宽 ===
wide = abundance_long.pivot(
index="species", # 行:物种
columns="sample_id", # 列:样本
values="read_count", # 填的值:reads数
)
# 结果就是标准的物种-样本丰度矩阵
# === pivot_table:带聚合的透视(有重复值时用)===
# 如果同一个 sample+species 有多条记录,可以指定聚合方式
wide_agg = abundance_long.pivot_table(
index="species",
columns="sample_id",
values="read_count",
aggfunc="sum", # 重复的加起来;也可以用 "mean"
fill_value=0, # 缺失位置填 0
)
# === melt:宽转长(pivot 的逆操作)===
long_again = wide.reset_index().melt(
id_vars="species",
var_name="sample_id",
value_name="read_count",
)
6. apply 自定义函数¶
import pandas as pd
import numpy as np
abundance = pd.DataFrame({
"species": ["E.coli", "B.fragilis", "Unknown_sp1"],
"S01": [1500, 800, 50],
"S02": [500, 1200, 30],
})
# === 对单列 apply ===
# 给物种名加前缀
abundance["species_fmt"] = abundance["species"].apply(
lambda x: f"s__{x}" # 类似 MetaPhlAn 的格式
)
# === 对每一行 apply(axis=1)===
# 判断某物种在多少个样本中检出(count > 0)
abundance["prevalence"] = abundance[["S01", "S02"]].apply(
lambda row: (row > 0).sum(), # 每行统计大于0的个数
axis=1, # axis=1 表示逐行操作
)
# === 对整列做向量化操作(推荐,比 apply 快 10-100 倍)===
# 计算 log10 变换(加伪计数避免 log(0))
sample_cols = ["S01", "S02"]
abundance[sample_cols] = np.log10(abundance[sample_cols] + 1)
# 白话:能用 numpy 向量化就不要用 apply,速度差距巨大
7. 缺失值处理¶
import pandas as pd
import numpy as np
# 生信中缺失值常见场景:某物种在某样本中未检出
df = pd.DataFrame({
"species": ["E.coli", "B.fragilis", "Archaea_sp"],
"S01": [1500, np.nan, 50], # B.fragilis 在 S01 未检出
"S02": [500, 1200, np.nan], # Archaea_sp 在 S02 未检出
})
# === 检查缺失值 ===
print(df.isnull().sum()) # 每列有几个缺失
print(df.isnull().sum().sum()) # 总共几个缺失
# === 填充缺失值 ===
df_filled = df.fillna(0) # 未检出的填 0(生信最常用)
# === 按列的中位数填充(适合元数据中的连续变量如 BMI)===
df["S01"] = df["S01"].fillna(df["S01"].median())
# === 删除含缺失值的行 ===
df_clean = df.dropna() # 任一列有 NaN 就删
df_clean2 = df.dropna(subset=["S01"]) # 只看 S01 列有没有 NaN
# === 替换特定值为 NaN(比如把 "NA"、"unclassified" 标记为缺失)===
df.replace({"unclassified": np.nan, "NA": np.nan}, inplace=True)
8. 字符串操作 str¶
import pandas as pd
# 生信中常见字符串处理:解析分类学层级
taxonomy = pd.DataFrame({
"lineage": [
"k__Bacteria|p__Firmicutes|g__Lactobacillus|s__L.rhamnosus",
"k__Bacteria|p__Bacteroidetes|g__Bacteroides|s__B.fragilis",
"k__Bacteria|p__Proteobacteria|g__Escherichia|s__E.coli",
]
})
# === 提取属名(genus)===
# 用正则从 g__ 到下一个 | 之间提取
taxonomy["genus"] = taxonomy["lineage"].str.extract(r"g__([^|]+)")
# [^|]+ 意思是:匹配一个或多个非 | 字符
# === 提取种名(species)===
taxonomy["species"] = taxonomy["lineage"].str.extract(r"s__(.+)$")
# === 按分隔符切割 ===
# 把 lineage 按 | 分割成多列
split_cols = taxonomy["lineage"].str.split("|", expand=True)
split_cols.columns = ["kingdom", "phylum", "genus", "species"]
# === 常用字符串方法 ===
taxonomy["lineage_lower"] = taxonomy["lineage"].str.lower() # 全部小写
taxonomy["has_firm"] = taxonomy["lineage"].str.contains("Firmicutes") # 是否包含
taxonomy["lineage_clean"] = taxonomy["lineage"].str.replace("k__", "") # 替换
9. 时间序列¶
import pandas as pd
# 纵向队列研究:同一病人多时间点采样
timeline = pd.DataFrame({
"sample_id": ["P01_D0", "P01_D7", "P01_D30", "P02_D0", "P02_D7"],
"patient": ["P01", "P01", "P01", "P02", "P02"],
"collection_date": ["2024-01-01", "2024-01-08", "2024-01-31",
"2024-02-15", "2024-02-22"],
"shannon_diversity": [3.2, 2.8, 3.5, 4.1, 3.9],
})
# === 转为日期类型 ===
timeline["collection_date"] = pd.to_datetime(timeline["collection_date"])
# === 计算采样间隔天数 ===
timeline["days_since_first"] = timeline.groupby("patient")["collection_date"].transform(
lambda x: (x - x.min()).dt.days # 每个病人组内,算距第一次采样的天数
)
# === 按月统计采样数 ===
timeline["month"] = timeline["collection_date"].dt.month # 提取月份
monthly_count = timeline.groupby("month")["sample_id"].count()
# === 设置日期为索引后可以切片 ===
timeline = timeline.set_index("collection_date")
jan_data = timeline.loc["2024-01"] # 只取1月的数据
性能优化技巧¶
向量化 vs 循环¶
import pandas as pd
import numpy as np
# 构造 100万行 数据
n = 1_000_000
df = pd.DataFrame({
"read_count": np.random.randint(0, 10000, n),
"total_reads": np.random.randint(100000, 500000, n),
})
# ❌ 慢:用 for 循环逐行算相对丰度
# 不要这么写!处理 100 万行要好几秒
# for i in range(len(df)):
# df.loc[i, "rel_ab"] = df.loc[i, "read_count"] / df.loc[i, "total_reads"]
# ❌ 中等:用 apply(比循环快一点,但还是逐行)
# df["rel_ab"] = df.apply(lambda row: row["read_count"] / row["total_reads"], axis=1)
# ✅ 快:向量化运算(整列直接除,底层是 C 实现)
df["rel_ab"] = df["read_count"] / df["total_reads"]
# 速度对比:向量化通常比 for 循环快 100 倍以上
dtypes 优化¶
import pandas as pd
df = pd.read_csv("big_abundance.tsv", sep="\t")
# 查看内存占用
print(df.memory_usage(deep=True).sum() / 1024**2, "MB")
# === 优化策略 ===
# 1. 整数列降精度:int64 → int32 或 int16
df["read_count"] = df["read_count"].astype("int32") # 省一半内存
# 2. 分类列用 category 类型(重复值多的字符串列)
df["group"] = df["group"].astype("category") # "T2D"/"Healthy" 只存两次
df["phylum"] = df["phylum"].astype("category") # 门级别最多几十种
# 3. 浮点数降精度:float64 → float32
df["relative_abundance"] = df["relative_abundance"].astype("float32")
# 优化后再看内存,通常能省 50-70%
print(df.memory_usage(deep=True).sum() / 1024**2, "MB")
大文件分块读取¶
import pandas as pd
# 文件太大一次读不进内存时,分块处理
# chunksize=10000 表示每次只读 1万行
chunks = []
for chunk in pd.read_csv("huge_abundance.tsv", sep="\t", chunksize=10000):
# 在每个块内做过滤,只保留需要的数据
filtered = chunk[chunk["relative_abundance"] > 0.01]
chunks.append(filtered)
# 最后把过滤后的小块拼起来
result = pd.concat(chunks, ignore_index=True)
# 这样即使原文件有几十GB,也能处理
Polars 简介:Pandas 的高性能替代¶
Polars 是用 Rust 写的 DataFrame 库,API 风格类似 Pandas 但速度快 5-20 倍,原生支持多线程和惰性求值。
# 安装:pip install polars
import polars as pl
# === 读取 TSV ===
df = pl.read_csv("species_abundance.tsv", separator="\t")
# === 筛选 + 选列 ===
result = df.filter(
(pl.col("group") == "T2D") & (pl.col("relative_abundance") > 0.01)
).select(["sample_id", "species", "relative_abundance"])
# pl.col("列名") 是 Polars 的表达式写法,代替了 Pandas 的 df["列名"]
# === groupby 聚合 ===
stats = df.group_by("group").agg([
pl.col("relative_abundance").mean().alias("mean_ab"), # 均值
pl.col("relative_abundance").std().alias("std_ab"), # 标准差
pl.col("sample_id").n_unique().alias("n_samples"), # 不重复样本数
])
# === 惰性求值(Lazy API,处理大数据更快)===
# 先描述操作,不立即执行;最后 .collect() 时才计算
lazy_result = (
pl.scan_csv("huge_file.tsv", separator="\t") # scan 而非 read,不加载全部
.filter(pl.col("kingdom") == "Bacteria")
.group_by("phylum")
.agg(pl.col("read_count").sum())
.sort("read_count", descending=True)
.collect() # 这一步才真正执行,Polars 会自动优化执行计划
)
# === Pandas ↔ Polars 互转 ===
pandas_df = df.to_pandas() # Polars → Pandas
polars_df = pl.from_pandas(pandas_df) # Pandas → Polars
什么时候用 Polars:文件 >1GB、需要多线程加速、对速度有硬性要求时。日常小数据用 Pandas 更方便(生态更成熟、教程更多)。
生信实战案例¶
案例 1:处理 Kraken2 分类报告¶
import pandas as pd
# Kraken2 标准输出格式(6列,无表头)
# 列含义:百分比、直接覆盖reads、该层级reads、分类层级、TaxID、物种名
col_names = ["pct", "reads_clade", "reads_direct", "rank", "taxid", "name"]
kraken = pd.read_csv(
"sample01.kreport",
sep="\t",
header=None, # 没有表头
names=col_names, # 手动指定列名
)
# 去掉物种名前后的空格(Kraken2 输出有缩进)
kraken["name"] = kraken["name"].str.strip()
# 只提取种级别(rank == "S")
species = kraken[kraken["rank"] == "S"].copy()
# 按 reads 数从大到小排序
species = species.sort_values("reads_clade", ascending=False)
# 取 Top20 物种
top20 = species.head(20)
print(top20[["name", "reads_clade", "pct"]])
案例 2:合并多样本丰度表¶
import pandas as pd
import os
# 假设每个样本有一个 TSV 文件:S01_abundance.tsv, S02_abundance.tsv ...
# 每个文件两列:species, read_count
input_dir = "kraken_outputs/"
all_samples = []
for filename in os.listdir(input_dir):
if filename.endswith("_abundance.tsv"):
# 从文件名提取样本ID
sample_id = filename.replace("_abundance.tsv", "")
# 读取该样本的丰度表
df = pd.read_csv(
os.path.join(input_dir, filename),
sep="\t",
names=["species", "read_count"], # 如果没表头就手动指定
)
df["sample_id"] = sample_id # 加一列标记是哪个样本
all_samples.append(df)
# 纵向合并所有样本
combined = pd.concat(all_samples, ignore_index=True)
# 转为宽格式丰度矩阵(物种 × 样本)
abundance_matrix = combined.pivot_table(
index="species",
columns="sample_id",
values="read_count",
fill_value=0, # 未检出的填 0
)
# 保存
abundance_matrix.to_csv("merged_abundance_matrix.tsv", sep="\t")
案例 3:计算相对丰度并过滤低丰度物种¶
import pandas as pd
# 读取合并好的丰度矩阵(行=物种,列=样本)
matrix = pd.read_csv("merged_abundance_matrix.tsv", sep="\t", index_col=0)
# === 计算相对丰度 ===
# 每个样本(列)的 reads 总数
col_sums = matrix.sum(axis=0) # axis=0 按列求和
# 每个值除以该列总数 → 相对丰度(0-1之间)
rel_abundance = matrix.div(col_sums, axis=1) # axis=1 表示按列广播
# 白话:matrix 的每一列,都除以那一列的总和
# 验证:每列之和应该等于 1
print(rel_abundance.sum(axis=0))
# === 过滤低丰度物种 ===
# 策略:在所有样本中平均相对丰度 < 0.01% 的物种删掉
mean_abundance = rel_abundance.mean(axis=1) # 每个物种跨样本的平均
keep_species = mean_abundance[mean_abundance >= 0.0001].index # 保留的物种名
rel_filtered = rel_abundance.loc[keep_species]
print(f"过滤前: {matrix.shape[0]} 个物种")
print(f"过滤后: {rel_filtered.shape[0]} 个物种")
# 保存
rel_filtered.to_csv("relative_abundance_filtered.tsv", sep="\t")
Pandas vs Polars vs R dplyr 对比¶
| 维度 | Pandas | Polars | R dplyr |
|---|---|---|---|
| 语言 | Python | Python (Rust底层) | R |
| 速度 | 中等 | 快 5-20x | 中等 (data.table 很快) |
| 内存 | 高(默认 64 位) | 低(自动优化) | 中等 |
| 多线程 | 不支持 | 原生支持 | 不支持 (data.table 支持) |
| 惰性求值 | 无 | 有 (scan/lazy) | 无 (dbplyr 有) |
| 生态 | 最丰富 | 快速增长中 | 生信最成熟 (Bioconductor) |
| 学习曲线 | 低 | 中(表达式语法不同) | 低 (tidyverse 很直觉) |
| 生信专用包 | 少(要自己写) | 更少 | 最多 (phyloseq, DESeq2) |
| 面试考频 | 高(Python岗必考) | 低(但加分项) | 高(R岗必考) |
实际建议: - 面试准备:Pandas 必须熟练,面试最常考 - 日常分析:小数据 (<1GB) 用 Pandas,大数据用 Polars - R 包无替代时(如 DESeq2 差异分析),仍需切回 R
常见报错与解决¶
1. SettingWithCopyWarning¶
SettingWithCopyWarning: A value is trying to be set on a copy of a slice
原因:对切片后的 DataFrame 直接赋值,Pandas 不确定你是在改原表还是副本。
# ❌ 触发警告
df_sub = df[df["group"] == "T2D"]
df_sub["new_col"] = 1 # 警告!
# ✅ 正确写法:加 .copy()
df_sub = df[df["group"] == "T2D"].copy()
df_sub["new_col"] = 1 # 没问题
2. KeyError: 'column_name'¶
原因:列名写错了,或者列名有隐藏的空格/特殊字符。
# 排查:打印所有列名,检查有没有空格
print(df.columns.tolist())
# 如果发现列名有空格:
df.columns = df.columns.str.strip() # 去掉所有列名的前后空格
3. MergeError 或合并后行数暴增¶
原因:merge 的 key 列有重复值,导致多对多笛卡尔积。
# 排查:检查 key 列是否有重复
print(df1["sample_id"].duplicated().sum()) # 看有几个重复
print(df2["sample_id"].duplicated().sum())
# 解决:先去重,或用 validate 参数让 Pandas 自动检查
merged = df1.merge(df2, on="sample_id", validate="one_to_one")
# validate 选项:one_to_one / one_to_many / many_to_one
4. TypeError: unsupported operand type 数值列实际是字符串¶
原因:CSV 中数字列被读成了字符串(比如有个值是 "NA" 或空格)。
# 排查
print(df["read_count"].dtype) # 如果显示 object,说明是字符串
# 解决:强制转换,无法转换的变成 NaN
df["read_count"] = pd.to_numeric(df["read_count"], errors="coerce")
# errors="coerce":遇到不能转的值(如 "NA")就变成 NaN 而不报错
5. MemoryError 内存不够¶
原因:文件太大,一次性加载超出可用内存。
# 解决方案1:分块读取(见上面"大文件分块读取"章节)
# 解决方案2:只读需要的列
df = pd.read_csv("huge.tsv", sep="\t", usecols=["species", "read_count"])
# 解决方案3:换 Polars(内存更高效)
import polars as pl
df = pl.scan_csv("huge.tsv", separator="\t").collect()
6. ValueError: cannot reindex from a duplicate axis¶
原因:索引有重复值时做对齐操作。
# 排查
print(df.index.duplicated().sum())
# 解决:重置索引
df = df.reset_index(drop=True) # drop=True 表示不把旧索引变成列
速查表¶
读写操作¶
| 操作 | 代码 |
|---|---|
| 读 CSV | pd.read_csv("f.csv") |
| 读 TSV | pd.read_csv("f.tsv", sep="\t") |
| 读 Excel | pd.read_excel("f.xlsx") |
| 预览前 N 行 | pd.read_csv("f.csv", nrows=100) |
| 只读指定列 | pd.read_csv("f.csv", usecols=["a","b"]) |
| 写 TSV | df.to_csv("f.tsv", sep="\t", index=False) |
查看与描述¶
| 操作 | 代码 |
|---|---|
| 前 5 行 | df.head() |
| 形状 | df.shape |
| 列类型 | df.dtypes |
| 统计摘要 | df.describe() |
| 缺失值计数 | df.isnull().sum() |
| 内存占用 | df.memory_usage(deep=True) |
| 唯一值 | df["col"].nunique() |
筛选与排序¶
| 操作 | 代码 |
|---|---|
| 条件筛选 | df[df["col"] > 5] |
| 多条件 | df[(df["a"]>1) & (df["b"]=="x")] |
| isin | df[df["col"].isin(["a","b"])] |
| query | df.query("col > 5") |
| 排序 | df.sort_values("col", ascending=False) |
| 去重 | df.drop_duplicates(subset=["col"]) |
分组与聚合¶
| 操作 | 代码 |
|---|---|
| 分组均值 | df.groupby("g")["v"].mean() |
| 多统计量 | df.groupby("g")["v"].agg(["mean","std"]) |
| 分组计数 | df.groupby("g").size() |
| 分组排名 | df.groupby("g")["v"].rank() |
合并与重塑¶
| 操作 | 代码 |
|---|---|
| 左连接 | df1.merge(df2, on="key", how="left") |
| 纵向拼接 | pd.concat([df1, df2], ignore_index=True) |
| 长转宽 | df.pivot(index=, columns=, values=) |
| 宽转长 | df.melt(id_vars=, var_name=, value_name=) |
缺失值与类型¶
| 操作 | 代码 |
|---|---|
| 填充 0 | df.fillna(0) |
| 删缺失行 | df.dropna() |
| 强制转数值 | pd.to_numeric(df["c"], errors="coerce") |
| 转日期 | pd.to_datetime(df["c"]) |
| 转 category | df["c"].astype("category") |
延伸资源¶
| 资源 | 说明 |
|---|---|
| Pandas 官方文档 10 minutes to pandas | 官方入门,看完就能用 |
| Polars 官方 User Guide | Polars 从零学习 |
| Python for Data Analysis (Wes McKinney) | Pandas 作者写的书,权威教材 |
| Pandas Cheat Sheet (DataCamp) | 一页纸速查,适合打印贴桌上 |
| Bioinformatics with Python Cookbook | 生信场景的 Pandas 实战 |
| real-world Polars benchmarks (pola-rs/tpch) | Polars 官方性能对比数据 |
面试自测题¶
merge的how参数有哪几种?分别什么含义?groupby后用transform和agg有什么区别?- 为什么向量化运算比
apply快? - 如何处理一个 20GB 的 TSV 文件?
SettingWithCopyWarning是什么原因?怎么解决?- 把物种丰度表从宽格式转为长格式用哪个函数?
提示:以上 6 题答案全在本文档中,面试前过一遍确保能脱口而出。