Python数据分析(Python for Bioinformatics)¶
一句话说明¶
Python是生信数据处理和统计分析的主要编程语言,掌握pandas/scipy/matplotlib是分析工程师的基本要求。
核心知识点¶
2.1 pandas基础操作¶
常用操作速查表¶
| 操作 | 方法 | 示例 | 生信应用 |
|---|---|---|---|
| 读取TSV | pd.read_csv() |
pd.read_csv("otu.tsv", sep="\t", index_col=0) |
读取OTU丰度表 |
| 读取Excel | pd.read_excel() |
pd.read_excel("metadata.xlsx") |
读取样本信息表 |
| 写入文件 | df.to_csv() |
df.to_csv("result.tsv", sep="\t") |
保存分析结果 |
| 查看前N行 | df.head() |
df.head(10) |
快速预览数据 |
| 查看数据概况 | df.describe() |
df.describe() |
了解数值分布 |
| 查看形状 | df.shape |
df.shape |
确认样本数和特征数 |
| 查看列名 | df.columns |
df.columns.tolist() |
获取所有列名 |
| 按条件筛选 | df[条件] |
df[df["abundance"] > 0.01] |
筛选丰度>1%的物种 |
| 按列排序 | df.sort_values() |
df.sort_values("abundance", ascending=False) |
按丰度降序排列 |
| 分组聚合 | df.groupby() |
df.groupby("phylum").sum() |
按门水平汇总丰度 |
| 合并表格 | pd.merge() |
pd.merge(otu, meta, on="sample_id") |
合并丰度表和元数据 |
| 纵向拼接 | pd.concat() |
pd.concat([df1, df2], axis=0) |
合并多个样本的结果 |
| 横向拼接 | pd.concat() |
pd.concat([df1, df2], axis=1) |
按列合并(如多个指标) |
| 转置 | df.T |
otu_t = otu.T |
将样本×物种转为物种×样本 |
| 透视表 | df.pivot_table() |
df.pivot_table(values="count", index="sample", columns="taxa") |
重塑数据格式 |
| 缺失值处理 | df.fillna() |
df.fillna(0) |
将缺失丰度填充为0 |
| 应用函数 | df.apply() |
df.apply(lambda x: x/x.sum(), axis=0) |
按列归一化为相对丰度 |
代码示例:读取和处理OTU表¶
import pandas as pd
import numpy as np
# ============================================================
# 读取OTU丰度表
# 假设格式:行=OTU/物种,列=样本,值=reads计数
# ============================================================
# 读取TSV文件,第一列作为索引(OTU ID)
otu = pd.read_csv("otu_table.tsv", sep="\t", index_col=0)
print(f"OTU表形状: {otu.shape}") # 输出: (500, 30) 表示500个OTU × 30个样本
print(f"样本数: {otu.shape[1]}") # 列数 = 样本数
print(f"OTU数: {otu.shape[0]}") # 行数 = OTU数
# 查看前5行
print(otu.head())
# ============================================================
# 计算相对丰度(relative abundance)
# 每个样本的所有OTU丰度除以该样本的总reads数
# ============================================================
# 方法一:apply函数(推荐,语义清晰)
rel_abundance = otu.apply(lambda col: col / col.sum(), axis=0)
# axis=0 表示对每一列(每个样本)操作
# 结果:每列之和 = 1.0
# 方法二:等价写法
rel_abundance = otu.div(otu.sum(axis=0), axis=1)
# 验证:每个样本的相对丰度之和应该为1
print(rel_abundance.sum(axis=0)) # 应该全是1.0
# ============================================================
# 筛选低丰度OTU
# 去除在所有样本中平均相对丰度<0.001(0.1%)的OTU
# ============================================================
mean_abundance = rel_abundance.mean(axis=1) # 每个OTU在所有样本中的平均丰度
filtered_otu = rel_abundance[mean_abundance >= 0.001] # 保留平均丰度>=0.1%的OTU
print(f"筛选前OTU数: {otu.shape[0]}")
print(f"筛选后OTU数: {filtered_otu.shape[0]}")
# ============================================================
# 按分类层级汇总
# 将OTU level的丰度汇总到门(Phylum)水平
# ============================================================
# 假设有分类信息表:OTU_ID → 门、纲、目、科、属、种
taxonomy = pd.read_csv("taxonomy.tsv", sep="\t", index_col=0)
# 合并OTU表和分类信息
otu_with_tax = otu.join(taxonomy) # 按索引(OTU ID)合并
# 按门水平汇总
phylum_abundance = otu_with_tax.groupby("Phylum").sum()
# 结果:行=门,列=样本,值=该门下所有OTU的reads总和
# 保存结果
phylum_abundance.to_csv("phylum_abundance.tsv", sep="\t")
2.2 scipy统计检验¶
常用统计方法一览¶
| 检验方法 | 适用场景 | scipy函数 | 前提假设 |
|---|---|---|---|
| t检验 | 两组连续变量比较(正态分布) | scipy.stats.ttest_ind() |
正态分布、方差齐性 |
| Wilcoxon秩和检验 | 两组连续变量比较(非正态) | scipy.stats.mannwhitneyu() |
无分布假设 |
| 配对t检验 | 配对样本比较(正态) | scipy.stats.ttest_rel() |
配对、正态分布 |
| Wilcoxon符号秩检验 | 配对样本比较(非正态) | scipy.stats.wilcoxon() |
配对 |
| 卡方检验 | 分类变量关联性 | scipy.stats.chi2_contingency() |
期望频数>=5 |
| Kruskal-Wallis检验 | 多组比较(非正态) | scipy.stats.kruskal() |
无分布假设 |
| ANOVA | 多组比较(正态) | scipy.stats.f_oneway() |
正态、方差齐性 |
| Pearson相关 | 线性相关性 | scipy.stats.pearsonr() |
正态分布 |
| Spearman相关 | 单调相关性 | scipy.stats.spearmanr() |
无分布假设 |
检验方法选择流程¶
数据类型?
├── 连续变量(如丰度值)
│ ├── 正态分布?
│ │ ├── 是 → 两组: t检验 / 多组: ANOVA
│ │ └── 否 → 两组: Wilcoxon(Mann-Whitney U) / 多组: Kruskal-Wallis
│ └── 配对数据?
│ ├── 正态 → 配对t检验
│ └── 非正态 → Wilcoxon符号秩
└── 分类变量(如有/无)
└── 卡方检验 或 Fisher精确检验
生信数据的关键提醒:微生物丰度数据通常不满足正态分布,优先使用非参数检验(Wilcoxon)。
多重检验校正¶
from statsmodels.stats.multitest import multipletests
# ============================================================
# 多重检验校正(Multiple Testing Correction)
# 为什么需要:同时检验多个物种时,P值需要校正,否则假阳性率会很高
# 常用方法:Benjamini-Hochberg (FDR校正)
# ============================================================
# 假设我们对100个物种分别做了Wilcoxon检验
p_values = [0.001, 0.03, 0.04, 0.05, 0.15] # 示意,实际为100个p值
# BH校正(控制FDR,False Discovery Rate)
reject, pvals_corrected, _, _ = multipletests(
p_values,
alpha=0.05, # 显著性阈值
method='fdr_bh' # Benjamini-Hochberg方法
)
# reject: 布尔数组,True表示该检验显著
# pvals_corrected: 校正后的p值(q值)
# 筛选显著差异的物种
significant = [i for i, r in enumerate(reject) if r]
print(f"校正前显著(p<0.05): {sum(p < 0.05 for p in p_values)}个")
print(f"校正后显著(q<0.05): {len(significant)}个")
2.3 scikit-learn基础¶
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import pandas as pd
import numpy as np
# ============================================================
# 数据标准化(Standardization)
# 目的:消除量纲差异,让不同特征在同一尺度上比较
# ============================================================
# 读取丰度矩阵:行=样本,列=物种
abundance = pd.read_csv("abundance_samples_by_species.tsv", sep="\t", index_col=0)
scaler = StandardScaler() # 创建标准化器
abundance_scaled = scaler.fit_transform(abundance)
# fit_transform: 计算均值和标准差 → 对每列做 (x - mean) / std
# 结果:每列均值=0,标准差=1
# ============================================================
# PCA降维(Principal Component Analysis,主成分分析)
# 目的:将高维数据投影到低维空间,用于可视化和探索
# ============================================================
pca = PCA(n_components=2) # 降到2维
pca_result = pca.fit_transform(abundance_scaled)
# pca_result 形状: (样本数, 2)
# 获取解释方差比例(每个主成分解释了多少数据变异)
print(f"PC1解释方差: {pca.explained_variance_ratio_[0]:.2%}")
print(f"PC2解释方差: {pca.explained_variance_ratio_[1]:.2%}")
# 转为DataFrame方便后续绘图
pca_df = pd.DataFrame(
pca_result,
columns=["PC1", "PC2"],
index=abundance.index # 保留样本名
)
# ============================================================
# 随机森林分类(Random Forest)
# 目的:根据微生物组成预测样本分组(如健康vs疾病)
# ============================================================
# 准备数据
X = abundance_scaled # 特征矩阵(样本×物种)
y = metadata["group"].values # 标签(如 "healthy", "disease")
# 创建随机森林分类器
rf = RandomForestClassifier(
n_estimators=100, # 100棵决策树
random_state=42, # 随机种子(保证结果可重复)
n_jobs=-1 # 使用所有CPU核
)
# 交叉验证评估模型效果
scores = cross_val_score(
rf, X, y,
cv=5, # 5折交叉验证
scoring='accuracy' # 准确率
)
print(f"5折交叉验证准确率: {scores.mean():.2%} (+/- {scores.std():.2%})")
# 训练模型并查看特征重要性
rf.fit(X, y)
importances = pd.Series(
rf.feature_importances_, # 每个特征的重要性得分
index=abundance.columns # 对应的物种名
).sort_values(ascending=False)
print("Top 10重要物种:")
print(importances.head(10))
2.4 可视化(matplotlib/seaborn)¶
基本图表类型¶
| 图表类型 | 函数 | 适用场景 |
|---|---|---|
| 柱状图 | plt.bar() / sns.barplot() |
组间均值比较 |
| 箱线图 | sns.boxplot() |
数据分布比较 |
| 散点图 | plt.scatter() / sns.scatterplot() |
PCoA/PCA结果展示 |
| 热图 | sns.heatmap() |
相关性矩阵、丰度热图 |
| 堆叠柱状图 | df.plot(kind="bar", stacked=True) |
物种组成展示 |
| 小提琴图 | sns.violinplot() |
数据分布形状比较 |
实战代码¶
计算Alpha多样性¶
import pandas as pd
import numpy as np
# ============================================================
# Alpha多样性指数计算
# Alpha多样性衡量单个样本内的物种多样性
# ============================================================
def shannon_diversity(counts):
"""
计算Shannon多样性指数
Shannon Index = -sum(pi * ln(pi))
其中 pi = 第i个物种的相对丰度
参数:
counts: 一维数组,每个物种的reads计数
返回:
float: Shannon指数值(越高表示多样性越高)
"""
# 去除0值(0不参与计算,因为 0*log(0) 在数学上定义为0)
counts = np.array(counts)
counts = counts[counts > 0] # 只保留>0的值
# 计算相对丰度 pi
total = counts.sum()
proportions = counts / total # pi = ni / N
# 计算Shannon指数
shannon = -np.sum(proportions * np.log(proportions))
return shannon
def simpson_diversity(counts):
"""
计算Simpson多样性指数
Simpson Index = 1 - sum(pi^2)
取值范围 [0, 1),越接近1表示多样性越高
参数:
counts: 一维数组,每个物种的reads计数
返回:
float: Simpson指数值
"""
counts = np.array(counts)
counts = counts[counts > 0]
total = counts.sum()
proportions = counts / total
simpson = 1 - np.sum(proportions ** 2)
return simpson
def observed_species(counts):
"""
计算观测到的物种数(Observed Species / Richness)
即计数>0的物种数量
"""
counts = np.array(counts)
return np.sum(counts > 0)
# ============================================================
# 应用到OTU表
# ============================================================
# 读取OTU表(行=OTU, 列=样本)
otu = pd.read_csv("otu_table.tsv", sep="\t", index_col=0)
# 对每个样本计算多样性指数
alpha_diversity = pd.DataFrame({
"Shannon": otu.apply(shannon_diversity, axis=0), # 对每列(样本)计算
"Simpson": otu.apply(simpson_diversity, axis=0),
"Observed_Species": otu.apply(observed_species, axis=0)
})
print(alpha_diversity.head())
# 输出示例:
# Shannon Simpson Observed_Species
# sample_01 3.42 0.93 187
# sample_02 2.89 0.88 156
# 保存结果
alpha_diversity.to_csv("alpha_diversity.tsv", sep="\t")
Bray-Curtis距离矩阵计算¶
import pandas as pd
import numpy as np
from scipy.spatial.distance import pdist, squareform
# ============================================================
# Bray-Curtis距离(Beta多样性)
# Bray-Curtis衡量两个样本之间物种组成的差异
# 取值范围 [0, 1]:0=完全相同,1=完全不同
# ============================================================
# 读取相对丰度表(行=样本, 列=物种)
# 注意:这里要转置,因为pdist要求行是样本
rel_abundance = pd.read_csv("relative_abundance.tsv", sep="\t", index_col=0)
rel_abundance_T = rel_abundance.T # 转置:行变成样本
# 计算Bray-Curtis距离
# pdist: pairwise distances(成对距离)
bc_condensed = pdist(rel_abundance_T, metric="braycurtis")
# 返回压缩格式的距离向量
# 转换为方阵(距离矩阵)
bc_matrix = squareform(bc_condensed)
# 转为DataFrame,添加样本名
bc_df = pd.DataFrame(
bc_matrix,
index=rel_abundance_T.index, # 行名=样本名
columns=rel_abundance_T.index # 列名=样本名
)
print("Bray-Curtis距离矩阵:")
print(bc_df.head())
# 保存距离矩阵
bc_df.to_csv("bray_curtis_distance.tsv", sep="\t")
差异检验(Wilcoxon)¶
import pandas as pd
import numpy as np
from scipy import stats
from statsmodels.stats.multitest import multipletests
# ============================================================
# Wilcoxon秩和检验(Mann-Whitney U检验)
# 比较两组样本之间每个物种的丰度差异
# 适用于:非正态分布的微生物丰度数据
# ============================================================
# 读取数据
rel_abundance = pd.read_csv("relative_abundance.tsv", sep="\t", index_col=0)
metadata = pd.read_csv("metadata.tsv", sep="\t", index_col=0)
# 按分组拆分样本
# 假设metadata有一列"group",值为"healthy"或"disease"
group1_samples = metadata[metadata["group"] == "healthy"].index
group2_samples = metadata[metadata["group"] == "disease"].index
# 对每个物种进行Wilcoxon检验
results = []
for species in rel_abundance.index: # 遍历每个物种
# 提取两组的丰度值
group1_values = rel_abundance.loc[species, group1_samples].values
group2_values = rel_abundance.loc[species, group2_samples].values
# Mann-Whitney U检验(即Wilcoxon秩和检验)
try:
stat, pvalue = stats.mannwhitneyu(
group1_values,
group2_values,
alternative='two-sided' # 双侧检验
)
except ValueError:
# 如果所有值都相同,检验会报错
pvalue = 1.0
stat = 0
# 计算效应大小(两组均值的差异)
mean_g1 = np.mean(group1_values)
mean_g2 = np.mean(group2_values)
log2fc = np.log2((mean_g2 + 1e-10) / (mean_g1 + 1e-10))
# 加1e-10避免除以0
# log2FC > 0: disease组丰度更高
# log2FC < 0: healthy组丰度更高
results.append({
"species": species,
"mean_healthy": mean_g1,
"mean_disease": mean_g2,
"log2FC": log2fc,
"p_value": pvalue
})
# 转为DataFrame
results_df = pd.DataFrame(results)
# 多重检验校正(BH方法)
reject, q_values, _, _ = multipletests(
results_df["p_value"],
alpha=0.05,
method="fdr_bh" # Benjamini-Hochberg
)
results_df["q_value"] = q_values # 添加校正后的q值
results_df["significant"] = reject # 是否显著
# 按q值排序
results_df = results_df.sort_values("q_value")
# 筛选显著差异物种
sig_species = results_df[results_df["significant"]]
print(f"显著差异物种数: {len(sig_species)} / {len(results_df)}")
print(sig_species.head(10))
# 保存结果
results_df.to_csv("differential_abundance.tsv", sep="\t", index=False)
堆叠柱状图绘制¶
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['SimHei'] # 显示中文
matplotlib.rcParams['axes.unicode_minus'] = False # 显示负号
# ============================================================
# 堆叠柱状图(Stacked Bar Chart)
# 展示各样本的门水平物种组成
# ============================================================
# 读取门水平相对丰度表(行=门, 列=样本)
phylum_rel = pd.read_csv("phylum_relative_abundance.tsv", sep="\t", index_col=0)
# 只保留Top 10的门,其余归为"Others"
top_phyla = phylum_rel.mean(axis=1).nlargest(10).index # 按平均丰度取前10
others = phylum_rel.loc[~phylum_rel.index.isin(top_phyla)].sum() # 其余汇总
plot_data = pd.concat([
phylum_rel.loc[top_phyla],
pd.DataFrame(others, columns=["Others"]).T # 添加Others行
])
# 转置:行=样本,列=门(pandas绘制柱状图的格式要求)
plot_data_T = plot_data.T
# 定义配色(选择区分度高的颜色)
colors = [
'#E64B35', '#4DBBD5', '#00A087', '#3C5488', '#F39B7F',
'#8491B4', '#91D1C2', '#DC0000', '#7E6148', '#B09C85',
'#CCCCCC' # Others用灰色
]
# 绘图
fig, ax = plt.subplots(figsize=(14, 6))
plot_data_T.plot(
kind='bar', # 柱状图
stacked=True, # 堆叠
ax=ax, # 指定Axes
color=colors[:len(plot_data_T.columns)], # 颜色
edgecolor='white', # 白色边框
linewidth=0.5 # 边框宽度
)
# 美化
ax.set_ylabel("Relative Abundance", fontsize=12)
ax.set_xlabel("")
ax.set_title("Phylum-level Composition", fontsize=14)
ax.legend(
title="Phylum",
bbox_to_anchor=(1.02, 1), # 图例放右边
loc='upper left',
fontsize=9
)
ax.set_ylim(0, 1) # y轴范围 [0, 1]
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right')
plt.tight_layout()
# 保存图片
plt.savefig("phylum_barplot.pdf", dpi=300, bbox_inches='tight')
plt.savefig("phylum_barplot.png", dpi=300, bbox_inches='tight')
plt.show()
PCoA绘图¶
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial.distance import pdist, squareform
from skbio.stats.ordination import pcoa # scikit-bio包
# 安装: conda install -c conda-forge scikit-bio
# ============================================================
# PCoA分析与可视化(Principal Coordinates Analysis)
# PCoA = 主坐标分析,基于距离矩阵进行降维
# 常用于展示样本间的beta多样性
# ============================================================
# 读取数据
rel_abundance = pd.read_csv("relative_abundance.tsv", sep="\t", index_col=0)
metadata = pd.read_csv("metadata.tsv", sep="\t", index_col=0)
# 计算Bray-Curtis距离矩阵
from skbio import DistanceMatrix
rel_T = rel_abundance.T # 转置:行=样本
bc_condensed = pdist(rel_T, metric="braycurtis")
dm = DistanceMatrix(squareform(bc_condensed), ids=rel_T.index.tolist())
# PCoA分析
pcoa_result = pcoa(dm)
# 提取前两个主坐标轴的坐标
pcoa_coords = pcoa_result.samples[["PC1", "PC2"]]
# 解释方差比例
pc1_var = pcoa_result.proportion_explained["PC1"]
pc2_var = pcoa_result.proportion_explained["PC2"]
# 合并分组信息
pcoa_df = pcoa_coords.join(metadata[["group"]])
# ============================================================
# 绘制PCoA散点图
# ============================================================
fig, ax = plt.subplots(figsize=(8, 6))
# 定义分组颜色
group_colors = {"healthy": "#00A087", "disease": "#E64B35"}
groups = pcoa_df["group"].unique()
for group in groups:
subset = pcoa_df[pcoa_df["group"] == group]
ax.scatter(
subset["PC1"],
subset["PC2"],
c=group_colors[group],
label=group,
s=80, # 点大小
alpha=0.7, # 透明度
edgecolors='white', # 白色边框
linewidth=0.5
)
# 添加坐标轴标签(含解释方差)
ax.set_xlabel(f"PC1 ({pc1_var:.1%})", fontsize=12)
ax.set_ylabel(f"PC2 ({pc2_var:.1%})", fontsize=12)
ax.set_title("PCoA (Bray-Curtis)", fontsize=14)
ax.legend(title="Group", fontsize=10)
# 添加参考线
ax.axhline(y=0, color='gray', linestyle='--', linewidth=0.5, alpha=0.5)
ax.axvline(x=0, color='gray', linestyle='--', linewidth=0.5, alpha=0.5)
plt.tight_layout()
plt.savefig("pcoa_plot.pdf", dpi=300, bbox_inches='tight')
plt.savefig("pcoa_plot.png", dpi=300, bbox_inches='tight')
plt.show()
# ============================================================
# PERMANOVA检验(检验组间距离是否显著不同)
# ============================================================
from skbio.stats.distance import permanova
permanova_result = permanova(dm, metadata["group"], permutations=999)
print(f"PERMANOVA结果:")
print(f" test statistic (pseudo-F): {permanova_result['test statistic']:.3f}")
print(f" p-value: {permanova_result['p-value']:.4f}")
# p < 0.05 表示两组的微生物组成有显著差异
相关性热图¶
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from scipy import stats
# ============================================================
# 物种间相关性热图
# 用Spearman相关分析物种之间的共现关系
# ============================================================
# 读取丰度数据(行=样本, 列=物种)
abundance = pd.read_csv("top_species_abundance.tsv", sep="\t", index_col=0)
# 计算Spearman相关系数矩阵
corr_matrix = abundance.corr(method="spearman")
# 计算p值矩阵(判断相关性是否显著)
n_species = abundance.shape[1]
p_matrix = pd.DataFrame(
np.ones((n_species, n_species)),
index=abundance.columns,
columns=abundance.columns
)
for i in range(n_species):
for j in range(i+1, n_species):
rho, pval = stats.spearmanr(
abundance.iloc[:, i],
abundance.iloc[:, j]
)
p_matrix.iloc[i, j] = pval
p_matrix.iloc[j, i] = pval
# 创建掩码:只显示下三角
mask = np.triu(np.ones_like(corr_matrix, dtype=bool))
# 绘制热图
fig, ax = plt.subplots(figsize=(10, 8))
sns.heatmap(
corr_matrix,
mask=mask, # 只显示下三角
cmap="RdBu_r", # 红蓝配色(正相关=红,负相关=蓝)
center=0, # 以0为中心
vmin=-1, vmax=1, # 范围[-1, 1]
square=True, # 方格
linewidths=0.5, # 格子边框
cbar_kws={"shrink": 0.8, "label": "Spearman rho"},
annot=True, # 显示数值
fmt=".2f", # 保留2位小数
ax=ax
)
# 在显著相关的格子上标记星号
for i in range(n_species):
for j in range(i):
if p_matrix.iloc[i, j] < 0.05:
ax.text(
j + 0.5, i + 0.8, "*",
ha='center', va='center',
color='black', fontsize=10
)
ax.set_title("Species Correlation (Spearman)", fontsize=14)
plt.tight_layout()
plt.savefig("correlation_heatmap.pdf", dpi=300, bbox_inches='tight')
plt.show()
面试常问点¶
★ pandas的DataFrame和Series有什么区别?¶
参考答案:
Series 是一维数据结构,类似于一列数据,有索引(index)和值(values)。可以理解为 Excel 表格中的一列。
DataFrame 是二维数据结构,由多个 Series 组成,有行索引(index)和列名(columns)。可以理解为一张完整的 Excel 表格。
# Series:一维 s = pd.Series([3.42, 2.89, 4.01], index=["S1", "S2", "S3"]) # DataFrame:二维 df = pd.DataFrame({ "Shannon": [3.42, 2.89, 4.01], "Simpson": [0.93, 0.88, 0.95] }, index=["S1", "S2", "S3"])从 DataFrame 中取一列就得到一个 Series:
df["Shannon"]返回 Series。
★ 如何用Python计算Shannon多样性指数?¶
参考答案:
Shannon 多样性指数的公式是:H' = -sum(pi * ln(pi)),其中 pi 是第 i 个物种的相对丰度。
import numpy as np def shannon(counts): counts = np.array(counts) counts = counts[counts > 0] # 去除0值 proportions = counts / counts.sum() # 计算相对丰度 return -np.sum(proportions * np.log(proportions))Shannon 指数越高,表示群落多样性越高。一般微生物群落的 Shannon 指数在 2-5 之间。在我实习做 16S 分析时,会用 QIIME2 自动计算,但也会用 Python 自己验证结果。
★ Wilcoxon和t检验的区别?什么时候用哪个?¶
参考答案:
核心区别在于对数据分布的假设:
对比项 t检验 Wilcoxon秩和检验 分布假设 要求正态分布 不要求,非参数方法 比较对象 比较均值差异 比较秩次(排名)差异 敏感性 对异常值敏感 对异常值稳健 检验效能 正态数据下效能更高 非正态数据更可靠 在生信中,微生物丰度数据几乎都不是正态分布(通常是零膨胀的、右偏的),所以优先使用 Wilcoxon检验(也叫 Mann-Whitney U 检验)。
在我的实习项目中,比较健康组和疾病组的菌群差异时,就是用的 Wilcoxon 秩和检验,然后用 BH 方法做多重检验校正。
★ PCA和PCoA的区别?¶
参考答案:
对比项 PCA PCoA 全称 Principal Component Analysis Principal Coordinates Analysis 输入 原始数据矩阵(样本x特征) 距离矩阵(样本x样本) 距离度量 固定使用欧氏距离 可以使用任意距离(如Bray-Curtis) 适用场景 基因表达、环境因子 微生物群落组成 为什么微生物分析常用PCoA而不是PCA?
因为微生物丰度数据是"成分数据"(compositional data),使用欧氏距离不合适。而 Bray-Curtis 距离专门设计用于物种丰度数据的比较,它考虑了物种的有无和丰度差异。所以我们先计算 Bray-Curtis 距离矩阵,再用 PCoA 降维可视化。
在 QIIME2 中,beta 多样性分析默认就是用 Bray-Curtis + PCoA 的组合。
★ 你在项目中用Python做了什么分析?¶
参考答案:
在百迈客实习期间,我主要用 Python 做以下分析:
数据预处理:用 pandas 读取和整理 OTU 丰度表、合并样本元数据、计算相对丰度、按分类层级汇总(如从 OTU 汇总到门水平)。
Alpha 多样性计算:用 numpy 实现 Shannon、Simpson 指数的计算(虽然 QIIME2 也能算,但有时需要自定义分析或验证)。
差异分析:用 scipy.stats 的 Mann-Whitney U 检验比较两组间的物种丰度差异,用 statsmodels 做 BH 多重检验校正。
可视化:用 matplotlib 和 seaborn 绘制堆叠柱状图(物种组成)、PCoA 散点图(beta 多样性)、箱线图(alpha 多样性组间比较)、热图(物种相关性)。
批量处理:写 Python 脚本批量整理客户交付的分析报告数据,自动生成标准化的结果表格。
易错/易混淆点¶
1. merge vs join vs concat¶
import pandas as pd
# ============================================================
# 三种合并方式的区别
# ============================================================
# ---------- merge:按指定列合并(类似SQL的JOIN) ----------
# 最灵活,可以指定合并键
result = pd.merge(
otu_table, # 左表
metadata, # 右表
on="sample_id", # 合并键(两表共有的列名)
how="inner" # inner/left/right/outer
)
# 也可以用不同列名合并:
# pd.merge(df1, df2, left_on="id", right_on="sample")
# ---------- join:按索引合并 ----------
# 基于DataFrame的index进行合并,更简洁
result = otu_table.join(metadata) # 默认按index合并
# 等价于 pd.merge(otu_table, metadata, left_index=True, right_index=True)
# ---------- concat:简单拼接 ----------
# 纵向拼接(追加行)
all_samples = pd.concat([batch1, batch2], axis=0)
# axis=0: 上下拼接(追加行)
# 横向拼接(追加列)
combined = pd.concat([otu_table, diversity_index], axis=1)
# axis=1: 左右拼接(追加列)
# 记忆口诀:
# merge = 按列合并(灵活),join = 按索引合并(简便),concat = 简单拼接(堆叠)
2. axis=0 vs axis=1¶
import pandas as pd
import numpy as np
# ============================================================
# axis参数的含义(新手最容易搞混的地方!)
# ============================================================
# 假设有一个 3x4 的DataFrame
df = pd.DataFrame(
np.random.rand(3, 4), # 3行4列
index=["sample_A", "sample_B", "sample_C"], # 行 = 样本
columns=["sp1", "sp2", "sp3", "sp4"] # 列 = 物种
)
# axis=0:沿着行的方向操作(结果是对每列操作)
df.sum(axis=0) # 对每一列求和 → 得到4个值(每个物种的总丰度)
df.mean(axis=0) # 每列的均值 → 每个物种在所有样本中的平均丰度
df.drop("sample_A", axis=0) # 删除一行
# axis=1:沿着列的方向操作(结果是对每行操作)
df.sum(axis=1) # 对每一行求和 → 得到3个值(每个样本的总reads数)
df.mean(axis=1) # 每行的均值 → 每个样本的平均物种丰度
df.drop("sp1", axis=1) # 删除一列
# 记忆技巧:
# axis=0 → 上下方向(垂直,像0的形状) → 操作行 → 结果按列
# axis=1 → 左右方向(水平,像1的形状) → 操作列 → 结果按行
# 生信实战:计算相对丰度
# 每个样本的丰度除以该样本的总丰度(行=样本,列=物种)
relative = df.div(df.sum(axis=1), axis=0) # axis=1 对行求和,axis=0 按行除
3. 深拷贝 vs 浅拷贝¶
import pandas as pd
# ============================================================
# 深拷贝 vs 浅拷贝
# 这是Python中非常重要的概念,搞混了会修改原始数据!
# ============================================================
otu_original = pd.read_csv("otu_table.tsv", sep="\t", index_col=0)
# ---------- 浅拷贝(引用)—— 危险! ----------
otu_ref = otu_original # 这不是拷贝!只是给同一个对象起了新名字
otu_ref.iloc[0, 0] = 999 # 修改otu_ref...
print(otu_original.iloc[0, 0]) # 输出: 999 ← 原始数据也被修改了!
# ---------- 深拷贝 —— 安全 ----------
otu_copy = otu_original.copy() # 创建完全独立的副本
otu_copy.iloc[0, 0] = 0 # 修改副本
print(otu_original.iloc[0, 0]) # 原始数据不受影响
# 生信场景:做数据筛选/过滤时,一定要用 .copy()
filtered = otu_original[otu_original.mean(axis=1) > 10].copy()
# 如果不加 .copy(),后续修改filtered可能触发SettingWithCopyWarning
4. loc vs iloc¶
import pandas as pd
# ============================================================
# loc vs iloc
# loc = 按标签(名称)索引
# iloc = 按位置(整数)索引
# ============================================================
df = pd.DataFrame(
{"Shannon": [3.42, 2.89, 4.01], "Simpson": [0.93, 0.88, 0.95]},
index=["sample_A", "sample_B", "sample_C"]
)
# ---------- loc:按标签名称 ----------
df.loc["sample_A", "Shannon"] # 返回: 3.42
df.loc["sample_A":"sample_B"] # 返回前两行(包含末尾!与Python切片不同!)
df.loc[df["Shannon"] > 3, "Shannon"] # 条件筛选
# ---------- iloc:按整数位置 ----------
df.iloc[0, 0] # 返回: 3.42(第0行第0列)
df.iloc[0:2] # 返回前两行(不包含末尾,标准Python切片)
df.iloc[:, 0] # 所有行的第0列
# 记忆:
# loc = label-based(标签)→ loc 里面用"名称"
# iloc = integer-based(整数)→ iloc 里面用"数字"
# 关键区别:loc切片包含末尾,iloc切片不包含末尾!
df.loc["sample_A":"sample_C"] # 包含sample_C(3行)
df.iloc[0:3] # 不包含index=3(也是3行,但规则不同)
常用Python包安装¶
# ============================================================
# 创建生信Python分析环境
# ============================================================
# 创建新的conda环境
conda create -n bioinfo_py python=3.10 -y
# 激活环境
conda activate bioinfo_py
# 安装核心数据分析包
conda install -c conda-forge pandas numpy scipy matplotlib seaborn scikit-learn -y
# 安装统计分析包
conda install -c conda-forge statsmodels -y
# 安装生信特定包
conda install -c conda-forge scikit-bio -y # 距离矩阵、PCoA、PERMANOVA
# 安装Jupyter(交互式分析)
conda install -c conda-forge jupyterlab -y
# 验证安装
python -c "
import pandas as pd
import numpy as np
import scipy
import matplotlib
import seaborn as sns
import sklearn
print('所有包安装成功!')
print(f'pandas: {pd.__version__}')
print(f'numpy: {np.__version__}')
print(f'scipy: {scipy.__version__}')
"
快速复习卡片¶
| 问题 | 一句话答案 |
|---|---|
| pandas读TSV | pd.read_csv("file.tsv", sep="\t", index_col=0) |
| 计算相对丰度 | df.apply(lambda col: col / col.sum(), axis=0) |
| DataFrame vs Series | DataFrame是二维表格,Series是一维列 |
| Shannon指数公式 | H' = -sum(pi * ln(pi)) |
| Wilcoxon检验函数 | scipy.stats.mannwhitneyu() |
| 多重检验校正 | multipletests(p_values, method='fdr_bh') |
| PCA vs PCoA | PCA用原始矩阵+欧氏距离,PCoA用距离矩阵+任意距离 |
| loc vs iloc | loc按标签名,iloc按整数位置 |
| axis=0 vs axis=1 | axis=0沿行方向(结果按列),axis=1沿列方向(结果按行) |
| 深拷贝 | df.copy() 创建独立副本,避免修改原数据 |
学习路径建议¶
- 先掌握:pandas 读写/筛选/分组 + matplotlib 基本图表(日常工作最常用)
- 再学习:scipy 统计检验 + seaborn 高级可视化(差异分析和发表级图表)
- 进阶:scikit-learn PCA/随机森林 + scikit-bio PCoA/PERMANOVA
- 面试重点:能手写 Shannon 指数计算 + 解释 PCA vs PCoA 的区别