欢迎来到《商业大数据分析与应用》的数据可视化章节。
在商业世界,原始数据是冰冷的、抽象的。我们的任务是赋予它们意义。
数据可视化,就是将数字转化为洞察的艺术与科学。
想象一下,你需要向公司董事会汇报季度业绩。哪种方式更有效?
方案A:表格
季度 | 销售额 (百万) |
---|---|
Q1 | 12.5 |
Q2 | 15.2 |
Q3 | 11.8 |
Q4 | 18.1 |
方案B:图表
图表能瞬间揭示趋势与异常,这是纯文本无法比拟的。
本章,我们将学习 Python 中最基础、最强大的可视化库——Matplotlib。
学完本章,你将能够:
Matplotlib 是 Python 生态中许多其他高级可视化库(如 Seaborn, Pandas plotting)的底层依赖。
掌握 Matplotlib,意味着你理解了 Python 可视化的基本原理。
pyplot
模块开始绘图前,我们需导入 matplotlib.pyplot
模块。
按照全球通行的惯例,我们将其重命名为 plt
,以简化代码。
这个 plt
对象,将是我们接下来执行所有绘图命令的入口。
plt
提供了四种对应核心图形的函数:
plt.plot()
: 折线图 (Line Chart) - 展示连续数据趋势。plt.bar()
: 柱状图 (Bar Chart) - 比较不同类别数据。plt.scatter()
: 散点图 (Scatter Plot) - 探索两个变量间的关系。plt.hist()
: 直方图 (Histogram) - 查看单个变量的分布。接下来,我们将逐一深入学习。
我们将从最基础的图形开始,一步步构建我们的可视化武器库。
折线图是观察数据随某一连续变量(通常是时间)变化的理想选择。
商业应用场景:
plt.plot()
:折线图的基本语法plt.plot()
函数最简单的用法是传入两个等长的列表或数组:
Matplotlib 会自动将这些 (X, Y) 点用直线连接起来。
假设我们有一周的用户数据,我们来将其可视化。
上图清晰地展示了周中用户活跃度上升,周末有所回落的趋势。
pylab
vs pyplot
在一些旧代码或教程中,你可能会看到 import pylab as pl
。
pylab
:将 pyplot
和 NumPy
功能打包在一起,方便交互式探索。pyplot
:只包含绘图功能。现代编程规范推荐:明确地分开导入,以增强代码可读性和避免命名冲突。
本课程将始终遵循这一现代规范。
如果数据点之间存在数学关系(如 \(y = x^2\)),使用 Python 列表进行计算会很繁琐。
NumPy (Numerical Python) 库是科学计算的基石,其核心是 array
对象,它支持向量化运算,让数据处理更高效简洁。
我们来绘制 \(y = x+1\) 和 \(y = x^2\) 两条函数曲线。
在上例中,我们为第二条线添加了额外的参数:
color='red'
: 设置线条颜色。linewidth=3
: 设置线条宽度,单位是像素。linestyle='--'
: 设置线条样式为虚线。常见的还有 '-'
(实线), ':'
(点线), '-.'
(点划线)。这些参数是增强图表可读性的重要工具。
当需要比较不同类别的数据大小时,柱状图是最佳选择。
商业应用场景:
plt.bar()
:柱状图的基本语法plt.bar()
函数的用法与 plt.plot()
类似:
假设我们需要展示三个产品线(A, B, C)的销售额。
从图中可以直观地看出,产品线 B 的销售额最高。
散点图主要用于观察两个数值型变量之间是否存在某种关系或相关性。
商业应用场景:
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()
上图显示出一个明显的趋势:广告投入越高的点,其销售额也倾向于越高。
直方图用于展示单个数值变量的分布情况。它能告诉我们数据的集中趋势、离散程度和分布形状(如是否对称)。
商业应用场景:
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岁附近,呈现经典的钟形正态分布。
默认情况下,直方图的Y轴显示的是频数(每个箱子里的数据点数量)。
有时我们更关心频率或概率密度(即每个箱子数据点占总体的比例)。只需设置 density=True
即可。
此时Y轴的含义变为频率密度,整个直方图下方的总面积为1。
对于存储在 Pandas DataFrame 中的数据,直接使用其内置的绘图方法通常更方便。
在实际工作中,我们的数据几乎总是存储在 Pandas 的 DataFrame 或 Series 对象中。
Pandas 提供了一套高级绘图接口,它在底层调用 Matplotlib,但语法更简洁,与数据操作流程结合更紧密。
.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')
我们复用上一节生成的年龄数据,将其放入 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()
代码更简洁,效果与直接使用 plt.hist()
完全相同。
Pandas 的接口使得在同一张图上绘制多列数据或多种图形变得非常容易。
Matplotlib 默认字体不支持中文,会导致中文显示为方块(乱码)。
一个一劳永逸的解决方案是在绘图代码开头加入以下“魔法代码”:
'SimHei'
是黑体,也可替换为 'Microsoft YaHei'
(微软雅黑) 等其他中文字体。
强烈建议将此代码片段作为你未来所有绘图脚本的“标准开头”。
背景: 使用 NumPy 模拟两只股票 (“A股” 和 “B股”) 连续100天的收盘价。
任务:
plt.close()
与 alpha
alpha=0.7
: 在绘制直方图时,我们设置了 alpha
参数。它代表透明度,取值范围从0 (完全透明) 到1 (完全不透明)。通过设置半透明,我们可以清晰地看到两个直方图重叠的部分,便于比较。
plt.close()
: 在原始脚本中,可能会看到 plt.savefig()
后面跟着 plt.close()
。这个命令用于关闭当前的图形窗口,是一个好习惯,尤其是在一个脚本中需要生成大量图片并保存到文件时,可以防止图形在内存中累积。
掌握基础绘图后,我们来学习如何对图表进行“精装修”,让它们信息更完整、更专业。
一张没有文字说明的图表是“沉默”的,读者无法理解其含义。
plt.title()
: 设置图形的总标题,概括图表核心内容。plt.xlabel()
: 设置 X 轴的标签,说明 X 轴代表什么。plt.ylabel()
: 设置 Y 轴的标签,说明 Y 轴代表什么。这张图的信息变得完整且自洽。
当一张图上有多条数据线或多个数据系列时,图例是必不可少的。
图例就像地图上的符号说明,解释了每种颜色或样式的线条/标记代表什么。
plt.plot()
) 中,通过 label
参数为每个数据系列命名。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)
适合宽屏展示。
plt.xticks(rotation=angle)
当 X 轴的标签很长(如日期、公司全称)时,它们会重叠。rotation=45
或 rotation=90
可以有效解决此问题。
在数据分析报告中,我们常常需要将多个相关的图表并排展示以便于比较。
理解 Matplotlib 的两个核心对象是掌握多图绘制的关键。
我们之前使用的 plt.plot()
等函数,实际上是在当前 Figure 的默认 Axes上进行绘制。
要精确控制多图布局,我们需要从“隐式”的 plt
调用转向“显式”地操作 Axes
对象。
plt.subplot()
(传统方法)plt.subplot(nrows, ncols, index)
nrows
行 ncols
列的网格。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年每个季度的销售额与净利润。
任务:
这张信息丰富的组合图,是数据分析报告中的一个优秀范例。
理论结合实践是最好的学习方式。我们将运用所学知识,对真实的财务数据进行分析。
ROE (Return on Equity),即净资产收益率,是衡量公司盈利能力的核心指标。
我们首先创建一个模拟的公司2016-2019年的ROE数据。
从图中可以看出,该公司ROE在2018年达到顶峰后,2019年有所回落。
财务分析的另一个重要视角是同业比较,以评估公司在行业中的竞争地位。
我们创建一个包含中国部分白酒上市公司某年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')
在示例代码中,token
被直接写入代码(硬编码)。这是极其危险的,会导致密钥泄露。正确的做法是:将密钥存储在环境变量或独立的配置文件中,在代码中读取。
原始代码中使用 x = np.arange(10)
来生成X轴位置,这假设了数据永远是10条。更健壮的写法是 x = np.arange(len(data))
,根据数据的实际长度动态生成X轴位置,确保图表在数据更新后依然能正确绘制。
重要提醒:为通过平台检测,在线练习时仍需按原始代码输入。但在未来的实践中,请务必采纳以上建议。
今天,我们系统学习了 Matplotlib 的核心技能:
plot
, bar
, scatter
, hist
,分别应对趋势、比较、关系、分布这四类分析场景。Matplotlib 功能非常强大,我们今天学习的只是冰山一角。
鼓励大家继续探索更高级的功能,例如:
plotly
, bokeh
)数据可视化是数据分析师的画笔,请拿起它,让数据“开口说话”。