【机器学习实战】科学处理鸢尾花数据集

目录

一、案例分析

二、数据处理

2.1 回答问题

2.2 检查数据

2.3 清理数据

2.4 测试数据

三、用 scikit-learn 来预测数据

3.1 选出特征 (输入变量) 和标记 (输出变量)

3.2 划分训练集和测试集

3.3 用模型来学习

四、思考题


一、案例分析

假设我们要创建一个智能手机应用程序,从智能手机拍摄的照片中自动识别花的种类。 我们需创建一个演示机器学习模型,测量花的萼片长度 (sepal length),萼片宽度 (sepal width),花瓣长度 (petal length) 和花瓣宽度 (petal width) 四个变量,并根据这些测量识别物种。

如图,花的萼片和花瓣。(萼片是花的最外一环)

三种类型的鸢尾花,如图所示:

根据当地研究人员测量的每种鸢尾花的四个数据 (萼片长/宽和花瓣长/宽),我们最终目的是想正确的分类这三种花。

 

二、数据处理

2.1 回答问题

任何数据分析项目的第一步就是提出想要解决的问题,并为成功解决该问题而定义一个度量 。一些常见的问题如下:

(1)在查看数据之前是否了解数据分析问题的类型,是回归,分类还是聚类问题?(明晰问题本质)

这是个根据萼片长度,萼片宽度,花瓣长度和花瓣宽度四个测量指标的分类问题

(2)是否在一开始就定义了成功的度量?(设定量化指标)

因为是分类问题,所以可以使用查准率,即正确分类花的百分比,来量化模型的表现。我们的数据主管告诉我们应该实现 90% 的准确性。

(3)现有数据是否解决分类问题?(了解数据局限性)

我们目前的数据集只有三种类型的鸢尾花。从这个数据集建立的模型将只适用于那些鸢尾花,未来创建一个通用的花分类器需要更多的数据

(注意:思考这些问题执行有效数据分析的重要一步,不可忽略哦。)

2.2 检查数据

在花费太多时间分析数据之前,提早检查并修正这些数据错误能节省大量时间。一般来说,我们希望回答以下问题:

  1. 数据格式有什么问题吗?
  2. 数据数值有什么问题吗?
  3. 数据需要修复或删除吗?

首先引进 python 里面的几个包,numpy 是为了做数学运算,pandas 是为了处理数据,matplotlib 是为了画图,seaborn 是为了画高级图。代码如下:

import numpy as np     # 用来做数学运算
import pandas as pd    # 用来处理数据表
import seaborn as sns   # 用来画高级统计图

# 将所有图都在 Notebook 里显示
%matplotlib inline               
import matplotlib.pyplot as plt  # 用来画图

from sklearn.model_selection import train_test_split     # 做交叉验证,划分训练集和测试集
from sklearn.tree import DecisionTreeClassifier           # 用决策树来分类

检查点 1. 数据格式 (format)

首先用 pandas 读取 csv 文件并将数据存成数据表 (data frame) 格式。

iris_data = pd.read_csv('data/iris-data.csv', na_values=['NA'])   # 从名为iris_data的csv文件读数据存成数据表
#第二个参数用来把 csv 里面空白处用 NaN 代替
iris_data.head(5).append(iris_data.tail())    # 展示数据表前5个和后5个数据
 sepal_length_cmsepal_width_cmpetal_length_cmpetal_width_cmclass
05.13.51.40.2Iris-setosa
14.93.01.40.2Iris-setosa
24.73.21.30.2Iris-setosa
34.63.11.50.2Iris-setosa
45.03.61.40.2Iris-setosa
1456.73.05.22.3Iris-virginica
1466.32.55.02.3Iris-virginica
1476.53.05.22.0Iris-virginica
1486.23.45.42.3Iris-virginica
1495.93.05.11.8Iris-virginica

检查点 2. 数据统计 (statistics)

接下来,检查数据的分布可以识别异常值。我们从数据集的汇总统计数据开始。

iris_data.describe()    # 检查四列数据的个数,平均数,标准差,最小值,最大值和25,50,75的百分位数
 sepal_length_cmsepal_width_cmpetal_length_cmpetal_width_cm
count150.000000150.000000150.000000145.000000
mean5.6446273.0546673.7586671.236552
std1.3127810.4331231.7644200.755058
min0.0550002.0000001.0000000.100000
25%5.1000002.8000001.6000000.400000
50%5.7000003.0000004.3500001.300000
75%6.4000003.3000005.1000001.800000
max7.9000004.4000006.9000002.500000

从该表中看到几个有用的值。 例如,我们看到缺少 5 条花瓣宽度的数据(表里 count 那一行的萼片长度,萼片宽度和花瓣长度的个数都是 150 个,唯独花瓣宽度是 145 个)。此外,这样的表给不了太多有用信息,除非我们知道数据应该在一个特定的范围 (如萼片长度的最小值是 0.055,和它其他指标如均值和几个百分位数都不是一个数量级的,很有可能是测量错误)。 

比起一串枯燥的数值,我们可能更喜欢绚烂的绘图。接下来可视化数据,它能使异常值立即脱颖而出。

sns.pairplot(iris_data.dropna(), hue='class')    # 画散点矩阵图

  • 第一个参数 iris_data.dropna() 就是除去 NaN 的数据表,这么做原因很简单,图里不可能显示的出 NaN 值的;
  • 第二个参数 hue = 'class' 就是根据类 (class) 下不同的值赋予不同的颜色 (hue 就是色彩的意思) 。

散点矩阵图绘制前四列变量(萼片长/宽和花瓣长/宽)的相关系数图,而且用不同颜色区分不同的类下面的这四个变量。 从上图可知,横轴纵轴都有四个变量,那么总共可以画出 16 (4*4) 张小图。

  • 对角线上的 4 张都是某个变量和自己本身的关系,由于自己和自己的相关系数永远是 1,画出相关系数图意义不大。
  • 非对角线的 12 张就是某个变量和另一个变量的关系。比如第一行第二列的图描述的就是萼片长度 (看纵轴第一个 sepal_length_cm 字样) 和萼片宽度 (看横轴第二个 sepal_width_cm 字样)。

从散点矩阵图中,我们可以迅速看出数据集的一些问题:

(1)图的右侧标注这五个类 (Iris-setosa, Iris-setossa, Iris-versicolor, versicolor, Iris-virginica),但原本要分类的花只有三类 (Iris-setosa, Iris-versicolor, Iris-virginica)。这意味着在记录数据时可能会犯下一些错误。

(2)在测量中有一些明显的异常值可能是错误的。

  • 例如第一行后三张小图,对于 Iris-setosa (山鸢尾花,蓝点),一个萼片宽度值落在其正常范围之外;
  • 例如第二行第一,三,四张小图,对于 Iris-versicolor (变色鸢尾花,红点) ,几个萼片长度值都接近零。

下一步我们的任务是要处理错误的数据。

2.3 清理数据

修正 1. 数据类(class)

问题:按理应该只有三个类,图中却显示五个。

原因是:标记数据时忘记在 Iris-versicolor 之前添加 Iris-。另一个类 Iris-setossa 他们只是多打了一个 s。让我们使用代码来修复这些错误。

iris_data['class'].unique()    # 查看数据表的类的不重复值,有5个,但按理说只有3个
array(['Iris-setosa', 'Iris-setossa', 'Iris-versicolor', 'versicolor',
       'Iris-virginica'], dtype=object)
iris_data.loc[iris_data['class'] == 'versicolor', 'class'] = 'Iris-versicolor'   # 将 versicolor 改为 Iris-versicolor
iris_data.loc[iris_data['class'] == 'Iris-setossa', 'class'] = 'Iris-setosa'     # 将 Iris-setossa 改为 Iris-setosa

iris_data['class'].unique()    # 再查看数据表的类的不重复值,现在只有3个
array(['Iris-setosa', 'Iris-versicolor', 'Iris-virginica'], dtype=object)

更新后的散点矩阵图如下:

现在只有三个类而分别是 Iris-setosa, Iris-versicolor 和 Iris-virginica。

修正点 2. 异常数值 (outliers)

修复异常值是一件棘手的事情。因为我们很难判断异常值是否由测量误差引起,或者是不正确的单位记录数据,或者是真正的异常。如果我们决定排除任何数据,需要记录排除的数据并提供排除该数据的充分理由。由上节所知,我们有两种类型的异常值。

问题:山鸢尾花的一个萼片宽度值落在其正常范围之外 (黑色圆框)。

我们发现,山鸢尾花 (Iris-setosa) 的萼片宽度 (sepal_width_cm) 不可能低于 2.5 厘米。显然,这个记录是错误的,这种情况下最有效的方法是删除它而不是花时间查找原因。但是,我们仍需要知道有多少个类似这样的错误数据,如果很少删除它没有问题,如果很多我们需要查明原因。

# 查看 Iris-setosa 里萼片宽度小于2.5厘米的数据,只有一个
iris_data.loc[(iris_data['class'] == 'Iris-setosa') & (iris_data['sepal_width_cm'] < 2.5)]    
 sepal_length_cmsepal_width_cmpetal_length_cmpetal_width_cmclass
414.52.31.30.3Iris-setosa
iris_data.loc[iris_data['class'] == 'Iris-setosa', 'sepal_width_cm'].hist()

第一行代码是用数据表里的 loc[] 函数来找到类值为 Iris-setoa (因为是蓝点) 并且 sepal width 小于 2.5 的所有行。最后发现只有一个这样的数据,而上图的条形图也确认了这样的异常值只有一个。因此可以直接删除此数据。

# 去掉 Iris-setosa 里萼片宽度大于2.5厘米的数据,然后画出其条形图
iris_data = iris_data.loc[(iris_data['class'] != 'Iris-setosa') | (iris_data['sepal_width_cm'] >= 2.5)]
iris_data.loc[iris_data['class'] == 'Iris-setosa', 'sepal_width_cm'].hist()

第一行代码将类值为 Iris-setosa 并且 sepal width 大于 2.5 的所有数据都新存到 iris_data 中。从上面条形图也看到了再没有这个异常值。现在所有的山鸢尾花的萼片宽度都大于 2.5 厘米。

问题:变色鸢尾花的几个萼片长度值接近与零 (黑色椭圆框)。

我们发现,所有这些接近零的 sepal_length_cm 似乎错位了两个数量级。

# 查看 Iris-versicolor 里的萼片长度接近于零的所有数据
iris_data.loc[(iris_data['class'] == 'Iris-versicolor') & (iris_data['sepal_length_cm'] < 1.0)]
 sepal_length_cmsepal_width_cmpetal_length_cmpetal_width_cmclass
770.0673.05.01.7Iris-versicolor
780.0602.94.51.5Iris-versicolor
790.0572.63.51.0Iris-versicolor
800.0552.43.81.1Iris-versicolor
810.0552.43.71.0Iris-versicolor
# 画出其条形图
iris_data.loc[iris_data['class'] == 'Iris-versicolor', 'sepal_length_cm'].hist()

第一行代码是用数据表里的 loc[] 函数来找到类值为 Iris-versicolor (因为是红点) 并且 sepal length 接近零的所有行,发现有五个数据,而条形图最左边显示的数据个数也确认了是五个。

# 将萼片长度乘以100倍,从单位米换成单位厘米
iris_data.loc[(iris_data['class'] == 'Iris-versicolor') &
              (iris_data['sepal_length_cm'] < 1.0),
              'sepal_length_cm'] *= 100.0

iris_data.loc[iris_data['class'] == 'Iris-versicolor', 'sepal_length_cm'].hist()

修正点 3. 缺失数值 (missing value)

我们还有些 NaN 数据。通常我们有两种方式来处理这类数据。

  1. 删除 (deletion)
  2. 插补 (imputation)

在本例中删除不是理想的做法,特别是考虑到它们都在 Iris-setosa 下,如图

所有缺失的值都属于 Iris-setosa类,直接删除可能会对日后数据分析带来偏差。此外,可以用插补方法,其最常见的方法平均插补 (mean imputation)。其做法就是“假设知道测量的值落在一定范围内,就可以用该测量的平均值填充空值”。

# 查看所有有NaN值的行数据,发现只有花瓣宽度 (petal_width_cm) 列下才有
iris_data.loc[(iris_data['sepal_length_cm'].isnull()) |
              (iris_data['sepal_width_cm'].isnull()) |
              (iris_data['petal_length_cm'].isnull()) |
              (iris_data['petal_width_cm'].isnull())]
 sepal_length_cmsepal_width_cmpetal_length_cmpetal_width_cmclass
75.03.41.5NaNIris-setosa
84.42.91.4NaNIris-setosa
94.93.11.5NaNIris-setosa
105.43.71.5NaNIris-setosa
114.83.41.6NaNIris-setosa
# 画出其条形图
iris_data.loc[iris_data['class'] == 'Iris-setosa', 'petal_width_cm'].hist()

接下来用 hist() 函数画出 Iris-setosa 花瓣宽度的条形图,可以清楚看到大多数宽度在 0.25 左右。

# 用平均值来代替NaN值
average_petal_width = iris_data.loc[iris_data['class'] == 'Iris-setosa', 'petal_width_cm'].mean()

iris_data.loc[(iris_data['class'] == 'Iris-setosa') &
              (iris_data['petal_width_cm'].isnull()),
              'petal_width_cm'] = average_petal_width

iris_data.loc[(iris_data['class'] == 'Iris-setosa') &
              (iris_data['petal_width_cm'] == average_petal_width)]
 sepal_length_cmsepal_width_cmpetal_length_cmpetal_width_cmclass
75.03.41.50.25Iris-setosa
84.42.91.40.25Iris-setosa
94.93.11.50.25Iris-setosa
105.43.71.50.25Iris-setosa
114.83.41.60.25Iris-setosa

然后用 mean() 准确求出其宽度的平均值,将其 NaN 值全部用平均值代替,最后打出那 5 行插补后的数据表。

# 确保所有NaN值都已被更新
iris_data.loc[(iris_data['sepal_length_cm'].isnull()) |
              (iris_data['sepal_width_cm'].isnull()) |
              (iris_data['petal_length_cm'].isnull()) |
              (iris_data['petal_width_cm'].isnull())]

为了确保所有 NaN 值已被替换,再次用 iris_data[A].isnull() 语句来查看,出来的结果是一个只有列标题的空数据表。这表示表内已经没有 NaN 值了。

2.4 测试数据

1. 存储数据(save data)

iris_data.to_csv('data/iris-data-clean.csv', index = False)    # 将整理好的数据写到一个名叫iris-data-clean的csv文件里,没有行标

iris_data_clean = pd.read_csv('data/iris-data-clean.csv')      # 重新从iris-data-clean的csv文件里读数据存成iris_data_clean数据表

让我们再看看基于干净数据画的散点矩阵图吧。

sns.pairplot(iris_data_clean, hue='class')  # 查看散点矩阵图

从上图可看到:

  1. 五个类变成三个类;
  2. 异常值全部删除或修正了。

2. 声明数据(assert data)

为了防止一些数据问题没有解决,我们可以用 assert 语句来做声明。该语句好处是,在运行时如果声明语句为真,没有任何事发生,反之会报错而警告我们有哪些错误数据需要注意且修正。

# 声明花只有三种类型
assert len(iris_data_clean['class'].unique()) == 3

# 声明变色鸢尾花的萼片长度应该大于 2.5 厘米
assert iris_data_clean.loc[iris_data_clean['class'] == 'Iris-versicolor', 'sepal_length_cm'].min() >= 2.5

# 数据不应该有缺失
assert len(iris_data_clean.loc[(iris_data_clean['sepal_length_cm'].isnull()) |
                               (iris_data_clean['sepal_width_cm'].isnull()) |
                               (iris_data_clean['petal_length_cm'].isnull()) |
                               (iris_data_clean['petal_width_cm'].isnull())]) == 0

如果任何声明被违反,我们应该立即停止分析,而回到整理阶段。

 

三、用 scikit-learn 来预测数据

3.1 选出特征 (输入变量) 和标记 (输出变量)

# 读数据
iris_data_clean = pd.read_csv('data/iris-data-clean.csv')

# 选出特征
all_inputs = iris_data_clean[['sepal_length_cm', 'sepal_width_cm',
                             'petal_length_cm', 'petal_width_cm']].values

# 选出标记
all_classes = iris_data_clean['class'].values

3.2 划分训练集和测试集

(training_inputs, test_inputs, training_classes, 
 test_classes) = train_test_split(all_inputs, all_classes, train_size=0.75, random_state=1)

用 75% 和 25% 的比例来划分训练集和测试集,设一个 random_state 是为了在机器学习中每次出的结果一样,便于找问题。

3.3 用模型来学习

# 创建决策树分类器
decision_tree_classifier = DecisionTreeClassifier()

# 训练数据
decision_tree_classifier.fit(training_inputs, training_classes)

# 分类精度
decision_tree_classifier.score(test_inputs, test_classes)
0.9736842105263158

再一次感叹 scikit-learn 的强大和好用。

 

四、思考题

这时候有人会问了:

  1. 这个训练集和测试集划分是随机的,一次不足以说明你查准率高;
  2. 这数据太少没代表性,换套数据模型可能过拟合;
  3. 特征选择有很大学问,凭什么就选萼片长度,萼片宽度,花瓣长度和花瓣宽度这四个变量?

有待学习,思考哦。

 

 

  • 32
    点赞
  • 200
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值