Skip to content

常见图形绘制

Matplotlib 能够绘制折线图、散点图、柱状图、直方图、饼图。

我们需要知道不同的统计图的意义,以此来决定选择哪种统计图来呈现我们的数据。

折线图

以折线的上升或下降来表示统计数量的增减变化的统计图

特点:能够显示数据的变化趋势,反映事物的变化情况。(变化)

api:plt.plot(x, y)

柱状图(条形图)

排列在工作表的列或行中的数据可以绘制到柱状图中。

特点:绘制连离散的数据,能够一眼看出各个数据的大小,比较数据之间的差别。(统计/对比)

api:plt.bar(x, width, align='center', **kwargs)

应用场景:数量统计,频率统计。

条形图的绘制方式跟折线图非常的类似,只不过是换成了plt.bar方法。plt.bar方法有以下常用参数:

  1. x:一个数组或者列表,代表需要绘制的条形图的 x 轴的坐标点。
  2. height:一个数组或者列表,代表需要绘制的条形图 y 轴的坐标点。
  3. width:每一个条形图的宽度,默认是 0.8 的宽度。
  4. bottomy轴的基线,默认是 0,也就是距离底部为 0.
  5. align:对齐方式,默认是center,也就是跟指定的x坐标居中对齐,还有为edge,靠边对齐,具体靠右边还是靠左边,看width 的正负。
  6. color:条形图的颜色。

返回值为 BarContainer,是一个存储了条形图的容器,而条形图实际上的类型是 matplotlib.patches.Rectangle 对象。

更多参考:https://matplotlib.org/api/_as_gen/matplotlib.pyplot.bar.html

条形图的绘制

比如现在有2019年贺岁片票房的数据

数据来源:https://piaofang.maoyan.com/dashboard

python
# 票房单位亿元
movies = {
    "流浪地球": 40.78,
    "飞驰人生": 15.77,
    "疯狂的外星人": 20.83,
    "新喜剧之王": 6.10,
    "廉政风云": 1.10,
    "神探蒲松龄": 1.49,
    "小猪佩奇过大年": 1.22,
    "熊出没·原始时代": 6.71
}

用条形图绘制每部电影及其票房的代码如下:

python
import matplotlib.pyplot as plt
import random

# 设置显示中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']  # 指定默认字体
# 设置正常显示符号
plt.rcParams['axes.unicode_minus'] = False

plt.figure(figsize=(20, 8), dpi=100)

movies = {
    "流浪地球": 40.78,
    "飞驰人生": 15.77,
    "疯狂的外星人": 20.83,
    "新喜剧之王": 6.10,
    "廉政风云": 1.10,
    "神探蒲松龄": 1.49,
    "小猪佩奇过大年": 1.22,
    "熊出没·原始时代": 6.71
}

plt.bar(range(len(movies)), list(movies.values()))
plt.xticks(range(len(movies)), list(movies.keys()))
plt.grid()

效果图如下:

其中 xticksyticks 的用法跟之前的折线图一样。这里新出现的方法是 barbar 常用的有 3 个参数,分别是 x(x 轴的坐标点), y(y 轴的坐标点)以及 width(条形的宽度)。

横向条形图

横向条形图需要使用plt.barh这个方法跟bar非常的类似,只不过把方向进行旋转。参数跟bar类似,但也有区别。如下:

  1. y:数组或列表,代表需要绘制的条形图在y轴上的坐标点。
  2. width:数组或列表,代表需要绘制的条形图在x轴上的值(也就是长度)。
  3. height:条形图的高度,默认是 0.8。
  4. left:条形图的基线,也就是距离 y 轴的距离。
  5. 其他参数跟bar一样。

返回值也是BarContainer容器对象。

还是以以上数据为例,将电影名和票房反转一下。示例代码如下:

python
import matplotlib.pyplot as plt

plt.figure(figsize=(20, 8), dpi=100)

movies = {
    "流浪地球": 40.78,
    "飞驰人生": 15.77,
    "疯狂的外星人": 20.83,
    "新喜剧之王": 6.10,
    "廉政风云": 1.10,
    "神探蒲松龄": 1.49,
    "小猪佩奇过大年": 1.22,
    "熊出没·原始时代": 6.71
}

plt.barh(range(len(movies)), list(movies.values()))
plt.yticks(range(len(movies)), list(movies.keys()))  # 修改轴坐标
plt.grid()

效果图如下:

分组条形图

现在有一组数据,是 2019 年春节贺岁片前五天的电影票房记录。 示例代码如下:

python
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']  # 指定默认字体
plt.rcParams['axes.unicode_minus'] = False

movies = {
    "流浪地球": [2.01, 4.59, 7.99, 11.83, 16],
    "飞驰人生": [3.19, 5.08, 6.73, 8.10, 9.35],
    "疯狂的外星人": [4.07, 6.92, 9.30, 11.29, 13.03],
    "新喜剧之王": [2.72, 3.79, 4.45, 4.83, 5.11],
    "廉政风云": [0.56, 0.74, 0.83, 0.88, 0.92],
    "神探蒲松龄": [0.66, 0.95, 1.10, 1.17, 1.23],
    "小猪佩奇过大年": [0.58, 0.81, 0.94, 1.01, 1.07],
    "熊出没·原始时代": [1.13, 1.96, 2.73, 3.42, 4.05]
}
plt.figure(figsize=(20, 8))
width = 0.75
bin_width = width / 5

ind = range(0, len(movies))

movie_data = list(movies.values())
print(movie_data)

every_day = []
for i in range(len(movie_data[0])):
    every_day.append([
        movie_data[0][i],
        movie_data[1][i],
        movie_data[2][i],
        movie_data[3][i],
        movie_data[4][i],
        movie_data[5][i],
        movie_data[6][i],
        movie_data[7][i],
    ])

print(every_day)
# 第一种方案

# plt.bar([i - bin_width * 2 for i in ind], every_day[0], width=bin_width, label='第一天')
# plt.bar([i - bin_width for i in ind], every_day[1], width=bin_width, label='第二天')
# plt.bar(ind, every_day[2], width=bin_width, label='第三天')
# plt.bar([i + bin_width for i in ind], every_day[3], width=bin_width, label='第四天')
# plt.bar([i + bin_width * 2 for i in ind], every_day[4], width=bin_width, label='第五天')

# 第二种方案
for index in range(len(every_day)):
    day_tickets = every_day[index]
    xs = [i - (bin_width * (2 - index)) for i in ind]
    plt.bar(xs, day_tickets, width=bin_width, label="第%d 天" % (index + 1))
    # 添加坐标上的数字
    for ticket, x in zip(day_tickets, xs):
        plt.annotate(ticket, xy=(x, ticket), xytext=(x - 0.1, ticket + 0.1))

# 设置图例
plt.legend()
plt.ylabel("单位:亿")
plt.title("春节前 5 天电影票房记录")
# 设置 x 轴的坐标
plt.xticks(ind, movies.keys())
plt.grid(True)
plt.show()

示例图如下:

堆叠条形图

堆叠条形图,是将一组相关的条形图堆叠在一起进行比较的条形图。比如以下案例:

python
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']  # 指定默认字体
plt.rcParams['axes.unicode_minus'] = False

# 准备数据
menMeans = (20, 35, 30, 35, 27)
womenMeans = (25, 32, 34, 20, 25)
groupNames = ('G1', 'G2', 'G3', 'G4', 'G5')

# 生成序号
xs = range(len(menMeans))
plt.bar(xs, menMeans)
plt.bar(xs, womenMeans, bottom=menMeans)
plt.xticks(xs, groupNames)
plt.show()

效果图如下:

在绘制女性得分的条形图的时候,因为要堆叠在男性得分的条形图上,所以使用到了一个 bottom 参数,就是距离x 轴的距离。通过对贴条形图,我们就可以清楚的知道,哪一个队伍的综合排名是最高的,并且在每个队伍中男女的得分情况。

案例-对比每部电影的票房收入

电影数据如下图所示:

准备数据

python
movie_name = ['雷神 3:诸神黄昏', '正义联盟', '东方快车谋杀案', '寻梦环游记',
              '全球风暴', '降魔传', '追捕', '七十七天', '密战', '狂兽', '其它']
y = [73853, 57767, 22354, 15969, 14839, 8725, 8716, 8318, 7916, 6764, 52222]

绘制柱状图

python
import matplotlib.pyplot as plt

# 0. 准备数据
# 电影名字
movie_name = [
    '雷神 3:诸神黄昏', '正义联盟', '东方快车谋杀案', '寻梦环游记', '全球风暴',
    '降魔传', '追捕', '七十七天', '密战', '狂兽', '其它']
# 横坐标
x = range(len(movie_name))
# 票房数据
y = [73853, 57767, 22354, 15969, 14839, 8725, 8716, 8318, 7916, 6764, 52222]

# 1. 创建画布
plt.figure(figsize=(20, 8), dpi=100)

# 2. 绘制柱状图
plt.bar(x, y, width=0.5,
        color=['b', 'r', 'g', 'y', 'c', 'm', 'y', 'k', 'c', 'g', 'b'])

# 2.1b 修改 x 轴的刻度显示
plt.xticks(x, movie_name)

# 2.2 添加网格显示
plt.grid(linestyle="--", alpha=0.5)

# 2.3 添加标题
plt.title("电影票房收入对比")

# 3. 显示图像
plt.show()

散点图

用两组数据构成多个坐标点,考察坐标点的分布,判断两变量之间是否存在某种关联或总结坐标点的分布模式。

特点:判断变量之间是否存在数量关联趋势,展示离群点(分布规律)

api:plt.scatter(x, y)

散点图也叫 X-Y 图,它将所有的数据以点的形式展现在直角坐标系上,以显示变量之间的相互影响程度,点的位置由变量的数值决定。

通过观察散点图上数据点的分布情况,我们可以推断出变量间的相关性。如果变量之间不存在相互关系,那么在散点图上就会表现为随机分布的离散的点,如果存在某种相关性,那么大部分的数据点就会相对密集并以某种趋势呈现。数据的相关关系主要分为:正相关(两个变量值同时增长)、负相关(一个变量值增加另一个变量值下降)、不相关、线性相关、指数相关等,表现在散点图上的大致分布如下图所示。那些离点集群较远的点我们称为离群点或者异常点。

示例图如下:

img

绘制散点图

散点图的绘制,使用的是plt.scatter方法,这个方法有以下参数:

  1. x,y:分别是 x 轴和 y 轴的数据集。两者的数据长度必须一致。
  2. s:点的尺寸。如果是一个具体的数字,那么散点图的所有点都是一样大小,如果是一个序列,那么这个序列的长度应该和 x 轴数据量一致,序列中的每个元素代表每个点的尺寸。
  3. c:点的颜色。可以为具体的颜色,也可以为一个序列或者是一个cmap对象。
  4. marker:标记点,默认是圆点,也可以换成其他的。
  5. 其他参数:https://matplotlib.org/api/_as_gen/matplotlib.pyplot.scatter.html

散点图示例

python
'''1.简单示例'''
import matplotlib.pyplot as plt
import numpy as np

N = 10
x = np.random.rand(N)
y = np.random.rand(N)
plt.scatter(x, y)
plt.show()

image-20241219232327727

python
'''2.随机改变点的大小'''

N = 10
x = np.random.rand(N)
y = np.random.rand(N)
size = (30 * np.random.rand(N)) ** 2
plt.scatter(x, y, s=size)
plt.show()

image-20241219232443213

python
'''3.随机更改颜色,透明度为0.5'''

N = 10
x = np.random.rand(N)
y = np.random.rand(N)
size = (30 * np.random.rand(N)) ** 2
color = np.random.rand(N)
plt.scatter(x, y, s=size, c=color, alpha=0.5)
plt.show()

image-20241219232453370

python
'''4.更改散点形状'''

N = 10
x = np.random.rand(N)
y = np.random.rand(N)
size = (30 * np.random.rand(N)) ** 2
color = np.random.rand(N)
plt.scatter(x, y, s=size, c=color, alpha=0.5, marker='^')
plt.show()

image-20241219232501503

python
'''5.绘制两组数据'''

N = 10
x1 = np.random.rand(N)
y1 = np.random.rand(N)

x2 = np.random.rand(N)
y2 = np.random.rand(N)

plt.scatter(x1, y1, alpha=0.5, marker='^')
plt.scatter(x2, y2, alpha=0.5, marker='o')
plt.show()

image-20241219232512527

python
'''6.增加图例'''

N = 10
x1 = np.random.rand(N)
y1 = np.random.rand(N)

x2 = np.random.rand(N)
y2 = np.random.rand(N)

plt.scatter(x1, y1, alpha=0.5, marker='^', label='triangle')
plt.scatter(x2, y2, alpha=0.5, marker='o', label='circle')
plt.legend(loc='best')
plt.show()

image-20241219232527285

案例:探究房屋面积和房屋价格的关系

房屋面积数据:

python
x = [225.98, 247.07, 253.14, 457.85, 241.58, 301.01, 20.67, 288.64,
     163.56, 120.06, 207.83, 342.75, 147.9, 53.06,
     224.72, 29.51, 21.61, 483.21, 245.25, 399.25, 343.35]

房屋价格数据:

python
y = [196.63, 203.88, 210.75, 372.74, 202.41, 247.61, 24.9, 239.34,
     140.32, 104.15, 176.84, 288.23, 128.79, 49.64, 191.74, 33.1,
     30.74, 400.02, 205.35, 330.64, 283.45]

python
import matplotlib.pyplot as plt

# 0. 准备数据
x = [225.98, 247.07, 253.14, 457.85, 241.58, 301.01, 20.67, 288.64, 163.56,
     120.06, 207.83, 342.75, 147.9, 53.06, 224.72, 29.51, 21.61, 483.21,
     245.25, 399.25, 343.35]
y = [196.63, 203.88, 210.75, 372.74, 202.41, 247.61, 24.9, 239.34, 140.32,
     104.15, 176.84, 288.23, 128.79, 49.64, 191.74, 33.1, 30.74, 400.02,
     205.35, 330.64, 283.45]

# 1. 创建画布
plt.figure(figsize=(20, 8), dpi=100)

# 2. 绘制散点图
plt.scatter(x, y)

# 3. 显示图像
plt.show()

饼图

用于表示不同分类的占比情况,通过弧度大小来对比各种分类。饼图可以看成数据的合计后的占比,适合突出表现份额。

特点:分类数据的占比情况(占比)

api:plt.pie(x, labels=,autopct=,colors)

饼图是一个划分为几个扇形的圆形统计图表,用于描述量、频率或百分比之间的相对关系的。 在matplotlib中,可以通过plt.pie 来实现,其中的参数如下:

  1. x:饼图的比例序列。
  2. labels:饼图上每个分块的名称文字。
  3. explode:设置某几个分块是否要分离饼图。
  4. autopct:设置比例文字的展示方式。比如保留几个小数等。
  5. shadow:是否显示阴影。
  6. textprops:文本的属性(颜色,大小等)。
  7. 其他参数:https://matplotlib.org/api/_as_gen/matplotlib.pyplot.pie.html

返回值:

  1. patches:饼图上每个分块的对象。
  2. texts:分块的名字文本对象。
  3. autotexts:分块的比例文字对象。
python
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']  # 指定默认字体
plt.rcParams['axes.unicode_minus'] = False

# 设置绘画的主题风格
plt.figure(figsize=(10, 8))
# 服务行业单位个数

edu = [59104, 1467937, 3579974]
labels = ['第一产业', '第二产业', '第三产业']

explode = [0, 0, 0.1, ]  # 用于突出第三产业
colors = ['#FEB748', '#EDD25D', '#FE4F54']  # 自定义颜色

plt.axes(aspect='equal')  # 保证饼图是圆  不是默认的椭圆
plt.pie(x=edu,  # 数据
        labels=labels,  # 标签名称
        autopct='%.2f%%',  # 设置百分比格式  保留几位小数
        colors=colors,  # 使用自定义颜色
        labeldistance=1.1,  # 设置教育标签与圆心的距离
        # startangle=30,  # 设置饼图的初始角度  逆时针
        textprops={'fontsize': 12, 'color': 'k'},  # 设置文本标签的属性值
        explode=explode,  # 突出显示第三产业
        pctdistance=0.5,  # 占比和图距离
        )
plt.title('服务行业单位个数')
plt.show()

案例

假如现在我们有一组数据,用来记录各个操作系统的市场份额的。那么用饼状图表示如下:

python
import matplotlib.pyplot as plt

# 设置绘画的主题风格
plt.figure(figsize=(10, 8))
# 构造数据
edu = [0.2515, 0.3724, 0.3336, 0.0368, 0.0057]
labels = ['中专', '大专', '本科', '硕士', '其他']

explode = [0, 0.1, 0, 0, 0]  # 用于突出大专
colors = ['#FEB748', '#EDD25D', '#FE4F54', '#51B4FF', '#dd5555']  # 自定义颜色

plt.axes(aspect='equal')  # 保证饼图是圆  不是默认的椭圆
plt.pie(x=edu,  # 数据
        labels=labels,  # 标签名称
        autopct='%.2f%%',  # 设置百分比格式  保留几位小数
        colors=colors,  # 使用自定义颜色
        #         radius = 1,   # 设置饼图半径
        #         center = (80,80),  # 设置圆点
        labeldistance=1.1,  # 设置教育水平标签与圆心的距离
        startangle=30,  # 设置饼图的初始角度  逆时针
        textprops={'fontsize': 12, 'color': 'k'},  # 设置文本标签的属性值
        explode=explode,  # 突出显示大专人群
        pctdistance=0.5,  # 占比和图距离
        #         shadow=True,    # 阴影
        #         frame=True    # frame 显示
        )
plt.title('失信人员组成')

_images/matplotlib12.png

雷达图

雷达图(Radar Chart)又被叫做蜘蛛网图,适用于显示三个或更多的维度的变量的强弱情况。比如英雄联盟中某个影响的属性(法术伤害,物理防御等),或者是某个企业在哪些业务方面的投入等,都可以用雷达图方便的表示。

matplotlib.pyplot中,可以通过plt.polar来绘制雷达图,这个方法的参数跟plt.plot非常的类似,只不过是x 轴的坐标点应该为弧度(2*PI=360°)。示例代码如下:

python
import numpy as np
import matplotlib.pyplot as plt

properties = ['输出', 'KDA', '发育', '团战', '生存']
values = [40, 91, 44, 90, 95, 40]
theta = np.linspace(0, np.pi * 2, 6)
plt.polar(theta, values)
plt.xticks(theta, properties)
plt.fill(theta, values)
plt.show()

效果图如下:

其中有几点需要注意:

  1. 因为polar并不会完成线条的闭合绘制,所以我们在绘制的时候需要在theta中和values 中在最后多重复添加第 0 个位置的值,然后在绘制的时候就可以和第 1 个点进行闭合了。
  2. polar只是绘制线条,所以如果想要把里面进行颜色填充,那么需要调用fill函数来实现。
  3. polar默认的圆圈的坐标是角度,如果我们想要改成文字显示,那么可以通过xticks来设置。

小结

  • 折线图【知道】
    • 能够显示数据的变化趋势,反映事物的变化情况。(变化)
    • plt.plot()
  • 散点图【知道】
    • 判断变量之间是否存在数量关联趋势,展示离群点(分布规律)
    • plt.scatter()
  • 柱状图【知道】
    • 绘制连离散的数据,能够一眼看出各个数据的大小,比较数据之间的差别。(统计/对比)
    • plt.bar(x, width, align="center")
  • 直方图【知道】
    • 绘制连续性的数据展示一组或者多组数据的分布状况(统计)
    • plt.hist(x, bins)
  • 饼图【知道】
    • 用于表示不同分类的占比情况,通过弧度大小来对比各种分类
    • plt.pie(x, labels, autopct, colors)

附录:中文显示问题解决

解决方案一:

下载中文字体(黑体,看准系统版本)

  • 步骤一:下载 SimHei 字体(或者其他的支持中文显示的字体也行)

  • 步骤二:安装字体

    • linux 下:拷贝字体到 usr/share/fonts 下:

      sudo cp ~/SimHei.ttf /usr/share/fonts/SimHei.ttf
    • windows 和 mac 下:双击安装

  • 步骤三:删除~/.matplotlib 中的缓存文件

    shell
    cd ~/.matplotlib
    rm -r *
  • 步骤四:修改配置文件 matplotlibrc

    shell
    vi ~/.matplotlib/matplotlibrc

    将文件内容修改为:

    font.family         : sans-serif
    font.sans-serif     : SimHei
    axes.unicode_minus  : False

解决方案二:

在 Python 脚本中动态设置 matplotlibrc,这样也可以避免由于更改配置文件而造成的麻烦,具体代码如下:

python
import matplotlib.pyplot as plt

# 设置显示中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']  # 指定默认字体

有时候,字体更改后,会导致坐标轴中的部分字符无法正常显示,此时需要更改 axes.unicode_minus 参数:

python
# 设置正常显示符号
plt.rcParams['axes.unicode_minus'] = False