机器学习

1 数据探索性分析

1.1 介绍

数据探索性分析,Exploratory Data Analysis(EDA),是指对已有数据在尽量少的先验假设下,通过作图、制表、方程拟合、计算特征量等手段探索数据的结构和规律的一种数据分析方法。

获得数据后,首先需要进行数据探索性分析。
作用:

  1. 获得对数据集的感性认识。
    对特征间的关系以及特征与标签间的关系进行初步分析有助于熟悉数据集;
  2. 为数据预处理过程提供灵感与思路。
    例如数据的异常值和缺失值处理等,这样做可以确保数据集的结构和特征在接下来的问题分析过程中更加可靠。
  3. 获得特征工程的启发。
    判断出各个特征的重要程度以及相关性,为特征选择作准备工作。
1.2 流程
  1. 载入数据并简略观察数据;
  2. 总览数据概况;
    使用describe观察每列数据的统计量、均值、标准差、最小值、最大值、四分位点(25%、50%、75%)等,这样做有助于快速掌握数据的大致范围并进行异常值的判断。
  3. 将describe返回的结果结合matplotlib进行可视化分析;
    例如使用柱状图查看数据的相关统计量。
  4. 缺失值处理;
  5. 查看目标数据分布
    对于分类问题,重点查看类别是否分布不均衡,如果分布不均衡,考虑使用过抽样/欠抽样处理;
    对于回归问题,重点查看是否存在离群点数据,可以考虑过滤掉离群点数据。
    离群点是指一个数据序列中,远离序列一般水平的极端值数据点(极大值/极小值),且这些值会对数据分析结果产生异常影响。
  6. 绘制特征分布图
    绘制数值特征分布图(直方图),
    可以观测特征属于连续型还是离散型,
    可以观测特征数值的范围和分布情况,
    可以进一步判断是否存在离群点。
    绘制类别特征分布图(柱状图),可以查看特征中是否存在稀疏类。
    在构建模型时稀疏类往往会出现问题,但如果当前特征比较重要则可以将特征的稀疏类数据删除。
  7. 查看特征之间的相关性(热力图)
    一般特征与特征之间的相关性越弱越好,通常认为相关系数大于0.5为强相关。
    相关性强的特征可以认为是冗余特征,可以考虑删除。
  8. 查看特征和标签之间的相关性
    一般特征与标签之间的相关性越强越好。
    特征与标签之间的相关性越强,则说明特征对结果的影响权重越高,特征越重要。

2 机器学习项目实战-EDA

2.1 工作流程
  1. 数据清洗与格式转换
  2. 数据探索性分析
  3. 特征工程
  4. 建立基础模型,尝试多种算法
  5. 模型调参
  6. 评估与测试
  7. 交付上线使用
2.2 数据集背景介绍

2009年《纽约市基准法律》要求对建筑的能源和水的使用信息进行说明和评分。 涵盖的建筑包括具有单个建筑物的总建筑面积超过50,000平方英尺(平方英尺)和群建筑面积超过100,000平方英尺,指标是由环境保护署的工具ENERGY STAR Portfolio Manager计算的,并且数据由建筑物所有者自行报告。

标签数据ENERGY STAR Score表示指定建筑物类型从1到100的百分位排名,在投资组合管理器中根据自我报告计算报告年度的能源使用情况。

2.3 数据清洗与格式转换

导入并查看数据

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
%matplotlib inline

data = pd.read_csv('data/Energy_and_Water_Data_Disclosure_for_Local_Law_84_2017__Data_for_Calendar_Year_2016_.csv')

# 查看所有列
data.columns
'''
Index(['Order', 'Property Id', 'Property Name', 'Parent Property Id',
       'Parent Property Name', 'BBL - 10 digits',
       'NYC Borough, Block and Lot (BBL) self-reported',
       ...
       'Source EUI (kBtu/ft²)', 'Release Date', 'Water Required?',
       'DOF Benchmarking Submission Status', 'Latitude', 'Longitude',
       'Community Board', 'Council District', 'Census Tract', 'NTA'],
      dtype='object')
'''

# 查看数据形状
data.shape  # (11746, 60)

# 查看数据字段类型,是否存在缺失值
data.info()

数据集中某些字段存在值"Not Available",表示无效值,可以视为缺失值。
首先将"Not Available"转换为np.nan。

data = data.replace({'Not Available': np.nan})

数据集中某些字段中显示的数据是数字,但实际类型是字符串,将这些字段的数值类型转化为float。

for col in list(data.columns):
    if ('ft²' in col or 'kBtu' in col or 'Metric Tons CO2e' in col or 'kWh' in col or 'therms' in col or 'gal' in col or 'Score' in col):
        data[col] = data[col].astype(float)
2.4 数据探索性分析

将describe返回的结果结合matplotlib进行可视化分析。
使用柱状图查看数据的相关统计量。

data_desc = data.describe()
cols = data_desc.columns
print(len(cols))  # 37
index = data_desc.index[1:]  # 去除count行,不需要知道特征下元素的数量。
plt.figure(figsize=(30, 30))
# 遍历每个特征
for i in range(len(cols)):
    ax = plt.subplot(10, 4, i + 1)  # 10*4=40 > 37
    ax.set_title(cols[i])
    for j in range(len(index)):
        plt.bar(index[j], data_desc.loc[index[j], cols[i]])
plt.show()

在这里插入图片描述
正常的数据特征
最小值、中位数、最大值从低到高排列;
均值与方差差别有限。

特征Order的图形比较正常,因为最小值,中位数,最大值是从低到高错落分布,均值和标准差差别不大。
特征DOF Gross Floor Area的图形可能有问题,最大值比其它数值都大很多,说明最大值对应的数据可能是异常数据或离群点数据,如果最大值对应的数据量较少,可以考虑将该特征删除。
特征经度和维度的std极低,且其它数值分布均匀,说明特征经度和维度对结果的影响不大,可以考虑过滤掉该特征。

2.5 缺失值处理

查看每列特征的缺失值比例。

def missing_values_table(df):
    # 计算每一列缺失值的个数
    mis_val = df.isnull().sum(axis=0)

    # 计算每列缺失值占该列总数据的百分比
    mis_val_percent = 100 * df.isnull().sum(axis=0) / data.shape[0]

    # 将每一列缺失值的数量和缺失值的百分比级联到一起,形成一个新的表格
    mis_val_table = pd.concat([mis_val, mis_val_percent], axis=1)

    # 重新给上步表格的列命名
    mis_val_table_ren_columns = mis_val_table.rename(
        columns={0: 'Missing Values', 1: '% of Total Values'})

    # 将百分比不为0的行数据根据百分比进行降序排序
    mis_val_table_ren_columns = mis_val_table_ren_columns[
        mis_val_table_ren_columns.iloc[:, 1] != 0].sort_values(
        '% of Total Values', ascending=False).round(1)

    # 打印概述
    print("Your selected dataframe has " + str(df.shape[1]) + " columns.\n"
                                                              "There are " + str(mis_val_table_ren_columns.shape[0]) +
          " columns that have missing values.")

    # Return the dataframe with missing information
    return mis_val_table_ren_columns
missing_df = missing_values_table(data);
'''
Your selected dataframe has 60 columns.
There are 46 columns that have missing values.
'''
missing_df.head(3)
'''
 				Missing Values 	% of Total Values
Fuel Oil #1 Use (kBtu) 	 11737 				 99.9
Diesel #2 Use (kBtu) 	 11730 				 99.9
Address 2 				 11539 				 98.2
'''

设置阈值,将缺失比例超过50%的列删除。

# 找出超过阈值的列
missing_df = missing_values_table(data);
missing_columns = list(missing_df.loc[missing_df['% of Total Values'] > 50].index)
print('We will remove %d columns.' % len(missing_columns))
data = data.drop(columns=list(missing_columns))
'''
Your selected dataframe has 60 columns.
There are 46 columns that have missing values.
We will remove 11 columns.
'''

使用中位数填充剩下的空值。

for x in data.columns:
    # 过滤掉类型为object的列
    if str(data[x].dtypes) == 'object':
        continue
    if data[x].isnull().sum() > 0:
    	# 获取非空元素的中位数
        data[x] = data[x].fillna(value=np.median(data.loc[~data[x].isnull(), x]))
2.6 查看目标数据分布情况,离群点数据处理

当前问题属于回归问题,需要考虑的是离群点数据处理。

离群点
离群点指的是一个数据序列中,远离序列一般水平分布的极端数据,包括极大值和极小值。离群点数据会对数据分析产生异常影响,因此需要对离群点数据进行过滤。

查看特征ENERGY STAR Score中是否存在离群点数据。

data['ENERGY STAR Score'].hist(bins=20)

在这里插入图片描述
从直方图中可以看出,60靠后的数据点分布较多。

plt.figure(figsize=(40, 20))
plt.scatter(data['ENERGY STAR Score'].index,data['ENERGY STAR Score'].values)

在这里插入图片描述
可以看出散点图中有一条水平直线,说明在直线处(60附近)数据分布较为集中。

data['ENERGY STAR Score'].value_counts().sort_values().tail(1)
'''
65.0    2216
'''

结论:特征ENERGY STAR Score中不存在离群点数据。

查看特征Site EUI (kBtu/ft²)中是否存在离群点数据。

data['Site EUI (kBtu/ft²)'].describe()
'''
count     11746.000000
mean        277.274264
std        8547.276458
min           0.000000
25%          62.100000
50%          78.500000
75%          97.200000
max      869265.000000
Name: Site EUI (kBtu/ft²), dtype: float64
'''
data['Site EUI (kBtu/ft²)'].hist(bins=20)

在这里插入图片描述

plt.figure(figsize=(15, 8))
plt.scatter(data['Site EUI (kBtu/ft²)'].index, data['Site EUI (kBtu/ft²)'].values)

在这里插入图片描述
结论:从散点图中可以看出,特征Site EUI (kBtu/ft²)中很可能存在离群点数据。

判断并过滤离群点数据

离群点的判定指标

极小的离群点数据:x < (Q1 - 3 * IQ)
极大的离群点数据:x > (Q3 + 3 * IQ) 

Q1为序列中25%的四分位点,
Q3为序列中75%的四分位点,
IQ=Q3-Q1

在这里插入图片描述

q1 = data['Site EUI (kBtu/ft²)'].describe()['25%']
q3 = data['Site EUI (kBtu/ft²)'].describe()['75%']
iq = q3 - q1
# Remove outliers
data_copy = data[
    (data['Site EUI (kBtu/ft²)'] > (q1 - 3 * iq)) &
    (data['Site EUI (kBtu/ft²)'] < (q3 + 3 * iq))
]
data_copy['Site EUI (kBtu/ft²)'].hist(bins=30)

在这里插入图片描述

plt.scatter(data_copy['Site EUI (kBtu/ft²)'].index,data_copy['Site EUI (kBtu/ft²)'].values)

在这里插入图片描述

2.7 查看各个特征数据的分布情况

查看特征的不同取值数量。
数量少的特征可能是类别型特征,数量多的特征可能是连续型特征。

for x in data.columns:
    print('*' * 50)
    print(x, data[x].nunique())

绘制直方图

for col in data.columns:
    if 'int' in str(data[col].dtypes) or 'float' in str(data[col].dtypes):
        # plt.hist(data[col], bins=50)
        sns.distplot(data.loc[~data[col].isnull(), col])
        plt.title(col)
        plt.show()

近正太分布
在这里插入图片描述

2.8 长尾分布

在这里插入图片描述
长尾分布的特征中数量较少的数据是离群点数据,如果不选择删除,可以通过转换将其转换为正太或近正太分布。

通过log变换操作,将其转换为近正太分布。

sns.distplot(np.log(data.loc[~data['DOF Gross Floor Area'].isnull(), 'DOF Gross Floor Area']))

在这里插入图片描述

2.9 类别型特征的特征值化

结合着项目目标和对非数值型字段特征的理解,选取出两个代表性特征Borough和Largest Property Use Type,对对其进行onehot编码处理来实现特征值化。

features = data.loc[:, data.columns != 'ENERGY STAR Score']  # 提取特征数据
fea_name = features.select_dtypes('number').columns  # 提取数值型特征名称
features = features[fea_name]  # 提取数值型特征
# 提取指定的两个类别型特征
categorical_subset = data[['Borough', 'Largest Property Use Type']]
categorical_subset = pd.get_dummies(categorical_subset)
features = pd.concat([features, categorical_subset], axis=1)

探索特征之间的相关性。

plt.subplots(figsize=(30, 15))  # 指定窗口尺寸(单位英尺)
features_corr = features.corr().abs()  # 返回每一列与每一列之间的相关系数
# 数据为相关系数,显示数值,显示颜色条
sns.heatmap(features_corr, annot=True)

在这里插入图片描述

删除相关性强的冗余特征,工具包封装如下。

cols = features.columns  # 获取列的名称
corr_list = []
size = features.shape[1]  # 数据长度
high_corr_fea = []  # 存储相关系数大于0.5的特征名称
for i in range(0, size):
    for j in range(i + 1, size):
    	# 相关系数大于0.5
        if (abs(features_corr.iloc[i, j]) >= 0.5):
            corr_list.append([features_corr.iloc[i, j], i, j])  # features_corr.iloc[i,j],按位置选取数据

sorted_corr_list = sorted(corr_list, key=lambda xx: -abs(xx[0]))
# print(sorted_corr_list)
for v, i, j in sorted_corr_list:
    high_corr_fea.append(cols[i])
    print("%s and %s = %.2f" % (cols[i], cols[j], v))  # cols: 列名

删除相关性大于0.5的特征(两个特征中的一个)。

features.drop(labels=high_corr_fea, axis=1, inplace=True)

features.shape  # (11746, 69)
2.10 特征和标签之间的关系

如果特征和标签之间存在线性关系,可以采用如下方式处理。

target = data['ENERGY STAR Score']
target = pd.DataFrame(data=target, columns=['ENERGY STAR Score'])
# 级联features和target
new_data = pd.concat((features, target), axis=1)

计算特征和标签之间的相关性

fea_target_corr = abs(new_data.corr()['ENERGY STAR Score'][:-1])

相关性越大,则说明特征越重要。

2.11 保存数据
# 改名字
new_data = new_data.rename(columns={'ENERGY STAR Score': 'score'})
# 保存
new_data.to_csv('./data/eda_data.csv')

3 机器学习项目实战-建模

3.1 准备数据
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import MinMaxScaler
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
from sklearn.svm import SVR
from sklearn.neighbors import KNeighborsRegressor
from lightgbm import LGBMRegressor
# Hyperparameter tuning
from sklearn.model_selection import GridSearchCV,train_test_split
%matplotlib inline

data = pd.read_csv('./data/eda_data.csv').drop(labels='Unnamed: 0', axis=1)
fea_name = [x for x in data.columns if x not in ['score']]
feature = data[fea_name]
target = data['score']
x_train, x_test, y_train, y_test = train_test_split(feature, target, test_size=0.2, random_state=2020)
3.2 选择机器学习算法

回归问题

Linear Regression
Support Vector Machine Regression
Random Forest Regression
lightGBM
xgboost

模型的调用和评价方式是一样的,则封装如下工具函数。

# Function to calculate mean absolute error
def mae(y_true, y_pred):
    return np.mean(abs(y_true - y_pred))

# Takes in a model, trains the model, and evaluates the model on the test set
def fit_and_evaluate(model):
    # Train the model
    model.fit(x_train, y_train)
    # Make predictions and evalute
    model_pred = model.predict(x_test)
    model_mae = mae(y_test, model_pred)

    # Return the performance metric
    return model_mae
lr = LinearRegression()
lr_mae = fit_and_evaluate(lr)

print('Linear Regression Performance on the test set: MAE = %0.4f' % lr_mae)
# Linear Regression Performance on the test set: MAE = 20.8585
svm = SVR(C = 1000, gamma = 0.1)
svm_mae = fit_and_evaluate(svm)

print('Support Vector Machine Regression Performance on the test set: MAE = %0.4f' % svm_mae)
# Support Vector Machine Regression Performance on the test set: MAE = 21.3965
random_forest = RandomForestRegressor(random_state=60)
random_forest_mae = fit_and_evaluate(random_forest)

print('Random Forest Regression Performance on the test set: MAE = %0.4f' % random_forest_mae)
# Random Forest Regression Performance on the test set: MAE = 11.7919
# LGBMRegressor
lgb = LGBMRegressor(random_state=60)
lgb.fit(x_train.values,y_train.values)
lgb_mae = mae(y_test,lgb.predict(x_test))
print('lgb Performance on the test set: MAE = %0.4f' % lgb_mae)
# lgb Performance on the test set: MAE = 11.7255

条形图显示mae

plt.style.use('fivethirtyeight')
model_comparison = pd.DataFrame({
    'model': [
        'Linear Regression',
        'Support Vector Machine',
        'Random Forest',
        'lgb',
    ],
    'mae': [
        lr_mae,
        svm_mae,
        random_forest_mae,
        lgb_mae
    ]
})
model_comparison.sort_values('mae', ascending=False).plot(
    x='model',
    y='mae',
    kind='barh',
    color='red',
    edgecolor='black'
)
plt.ylabel('')
plt.yticks(size=14)
plt.xlabel('Mean Absolute Error')
plt.xticks(size=14)
plt.title('Model Comparison on Test MAE', size=20)

在这里插入图片描述

3.3 模型调参

这里以随机森林参数调优为例。

from sklearn.model_selection import GridSearchCV

parameters = {
    'n_estimators': [150, 200, 250],
    'max_depth': [2, 3, 5, 10, 15],
    'min_samples_leaf': [1, 2, 4, 6, 8],
    'min_samples_split': [2, 4, 6, 10]
}
model = RandomForestRegressor()
GS = GridSearchCV(estimator=model, param_grid=parameters, cv=5, scoring='neg_mean_absolute_error')
GS.fit(x_train, y_train)
GS.best_params_  
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐