介绍~

本文将从最基础的医学生信分析讲起,带领大家一步步入门,最终完成并发表属于自己的第一篇SCI论文。全文内容基于个人经验总结,特别适合零基础的小伙伴学习。一般而言,文章设计需要一个数据集进行初步分析(测试集),再用另一个独立数据集进行验证(验证集)。这里我们主要选取其中一个数据集进行详细讲解。我们以中国脑胶质瘤基因图谱计划(CGGA)中的公开数据集CGGA-325为例,介绍数据预处理与基因差异分析的基本流程。该数据集可通过CGGA官网(https://www.cgga.org.cn)获取。接下来,我们将重点讲解该部分的数据处理与分析方法。

1. 数据的读取与数据合并

下载的数据分为临床数据集与测序数据集,不要对原始数据进行改动,我们直接在pandas中进行操作。首先,导入CGGA_325_clinical.csv临床样本数据集:

import pandas as pd

# 读取训练集 - 使用文件自带的列名,然后选择需要的列
dataset_1 = pd.read_csv("CGGA_325_clinical.csv", 
                       header=0,         # 使用第一行作为列名
                       index_col=None,   # 第一列作为行索引
                       sep=',\s*',       # 修正正则表达式
                       na_values=["?"],  # 将"?"识别为缺失值(NaN)
                       engine='python')  # 使用Python引擎以支持正则分隔符


# 显示前10行
dataset_1

其次,以同样的方式导入CGGA_325_gene.csv基因组数据:

dataset_2 = pd.read_csv("CGGA_325_gene.csv", 
                        header=0,           # 第一行作为列名
                        index_col=0,        # 第一列作为行索引
                        sep=',\s*',         # 修正正则表达式
                        na_values=["?"],    # 将"?"识别为缺失值(NaN)
                        engine='python'     # 使用Python引擎以支持正则分隔符
)

# 显示前10行
dataset_2

最后,将基因组数据集进行转置操作,并对临床数据集与测序数据集进行合并,且命名为CGGA_325_cinical_gene.csv,合并后的数据集结果如下所示:

2. Log₂FC、P Value计算

2.1 导入整理好的新数据集

在这一步导入前面整理好的合并数据集CGGA_325_cinical_gene.csv,以后所有的操作均在这个数据集中完成:

import pandas as pd
CGGA_325_cinical_gene = pd.read_csv("CGGA_325_cinical_gene.csv",
                       header=0,         # 使用第一行作为列名
                       index_col=0,      # 第一列作为行索引
                       sep=',\s*',       # 修正正则表达式
                       na_values=["?"],  # 将"?"识别为缺失值(NaN)
                       engine='python')  # 使用Python引擎以支持正则分隔符


# 显示前10行
CGGA_325_cinical_gene

2.2 选择分组

这一步我们需要分组的选择,本节我们以高低级别胶质瘤进行分组,病理组织与WHO分级见补充部分

CGGA_325_cinical_gene['Histology'].unique()
结果:array(['GBM', 'AA', 'rGBM', 'rA', 'O', 'rAA', 'sGBM', 'A', nan, 'AO',
       'rO'], dtype=object)

补充:

高级别胶质瘤(High-Grade Glioma) 这些类别对应WHO III级或IV级,肿瘤恶性程度较高,生长较快:

  • GBM: 胶质母细胞瘤(Glioblastoma),WHO IV级。
  • AA: 间变性星形细胞瘤(Anaplastic Astrocytoma),WHO III级。
  • rGBM: 复发性胶质母细胞瘤(Recurrent GBM),基于GBM,WHO IV级。
  • rAA: 复发性间变性星形细胞瘤(Recurrent AA),基于AA,WHO III级。
  • sGBM: 继发性胶质母细胞瘤(Secondary GBM),通常从低级别进展而来,但最终为WHO IV级。
  • AO: 间变性少突胶质细胞瘤(Anaplastic Oligodendroglioma),WHO III级。

低级别胶质瘤(Low-Grade Glioma) 这些类别对应WHO I级或II级,肿瘤恶性程度较低,生长较慢:

  • rA: 复发性星形细胞瘤(Recurrent Astrocytoma),基于A(星形细胞瘤),通常WHO II级。
  • O: 少突胶质细胞瘤(Oligodendroglioma),通常WHO II级。
  • A: 星形细胞瘤(Astrocytoma),通常WHO II级。
  • rO: 复发性少突胶质细胞瘤(Recurrent Oligodendroglioma),基于O,通常WHO II级。

2.3 数据编码

# 创建真实副本
df = CGGA_325_cinical_gene.copy()

# 明确指定需要编码的列
categorical_columns = ['Censor','Gender','Grade', 'Histology', 'IDH', 'MGMTp', '1p19q']

# 创建字典保存映射关系
encoding_mappings = {}

for col in categorical_columns:
    # 进行编码并保存映射关系
    encoded, categories = df[col].factorize()
    df[col] = encoded
    encoding_mappings[col] = dict(enumerate(categories))
    
# 查看所有列的编码映射
for col, mapping in encoding_mappings.items():
    print(f"\n{col} 列的编码映射:")
    for code, category in mapping.items():
        print(f"  {code}: {category}")
Censor 列的编码映射:
  0: Alive
  1: Dead

Gender 列的编码映射:
  0: Male
  1: Female

Grade 列的编码映射:
  0: WHO IV
  1: WHO III
  2: WHO II

Histology 列的编码映射:
  0: GBM
  1: AA
  2: rGBM
  3: rA
  4: O
  5: rAA
  6: sGBM
  7: A
  8: AO
  9: rO

IDH 列的编码映射:
  0: Wildtype
  1: Mutant

MGMTp 列的编码映射:
  0: Un-methylated
  1: Methylated

1p19q 列的编码映射:
  0: Non-codel
  1: Codel

2.4 Log₂FC、P Value计算

本节以高低级别胶质瘤进行分组WHO IV与WHO III期属高级别,WHO I与WHO II属低级别

import pandas as pd
import numpy as np
from scipy import stats
from statsmodels.stats.multitest import multipletests

# 提取基因列(从A1BG开始)
gene_columns = [col for col in df.columns if col not in [
    'Histology', 'Grade', 'Gender', 'Age', 'OS',
    'Censor', 'IDH', '1p19q', 'MGMTp'
]]

print(f"找到 {len(gene_columns)} 个基因进行差异表达分析")

# 根据Grade分组
group1 = df[df['Grade'] == 2]  # 低级别
group2 = df[df['Grade'].isin([0, 1])]  # 高级别

print(f"组1 (低级别) 样本数: {len(group1)}")
print(f"组2 (高级别) 样本数: {len(group2)}")

# 计算每个基因的logFC和p值
results = []

for gene in gene_columns:
    # 获取两组的表达值
    expr1 = group1[gene].values
    expr2 = group2[gene].values

    # 计算均值
    mean1 = np.mean(expr1)
    mean2 = np.mean(expr2)

    # 计算logFC (以2为底)
    if mean1 == 0 or mean2 == 0:
        # 处理零值情况
        logFC = np.nan
    else:
        logFC = np.log2(mean2 / mean1)

    # 进行t检验
    if len(expr1) > 1 and len(expr2) > 1:  # 确保每组至少有2个样本
        t_stat, p_value = stats.ttest_ind(expr1, expr2, equal_var=False)  # Welch's t-test
    else:
        p_value = np.nan

    results.append({
        'Gene': gene,
        'Mean_Group1': mean1,
        'Mean_Group2': mean2,
        'logFC': logFC,
        'p_value': p_value
    })

# 创建结果DataFrame
result_df = pd.DataFrame(results)

# 多重检验校正 (FDR)
result_df['p_adjust'] = multipletests(result_df['p_value'], method='fdr_bh')[1]

# 排序结果(按p值)
result_df = result_df.sort_values('p_value')

# 显示前20个最显著的基因
print("\n前20个最显著的差异表达基因:")
print(result_df.head(20))

# 保存结果到CSV文件
result_df.to_csv('differential_expression_results.csv', index=False)
print("\n结果已保存到 'differential_expression_results.csv'")

我们得到differential_expression_results.csv数据集,结果如下:

3. 火山图绘制

3.1 导入分析结果数据集

from adjustText import adjust_text
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

LogFC_Pvalue = pd.read_csv("differential_expression_results.csv",
                       header=0,         # 使用第一行作为列名
                       index_col=None,      # 第一列作为行索引
                       sep=',\s*',       # 修正正则表达式
                       na_values=["?"],  # 将"?"识别为缺失值(NaN)
                       engine='python')  # 使用Python引擎以支持正则分隔符


result_df = LogFC_Pvalue

3.2 火山图绘制

import matplotlib.pyplot as plt
import numpy as np
from adjustText import adjust_text  # 确保已安装:pip install adjustText

def plot_volcano(result_df, logFC_threshold=1, p_value_threshold=0.05,
                 max_labels=50, figsize=(12, 8), title='Volcano Plot',
                 custom_genes=None):
    """
    绘制SCI风格火山图

    参数:
    result_df: 包含logFC和p_value的数据框
    logFC_threshold: logFC阈值,默认1(|logFC| > 1为显著)
    p_value_threshold: p值阈值,默认0.05
    max_labels: 最大标注基因数量,默认50
    figsize: 图形大小
    title: 图表标题
    custom_genes: 自定义要标注的基因列表,默认为None
    """
    # 设置SCI风格:无衬线字体、白色背景、网格
    plt.style.use('default')
    plt.rcParams['font.family'] = 'Arial'  # 使用Arial字体
    plt.rcParams['axes.edgecolor'] = 'black'
    plt.rcParams['axes.linewidth'] = 0.8
    plt.rcParams['grid.color'] = '#E6E6E6'
    plt.rcParams['grid.linestyle'] = '--'

    # 创建图形
    fig, ax = plt.subplots(figsize=figsize)
    ax.set_facecolor('white')  # 设置背景为白色

    # 计算-log10(p_value)
    result_df['neg_log10_p'] = -np.log10(result_df['p_value'])

    # 识别上调、下调和不变的基因
    up_regulated = (result_df['logFC'] > logFC_threshold) & (result_df['p_value'] < p_value_threshold)
    down_regulated = (result_df['logFC'] < -logFC_threshold) & (result_df['p_value'] < p_value_threshold)
    not_significant = ~(up_regulated | down_regulated)

    # 绘制点:使用SCI风格配色和透明度
    ax.scatter(result_df['logFC'][not_significant],
               result_df['neg_log10_p'][not_significant],
               c='#B0B0B0', alpha=0.3, s=40, label='Not significant', edgecolors='none')

    ax.scatter(result_df['logFC'][up_regulated],
               result_df['neg_log10_p'][up_regulated],
               c='#D62728', alpha=0.4, s=70, label=f'Up (logFC > {logFC_threshold})', edgecolors='black')

    ax.scatter(result_df['logFC'][down_regulated],
               result_df['neg_log10_p'][down_regulated],
               c='#1F77B4', alpha=0.4, s=70, label=f'Down (logFC < -{logFC_threshold})', edgecolors='black')

    # 添加阈值线:黑色虚线,更细
    ax.axhline(-np.log10(p_value_threshold), color='black', linestyle='--', linewidth=0.8, alpha=0.7)
    ax.axvline(logFC_threshold, color='black', linestyle='--', linewidth=0.8, alpha=0.7)
    ax.axvline(-logFC_threshold, color='black', linestyle='--', linewidth=0.8, alpha=0.7)
    ax.axvline(0, color='black', linestyle='-', linewidth=0.5, alpha=0.5)

    # 标注自定义基因
    if 'Gene' in result_df.columns and custom_genes is not None:
        custom_genes_df = result_df[result_df['Gene'].isin(custom_genes)].copy()
        if not custom_genes_df.empty:
            custom_genes_df['regulation'] = custom_genes_df['logFC'].apply(
                lambda x: 'up' if x > 0 else 'down'
            )
            max_labels = min(max_labels, len(custom_genes_df))
            texts = []
            for i, (idx, row) in enumerate(custom_genes_df.head(max_labels).iterrows()):
                # 根据上下调选择颜色
                if row['regulation'] == 'up':
                    bbox_color = '#FFE4E1'  # 浅红色背景
                    text_color = '#8B0000'  # 深红色文字
                else:
                    bbox_color = '#E6F3FF'  # 浅蓝色背景
                    text_color = '#00008B'  # 深蓝色文字

                text = ax.annotate(row['Gene'],
                                  (row['logFC'], row['neg_log10_p']),
                                  fontsize=9,
                                  fontweight='bold',
                                  color=text_color,
                                  bbox=dict(boxstyle="round,pad=0.3",
                                           facecolor=bbox_color,
                                           edgecolor=text_color,
                                           alpha=0.8,
                                           linewidth=0.5))
                texts.append(text)

            # 自动调整文本位置,避免重叠
            if texts:
                adjust_text(texts,
                            ax=ax,
                            arrowprops=dict(arrowstyle="->", color='gray', lw=0.5, alpha=0.6),
                            expand_text=(1.2, 1.2),
                            force_text=0.5,
                            ensure_inside_axes=True)

    # 设置标签和标题:使用粗体和大字体
    ax.set_xlabel('log2(Fold Change)', fontsize=12, fontweight='bold')
    ax.set_ylabel('-log10(p-value)', fontsize=12, fontweight='bold')
    ax.set_title(title, fontsize=14, fontweight='bold', pad=20)

    # 添加网格:浅灰色虚线
    ax.grid(True, alpha=0.4, linestyle='--')

    # 图例:位置在右上角,边框透明
    ax.legend(loc='upper right', frameon=True, framealpha=0.9, edgecolor='black')

    # 调整布局
    plt.tight_layout()

    return fig, up_regulated, down_regulated

# 使用示例(保持不变)
my_custom_genes = ['HSPA7']  # 替换为您的基因列表
plt_fig, up_regulated, down_regulated = plot_volcano(result_df,
                   logFC_threshold=2.0,
                   p_value_threshold=0.05,
                   max_labels=10,
                   title='CGGA-325 Volcano Plot: Low-Grade vs High-Grade',
                   custom_genes=my_custom_genes)

# 保存图片(推荐PNG或PDF格式以保持质量)
plt_fig.savefig('volcano_plot_sci_style.png', dpi=300, bbox_inches='tight')
plt_fig.show()

# 输出统计信息
print(f"总基因数: {len(result_df)}")
print(f"显著上调基因数 (logFC > 2.0, p < 0.05): {up_regulated.sum()}")
print(f"显著下调基因数 (logFC < -2.0, p < 0.05): {down_regulated.sum()}")

绘图示例

def plot_volcano(result_df, logFC_threshold=1, p_value_threshold=0.05,
                 max_labels=50, figsize=(12, 8), title='Volcano Plot'):
    """
    绘制火山图

    参数:
    result_df: 包含logFC和p_value的数据框
    logFC_threshold: logFC阈值,默认1(|logFC| > 1为显著)
    p_value_threshold: p值阈值,默认0.05
    max_labels: 最大标注基因数量,默认50
    figsize: 图形大小
    title: 图表标题
    """
    # 创建图形
    plt.figure(figsize=figsize)

    # 计算-log10(p_value)
    result_df['neg_log10_p'] = -np.log10(result_df['p_value'])

    # 识别上调、下调和不变的基因
    up_regulated = (result_df['logFC'] > logFC_threshold) & (result_df['p_value'] < p_value_threshold)
    down_regulated = (result_df['logFC'] < -logFC_threshold) & (result_df['p_value'] < p_value_threshold)
    not_significant = ~(up_regulated | down_regulated)

    # 绘制点
    plt.scatter(result_df['logFC'][not_significant],
                result_df['neg_log10_p'][not_significant],
                c='gray', alpha=0.2, s=50, label='Not significant', edgecolors='None')

    plt.scatter(result_df['logFC'][up_regulated],
                result_df['neg_log10_p'][up_regulated],
                c='red', alpha=0.5, s=70, label=f'Up-regulated (logFC > {logFC_threshold})', edgecolors='black')

    plt.scatter(result_df['logFC'][down_regulated],
                result_df['neg_log10_p'][down_regulated],
                c='blue', alpha=0.5, s=70, label=f'Down-regulated (logFC < -{logFC_threshold})', edgecolors='black')

    # 添加阈值线
    plt.axhline(-np.log10(p_value_threshold), color='black', linestyle='--', alpha=0.8)
    plt.axvline(logFC_threshold, color='black', linestyle='--', alpha=0.8)
    plt.axvline(-logFC_threshold, color='black', linestyle='--', alpha=0.8)
    plt.axvline(0, color='black', linestyle='-', alpha=0.5)

    # 标注显著基因
    if 'Gene' in result_df.columns:
        # 筛选显著基因
        significant_genes = result_df[
            ((result_df['logFC'] > logFC_threshold) | (result_df['logFC'] < -logFC_threshold)) &
            (result_df['p_value'] < p_value_threshold)
        ].copy()

        # 添加一列标识上/下调
        significant_genes['regulation'] = significant_genes['logFC'].apply(
            lambda x: 'up' if x > 0 else 'down'
        )

        # 按显著性排序
        significant_genes = significant_genes.sort_values('p_value')

        # 限制标注数量
        max_labels = min(max_labels, len(significant_genes))
        if max_labels > 0:
            texts = []

            # 标注基因
            for i, (idx, row) in enumerate(significant_genes.head(max_labels).iterrows()):
                # 根据上/下调选择颜色
                if row['regulation'] == 'up':
                    bbox_color = "#FFCCCB"  # 浅红色
                    text_color = "darkred"
                else:
                    bbox_color = "#ADD8E6"  # 浅蓝色
                    text_color = "darkblue"

                text = plt.annotate(row['Gene'],
                                   (row['logFC'], row['neg_log10_p']),
                                   fontsize=8,
                                   alpha=0.9,
                                   color=text_color,
                                   bbox=dict(boxstyle="round,pad=0.2",
                                            facecolor=bbox_color,
                                            edgecolor=text_color,
                                            alpha=0.8))
                texts.append(text)

            # 自动调整文本位置
            if texts:
                adjust_text(texts,
                            arrowprops=dict(arrowstyle="->", color='gray', lw=0.5, alpha=0.7, shrinkA=10, shrinkB=5),
                            expand_text=(1.2, 1.2),
                            expand_points=(1.3, 1.3),
                            force_text=0.5,
                            force_points=0.5,
                            ensure_inside_axes=True)  # 确保文本在坐标轴内

    # 设置标签和标题
    plt.xlabel('log2(Fold Change)')
    plt.ylabel('-log10(p-value)')
    plt.title(title)
    plt.legend()

    # 添加网格
    plt.grid(True, alpha=0.3)

    # 调整布局
    plt.tight_layout()

    return plt, up_regulated, down_regulated

# 使用示例
# 假设你已经有了result_df,包含'logFC', 'p_value', 'Gene'列
# 绘制火山图
plt_fig, up_regulated, down_regulated = plot_volcano(result_df,
                   logFC_threshold=2.5,
                   p_value_threshold=0.05,
                   max_labels=10,
                   title='Volcano Plot: Low-Grade vs High-Grade')

# 保存图片
plt_fig.savefig('volcano_plot.png', dpi=300, bbox_inches='tight')

# 显示图片
plt_fig.show()

# 输出统计信息
print(f"总基因数: {len(result_df)}")
print(f"显著上调基因数 (logFC > 2.5, p < 0.05): {up_regulated.sum()}")
print(f"显著下调基因数 (logFC < -2.5, p < 0.05): {down_regulated.sum()}")
print(f"最显著上调基因: {result_df[up_regulated].nsmallest(154, 'p_value')['Gene'].tolist() if up_regulated.sum() > 0 else '无'}")
print(f"最显著下调基因: {result_df[down_regulated].nsmallest(1, 'p_value')['Gene'].tolist() if down_regulated.sum() > 0 else '无'}")

本节我们将得到高级别与低级别脑胶质瘤的差异基因可视化

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐