Python 提供了非常强大的数据可视化工具,本部分逐步学习如何在 Python 中实现数据的可视化。
基本概念
要熟练使用 Matplotlib 来画图,首先要理解 Matplotlib 图像的三层结构。
从外向内的方向来看,依次如下。
- 画板(Canvas),Matplotlib 所有图像都显示在一个 Canvas 之上,但 Canvas 一般都是系统放置好的,代码中并不需要显式地创建 Canvas。
- 画布(Figure),Canvas 上方的一层是画布,画布一般需要手动创建,一般在画图的代码编写中,第一件事就是创建画布,并指定画布的大小、材质等信息。
- 子图(Axes),Axes 的意思是坐标轴。原本的意思为一个画布上可以有多个坐标轴,但习惯性地认为一个坐标轴就是一个子图,所以把 Axes 叫作子图。一个画布上面可以添加多个子图。
绘图流程
(1)导入 Matplotlib 和 numpy
为什么需要 numpy?原因是基本上所有绘图的数据源要么是 pandas 的 Series,要么是 numpy 的 ndarray。这里为了方便讲解,使用更轻量级的 ndarray 作为主数据源。
import matplotlib.pyplot as plt
import numpy as np
这里把 Matplotlib 简写为 plt、numpy 简写为 np。虽然这个简写可以任意指定,不过基本上用 plt 和 np 已经成为行业标准,网上大量的示例代码都采用这种风格,所以一般情况下建议保持一致。
(2)准备图的数据源
画图,首先就要思考数据源是什么。因为图本身只是一个数据源更好的呈现方式。准备数据源,本质就是思考把什么东西画出来。在这个例子中,要画出 y = 2x + 1 的函数曲线,就要决定定义域(x 的范围)是多少。因为只要 x 确定了,y 也就可以直接算出来。
另外,在一般情况下,画图传入的只能是离散的值,不能是连续的值。比如画 x 取值在 0 到 5 之间的函数曲线,无法直接告诉 Matplotlib 要这个范围的曲线,而是需要以一定的步长从这个范围中采样,生成一个离散的数据点列表告诉 Matplotlib。
比如按 1 采样,就是 [0,1,2,3,4];也可以是按 0.5 采样,也就是 [0. , 0.5, 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. , 4.5]。
这里画 0 到 10 范围内的曲线,按 1 采样。根据 NumPy 部分学习到的内容,使用 arange 函数可以轻松实现。
x_ranges = np.arange(0, 10)
x_ranges
输出:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
现在,画图的基本数据点就准备好了。
(3)创建画布
通过 plt.figure 函数可以创建一块画布,并且可以通过 figsize 参数指定画布的大小。figsize 接受一个两个元素的列表,第一个元素为画布的宽,第二个元素为画布的高,单位是英寸。
在接下来的代码中,创建了一个 18x6 大小的画布,并将其保存在变量 figure1 中。
figure1 = plt.figure(figsize=(18, 6),facecolor=[0,0,1])
上述代码可以先不执行,不然会生成一个空的图。一般来说 Matplotlib 的画图代码中,创建画布、创建子图、设置子图标题、坐标轴属性以及绘制步骤往往都放在一个 Cell 中,避免中间放在不同 cell 中间会画出不完整的图像。
(4)创建子图
创建完画布之后,下一个步骤就是创建子图。在步骤 2 的基础上,添加如下的代码,创建一个充满整块画布的子图。
ax1 = figure1.add_subplot(1,1,1)
add_subplot 函数参数里面的三个 1 是什么意思?后续的子图布局会展开讲,这里不再赘述。
(5)设置子图标题
在创建子图之后,直接使用 plt.title 函数即可指定当前子图的标题。将上述代码添加到步骤 2 的 cell 中。
plt.title("y = 2x +1")
这里可能会有疑问:没有传入子图对象 ax1,它怎么知道设置的是哪个子图?原因是 Matplotlib 内部有一个状态机的机制,每次执行 add_subplot 的时候,系统都会记录当前操作的子图,这样执行 plt 的这些方法都是针对当前子图生效。
(6)设置坐标轴的属性。
坐标轴的常见属性有标题、范围和刻度。一般来说指定范围之后可以直接默认推导出刻度,所以主要还是以设置标题和范围为主。在步骤 2 的 cell 中,添加如下的代码。
plt.xlabel("X")
plt.xlim([0, 20])
plt.ylabel("Y")
plt.ylim([0, 20])
这里设置的范围,x 轴和 y 轴都是 0 到 20。这里的范围和数据源范围的关系是什么呢?这里设置的是坐标轴的范围,而数据源指定的是绘制曲线的范围。比如刚才设置的曲线范围是 0 到 10,而这里的轴范围指定了 0 到 20,所以待会儿画出的曲线理论上只会在坐标系的左边部分出现。
继续不用执行,看下面的步骤。
(7)绘图并显示
接下来就到了最关键的步骤:绘图。绘图的本质就是在好画布和轴之后将数据源告诉 Matplotlib 的过程,这里要用到 plt.plot 函数。
一般情况下 plot 函数接受两个参数:一个是 x 轴的数据,一个是对应的 y 轴的数据。x 轴的数据步骤 2 已经创建好了,而画的是 y = 2x + 1 的曲线,所以 y 轴的数据直接用 x 轴的数据乘以 2 + 1 即可。因为 x_range 是一个 NumPy 的 ndarray,所以可以直接运算。
在调用完 plot 函数后,再调用 plt.show 函数,将刚才做的全部工作展示出来。
整个绘图过程,完成的代码如下:
figure1 = plt.figure(figsize=(18, 6))
ax1 = figure1.add_subplot(1,1,1)
plt.title("y = 2x +1")
plt.xlabel("X")
plt.xlim([0, 20])
plt.ylabel("Y")
plt.ylim([0, 20])
# 本步骤新增
plt.plot(x_ranges, x_ranges * 2 + 1)
plt.show()
执行之后输出如下:
可以看到,曲线已经被画出来了,并且和预期一致,曲线只在左边部分出现,因为数据源只包含了 0 到 10 的数据。画布大小也设置的一样,宽是高的三倍,对于图的标题、坐标轴的标题设置也都生效。
但细心的读者可能发现问题:虽然图确实展示了数据在坐标轴的位置关系,但是 x 轴的刻度宽度却远比 y 轴的宽。本质原因是我们设定了坐标的 x 轴比 y 轴长,但是两个轴的范围都一样,都是 0 到 20,这样就使得函数曲线看上去有点变形。
在对 Matplotlib 的绘图经验不足的时候,很难一次性把参数设置对,经常都需要先画出图来,然后再调整参数。
(8)根据绘图结果调整参数
要绘制出比例一致的图形,有多种方式,本质只要坐标系的大小和 x 轴、y 轴的范围成比例即可。这里简单地将画布大小调整为 10x10,这样两边比例就一致了。
figure1 = plt.figure(figsize=(10, 10)) # 改动点
ax1 = figure1.add_subplot(1,1,1)
plt.title("y = 2x +1")
plt.xlabel("X")
plt.xlim([0, 20])
plt.ylabel("Y")
plt.ylim([0, 20])
plt.plot(x_ranges, x_ranges * 2 + 1)
plt.show()
输出如下:
现在的图能够比较真实地反映函数 y=2x+1 的形状了。
子图的布局
本文介绍子图的布局方式。Matplotlib 的子图默认是按照行列的方式来布局的,核心就是通过画布对象的 add_subplot 函数。
add_subplot 函数的形式如下:
add_subplot(行数,列数,当前子图索引)
行数和列数代表子图的位置在画布中如何划分。
比如如果行数和列数都是 2 的话,代表把画纸拆分成 2x2,一共四个格子,每个格子可以放置一个子图。当前子图索引参数指的是告诉 Matplotlib 接下来要设置的是这个 2x2 的划分里的第几幅子图。
这里要注意的是,当前子图索引和绝大多数 Python 索引不同,这里的索引是从 1 开始的,顺序对应网格的顺序是从左到右、从上到下。
还是通过例子来学习一下 add_subplot 的用法。接下来创建一个 2x2 的划分,并分别逆序添加四幅子图,第一幅添加在最后,以此类推。
figure2 = plt.figure(figsize=[18,9])
# 第一幅,摆在右下角,也就是第四个位置
ax1 = figure2.add_subplot(2,2, 4)
plt.title("fig1")
# 第二幅,摆在第三个位置,也就是第二行第一个
ax2 = figure2.add_subplot(2,2,3)
plt.title("fig2")
# 第三幅,摆在第二个位置,也就是第一行第二个
ax3 = figure2.add_subplot(2,2,2)
plt.title("fig3")
# 第四幅,摆在第一个位置,也就是第一行第一个
ax4 = figure2.add_subplot(2,2,1)
plt.title("fig4")
plt.show()
输出如下:
因为这次添加的子图并没有做属性的设置和数据的填充,所以画的都是默认的空白图。但为每个子图都设置了标题来区分。可以看到第一个子图在右下角、第二个在左下角,这与添加时设置的子图索引是匹配的。
颜色与线条样式
Matplotlib 支持丰富的样式,其中最基本的就是控制画出的图形颜色和线条的粗细。
一般来说有以下几个原则:
- 画布的样式,通过设置 figure 函数的参数决定;
- 子图的样式,通过设置 add_subplot 函数的参数决定;
- 线条的样式,通过设置 plot 函数的参数决定。
另外,在 Matplotlib 中,颜色往往由三个元素的列表来表示,分别代表颜色 R、G、B 三个通道的分量,范围都是 0-1。
学习过计算机图像处理的读者应该并不陌生,比如纯红色对应 [1, 0, 0]、绿色是 [0, 1, 0]、蓝色是 [0,0,1]。还有一些颜色是三种颜色混合运算的,比如青色就是 [0,1,1]。感兴趣的读者可以参考RGB 颜色表 (360doc.com)里面的颜色值除以 255,就可以得到 Matplotlib 中可以使用的色值。
接下来,还是通过一个例子来学习如何改变图像的各种颜色。任务是将刚才 y=2x + 1 的图像做以下修改:
- 画布颜色修改为红色;
- 子图颜色修改为蓝色;
- 线条颜色修改为绿色;
- 线条粗细变为 3。
实现的代码如下:
# 通过 figure 函数的 facecolor 参数设置画布颜色
figure1 = plt.figure(figsize=(10, 10), facecolor=[1, 0, 0]) # 改动点
# 通过 add_subplot 函数的参数设置子图的颜色
ax1 = figure1.add_subplot(1,1,1, facecolor = [0, 0, 1])
plt.title("y = 2x +1")
plt.xlabel("X")
plt.xlim([0, 20])
plt.ylabel("Y")
plt.ylim([0, 20])
# 通过 plot 函数的 linewidth 参数设定线条的粗细
# 通过 color 参数来设定线条的颜色
plt.plot(x_ranges, x_ranges * 2 + 1, linewidth = 3, color=[0,1,0])
plt.show()
输出为:
从上图中可以看到,各个部分都和刚才的设置保持了一致。
实战:显示评分曲线
下面通过一个小实战来演练一下今天学习的内容。
任务说明:将国产电视剧评分的前 100 条记录的评分情况用折线图表示出来。
准备数据源
把前 100 条记录的评分用折线图画出来,x 轴就是电视剧的序号 0 到 100,y 轴就是具体序号对应的电视剧的评分。
首先将 tv_rating.csv 放在工作目录中。然后新建 Cell,加载数据集。如下所示:
import pandas as pd
df_rating = pd.read_csv("tv_rating.csv")
df_rating
输出:
现在评分一列是字符串表示的,而且还有一个“分”字,而送到 Matplotlib 中的数据需要是数字,所以需要对这一列进行处理,去掉“分”字,并把数据转换为浮点数。把处理后的数据存为一个新的列 rating_num。
df_rating["rating_num"] = df_rating.rating.apply(lambda x:float(x.replace("分","")))
df_rating
这里继续使用了 lambda 表达式来处理列,针对 rating 列的每一项,首先删除“分”字,然后转换为 float。转换后的 Series 存储为 rating_num。
输出如下:
可以看到,新列已经添加成功。由于只处理前 100 条,所以将前 100 条保存为一个新的 DataFrame:df_head_100。
df_head_100 = df_rating[:100]
df_head_100
输出如下:
至此,数据源就准备完毕了。
绘制评分图
在数据源准备好之后,按照之前介绍的步骤直接绘图即可。
代码如下:
# 创建图表
figure = plt.figure(figsize=[18,9])
# 创建1x1分割的网格,并选中第一个区域创建子图
ax1 = figure.add_subplot(1,1,1)
# 要显示中文需要指定字体
plt.rcParams["font.sans-serif"] = "SimHei"
# 设置标题和轴属性
plt.title("电视剧评分折线图")
plt.xlabel("序号")
plt.xlim([0, 100])
plt.ylabel("评分")
plt.ylim([0,5])
# 画图,x 轴是序号,所以可以直接用 index 属性去索引即可
# y 轴的评分的值,则直接用我们准备的 rating_num 列
plt.plot(df_head_100.index, df_head_100.rating_num)
plt.show()
输出:
折线图
折线图已经不陌生了,前文绘制的图像都是折线图。Matplotlib 中,绘制折线图主要通过 plot 函数实现。
下面学习折线图一些更深入的使用技巧和方法。
绘制平滑的曲线
sin 函数的范围是 -1 到 1,所以这次设定 y 轴的范围为 -5 到 5,x 轴的范围取 0 到 20。画出 x 取值 0 到 20 的正弦曲线。前文已经学习过画图的做法,稍加改动就能实现这次的需求。代码如下:
# 准备数据源
x_ranges = np.arange(0, 20)
# 准备画布和轴
figure1 = plt.figure(figsize=(10, 10)) # 改动点
ax1 = figure1.add_subplot(1,1,1)
plt.title("y = sin(x)")
plt.xlabel("X")
plt.xlim([0, 20])
plt.ylabel("Y")
plt.ylim([-2.5, 2.5])
# 用 x_ranges,和对应的 sin 值画线
plt.plot(x_ranges,np.sin(x_ranges) )
plt.show()
输出如下:
可以看到,曲线虽然画出来了,但却不够平滑,比较生硬。本质的原因是数据源:plot 函数本质就是把数据源每个点用线连起来,所以只需提供更“密集”的数据源,也就是提高采样的频率,就能获得更平滑的曲线。
用这个例子来说,y 的数据是由 x 决定的,所以只需提供一个间隔更小的 x_ranges 即可。之前学过,numpy 的 arange 函数的第三个参数就是步长,默认是 1。这里只需要在同样的范围情况下缩短步长,就能获得更密集的数据。测试一下:
print("步长为1:", np.arange(0,20))
print("步长为0.2:", np.arange(0, 20, 0.2))
输出如下:
步长为1: [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19]
步长为0.2: [ 0. 0.2 0.4 0.6 0.8 1. 1.2 1.4 1.6 1.8 2. 2.2 2.4 2.6
2.8 3. 3.2 3.4 3.6 3.8 4. 4.2 4.4 4.6 4.8 5. 5.2 5.4
5.6 5.8 6. 6.2 6.4 6.6 6.8 7. 7.2 7.4 7.6 7.8 8. 8.2
8.4 8.6 8.8 9. 9.2 9.4 9.6 9.8 10. 10.2 10.4 10.6 10.8 11.
11.2 11.4 11.6 11.8 12. 12.2 12.4 12.6 12.8 13. 13.2 13.4 13.6 13.8
14. 14.2 14.4 14.6 14.8 15. 15.2 15.4 15.6 15.8 16. 16.2 16.4 16.6
16.8 17. 17.2 17.4 17.6 17.8 18. 18.2 18.4 18.6 18.8 19. 19.2 19.4
19.6 19.8]
可以看到,当步长变为 0.2 之后,生成的数组就变得很密集。
将 x_ranges 的生成增加步长,代码如下:
# 准备数据源
x_ranges = np.arange(0, 20, 0.2) # 改动点
# 准备画布和轴
figure1 = plt.figure(figsize=(10, 5))
ax1 = figure1.add_subplot(1,1,1)
plt.title("y = sin(x)")
plt.xlabel("X")
plt.xlim([0, 20])
plt.ylabel("Y")
plt.ylim([-2.5, 2.5])
# 用 x_ranges,和对应的 sin 值画线
plt.plot(x_ranges,np.sin(x_ranges) )
plt.show()
输出如下:
可以看到,数据曲线变得平滑了很多。
连线规则
既然 plot 函数是把传入的点用线连接起来,那连接的顺序是什么呢?plot 函数的连接顺序并不是按照 x 从左到右画,而是严格按照传入的数据源的顺序:首先将 x[0]、y[0] 连接到 x[1]、y[1],然后再将 x[1]、y[1] 连接到 x[2]、y[2],以此类推。所以理论上,坐标轴中并不仅限于画函数图形,而是可以画任意图形。
接下来,通过四个点在坐标轴上画一个三角形,来演示 plot 函数的连线规则。
figure2 = plt.figure(figsize=(10, 5))
ax1 = figure2.add_subplot(1,1,1)
plt.title("Test Line")
plt.xlabel("X")
plt.xlim([0, 20])
plt.ylabel("Y")
plt.ylim([0, 10])
# 传入四个点,画三角形
plt.plot([2, 10, 5,2],[2, 2, 5,2])
plt.show()
输出如下:
根据传入的点,上述图形的绘制顺序如下,1 → 2 → 3。
fmt 语法
折线图的最后一个话题,介绍格式设置的语法。折线图常见的格式设置主要包含三种:
- marker,也就是点的样式,这里的点指的就是数据源中传入的点;
- line,线的样式,比如有实线、虚线;
- color,也就是线条的颜色,这个之前通过参数 c 设置过。
先简单感受一下样式的设置。在刚才的 cell 中,将 plot 函数增加一个字符串的参数。如下所示:
plt.plot([2, 10, 5,2],[2, 2, 5,2], "o--r")
三角形将会变成如下所示:
主要有三个变化:
- 线的连接处变成了圆点,这个就是 marker;
- 线变成了虚线;
- 颜色变成了红色。
这些都是通过 "o--r" 这个字符串实现的,这个字符串就称之为 fmt 参数。
fmt 参数本质就是通过一个字符串来一次性设置三种样式。它的格式如下:
fmt = "[marker][line][color]"
中括号表示可以只写其中的一项或者两项。
(1)marker 的语法
三个部分逐一来看。首先是 marker,可以有如下几种取值:
设置 marker 的样式除了在 fmt 字符串中设定其样式外,plot 函数还支持 markersize 参数用于指定 marker 的大小。
可以将上述 plot 函数中的 fmt 字符串的第一个字符替换为其他样式,测试样式的效果。比如替换为六边形,代码如下:
plt.plot([2, 10, 5,2],[2, 2, 5,2], "H--r", markersize=10)
输出如下:
可以看到,定点的样式已经变成了六边形。
(2)line 的语法
在之前的例子中,line 的类型使用的是线段虚线(--)。可以修改之前的 plot 函数调用,来感受不同线条的样式,比如这里改成线段-点混合虚线(-.)。
plt.plot([2, 10, 5,2],[2, 2, 5,2], "H-.r", markersize=10)
输出如下:
(3)颜色的语法
设置线条和 marker 的颜色,之前介绍过可以通过传入 RGB 三个数字的形式来指定。在 fmt 语法中也可以通过颜色的简写来指定,对应关系如下。
这次来改个颜色试试。还是改一下 fmt 的参数,将颜色标记改为 g,即绿色,如下所示:
plt.plot([2, 10, 5,2],[2, 2, 5,2], "H-.g", markersize=10)
输出如下:
散点图
散点图在前一节已经画过,和折线图最核心的区别是,散点图是直接将点画出来,而不会用线去连接。在很多离散数据的场景,用折线图往往不能很好地反映特征之间的相关性,尤其是在数据点比较多的情况下,用折线图往往就会非常凌乱。这个时候直接将数据点画出来往往更加直观。
散点图作图的流程和折线图完全一样,只是在绘图的时候,散点图调用的方法是 plt.scatter,而折线图调用的是 plt.plot。plt.scatter 函数的用法和 plt.plot 很类似,同样是传入 x 轴的数组和 y 轴的数组,而且 scatter 函数同样支持设置点的样式,但不支持 fmt 语法。设置散点图的点参数可以使用 scatter 函数的 marker 参数,取值和 fmt 中介绍的 marker 的表格一致。
现在直接基于之前画正弦函数(sin 函数)的例子,来看一下散点图的效果:
# 准备数据源
x_ranges = np.arange(0, 20, 0.2) # 改动点
# 准备画布和轴
figure1 = plt.figure(figsize=(10, 5))
ax1 = figure1.add_subplot(1,1,1)
plt.title("y = sin(x)")
plt.xlabel("X")
plt.xlim([0, 20])
plt.ylabel("Y")
plt.ylim([-2.5, 2.5])
# 用 * 画出正弦的散点图
plt.scatter(x_ranges,np.sin(x_ranges), marker="*")
plt.show()
输出如下:
相关性分析实战
散点图在异常数据分析与特征相关性分析中非常常见。下面通过一个实际的案例来学习如何使用散点图进行异常值和相关性的分析。
数据准备
然后,在 notebook 中查看一下数据。
import pandas as pd
df_goods = pd.read_csv("data.csv")
df_goods
输出如下:
data.csv 存储了欧洲某礼品电商公司销售的订单数据。从上图中可以看到有 54 万条数据,本次重点关注以下三列:
异常数据剔除
首先,分析 Quantity 这个列是否存在异常值。异常值指的就是和绝大多数值差得很远,或者明显不符合常识的值。对于 Quantity 列来说,小于 0 的肯定是异常值;如果大多数订单都只是 10 到 20,那如果有记录是几千、几万,那肯定也是异常值。
一般来说,异常值分析主要包含两个方面:
- 分析是否存在异常值;
- 分析异常值存在的比例。
为什么要分析比例呢?如果异常值的比例太高,那说明这个列就没有分析的价值,就需要想其他的办法帮助我们找到利于分析的数据。
使用散点图,上述两个步骤都可以轻松完成。
尝试绘制 Quantity 与时间的散点图,这样就能看出随着时间的推移 Quantity 数据的分布。
(1)清洗 Quantity 异常值
第一步,将订单时间 InvoiceDate 转换成 datetime 格式,这样才能将其作为横轴。代码如下:
df_goods.InvoiceDate = pd.to_datetime(df_goods.InvoiceDate)
然后,画出 Quantity 沿着下单日期的分布图。
plt.figure(figsize = [18,6])
plt.scatter(df_goods.InvoiceDate, df_goods.Quantity)
上述代码中没有创建子图,也没有设置 x/y 轴的属性。当没有这些代码的时候,Matplotlib 会自动创建一个充满画布的子图,然后用传入数据的范围来作为轴的尺度。
上述代码输出如下:
从图中可以清晰地看出 Quantity 数据存在异常值,一部分是小于 0 的,一部分远大于大多数值。首先剔除小于 0 的值,以及大于 10000 的值,再来看一下分布情况。
代码如下:
df_goods = df_goods.loc[(df_goods.Quantity > 0) & (df_goods.Quantity < 10000)]
plt.figure(figsize = [18,6])
plt.scatter(df_goods.InvoiceDate, df_goods.Quantity)
输出如下:
第一轮异常值剔除后,在 0~10000 的范围上可以看得更加细致。从上图中不难发现,完全可以以 800 作为阈值来剔除异常值。
代码如下:
df_goods = df_goods.loc[(df_goods.Quantity > 0) & (df_goods.Quantity < 800)]
df_goods
输出如下:
异常值剔除之后还剩 53 万条记录,影响不大,也符合从散点图中观察得到的结论。
(2)清洗 UnitPrice 异常值
用类似的方法看 UnitPrice 的分布:
plt.figure(figsize = [18,6])
plt.scatter(df_goods.InvoiceDate, df_goods.UnitPrice)
输出如下:
可以看到,UnitPrice 仍然存在异常值,首先初步排除 < 0 以及大于 1000 的。然后再看一次。
df_goods = df_goods.loc[(df_goods.UnitPrice > 0) & (df_goods.UnitPrice < 1000)]
plt.figure(figsize = [18,6])
plt.scatter(df_goods.InvoiceDate, df_goods.UnitPrice)
输出如下:
这次的 UnitPrice 虽然分布均衡了一些,但不难发现底部那一条的数据点的密集程度还是远超过上方的区域。从点的稀疏程度来看,认为超过 50 的都算异常值。
在之前的基础上做以下调整。
df_goods = df_goods.loc[(df_goods.UnitPrice > 0) & (df_goods.UnitPrice < 50)]
df_goods
输出如下:
可以看到,在过滤完 Quantity 和 UnitPrice 的异常值后,数据表还剩 52 万条记录,整体还是不影响分析的。
特征相关性分析
在剔除完异常值之后,接下来的任务是分析 UnitPrice 和 Quantity 之间是否存在一定的相关性。相关性分析之前已经学习过使用 NumPy 的 corrcoef 函数来计算,今天尝试通过散点图的形式来分析。
一般来说,要分析两个样本量很高的特征的相关性,直接把其中一个特征作为横轴、另一个特征作为纵轴,然后将样本数据以点的形式画出来。
代码如下所示:
plt.figure(figsize = [18,6])
plt.scatter(df_goods.UnitPrice, df_goods.Quantity)
输出如下:
上图中以 UnitPrice 为横轴、Quantity 为纵轴。从图中可以看到,随着 UnitPrice 增加,Quantity 对应就减少,尤其是超过 20 之后更加明显;而随着 UnitPrice 降低,Quantity 增加,UnitPrice 在 0 到 6 这个区间,Quantity 的数量最高。说明东西越便宜,卖的数量就越多。
从上面的推论不难看出,UnitPrice 和 Quantity 存在一定的负相关性,但这个相关性不是线性的。通过 NumPy 的 corrcoef 函数只能得到一个相关性的系数,而通过散点图往往能解读出更多更有用的信息。
小结
复习一下今天学习的内容。首先,学习了折线图的几个实用技巧:
- 如何绘制平滑的曲线;
- 如何利用连线规则来画出任意的图形;
- 如何利用 fmt 语法来指定折线图的样式。
然后,学习了散点图的基本用法:将 plot 函数替换为 scatter 函数即可。
最后,通过一个相关性分析的案例实战,体会了如何使用散点图来做异常值的清洗和特征相关性的分析。
课后作业:
在坐标系内绘制一个以 (50,200) 作为左上角点、宽度为 150 的正方形,点的样式为 8 边形,线的样式为虚线点,颜色为青色。
版本差异(数据科学栈 → 当前版本)
| 库 | 本文编写时 | 当前稳定版 | 升级要点 |
|---|---|---|---|
| Python | 3.8-3.12 | 3.14 | 3.12+ 起性能显著提升;3.14 PEP 649/750 |
| NumPy | 1.x/2.0 | 2.3.x | np.float_ 等别名移除;NEP 50 类型提升 |
| Pandas | 1.x/2.x | 3.0.x | Copy-on-Write 默认开启;inplace 移除;字符串 dtype 变化 |
| Matplotlib | 3.x | 3.x 稳定版 | API 兼容,样式更新 |
| Seaborn | 0.12/0.13 | 0.13.x | API 稳定 |
| scikit-learn | 1.x | 1.7.x | API 稳定,新算法持续加入 |
本文讲解的数据分析流程(读取→清洗→分析→可视化)与核心 API 在最新版本中成立;升级时重点关注 Pandas 3.0 的 Copy-on-Write 与 NumPy 2.x 的类型变化。