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

各位同学,欢迎来到《商业大数据分析与应用》的数据可视化章节。在商业世界中,单纯的数字和表格往往是冰冷且难以解读的。无论是向管理层汇报季度业绩,向客户展示产品价值,还是分析市场调研数据,将数据转化为直观的图形,都能极大地提升沟通效率和决策质量。这便是数据可视化的力量。

本章我们将学习 Python 中最基础、最强大的数据可视化库——Matplotlib。我们将从绘制最常见的四种基本图形(折线图、柱状图、散点图和直方图)入手,然后学习一系列让图形更专业、信息更丰富的实用技巧,最后通过一个真实的财务数据分析案例,将所学知识融会贯通。

5.1 Matplotlib 基础图形绘制

在数据分析的实践中,图形能够帮助我们更直观、清晰地洞察数据背后的关系和趋势,例如公司利润的变化、股票价格的波动、不同产品线的销售额对比等。Matplotlib 库正是实现这一切的得力工具。

知识点

在开始绘图前,我们首先需要导入 matplotlib 库中的 pyplot 模块。pyplot 模块汇集了大量绘图函数,能让我们轻松创建和定制图形。按照惯例,我们通常会给它一个别名 plt,以简化后续代码的编写。

import matplotlib.pyplot as plt

导入后,我们就可以通过 plt 调用各种函数来生成图表:

  • plt.plot(): 创建折线图,常用于展示数据随时间变化的趋势。
  • plt.bar(): 创建柱状图(或条形图),常用于比较不同类别的数据。
  • plt.scatter(): 创建散点图,用于探索两个数值变量之间的关系。
  • plt.hist(): 创建直方图,用于观察单个数值变量的分布情况。

接下来,我们将逐一学习这四种基本图形的绘制方法。

5.1.1 折线图 (Line Chart)

折线图是观察数据连续变化的理想选择。想象一下,作为一名运营分析师,你需要追踪一款 App 过去一周的日活跃用户(DAU)数量,折线图可以清晰地展示用户量的增减趋势。

我们可以使用 plt.plot() 函数来绘制折线图。它最基本的用法是传入两个列表或数组,分别代表 X 轴和 Y 轴的数据点。

import matplotlib.pyplot as plt
x = [1,2,3,4]
y = [2,3,4,5]
plt.plot(x, y) # 绘制折线图
import pylab as pl
pl.xticks(rotation=45)
plt.show() # 展示图形
图 5.1: 一个简单的折线图
教学提示:pylabpyplot

在上面的代码 图 fig-simple-line-chart 中,我们看到了 import pylab as pl 的用法。pylab 是 Matplotlib 的一个模块,它将 pyplot 的绘图功能和 NumPy 的数学计算功能捆绑在一起,对于交互式数据探索很方便。

然而,在现代 Python 开发和严谨的学术项目中,更推荐的做法是分别导入 matplotlib.pyplotnumpy,即:

import matplotlib.pyplot as plt
import numpy as np

这样做可以使代码的依赖关系更清晰,避免命名冲突。在后续的课程中,我们将遵循这一规范。

如果数据点之间存在某种数学关系,使用 Python 的列表进行批量计算会比较繁琐。这时,NumPy 库就派上了用场。NumPy 的数组(array)支持向量化运算,让数据处理变得异常高效。

下面的例子展示了如何使用 NumPy 数组来计算函数值,并在同一张图上绘制两条不同样式的折线。

import numpy as np
import matplotlib.pyplot as plt

x1 = np.array()

# 第一条线:y = x + 1
y1 = x1 + 1
plt.plot(x1, y1) # 使用默认参数画图

# 第二条线:y = x*2
y2 = x1*2
# color设置颜色,linewidth设置线宽,单位像素,linestyle默认为实线,“--”表示虚线
plt.plot(x1, y2, color='red', linewidth=3, linestyle='--')

plt.show()
图 5.2

图 fig-numpy-line-chart 中,我们不仅绘制了两条线,还通过 color, linewidth, linestyle 等参数定制了第二条线的外观,使其更加醒目。这些参数是数据可视化中常用的“化妆品”,能够极大地增强图表的可读性。

5.1.2 柱状图 (Bar Chart)

当我们需要比较不同类别的数据大小时,柱状图是最佳选择。例如,作为一名销售总监,你可能需要比较公司旗下不同产品线在上一季度的销售收入。

使用 plt.bar() 函数可以轻松绘制柱状图。

import matplotlib.pyplot as plt
x =
y =
plt.bar(x, y)
plt.show()
图 5.3

5.1.3 散点图 (Scatter Plot)

散点图主要用于观察两个数值变量之间的关系或相关性。在金融领域,一个经典的例子是分析单个股票的回报率与整个市场回报率(如沪深300指数)之间的关系,以估算该股票的 Beta 系数。

我们可以通过 plt.scatter() 绘制散点图。下面的例子中,我们使用 numpy.random.rand() 生成两组0到1之间的随机数作为示例数据。

import matplotlib.pyplot as plt
import numpy as np

x = np.random.rand(10)
y = np.random.rand(10)
plt.scatter(x, y)
plt.show()
图 5.4: 一个简单的散点图

5.1.4 直方图 (Histogram)

直方图用于展示单个数值变量的分布情况。它将数据划分成若干个等宽的区间(称为“箱子”或 bins),然后统计每个区间内数据点的数量(频数)或比例(频率)。例如,一位市场研究员可以通过绘制消费者年龄的直方图,来了解目标客户群体的年龄构成。

使用 plt.hist() 可以绘制直方图。让我们生成10000个服从标准正态分布(均值为0,标准差为1)的随机数,并观察它们的分布。

import matplotlib.pyplot as plt
import numpy as np  

# 随机生成10000个服从正态分布的数据
data = np.random.randn(10000)

# 绘制直方图,bins为颗粒度,即直方图的长条形数目,edgecolor为长条形边框颜色
plt.hist(data, bins=40, edgecolor='black')

plt.show()
图 5.5: 频数直方图

图 fig-simple-histogram 中,我们可以清晰地看到数据点主要集中在0附近,呈现出经典的钟形曲线,这与正态分布的特征完全吻合。bins 参数控制了直方图的精细程度,bins 越多,图形越精细,但可能引入更多噪音;bins 越少,图形越平滑,但可能丢失细节。

此外,如果想显示频率而非频数,只需在 plt.hist() 函数中设置参数 density=True 即可。

5.1.5 Pandas 绘图接口的便捷之道

虽然 Matplotlib 功能强大,但对于存储在 Pandas DataFrame 中的数据,Pandas 自身提供了一套更简洁的绘图接口。其底层仍然是调用 Matplotlib,但语法上更加方便。

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

# 这种写法只适合pandas中的DataFrame,不能直接用于Numpy的数组
import pandas as pd
# 我们复用上一节生成的 data 数组
data = np.random.randn(10000)
df = pd.DataFrame(data)  # 将绘制直方图中的data数组转换成DataFrame()格式
df.hist(bins=40, edgecolor='black')
array([[<AxesSubplot: title={'center': '0'}>]], dtype=object)
(a)
(b)
图 5.6

除了专门的 .hist() 方法,Pandas 还有一个通用的 .plot() 方法,通过 kind 参数可以指定绘图类型。

import pandas as pd
data = np.random.randn(10000)
df = pd.DataFrame(data)
df.plot(kind='hist')
图 5.7: 使用 Pandas 的 .plot(kind=…) 方法绘制直方图

当 DataFrame 包含多列数据时,这种便捷性体现得更加明显。我们可以轻松地对特定列进行绘图,甚至在同一张图上绘制多种图形。

import pandas as pd
import matplotlib.pyplot as plt
df = pd.DataFrame([,,], columns=['人均收入', '人均支出'], index=['北京', '上海', '广州'])

# 此时可以通过pandas同时绘制折线图或者柱状图,代码如下:
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 解决负号'-'显示为方块的问题

df['人均收入'].plot(kind='line')  # kind=line绘制折线图,不设置则默认折线图
df['人均收入'].plot(kind='bar')  # kind=bar绘制柱状图
图 5.8

图 fig-pandas-multi-plot 中,我们为“人均收入”这一列数据同时绘制了折线图和柱状图。如果不指定 kind 参数,即 df['人均收入'].plot(),则默认绘制折线图。

解决中文乱码问题

在进行数据可视化时,尤其是在处理中文标签或标题时,经常会遇到中文显示为方块(乱码)的问题。这是因为 Matplotlib 的默认字体不支持中文字符。

一个固定的解决方案是在代码的开头加入以下配置代码:

列表 5.1
import matplotlib.pyplot as plt # 下面这几行代码用于解决中文乱码问题
plt.rcParams['font.sans-serif'] = ['SimHei']  # 指定默认字体为黑体
plt.rcParams['axes.unicode_minus'] = False  # 解决负号'-'显示为方块的问题

'SimHei' 是黑体的名称,您也可以替换为系统中安装的其他中文字体,如 'Microsoft YaHei' (微软雅黑) 等。第二行代码 axes.unicode_minus 是为了在更改字体后能正常显示负号。建议将 列表 lst-chinese-font-config 这段代码作为您未来所有绘图脚本的“标准开头”。

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

现在,让我们通过一个模拟的金融场景来巩固所学。

背景: 假设我们使用 NumPy 库模拟了两只股票(“Stock A” 和 “Stock B”)连续100天的收盘价,并希望通过可视化来比较它们的走势和价格分布。

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

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

# 创建金融数据
stock_a = np.random.normal(loc=50, scale=10, size=100) # 股票A每日收盘价
stock_b = np.random.normal(loc=40, scale=10, size=100) # 股票B每日收盘价
months = np.arange(100) + 1 # 月份

# 绘制折线图
plt.plot(months, stock_a, label="Stock A")
plt.plot(months, stock_b, label="Stock B")
plt.xlabel("Month")
plt.ylabel("Price ($)")
plt.title("Stock Prices Over Time")
plt.legend()
plt.savefig("1.png")
plt.close()

# 绘制直方图
plt.hist(stock_a, bins=20, label="Stock A")
plt.hist(stock_b, bins=20, label="Stock B", alpha=0.5)
plt.xlabel("Price ($)")
plt.ylabel("Frequency")
plt.title("Distribution of Stock Prices")
plt.legend()
plt.savefig("2.png")
图 5.9: 模拟股票价格的折线图与直方图
代码解释

在上述 图 fig-stock-simulation 代码中: - plt.close(): 这个命令用于关闭当前的图形窗口。在脚本连续生成多张图并保存到文件时,这是一个好习惯,可以防止图形在内存中累积或在屏幕上意外显示。 - alpha=0.5: 在绘制股票B的直方图时,我们设置了 alpha 参数。它代表透明度,取值范围从0(完全透明)到1(完全不透明)。通过设置半透明,我们可以清晰地看到两个直方图重叠的部分。

5.2 Matplotlib 可视化实用技巧

掌握了基本图形的绘制方法后,我们还需要学习如何对图表进行“精装修”,使其信息更完整、更具专业性。本节将介绍一系列实用技巧,包括添加文字说明、图例、设置双坐标轴、自定义图形外观以及在同一画布上绘制多个子图等。

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

一张没有标题和坐标轴标签的图表就像一篇没有标题和段落的文章,读者很难理解其核心内容。 - plt.title(): 设置图形的总标题。 - plt.xlabel(): 设置 X 轴的标签。 - plt.ylabel(): 设置 Y 轴的标签。

import matplotlib.pyplot as plt

x =
y =
plt.plot(x, y)
plt.title('TITLE')  # 添加标题
plt.xlabel('X')  # 添加X轴标签
plt.ylabel('Y')  # 添加Y轴标签
plt.show()  # 显示图片
图 5.10

5.2.2 添加图例 (Legend)

当一张图上有多条数据线或多个数据系列时,图例是必不可少的。它像地图上的图例一样,解释了每种颜色或样式的线条/标记代表什么。

要添加图例,需要两步: 1. 在绘图函数(如 plt.plot())中,通过 label 参数为每个数据系列命名。 2. 调用 plt.legend() 函数,它会自动收集所有 label 并生成图例。

import numpy as np
import matplotlib.pyplot as plt

# 第一条线, 设定标签lable为y = x + 1
x1 = np.array()
y1 = x1 + 1
plt.plot(x1, y1, label='y = x + 1') 

# 第二条线, 设定标签lable为y = x*2
y2 = x1*2
plt.plot(x1, y2, color='red', linestyle='--', label='y = x*2')

plt.legend(loc='upper left') # 图例位置设置为左上角
plt.show()
图 5.11

plt.legend() 中的 loc 参数用于控制图例的位置,'upper left' 表示左上角,其他常用选项还包括 'upper right' (右上角), 'lower left' (左下角), 'lower right' (右下角), 和 'best' (自动选择最佳位置)。

5.2.3 设置双坐标轴 (Twin Axes)

在商业分析中,我们常常需要比较两个量纲(单位或数值范围)差异巨大的变量。例如,一家公司的销售额(可能数以亿计)和其同比增长率(通常是百分之几)。如果将它们绘制在同一个 Y 轴上,增长率的曲线会被“压”成一条近乎水平的直线,完全无法观察其波动。

plt.twinx() 函数就是解决这个问题的利器。它可以在现有图形的基础上,创建一个共享 X 轴但拥有独立 Y 轴的新坐标系。

import numpy as np
import matplotlib.pyplot as plt

# 第一条线, 设定标签lable为y = x
x1 = np.array()
y1 = x1
plt.plot(x1, y1, color='red', linestyle='--', label='y = x')
plt.legend(loc='upper left')  # 该图图例设置在左上角

plt.twinx()  # 设置双坐标轴

# 第二条线, 设定标签lable为y = x^2
y2 = x1*x1
plt.plot(x1, y2, label='y = x^2') 
plt.legend(loc='upper right')  # 改图图例设置在右上角

plt.show()
图 5.12

图 fig-twin-axes 所示,左侧 Y 轴对应红色的虚线 y=x,右侧 Y 轴对应蓝色的实线 y=x^2。通过双坐标轴,两条曲线都能得到充分的展示。

双坐标轴图例的注意事项

在使用 plt.twinx() 时,为两个坐标轴分别添加图例需要特别注意:必须在每个坐标轴的绘图命令执行后,立刻调用 plt.legend()。不能在所有绘图命令都执行完毕后,才统一调用一次 plt.legend(),那样只会显示最后一个坐标轴的图例。

5.2.4 自定义图形外观

5.2.4.1 设置图形大小

默认的图形尺寸可能不适合报告或演示文稿。我们可以使用 plt.figure(figsize=(width, height)) 来创建一个指定尺寸的“画布”。尺寸单位是英寸,figsize=(8, 6) 通常对应 800x600 像素的图像。

import matplotlib.pyplot as plt
plt.figure(figsize=(8,6))
x =
y =
plt.plot(x, y)
plt.show()  # 显示图片
图 5.13

5.2.4.2 旋转 X 轴刻度标签

当 X 轴的刻度标签很长(例如,完整的日期或类别名称)时,它们可能会重叠在一起,难以辨认。通过旋转标签可以有效地解决这个问题。

import matplotlib.pyplot as plt
x =
y =
plt.plot(x, y)  # 绘制折线图

import pylab as pl
pl.xticks(rotation=45)  # 设置角度为45度

plt.show()  # 展示图形
图 5.14

图 fig-rotated-ticks 中,我们再次看到了 pylab 的使用。同样,更规范的写法是 plt.xticks(rotation=45)

5.2.4.3 中文显示配置

我们再次强调中文显示问题的解决方案,因为它在实际应用中极为常见。

import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 解决负号'-'显示为方块的问题

x =
y =
plt.plot(x, y)
plt.title('中文标题')  # 添加标题
plt.xlabel('中文X轴')  # 添加X轴标签
plt.ylabel('中文Y轴')  # 添加Y轴标签
plt.show()  # 显示图片
图 5.15

5.2.5 绘制多图 (Subplots)

在数据分析报告中,我们常常需要将多个相关的图表并排或堆叠展示,以便于比较。Matplotlib 提供了强大的子图(Subplot)功能来实现这一点。

在 Matplotlib 中,有两个核心概念: - Figure (画布): 整个图形窗口,可以把它想象成一张画纸。 - Axes (坐标系/子图): 画布上的一个绘图区域,一个 Figure 可以包含一个或多个 Axes。我们之前使用的 plt.plot() 等函数,实际上是在当前 Figure 的默认 Axes 上进行绘制。

5.2.5.1 方法一:plt.subplot()

plt.subplot(nrows, ncols, index) 是创建子图的传统方法。它将画布划分为 nrowsncols 列的网格,并激活第 index 个子图(索引从1开始,从左到右,从上到下)。

# 演示代码如下:
import matplotlib.pyplot as plt
# 绘制第一个子图:折线图
ax1 = plt.subplot(221)  
plt.plot(,)  # 这里plt其实也可以换成ax1

# 绘制第二个子图:柱状图
ax2 = plt.subplot(222)  
plt.bar(,)

# 绘制第三个子图:散点图
ax3 = plt.subplot(223)  
plt.scatter(,)

# 绘制第四个子图:直方图
ax4 = plt.subplot(224)  
plt.hist()
图 5.16

我们也可以创建多个独立的 Figure 画布。

import matplotlib.pyplot as plt
plt.figure(figsize=(8,4)) # 设置画布大小

plt.figure(1)  # 激活或创建第一张画布
ax1 = plt.subplot(121)  # 第一张画布的第一个子图
plt.plot(,)  # 这里的plt可以换成ax1

ax2 = plt.subplot(122)  # 第一张画布的第二个子图
plt.plot(,)

plt.figure(2)  # 激活或创建第二张画布
plt.plot(,)
图 5.17

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

plt.subplot() 每次只能创建一个子图,如果需要创建多个,需要多次调用。plt.subplots(nrows, ncols) 函数则更加高效,它一次性创建好整个网格的子图,并返回一个 Figure 对象和一个包含所有 Axes 对象的 NumPy 数组。这是目前更推荐的方法。

import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, figsize=(10, 8)) 
ax1, ax2, ax3, ax4 = axes.flatten()
ax1.plot(,)  # 绘制第一个子图
ax2.bar(,)  # 绘制第二个子图
ax3.scatter(,)  # 绘制第三个子图
ax4.hist()  # 绘制第四个子图
图 5.18

图 fig-subplots-demo 中,axes 是一个 2x2 的 NumPy 数组,axes.flatten() 将其展平为一维数组,便于我们通过 ax1, ax2, ... 的方式解包赋值,从而方便地对每个子图进行操作。

5.2.5.3 对子图设置标题和标签

当我们使用 plt.subplots() 创建的 axes 对象进行绘图时,添加标题和标签的方法与 plt. 的形式略有不同,需要使用 set_ 前缀的方法: - ax.set_title() - ax.set_xlabel() - ax.set_ylabel()

import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
fig, axes = plt.subplots(2, 2, figsize=(10, 8)) 
ax1, ax2, ax3, ax4 = axes.flatten()
ax1.plot(,)  # 绘制第一个子图
ax1.set_title('子图1')
ax1.set_xlabel('日期')
ax1.set_ylabel('分数')
ax2.bar(,)  # 绘制第二个子图
ax3.scatter(,)  # 绘制第三个子图
ax4.hist()  # 绘制第四个子图
图 5.19

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

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

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

import numpy as np
import matplotlib.pyplot as plt

# 读入数据
sales_2019 =
sales_2020 =
income_2019 =
income_2020 =
index = ['Q1', 'Q2', 'Q3', 'Q4']

# 创建画布
fig, axes = plt.subplots(1, 2, figsize=(12, 6)) 
ax1, ax2 = axes.flatten()

# 子图1
# 绘制销售额折线图
ax1.plot(index, sales_2019, marker='o', label='2019')
ax1.plot(index, sales_2020, marker='s', label='2020')

# 添加文字说明
ax1.set_title('Sales Compare')
ax1.set_xlabel('Quarter')
ax1.set_ylabel('Sales (million yuan)')

# 添加图例
ax1.legend(loc='upper left')

# 双坐标轴
ax1_2 = ax1.twinx()
ax1_2.plot(index, np.array(sales_2020) / np.array(sales_2019), 'g-', label='growth rate')
ax1_2.set_ylabel('Growth Rate')
ax1_2.legend(loc= 'upper right')


# 子图2
ax2.bar(index, income_2019, width=0.3,color='r',alpha=0.6, label='2019')
ax2.bar(index, income_2020, width= 0.3,color='g', alpha=0.6, label='2020')
ax2.set_title('Profit Compare')
ax2.set_xlabel('Quarter')
ax2.set_ylabel('Net Profit(million yuan)')
ax2.legend(loc='upper left')

# 设置X轴角度
plt.xticks(rotation=45)

# 调整子图之间的距离
plt.subplots_adjust(wspace=0.3)

# 显示图形
plt.savefig("1.png")
图 5.20

这张信息丰富的组合图 图 fig-sales-profit-comparison ,清晰地展示了公司业绩的多个维度,是数据分析报告中的一个优秀范例。

5.3 财务可视化案例实战

理论结合实践是最好的学习方式。在本节中,我们将运用前面学到的可视化知识,对真实的财务数据进行分析。财务数据分析主要涉及两大维度:时间序列分析(追踪公司自身业绩随时间的变化)和横向截面分析(在同一时间点上,将公司与行业内其他竞争对手进行比较)。这两种分析场景分别对应我们已经学过的折线图和柱状图。

5.3.1 时间序列可视化:分析公司业绩趋势

对于描述财务指标(如净资产收益率ROE、营业收入、净利润等)趋势的可视化任务,折线图是首选。

我们首先创建一个简单的 Pandas DataFrame,模拟一家公司2016年至2019年的净资产收益率(ROE)数据。

import pandas as pd
data = pd.DataFrame([[0.2362], [0.3021], [0.3222],[0.3099]], columns=['roe'])
data
表 5.1: 模拟的年度ROE数据
roe
0 0.2362
1 0.3021
2 0.3222
3 0.3099

接下来,我们使用 Matplotlib 将 表 tbl-roe-timeseries 中的数据绘制成折线图。

import matplotlib.pyplot as plt
plt.rcParams['axes.unicode_minus'] = False  # 解决负号'-'显示为方块的问题
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
years = ['2016','2017','2018','2019']
plt.plot(years,data['roe'],label='净资产收益率(roe)')
plt.legend(loc = 'upper left')
plt.show()
图 5.21: 2016-2019年公司净资产收益率(ROE)趋势图

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

财务分析的另一个重要视角是同业比较。通过将公司的关键财务指标与竞争对手进行对比,可以评估其在行业中的竞争地位。柱状图是进行此类比较的理想工具。

我们创建一个包含中国部分白酒上市公司某年ROE的数据集,并按ROE从高到低进行排序。

import pandas as pd
data = pd.DataFrame([['泸州老窖',0.257],['古井贡酒',0.1769],['酒鬼酒',0.1721],['五粮液',0.2383],['顺鑫农业',0.0549],['皇台酒业',0.2503],['洋河股份',0.1946],['青青稞酒',-0.056],['伊力特',0.0946],['金种子酒',0.0243],['贵州茅台',0.2953],['老白干酒',0.0902],['舍得酒业',0.1606],['水井坊',0.3424],['山西汾酒',0.3096],['迎驾贡酒',0.1854],['今世缘',0.1895],['口子窖',0.1762],['金徽酒',0.1193]], columns=['name','roe'])
data = data.sort_values(by = 'roe',ascending = False)
data
表 5.2: 中国白酒行业部分上市公司ROE对比
name roe
13 水井坊 0.3424
14 山西汾酒 0.3096
10 贵州茅台 0.2953
0 泸州老窖 0.2570
5 皇台酒业 0.2503
3 五粮液 0.2383
6 洋河股份 0.1946
16 今世缘 0.1895
15 迎驾贡酒 0.1854
1 古井贡酒 0.1769
17 口子窖 0.1762
2 酒鬼酒 0.1721
12 舍得酒业 0.1606
18 金徽酒 0.1193
8 伊力特 0.0946
11 老白干酒 0.0902
4 顺鑫农业 0.0549
9 金种子酒 0.0243
7 青青稞酒 -0.0560

然后,我们用柱状图将 表 tbl-liquor-roe 的数据可视化。

import matplotlib.pyplot as plt
plt.figure(figsize=(10, 6))
plt.bar(data['name'],data['roe'],label='净资产收益率(roe)')
plt.legend(loc = 'upper right')
plt.xticks(data['name'],rotation=45)
plt.show()
图 5.22: 中国白酒行业部分上市公司ROE横向对比

图 fig-liquor-roe-bar 这张图中,我们可以一目了然地看出各家酒企的盈利能力差异。

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

现在,让我们进入一个更真实的综合案例。我们将使用 Tushare 金融数据接口,获取A股“股王”贵州茅台(600519.SH)自2021年以来的季度总利润数据,并用柱状图将其可视化。

Tushare 是一个免费、开源的 Python 财经数据接口包,可以方便地获取股票、基金、期货、宏观经济等各类数据。使用前需要先注册获取 token (API密钥)。

案例要求: 1. 使用 Tushare pro.income 接口获取贵州茅台的利润表数据。 2. 筛选出2021年1月1日至今的数据。 3. 提取公告日期 (ann_date) 和总利润 (total_profit) 两列。 4. 处理可能存在的重复数据,并按日期排序。 5. 绘制柱状图展示每个报告期的总利润。

import tushare as ts
import matplotlib.pyplot as plt
import numpy as np

pro = ts.pro_api('ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474')

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")
x = np.arange(10)
fig,ax = plt.subplots(figsize=(16,8))
r1 = ax.bar(x,data["total_profit"],color="#1d86ae")
ax.bar_label(r1,padding=3,fontsize=10)
ax.set_xticks(ticks=np.arange(10))
ax.set_xticklabels(labels=data["ann_date"])
plt.savefig("1.png")
图 5.23
API密钥安全与代码健壮性提示

5.3.3.1 API密钥 (Token) 安全

图 fig-moutai-profit 的代码中,Tushare 的 token 是直接写在代码里的(硬编码)。这在教学演示中尚可接受,但在实际项目或公开代码中是极其危险的行为,会导致您的密钥泄露。正确的做法是: - 将密钥存储在环境变量或独立的配置文件中。 - 在代码中读取这些外部配置,而不是直接写入代码。

5.3.3.2 代码逻辑的健壮性

代码中使用了 x = np.arange(10) 来生成X轴的位置。这暗含了一个假设:我们获取到的数据不多不少正好是10条。但实际上,从2021年Q1到2024年Q1,应该有13个季度的财报数据。如果数据源更新,或者筛选条件改变,数据的条数很可能会变化,导致 arange(10) 与实际数据长度不匹配,进而引发绘图错误或标签错位。

一个更健壮的写法是根据数据的实际长度来动态生成X轴位置:

# 建议的修改
data = data.sort_values(by="ann_date").reset_index(drop=True) # 排序并重置索引
x = np.arange(len(data)) # 根据数据实际长度生成 x
...
r1 = ax.bar(x, data["total_profit"], color="#1d86ae")
...
ax.set_xticks(ticks=x) # 使用 x 设置刻度位置
ax.set_xticklabels(labels=data["ann_date"])

这种写法确保了无论获取到多少条数据,图表都能正确绘制。

5.3.3.3 重要提醒

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

通过本章的学习,我们不仅掌握了 Matplotlib 的核心绘图技能,更重要的是学会了如何根据不同的商业分析场景选择合适的图表类型,并通过各种定制化技巧,让数据“开口说话”。