背景:
空气质量指数是用来衡量空气清洁或者污染的程度,值越小,表示空气质量越好;近年来,空气质量越来越受到人们的关注。
任务描述:
一、描述性统计
二、推断统计
三、相关系数分析
四、区间估计
五、统计建模
现有数据:
相关库与数据的导入
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
import matplotlib as mpl
warnings.filterwarnings("ignore")
sns.set(style="darkgrid",font="SimHei",
rc={
"axes.unicode_minus":False})#风格,字体,以及正常显示负号
data = pd.read_csv("C://Users//ganyu//Desktop//data.csv")
data.isnull().sum(axis=0)
#data.info()
#print(data["Precipitation"].skew())#偏度
sns.distplot(data["Precipitation"].dropna())#要删除NA值才能做分布密度图
plt.title("分布密度图")
可以看出数据有点右偏
o 左偏分布(负偏态)中:mean(平均数)
对缺失值进行中位数填充
data.fillna({
"Precipitation":data["Precipitation"].median()},inplace=True)
检查异常值:
检查三倍标准差外的数据
mean,std = data["AQI"].mean(),data["AQI"].std()
print("mean:%d"%mean)
print("std:%d"%std)
"""对数据进行过滤 正负三倍标准差之外的数据 理论很少"""
data["AQI"][(data["AQI"] < mean - 3 * std) | (data["AQI"] > mean + 3 * std)]
"""绘制所有箱线图(建议分开作图,不同特征数值差异大,难看~~~)
客观存在的数据,异常值也是真实的数据,无需处理
"""
plt.figure(figsize=(16,7))
plt.xticks(rotation=45)
sns.boxplot(data=data)
for i in data:
#对数据集的每列循环
#循环 的 i 是列名称
if pd.api.types.is_numeric_dtype(data[i]):
#判断是否是数值型数据
descr = data[i].describe()#描述性统计。
IQR = descr.loc["75%"] - descr.loc["25%"]
upper = descr.loc["75%"] + 1.5 * IQR
lower = descr.loc["25%"] - 1.5 * IQR
#对异常值进行处理 。填充临界值
data[i][data1[i] < lower] = lower
data[i][data1[i] > upper] = upper
"""查看重复值的数据行数 """
print(data1.duplicated().sum())
#查看重复的记录
# data1[data1.duplicated(keep=False)]
#False指保留重复的值(保留那些值)
"""重复值的处理"""
data.drop_duplicates(inplace=True)
data.duplicated().sum()#去重后检查
最好的5个城市
dt = data
t = dt[["City","AQI"]].sort_values("AQI",ascending=True)#默认升序
#display(t.head(5))
#做柱状图
plt.xticks(rotation = 45)
sns.barplot(x="City",y="AQI",data=t.head(5))
"""最差的5个城市"""
#display(t.tail(5))
plt.xticks(rotation = 45)
sns.barplot(x="City",y="AQI",data=t.tail(5))
plt.title("空气质量最不好的5个城市",size=18)
plt.show()
分级如函数:
"""定义一个函数 将AQI转换成等级"""
def AQI_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(AQI_to_level)
display(level.value_counts())
sns.countplot(x=level,order=["一级","二级","三级","四级","五级"])
sns.scatterplot(x="Longitude",
y="Latitude",
hue="AQI",
palette=plt.cm.RdYlGn_r,
data=data)
plt.title("空气质量指数分布图",size=16)
plt.show()
结果:
可见低纬度城市的空气质量普遍比高纬度的城市空气质量好
分布散点图
#display(data["Coastal"].value_counts())
# sns.countplot(x="Coastal",data=data)
"""画散点分布图"""
#sns.stripplot(x="Coastal",y="AQI",data=data,jitter=True)#分布散点图
#sns.violinplot(x="Coastal",y="AQI",data=data)#小提琴图
sns.swarmplot(x="Coastal",y="AQI",data=data)#蜂群图效果更佳
"""统计空气质量均值"""
display(data.groupby("Coastal")["AQI"].mean())
#sns.barplot(x="Coastal",y="AQI",data=data)#只能看均值数据
就分布图可见沿海城市的AQI分布普遍低于100,均值为64,内陆的更多分布在120以下,均值在79。
假设检验: 检验内陆AQI和沿海城市AQI的均值是否相等(两样本t检验)
from scipy import stats
coastal = data[data["Coastal"] == 1]["AQI"]
inland = data[data["Coastal"] == 0]["AQI"]
stats.levene(coastal,inland)#方差齐性检验 P=0.76 很大可能两样本的方差是一致的
进行方差齐性检验,为后续两样本t检验服务
即检验两样本的方差是否一致 ,是否一致,检验的方法是不一样的
H0:方差相等(齐性)
H1:方差不等
结果解析 返回统计值(具有统计意义) 和p值 (接受原假设的概率)
"""t检验"""
"""t检验"""
stats.ttest_ind(inland,coastal,
equal_var=True)#equal_var=True表示两样本方差一致,False表示方差不一致。
结果:
Ttest_indResult(statistic=2.7303827520948905, pvalue=0.006675422541012958)
s 值 表示偏离了2.73倍标准差(负值表示 临海的比内陆的小),P值<0.05,所以拒绝原假设,接受备择假设,即二者均值不相等
"""绘制散点图矩阵"""
sns.pairplot(data[["AQI","PopulationDensity","GreenCoverageRate"]])
#参数 kind="reg"给散点图绘制一条回归线
相关系数:
皮尔逊相关系数
r(X,Y) = Cov(X,Y)/sqrt(Var(X)*Var(Y))
Cov(X,Y)为x与y的协方差
Var(X/Y)为X/Y的方差
Cov(X,Y) = E[( X-E(X) )(Y-E(Y)]
"""手工计算 ======= 计算AQI与降雨量的相关系数"""
x = data["AQI"]
y = data["Precipitation"]
#计算协方差
a = (x - x.mean()) * (y - y.mean())
cov = np.sum(a) / (len(a) - 1)
print("协方差 cov : " , cov)
#计算相关系数
corr = cov / np.sqrt(x.var() * y.var())
print("相关系数 :", corr)
结果:
协方差 cov : -10098.209013903044
相关系数 : -0.40184407003013883
"""内置方法计算"""
print("协方差 cov : " , x.cov(y))
print("相关系数 :", x.corr(y))
协方差 cov : -10098.20901390304
相关系数 : -0.4018440700301391
"""直接调用DataFrame中的方法计算相关系数"""
data.corr()
"""绘制相关系数矩阵"""
plt.figure(figsize=(15,12))
sns.heatmap(data.corr(),cmap=plt.cm.RdYlGn,annot=True,fmt=".2f")
"""
cmap :调色板
annot :是否显示数值
fmt :保留小数位
"""
结果:
小结论:
根据相关系数图可以看到,GDP与焚烧量成正相关 0.9,
维度与温度负相关 -0.81
以及其他相关系数较大的因素:如AQI与人口密度,还有经度相关系数较小,可以不考虑此特征
验证全部城市的AQI是否为传闻中的71左右
print("样本均值 :",data["AQI"].mean())
t = stats.ttest_1samp(data["AQI"],71)
print("s值:",t.statistic)
print("P值:",t.pvalue)
结果:
样本均值 : 75.3343653250774
s值: 1.8117630617496872
P值: 0.07095431526986647
我们可以看到偏离样本均值1.81倍,P值大于0.05,所以不能拒绝原假设,所以说所有城市的AQI均值在71左右这个说法是有一定依据的。
下面来计算一下置信区间:
"""0.95置信区间"""
mean = data["AQI"].mean()
std = data["AQI"].std()
print("置信区间为 :",mean - 1.96 * (std / np.sqrt(len(data))),mean + 1.96 * (std / np.sqrt(len(data))))
"""
内置方法获取置信区间
0.95置信度
df:自由度
loc:样本均值
scale : 样本均值服从的正态分布的标准差(标准误差)
"""
stats.t.interval(0.95,df=len(data) - 1,loc=mean,scale=std / np.sqrt(len(data)))
结果: 70.64536585461275 80.02336479554205
因此我们可以说全国城市的平均AQI值,有95%的可能在(70.63, 80.04)区间内
备注: ±1.96倍标准差是正态分布的95%置信度下的临界值,严格来说,对于t分布不是这样的,但是当样本容量较大的时候,t分布近似与正态分布。然而实际上我们可以通过内置方法获得更准确的置信区间,当样本容量较小的时候,t分布与正太分布差异较大
问题:已知某市部分的数据,如何预测其空气质量?
1.类别数据转换
"""数据转换"""
# data["Coastal"] = data["Coastal"].map({"是": 1,"否":0})
data["Coastal"].value_counts()
0 243
1 80
Name: Coastal, dtype: int64
备注:如有多个类别的时候,则用独热编码对数据进行编码
2.建立模型
模型的导入
#线性回归模型
from sklearn.linear_model import LinearRegression
#数据集的处理。训练集和测试集
from sklearn.model_selection import train_test_split
#构建X、y变量
X = data.drop(["City","AQI"],axis=1)#删除多余变量 ,Y变量不能在此
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)
#R方值 模型评价
print(lr.score(X_train,y_train))
print(lr.score(X_test,y_test))
结果:
0.4538897765064036
0.40407705623832957
直接调用的模型效果较差,训练集和测试集的得分都比较低
绘制预测结果图
#预测值
y_hat = lr.predict(X_test)
#设置画布
plt.figure(figsize=(15,9),dpi=180)
#绘图
plt.plot(y_test.values,"-r",label="真实值",marker="o")
plt.plot(y_hat,"-b",label="预测值",marker="D")
plt.legend(loc="upper left")
plt.title("线性回归预测结果与真实结果对比图")
plt.show()
3.特征选择
以上使用所有特征去训练模型,得出的结果反而不好,因此:
使用RFE(Recursive feature elimination 递归特征消除) 方法实现特征选择 过程如下:
1.初始的特征集为所有可用的特征集
2.使用当前的特征集进行建模 然后计算每个特征的重要性
3.删除不重要的一个或多个特征,然后更新特征集
4.回到2,直到找到最合适的特征为止
"""导入特征选择相关方法"""
from sklearn.feature_selection import RFECV
rfecv = RFECV(estimator=lr,step=1,cv=5,n_jobs=1,scoring="r2")
#estimator :要操作的模型
#step : 每次删除的变量数
#cv : 使用的交叉验证的折数 交叉验证 将数据切分成n分,n-1份数据训练模型,1份测试模型,重复此操作n次,n为较差验证的折数
#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.24573222 0.26368987 0.25744818 0.25389817]
绘图表示,在特征选择的过程中,使用交叉验证获取R平方的值
plt.plot(range(1,len(rfecv.grid_scores_) + 1),rfecv.grid_scores_,marker="o")
小结论:
由上面结果可知,我们成功删了两个特征(等级为2,3的),使用8个特征的时候模型评分最高。
下面改变交叉验证的折数 ,对特征机型选择
"""定义一个全0数组"""
num = np.zeros(shape=10,dtype=np.int32)
#从2折到10折进行交叉验证
for fold in range(2,11):
rfecv = RFECV(estimator=lr,step=1,cv=fold,n_jobs=-1,scoring="r2")
rfecv.fit(X_train,y_train)
num += rfecv.support_#返回的是一个数组,对应位置的特征是否被选择
print("对应位置的特征被选择的次数,一共9次验证:","\n",num)
print("对应位置的特征名称为:","\n",X_train.columns.values)
对应位置的特征被选择的次数,一共9次验证:
[4 1 9 8 9 5 0 9 5 5]
对应位置的特征名称为:
[‘Precipitation’ ‘GDP’ ‘Temperature’ ‘Longitude’ ‘Latitude’ ‘Altitude’
‘PopulationDensity’ ‘Coastal’ ‘GreenCoverageRate’
‘Incineration(10,000ton)’]
可以看出 温度 和维度,是否沿海城市 这三个特征在9次验证的时候都被留下来了。而人口密度在每次验证中都被剔除了,说明 温度、维度、沿海城市这三个特征非常重要,其次到经度这个特征,而人口密度这一特征则不重要,其他特征次次之。
更新训练集和测试集
#只留下三个最重要的特征
X_train_eli = X_train[["Temperature","Latitude","Coastal"]]
X_test_eli = X_test[["Temperature","Latitude","Coastal"]]
"""重新拟合模型 """
lr.fit(X_train_eli,y_train)
"""查看模型效果 R方"""
print("训练模型的分数:",lr.score(X_train_eli,y_train))
print("测试模型的分数:",lr.score(X_test_eli,y_test))
训练模型的分数: 0.3515764045851913
测试模型的分数: 0.5013595465844172
。。欠拟合了。。。。
重新处理数据:
1.取对数处理
如果数据存在较大的异常值的时候,可以通过取对数来进行转换,如Incineration(焚烧量)变量右偏,可以通过取对数进行转换
2.使用边界值替换
前面箱线图的上届与下届替换异常值
3.进行分箱离散化处理
即对连续形数据,划分成n段,每段为一个新类别(成了类别形变量),再用one-hot编码
"""密度分布图。明显右偏"""
sns.distplot(data["Incineration(10,000ton)"])
"""明显取对数后大致成正态分布"""
sns.distplot(np.log(data["Incineration(10,000ton)"]))
特别注意 :
取对数是消除极大值,当数据存在极小值的时候,则不能去取对数,因为取对数后,极小值仍在
取对数之后 ,仍可能存在以下离群值(极大值)
数据中存在负数的情况下 。需要通过转换才能对数转换 ;:np.sign(X)*np.log(np.abs(X)+1)
分箱处理数据:
k个分箱的离散器,用于将数值(通常是连续变量)变量进行区间离散化操作
from sklearn.preprocessing import KBinsDiscretizer
k = KBinsDiscretizer(n_bins=[9,9],encode="onehot-dense",strategy="uniform")#这里9个区间
"""
n_bins:分箱(区间)的个数
encode:离散化编码的形式 分为:onehot,onehot_dense,ordinal
onehot:独热编码,返回稀疏矩阵 有很多0
onehot-dense:独热编码, 返回稠密矩阵 1 和 0 都进行存储
ordinal:使用序数编码 (1,2,3,4.。。。。。)
strategy:分箱方式
uniform:每个区间的长度范围大致相同
quantile:每个区间的包含的元素的个数大致相同
kmeans:使用一维kmeans方式进行分箱
"""
"""定义离散化 特征 """
discretize = ["Temperature","Latitude"]
r = k.fit_transform(X_train_eli[discretize])#转换之后成了np.array
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)#重新组合成离散化后的特征
"""查看离散化后的数据 """
X_train_dis.head()#特征选择后就剩三个特征,,后然后对两个变量进行分箱
"""重新训练模型"""
lr.fit(X_train_dis,y_train)
"""查看模型效果 R方"""
print("训练模型的分数:",lr.score(X_train_dis,y_train))
print("测试模型的分数:",lr.score(X_test_dis,y_test))
训练模型的分数: 0.5163452252653639
测试模型的分数: 0.5128234785039706
可见模型评分明显得到了提升。
4.残差图分析
残差就是模型的预测值与真实值之间的差异,可以绘制残差图对模型进行评估,横坐标为预测值,纵坐标为真实值
1.异方差性
对于一个模型,误差应该的随机性的,而不是有规律的。残差也随机分布于中心线附近,如果残差图中残差是有规律的,则说明模型遗漏了某些能够影响残差的解释信息
绘制残差图:
"""绘制残差图"""
#训练集的预测值
y_hat_train = lr.predict(X_train_dis)
residual = y_hat_train-y_train.values
plt.xlabel("预测值")
plt.ylabel("残差")
plt.axhline(y=0,color="r")
sns.scatterplot(x=y_hat_train,y=residual)
可以看出随着预测值的增大,残差也有增大的趋势。(称为异方差性)此时我们可以对y值进行取对数进行解决
"""对y进行取对数 """
y_train_log = np.log(y_train)
y_test_log = np.log(y_test)
"""再次训练模型 """
lr.fit(X_train_dis,y_train_log)
"""查看模型效果 R方"""
print("训练模型的分数:",lr.score(X_train_dis,y_train_log))
print("测试模型的分数:",lr.score(X_test_dis,y_test_log))
训练模型的分数: 0.5927243794948202
测试模型的分数: 0.5739854718468524
模型的效果明显也得到了提升
重新绘制残差图:
"""绘制残差图"""
#训练集的预测值
y_hat_train = lr.predict(X_train_dis)
residual = y_hat_train-y_train_log.values
plt.xlabel("预测值")
plt.ylabel("残差")
plt.axhline(y=0,color="r")
sns.scatterplot(x=y_hat_train,y=residual)
2.离群点:偏离2倍标准差的点
"""检测离群点"""
r = (residual - residual.mean()) / residual.std()
#偏离均值的 标准差的倍数 即偏离均值了r倍标准的
plt.xlabel("预测值")
plt.ylabel("残差")
plt.axhline(y=0,color="r")
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="r",label="异常值")
"""剔除异常值"""
X_train_dis_filter = X_train_dis[np.abs(r) <= 2]
y_train_log_filter = y_train_log[np.abs(r) <= 2]
"""再再再次拟合模型 """
lr.fit(X_train_dis_filter,y_train_log_filter)
"""查看模型效果 R方"""
print("训练模型的分数:",lr.score(X_train_dis_filter,y_train_log_filter))
print("测试模型的分数:",lr.score(X_test_dis,y_test_log))
训练模型的分数: 0.6819117447429536
测试模型的分数: 0.5603806404980185
1.从空气质量总体分布上来说,南方城市优于北方城市,西部城市优于东部城市。
2.临海城市空气质量整体好于内陆城市
3.城市是否临海,降雨量,以及维度对空气质量指数影响最大
4.有95%的把握可以说城市平均空气质量指数在区间(70.63, 80.04)之间
5.通过历史数据,我们可以对空气质量指数进行预测(对数据进行相应的处理后再预测,结果为AQI的对数)