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 图,我画出了它们的关系:

Figure(画布) Axes 1 折线图 Axes 2 柱状图 图例 / 标题 / 坐标轴标签 这些都属于 Artist 对象,挂载在 Figure 或 Axes 上

我曾经踩过的坑:刚学的时候,我总以为 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% 的男性是色盲。我用的是 viridisplasma 色系。
  • 语义化用色:红色代表风险/亏损,绿色代表收益/增长,蓝色代表中性。别乱用。
# 推荐配色方案
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