4、数据可视化基础:Matplotlib架构、Figure与Axes、基本图表绘制、样式与配色方案
做量化投研这几年,我越来越觉得可视化就是策略的「翻译官」。你模型再牛,回测收益再高,一张丑图就能让老板皱眉头。反过来,一张干净漂亮的图表,能把复杂的逻辑讲得明明白白。
今天咱们就聊聊 Matplotlib。这玩意儿是 Python 可视化的基石,很多高级库(比如 Seaborn、Plotly)底层都离不开它。但说实话,很多人用了好几年 Matplotlib,其实连它的核心架构都没搞明白。
核心认知:Matplotlib 不是「画图工具」,它是一个「面向对象的绘图框架」。理解了这个,你才能从「调参侠」变成「架构师」。
4.1 Matplotlib 的三层架构
我个人习惯把 Matplotlib 想象成一个画室。它有三层结构:
- 后端(Backend):负责渲染,就像画室的画布和颜料。你不需要直接操作它,但要知道它存在。
- Artist 层:所有你能看到的东西——线条、文字、图例——都是 Artist 对象。这是最底层。
- pyplot 层:这是给新手用的快捷方式。说白了就是「自动帮你创建 Figure 和 Axes,然后往上堆 Artist」。
嗯,这里要注意:千万别只依赖 pyplot。我在项目中遇到过,用 pyplot 画简单的折线图没问题,但一旦要做复杂的多子图、自定义坐标轴,pyplot 就捉襟见肘了。你得学会直接操作 Figure 和 Axes。
我的建议:日常快速探索用 pyplot,正式报告或复杂图表,请用面向对象的方式。这是区分新手和老手的分水岭。
4.2 Figure 与 Axes:画布与子图
这两个概念太重要了,我单独拎出来讲。
- Figure:就是整张画布。它负责承载所有内容,包括标题、图例、以及多个子图。
- Axes:是画布上的一个「绘图区域」。注意,Axes 不是坐标轴(Axis),它是包含数据、坐标轴、刻度、标签的完整子图。
你想想看,一张 Figure 可以包含多个 Axes,每个 Axes 又可以独立绘制不同的图表。这就是多子图布局的基础。
下面这张 SVG 图,我画出了它们的关系:
我曾经踩过的坑:刚学的时候,我总以为 plt.plot() 是直接画在 Figure 上的。后来才发现,它其实是自动创建了一个 Axes,然后画在 Axes 上。如果你连续调用 plt.plot() 两次,它们会画在同一个 Axes 上,而不是新建一个子图。这就是为什么很多人画多子图时,图会叠在一起。
4.3 基本图表绘制
量化投研里,最常用的就那几种图。我按使用频率排个序:
4.3.1 折线图(时间序列必备)
这是最核心的。价格曲线、净值曲线、回撤曲线,全是折线图。
import matplotlib.pyplot as plt
import numpy as np
# 面向对象方式(推荐)
fig, ax = plt.subplots(figsize=(10, 6))
dates = np.arange('2024-01-01', '2024-02-01', dtype='datetime64[D]')
prices = np.cumsum(np.random.randn(len(dates))) + 100
ax.plot(dates, prices, color='#2c3e50', linewidth=1.5, label='策略净值')
ax.fill_between(dates, prices, 0, where=(prices < 0), color='red', alpha=0.1)
ax.set_title('策略净值曲线', fontsize=14)
ax.legend()
plt.show()
一个小技巧:用 fill_between 填充回撤区域,能让风险暴露一目了然。我在做风控报告时,这个函数帮了大忙。
4.3.2 柱状图(对比与分布)
行业配置、因子收益、每日盈亏,柱状图最直观。
# 柱状图示例
fig, ax = plt.subplots()
sectors = ['金融', '科技', '消费', '医药', '能源']
returns = [0.05, 0.12, -0.03, 0.08, 0.01]
colors = ['#3498db' if r > 0 else '#e74c3c' for r in returns]
ax.bar(sectors, returns, color=colors, edgecolor='white')
ax.axhline(y=0, color='black', linewidth=0.8)
ax.set_title('行业因子收益')
4.3.3 散点图(相关性探索)
因子与收益的关系、两个资产的相关性,散点图加一条回归线,效果拔群。
# 散点图 + 回归线
fig, ax = plt.subplots()
x = np.random.randn(100)
y = 0.5 * x + 0.1 * np.random.randn(100)
ax.scatter(x, y, alpha=0.6, s=30)
# 拟合回归线
m, b = np.polyfit(x, y, 1)
ax.plot(x, m*x + b, color='red', linestyle='--')
ax.set_xlabel('因子暴露')
ax.set_ylabel('未来收益')
4.3.4 箱线图(异常值检测)
我每周都会用箱线图检查因子数据。一眼就能看出哪些股票是异常值。
# 箱线图
data = [np.random.randn(50) for _ in range(5)]
fig, ax = plt.subplots()
ax.boxplot(data, labels=['因子A', '因子B', '因子C', '因子D', '因子E'])
ax.set_title('因子分布对比')
4.4 样式与配色方案
说实话,Matplotlib 默认的配色真的丑。蓝配黄,红配绿,看着像 90 年代的 Excel 图表。我建议你直接换风格。
4.4.1 内置样式
Matplotlib 提供了很多内置样式,一行代码切换:
# 查看所有可用样式
print(plt.style.available)
# 我常用的几个
plt.style.use('seaborn-v0_8') # 干净,适合学术
plt.style.use('ggplot') # 仿 R 语言,对比度高
plt.style.use('fivethirtyeight') # 数据新闻风格,适合报告
plt.style.use('dark_background') # 深色背景,适合演示
我的个人偏好:做内部研究用 'seaborn-v0_8',出正式报告用 'fivethirtyeight'。前者柔和,后者有冲击力。
4.4.2 配色方案
配色是门学问。我总结了几条铁律:
- 不要超过 6 种颜色:人眼分辨能力有限,颜色多了就是灾难。
- 用色盲友好的配色:大约 8% 的男性是色盲。我用的是
viridis或plasma色系。 - 语义化用色:红色代表风险/亏损,绿色代表收益/增长,蓝色代表中性。别乱用。
# 推荐配色方案
colors = {
'positive': '#2ecc71', # 翠绿
'negative': '#e74c3c', # 正红
'neutral': '#3498db', # 亮蓝
'warning': '#f39c12', # 橙黄
'background': '#f8f9fa' # 浅灰背景
}
# 使用 colormap
import matplotlib.cm as cm
cmap = cm.get_cmap('viridis')
norm = plt.Normalize(0, 1)
colors = [cmap(norm(i/10)) for i in range(10)]
4.4.3 自定义样式文件
如果你像我一样,每周要出几十张图,建议写一个自定义样式文件。这样所有图表风格统一,改起来也方便。
# 保存为 my_style.mplstyle
# axes.facecolor: f8f9fa
# axes.edgecolor: 2c3e50
# axes.grid: True
# grid.alpha: 0.3
# font.family: sans-serif
# font.size: 12
# lines.linewidth: 1.5
# 使用
plt.style.use('my_style.mplstyle')
我曾经犯过的错:有一次做周报,用了 8 种不同的颜色画了 8 条净值曲线。结果老板说:「你这彩虹图是给幼儿园看的吗?」从那以后,我严格限制颜色数量,超过 5 条线就用黑白灰加线型区分。
4.5 实战:一张完整的量化报告图表
最后,我把今天讲的东西串起来。这是一张我实际用在周报里的图表:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
# 设置全局样式
plt.style.use('fivethirtyeight')
plt.rcParams['font.family'] = 'sans-serif'
# 生成模拟数据
dates = pd.date_range('2024-01-01', periods=100)
strategy = np.cumsum(np.random.randn(100)) + 100
benchmark = np.cumsum(np.random.randn(100) * 0.8) + 100
# 创建画布和子图
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8),
gridspec_kw={'height_ratios': [3, 1]})
# 主图:净值曲线
ax1.plot(dates, strategy, color='#2c3e50', linewidth=2, label='策略')
ax1.plot(dates, benchmark, color='#95a5a6', linewidth=1.5,
linestyle='--', label='基准', alpha=0.7)
ax1.fill_between(dates, strategy, benchmark,
where=(strategy > benchmark),
color='#2ecc71', alpha=0.1, label='超额收益')
ax1.fill_between(dates, strategy, benchmark,
where=(strategy <= benchmark),
color='#e74c3c', alpha=0.1)
ax1.set_title('策略表现 vs 基准', fontsize=16, fontweight='bold')
ax1.legend(loc='upper left')
ax1.set_ylabel('净值')
# 副图:每日收益
daily_ret = np.diff(strategy) / strategy[:-1]
ax2.bar(dates[1:], daily_ret, color=['#2ecc71' if r > 0 else '#e74c3c'
for r in daily_ret],
width=0.8, alpha=0.7)
ax2.axhline(y=0, color='black', linewidth=0.5)
ax2.set_ylabel('日收益')
ax2.set_xlabel('日期')
plt.tight_layout()
plt.show()
你看,一张图里包含了折线、填充、柱状、双轴、自定义配色。这就是 Matplotlib 的威力。掌握了 Figure 和 Axes 的关系,你就能随心所欲地组合各种图表元素。
做量化投研,图表就是你的脸面。别让一张丑图毁了你的好策略。
公众号:蓝海资料掘金营,微信deep3321