本节主要对AQI进行预测,并学习相关知识点。
在预测之前,再次查看数据是否清洗到位,并对不符合建模要求的数据进行转换,在此次数据集中,需要将离散变量转换成离散变量。
#1、数据读取
#导入相关库
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
#进行初始化设置
sns.set(style="darkgrid")
plt.rcParams["font.family"]="SimHei"
plt.rcParams["axes.unicode_minus"]=False
warnings.filterwarnings("ignore")
#2、加载数据集
data=pd.read_csv("data-Copy1.csv")
print(data.shape)
#概览数据大致情况
data.head()
#3、空值、重复值处理
data.fillna({"Precipitation":data["Precipitation"].mean()},inplace=True)
data.drop_duplicates(inplace=True)
print(data.shape)
#4、数据转换
data["Coastal"]=data["Coastal"].map({"是":1,"否":0})
data["Coastal"].value_counts()
输出:
0 243
1 80
Name: Coastal, dtype: int64
我们先不进行处理,建立一个基模型,后续的操作可以在此基础上进行改进。
#建立基模型
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
X=data.drop(["City","AQI"],axis=1)
y=data["AQI"]
#划分训练集和测试集:
X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.3,random_state=0)
lr=LinearRegression()
lr.fit(X_train,y_train)
print("训练集R^2:",lr.score(X_train,y_train))
print("测试集R^2:",lr.score(X_test,y_test))
输出:
训练集R^2: 0.4541981876125555
测试集R^2: 0.40481803355516865
y_hat=lr.predict(X_test)
plt.figure(figsize=(15,5))
plt.plot(y_test.values,"-r",label="真实值",marker="o")
plt.plot(y_hat,"-g",label="预测值",marker="D")
plt.legend(loc="upper left")
plt.title("线性回归预测结果",fontsize=20)
输出:Text(0.5, 1.0, '线性回归预测结果')
上面使用了所有的原始数据作为特征建立模型,实际上,特征并非越多越好,有些特征对质量并没有什么改善,我们可以进行删除,同时也能提升模型训练速度。
(1)RFECV
特征选择的方式有很多,这里使用RFECV来实现特征选择,其分成两个部分,分别是:
其具体过程如下:
RFE阶段:
CV阶段:
#特征选择实例
from sklearn.feature_selection import RFECV
rfecv=RFECV(estimator=lr,step=1,cv=5,n_jobs=-1,scoring="r2")
#estimator:表示要操作的模型;step:每次删除的变量数;cv表示使用的交叉验证折数;n_jobs表示并发的数量;scoring表示评估的方式。
rfecv.fit(X_train,y_train)
print(rfecv.n_features_)#返回经过选择之后,剩余的特征数量
print(rfecv.estimator_)#返回特征选择之后,使用缩减特征训练后的模型
print(rfecv.ranking_)#返回每个特征的等级,数值越小,特征越重要
print(rfecv.support_)#返回布尔数组,用来表示特征是否被选择
print(rfecv.grid_scores_)#返回对应数量特征,模型交叉验证的评分。
输出:
8
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)
[1 2 1 1 1 1 3 1 1 1]
[ True False True True True True False True True True]
[-0.06091362 0.1397744 0.2302962 0.22814855 0.22644355 0.21342713
0.21318941 0.26363902 0.25743373 0.25389822]
从结果可知,我们删除了两个特征,我们可以绘制下,在特征选择过程中,我们选择交叉验证获取的R方的值。
plt.plot(range(1,len(rfecv.grid_scores_)+1),rfecv.grid_scores_,marker="o")
plt.xlabel("特征数量")
plt.ylabel("交叉验证$R^2值")
输出:
Text(0, 0.5, '交叉验证$R^2值')
print("剔除的变量:",X_train.columns[~rfecv.support_])
X_train_eli=rfecv.transform(X_train)
X_test_eli=rfecv.transform(X_test)
print(rfecv.estimator_.score(X_train_eli,y_train))
print(rfecv.estimator_.score(X_test_eli,y_test))
输出:
剔除的变量: Index(['GDP', 'PopulationDensity'], dtype='object')
0.4535472684773474
0.39917432038836986
从特征选择的结果可以看到,消除了GDP与PopulationDensity两个特征,而使用剩余8个特征训练的模型与之前基模型(10个特征)在训练集和测试集的表现上几乎都相同,这可以证明,我们清除的两个特征确实对拟合目标没有什么帮助,可以去掉。
如果数据存在异常值,很有可能会影响到模型的效果,因此在建模的时候非常有必要对异常值进行处理。
#coastal是类别变量,映射为离散变量,不会有异常值;
for col in X.columns.drop("Coastal"):
if pd.api.types.is_numeric_dtype(X_train[col]):
quartile=np.quantile(X_train[col],[0.25,0.75])
IQR=quartile[1]-quartile[0]
lower=quartile[0]-1.5*IQR
upper=quartile[1]+1.5*IQR
X_train[col][X_train[col]<lower]=lower
X_train[col][X_train[col]>upper]=upper
X_test[col][X_test[col]<lower]=lower
X_test[col][X_test[col]>upper]=upper
#效果对比:现在使用新的训练集与测试集来评估模型效果
lr.fit(X_train,y_train)
print(lr.score(X_train,y_train))
print(lr.score(X_test,y_test))
输出:
0.4635071067534817
0.44669286100652966
效果有轻微改进,不过不明显,我们可以使用RFECV在去除异常值的数据上,再次尝试。
rfecv=RFECV(estimator=lr,step=1,cv=5,n_jobs=-1,scoring="r2")
#estimator:表示要操作的模型;step:每次删除的变量数;cv表示使用的交叉验证折数;n_jobs表示并发的数量;scoring表示评估的方式。
rfecv.fit(X_train,y_train)
print(rfecv.n_features_)#返回经过选择之后,剩余的特征数量
print(rfecv.estimator_)#返回特征选择之后,使用缩减特征训练后的模型
print(rfecv.ranking_)#返回每个特征的等级,数值越小,特征越重要
print(rfecv.support_)#返回布尔数组,用来表示特征是否被选择
print(rfecv.grid_scores_)
plt.plot(range(1,len(rfecv.grid_scores_)+1),rfecv.grid_scores_,marker="o")
plt.xlabel("特征数量")
plt.ylabel("交叉验证$R^2值")
输出:
9
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)
[1 1 1 1 1 1 2 1 1 1]
[ True True True True True True False True True True]
[-0.06091362 0.1397744 0.19933237 0.16183209 0.18281661 0.20636585
0.29772708 0.30744774 0.30881521 0.30025764]
Text(0, 0.5, '交叉验证$R^2值')
print("剔除的变量:",X_train.columns[~rfecv.support_])
#X_train_eli=rfecv.transform(X_train)
#X_test_eli=rfecv.transform(X_test)
#为了方便后面列的筛选操作,这里我们换一种方式转换;
X_train_eli=X_train[X_train.columns[rfecv.support_]]
X_test_eli=X_test[X_test.columns[rfecv.support_]]
print(rfecv.estimator_.score(X_train_eli,y_train))
print(rfecv.estimator_.score(X_test_eli,y_test))
输出:
剔除的变量: Index(['PopulationDensity'], dtype='object')
0.4634635243609017
0.44562381612190854
注意:分箱后,我们不能将每个区间映射为离散数值,而是应当使用One-Hot编码。
from sklearn.preprocessing import KBinsDiscretizer
k=KBinsDiscretizer(n_bins=[4,5,14,6],encode="onehot-dense",strategy="uniform")
#KBinsDiscretizer k个分箱的离散器,用于将数值(通常是连续变量)进行区间离散化操作
#n_bins表示分箱(区间)的个数;
#encode离散化编码方式,分为
#onehot;使用独热编码;返回稀疏矩阵
#onehot-dense;使用独热编码;返回稠密矩阵
#ordinal;使用序数编码(0,1,2……)
#strategy:分箱的方式,分为uniform,quantile,kmeans
#uniform:每个区间的长度范围大概相同
#quantile:每个区间所包含的元素个数基本相同;
#使用一维kmeans方式进行分箱
#定义离散化特征:
discretize=["Longitude","Temperature","Precipitation","Latitude"]
r=k.fit_transform(X_train_eli[discretize])
r=pd.DataFrame(r,index=X_train_eli.index)
#获取除了离散化特征之外的其他特征
X_train_dis=X_train_eli.drop(discretize,axis=1)
#将离散化特征与其他特征进行重新组合
X_train_dis=pd.concat([X_train_dis,r],axis=1)
#对测试集进行同样的离散化操作
r=pd.DataFrame(k.transform(X_test_eli[discretize]),index=X_test_eli.index)
X_test_dis=X_test_eli.drop(discretize,axis=1)
X_test_dis=pd.concat([X_test_dis,r],axis=1)
#查看转换之后的格式
display(X_train_dis.head())
lr.fit(X_train_dis,y_train)
print(lr.score(X_train_dis,y_train))
print(lr.score(X_test_dis,y_test))
输出:
0.6900141413133731
0.6526825124900613
离散化后,从结果可以看到,模型效果有了很大的提升。
残差就是模型预测值与真实值之间的差异,我们可以绘制残差图来评估回归模型。残差图的横坐标为预测值,纵坐标为残差值
异方差性
对于一个好的回归模型,误差应该是随机分布的,因此残差也随机分布于中心线附近,如果我们从残差图中找出规律,说明模型遗漏了某些能够影响残差的解释信息。
异方差性是指残差具有明显的方差不一致性,这里我们异常值处理前后的两组数据,然后观察其残差效果。
fig,ax=plt.subplots(1,2)
fig.set_size_inches(15,5)
data=[X_train,X_train_dis]
title=["原始数据","处理后数据"]
for d,a,t in zip(data,ax,title):
model=LinearRegression()
model.fit(d,y_train)
y_hat_train=model.predict(d)
residual=y_hat_train-y_train.values
a.set_xlabel("预测值")
a.set_ylabel("残差")
a.axhline(y=0,color="red")
a.set_title(t)
sns.scatterplot(x=y_hat_train,y=residual,ax=a)
可以从左图中发现,随着预测值的增大,模型的误差也在增大,对于这种情况,我们采用对目标y值取对数的方式处理。
model=LinearRegression()
y_train_log=np.log(y_train)
y_test_log=np.log(y_test)
model.fit(X_train,y_train_log)
y_hat_train=model.predict(X_train)
residual=y_hat_train-y_train_log.values
plt.xlabel("预测值")
plt.ylabel("残差")
plt.axhline(y=0,color="red")
sns.scatterplot(x=y_hat_train,y=residual)
离群点:多元线性回归不像一元线性回归能轻易看出离群点,这时我们采用绘制残差图,通过预测值与实际值之间的关系,来检测离群点。
y_hat_train=lr.predict(X_train_dis)
residual=y_hat_train-y_train.values
r=(residual - residual.mean())/residual.std()
plt.xlabel("预测值")
plt.ylabel("残差")
plt.axhline(y=0,color="red")
sns.scatterplot(x=y_hat_train[np.abs(r)<=2],y=residual[np.abs(r)<=2],color="b",label="正常值")
sns.scatterplot(x=y_hat_train[np.abs(r)>2],y=residual[np.abs(r)>2],color="orange",label="异常值")
X_train_dis_filter=X_train_dis[np.abs(r)<=2]
y_train_filter=y_train[np.abs(r)<=2]
lr.fit(X_train_dis_filter,y_train_filter)
print(lr.score(X_train_dis_filter,y_train_filter))
print(lr.score(X_test_dis,y_test))
输出:
0.7320093475940779
0.6358993554477039
这一篇太难了,敲了一遍也没懂,后面碰到有印象的再去拓展、加深印象。