失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 通过线性回归模型及优化实现AQI分析与预测

通过线性回归模型及优化实现AQI分析与预测

时间:2021-05-31 21:04:16

相关推荐

通过线性回归模型及优化实现AQI分析与预测

目录

1 项目背景与分析说明1.1 项目背景1.2 数据说明1.3 分析说明2 数据预处理2.1 导入相关库2.2 导入数据2.3 数据预处理2.3.1 缺失值2.3.2 异常值2.3.3 重复值3 数据分析3.1 临海城市的空气质量是否优于内陆城市?3.2 空气质量主要受哪些因素影响?3.3 关于空气质量的验证3.4 通过线性回归建模及优化,对空气质量进行预测3.4.1 特征选择3.4.2 异常值处理3.4.3 分箱离散化3.4.4 残差图分析3.5 模型总结4 分析总结

1 项目背景与分析说明

1.1 项目背景

AQI(Air Quality Index),指空气指数,用来衡量空气清洁或污染的程度,值越小,表示空气质量越好。由于空气污染问题,近年来空气质量越来越受人们的重视。

1.2 数据说明

本数据集总记录了全国主要地区城市共325地,使用的字段有12列包括:

· City:城市名

· AQI:空气质量指数

· Precipitation:降雨量

· GDP:城市生产总值

· Temperature:湿度

· Longitude:经度

· Latitude:纬度

· Altitude:海拔高度

· PopulationDensity:人口密度

· Coastal:是否沿海

· GreenCoverageRate:绿化覆盖率

· Incineration(10,000ton):焚烧量(10,000吨)

1.3 分析说明

本次我们期望能够运用线性回归模型算法以及相关的模型优化来对全国城市空气质量进行分析,希望能够解决一下疑问:

· 临海城市的空气质量是否有别于内陆城市?

· 空气质量主要受哪些因素影响?

· 全国城市空气质量普遍处于何种水平?

· 怎样预测一个城市的空气质量?

2 数据预处理

2.1 导入相关库

import numpy as npimport pandas as pdimport matplotlib.pyplot as pltimport seaborn as snsimport warningssns.set(style='darkgrid')plt.rcParams['font.family'] = 'SimHei'plt.rcParams['axes.unicode_minus'] = Falsewarnings.filterwarnings('ignore')

2.2 导入数据

data = pd.read_csv('./data.csv')data.head(10)

数据集展示如下:

2.3 数据预处理

2.3.1 缺失值

数据集中缺失值常见处理方式

查看数据缺失情况,发现在Precipitation列中存在少量缺失

# data.info()data.isnull().sum(axis=0)

通过数据的偏度判断数据是否左偏或是右偏,从而判断缺失值应该用均值还是用中值填充

#数据偏度print('Precipitation列数据偏度:',data['Precipitation'].skew())#数据分布sns.distplot(data['Precipitation'].dropna())

结果如下:

由结果可知数据右偏,那我们在后续的填充缺失值时选用中值填充

data.fillna({'Precipitation':data['Precipitation'].median()},inplace=True)data.isnull().sum(axis=0)

结果如下:

2.3.2 异常值
异常值查看常见方式: 通过describe()查看数据信息通过3σ原则判断使用箱线图辅助相关异常检测算法

(1)通过describe()查看数据信息,虽然提供的信息全面,但异常值也不够直观,仅作为概览数据的简单方式

data.describe()

结果如下

(2)通过3σ原则判断,根据正态分布的特性,我们可以将3σ之外的数据视为异常值,此处以GDP这一列为例

sns.distplot(data["GDP"])print(data["GDP"].skew())

可见GDP此列存在严重的右偏情况,即存在很多极大的异常值

mean, std = data["GDP"].mean(), data["GDP"].std()lower, upper = mean - 3 * std, mean + 3 * stdprint("均值:", mean)print("标准差:", std)print("下限:", lower)print("上限:", upper)data["GDP"][(data["GDP"] < lower) | (data["GDP"] > upper)]

结果如下

(3)使用箱线图辅助,由箱线图也可得到如上的结论,存在许多极大的异常值

sns.boxplot(data=data["GDP"])

异常值处理常见方式: 删除异常值视为缺失值处理对数转换使用临界值填充使用分箱离散化处理

(1)对数转换

如果数据中存在较大的异常值,我们可以通过取对数来进行转化,这样可以得到一定程度的缓解

fig, ax = plt.subplots(1, 2)fig.set_size_inches(15, 5)sns.distplot(data["GDP"], ax=ax[0])sns.distplot(np.log(data["GDP"]), ax=ax[1])

如图,取对数后的分布图比原分布图显得更加正态

取对数的方式比较简单,不过也存在一些局限:

取对数只能针对正数操作,不过我们可以通过如下方式进行转换:

np.sign(X)*np.log(np.abs(X)+1)#通过sign取符号,通过+1来防止X出现0的情况

适用于右偏分布,不适用与左偏分布

(2)使用边界值替换

我们可以通过对异常值进行“截断”处理,即使用临界值替换异常值,例如,在3σ与箱线图中,就可以这样处理

(3)分箱离散化

有时候特征对目标存在一定影响,但是,这种影响可能未必是线性的增加,此时,我们可以通过分箱的方式,对特征进行离散化处理。

2.3.3 重复值
重复值查看

使用duplicate检查重复值,可配合keep参数进行调整

# 发现重复值。print(data.duplicated().sum())# 查看哪些记录出现了重复值。data[data.duplicated(keep=False)]

结果如下:

重复值处理

重复值对分析通常没有作用,直接删除即可

data.drop_duplicates(inplace=True)data.duplicated().sum()

3 数据分析

3.1 临海城市的空气质量是否优于内陆城市?

我们先来看一下临海与内陆城市的数量与AQI均值

display(data["Coastal"].value_counts())display(data.groupby("Coastal")["AQI"].mean())sns.barplot(x="Coastal", y="AQI", data=data)

进行方差齐性检验

from scipy import statscoastal = data[data["Coastal"] == "是"]["AQI"]inland = data[data["Coastal"] == "否"]["AQI"]# 进行方差齐性检验,为后续的两样本t检验服务stats.levene(coastal,inland)

得到的结果为:statistic=0.08825036641952543, pvalue=0.7666054880248168

进行两样本t检验。equal_var设置两个样本的方差是否是一致的,在levene中给出的P值可认为当前这两个样本的方差一致。

# 注意一点,两样本的方差相同与不相同,取得的结果是不同的。r = stats.ttest_ind(coastal,inland,equal_var=True)print(r)#这边给出的判断是双边检验,而我们要的结果是 临海是否小于内陆,是单边检验p = stats.t.sf(r.statistic,df=len(coastal)+len(inland)-2)#单边检验和双边检验的统计量statistic是一致的,df为自由度print(p)

得到的结果为:pvalue=0.9966622887294936

故有超过99%的概率认为临海城市的AQI低于内陆城市

3.2 空气质量主要受哪些因素影响?

通过热力图可看出各个特征之间的相关程度

plt.figure(figsize=(15, 10))ax = sns.heatmap(data.corr(), cmap=plt.cm.YlOrRd, annot=True, fmt=".2f",alpha=0.85)# 注意:Matplotlib 3.1.1版本的bug,heatmap的首行与末行会显示不全。# 可手动调整y轴的范围来进行修复。(老版本的Matplotlib不需要调整y轴范围。)# a, b = ax.get_ylim()# ax.set_ylim(a + 0.5, b - 0.5)

从结果可知,空气质量指数主要受降雨量(-0.40)与维度(0.55)影响

降雨量越多,AQI越低,空气质量越好维度越低,AQI越低,空气质量越好

3.3 关于空气质量的验证

有传言称全国所有城市的空气质量指数均值为71?

display(data["AQI"].mean())stats.ttest_1samp(data["AQI"], 71)

结果为:pvalue=0.07095431526986647

可以看到,P值大于0.05,我们无法拒绝原假设,因此选择接受

mean = data["AQI"].mean()std = data["AQI"].std()stats.t.interval(0.95,df=len(data)-1,loc=mean,scale=std/np.sqrt(len(data)))

通过计算可得,全国所有城市的平均空气质量指数,95%的可能在大致区间(70.63, 80.04)中

3.4 通过线性回归建模及优化,对空气质量进行预测

对于某城市,如果我们已知降雨量,温度,经纬度等指标,通过对已知的数据建模,应用于未知的数据,进而预测结果。

(1)数据转换

对于模型来说,内部进行的都是数学上的运算。在进行建模之前,我们需要先进行数据转换,将类别变量转换为离散变量。

(2)基础模型

from sklearn.linear_model import LinearRegressionfrom sklearn.model_selection import train_test_splitX = 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(lr.score(X_train, y_train))print(lr.score(X_test, y_test))

计算得到的训练集和测试集的R2R^2R2分别为:0.4538897765064036

0.40407705623832957

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)

3.4.1 特征选择

刚才,我们使用所有可能的原始数据作为特征,建立模型,然而,特征并非越多越好,有些特征可能对模型质量并没有什么改善,我们可以进行删除,同时,也能够提高模型训练速度。

(1)RFECV

特征选择的方式有很多,这里我们选择使用RFECV方法来实现特征选择,RFECV分成两个部分,分别是:

RFE(recursive feature elimination):递归特征消除,用来对特征进行重要性评级。CV(cross validation):交叉验证,在特征评级后,通过交叉验证,选择最佳数量的特征。

具体过程如下:

RFE阶段:

1.初始的特征集为所有可用的特征。

2.使用当前特征集进行建模,然后计算每个特征的重要性。

3.删除最不重要的一个(或多个)特征,更新特征集。

4.跳转到步骤2,直到完成所有特征的重要性评级。

CV阶段:

1.根据RFE阶段确定的特征重要性,依次选择不同数量的特征。

2.对选定的特征集进行交叉验证。

3.确定平均分最高的特征数量,完成特征选择。

from sklearn.feature_selection import RFECV# estimator: 要操作的模型# step: 每次删除的变量数# cv: 使用的交叉验证折数# n_jobs: 并发的数量# scoring: 评估的方式rfecv = RFECV(estimator=lr, step=1, cv=5, n_jobs=-1, scoring="r2")rfecv.fit(X_train,y_train)# 返回经过选择之后,剩余的特征数量print(rfecv.n_features_)# 返回经过特征选择后,使用缩减特征训练后的模型print(rfecv.estimator_)# 返回每个特征的等级,数值越小,特征越重要print(rfecv.ranking_)# 返回布尔数组,用来表示特征是否被选择print(rfecv.support_)# 返回对应数量特征时,模型交叉验证时的评分print(rfecv.grid_scores_)

通过结果可知,我们成功删除了两个特征,在特征选择过程中,使用交叉验证获得的R2R^2R2值变化如下:

plt.plot(range(1, len(rfecv.grid_scores_) + 1), rfecv.grid_scores_, marker="o")plt.xlabel("特征数量")plt.ylabel("交叉验证$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))

我们发现,经过特征选择后,消除了GDP与PopulationDensity两个特征,而使用剩余8个特征训练的模型,与之前未消除特征训练的模型,无论在训练集还是测试集的表现上,都几乎相同,这就证明,我们消除的这两个特征,确实对拟合目标没有什么帮助,可以去掉。

3.4.2 异常值处理

如果数据中存在异常值,很有可能会影响模型的效果,这里我们选择使用临界值替换异常值。

# Coastal是类别变量,映射为离散变量,不会有异常值for i in X.columns.drop("Coastal"):if pd.api.types.is_numeric_dtype(X_train[i]):quartile = np.quantile(X_train[i], [0.25, 0.75])IQR = quartile[1] - quartile[0]lower = quartile[0] - 1.5 * IQRupper = quartile[1] + 1.5 * IQRX_train[i][X_train[i] < lower] = lowerX_train[i][X_train[i] > upper] = upperX_test[i][X_test[i] < lower] = lowerX_test[i][X_test[i] > upper] = upperlr.fit(X_train,y_train)print(lr.score(X_train,y_train))print(lr.score(X_test,y_test))

去除异常值之后,我们使用新的训练集和测试集来评估模型的效果。

效果对比之前,有轻微改进,我们可以使用RFECV在去除异常值的数据上,再次尝试。

rfecv = RFECV(estimator=lr, step=1, cv=5, n_jobs=-1, scoring="r2")rfecv.fit(X_train, y_train)print(rfecv.n_features_)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$值")

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))

3.4.3 分箱离散化

注意:分箱后,我们不能将每个区间映射为离散数值,而是应当使用One-Hot编码

from sklearn.preprocessing import KBinsDiscretizer# KBinsDiscretizer K个分箱的离散器。用于将数值(通常是连续变量)变量进行区间离散化操作# n_bins:分箱(区间)的个数# encode:离散化编码方式。分为:onehot,onehot-dense与ordinal#onehot:使用独热编码,返回稀疏矩阵#onehot-dense:使用独热编码,返回稠密矩阵#ordinal:使用序数编码(0,1,2……)# strategy:分箱的方式。分为:uniform,quantile,kmeans#uniform:每个区间的长度范围大致相同#quantile:每个区间包含的元素个数大致相同#kmeans:使用一维kmeans方式进行分箱k = KBinsDiscretizer(n_bins=[4,5,15,6], encode="onehot-dense", strategy="uniform")# 定义离散化的特征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)lr.fit(X_train_dis,y_train)print(lr.score(X_train_dis,y_train))print(lr.score(X_test_dis,y_test))

由此得到的数据进行训练后的结果如下

可见,离散化操作后,模型效果有了进一步提升。

此外,在分箱离散化的过程中,选择特征数量不同以及分箱数量不同都会产生不同的结果,应该多多尝试不同的组合。

3.4.4 残差图分析

残差,就是模型预测值与真实值之间的差异,我们可以绘制残差图,来对回归模型进行评估。残差图的横坐标为预测值,纵坐标为残差值。

对于一个好的回归模型,误差应该随机分布的。因此,残差也应随机分布与中心线附近,如果我们从残差图中找出变化规律,这就意味着模型遗漏了某些能够影响残差的解释信息。

异方差性

是指残差具有明显的方差不一致性,这里我们异常值处理前后的两组数据,分别训练模型,然后观察残差的效果

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.valuesa.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值取对数的方式处理。

y_train_log = np.log(y_train)y_test_log = np.log(y_test)lr.fit(X_train, y_train_log)y_hat_train = lr.predict(X_train)residual = y_hat_train - y_train_log.valuesplt.xlabel("预测值")plt.ylabel(" 残差")plt.axhline(y=0, color="red")sns.scatterplot(x=y_hat_train, y=residual)

此时,异方差性得到解决,同时模型的效果也可以得到一定的提升。

离群点

如果是简单线性回归,我们可以通过绘制回归线轻松看出是否存在一些离群点。然而对多远线性回归,其回归线已经拓展成超平面,无法通过可视化来观察。

然而还是可以通过绘制残差图,通过预测值与实际值之间的关系,来检测离群点

model = LinearRegression()model.fit(X_train_dis, y_train)y_hat_train = model.predict(X_train_dis)residual = y_hat_train - y_train.valuesr = (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))

由此可见,模型效果又进一步提升了。

3.5 模型总结

本文线性回归模型的优化方向为:

特征选择 – 通过RFECV的方式进行特征筛选使用临界值处理异常值分箱离散化残差分析 – 离群点剔除

4 分析总结

1.临海城市的空气质量整体好于内陆城市。

2.是否临海,降雨量与纬度对空气质量指数的影响较大。

3.我国城市的平均空气质量指数有95%的可信度大致在(70.63,80.04)这个区间内。

4.通过已有数据,我们可以对未知地区的空气质量指数进行预测。

如果觉得《通过线性回归模型及优化实现AQI分析与预测》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。