2.1 搭建Python机器学习环境
Python是一种解释型、面向对象、动态数据类型的高级程序设计语言,可以运行在Windows、Mac和Linux/UNIX系统上。这里强烈推荐Anaconda,它是一个开源的Python发行版本,集成了科学计算、数学和工程所需的几乎所有常用的Python工具包,用户无须再一一安装,使用十分方便,安装方法可参考Anaconda安装指南。
除了Anaconda之外,读者也可以通过Python的包管理工具pip安装第三方包。如果已经安装了Python(本书版本2.7.12)和pip(本书版本10.0.1),则可通过如下命令安装Python包:
pip install SomePackage
本书主要用到的Python工具包及版本号如下。
·Jupyter Notebook(版本4.2.0):一个交互式笔记本,支持实时代码、数学方程和可视化。
·NumPy(版本1.11.3):一个用Python实现的科学计算包,适用于向量、矩阵等复杂科学计算。
·Pandas(版本0.18.1):基于NumPy,用于数据快速处理和分析。
·Matplotlib(版本1.5.3):一个Python的2D绘图库。
·scikit-learn(版本0.19.1):基于NumPy和Scipy的一个常用机器学习算法库,包含大量经典机器学习模型。
为保证本书中的代码可正确运行,请确认已安装的软件包版本号大于或等于上述版本。读者可通过下列命令安装上述指定版本的Python包:
pip install jupyter==4.2.0 pip install numpy==1.11.3 pip install pandas==0.18.1 pip install matplotlib==1.5.3 pip install sklearn==0.19.1
2.1.1 Jupyter Notebook
Jupyter Notebook是基于浏览器的图形界面,支持IPython Shell,具有丰富的显示功能,除了可以执行Python语句之外,还支持格式化文本、静态和动态可视化、数学方程等。另外,Jupyter文档也允许其他人打开,在自己的系统上执行代码并保存。
虽然Jupyter Notebook是通过浏览器访问的,但在访问之前需要先启动Jupyter Notebook,启动命令如下:
jupyter notebook
启动后,会看到类似下面的日志信息:
https://jupyter.readthedocs.io/en/latest/running.html#running $ jupyter notebook [I 16:53:17.122 NotebookApp] Serving notebooks from local directory: /Users/xgb [I 16:53:17.122 NotebookApp] 0 active kernels [I 16:53:17.122 NotebookApp] The Jupyter Notebook is running at: http://localhost:8888/ [I 16:53:17.122 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
其中,http://localhost:8888/为Jupyter Notebook应用访问的URL。打开该URL便可以看到Jupyter Notebook面板,如图2-1所示。
图2-1 Jupyter-Notebook面板
可以看到,该面板显示了当前启动目录包含的文件及子目录,通常当前启动目录即为Jupyter Notebook程序的主目录。
如果想新建一个Notebook,只需要单击New下拉按钮,选择希望启动的Notebook类型即可,如图2-2所示。
图2-2 新建Notebook
也可以单击列表中Notebook的名称,打开现有Notebook。每个Notebook由多个单元格组成,用户可以在单元格内执行代码,如图2-3所示。
图2-3 要执行的代码
图2-4 执行结果
按Shift+Enter组合键之后,即可执行代码,执行结果如图2-4所示。
此外,Jupyter Notebook还有许多其他非常实用的特性,可参考Jupyter官方文档。
2.1.2 NumPy
NumPy是Python用于科学计算的开源工具包,提供了高效的接口来存储和操作密集数据缓冲区。在某种程度上,NumPy数组就像Python的内置列表,但NumPy数组提供了更为高效的存储和数据操作。NumPy提供了强大的N维数组对象Array、较为成熟的(广播)函数库及线性代数、傅里叶变换和随机数等功能。下面对NumPy的基本操作进行介绍。
1.NumPy数组
NumPy数组包含相同类型的元素,和标准Python类库中的array.array不同,后者只提供一维数组及少量功能,而NumPy数组包含很多重要属性。首先定义一个NumPy数组,代码如下:
In [1]: import numpy as np a = np.arange(10).reshape(2, 5) print a Out [1]: [[0 1 2 3 4] [5 6 7 8 9]]
NumPy数组创建完成后,可以通过ndim、shape和size查看数组维度、每一维的大小及数组总大小,代码如下:
In [2]: a.ndim Out [2]: 2
其中,通过shape输出的结果是一个整型元组,表示每一维的大小。例如,对于一个具有n行m列的矩阵,其shape输出为(n,m)。Numpy数组另外一个比较实用的属性是dtype,用于显示数组元素的数据类型,示例如下:
In [3]: a.dtype Out [3]: dtype('int64')
NumPy数组也可以是多维数组,其中维度称为轴(axis),以下面数组为例:
[[1,2],
[2,3],
[3,4]]
其总共包含两维,第一维长度为3,第二维长度为2。
2.基本操作
(1)矩阵运算
可以将数学运算符直接应用于NumPy数组,从而产生一个新的数组。例如,对两个数组进行加、减、乘、除运算,即对应位置的元素进行相加、相减、相乘、相除,得到一个新的数组。代码如下:
In [4]: # 加法 a = np.array([1,2,3,4,5]) b = a + 2 print b Out [4]: [3 4 5 6 7] In [5]: # b数组求立方 b ** 3 Out [5]: array([ 27, 64, 125, 216, 343]) In [6]: # 矩阵加法 a = np.array([[1,2], [3,4]]) b = np.array([[1,2], [2,1]]) a + b Out [6]: array([[2, 4], [5, 5]]) In [7]: # 矩阵减法 a - b Out [7]: array([[0, 0], [1, 3]])
值得注意的是,在NumPy中,乘积符号“*”代表矩阵中对应元素相乘,而非矩阵乘积。矩阵乘积可以用dot()函数来实现,代码如下:
In [8]: # 矩阵对应元素相乘 a * b Out [8]: array([[1, 4], [6, 4]]) In [9]: # 矩阵乘法 np.dot(a, b) Out [9]: array([[ 5, 4] [11, 10]])
(2)数组索引与切片
NumPy支持强大的数组索引和切片功能,代码如下:
In [10]: # 随机生成一维数组 # low、high分别表示随机整数的下界和上界,size表示数组大小 a = np.random.randint(low=1, high=20, size=5) a Out [10]: array([12, 10, 14, 6, 9]) In [11]: # 获取一维数组a中索引为2~3的元素 a[2:4] Out [11]: array([14, 6]) In [12]: # 随机生成二维数组 a = np.random.randint(low=1, high=20, size=(5,5)) a Out [12]: array([[16, 2, 16, 8, 6], [11, 18, 5, 4, 19], [ 7, 19, 2, 10, 16], [14, 7, 17, 18, 3], [ 6, 4, 18, 18, 3]]) In [13]: # 第2~4行中的第2列元素(此处行列均为索引) a[2:5, 2] Out [13]: array([ 2, 17, 18]) In [14]: # a中的1~2行,输出所有列 a[1:3, ] Out [14]: array([[11, 18, 5, 4, 19], [ 7, 19, 2, 10, 16]]) In [15]: # 对a中1~3行、2~3列进行切片并赋值给b b = a[1:4, 2:4] b Out [15]: array([[ 5, 4], [ 2, 10], [17, 18]])
另外,还可以通过reshape()函数改变数组形状。
In [16]: # 改变数组形状 a = np.random.randint(low=1, high=20, size=9) a Out [16]: array([19, 8, 4, 17, 6, 16, 15, 2, 15]) In [17]: a.reshape(3,3) a Out [17]: array([[19, 8, 4], [17, 6, 16], [15, 2, 15]])
(3)矩阵拼接
通过NumPy可以方便地实现多个矩阵之间的拼接,代码如下:
In [18]: a = np.random.randint(low=1, high=20, size=(3,3)) a Out [18]: array([[11, 1, 1], [10, 4, 8], [ 6, 4, 5]]) In [19]: b = np.random.randint(low=1, high=20, size=(3,3)) b Out [19]: array([[19, 10, 19], [16, 6, 6], [14, 18, 15]]) In [20]: # 垂直拼接 np.vstack((a,b)) Out [20]: array([[11, 1, 1], [10, 4, 8], [ 6, 4, 5], [19, 10, 19], [16, 6, 6], [14, 18, 15]]) In [21]: # 水平拼接 np.hstack((a,b)) Out [21]: array([[11, 1, 1, 19, 10, 19], [10, 4, 8, 16, 6, 6], [ 6, 4, 5, 14, 18, 15]])
(4)统计运算
对于数组元素的统计运算,NumPy提供了十分便利的方法,既可以通过a.min()直接统计数组a中最小元素,也可以通过np.mean(a,axis=0)来对某一维度进行统计。下面介绍NumPy中常用的几种统计运算。
In [22]: a = np.random.randint(low=1, high=20, size=(2,3)) a Out [22]: array([[ 1 9 3], [11 7 8]]) In [23]: # 最小值 a.min() Out [23]: 1 In [24]: # 最大值 a.max() Out [24]: 11 In [25]: # 求和 a.sum() Out [25]: 39 In [26]: # 平均值 a.mean() Out [26]: 6.5 In [27]: # 标准差 a.std() Out [27]: 3.4520525295346629 In [28]: # 每列最大值 np.amax(a, axis = 0) Out [28]: array([11, 9, 8]) In [29]: # 每行最小值 np.amin(a, axis = 1) Out [29]: array([1, 7]) In [30]: # 每行标准差 np.std(a, axis = 1) Out [30]: array([ 3.39934634, 1.69967317]) In [31]: # 每列方差 np.var(a, axis = 0) Out [31]: array([ 25. , 1. , 6.25])
(5)数组排序
NumPy可以按任意维度对数组进行排序,支持3种不同的排序方法:快速排序(quicksort)、归并排序(mergesort)和堆排序(heapsort),可以通过参数指定不同的排序方法,若不指定,则默认使用快速排序。此外,NumPy还支持按照某种属性进行排序。
In [32]: # 生成矩阵 a = np.random.randint(low=1, high=20, size=(3,3)) a Out [32]: array([[10, 18, 1], [10, 5, 5], [ 2, 18, 2]]) In [33]: # 默认按行进行排序 np.sort(a) Out [33]: array([[ 1, 10, 18], [ 5, 5, 10], [ 2, 2, 18]]) In [34]: # 按列排序 np.sort(a, axis=0) Out [34]: array([[ 2, 5, 1], [10, 18, 2], [10, 18, 5]]) In [35]: # 现有3列属性,分别为id、salary和age schema = [('id', int), ('salary', int), ('age', int)] # 3条记录 records = [(1, 3000, 21), (2, 5000, 30), (3, 8000, 38)] # 创建NumPy数组 a = np.array(records, dtype=schema) # 将数组按照salary进行排序 np.sort(a, order='salary') Out [35]: array([(1, 3000, 21), (2, 5000, 30), (3, 8000, 38)], dtype=[('id', '<i8'), ('salary', '<i8'), ('age', '<i8')])
本节主要介绍了NumPy的常用基础功能,使读者对NumPy有初步的了解。若想学习更多NumPy的使用技巧,可参考NumPy官方文档。
2.1.3 Pandas
Pandas是一个基于NumPy的开源软件包,可以方便快捷地进行数据处理和数据分析。Pandas功能十分强大,它集成了大量的库和数据模型,提供了很多数据操作的方法,基本可以满足绝大多数实际应用中的数据处理和分析需求。
Pandas主要实现了两个数据结构:Series和DataFrame。Series是一维数组,类似于NumPy中的一维数组,能够保存任何数据类型(如整型、字符串、浮点数和Python对象等)。Series中的数据通过索引进行标记,从而可以方便、高效地通过索引访问数据。DataFrame是一个二维数据结构,不同的列可以存放不同类型的数据,有点类似数据库中的表。此外,Pandas还提供了数据库用户都比较熟悉的数据操作方法。通过如下代码可引入Pandas包:
import pandas as pd
1.Series和DataFrame基本操作
Series由一组数据及其相应的索引组成,可以通过如下方式创建:
In [36]: import pandas as pd s = pd.Series([1, 2, 3, 4, 5], index=['a', 'b', 'c', 'd', 'e']) s Out [36]: a 1 b 2 c 3 d 4 e 5 dtype: int64
也可通过Python中的dict来创建,如下:
In [37]: records = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5} s = pd.Series(records) s Out [37]: a 1 b 2 c 3 d 4 e 5 dtype: int64
Series也可以像NumPy数组一样支持强大的索引和切片功能,代码如下:
In [38]: s[1:3] Out [38]: b 2 c 3 dtype: int64 In [39]: s[(s > 1) & (s < 5)] Out [39]: b 2 c 3 d 4 dtype: int64 In [40]: s[s > s.median()] Out [40]: d 4 e 5 dtype: int64 In [41]: s['c'] Out [41]: 3
DataFrame是Pandas中最常用的数据结构,它可以包含多个列,每一列可以是不同的数据类型,可将其看作电子表格、SQL表或Series对象的字典。通过如下方式创建DataFrame:
默认行索引是从0开始的正整数,也可以指定行索引:
然后即可通过行索引对数据进行选取:
In [44]: df1.loc[12] Out [44]: a 2 b 6 c 10 Name: 12, dtype: int64 In [45]: df1.iloc[2] Out [45]: a 3 b 7 c 11 Name: 13, dtype: int64
也可以采用类似dict的方式按列选取、设置和删除数据:
上述按列选取也可以通过df1.c来实现,结果是一样的。另外,还可以通过布尔值选取,例如:
2.算术运算和数据对齐
Pandas中的DataFrame支持丰富的算术运算,代码如下:
另外,NumPy中的一些函数也可直接应用于DataFrame,如exp、log等,代码如下:
除了单个DataFrame的运算之外,Pandas也支持多个DataFrame之间的算术运算:
此外,Pandas可以根据索引实现数据自动对齐,索引不重合的部分被置为NaN,代码如下:
3.统计与汇总数据
Pandas中的对象包含许多统计和汇总数据的方法,大多是聚合函数,如sum()、mean()等,如下所示:
也可以直接通过describe()函数计算各种统计信息:
4.数据排序
Pandas支持多种方式的排序,如按索引排序、按值排序等。通过sort_index()方法可实现按索引级别对Pandas对象(如Series、DataFrame等)进行排序。
通过sort_values()方法可实现Pandas对象按值排序。Series通过sort_values()方法对对象内的值进行排序,DataFrame通过该方法按列或行对DataFrame进行排序。
5.函数应用
Pandas支持通过apply()方法将自定义函数应用到DataFrame的行和列上:
也可以采用下面形式:
In [74]: def func1(df, a, b=1): return (df.max() - df.min() + a) * b df.apply(func1, args=(2,), b=2) Out [74]: b 6 c 12 a 16 d 6 dtype: int64
6.缺省值处理
在数据分析中,数据缺省的情况经常出现,在Pandas中以NaN表示数据缺省。Pandas提供了多种缺省值处理函数,可以通过isnull()、notnull()来判断数据是否缺省,这两个函数的返回值均为一个包含布尔值的对象,布尔值表示该元素是否为缺省。isnull()函数的返回值True表示缺省值,False表示非缺省值,notnull()函数则相反。
可通过dropna()方法丢弃包含缺省值的行或列,默认丢弃含有缺省值的行,也可通过指定参数只丢弃全为缺省值的行或列:
也可对缺省值进行填充处理:
7.时间序列
实际应用中经常需要对时间进行一系列处理,Pandas包含了许多关于时间序列的工具,使其非常适于处理时间序列。
In [83]: time = pd.Series(np.random.randn(8), index =pd.date_range('2018-06-01', periods = 8)) time Out [83]: 2018-06-01 1.187882 2018-06-02 0.667788 2018-06-03 -1.098277 2018-06-04 -0.363420 2018-06-05 -0.257057 2018-06-06 0.543445 2018-06-07 2.671669 2018-06-08 -0.101492 Freq: D, dtype: float64
对于时间序列的数据,可灵活地通过时间范围对数据进行切片索引:
In [84]: time['2018-06-03'] Out [84]: -1.0982767096049197 In [85]: time['2018/06/03'] Out [85]: -1.0982767096049197 In [86]: time['2018-06-03':'2018-06-06'] Out [86]: 2018-06-03 -1.098277 2018-06-04 -0.363420 2018-06-05 -0.257057 2018-06-06 0.543445 Freq: D, dtype: float64 In [87]: time['2018-06'] Out [87]: 2018-06-01 1.187882 2018-06-02 0.667788 2018-06-03 -1.098277 2018-06-04 -0.363420 2018-06-05 -0.257057 2018-06-06 0.543445 2018-06-07 2.671669 2018-06-08 -0.101492 Freq: D, dtype: float64
对于带有重复索引的时间序列,可以通过groupby()对数据进行聚合:
In [88]: dates = pd.DatetimeIndex(['2018-06-06','2018-06-07', '2018-06-07','2018-06-07', '2018-06-08','2018-06-09']) time = pd.Series(np.arange(6),index = dates) time Out [88]: 2018-06-06 0 2018-06-07 1 2018-06-07 2 2018-06-07 3 2018-06-08 4 2018-06-09 5 dtype: int64 In [89]: time.groupby(level=0).sum() Out [89]: 2018-06-06 0 2018-06-07 6 2018-06-08 4 2018-06-09 5 dtype: int64
8.数据存取
Pandas支持多种文件形式的数据存储与读取,如csv、json、excel、parquet等。
2.1.4 Matplotlib
Matplotlib是一个强大的Python数据可视化库,可以方便地创建多种类型的图表。Matplotlib十分适用于交互式制图,也可以将其作为制图空间,嵌入GUI应用程序中。
1.导入Matplotlib
和NumPy、Pandas类似,可通过如下语句载入Matplotlib库:
import matplotlib.pyplot as plt
下面是一个简单的折线图绘制示例。
In [94]: import numpy as np import matplotlib.pyplot as plt x = np.array([1,2,3,4,5,6]) y = np.array([1,5,4,6,10,6]) # plot中参数x、y分别为横、纵坐标数据,b表示折线颜色为蓝色 plt.plot(x,y,'b') plt.show()
输出结果如图2-5所示。
图2-5 输出结果(折线图)
2.设置坐标轴
创建一个简单的折线图后,下面介绍如何设置坐标轴。
In [95]: x = np.array([1,2,3,4,5,6]) y = x*3 +1 plt.plot(x, y) # 设置x轴、y轴显示的范围 plt.xlim((3, 6)) plt.ylim((5, 35)) # 设置x轴、y轴的标签 plt.xlabel('x') plt.ylabel('y') # 设置x轴、y轴的刻度 plt.xticks([3, 3.5, 4, 4.5, 5, 5.5, 6]) plt.yticks([10, 15, 20, 25, 30, 35]) plt.show()
输出结果如图2-6所示。
图2-6 输出结果设置折线图的坐标轴
如图2-6所示,Matplotlib可通过xlim()和ylim()来设置x轴和y轴的显示范围,通过xlabel()、ylabel()设置x轴和y轴的标签名称,通过xticks()和yticks()设置x轴和y轴的刻度。此外,还可以设置x轴和y轴的位置:
In [96]: x = np.linspace(-6, 6, 50) y = x**2 plt.plot(x, y, color='red') # 线条为红色 # 获取当前的坐标轴 ax = plt.gca() # 设置右边框和上边框 ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') # 设置x坐标轴为下边框 ax.xaxis.set_ticks_position('bottom') # 设置y坐标轴为左边框 ax.yaxis.set_ticks_position('left') # 设置x轴、y轴在(0,0)的位置 ax.spines['bottom'].set_position(('data', 0)) ax.spines['left'].set_position(('data', 0)) plt.show()
输出结果如图2-7所示。
图2-7 输出结果(设置x轴、y轴)
3.绘制多种类型的数据图
Matplotlib支持绘制多种类型的数据图,如直方图、散点图、饼状图、等高线图等。
(1)直方图
Matplotlib通过plt.hist()方法绘制直方图:
In [97]: mu ,sigma = 0, 1 sampleNum = 1024 np.random.seed(0) s = np.random.normal(mu, sigma, sampleNum) plt.xlim((-3, 3)) plt.ylim((0, 0.5)) plt.hist(s, bins=50, normed=True) plt.show()
输出结果如图2-8所示。
(2)散点图
Matplotlib通过plt.scatter()方法绘制散点图:
In [98]: X = np.random.normal(0, 1, 1024) Y = np.random.normal(0, 1, 1024) # 绘制散点图 plt.scatter(X, Y, s=75) plt.show()
图2-8 输出结果(直方图)
输出结果如图2-9所示。
图2-9 输出结果(散点图)
(3)饼状图
Matplotlib通过pie()方法绘制饼状图:
In [99]: X = [1,2,3,4] # 饼状图中每个部分离中心点的距离,其中0.2表示图中远离中心的A部分 explode=(0.2,0,0,0) plt.pie(X, labels=['A','B','C','D'], explode=explode, autopct='percent:%1.1f%%' # 每个部分的比例标签 ) plt.axis('equal') # 防止饼状图被压缩成椭圆 plt.show()
输出结果如图2-10所示。
图2-10 输出结果(饼状图)
(4)等高线图
Matplotlib通过contour()方法绘制等高线图:
In [100]: delta = 0.025 x = np.arange(-2.0, 2.0, delta) y = np.arange(-1.5, 1.5, delta) X, Y = np.meshgrid(x, y) Z1 = np.exp(-X**2 - Y**2) Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2) Z = (Z1 - Z2) * 2 C = plt.contour(X, Y, Z) plt.clabel(C, inline=True, fontsize=10) plt.show()
输出结果如图2-11所示。
图2-11 输出结果(等高线图)
(5)Figure对象
在Matplotlib中,Figure(图像)对象是整个绘图区域,可以包含一个或者多个axes,其中每个axes拥有自己的坐标系,是一个单独的绘图区域,axes可以在整个绘图区域任意摆放。用户可以通过subplot()来绘制多个子图,通过subplot()创建的子图只能按网格整齐排列。
In [101]: plt.figure() # 绘制一个子图,其中row=2,col=2,该子图占第1个位置 plt.subplot(2, 2, 1) plt.plot([0, 1], [0, 1]) # 绘制一个子图,其中row=2,col=2,该子图占第2个位置 plt.subplot(2, 2, 2) plt.plot([0, 1], [1, 0]) plt.subplot(2, 2, 3) plt.plot([1, 2], [2, 1]) plt.subplot(2, 2, 4) plt.plot([1, 2], [1, 2]) plt.show()
输出结果如图2-12所示。
图2-12 输出结果(Figure对象)
2.1.5 scikit-learn
scikit-learn是一个包含大量经典机器学习模型的开源工具库,用Python实现,包括数据预处理、分类、回归、降维、模型选择等常用的机器学习算法,可见scikit-learn是一个功能十分强大的机器学习工具包。XGBoost配合scikit-learn使用,可以说是如虎添翼。因scikit-learn中的算法较多,在此不做一一介绍,后续示例用到时再进行针对性的讲解。关于scikit-learn的资料非常多,市面上也有很多介绍scikit-learn的书籍,想要深入了解的读者可以自行选择相关资料进行学习。