第4章 数据可视化基础:Matplotlib vs Plotly,Plotly基础图表类型

终于要到动手画图的部分了。

我记得刚开始接触生物信息学那会儿,做差异表达分析最头疼的不是p值怎么调,而是拿到结果后不知道怎么展示给同行看。把几十个基因的表达量贴到论文里?没人看得懂。所以数据可视化不仅是“画图”,更是把数据规律翻译成视觉语言的过程。

在Python里,主流可视化库有两大阵营:Matplotlib和Plotly。我的课程里为什么选Plotly来做交互式报告?说白了,就一个原因——它能响应用户的点击和悬停。你想想看,一篇差异表达报告背后可能是几万个基因,静态图根本看不过来,交互才能让读者自己探索。

这一章我会带你捋清楚这两个库的定位差异,然后重点上手Plotly的几种基础图表。这些都是后续构建Dash仪表盘的基石。

Matplotlib vs Plotly:核心差异

先看一张对比表,帮你快速建立认知。

维度 Matplotlib Plotly
交互性 基本无,输出静态图片(PNG/SVG) 原生支持悬停提示、缩放、平移、点击事件
默认美观度 偏科学论文风格,需手动调样式 现代简约,默认配色和布局更友好
输出格式 静态图像文件 HTML/JSON(可在浏览器中直接渲染)
大数据性能 中等,百万级点可能卡顿 通过WebGL加速能处理百万级点
与Dash集成 需转成base64嵌入,交互性丢失 原生组件dcc.Graph直接接收
学习曲线 底层层级多,API风格老旧 高层封装(Plotly Express)一两行出图

我在项目中遇到过这样的情况:用Matplotlib画了一张火山图,老板问“这个基因是哪个?”我只能在PPT上画个红圈发过去。换成Plotly之后,鼠标悬停就能看到基因名,双击还能放大。这就是我坚持用Plotly的原因——它让报告“活”了起来。

我的建议:如果你的目标是发表论文或插入Word文档,Matplotlib仍是首选。但如果你在做在线报告、Dashboard或需要读者交互的web应用,直接学Plotly更高效。

数据可视化的通用工作流

不管用哪个库,画图的逻辑都是一样的。下面这张SVG图展示了我个人习惯的流程。

数据可视化通用工作流 数据准备 理解需求 选择图表 创建图表 交互化 输出/嵌入 💡 右侧虚线箭头表示:在交互式Dash应用中,创建完图表后通常需要添加回调 “交互化”步骤是将静态Plotly图与用户输入绑定的关键。

这个流程看起来很简单,但每个步骤都有坑。比如“理解需求”这一步:师兄要火山图,你以为就是散点图加阈值线?不不不,他还需要标注top 10基因的标签。如果你一开始没确认“标注”这个需求,后面返工就很麻烦。

Plotly基础图表类型

Plotly有两种使用方式。一种是高层封装的Plotly Express,用一行代码就能生成常见图表。另一种是Graph Objects,底层控制力更强,适合定制复杂的交互。我一般先用Express快速出原型,再切换到Graph Objects调细节。

下面是差异表达分析中最常用的几种图表,我挑了几个典型场景来讲。

1. 散点图 —— 火山图的基础

火山图的核心就是散点图,x轴是log2倍变化,y轴是负log10调整后的p值。用Plotly Express写出来是这样的:

import plotly.express as px
import pandas as pd

# 假设df是差异表达结果,包含log2FC, p_adj, gene三列
df = pd.read_csv('deg_results.csv')

fig = px.scatter(
    df, 
    x='log2FC', 
    y='neg_log10_padj',
    hover_data=['gene'],
    title='火山图 - 差异表达基因',
    labels={'log2FC': 'Log2 Fold Change', 'neg_log10_padj': '-Log10(Adjusted P-value)'}
)
fig.show()

注意这里的hover_data参数。用户把鼠标移到任意一个点上,就会弹出该点的基因名。这在静态图里根本做不到。我个人习惯还会加上color参数,用上调或下调的标志列来区分显著基因。

2. 条形图 —— 展示top差异基因

有时候你想直观对比前10个上调基因和前10个下调基因的表达变化。条形图最直接:

top_genes = df.nlargest(10, 'log2FC')  # 取上调最多的10个
fig = px.bar(top_genes, x='gene', y='log2FC', 
             title='Top 10 上调基因')
fig.update_xaxes(tickangle=45)  # 调整基因名角度防止重叠
fig.show()

我曾经被导师批评说“bar的基因名叠在一起看不清”,后来学乖了,用tickangle=45倾斜显示,或者把图拉宽。嗯,这里有个隐藏技巧:用Plotly的fig.update_layout(width=1000)可以手动控制宽度,非常方便。

3. 箱线图 —— 比较组间表达分布

差异表达不只是看倍数变化,还要看具体的原始表达分布。比如某个基因在Control和Treat两组中的表达值是否有重叠?箱线图能直观展示中位数、四分位和离群值。

# 假设expression_data包含gene, sample, group, expression列
fig = px.box(expression_data, x='group', y='expression', color='group',
             title='基因A在对照组/处理组的表达分布')
fig.show()

用color='group'可以自动给两组分别配色。Plotly的默认配色——蓝红组合,在生物医学领域里挺顺眼。

4. 热图 —— 表达模式聚类

热图在差异表达报告里几乎是标配。它能同时展示多个基因在多个样本中的表达水平,而且通常配合聚类,让模式一目了然。Plotly的热图用px.imshow实现,但如果数据量较大(上百基因*几十样本),我建议用go.Heatmap:

import plotly.graph_objects as go
import numpy as np

# 假设matrix是基因×样本的表达矩阵,rows是基因名,cols是样本ID
fig = go.Figure(data=go.Heatmap(
    z=matrix.values,
    x=cols,
    y=rows,
    colorscale='RdBu_r',  # 红蓝配色,适合表达值
    zmid=0                # 确保零值对应白色
))
fig.update_layout(title='差异表达基因热图',
                  xaxis_title='样本',
                  yaxis_title='基因')
fig.show()

这里zmid=0很关键。如果你做的是标准化后的Z-score表达量,零值就是均值水平,用红蓝渐变时白色正好代表零,视觉上很清爽。我记得刚开始用热图时忘了设这个参数,结果所有基因看起来都在上调,差点出洋相。

小心性能雷区:如果基因数量超过500,直接用px.imshow或go.Heatmap的默认模式可能会卡。我曾经在一个项目里画了2000个基因的热图,浏览器直接崩了。解决方案是启用WebGL加速,在go.Heatmap中添加参数transforms=[dict(type='aggregate', ...)],或者限缩到top 50个显著基因。

从静态到交互:一个简单的提升

上面所有的例子里,fig.show()都会在Jupyter Notebook里生成交互图。但你也可以把它们保存为独立的HTML文件,发给任何人直接在浏览器打开:

fig.write_html('volcano_plot.html')

这一句我几乎在每个项目的分析脚本里都会写。做完报告后直接生成一个HTML文件夹,扔给服务器或者发给合作者,对方不需要装Python、不需要装库,只要双击就能交互式查看。这才是“可复现报告”的真正含义。

避坑指南:如果你决定用fig.write_html(),注意文件体积。一个包含数万个点的散点图可能产生几MB的HTML,加载会慢。这时候可以设置include_plotlyjs='cdn',让HTML引用在线Plotly库,而不是把库文件嵌入进去,能把文件大小压缩80%以上。

小结:这节课你带走了什么

我把这一章的核心知识点列了个清单,方便你日后回顾:

  • Matplotlib适合论文出版级静态图,Plotly适合交互式Web报告
  • 数据可视化工作流:数据准备→理解需求→选图表→创建→交互→输出
  • Plotly基础图表:散点图、条形图、箱线图、热图——四张图覆盖差异表达报告80%的需求
  • 交互的核心:用hover_data显示基因名,用color区分上下调,用write_html分发报告