05 数据可视化基础:使用 Matplotlib

课程开篇:为何数据可视化至关重要?

欢迎来到《商业大数据分析与应用》的数据可视化章节。

在商业世界,原始数据是冰冷的、抽象的。我们的任务是赋予它们意义。

数据可视化,就是将数字转化为洞察的艺术与科学。

商业决策依赖于清晰的沟通

想象一下,你需要向公司董事会汇报季度业绩。哪种方式更有效?

方案A:表格

季度 销售额 (百万)
Q1 12.5
Q2 15.2
Q3 11.8
Q4 18.1

方案B:图表

图表能瞬间揭示趋势与异常,这是纯文本无法比拟的。

本章目标:掌握 Matplotlib 核心技能

本章,我们将学习 Python 中最基础、最强大的可视化库——Matplotlib

学完本章,你将能够:

  • 绘制四种核心图形:折线图、柱状图、散点图、直方图。
  • 精修图表:添加标题、图例、调整坐标轴等,使图表更专业。
  • 组合图表:使用子图功能,将多个分析维度呈现在一张画布上。
  • 实战应用:分析真实的财务数据,将所学知识融会贯通。

Matplotlib:Python 可视化的基石

Matplotlib 是 Python 生态中许多其他高级可视化库(如 Seaborn, Pandas plotting)的底层依赖。

掌握 Matplotlib,意味着你理解了 Python 可视化的基本原理。

准备工作:导入 pyplot 模块

开始绘图前,我们需导入 matplotlib.pyplot 模块。

按照全球通行的惯例,我们将其重命名为 plt,以简化代码。

import matplotlib.pyplot as plt

这个 plt 对象,将是我们接下来执行所有绘图命令的入口。

核心绘图函数概览

plt 提供了四种对应核心图形的函数:

  • plt.plot(): 折线图 (Line Chart) - 展示连续数据趋势。
  • plt.bar(): 柱状图 (Bar Chart) - 比较不同类别数据。
  • plt.scatter(): 散点图 (Scatter Plot) - 探索两个变量间的关系
  • plt.hist(): 直方图 (Histogram) - 查看单个变量的分布

接下来,我们将逐一深入学习。

第一部分:Matplotlib 基础图形绘制

我们将从最基础的图形开始,一步步构建我们的可视化武器库。

折线图 (Line Chart):追踪数据的连续变化

折线图是观察数据随某一连续变量(通常是时间)变化的理想选择。

商业应用场景:

  • 追踪公司股价随时间的变化。
  • 分析月度活跃用户 (MAU) 的增长趋势。
  • 监控国民生产总值 (GDP) 的季度变化。

plt.plot():折线图的基本语法

plt.plot() 函数最简单的用法是传入两个等长的列表或数组:

  • 第一个参数:X 轴的数据点。
  • 第二个参数:Y 轴的数据点。

Matplotlib 会自动将这些 (X, Y) 点用直线连接起来。

案例:绘制简单的周活跃用户趋势

假设我们有一周的用户数据,我们来将其可视化。

import matplotlib.pyplot as plt

# 准备数据: x轴代表周一到周日,y轴代表活跃用户数
days = [1, 2, 3, 4, 5, 6, 7]
dau = [120, 132, 125, 145, 160, 130, 128]

# 绘制折线图
plt.plot(days, dau)

# 显示图形
plt.show()

折线图绘制结果

上图清晰地展示了周中用户活跃度上升,周末有所回落的趋势。

代码规范提示: pylab vs pyplot

在一些旧代码或教程中,你可能会看到 import pylab as pl

  • pylab:将 pyplotNumPy 功能打包在一起,方便交互式探索。
  • pyplot:只包含绘图功能。

现代编程规范推荐:明确地分开导入,以增强代码可读性和避免命名冲突。

# 推荐的规范写法
import matplotlib.pyplot as plt
import numpy as np

本课程将始终遵循这一现代规范。

引入 NumPy:处理数学函数与向量化计算

如果数据点之间存在数学关系(如 \(y = x^2\)),使用 Python 列表进行计算会很繁琐。

NumPy (Numerical Python) 库是科学计算的基石,其核心是 array 对象,它支持向量化运算,让数据处理更高效简洁。

案例:使用 NumPy 绘制函数图像

我们来绘制 \(y = x+1\)\(y = x^2\) 两条函数曲线。

代码解析:自定义线条样式

在上例中,我们为第二条线添加了额外的参数:

plt.plot(x1, y2, color='red', linewidth=3, linestyle='--')
  • color='red': 设置线条颜色。
  • linewidth=3: 设置线条宽度,单位是像素。
  • linestyle='--': 设置线条样式为虚线。常见的还有 '-' (实线), ':' (点线), '-.' (点划线)。

这些参数是增强图表可读性的重要工具。

柱状图 (Bar Chart):比较不同类别的数据

当需要比较不同类别的数据大小时,柱状图是最佳选择。

商业应用场景:

  • 比较不同产品线在上一季度的销售收入。
  • 展示各个国家或地区的市场份额。
  • 对比不同营销渠道带来的用户转化率。

plt.bar():柱状图的基本语法

plt.bar() 函数的用法与 plt.plot() 类似:

  • 第一个参数:X 轴的类别标签 (通常是字符串列表)。
  • 第二个参数:Y 轴的数值大小 (每个类别对应的高度)。

案例:比较各产品线销售额

假设我们需要展示三个产品线(A, B, C)的销售额。

import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 准备数据
products = ['产品线 A', '产品线 B', '产品线 C']
sales = [150, 230, 180]

# 绘制柱状图
plt.bar(products, sales)

# 添加标题和标签
plt.title('各产品线季度销售额对比')
plt.ylabel('销售额 (百万元)')

# 显示图形
plt.show()

柱状图绘制结果

从图中可以直观地看出,产品线 B 的销售额最高。

散点图 (Scatter Plot):探索变量间的关系

散点图主要用于观察两个数值型变量之间是否存在某种关系或相关性。

商业应用场景:

  • 分析广告投入与销售额之间的关系。
  • 研究用户年龄与月均消费金额的关联。
  • 在金融中,估算股票回报率与市场回报率的关系 (计算Beta)。

plt.scatter():散点图的基本语法

plt.scatter() 函数的用法与 plt.plot() 完全相同,只是它不会用线连接数据点,而是为每个 (X, Y) 对绘制一个标记。

案例:广告投入与销售额关系探索

我们用随机数模拟10个季度的广告投入和销售额数据。

import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 生成模拟数据
np.random.seed(42) # 固定随机种子以保证结果可复现
ad_spend = np.random.rand(10) * 100 # 广告投入 (0-100)
sales = ad_spend * 1.5 + np.random.randn(10) * 10 # 销售额,与投入正相关并加入随机噪声

# 绘制散点图
plt.scatter(ad_spend, sales)

# 添加标题和标签
plt.title('广告投入与销售额关系')
plt.xlabel('广告投入 (万元)')
plt.ylabel('销售额 (万元)')
plt.show()

散点图绘制结果

上图显示出一个明显的趋势:广告投入越高的点,其销售额也倾向于越高。

直方图 (Histogram):洞察数据的分布形态

直方图用于展示单个数值变量的分布情况。它能告诉我们数据的集中趋势、离散程度和分布形状(如是否对称)。

商业应用场景:

  • 了解目标客户群体的年龄构成。
  • 分析网站用户每次会话的停留时间分布。
  • 评估产品零件尺寸的质量控制情况。

plt.hist():直方图的基本语法

plt.hist() 函数最主要的参数是:

  • x: 包含数值型数据的列表或数组。
  • bins: 控制直方图的“箱子”数量。bins 越多,图形越精细,但也可能引入更多噪音。

案例:分析用户年龄分布

我们生成10000个服从正态分布的随机数来模拟用户年龄数据。

import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 随机生成10000个服从正态分布的数据 (均值为35, 标准差为10)
np.random.seed(0)
ages = np.random.randn(10000) * 10 + 35

# 绘制直方图
# bins: 箱子数量, edgecolor: 边框颜色
plt.hist(ages, bins=40, edgecolor='black')

# 添加标题和标签
plt.title('用户年龄分布直方图')
plt.xlabel('年龄')
plt.ylabel('频数 (用户数量)')
plt.show()

直方图绘制结果

从图中可见,用户年龄主要集中在35岁附近,呈现经典的钟形正态分布。

直方图:频数 vs 频率

默认情况下,直方图的Y轴显示的是频数(每个箱子里的数据点数量)。

有时我们更关心频率概率密度(即每个箱子数据点占总体的比例)。只需设置 density=True 即可。

此时Y轴的含义变为频率密度,整个直方图下方的总面积为1。

第二部分:Pandas 绘图接口的便捷之道

对于存储在 Pandas DataFrame 中的数据,直接使用其内置的绘图方法通常更方便。

Pandas:数据分析的核心容器

在实际工作中,我们的数据几乎总是存储在 Pandas 的 DataFrame 或 Series 对象中。

Pandas 提供了一套高级绘图接口,它在底层调用 Matplotlib,但语法更简洁,与数据操作流程结合更紧密。

从 DataFrame 直接绘图:.plot() 方法

可以直接在 DataFrame 或 Series 对象上调用 .plot() 方法。

通过 kind 参数,可以指定要绘制的图形类型。

  • df['column'].plot(kind='line') (默认)
  • df['column'].plot(kind='bar')
  • df.plot(kind='scatter', x='col1', y='col2')
  • df['column'].plot(kind='hist')

案例:使用 Pandas 绘制直方图

我们复用上一节生成的年龄数据,将其放入 DataFrame 中。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']

# 复用数据并创建DataFrame
np.random.seed(0)
ages = np.random.randn(10000) * 10 + 35
df = pd.DataFrame({'age': ages})

# 使用Pandas接口绘制直方图
df['age'].plot(kind='hist', bins=40, edgecolor='black', title='用户年龄分布 (Pandas)')
plt.xlabel('年龄')
plt.show()

Pandas 直方图绘制结果

代码更简洁,效果与直接使用 plt.hist() 完全相同。

案例:在同一坐标轴上混合绘图

Pandas 的接口使得在同一张图上绘制多列数据或多种图形变得非常容易。

重要技巧:解决 Matplotlib 中文乱码问题

Matplotlib 默认字体不支持中文,会导致中文显示为方块(乱码)。

一个一劳永逸的解决方案是在绘图代码开头加入以下“魔法代码”:

Listing 1
import matplotlib.pyplot as plt

# --- 解决中文乱码问题的标准代码 ---
plt.rcParams['font.sans-serif'] = ['SimHei']  # 指定默认字体为黑体
plt.rcParams['axes.unicode_minus'] = False  # 解决负号'-'显示为方块的问题
# ------------------------------------

'SimHei' 是黑体,也可替换为 'Microsoft YaHei' (微软雅黑) 等其他中文字体。

强烈建议将此代码片段作为你未来所有绘图脚本的“标准开头”

动手实践:模拟股票价格并可视化

背景: 使用 NumPy 模拟两只股票 (“A股” 和 “B股”) 连续100天的收盘价。

任务:

  1. 折线图: 绘制两只股票100天内的价格走势,进行比较。
  2. 直方图: 绘制两只股票价格的分布直方图,比较其波动范围和价格集中区域。

股票价格模拟与可视化:代码实现

代码解释:plt.close()alpha

  • alpha=0.7: 在绘制直方图时,我们设置了 alpha 参数。它代表透明度,取值范围从0 (完全透明) 到1 (完全不透明)。通过设置半透明,我们可以清晰地看到两个直方图重叠的部分,便于比较。

  • plt.close(): 在原始脚本中,可能会看到 plt.savefig() 后面跟着 plt.close()。这个命令用于关闭当前的图形窗口,是一个好习惯,尤其是在一个脚本中需要生成大量图片并保存到文件时,可以防止图形在内存中累积。

第三部分:Matplotlib 可视化实用技巧

掌握基础绘图后,我们来学习如何对图表进行“精装修”,让它们信息更完整、更专业。

添加文字说明:标题与坐标轴标签

一张没有文字说明的图表是“沉默”的,读者无法理解其含义。

  • plt.title(): 设置图形的总标题,概括图表核心内容。
  • plt.xlabel(): 设置 X 轴的标签,说明 X 轴代表什么。
  • plt.ylabel(): 设置 Y 轴的标签,说明 Y 轴代表什么。

案例:为图表添加完整文字说明

这张图的信息变得完整且自洽。

添加图例 (Legend):区分多组数据

当一张图上有多条数据线或多个数据系列时,图例是必不可少的。

图例就像地图上的符号说明,解释了每种颜色或样式的线条/标记代表什么。

添加图例的两步操作

  1. 在绘图函数 (如 plt.plot()) 中,通过 label 参数为每个数据系列命名
  2. 在所有绘图命令之后,调用 plt.legend() 函数,它会自动收集所有 label生成图例。

案例:带图例的多折线图

loc 参数:控制图例位置

plt.legend() 中的 loc 参数用于控制图例显示的位置。

常用选项包括:

  • 'upper left' (左上角)
  • 'upper right' (右上角)
  • 'lower left' (左下角)
  • 'lower right' (右下角)
  • 'best' (Matplotlib 自动选择一个遮挡数据最少的位置,是默认选项)

问题:如何比较量纲差异巨大的数据?

场景:比较一家公司的销售额 (可能数以亿计) 和其同比增长率 (通常是百分之几)。

如果将它们绘制在同一个 Y 轴上,会发生什么?

增长率的曲线会被“压”成一条近乎水平的直线,完全无法观察其波动。

解决方案:plt.twinx() 创建双坐标轴

plt.twinx() 函数可以在现有图形的基础上,创建一个共享 X 轴但拥有独立右侧 Y 轴的新坐标系。

这使得我们可以在同一张图上清晰地展示两个量纲不同的变量。

案例:使用双坐标轴展示销售额与增长率

双坐标轴图例的注意事项

使用 plt.twinx() 时,为两个坐标轴分别添加图例需要特别注意:

必须在每个坐标轴的绘图命令执行后,立刻调用各自的 legend() 方法。

不能在所有绘图命令都执行完毕后,才统一调用一次 plt.legend(),那样只会显示最后一个坐标轴的图例。

自定义图形外观:尺寸与刻度

设置图形大小

plt.figure(figsize=(width, height))

figsize 参数接收一个元组,单位是英寸。figsize=(8, 6) 通常适合报告,figsize=(10, 5) 适合宽屏展示。

旋转 X 轴刻度标签

plt.xticks(rotation=angle)

当 X 轴的标签很长(如日期、公司全称)时,它们会重叠。rotation=45rotation=90 可以有效解决此问题。

案例:调整尺寸并旋转标签

第四部分:绘制多图 (Subplots)

在数据分析报告中,我们常常需要将多个相关的图表并排展示以便于比较。

核心概念:Figure 与 Axes

理解 Matplotlib 的两个核心对象是掌握多图绘制的关键。

  • Figure (画布): 整个图形窗口,可以把它想象成一张画纸。
  • Axes (坐标系/子图): 画布上的一个绘图区域,一个 Figure 可以包含一个或多个 Axes。

我们之前使用的 plt.plot() 等函数,实际上是在当前 Figure 的默认 Axes上进行绘制。

图解:Figure 与 Axes 的关系

Matplotlib Figure and Axes Relationship A diagram showing a main Figure area containing two smaller Axes subplots. Figure (画布) Axes 1 (子图1) X-axis Y-axis Axes 2 (子图2) X-axis Y-axis

要精确控制多图布局,我们需要从“隐式”的 plt 调用转向“显式”地操作 Axes 对象。

方法一:plt.subplot() (传统方法)

plt.subplot(nrows, ncols, index)

  • 将画布划分为 nrowsncols 列的网格。
  • 激活第 index 个子图 (索引从1开始,从左到右,从上到下)。
  • 后续的 plt 命令将作用于这个被激活的子图。

这个方法比较繁琐,每次只能创建一个子图。

方法二:plt.subplots() (更推荐)

fig, axes = plt.subplots(nrows, ncols)

  • 一次性创建好整个网格的子图。
  • 返回一个 Figure 对象 fig
  • 返回一个包含所有 Axes 对象的 NumPy 数组 axes

这是现代 Matplotlib 编程中创建子图的首选方法,因为它更清晰、更强大。

案例:使用 plt.subplots() 创建2x2子图网格

import matplotlib.pyplot as plt
import numpy as np

# 一次性创建2行2列的子图网格
# figsize 控制整个 Figure 的大小
fig, axes = plt.subplots(2, 2, figsize=(10, 8))

# `axes` 是一个 2x2 的 NumPy 数组, `flatten()` 将其展平为一维
ax1, ax2, ax3, ax4 = axes.flatten()

# 在每个子图 (Axes) 上分别绘图
x = np.linspace(0, 10, 100)
ax1.plot(x, np.sin(x))         # 子图1: 折线图
ax2.bar(['A', 'B'], [3, 5])    # 子图2: 柱状图
ax3.scatter(x, np.cos(x))      # 子图3: 散点图
ax4.hist(np.random.randn(100)) # 子图4: 直方图

plt.show()

为子图添加标题和标签

当我们使用 axes 对象进行绘图时,添加标题和标签的方法与 plt. 的形式略有不同:

plt 语法 (作用于当前激活子图) axes 对象语法 (面向对象)
plt.title('...') ax.set_title('...')
plt.xlabel('...') ax.set_xlabel('...')
plt.ylabel('...') ax.set_ylabel('...')

使用 set_* 前缀的方法是面向对象接口的标志。

案例:为每个子图添加说明

动手实践:公司年度销售额与净利润比较

案例背景: 作为财务分析师,你需要制作一张图,清晰地比较公司在2019年和2020年每个季度的销售额与净利润。

任务:

  1. 创建画布: 创建一个包含两个子图(1行2列)的画布。
  2. 子图1 (销售额): 使用折线图比较两年四个季度的销售额,并用双坐标轴在右侧绘制同比增长率。
  3. 子图2 (净利润): 使用并列柱状图比较两年四个季度的净利润。
  4. 美化: 添加完整的标题、轴标签、图例。

财务分析图表:代码实现

这张信息丰富的组合图,是数据分析报告中的一个优秀范例。

第五部分:财务可视化案例实战

理论结合实践是最好的学习方式。我们将运用所学知识,对真实的财务数据进行分析。

财务分析的两个维度

  1. 时间序列分析
    • 目的: 追踪公司自身业绩随时间的变化。
    • 常用工具: 折线图。
  2. 横向截面分析
    • 目的: 在同一时间点,将公司与行业内竞争对手进行比较。
    • 常用工具: 柱状图。

时间序列可视化:分析公司ROE趋势

ROE (Return on Equity),即净资产收益率,是衡量公司盈利能力的核心指标。

我们首先创建一个模拟的公司2016-2019年的ROE数据。

ROE趋势图绘制

从图中可以看出,该公司ROE在2018年达到顶峰后,2019年有所回落。

同业统计可视化:进行横向对比分析

财务分析的另一个重要视角是同业比较,以评估公司在行业中的竞争地位。

我们创建一个包含中国部分白酒上市公司某年ROE的数据集,并进行可视化。

白酒行业ROE数据

白酒行业ROE对比图

柱状图清晰地展示了各家酒企的盈利能力差异,水井坊、山西汾酒、贵州茅台位居前列。

综合案例:贵州茅台总利润分析

任务: 使用 Tushare 金融数据接口,获取贵州茅台(600519.SH)自2021年以来的季度总利润数据,并用柱状图将其可视化。

Tushare 是一个免费的 Python 财经数据接口包,使用前需注册获取 token (API密钥)。

数据获取与处理代码

# 导入库
import tushare as ts
import matplotlib.pyplot as plt
import numpy as np

# 设置Tushare Pro接口token
# !!! 警告: 实际项目中不要硬编码token !!!
pro = ts.pro_api('你的Tushare Token')

# 获取数据
df = pro.income(ts_code='600519.SH', end_date='20240430', fields='ann_date,total_profit')

# 数据清洗与处理
df.drop_duplicates('ann_date', inplace=True)
df['ann_date'] = df['ann_date'].astype('int')
data_2021_2024 = df[df['ann_date'] >= 20210101]
data = data_2021_2024.copy()
data['ann_date'] = data['ann_date'].astype('object')
data = data.sort_values(by='ann_date')

贵州茅台总利润可视化

警告:API密钥安全与代码健壮性

1. API密钥 (Token) 安全

在示例代码中,token 被直接写入代码(硬编码)。这是极其危险的,会导致密钥泄露。正确的做法是:将密钥存储在环境变量或独立的配置文件中,在代码中读取。

2. 代码逻辑的健壮性

原始代码中使用 x = np.arange(10) 来生成X轴位置,这假设了数据永远是10条。更健壮的写法是 x = np.arange(len(data)),根据数据的实际长度动态生成X轴位置,确保图表在数据更新后依然能正确绘制。

重要提醒:为通过平台检测,在线练习时仍需按原始代码输入。但在未来的实践中,请务必采纳以上建议。

课程总结

今天,我们系统学习了 Matplotlib 的核心技能:

  • 四大基本图形plot, bar, scatter, hist,分别应对趋势、比较、关系、分布这四类分析场景。
  • Pandas接口:提供了更便捷的从 DataFrame 绘图的方式。
  • 专业化技巧:标题、图例、双坐标轴、子图布局等,是制作报告级图表的关键。
  • 实战演练:通过财务数据案例,我们将理论知识应用到了解决实际问题中。

下一步:继续探索

Matplotlib 功能非常强大,我们今天学习的只是冰山一角。

鼓励大家继续探索更高级的功能,例如:

  • Seaborn 库: 基于 Matplotlib,提供更美观的统计图形。
  • 3D绘图
  • 交互式图表 (如 plotly, bokeh)

数据可视化是数据分析师的画笔,请拿起它,让数据“开口说话”