1.2 Python 库和功能
现在,我们已经对数据科学工作流的每一步有了初步的理解,下面来看看在每一步中,存在哪些有用的 Python 库和功能可供选择。
1.2.1 获取
访问数据常见的方式之一是通过 REST 风格的 API 接口,需要知道的库是 Python Request库(http://www.python-requests.org/en/latest/)。它被称为给人类使用的HTTP,为 API 的交互提供了一个整洁和简单的方式。
让我们来看一个使用 Requests 进行交互的例子,它从 GitHub 的 API 中拉取数据。在这里,我们将对该 API 进行调用,并请求某个用户的 starred 库列表。
import requests
r = requests.get(r"https://api.github.com/users/acombs/starred")
r.json()
这个请求将以 JSON 文档的形式,返回用户已经标记为 starred 的所有存储库以及它们的属性。图 1-2 是上述调用后输出结果的一个片段。
Requests 库有数量惊人的特性——这里无法全部涵盖,我建议你看看上面提供的链接所指向的文档。
1.2.2 检查
由于数据检查是机器学习应用开发中关键的一步,我们现在来深入了解几个库,它们将在此项任务中很好地为我们服务。
1.Jupyter 记事本
许多库有助于减轻数据检查过程的工作负荷。首先是带有 IPython(http://ipython.org/)的 Jupyter 记事本。这是一个全面的、交互式的计算环境,对于数据探索是非常理想的选择。和大多数开发环境不同,Jupyter 记事本是一个基于 Web 的前端(相对于 IPython 的内核而言),被分成单个的代码块或单元。根据需要,单元可以单独运行,也可以一次全部运行。这使得开发人员能够运行某个场景,看到输出结果,然后回到代码,做出调整,再看看所产生的变化——所有这些都无需离开记事本。图 1-3 是在Jupyter 记事本中进行交互的样例。
请注意,我们在这里做了一系列的事情,并不仅仅是和 IPython 的后端进行交互,而且也和终端 shell 进行了交互。这个特定的实例运行了 Python 3.5 的内核,但如果你愿意,也可以很容易地运行 Python 2.X 的内核。在这里,我们已经引入了 Python os 库,并进行了一次调用,找到当前的工作目录(单元#2),你可以看到输入代码单元格下方的输出。
然后,我们在单元#3 中使用 os 库改变了这个目录,但是在单元#4 中停止使用 os 库,而是开始使用基于 Linux 的命令。这是通过在单元前添加!符号来完成的。在单元#6 中可以看到,我们甚至能够将 shell 的输出保存到一个 Python 变量(file_two)。这是一个很棒的功能,使文件操作变成了一项简单的任务。
现在,让我们来看看使用该记事本所进行的一些简单的数据操作。这也是我们首次介绍另一个不可或缺的库:pandas。
2.Pandas
Pandas 是一个卓越的数据分析工具。根据 Pandas 的文档(http://pandas.pydata.org/pandas-docs/version/0.17.1/):
它有一个更广泛的目标,就是成为任何语言中,最强大和灵活的开源数据分析/操作工具。
即使它还没有达到这个目标,也不会差得太远。现在让我们来看看。
import os
import pandas as pd
import requests
PATH = r'/Users/alexcombs/Desktop/iris/'
r = requests.get('https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data')
with open(PATH + 'iris.data', 'w') as f:
f.write(r.text)
os.chdir(PATH)
df = pd.read_csv(PATH + 'iris.data', names=['sepal length', 'sepal width','petal length', 'petal width', 'class'])
df.head()
前面的代码和屏幕截图 如 图 1-4 所示,我们已经从https://archive.ics.uci.edu/ml/datasets/Iris 下载了一个经典的机器学习数据集:iris.data,并将其写入 iris 目录。这实际上是一个 CSV 文件,通过 Pandas,我们进行了一个调用并读取了该文件。我们还增加了列名,因为这个特定的文件缺一个标题行。如果该文件已经包含了一个标题行,Pandas 会自动解析并反映这一点。和其他 CSV库相比,Pandas 将其变为一个简单的操作。
解析文件只是该库的一个小功能。对适合于单台机器的数据集而言,Pandas 是个终极的工具,这有点像 Excel。就像流行的电子表格程序,操作的基本单位是表格形式的数据列和行。在 Pandas 的术语中,数据列称为系列(Series),而表格称为数据框(DateFrame)。
使用之前截屏中同样的 iris 数据框,让我们来看看几个常见的操作。
df['sepal length']
前面的代码生成图 1-5 的输出。
第一个操作是通过列名,从数据框中选择某一列。执行数据切片的另一种方式是使用.ix[row,column]标注。让我们使用下面这个标注,来选择前两列和前四行。
df.ix[:3, :2]
前面的代码生成图 1-6 的输出。
使用.ix 标注和 Python 列表切片的语法,我们能够选择该数据框中的一小片。现在,让我们更进一步,使用列表迭代器并只选择描述 width 的列。
df.ix[:3, [x for x in df.columns if 'width' in x]]
前面的代码生成图 1-7 所示的输出。
我们在这里所做的是创建一个列表,该列表是所有列的一个子集。前面的df.columns 返回所有列的列表,而我们的迭代使用了一个条件查询,只选择标题中含有width 字样的列。显然,在这种情况下,我们可以很容易地拼写出希望在列表中出现的列,但是这里展示了处理大规模数据集时该库所具有的能力。
我们已经看到了,如何基于其在数据框中的位置,来选择数据的分片,现在来看看另一种选择数据的方法。这次,我们将根据某些特定的条件,来选择数据的一个子集。我们首先列出所有可用的唯一类,然后选择其中之一。
df['class'].unique()
前面的代码生成图 1-8 的输出。
df[df['class']=='Iris-virginica']
在图 1-9 所示最右侧的一列中,我们可以看到数据框只包含 Iris-virginica 类的数据。事实上,选择之后图 1-11 中数据框的大小是 50 行,比图 1-10 中原来的 150 行要小一些。
df.count()
df[df['class']=='Iris-virginica'].count()
我们还可以看到,在左侧的索引保留了原始行号。现在,可以将这些数据保存为一个新的数据框并重置索引,如下面的代码和截图 1-12 所示。
virginica = df[df['class']=='Iris-virginica'].reset_index(drop=True)
virginica
我们通过在某个列上放置条件来选择数据,现在来添加更多的条件。我们将回到初始的数据框,并使用两个条件选择数据。
df[(df['class']=='Iris-virginica')&(df['petal width']>2.2)]
上述代码生成图 1-13 的输出。
数据框现在只包含来自 Iris-virginica 类、而且花瓣宽度大于 2.2 的数据。
现在,让我们使用 Pandas,从虹膜数据集中获取一些快速的描述性统计数据。
df.describe()
上述代码生成图 1-14 的输出。
随着数据框的.describe()方法被调用,我们收到了各相关列的描述性统计信息(请注意,类别信息被自动删除了,因为它在这里是不相关的)。如果想要更为详细的信息,还可以传入自定义的百分比。
df.describe(percentiles=[.20,.40,.80,.90,.95])
上述代码生成图 1-15 的输出。
接下来,让我们检查这些特征之间是否有任何相关性。这可以通过在数据框上调用.corr()来完成。
df.corr()
上述代码生成图 1-16 的输出。
默认地,系统返回每个行-列对中的 Pearson 相关系数。通过传递方法的参数,还可以切换到 Kendall's tau 或 Spearman's 秩相关系数(例如,.corr(method="spearman")或.corr(method="kendall"))。
3.可视化
目前为止,我们已经看到如何选择数据框的某一部分,并从数据中获取汇总的统计信息,现在让我们学习如何通过可视化的方式来观测数据。不过首先要回答的问题是,为什么要花费心思进行可视化的视察呢?来看一个例子就能明白这是为什么了。
表 1-1 展示了四组不同序列的 x 值和 y 值的汇总统计。
表 1-1
序列的x和y | 取值 |
x的平均值 | 9 |
y的平均值 | 7.5 |
序列的x样本方差 | 11 |
序列的y样本方差 | 4.1 |
x和y之间的相关性 | 0.816 |
回归线 | y=3.00+0.500x |
基于四组序列拥有相同的汇总统计,我们可能会认为这些系列的可视化看上去也是相似。我们当然是错误的,非常错误。这四个序列是安斯库姆四重奏的一部分,他们被刻意制造出来用于说明可视化数据检查的重要性。每个序列绘制在图 1-17 中。
安斯库姆四重奏的网址 :https://en.wikipedia.org/wiki/Anscombe%27squartet。
显然,经过可视化的观察之后,我们不再会认为这些数据集是相同的。所以,现在我们能理解可视化的重要性了,下面来看看一对用于可视化的、很有价值的 Python 库。
Matplotlib 库
我们将要看到的第一个库是 matplotlib。这是 Python 绘图库的鼻祖了。最初人们创建它是为了仿效 MATLAB 的绘图功能,现在它自己已经发展成为特性完善的库了,并拥有超多的功能。对于那些没有 MATLAB 背景的使用者,可能很难理解所有这些部件是如何共同协作来创造图表的。
我们将所有的部件拆分为多个逻辑模块,便于大家理解都发生了些什么。在深入理解matplotlib 之前,让我们先设置 Jupyter 记事本,以便看清每个图像。要做到这一点,需要将以下几行添加到 import 声明中。
import matplotlib.pyplot as plt
plt.style.use('ggplot')
%matplotlib inline
import numpy as np
第一行引入了 matplotlib,第二行将风格设置为近似 R 中的 ggplot 库(这需要matplotlib 1.41),第三行设置插图,让它们在记事本中可见,而最后一行引入了 numpy。
本章稍后,我们将在一些操作中使用 numpy。
现在,让我们使用下面的代码,在鸢尾花 Iris 数据集上生成第一个图:
fig, ax = plt.subplots(figsize=(6,4))
ax.hist(df['petal width'], color='black');
ax.set_ylabel('Count', fontsize=12)
ax.set_xlabel('Width', fontsize=12)
plt.title('Iris Petal Width', fontsize=14, y=1.01)
前面的代码生成图 1-18 中的输出。
即使是在这个简单的例子中,也发生了很多事情,让我们来逐行分析。第一行创建了宽度为 6 英寸和高度为 4 英寸的一个插图。然后,我们通过调用.hist()并传入数据,依照 iris 数据框绘制了花瓣宽度的直方图。这里还将直方图中柱子的颜色设置为 black(黑色)。接下来的两行分别在 y 轴和 x 轴上放置标签,最后一行为全图设置了标题。其中使用y 轴的参数调整了标题在 y 轴方向相对于图片顶部的位置,并微微增加了默认字体的大小。
这使得我们从花瓣宽度的数据得到了一个很漂亮的直方图。现在,让我们进一步扩展,为iris 数据集的每一列生成直方图。
fig, ax = plt.subplots(2,2, figsize=(6,4))
ax[0][0].hist(df['petal width'], color='black');
ax[0][0].set_ylabel('Count', fontsize=12)
ax[0][0].set_xlabel('Width', fontsize=12)
ax[0][0].set_title('Iris Petal Width', fontsize=14, y=1.01)
ax[0][1].hist(df['petal length'], color='black');
ax[0][1].set_ylabel('Count', fontsize=12)
ax[0][1].set_xlabel('Lenth', fontsize=12)
ax[0][1].set_title('Iris Petal Lenth', fontsize=14, y=1.01)
ax[1][0].hist(df['sepal width'], color='black');
ax[1][0].set_ylabel('Count', fontsize=12)
ax[1][0].set_xlabel('Width', fontsize=12)
ax[1][0].set_title('Iris Sepal Width', fontsize=14, y=1.01)
ax[1][1].hist(df['sepal length'], color='black');
ax[1][1].set_ylabel('Count', fontsize=12)
ax[1][1].set_xlabel('Length', fontsize=12)
ax[1][1].set_title('Iris Sepal Length', fontsize=14, y=1.01)
plt.tight_layout()
上述代码的输出显示如图 1-19 所示。
显然,这不是最有效的编码方法,但是对于展示 matplotlib 是如何工作的很有用处。
请注意,我们现在是通过 ax 数组来绘制四个子插图,而不是之前例子中的单一子插图对象ax。新增加的代码是调用 plt.tight_layout(),该方法将很好地自动调整子插图,以避免排版上显得过于拥挤。
现在来看看 matplotlib 所提供的一些其他类型的画图模式。一个有用的类型是散点图。
这里,我们将在 x 轴和 y 轴分布绘画花瓣宽度和花瓣长度。
fig, ax = plt.subplots(figsize=(6,6))
ax.scatter(df['petal width'],df['petal length'], color='green')
ax.set_xlabel('Petal Width')
ax.set_ylabel('Petal Length')
ax.set_title('Petal Scatterplot')
上述的代码生成了图 1-20 所示的输出。
如前所述,我们可以添加多个子插图,来检视每个方面。
我们可以考察的另一种类型是简单的线图。这里来看看花瓣长度的插图。
fig, ax = plt.subplots(figsize=(6,6))
ax.plot(df['petal length'], color='blue')
ax.set_xlabel('Specimen Number')
ax.set_ylabel('Petal Length')
ax.set_title('Petal Length Plot')
上述的代码生成了图 1-21 所示的输出。
基于这个简单的线图,我们已经可以看到对于每个类别存在鲜明的长度差别——请记住样本数据集在每个类别拥有 50 个排序的样例。这就告诉我们,花瓣长度很可能是用于区分类别的一个有用特征。
让我们来看看 matplotlib 库中最后一个类型的图表:条形图。这也许是最为常见的图表之一。这里将使用三类鸢尾花中每个特征的平均值绘制一个条形图,而且为了让其更有趣,我们将使用堆积条形图,它附带了若干新的 matplotlib 特性。
fig, ax = plt.subplots(figsize=(6,6))
bar_width = .8
labels = [x for x in df.columns if 'length' in x or 'width' in x]
ver_y = [df[df['class']=='Iris-versicolor'][x].mean() for x in labels]
vir_y = [df[df['class']=='Iris-virginica'][x].mean() for x in labels]
set_y = [df[df['class']=='Iris-setosa'][x].mean() for x in labels]
x = np.arange(len(labels))
ax.bar(x, vir_y, bar_width, bottom=set_y, color='darkgrey')
ax.bar(x, set_y, bar_width, bottom=ver_y, color='white')
ax.bar(x, ver_y, bar_width, color='black')
ax.set_xticks(x + (bar_width/2))
ax.set_xticklabels(labels, rotation=-70, fontsize=12);
ax.set_title('Mean Feature Measurement By Class', y=1.01)
ax.legend(['Virginica','Setosa','Versicolor'])
上述的代码生成图 1-22 所示的输出。
为了生成条形图,我们需要将 x 和 y 的值传递给.bar()方法。在这种情况下,x 值将只是我们感兴趣的特征的长度的数组,在这个例子中是 4,或者是数据框中列的数量。
函数 np.arange()是产生这个数值的简单方法,但也可以轻松地手动输入这个数组。由于我们不想在 x 轴显示 1 到 4,因此调用了.set_xticklabels()方法并传入想要显示的列名。为了让 x 轴的标签对齐,我们还需要调整标签之间的间隔。这就是为什么将 xticks设置为 x 加上 bar_width 值的一半,而我们先前已经将 bar_width 设置为 0.8。这里 y值来自每个类别中特征的平均值。然后,通过调用.bar()绘制每个插图。需要注意的是,我们为每个序列传入一个 bottom 参数,这个参数将该序列的 y 点最小值设置为其下面那个序列的 y 点最大值。这就能创建堆积条形图。最后,添加了一个图例来描述每个序列。
按照从顶部到底部条形放置的顺序,我们依次在图例中插入了相应的名称。
Seaborn 库
我们接下来将看到的可视化库被称为 seaborn(http://stanford.edu/~mwaskom/software/seaborn/index.html)。它是专门为统计可视化而创建的库。事实上,seaborn 可以和 pandas 数据框完美地协作,框中的列是特征而行是观测的样例。这种数据框的风格被称为整洁的数据,而且它是机器学习应用中最常见的形式。
现在让我们来看看 seaborn 的能力。
import seaborn as sns
sns.pairplot(df, hue="class")
仅仅通过这两行代码,我们就可以得到图 1-23 所示的输出。
就在刚刚我们详细地讨论了 matplotlib 错综复杂的细微之处,而生成这张图的简单性却显而易见。仅仅使用了两行代码,所有的特征都已经被绘画出来,彼此对照并标上了正确的标签。那么,当 seaborn 使得这种可视化变得如此简单的时候,学习 matplotlib是在浪费时间吗?幸运的是,情况并非如此,seaborn 是建立在 matplotlib 之上的。
事实上,我们可以使用所学的 matplotlib 知识来修改并使用 seaborn。让我们来看看另一个可视化的例子。
fig, ax = plt.subplots(2, 2, figsize=(7, 7))
sns.set(style='white', palette='muted')
sns.violinplot(x=df['class'], y=df['sepal length'], ax=ax[0,0])
sns.violinplot(x=df['class'], y=df['sepal width'], ax=ax[0,1])
sns.violinplot(x=df['class'], y=df['petal length'], ax=ax[1,0])
sns.violinplot(x=df['class'], y=df['petal width'], ax=ax[1,1])
fig.suptitle('Violin Plots', fontsize=16, y=1.03)
for i in ax.flat:
plt.setp(i.get_xticklabels(), rotation=-90)
fig.tight_layout()
以上代码行生成图 1-24 所示的输出。
这里,我们为 4 个特征分别生成了小提琴图。小提琴图显示了特征的分布情况。例如,我们可以很容易地看到类别 irissetosa 的花瓣长度高度聚集在 1~2 厘米之间,而类别iris-virginica 分散在 4~7 厘米之间。我们还可以看到,之前在构建 matplotlib图形时使用了许多相同的代码。这里主要的区别在于加入了 sns.plot()调用来取代之前的 ax.plot()调用。我们还使用了 fig.suptitle()方法,在所有的子图上添加了一个总标题,而不是在每个单独的子图上各自添加标题。另一个明显的添加部分,是每个子图的遍历取代了之前 xticklabels 的轮换。我们调用 ax.flat(),遍历每个子图的轴,并使用.setp()设置特定的属性。这可以让我们不再需要像之前 matplotlib 子图代码那样,单独地敲打 ax[0][0]…ax[1][1],并设置属性。
我们在这里使用的图是一个很好的开始,但是你可以使用 matplotlib 和 seaborn 创建上百种不同风格的图形。我强烈建议深入研究这两个库的文档,这将是非常值得的。
1.2.3 准备
我们已经学到了很多有关检查数据的内容,现在让我们开始学习如何处理和操作数据。
这里你将了解 pandas 的 Series.map()、Series.apply()、DataFrame.apply()、DataFrame.applymap()和 DataFrame.groupby()方法。这些对于处理数据而言是非常有价值的,而且在特征工程的机器学习场景下特别有用,我们将在后面的章节详细地讨论这个概念。
1.Map
Map 方法适用于序列数据,所以在我们的例子中将用它来转变数据框的某个列,它就是一个 pandas 的序列。假设我们觉得类别的名字太长了,并且希望使用特殊的 3 字母代码系统对其进行编码。为了实现这点,我们将使用 map 方法并将一个 Python 字典作为其参数。这里将为每个单独的鸢尾花类型传入替换的文本。
df['class'] = df['class'].map({'Iris-setosa': 'SET', 'Iris-virginica':
'VIR', 'Iris-versicolor': 'VER'})
df
前面的代码生成图 1-25 的输出。
下面来看看这里做了些什么。我们在现有 class 列的每个值上运行了 map 的方法。
由于每个值都能在 Python 字典中找到,所以它会被添加到被返回的序列。我们为返回序列赋予了相同的 class 名,所以它替换了原有的 class 列。如果我们选择了一个不同的名字,例如 shortclass,那么这一列会被追加到数据框,然后我们将有初始的 class 列外加新的 short class 列。
我们还可以向 map 方法传入另一个序列或函数,来执行对某个列的转变,但这个功能在 apply 方法也是可用的,下面这节会讨论该方法。字典的功能是 map 方法所独有的,这也是选择map而不是apply进行单列转变的最常见原因。现在让我们来看看apply方法。
2.Apply
Apply 的方法让我们既可以在数据框上工作,也可以在序列上工作。我们将从一个也能使用 map 的例子开始,然后再讨论只能使用 apply 的示例。
继续使用 iris 数据框,让我们根据花瓣的宽度来创建新的列。之前我们看到花瓣宽度的平均值为 1.3。现在,在数据框中创建一个新的列——宽花瓣,它包含一个基于 petal width 列的二进制值。如果花瓣宽度等于或宽于中值,那么我们将其编码为 1,而如果它小于中值,我们将其编码为 0。为了实现这点,这里将在 petal width 这列使用 apply方法。
df['wide petal'] = df['petal width'].apply(lambda v: 1 if v >= 1.3 else 0)
df
前面的代码生成图 1-26 所示的输出。
这里发生了几件事情,让我们一步一步来看。首先,我们为所要创建的列名简单地使用了列选择的语法,向数据框追加一个新的列,在这个例子中是 wide petal。我们将这个新列设置为 apply 方法的输出。这里在 petal width 列上运行 apply,并返回了 wide petal 列的相应值。Apply 方法作用于 petal width 列的每个值。如果该值大于或等于1.3,函数返回 1;否则,返回 0。这种类型的转换在机器学习领域是相当普遍的特征工程转变,所以最好熟悉如何执行它。
现在让我们来看看如何在数据框上使用 apply,而不是在一个单独的序列上。现在将基于 petal area 来创建一个新的特征。
df['petal area'] = df.apply(lambda r: r['petal length'] * r['petal width'],axis=1)
df
前面的代码生成图 1-27 的输出。
请注意,这里不是在一个序列上调用 apply,而是在整个数据框上。此外正是由于在整个数据框上调用了 apply,我们传送了 axis=1 的参数来告诉 pandas,我们要对行运用函数。如果传入了 axis=0,那么该函数将对列进行操作。这里,每列都是被顺序地处理,我们选择将 petal length 的值和 petal width 的值相乘。得到的序列就将成为数据框中的 petal area 列。这种能力和灵活性使得 pandas 成为了数据操作不可或缺的工具。
3.Applymap
我们已经学习了列的操作,并解释了如何在行上运作,不过,假设你想对数据框里所有的数据单元执行一个函数,那又该怎么办呢?这时 applymap 就是合适的工具了。这里看一个例子。
df.applymap(lambda v: np.log(v) if isinstance(v, float) else v)
前面的代码生成图 1-28 的输出。
在这里,我们在数据框上调用了 applymap,如果某个值是 float 类型的的实例,那么就会获得该值的对数(np.log()利用 numpy 库返回该值)。这种类型的检查,可以防止系统返回一个错误信息,或者是为字符串型的 class 列或整数形的 wide petal 列返回浮动值。Applymap 的常见用法是根据一定的条件标准来转变或格式化每一个单元。
4.Groupby
现在,让我们来看一个非常有用,但对于新 pandas 用户往往难以理解的操作——数据框.groupby()方法。我们将逐步分析若干例子,来展示这个最为重要的功能。
这个 groupby 操作就如其名——它基于某些你所选择的类别对数据进行分组。让我们使用 iris 数据集来看一个简单的例子。这里将回到之前的步骤,重新导入最初的 iris数据集,并运行第一个 groupby 操作。
df.groupby('class').mean()
前面的代码生成图 1-29 所示的输出。
系统按照类别对数据进行了划分,并且提供了每个特征的均值。让我们现在更进一步,得到每个类别完全的描述性统计信息。
df.groupby('class').describe()
前面的代码生成图 1-30 所示的输出。
现在我们可以看到每个 class 完整的分解。再来看看其他一些可执行的 groupby 操作。
之前,我们看出花瓣长度和宽度在不同类之间有一些比较明显的区别,这里让我们看看如何使用 groupby 来发现这一点。
df.groupby('petal width')['class'].unique().to_frame()
前面的代码生成图 1-31 所示的输出。
在这个例子中,我们通过和每个唯一类相关联的花瓣宽度,对类别进行分组。这里测量组的数量还是可管理的,但是如果这个数量将要增大很多,那么我们很可能需要将测量分割为不同的范围。正如之前看到的,这点可以使用 apply 方法来完成。
现在来看一个自定义的聚集函数。
df.groupby('class')['petal width']\
.agg({'delta': lambda x: x.max() - x.min(), 'max': np.max, 'min': np.min})
前面的代码生成图 1-32 所示的输出。
在这段代码中,我们根据类别来分组花瓣宽度的时候,使用np.max和np.min这两个函数(两个np函数来自numpy库),以及返回最大花瓣宽度减去最小花瓣宽度的lambda函数。这些都以字典的形式,传递给.agg()方法,以此返回一个将字典键值作为列名的数据框。可以仅仅运行函数本身或者传递函数的列表,不过列的名称所含信息量就更少了 ①。
(①译者注:就是将函数以列表的形式,而不是字典的形式进行传送。这样就缺乏"delta"、"min"和"max"这样的键值作为列名,自动生成的列名就不会有太多的含义。)
我们只是刚刚接触了 groupby 方法的一些功能,还有很多东西要学习,所以我建议你阅读这里的文档:
对于准备阶段中如何操纵和准备数据,我们现在有了扎实的基本理解,而下一步就是建模。这里即将讨论 Python 机器学习生态系统中最为主要的一些库。
1.2.4 建模和评估
对于统计建模和机器学习,Python 有许多很优秀的、文档详实的库供选择。下面只谈及最流行的几个库。
1.Statsmodels
我们要介绍的第一个库是 statsmodels(http://statsmodels.sourceforge.net/)。
Statsmodels 是用于探索数据、估计模型,并运行统计检验的 Python 包。在这里,让我们使用它来构建一个简单的线性回归模型,为 setosa 类中花萼长度和花萼宽度之间的关系进行建模。
首先,通过散点图来目测这两者的关系。
fig, ax = plt.subplots(figsize=(7,7))
ax.scatter(df['sepal width'][:50], df['sepal length'][:50])
ax.set_ylabel('Sepal Length')
ax.set_xlabel('Sepal Width')
ax.set_title('Setosa Sepal Width vs. Sepal Length', fontsize=14,y=1.02)
前面的代码生成图 1-33 所示的输出。
我们可以看到,似乎有一个正向的线性关系,也就是说,随着花萼宽度的增加,花萼长度也会增加。接下来我们使用 statsmodels,在这个数据集上运行一个线性回归模型,来预估这种关系的强度。
import statsmodels.api as sm
y = df['sepal length'][:50]
x = df['sepal width'][:50]
X = sm.add_constant(x)
results = sm.OLS(y, X).fit()
print(results.summary())
前面的代码生成图 1-34 所示的输出。
图 1-34 所示的屏幕截图显示了这个简单回归模型的结果。由于这是一个线性回归,该模型的格式为 Y = Β0+Β1X,其中 B0 为截距而 B1 是回归系数。在这里,最终公式是 Sepal Length = 2.6447 + 0.6909 × Sepal Width。我们也可以看到,该模型的 R2 值是一个可以接受的 0.558,而 p 值(Prob)是非常显著的——至少对于这个类而言。
现在让我们使用结果对象来绘制回归线。
fig, ax = plt.subplots(figsize=(7,7))
ax.plot(x, results.fittedvalues, label='regression line')
ax.scatter(x, y, label='data point', color='r')
ax.set_ylabel('Sepal Length')
ax.set_xlabel('Sepal Width')
ax.set_title('Setosa Sepal Width vs. Sepal Length', fontsize=14,y=1.02)
ax.legend(loc=2)
前面的代码生成图 1-35 所示的输出。
通过绘制 results.fittedvalues,我们可以获取从模型所得的回归线。
在 statsmodels 包中,还有一些其他的统计函数和测试模块,我希望你能去探索它们。对于 Python 中标准的统计建模而言,这是一个非常有用的包。接下来,让我们开始学习 Python 机器学习包中的王者:scikit-learn。
2.scikit-learn
scikit-learn 是一个令人惊喜的 Python 库,作者们为其设计了无与伦比的文档,为几十个算法提供了统一的 API 接口。它建立在 Python 科学栈的核心模块之上,也就是 NumPy、SciPy、pandas 和 matplotlib。scikit-learn 覆盖的一些领域包括:分类、回归、聚类、降维、模型选择和预处理。
我们来看看几个例子。首先,使用 iris 数据建立一个分类器,然后学习如何利用scikit-learn 的工具来评估得到的模型。
在 scikit-learn 中打造机器学习模型的第一步,是理解数据应该如何构建。独立变量应该是一个数字型的 n×m 纬的矩阵 X、一个因变量 y 和 n×1 维的向量。该 y 向量可以是连续的数字,也可以是离散的数字,还可以是离散的字符串类型。然后将这些向量传递到指定分类器的.fit()方法。这是使用 scikit-learn 最大的好处,每个分类器都尽最大可能地使用同样的方法。如此一来,它们的交换使用易如反掌。
让我们来看看在第一个例子中,如何实现。
from sklearn.ensemble import RandomForestClassifier
from sklearn.cross_validation import train_test_split
clf = RandomForestClassifier(max_depth=5, n_estimators=10)
X = df.ix[:,:4]
y = df.ix[:,4]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.3)
clf.fit(X_train,y_train)
y_pred = clf.predict(X_test)
rf = pd.DataFrame(list(zip(y_pred, y_test)), columns=['predicted', 'actual'])
rf['correct'] = rf.apply(lambda r: 1 if r['predicted'] ==
r['actual'] else 0, axis=1)
rf
前面的代码生成图 1-36 的输出。
现在,让我们来看看下面的代码。
rf['correct'].sum()/rf['correct'].count()
这会生成图 1-37 的输出。
在前面的几行代码中,我们建立、训练并测试了一个分类器,它在 Iris 数据集上具有 95%的准确度。这里逐项分析每个步骤。在代码的前两行,我们做了几个导入,前两个是从 scikit-learn,值得庆幸的是在 import 语句中其名字缩短为 sklearn了。第一个导入的是一个随机森林分类器,第二个导入的是一个将数据分成训练组和测试组的模块。出于某些原因,这种数据切分在机器学习应用的构建中是很关键的。我们将在以后的章节讨论这些,现在只需要知道这是必需的。模块 train_test_split 还会打乱数据的先后顺序,这也是非常重要的,因为原有的顺序可能包含误导实际预测的信息。
在这本书中,我们将使用最新的 Python 版本,撰写本书的时候是版本 3.5。如果你使用的 Python 是版本2.x,你需要添加额外的 import 语句,让整数的除法和 Python 3.x 中的一样运作。没有这一行,你的准确度将被报告为 0,而不是 95%。该行是:from __future__ import division
在 import 语句之后,第一行看上去很奇怪的代码实例化了我们的分类器,这个例子中是随机森林分类器。这里选择一个使用 10 个决策树的森林,而每棵树最多允许五层的判定深度。如此实施的原因是为了避免过拟合(overfitting),我们将在后面的章节中深入讨论这个话题。
接下来的两行创建了 X 矩阵和 y 向量。初始的 iris 数据框包含四个特征:花瓣的宽度和长度,以及花萼的宽度和长度。这些特征被选中并成为独立特征矩阵 X。最后一列,iris 类别的名称,就成为了因变的 y 向量。
然后这些被传递到 train_test_split 方法,该方法将数据打乱并划分为四个子集,X_train,X_test,y_train 和 y_test。参数 test_size 被设置为 0.3,这意味着数据集的 30%将被分配给 X_test 和 y_test 部分,而其余的将被分配到训练的部分,X_train 和 y_train。
接下来,使用训练数据来拟合我们的模型。一旦模型训练完毕,再通过测试数据来调用分类器的预测方法。请记住,测试数据是分类器没有处理过的数据。预测的返回结果是预估标签的列表。然后,我们创建对应实际标签与预估标签的数据框。最终,我们加和正确的预测次数,并将其除以样例的总数,从而看出预测的准确率。现在让我们看看哪些特征提供了最佳的辨别力或者说预测能力。
f_importances = clf.feature_importances_f_names = df.columns[:4]
f_std = np.std([tree.feature_importances_ for tree in
clf.estimators_], axis=0)
zz = zip(f_importances, f_names, f_std)
zzs = sorted(zz, key=lambda x: x[0], reverse=True)
imps = [x[0] for x in zzs]
labels = [x[1] for x in zzs]
errs = [x[2] for x in zzs]
plt.bar(range(len(f_importances)), imps, color="r", yerr=errs, align="center")
plt.xticks(range(len(f_importances)), labels);
从图 1-38 可以看出,正如我们根据之前可视化分析所作出的预期,花瓣的长度和宽度对于区分 iris 的类别而言,具有更好的辨别力。不过,这些数字究竟来自哪里?随机森林有一个名为.feature_importances_的方法,它返回特征在决策树中划分叶子节点的相对能力。如果一个特征能够将分组一致性地、干净拆分成不同的类别,那么它将具有很高的特征重要性。这个数字的总和将始终为 1。也许你注意到,在这里我们已经包括了标准差,它将有助于说明每个特征有多么的一致。这是如此生成的:对于每个特征,获取每10 棵决策树的特征重要性,并计算标准差。
现在,让我们看看另一个使用 scikit-learn 的例子。现在,切换分类器并使用支持向量机(SVM)。
from sklearn.multiclass import OneVsRestClassifier
from sklearn.svm import SVC
from sklearn.cross_validation import train_test_split
clf = OneVsRestClassifier(SVC(kernel='linear'))
X = df.ix[:,:4]
y = np.array(df.ix[:,4]).astype(str)
X_train, X_test, y_train, y_test = train_test_split(X, y,test_size=.3)
clf.fit(X_train,y_train)
y_pred = clf.predict(X_test)
rf = pd.DataFrame(list(zip(y_pred, y_test)), columns=['predicted','actual'])
rf['correct'] = rf.apply(lambda r: 1 if r['predicted'] ==r['actual'] else 0, axis=1)
rf
前面的代码生成图 1-39 的输出。
现在,让我们执行下面这行代码。
rf['correct'].sum()/rf['correct'].count()
前面的代码生成图 1-40 的输出。
这里,我们将模型切换为支持向量机,而没有改变代码的本质。唯一的变化是引入了 SVM 而不是随机森林,以及实例化分类器的那一行代码(标签 y 需要一个小小的格式改变,这是因为 SVM 无法像随机森林分类器那样,将这些标签解释为 NumPy 的字符串)。
这些仅仅是 scikit-learn 能力的一小部分,但它应该可以说明这个伟大的工具对于机器学习应用而言强大的功能和力量。还有许多其他的机器学习库,我们在这里没有机会讨论,不过会在后面的章节中探讨,这里我强烈建议,如果你是第一次使用机器学习库,而又想要一个强大的通用工具,scikit-learn 将是你明智的选择。
1.2.5 部署
将一个机器学习模型放入生产环境时,有许多可用的选项。它基本上取决于应用程序的性质。部署小到在本地机器上运行 cron 作业,大到在 Amazon EC2 实例上部署全面的实现。
这里不会深入具体实施的细节,不过全书中我们将有机会研究不同的部署实例。
本书评论