master
aolingwen 5 years ago
parent f565eff1ac
commit 8ad8ad9d44

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

@ -0,0 +1,9 @@
# 第十一章 综合实战:森林火灾数据可视化
亚马逊热带雨林位于南美洲的亚马逊平原,占地 550 万平方公里。雨林横越了9个国家巴西、哥伦比亚、秘鲁、委内瑞拉、厄瓜多尔、玻利维亚、圭亚那、苏里南以及法国法属圭亚那占据了世界雨林面积的一半占全球森林面积的 20% ,是全球最大及物种最多的热带雨林。亚马逊雨林被人们称为“地球之肺”和“绿色心脏”。
然而就在前不久,巴西爆发了前所未有的大火,并在 2019 年 8 月份愈演愈烈。今年迄今已发生超过 74000 起火灾,这是该国国家空间研究所记录的最多火灾数量。与 2018 年同一时期的火灾数量相比,这一数字大约增加了 80 。其中超过一半的火灾发生在亚马逊地区。
可想而知,如何对森林火灾的大量历史数据进行分析与挖掘,总结出森林发生火灾的规律,对于防止森林或者是雨林发生火灾是非常有意义的一件事情。
在这里已经有一份巴西近 20 年来亚马逊热带雨林的一些数据,我们的目标是对这些数据进行理解、分析、并对其进行可视化,来验证我们对于森林火灾的一些猜测和认知。

@ -0,0 +1,105 @@
# 11.3 亚马逊雨林地图可视化
在上一节中,已经知道了每个区域在这将近 20 年里总共发生过多少起火灾。那么我们能不能更加直观的感受一下每年每个区域的火灾次数的变化情况呢?此时你可能会想到这样的代码:
```python
data[['year','state','number']].groupby(['year','state']).number.sum()
```
然后就会看到这样的结果。
![](8.jpg)
没错,我们需要的数据就是这些,但是我们是感官动物,看到一堆数字时并不觉得这堆数字有多棒。所以我们接下来就尝试将这些数据进行可视化,让我们能从可视化结果中看出每年这些区域的火灾次数的变化情况。
首先,我们需要一张巴西的地图,这张地图在网络上有现成的,可以从 https://geodata.lib.berkeley.edu/catalog/stanford-ys298mq8577 获取。下载好地图的压缩包后,可以将后缀名为 .shp 的文件解压出来,然后使用如下代码就可以将地图画出来。
```python
# 加载geopandas
import geopandas as gp
# 读取解压好的.shp文件
geo_data = gp.read_file('./geo_brazil_data.shp')
# .shp文件中的nome字段表示state字段所以需要字段重命名
geo_data = geo_data.rename(columns = {'nome': 'state'})
# 画图
geo_data.plot()
```
![](9.jpg)
嗯,看上去是不是有点样子了?不过这还不算什么,因为这仅仅是巴西的地图轮廓而已。我们的目标是每年的火灾变化情况反应到这张地图上。所以可以编写如下代码来实现该功能:
```python
# 每年每个区域的火灾次数统计
ani_d2 = data[['year','state','number']].groupby(['year','state']).number.sum().reset_index()
# 构建透视表
ani_d2_pivot = ani_d2.pivot_table(values='number', index=['state'], columns='year').reset_index()
ani_d2_final = geo_data.set_index('state').join(ani_d2_pivot.set_index('state'))
# 火灾次数的最小和最大值
vmin, vmax = 200, 400
for year in ani_d2_final.columns[1:]:
# 创建图
fig = ani_d2_final.plot(column=year, cmap='Oranges', figsize=(8,5), linewidth=0.8, edgecolor='0.8', vmin=vmin, vmax=vmax, legend=True, norm=plt.Normalize(vmin=vmin, vmax=vmax))
# 关闭轴
fig.axis('off')
# 设置图的标题
fig.set_title('Fire In Brazil', fontdict={'fontsize': '15', 'fontweight' : '3'})
# 图的注释
fig.annotate(year, xy=(0.1, .225), xycoords='figure fraction', horizontalalignment='left', verticalalignment='top',fontsize=20)
chart = fig.get_figure()
# 按年份保存图片
chart.savefig(str(year) +'_fire.png', dpi=300)
plt.close()
```
当整段代码执行完毕后,我们会在当前目录下看到 20 张 .png 图片,图片如下:
![](10.jpg)
![](11.jpg)
![](12.jpg)
![](13.jpg)
![](14.jpg)
![](15.jpg)
![](16.jpg)
![](17.jpg)
![](18.jpg)
![](19.jpg)
![](20.jpg)
![](21.jpg)
![](22.jpg)
![](23.jpg)
![](24.jpg)
![](25.jpg)
![](26.jpg)
![](27.jpg)
![](28.jpg)
![](29.jpg)
可以看到,火灾的重灾区的火灾发生次数比较稳定。不过如果将这些图片合成为一张 gif 动图,可视化的效果就更加明显。那怎样将这图片转换为动图,就作为一个练习留给你练手了。

@ -0,0 +1,58 @@
# 11.1 亚马逊雨林数据初窥
拿到数据的第一步,肯定是先看看数据中有多少个字段,每个字段代表什么意思。这份数据是一个 csv 文件,所以可以使用 pandas 来读取数据。
```python
import pandas as pd
# 读取csv文件
data = pd.read_csv('./amazon.csv')
# 查看数据的前5行
data.head(5)
```
![](1.jpg)
可以看出,每一行数据表示当天在特定区域内发生火灾的次数。嗯,前 5 行数据中都没有发生过火灾,看上去还不错。
接下来,看看火灾发生次数的简单统计信息。
```python
data['number'].describe()
```
![](2.jpg)
哇,从 98 年到 19 年期间,亚马逊森林总共发生了 6454 起火灾!而且平均每天发生了 108 起火灾!
这样的统计对于分析来说,粒度还是有点粗,我们不妨对火灾发生的次数进行分组统计。比如根据年、月和地区来分组,那么在对月份分组之前,可以先将数据中的月份进行简化,然后再进行分组。
```python
# 将month中的月份改成缩写
data['month'].replace(to_replace = 'Janeiro', value = 'Jan', inplace = True)
data['month'].replace(to_replace = 'Fevereiro', value = 'Feb', inplace = True)
data['month'].replace(to_replace = 'Março', value = 'Mar', inplace = True)
data['month'].replace(to_replace = 'Abril', value = 'Apr', inplace = True)
data['month'].replace(to_replace = 'Maio', value = 'May', inplace = True)
data['month'].replace(to_replace = 'Junho', value = 'Jun', inplace = True)
data['month'].replace(to_replace = 'Julho', value = 'Jul', inplace = True)
data['month'].replace(to_replace = 'Agosto', value = 'Aug', inplace = True)
data['month'].replace(to_replace = 'Setembro', value = 'Sep', inplace = True)
data['month'].replace(to_replace = 'Outubro', value = 'Oct', inplace = True)
data['month'].replace(to_replace = 'Novembro', value = 'Nov', inplace = True)
data['month'].replace(to_replace = 'Dezembro', value = 'Dec', inplace = True)
# 分组统计
year_mo_state = data.groupby(by = ['year','state', 'month']).sum().reset_index()
year_mo_state
```
![](3.jpg)
现在已经统计出了,某年某月某个地区发生火灾的次数。但是这样的一份不完全的报表(由于篇幅原因,这里只截出了部分统计结果)放在你的面前,你可能并不会有什么感觉,因为人类喜欢的是图,而不是一堆数字。所以下一节,将带你画出有趣的图表,让你对这份数据有一份更加直观的认识。

@ -0,0 +1,71 @@
# 11.2 使用图表来探索亚马逊雨林
现在我们不妨使用 python 代码画出每年亚马逊雨林火灾发生次数的折线图。
```python
# 导入matplotlib中的MaxNLocator和FuncFormatter
from matplotlib.pyplot import MaxNLocator, FuncFormatter
# 设置折线图的大小
plt.figure(figsize=(12,4))
# 绘制折线图,横轴为年份,纵轴为火灾发生次数
ax = sns.lineplot(x = 'year', y = 'number', data = year_mo_state, estimator = 'sum', color = 'orange', lw = 3,
err_style = None)
# 设置折线图的标题
plt.title('Total Fires in Brazil : 1998 - 2017', fontsize = 18)
# 设置横轴与纵轴的名称
plt.xlabel('Year', fontsize = 14)
plt.ylabel('Number of Fires', fontsize = 14)
# 设置刻度
ax.xaxis.set_major_locator(plt.MaxNLocator(19))
ax.set_xlim(1998, 2017)
# 设置日期格式
ax.get_yaxis().set_major_formatter(plt.FuncFormatter(lambda x, p: format(int(x), ',')))
```
![](4.jpg)
从图表可以看出,火灾在过去的 20 年中急剧增加,从 1998 年的 2 万起增加到 2017 年的 36000将近一倍然后心细观察的话不难发现火灾有不断增长的趋势因此我们可以预计在接下来的几年里还会发生更多的火灾
看完每年的火灾发生次数,我们再来看看每月的火灾发生次数,看看发生火灾与月份有没有什么关系。
```python
# 设置折线图的大小
plt.figure(figsize=(12,4))
# 绘制盒图,横轴是月份,纵轴是火灾发生次数
sns.boxplot(x = 'month', order = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep','Oct', 'Nov', 'Dec'],
y = 'number', data = year_mo_state)
# 设置盒的标题和横纵轴的名称
plt.title('Fires in Brazil by Month', fontsize = 18)
plt.xlabel('Month', fontsize = 14)
plt.ylabel('Number of Fires', fontsize = 14)
```
![](5.jpg)
该盒图仿佛暗示着,每年的下半年,火灾发生的概率更高,尤其是从夏天到秋天这段时间。这也是符合常理的,毕竟气候干燥,容易发生火灾。
那么火灾的发生与区域有关吗?我们同样可以统计一下。
```python
# 统计火灾发生次数最高的10个区域
data.groupby(by = 'state')['number'].sum().sort_values(ascending = False).head(10)
```
![](6.jpg)
可以看到,火灾重灾区的第二名和第三名非常接近,但是第一名比第二名高出了几乎 400000 次!嗯,我们可以网上搜索一下巴西的地图,看看这些重灾区的位置。
![](7.jpg)
第一名重灾区在巴西的中部,如果发生火灾,后果可想而知。为什么 Mato Grosso 会排在榜首呢?那是因为 Mato Grosso 是巴西第三大州。这个州人口占巴西总人口的比重很小,约为 1.5 %,但农业产业非常强大。
过去Mato Grosso 是巴西最大的二氧化碳排放州之一,这是由于其强大的农业经济驱动下的森林火灾和森林砍伐所致。

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@ -0,0 +1,6 @@
# 第十二章 综合实战:信用卡欺诈检测
信用卡又叫贷记卡,是由商业银行或信用卡公司对信用合格的消费者发行的信用证明。持有信用卡的消费者可以到特约商业服务部门购物或消费,再由银行同商户和持卡人进行结算,持卡人可以在规定额度内透支。
然而林子大了什么鸟都会有,有些不法分子会尝试各种手段来使用信用卡进行欺诈交易。例如本章的数据集就是欧洲银行联盟所提供的包含 2013 年 9 月欧洲持卡人通过信用卡进行的交易数据。此数据集显示两天内发生的交易。我们希望能够通过对这些数据分析、挖掘与建模,能够实现信用卡欺诈行为的检测。

@ -0,0 +1,47 @@
# 12.1 了解数据
和上一章一样,我们要养成一个习惯,当拿到数据时,一定要先看看数据大概是个什么样子,有多少条记录,有多少个字段等。所以我们先来看一下数据。
```python
import pandas as pd
transactions = pd.read_csv('../input/creditcard.csv')
transactions.shape
```
![](1.jpg)
嗯,总共有 28 万多条信用卡交易记录31 个特征。我们来看看大概长什么样。
```python
# 随机采样5条数据
transactions.sample(5)
```
![](./2.jpg)
特征太多,截图一次性截不全,那么可以用 info 函数来看看整个数据集的特征名称与其对应的数据类型。
```python
transactions.info()
```
![](3.jpg)
不难看出数据集中的 class 字段是数据集的标签即该交易是否为欺诈交易1表示为敲诈交易0 表示为正常交易。Amount 字段表示该笔交易的金额Time 字段表示该交易发生的时间。然而其他的字段都是什么 V1, V2, V28 什么的,让我们变成了丈二的和尚摸不着头脑。这种字段我们通常称为匿名字段,为什么会有匿名字段呢?是由于该数据集是信用卡相关的数据集,里面可能会有一些客户的敏感信息,所以对原始数据进行了一些加工,来实现脱敏。
接下来,我们来看一看该数据集中是正常交易比较多还是欺诈交易比较多。
```python
transactions['Class'].value_counts()
```
![](4.jpg)
还是比较正常的,因为欺诈行为一般还是占很小一部分的。那这样会导致什么样的问题呢?很明显,假设我写一个程序,我就一直预测交易信息是属于正常交易的,那么预测的准确率也有 95% 以上。这个准确率看上去很高,但是这样的程序你敢用吗?我猜你肯定不敢用。
所以,从刚刚的数据探索我们发现,这个数据集有两个难点,一个是恶心的匿名字段,还有一个是数据集的标签分布严重倾斜。

@ -0,0 +1,77 @@
# 12.4 使用sklearn实现欺诈检测功能
前两节已经解决掉了这个数据集的两个难点,那么接下来,要做的事情就是一些非常基本的处理,来对该数据集进行建模,从而实现欺诈检测的功能了。
不过在建模之前,我们需要对 Time 和 Amount 这两个特征进行分析和处理。
首选是 Time ,可以看看 Time 的一个分布。
```python
transactions['Time'].describe()
```
![](8.jpg)
Time 特征的值是比较大的数,看起来像是以秒为单位的时间戳。那么这个时候我们可以将该时间戳转换成分钟和小时。
```python
# 将Time转换成秒为单位的时间戳
timedelta = pd.to_timedelta(transactions['Time'], unit='s')
# 创建一个新的特征Time_min表示分钟
transactions['Time_min'] = (timedelta.dt.components.minutes).astype(int)
# 创建一个新的特征Time_hour表示小时
transactions['Time_hour'] = (timedelta.dt.components.hours).astype(int)
```
然后是 Amount 特征,同样,先看看分布。
```python
transactions['Amount'].describe()
```
![](9.jpg)
从分布来看Amount 特征好像存在着严重的倾斜。我们把直方图画出来验证一下我们的猜测。
```python
plt.figure(figsize=(12,4), dpi=80)
sns.distplot(transactions['Amount'], bins=300, kde=False)
plt.ylabel('Count')
plt.title('Transaction Amounts')
```
![](10.jpg)
果然!大量数据的 Amount 都接近于 100 左右。一般在碰到这种倾斜严重的特征时,我们需要对它进行 log 变换log 变换的意图就是将倾斜严重的分布,尽量让它变得更均匀。
```python
transactions['Amount_log'] = np.log(transactions.Amount + 0.01)
```
然后再可视化一下,可以看出经过 log 变换之后,分布变得更加均匀了。
![](11.jpg)
接下来可以使用我们的特征来构造欺诈检测模型了。构建模型很简单,使用 sklearn 提供的接口即可。
```python
# 导入imblearn中的pipeline功能
from imblearn.pipeline import make_pipeline as make_pipeline_imb
# 导入上一节中提到的SMOTE功能
from imblearn.over_sampling import SMOTE
# 选用一些特征
transactions = transactions[["Time_hour","Time_min","V2","V3","V4","V9","V10","V11","V12","V14","V16","V17","V18","V19","V27","Amount_log","Class"]]
# 构建pipelinepipeline的意思是流水线在这个欺诈检测的流水线中做了两件事情
# 1. SMOTE过采样
# 2. 使用决策树来进行分类,即欺诈检测
smote_pipeline = make_pipeline_imb(SMOTE(), DecisionTreeClassifier()
)
```
有了 pipeline 之后,我们就相当于已经有了一条能够检测欺诈交易的流水线了。不过这样一条流水线的效果好不好,需要使用数据来进行验证。下一节中将会向你介绍怎样验证算法的效果。

@ -0,0 +1,27 @@
# 12.2 对匿名特征进行处理
首先来解决第一个问题:匿名特征。匿名特征是个很棘手的问题,因为我们并不知道特征的具体含义。所以,我们一般需要从这些特征的一些属性来分析。
我们可以尝试看看这些特征之间的协方差。协方差Covariance在概率论和统计学中用于衡量两个变量的总体误差这与只表示一个变量误差的方差不同。 如果两个变量的变化趋势一致,也就是说如果其中一个大于自身的期望值,另外一个也大于自身的期望值,那么两个变量之间的协方差就是正值。 如果两个变量的变化趋势相反,即其中一个大于自身的期望值,另外一个却小于自身的期望值,那么两个变量之间的协方差就是负值。
也就是说,当两个特征之间的协方差趋近于 0 时,那么这两个特征之间可能没有太多的相关性。可以通过以下代码计算特征之间的协方差,并以热力图的方式可视化出来。
```python
# 导入seaborn
import seaborn as sns
sns.heatmap(transactions.corr())
```
![](5.jpg)
从热力图可以看出,匿名特征与匿名特征之间的协方差几乎趋近于 0 。那这个时候可以大胆的猜测,这些匿名特征是原始特征经过 PCA 算法转换后的 28 个主成分。因为 PCA 算法计算后的数据有一个特性,就是协方差为 0 。
PCA 算法是一种无监督的降维算法,所谓的降维就是将原始数据的特征数量减少至你所指定的特征数量。即例如原始数据中有 108 个特征,但是现在只想保留其中比较重要且比较有用的 28 个特征。那么此时就可以使用 PCA 算法来对数据进行降维。
注意PCA并不是从那 108 个特征中选择 28 个特征出来,而是经过了一系列的计算之后所产生的 28 个特征。
既然 V1 到 V28 这 28 个特征是对原始数据进行 PCA 算法处理之后的特征。所以就没太大的必要去对这些匿名特征进行特征工程了,因为这些特征已经足够独立了(没什么相关性)。

@ -0,0 +1,45 @@
# 12.3 解决样本不平衡问题
在本章的第一节中已经提到过,该数据集有着非常严重的数据倾斜。
```python
sns.countplot('Class', data=transactions)
plt.title('Class Distributions \n (0: No Fraud || 1: Fraud)', fontsize=14)
```
![](6.jpg)
那么对于这种样本极不平衡的数据我们有两种大致的思路,一种是降采样,另一种是过采样。
降采样,顾名思义就是从标签所占比例非常高的那一部分样本中随机选取一些样本,至于选取多少个样本,就要看标签所占比例非常低的那一部分的样本有多少个了。对于该数据集来说,我们需要从标签值为 0 的样本中随机选取 492 个左右的样本出来,然后与标签值为 1 的样本组合成一个新的数据集。这就是降采样的意思。
```python
# 从标签值为0的数据中随机抽取492个样本
df0 = transactions[transactions['Class'] == 0].sample(492)
# 提取出标签值为1的数据
df1 = transactions[transactions['Class'] == 1]
# 将两部分数据合并在一起
all_df = pd.concate([df0, df1], axis=0)
```
然后依葫芦画瓢的将数据的分布图可视化出来,就能看到经过降采样之后的数据分布变成了均匀分布。
![](7.jpg)
但是降采样有个问题,就是丢弃了非常多的数据所提供的信息,因为我们是从 28 万条数据中随机选取的,所以无形之中就丢弃掉了大量信息!
那么有没有一种即让数据呈现出均匀分布的样子,又不会丢失信息呢?有!那就是过采样。一般来说,现在经常使用的过采样算法是 SMOTE 算法。SMOTE 算法与随机欠采样不同SMOTE 算法会创建新的数据,以便在类之间保持相等的平衡。这是解决数据不平衡问题的另一种很棒的选择。
SMOTE 算法的大致思路是根据标签所占比例较少的那一部分数据创建一些合成点,这些合成点可以看成是数据。而这些数据中字段的值,是最近邻的思想来构建的。所以使用 SMOTE 算法来解决样本不平衡的问题,实际上就是通过使用 SMOTE 算法来生成一些新的数据,然后与原始数据合并起来,让整个数据变成均匀分布。下面是使用 SMOTE 算法实现过采样的示例代码:
```python
# 导入SMOTE
from imblearn.over_sampling import SMOTE
# 使用SMOTE算法实现过采样
transactions = SMOTE().fit_sample(transactions)
```
好了,这节主要介绍了解决样本不平衡问题的两种方法,至于哪种方法的效果好,你可以自己在本章结束之后自己动手试试。至于过采样之后的数据分布图是怎样的,这里就当给你留的一个作业吧。

@ -0,0 +1,163 @@
# 12.5 验证算法性能
上一节,我们已经学会了怎样使用 sklearn 提供的接口来构建一棵决策树模型。那这节就要来看看我们构建的模型的准确度高不高了。然而验证该模型的性能存在两个问题。一是什么样的指标能够衡量该模型的性能,二是怎样才能不偏不倚地验证算法的性能。接下来,我会一一解答。
首先是准确度的量化问题。本章一开始就提到过,该数据集不能用准确率这个指标来衡量我们的算法的性能。因为该数据集的标签是不平衡的。那什么样的指标能够衡量这种不平衡的数据呢?那就是 F1 Score !
想要弄明白 F1 Score 所代表的意义,就需要先从混淆矩阵说起。以癌症检测系统为例,癌症检测系统的输出不是有癌症就是健康,这里为了方便,就用`1`表示患有癌症,`0`表示健康。假设现在拿`10000`条数据来进行测试,其中有`9978`条数据的真实类别是`0`,系统预测的类别也是`0`,有`2`条数据的真实类别是`1`却预测成了`0`,有`12`条数据的真实类别是`0`但预测成了`1`,有`8`条数据的真实类别是`1`,预测结果也是`1`。
如果我们把这些结果组成如下矩阵,则该矩阵就成为**混淆矩阵**。
| 真实预测 | 0 | 1 |
| ------------ | ------------ | ------------ |
| 0 | 9978 | 12 |
| 1 | 2 | 8 |
混淆矩阵中每个格子所代表的的意义也很明显,意义如下:
| 真实预测 | 0 | 1 |
| ------------ | ------------ | ------------ |
| 0 | 预测`0`正确的数量 | 预测`1`错误的数量 |
| 1 | 预测`0`错误的数量 | 预测`1`正确的数量 |
如果将正确看成是`True`,错误看成是`False``0`看成是`Negtive``1`看成是`Positive`。然后将上表中的文字替换掉,混淆矩阵如下:
| 真实预测 | 0 | 1 |
| ------------ | ------------ | ------------ |
| 0 | TN | FP |
| 1 | FN | TP |
因此`TN`表示真实类别是`Negtive`,预测结果也是`Negtive`的数量;`FP`表示真实类别是`Negtive`,预测结果是`Positive`的数量;`FN`表示真实类别是`Positive`,预测结果是`Negtive`的数量;`TP`表示真实类别是`Positive`,预测结果也是`Positive`的数量。
很明显,当`FN`和`FP`都等于`0`时,模型的性能应该是最好的,因为模型并没有在预测的时候犯错误。即如下混淆矩阵:
| 真实预测 | 0 | 1 |
| ------------ | ------------ | ------------ |
| 0 | 9978 | 0 |
| 1 | 0 | 22 |
**所以模型分类性能越好,混淆矩阵中非对角线上的数值越小。**
然后还需要明白两个概念:精准率和召回率。
**精准率(`Precision`)**指的是模型预测为`Positive`时的预测准确度,其计算公式如下:
$$
Precisioin=\frac{TP}{TP+FP}
$$
假如癌症检测系统的混淆矩阵如下:
| 真实预测 | 0 | 1 |
| ------------ | ------------ | ------------ |
| 0 | 9978 | 12 |
| 1 | 2 | 8 |
则该系统的精准率=`8/(8+12)=0.4`。
`0.4`这个值表示癌症检测系统的预测结果中如果有`100`个人被预测成患有癌症,那么其中有`40`人是真的患有癌症。**也就是说,精准率越高,那么癌症检测系统预测某人患有癌症的可信度就越高。**
**召回率(`Recall`)**指的是我们关注的事件发生了,并且模型预测正确了的比值,其计算公式如下:
$$
Precisioin=\frac{TP}{FN+TP}
$$
假如癌症检测系统的混淆矩阵如下:
| 真实预测 | 0 | 1 |
| ------------ | ------------ | ------------ |
| 0 | 9978 | 12 |
| 1 | 2 | 8 |
则该系统的召回率=`8/(8+2)=0.8`。
从计算出的召回率可以看出,假设有`100`个患有癌症的病人使用这个系统进行癌症检测,系统能够检测出`80`人是患有癌症的。**也就是说,召回率越高,那么我们感兴趣的对象成为漏网之鱼的可能性越低。**
那么精准率和召回率之间存在着什么样的关系呢?举个例子,假设有这么一组数据,菱形代表`Positive`,圆形代表`Negtive`。
![](12.jpg)
现在需要训练一个模型对数据进行分类,假如该模型非常简单,就是在数据上画一条线作为分类边界。模型认为边界的左边是`Negtive`,右边是`Positive`。如果该模型的分类边界向左或者向右移动的话,模型所对应的精准率和召回率如下图所示:
![](13.jpg)
从上图可知,**模型的精准率变高,召回率会变低,精准率变低,召回率会变高。**
那么有没有像准确率一样值越高说明性能越好而且能够兼顾精准率和召回率的指标呢那就是F1 Score
`F1 Score`是统计学中用来衡量二分类模型精确度的一种指标。它同时兼顾了分类模型的准确率和召回率。`F1 Score`可以看作是模型准确率和召回率的一种加权平均,它的最大值是`1`,最小值是`0`。其公式如下:
$$
F1=\frac{2*precision*recall}{precision+recall}
$$
- 假设模型`A`的精准率为`0.2`,召回率为`0.7`,那么模型`A`的`F1 Score`为`0.31111`。
- 假设模型`B`的精准率为`0.7`,召回率为`0.2`,那么模型`B`的`F1 Score`为`0.31111`。
- 假设模型`C`的精准率为`0.8`,召回率为`0.7`,那么模型`C`的`F1 Score`为`0.74667`。
- 假设模型`D`的精准率为`0.2`,召回率为`0.3`,那么模型`D`的`F1 Score`为`0.24`。
从上述`4`个模型的各种性能可以看出模型C的精准率和召回率都比较高因此它的`F1 Score`也比较高。而其他模型的精准率和召回率要么都比较低,要么一个低一个高,所以它们的`F1 Score`比较低。
这也说明了只有当模型的精准率和召回率都比较高时`F1 Score`才会比较高。这也是`F1 Score`能够同时兼顾精准率和召回率的原因。
嗯,现在知道用什么指标来衡量模型性能了,那怎样才能不偏不倚地判别模型性能的好坏呢?那就是交叉验证!
一般在真实业务中,我们可能没有真正意义上的测试集,或者说不知道测试集中的数据长什么样子。那么怎样在没有测试集的情况下来验证模型好还是不好呢?这个时候就需要**验证集**了。
那么验证集从何而来,很明显,可以从训练集中抽取一小部分的数据作为验证集,用来验证模型的性能。
但如果仅仅是从训练集中抽取一小部分作为验证集的话,有可能会让对模型的性能有一种偏见或者误解。
比如现在要对手写数字进行识别,那么我就可能会训练一个分类模型。但可能模型对于数字`1`的识别准确率比较低 ,而验证集中没多少个数字为`1`的样本,然后用验证集测试完后得到的准确率为`0.96`。然后您可能觉得哎呀,我的模型很厉害了,但其实并不然,因为这样的验证集让您的模型的性能有了误解。那有没有更加公正的验证算法性能的方法呢?有,那就是**k-折交叉验证**
在**K-折交叉验证**中把原始训练数据集分割成K个不重合的⼦数据集然后做K次模型训练和验证。每⼀次使⽤⼀个⼦数据集验证模型并使⽤其它K1个⼦数据集来训练模型。在这K次训练和验证中每次⽤来验证模型的⼦数据集都不同。最后对这K次在验证集上的性能求平均。
![](14.jpg)
OK明白了什么是F1 Score 和 交叉验证之后。我们就可以使用 sklearn 提供好了的接口来验证我们模型的性能了,代码十分简单。
```python
# 导入K折功能
from sklearn.model_selection import KFold
from sklearn.metrics import f1_score
# 提取数据集中的标签
y = transactions['Class']
# 将Class这一列删除也就相当于提取了数据集中的所有特征
X = transactions.drop(['Class'])
# 5折交叉验证
kf = KFold(n_splits=5)
# 平均f1 score
mean_f1_score = 0
# 开始5折交叉验证
for train_index, test_index in kf.split(X):
#train_X表示训练集中的数据train_y表示训练集中的标签
train_X, train_y = X[train_index], y[train_index]
#test_X表示验证集中的数据test_y表示验证集中的标签
test_X, test_y = X[test_index], y[test_index]
smote_pipeline.fit(train_X, train_y)
pred = smote_pipeline.predict(test_X)
score = f1_score(test_y, pred)
mean_f1_score += score
# 因为是5折所以除以5
mean_f1_score /= 5
print(mean_f1_score)
```
![](15.jpg)
可以看到,我们的决策树模型的 F1 Score 为 0.8左右。嗯,结果还是不错的。当然,我们还可以做更多的分析和处理工作,来让我们的分数越来越高。希望你能自己动手,尝试提高分数,相信你会享受这个过程的。

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

@ -0,0 +1,71 @@
# 11.2 FIFA数据可视化
可视化是实现理解数据的最好途径,所以接下来会对不同的特征进行可视化。
首先先看一下 Overall 这个特征Overall 表示的是球员的综合水平,值越高说明球员越厉害。一般来说,球员的综合水平应该是符合高斯分布的。因为特别差劲与特别优秀的球员在所有球员中所占的比例应该是非常少的,水平中庸的蓝领球员应该是占大多数的。所以我们可以可视化看一下,球员的综合水平的整体分布是不是一个高斯分布。
```python
# 画分布图
sns.distplot(datafr['Overall'],color="Purple", ax=axs[1])
```
![](16.jpg)
基本上符合高斯分布,所以基本可以推断出 Overall 这个特征应该没什么毛病。
然后来看一下 Jersey Number 这个特征Jersey Number 的意思是球衣号码。为什么要看这个特征呢?因为我个人认为这种特征所代表的含义比较玄学,可能有些球衣号码的球员的普遍综合水平比较高。那为了验证这一想法,不如来可视化一下看看。
```python
plt.figure(figsize=(12,10))
# 画散点图,横轴是球衣号码,纵轴是球员的综合水平
ax = sns.scatterplot(x="Jersey Number", y="Overall", hue ="Overall", size= "Overall", data=datafr)
ax.set_title('Scatter plot of Jersey Number vs Overall', fontsize=16)
sns.set_context("paper", font_scale=1.4)
```
![](13.jpg)
从可视化的结果来看,球衣号码和球员的综合水平好想并没有比较直观的关系。所以如果想要根据球衣号码来推测出该球员的综合水平高低,不太现实。因此在后面就行推荐时可以将其删掉。
那么哪些特征会与球员的综合水平有关呢?我个人认为,球员的国籍可能是一个比较重要的特征。因为大部分绿茵场上的超级巨星都是来自如巴西、阿根廷、德国、意大利等国家。所以我们不妨来检查一下球员国籍这个特征有没有明显的异常值。
首先,看看出产球员最多的十个国家分别是哪些国家。
```python
datafr['Nationality'].value_counts()[:10]
```
![](14.jpg)
和我预期的差不多,英格兰,德国,西班牙,法国,巴西,意大利等足球强国中出产的球员数量比较多,很符合常理,应该没有什么异常值。不过,干巴巴的数字看上去不够直观,我们可以将球员比例可视化出来。
```python
England = len(datafr[datafr['Nationality'] == 'England'])
Germany = len(datafr[datafr['Nationality'] == 'Germany'])
Spain = len(datafr[datafr['Nationality'] == 'Spain'])
Argentina = len(datafr[datafr['Nationality'] == 'Argentina'])
France = len(datafr[datafr['Nationality'] == 'France'])
Brazil = len(datafr[datafr['Nationality'] == 'Brazil'])
Italy = len(datafr[datafr['Nationality'] == 'Italy'])
Colombia = len(datafr[datafr['Nationality'] == 'Colombia'])
Japan = len(datafr[datafr['Nationality'] == 'Japan'])
Netherlands = len(datafr[datafr['Nationality'] == 'Netherlands'])
labels = 'England','Germany','Spain','Argentina','France','Brazil','Italy','Colombia','Japan','Netherlands'
sizes = [England,Germany,Spain,Argentina,France,Brazil,Italy,Colombia,Japan,Netherlands]
plt.figure(figsize=(6,6))
# 画饼图
plt.pie(sizes, explode=(0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05), labels=labels, colors=sns.color_palette("Purples"),
autopct='%1.1f%%', shadow=True, startangle=90)
sns.set_context("paper", font_scale=1.2)
plt.title('Ratio of players by different Nationality', fontsize=16)
plt.show()
```
![](15.jpg)
非常直观,这幅图其实也从侧面反映了各大足球强国的甲级联赛的风靡程度。
当然,还有很多特征与综合水平有关,在这里我就不一一列举了,就当是作业留给你自己去动手挖掘了。祝愿你能发现一些有趣的信息。

@ -0,0 +1,6 @@
# 第十三章 综合实战FIFA球员数据分析与推荐
在本书的最后,我们来做点更加有趣的事情。假设你现在是一支足球俱乐部的高层管理者,有一天,你的上司对你说:“我们俱乐部需要 2 名像梅西一样的球员来当替补”。那你接到这个任务后,我想你肯定会去分析球员的一些历史数据,然后来招募一些你所需要的球员。
我想你已经猜到了,没错!这次的数据是 FIFA 2019 年的数据。废话不多说,我们开始吧!

@ -0,0 +1,65 @@
# 13.1 初步分析数据
这一步再熟悉不过了,可能会熟悉地让人心疼。但这又是数据挖掘非常重要也是必不可少的一步。
```python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
datafr = pd.read_csv("./data.csv")
# 查看前10行数据到底长什么样子
datafr.head(10)
```
![](1.jpg)
![](2.jpg)
![](3.jpg)
![](4.jpg)
![](5.jpg)
![](6.jpg)
![](7.jpg)
![](8.jpg)
怎么样,是不是感觉有点懵,有这么多特征,还有一些缺失值。看来这个数据集并不是很好处理的样子。是的,我们需要一步一步脚印的来分析它。
还是按照惯例,看一下数据集有多少行多少列。
```python
print("Dimension of the dataset is: ",datafr.shape)
```
![](9.jpg)
总共 89 个特征!然后再看一下数据缺失的情况。
```python
# 统计出含有缺失值的特征的数量
datafr.isnull().sum().sort_values(ascending=False)
```
![](10.jpg)
![](11.jpg)
![](12.jpg)
总共 89 个特征,也就只有 13 个特征是完好无损的。不过值得庆幸的是,含有缺失值的特征们的缺失比例并不高,只有 Loaned From 这个特征的缺失严重。所以我们暂且可以认为这个特征没有什么用处,把它删掉就好了。
```python
# 删除Loaned From
datafr.drop('Loaned From',axis=1,inplace=True)
```

@ -0,0 +1,98 @@
# 13.3 球员推荐
现在有这样一个需求需要从球员库中找到与某位球员相似度比较高的几位球员。那这样一个需求应该怎样实现呢我们可以回忆一下本书中提到的算法中哪个算法是与相似度有关的。没错就是k近邻算法所以我们只需要使用k近邻算法来找出离某位球员距离最近的 k 位球员就行了。不过在使用k近邻算法之前我们需要对数据进行一些处理。
首先是删除一些无用的特征。
```python
# 这些特征并没有太多用处,所以删掉。例如上一节所提到的球衣号码
datafr.drop(['ID','Unnamed: 0','Weak Foot','Release Clause','Wage','Photo', 'Nationality', 'Flag', 'Club Logo', 'International Reputation', 'Body Type', 'Real Face','Jersey Number', 'Joined','LS', 'ST', 'RS', 'LW', 'LF', 'CF', 'RF', 'RW','LAM', 'CAM', 'RAM', 'LM', 'LCM', 'CM', 'RCM', 'RM', 'LWB', 'LDM','CDM', 'RDM', 'RWB', 'LB', 'LCB','CB', 'RCB', 'RB'], axis=1,inplace=True)
# 查看剩下的特征名称
datafr.columns
```
![](17.jpg)
然后需要对剩下的特征做一些基本处理,比如离散化编码,处理缺失值等。
```python
attributes = datafr.iloc[:, 14:]
attributes['Skill Moves'] = datafr['Skill Moves']
attributes['Age'] = datafr['Age']
# 独热编码
workrate = datafr['Work Rate'].str.get_dummies(sep='/ ')
attributes = pd.concat([attributes, workrate], axis=1)
df = attributes
# 删掉有缺失值的数据
attributes = attributes.dropna()
df['Name'] = datafr['Name']
df['Position'] = datafr['Position']
df = df.dropna()
print(attributes.columns)
```
![](18.jpg)
接着看一下这些特征的相关性。
```python
plt.figure(figsize=(9,9))
# 计算特征之间的协方差
corr = attributes.corr()
# 因为相关性的热力图是对角线对称的,所以把上三角形遮掉看起来会清晰点
mask = np.zeros_like(corr, dtype=np.bool)
mask[np.triu_indices_from(mask)] = True
# 画相关性热力图
sns.heatmap(corr, mask=mask, cmap="RdBu", vmax=.3, center=0,
square=True, linewidths=.7, cbar_kws={"shrink": .7})
```
![](19.jpg)
从上面的相关图可以看出,守门员的许多属性与前锋、中场和后卫的属性呈负相关。而且大部分特征之间的相关性比较小,算是比较独立的特征。
验证完相关性后需要对特征进行标准化来减少特征值的量纲所带来的影响因为k近邻算法对量纲非常敏感。
```python
# 导入sklearn提供的z-score标准化接口
from sklearn.preprocessing import StandardScaler
scaled = StandardScaler()
X = scaled.fit_transform(attributes)
```
最后,可以使用 sklearn 提供的k近邻算法接口来建模。
```python
from sklearn.neighbors import NearestNeighbors
# 使用k近邻算法建模
recommendations = NearestNeighbors(n_neighbors=5,algorithm='kd_tree')
recommendations.fit(X)
```
有了模型之后,就可以实现相似球员的推荐了。
```python
def get_index(x):
return df[df['Name']==x].index.tolist()[0]
def recommend_similar(player):
print("These are 4 players similar to {} : ".format(player))
index= get_index(player)
for i in player_index[index][1:]:
print("Name: {0}\nPosition: {1}\n".format(df.iloc[i]['Name'],df.iloc[i]['Position']))
# 查找与哈扎德相似的4名球员
recommend_similar('E. Hazard')
```
![](20.jpg)
比利时的哈扎德,现在效力于皇家马德里。他的球风和能力与巴西的国脚内马尔是比较相近的。从结果可以看出,我们的推荐模型还是比较准确的。

@ -50,4 +50,17 @@
* [10.3 基于矩阵分解的协同过滤算法原理](Chapter10/基于矩阵分解的协同过滤算法原理.md)
* [10.4 动手实现基于矩阵分解的协同过滤](Chapter10/动手实现基于矩阵分解的协同过滤.md)
* [10.5 实现电影推荐系统](Chapter10/实战案例.md)
* [第十一章 综合实战:森林火灾数据可视化](Chapter11/README.md)
* [11.1 亚马逊雨林数据初窥](Chapter11/亚马逊雨林数据初窥.md)
* [11.2 使用图表来探索亚马逊雨林](Chapter11/使用图表来探索亚马逊雨林.md)
* [11.3 亚马逊雨林地图可视化](Chapter11/亚马逊雨林地图可视化.md)
* [第十二章 综合实战:信用卡欺诈检测](Chapter12/README.md)
* [12.1 了解数据](Chapter12/了解数据.md)
* [12.2 对匿名特征进行处理](Chapter12/对匿名特征进行处理.md)
* [12.3 解决样本不平衡问题](Chapter12/解决样本不平衡问题.md)
* [12.4 使用sklearn实现欺诈检测功能](Chapter12/使用sklearn实现欺诈检测功能.md)
* [12.5 验证算法性能](Chapter12/验证算法性能.md)
* [第十三章 综合实战FIFA球员数据分析与推荐](Chapter13/README.md)
* [13.1 初步分析数据](Chapter13/初步分析数据.md)
* [13.2 FIFA数据可视化](Chapter13/FIFA数据可视化.md)
* [13.3 球员推荐](Chapter13/球员推荐.md)

Binary file not shown.
Loading…
Cancel
Save