本博客主要来自作者对开课吧老师梁勇老师的学习笔记,想深入学习的同学就去开课吧报名吧。
1. 研究背景
AQI(空气质量指数),用来衡量空气清洁或者污染的程度。值越小,表示空气质量越好。近年来,因为环境问题,空气质量也越来越受到人们的重视。
2. 提出问题
哪些城市的空气质量较好?
空气质量在地理位置上是否具有一定的规律性?
临海城市的空气质量是否有别于内陆城市?
空气质量主要受哪些因素影响?
全国城市空气质量普遍处于何种水平?
怎样预测一个城市的空气质量?
3. 数据集描述
此数据集有12个变量,326条数据。
City | 城市名 |
AQI | 空气质量指数 |
Precipitation | 降雨量 |
GDP | 城市生产总值 |
Temperature | 温度 |
Longitude | 经度 |
Latitude | 纬度 |
Altitude | 海拔高度 |
PopulationDensity | 人口密度 |
Coastal | 是否沿海 |
GreenCoverageRate | 绿化覆盖率 |
Incineration(10,000ton) | 焚烧量(10000吨) |
4. 分析流程
难点理解
特征工程:最大限度地从原始数据中提取特征以供算法和模型使用。通俗的说就是数据预处理的方式,从源数据当中提取相关数据可以放到模型当中
超参数调整:模型参数通常是有数据来驱动调整。超参数不需要数据来驱动,而是在训练前或者训练中人为的进行调整的参数
5.数据预处理
5.1 导入相关库
数据处理
import numpy as np import pandas as pd
可视化
import matplotlib.pyplot as plt import seaborn as sns # 设置风格 --网格颜色风格 sns.set(style="darkgrid") # windows 设置字体为SimHei显示中文 # plt.rcParams["font.family"]="SimHei" # mac plt.rcParams['font.family'] = ['Arial Unicode MS'] # 设置正常显示字符 plt.rcParams['axes.unicode_minus'] = False
特征工程
from sklearn.model_selection import train_test_split #特征切分 切分训练集与测试集 from sklearn.feature_selection import RFECV #RFECV方法 from sklearn.preprocessing import KBinsDiscretizer #分箱操作
算法
from sklearn.linear_model import LinearRegression #线性回归算法
警告忽略
import warnings warnings.filterwarnings('ignore')
5.2 加载数据集
data = pd.read_csv("data.csv") data.shape
查看数据大致情况
# data.head() # data.tail() data.sample(10)
5.3 数据探索(EDA)
5.3.1缺失值探索
# 查看缺失值 # data.info() # 默认为0轴 data.isnull().sum(axis=0)
缺失值处理方案
# 查看数据分布 偏度 查看左偏还是右偏查看左偏还是右偏 print(data["Precipitation"].skew()) # 查看删除缺失值之后的图形 右偏 seaborn不支持空值绘制图形,所以需要使用dropna()将空值剔除掉 sns.distplot(data["Precipitation"].dropna())
data = data.fillna({"Precipitation" : data["Precipitation"].median()}) data.isnull().sum(axis=0)
5.3.2 异常值处理
查看数据信息
# describe查看数据信息 data.describe()
# 作图 sns.distplot(data["GDP"]) # 偏度 print(data["GDP"].skew())
mean,std = data["GDP"].mean(),data["GDP"].std() lower,upper = mean-3*std,mean+3*std print("均值:",mean) print("标准差:",std) print("下限:",lower) print("上限:",upper) data["GDP"][(data["GDP"]<lower) | (data["GDP"]>upper)]
sns.boxplot(data=data["GDP"])
异常值处理
# 如果存在较大异常值 这样可以得到一定缓解 例如:GDP 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])
5.3.3 重复值处理
发现重复值
# 重复值处理 # 发现重复值 print(data.duplicated().sum()) # 查看哪些记录出现了重复值 # keep='first'/'last'/False 只显示第一次出现、最后一次出现,全部显示 data[data.duplicated(keep=False)]
data.drop_duplicates(inplace=True)
data.duplicated().sum()
结果为:0
6. 数据分析
6.1 空气质量最好/最差的5个城市
t = data[["City","AQI"]].sort_values("AQI") tt = t.iloc[:5] display(tt) # 条形图:sns.barplot() plt.xticks(rotation=45) sns.barplot(x="City",y="AQI",data=tt) # sns.barplot(x=None, y=None, hue=None, data=None, order=None, hue_order=None, estimator=np.mean, ci=95, n_boot=1000, units=None, orient=None, color=None, palette=None, saturation=.75, errcolor=".26", errwidth=None, capsize=None, dodge=True, ax=None, **kwargs) # matplotlib画图 条形图 # plt.bar(x="City",height= "AQI",data=tt,color=['r','g','b','r','g','b'])
tt = t.iloc[-5:] display(tt) plt.xticks(rotation=45) sns.barplot(x="City",y="AQI",data=tt)
6.2 全国空气的空气质量
# 统计每个等级的数量 def value_to_level(AQI): if AQI >= 0 and AQI <= 50: return "一级" elif AQI >= 51 and AQI <= 100: return "二级" elif AQI >= 101 and AQI <= 150: return "三级" elif AQI >= 151 and AQI <= 200: return "四级" elif AQI >= 201 and AQI <= 300: return "五级" else: return "六级" level = data["AQI"].apply(value_to_level) display(level.value_counts()) # sns.countplot(x=None, y=None, hue=None, data=None, order=None, hue_order=None, orient=None, color=None, palette=None, saturation=.75, dodge=True, ax=None, **kwargs) sns.countplot(x=level,order=["一级","二级","三级","四级","五级","六级"])
# 空气质量指数分布图 # x,y经纬度 # hue="AQI":根据AQI来划分颜色等级 # palette=plt.cm.RdYlGn_r:选的调色板 sns.scatterplot(x="Longitude",y="Latitude",hue="AQI",palette=plt.cm.RdYlGn_r,data=data) # sns.scatterplot(x=None, y=None, hue=None, style=None, size=None, data=None, palette=None, hue_order=None, hue_norm=None, sizes=None, size_order=None, size_norm=None, markers=True, style_order=None, x_bins=None, y_bins=None, units=None, estimator=None, ci=95, n_boot=1000, alpha="auto", x_jitter=None, y_jitter=None, legend="brief", ax=None, **kwargs)
6.3 临海城市的空气质量是否优于内陆
display(data["Coastal"].value_counts()) sns.countplot(x="Coastal",data=data)
sns.stripplot(x="Coastal",y="AQI",data=data) # sns.stripplot(x=None, y=None, hue=None, data=None, order=None, hue_order=None, jitter=True, dodge=False, orient=None, color=None, palette=None, size=5, edgecolor="gray", linewidth=0, ax=None, **kwargs)
sns.swarmplot(x="Coastal",y="AQI",data=data) # sns.swarmplot(x=None, y=None, hue=None, data=None, order=None, hue_order=None, dodge=False, orient=None, color=None, palette=None, size=5, edgecolor="gray", linewidth=0, ax=None, **kwargs)
display(data.groupby("Coastal")["AQI"].mean()) # barplot内部会自己求均值 sns.barplot(x="Coastal",y="AQI",data=data) # 图中那条线代表总体均值所在的置信区间 默认为95%的置信度
# 可以查看更多信息 四分位 最大值最小值 异常值 sns.boxplot(x="Coastal",y="AQI",data=data) # sns.barplot(x=None, y=None, hue=None, data=None, order=None, hue_order=None, estimator=np.mean, ci=95, n_boot=1000, units=None, orient=None, color=None, palette=None, saturation=.75, errcolor=".26", errwidth=None, capsize=None, dodge=True, ax=None, **kwargs)
# 还能展现分布的密度 # 里面的是微型版的箱线图,外边是核密度图kde,小提琴图向右旋转90度,就可以看出这是个右偏的 sns.violinplot(x="Coastal",y="AQI",data=data) # sns.violinplot(x=None, y=None, hue=None, data=None, order=None, hue_order=None, bw="scott", cut=2, scale="area", scale_hue=True, gridsize=100, width=.8, inner="box", split=False, dodge=True, orient=None, linewidth=None, color=None, palette=None, saturation=.75, ax=None, **kwargs)
混合图形
# 散点图(这里用的蜂群图)与箱线图或小提琴图结合在一起进行绘制 一般不作为分析的主要手段 # inner=None:不展示里面的琴弦 sns.violinplot(x="Coastal",y="AQI",data=data,inner=None) sns.swarmplot(x="Coastal",y="AQI",color="g",data=data) # sns.boxplot(x="Coastal",y="AQI",color="g",data=data)
收集到的样本不能说明总体上的差异性,因为抽样可能会带来一定的误差
6.4 空气质量主要受哪些因素影响
假设检验
-差异检验
# 差异检验 # 两样本t检验 查看临海城市与内陆城市的均值差异是否显著 # 原假设:均值一致 # 备择假设:均值不一致 from scipy import stats coastal = data[data["Coastal"] == "是"]["AQI"] inland = data[data["Coastal"] == "否"]["AQI"] # 进行方差齐性检验-levene检验。 为后续的两样本t检验服务。 方差一致就叫齐性
# levene检验比较稳定
stats.levene(coastal,inland) # 如果P值大于0.05,那么我们认为两总体具有方差齐性 # p值为76%,有76%支持原假设 方差是齐性的,所以下面的equal_var=True。
引出如下:方差一致计算方法
# 进行两样本t检验。方法用的是stats.ttest_ind,注意:两样本的方差相同与不相同,取得的结果是不同的。
# 如果两总体不具有方差齐性,参数修改:equal_var=False
r = stats.ttest_ind(coastal,inland,equal_var=True) print(r) # 双边检验结果中p值非常小 说明不支持原假设两样本不一致, statistic<0,说明coastal<inland,
# 所以下面用右边假设检验 即沿海城市的AQI小于内陆
p = stats.t.sf(r.statistic,df=len(coastal)+len(inland)-2) print(p) # p值非常大,支持原假设 沿海小于内陆
空气质量主要受哪些影响?
散点图矩阵:sns.pairplot()
作用:可以大致查看两两变量之间的关系 这里并不是很明显 画图是一种辅助 有主观色彩 不能当做一种标准
代码及示意图如下:
# 散点图矩阵 :sns.pairplot() # --可以大致查看两两变量之间的关系 这里并不是很明显 画图是一种辅助 有主观色彩 不能当做一种标准 # 对角线是直方图绘制:同一个变量展现的是在一个小区间内有多少个值 sns.pairplot(data[["AQI", "PopulationDensity", "GreenCoverageRate"]]) # sns.pairplot(data[["AQI", "PopulationDensity", "GreenCoverageRate"]], kind="reg") # kind默认情况下是scatter,reg是画一条回归线
如何量化:采用相关系数
先看下协方差:
协方差:协方差体现的是两个变量之间的分散性以及两个变量变化步调是否一致。容易得知,当协方差的两个变量相同时,协方差就是方差。
相关系数公式:
公式含义:就是用X、Y的协方差除以X的标准差和Y的标准差。
统计学中常用相关系数 r 来表示两变量之间的相关关系。
相关系数 r 的取值范围为[-1,1],我们可以根据相关系数的取值来衡量两个变量的相关性
绝对值: 0.8~1.0 极强相关
0.6~0.8 强相关
0.4~0.6 中等程度相关
0.2~0.4 弱相关
0.0~0.2 极弱相关或无相关
正数:正相关性
负数:负相关性
相关系数优点:协方差可大可小不好衡量,所以用相关系数取值稳定。
# 计算协方差和相关系数 不推荐 x = data["AQI"] y = data["Precipitation"] # 计算AQI与Precipitation的协方差。 a = (x - x.mean()) * (y - y.mean()) cov = np.sum(a) / (len(a) - 1) print("协方差:", cov) # 计算AQI与Precipitation的相关系数。 corr = cov / np.sqrt(x.var() * y.var()) print("相关系数:", corr)
# 协方差:协方差体现的是两个变量之间的分散性以及两个变量变化步调是否一致。容易得知,当协方差的两个变量相同时,协方差就是方差。 # 相关系数,可以用来体现两个连续变量之间的相关性,最为常用的为皮尔逊相关系数 # 计算协方差和相关系数 推荐 # Series中已经提供好了计算协方差和相关系数的方法cov(),corr() x = data["AQI"] y = data["Precipitation"] print("协方差:", x.cov(y)) print("相关系数:", x.corr(y))
DataFrame中提供了计算相关系数的方法
data.corr()
用热力图来更清晰的呈现相关系数值
# 用热力图来更清晰的呈现相关系数值 plt.figure(figsize=(15, 10)) # annot=True:显示数值 False:不显示数值 # fmt=".2f":保留两位小数 ax = sns.heatmap(data.corr(), cmap=plt.cm.RdYlGn, annot=True, fmt=".2f") # 注意:Matplotlib 3.1.1版本的bug,heatmap的首行与末行会显示不全。 # 可手动调整y轴的范围来进行修复。(老版本的Matplotlib不需要调整y轴范围。) # a, b = ax.get_ylim() # ax.set_ylim(a + 0.5, b - 0.5) # “经度”【longitude】、“纬度”【 Latitude】
从结果可知,空气质量指数主要受降雨量(-0.40)与维度(0.55)影响。
- 降雨量越多,空气质量越好。
- 维度越低,空气质量越好
当然还可以发现其他结论:GDP与Incineration(10,000ton)正相关(0.90)
temputer与Precipitation正相关(0.69)
temputer与Latitude负相关(-0.81)等等。
6.5 全国空气质量的验证
# 我们使用单样本t检验 r = stats.ttest_1samp(data["AQI"], 71) print("t值:", r.statistic) print("p值:", r.pvalue) # P值大于0.05,所有无法拒绝原假设,故接受原假设
所以我们维持原假设,但不一定代表原假设就一定正确,只不过我们没有充足的理由去推翻它。
tf分布计算置信区间的正确方法:
(不能用均值加减1.96倍方差)
# t分布的置信区间 # 严格意义上来说不能用均值加减1.96倍标准差,因为当样本容量较大时,t分布近似正态分布,但当样本容量较小时,二者有较大差异 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))) # 71在这个区间,所以维持原假设
7. 对空气质量进行预测
机器学习心理路程 --通用的思路 但不是绝对的 不能学太死板
数据转换
# Series中的map函数 可以实现数据的替换 data["Coastal"] = data["Coastal"].map({"是": 1, "否": 0}) data["Coastal"].value_counts()
此时1,0没有大小区别 因为还可以使用其他值 都可以拟合出w
二分类变量:可以转换为任意两个离散变量
多分类变量(>=3个):不能将n个类别映射为0,1,2,...n-1 ,会增加真实的差异性
类别变量:用独热编码(one-hot)
x1 | x2 | x3 | |
w1 |
1 | 0 | 0 |
w2 | 0 | 1 | 0 |
w3 | 0 | 0 | 1 |
基模型
# 首先建立一个基模型 后续操作在此模型上进行操作 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("训练集:",lr.score(X_train, y_train)) print("测试集:",lr.score(X_test, y_test))
线性回归预测
# 线性回归预测 () 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)
特征选择
--去除没有用处的特征
from sklearn.feature_selection import RFECV # estimator: 要操作的模型。 # step: 每次删除的变量数。 # cv: 使用的交叉验证折数。(把数据分成几份) # n_jobs: 并发的数量。-1表示把所有的CPU来进行并发 # scoring: 评估的方式。这里采用的是R方 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("特征:",X.columns) print("返回布尔数组,用来表示特征是否被选择",rfecv.support_) # 返回对应数量特征时,模型交叉验证的评分。 print("返回对应数量特征时,模型交叉验证的评分",rfecv.grid_scores_)
绘制 特征数量与交叉验证$R^2$值的关系
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_]) print("保留的变量:", X_train.columns[rfecv.support_]) # 只保留剩下的那8个特征 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))
异常值处理
--临界值替换
使用箱线图判断离群点,然后使用临界值替换异常值
我们应该使用训练集的数据去计算临界值
--除了类别变量的列外,其他所有的列都需要异常值处理
# 使用箱线图判断离群点,然后使用临界值替换异常值 # 我们应该使用训练集的数据去计算临界值 # Coastal是类别变量,映射为离散变量,不会有异常值。查看除了Coastal之外有没有异常值 for col in X.columns.drop("Coastal"): # is_numeric_dtype:查看类别是否是数值类型的,是的话就继续往下 if pd.api.types.is_numeric_dtype(X_train[col]): # 计算1/4分位和3/4分位 IQR 下限和上限 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))
消除异常值之后再使用RFECV方法
# 消除异常值之后再使用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$值")
我们发现 9个特征的时候交叉验证得到的效果最好
print("剔除的变量:", X_train.columns[~rfecv.support_]) # 得到ndarry # X_train_eli = rfecv.transform(X_train) # X_test_eli = rfecv.transform(X_test) # 为了方便后面列的筛选操作,这里我们换一种方式转换。 # 得到DataFrame类型,可保留列名 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))
分箱操作 --原则:x对y是阶梯式的影响,而不是线性的
分箱后,我们不能将每个区间映射为离散数值,而是应当使用One-Hot编码
难点:哪些特征需要分箱操作需要去试或者查询!
分箱的个数需要去尝试,区间跨度比较大可以设置大点的值,还有如果值的跨度很大才有影响可以把个数设置少一点。
from sklearn.preprocessing import KBinsDiscretizer # KBinsDiscretizer K个分箱的离散器。用于将数值(通常是连续变量)变量进行区间离散化操作。 # n_bins:分箱(区间)的个数。--是尝试之后设置的可以调整。--对应的是discretize中传过来的 # encode:离散化编码方式。分为:onehot,onehot-dense与ordinal。 # onehot:使用独热编码,返回稀疏矩阵。只存1 不存0 # onehot-dense:使用独热编码,返回稠密矩阵。1和0都存 # ordinal:使用序数编码(0,1,2……)。 # strategy:分箱的方式。分为:uniform,quantile,kmeans。 # uniform:每个区间的长度范围大致相同。 # quantile:每个区间包含的元素个数大致相同。 # kmeans:使用一维kmeans方式进行分箱。 k = KBinsDiscretizer(n_bins=[4, 5, 14, 6], encode="onehot-dense", strategy="uniform") # 定义离散化的特征。 # 阶梯式的改变才采用分箱离散化,n_bins=[4, 5, 14, 6]这四个是老师试出来的,我们也可以试试其他的
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)) # 离散后 模型效果有了提升
残差分析
异方差性
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值取对数的方式处理
# 出现了异方差性 措施:对目标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)) # 训练集得到了提升
可以看出训练集得到了很好的提升,但是测试集还是相差无几 ,但这不失为一种考虑方案。
最后得出结论:
哪些城市的空气质量较好:Shaoguan,Nanping,Meizhou,Keelung,Sanming。
空气质量在地理位置上是否具有一定的规律性:空气质量总体分布上来说,南方城市优于北方城市,西部城市优于东部城市。
临海城市的空气质量是否有别于内陆城市:临海城市的空气质量优于内陆城市。
空气质量主要受哪些因素影响:是否临海,降雨量,维度对空气质量影响较大。
全国城市空气质量普遍处于何种水平:我国空气质量95%的概率在(70.63, 80.04)之间。
怎样预测一个城市的空气质量:通过历史数据,我们可以对空气质量进行预测。