环境数据分析课程的大作业,主要目的应用一下课上学到的一些数据分析方法。
通过爬虫获取我国沿海海水水质的监测数据;
以散点图直观反映水质类别的分布和随时间变化情况;
将化学需氧量数据转换为正态分布,以海区/省份为分类变量进行单因素方差分析;
将无机氮数据转换为正态分布,用Pearson分析和线性回归分析考察化学需氧量和无机氮数据的相关性;
利用机器学习,从多个污染指标数据预测海区分类。
数据获取
爬虫
来源:生态环境部的海水水质监测信息公开系统。网址:http://ep.nmemc.org.cn:8888/Water/
image-20220424020835110
1 2 3 4 5 from selenium import webdriverimport pandas as pdwd = webdriver.Chrome() wd.get('http://ep.nmemc.org.cn:8888/Water/' )
对动态网页的表格设置过滤属性。这里先筛选获取2020年的数据。
1 2 3 4 5 6 7 8 9 10 11 12 year = wd.find_element_by_id('ddl_year' ) sea = wd.find_element_by_id('ddl_sea' ) province = wd.find_element_by_id('ddl_province' ) city = wd.find_element_by_id('ddl_city' ) year.send_keys('2020' ) sea.send_keys('全部' ) province.send_keys('全部' ) city.send_keys('全部' ) botton = wd.find_element_by_id('btn' ) botton.click()
获取表头和数据,并整理成二维数组的类型。
1 2 3 4 5 6 7 8 9 10 11 header = wd.find_element_by_id('gridHd' ) heads = header.text.replace('\n' ,'' ).split(' ' ) body = wd.find_element_by_id('gridDatas' ) predatas = body.text.split('\n' ) total = int (len (predatas)/2 ) data = [] for i in range (total): row = predatas[2 *i].split(' ' ) row.append(predatas[2 *i+1 ]) data.append(row)
把2021年的数据也添加到数组里,转换成data frame。
1 2 3 4 5 6 7 8 9 10 11 12 year.send_keys('2021' ) botton.click() body = wd.find_element_by_id('gridDatas' ) predatas = body.text.split('\n' ) total = int (len (predatas)/2 ) for i in range (total): row = predatas[2 *i].split(' ' ) row.append(predatas[2 *i+1 ]) data.append(row) df = pd.DataFrame(data, columns=heads)
数据整理和保存
发现部分监测点数据位移,原因是部分监测点的溶解氧数据缺失,还有一些数据点的溶解氧和石油类数据都缺失。对这些监测点的信息进行修正,缺失的地方用None
标记。
1 2 3 4 5 6 7 8 9 for i in range (len (df)): if df['水质类别' ][i]==None : for j in range (13 ,8 ,-1 ): df.iloc[i,j] = df.iloc[i,j-1 ] df.iloc[i,8 ] = None for i in range (len (df)): if df['水质类别' ][i]==None : df.iloc[i,13 ] = df.iloc[i,12 ] df.iloc[i,12 ] = None
保存数据到本地。此时数据全都以字符串保存。缺失值标记为“None”,低于检出限的数据标记为“未检出”,或根据检出限的值标记为“<0.01”或“<0.1”。
1 2 wd.quit() df.to_pickle('./Data.pkl' )
image-20220424020754676
水质类别动态变化
根据每个月水质监测数据的经纬度信息和水质类别作图。
1 2 3 4 5 import pandas as pdimport matplotlib.pyplot as pltfrom matplotlib.pyplot import MultipleLocatordata = pd.read_pickle('./Data.pkl' )
查看一下水质类别有哪些分类,执行data['水质类别'].value_counts()
,输出:
1 2 3 4 5 6 一类 4082 二类 1343 劣四类 1154 三类 514 四类 476 Name: 水质类别, dtype: int64
将经纬度数据转换成浮点型。
1 2 data['实测经度' ] = pd.to_numeric(data['实测经度' ]) data['实测纬度' ] = pd.to_numeric(data['实测纬度' ])
定义返回某年某月所有监测点坐标的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 def getlongitude (dataframe,year,month ): longitude_by_level = [] a = dataframe[(dataframe['监测时间' ]==year+'-' +month)&(dataframe['水质类别' ]=='一类' )]['实测经度' ] b = dataframe[(dataframe['监测时间' ]==year+'-' +month)&(dataframe['水质类别' ]=='二类' )]['实测经度' ] c = dataframe[(dataframe['监测时间' ]==year+'-' +month)&(dataframe['水质类别' ]=='三类' )]['实测经度' ] d = dataframe[(dataframe['监测时间' ]==year+'-' +month)&(dataframe['水质类别' ]=='四类' )]['实测经度' ] e = dataframe[(dataframe['监测时间' ]==year+'-' +month)&(dataframe['水质类别' ]=='劣四类' )]['实测经度' ] longitude_by_level.append(a) longitude_by_level.append(b) longitude_by_level.append(c) longitude_by_level.append(d) longitude_by_level.append(e) return longitude_by_level def getlatitude (dataframe,year,month ): latitude_by_level = [] a = dataframe[(dataframe['监测时间' ]==year+'-' +month)&(dataframe['水质类别' ]=='一类' )]['实测纬度' ] b = dataframe[(dataframe['监测时间' ]==year+'-' +month)&(dataframe['水质类别' ]=='二类' )]['实测纬度' ] c = dataframe[(dataframe['监测时间' ]==year+'-' +month)&(dataframe['水质类别' ]=='三类' )]['实测纬度' ] d = dataframe[(dataframe['监测时间' ]==year+'-' +month)&(dataframe['水质类别' ]=='四类' )]['实测纬度' ] e = dataframe[(dataframe['监测时间' ]==year+'-' +month)&(dataframe['水质类别' ]=='劣四类' )]['实测纬度' ] latitude_by_level.append(a) latitude_by_level.append(b) latitude_by_level.append(c) latitude_by_level.append(d) latitude_by_level.append(e) return latitude_by_level
定义画出某年某月水质类别分布图的函数。将网上找的我国海岸线轮廓作为底图(网上下载下来是shp格式的,但是找到了一个很好的网站,可以将shp图层文件转换成位图 https://mapshaper.org/)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 def draw_plot (year,month,longitude_by_level,latitude_by_level ): fig, ax = plt.subplots(figsize=((128 -106 ),(42 -14 ))) ax.scatter(longitude_by_level[0 ], latitude_by_level[0 ], c='paleturquoise' , s=100 , EdgeColor='k' ) ax.scatter(longitude_by_level[1 ], latitude_by_level[1 ], c='c' , s=100 , EdgeColor='k' ) ax.scatter(longitude_by_level[2 ], latitude_by_level[2 ], c='cadetblue' , s=100 , EdgeColor='k' ) ax.scatter(longitude_by_level[3 ], latitude_by_level[3 ], c='teal' , s=100 , EdgeColor='k' ) ax.scatter(longitude_by_level[4 ], latitude_by_level[4 ], c='darkslategrey' , s=100 , EdgeColor='k' ) plt.rcParams['font.sans-serif' ]=['SimHei' ] plt.rcParams['axes.unicode_minus' ]=False ax.legend(['一类' ,'二类' ,'三类' ,'四类' ,'劣四类' ],fontsize=30 ,loc='upper left' ) ax.axis([106 ,128 ,14 ,42 ]) major_locator=MultipleLocator(5 ) ax.xaxis.set_major_locator(major_locator) ax.grid() plt.xticks(fontsize=30 ) plt.yticks(fontsize=30 ) plt.xlabel('经度(E°)' ,fontsize=30 ) plt.ylabel('纬度(N°)' ,fontsize=30 ) plt.title(str (year)+'年' +str (month)+'月我国沿海监测点水质类别情况' ,fontsize=40 ) img = plt.imread("land-sea.jpg" ) zoom = 0.0221 height,width = len (img)*zoom,len (img[0 ])*zoom x,y = 121.0 ,22.1 ax.imshow(img,extent=[x-width/2 ,x+width/2 ,y-height/2 ,y+height/2 ]) plt.savefig('../沿海检测点水质类别变化/' +year+'-' +month+'.jpg' , dpi=200 )
生成2020-2021年每月的水质分布图。
1 2 3 4 5 6 for year in ['2020' ,'2021' ]: for month in ['01' ,'02' ,'03' ,'04' ,'05' ,'06' ,'07' ,'08' ,'09' ,'10' ,'11' ,'12' ]: print ('Drawing ' +year+'-' +month) longitude_by_level = getlongitude(data,year,month) latitude_by_level = getlatitude(data,year,month) draw_plot(year,month,longitude_by_level,latitude_by_level)
将图片拼接成GIF动图。
1 2 3 4 5 6 import imageio, os, sysjpg_lst = os.listdir('../沿海检测点水质类别变化/' ) frames = [] for img in jpg_lst: frames.append(imageio.imread('../沿海检测点水质类别变化/' + img)) imageio.mimsave("../沿海检测点水质类别变化/result.gif" , frames, 'GIF' , duration=0.5 )
result
简单数据分析:正态性检验、方差分析、Pearson分析和线性回归
以“化学需氧量(mg/L)”为研究数据,考察不同海区/省份之间的海水COD差异性。首先将数据转换为数值类型,标记为“未检出”的数据记为0。由于方差分析适用于正态分布的样本,因此先对数据进行分布检验,变换为接近正态分布的形式后再进行方差分析。
1 2 3 4 5 6 7 8 9 10 11 import pandas as pdimport matplotlib.pyplot as pltfrom scipy import statsimport numpy as npimport randomimport seaborn as snsfrom statsmodels.formula.api import olsfrom statsmodels.stats.anova import anova_lmfrom sklearn.linear_model import LinearRegressionplt.rcParams['font.sans-serif' ]=['SimHei' ] plt.rcParams['axes.unicode_minus' ]=False
数据清理和转换
首先读取数据,转换成浮点型。
1 2 3 4 5 data = pd.read_pickle('./Data.pkl' ) for i in cod.index: if cod[i] == '未检出' : cod[i] = '0' cod = pd.to_numeric(cod)
从原始数据的直方图大致看一下分布情况。
1 2 3 4 5 fig, ax = plt.subplots(figsize=(15 ,5 )) sns.histplot(cod, bins=150 ) plt.xlim(min (cod), max (cod)) plt.xlabel('COD(mg/L)' ) plt.ylabel('Frequency' )
image-20220424021524945
可以看出样本是明显有偏的,计算样本的偏度(Skewness)和峰度(Kurtosis)。
1 2 3 s = cod.skew() k = cod.kurt() print ('偏度 = ' +str (s)+', 峰度 = ' +str (k))
1 偏度 = 4.58614227400281, 峰度 = 43.47585104239013
偏度\(S>0\) ,表明样本正偏,数据出现右侧长尾;峰度\(K>6\) ,表明分布陡峭。尝试通过取对数得到接近正态的分布。这里考虑到0的数据比较多,若采用\(x'=\lg(x+10^{-n})\) 的转换方法,会在\(x=-n\) 的地方出现一个异常峰,所以决定舍弃0数据。
1 2 3 4 5 6 cod2 = pd.Series.copy(cod,deep=True) for i in cod2.index: if cod[i] == 0: del cod2[i] else: cod2[i] = np.log10(cod[i])
1 2 3 4 5 fig, ax = plt.subplots(figsize=(7 ,5 )) sns.histplot(cod2, bins=100 ) plt.xlim(min (cod2), max (cod2)) plt.xlabel('log10(COD)' ) plt.ylabel('Frequency' )
1 2 3 s = cod2.skew() k = cod2.kurt() print ('偏度 = ' +str (s)+', 峰度 = ' +str (k))
1 偏度 = 0.08821022006460341, 峰度 = 0.6118708597667646
转换后的样本偏度约等于零,可以近似认为样本是无偏的。考虑到样本量>7500,根据大数定理认为样本总体符合正态分布。
事实上如果用K-S检验,还是会发现不能通过正态性检验的。可能是因为数据的差异性大,所以样本数量还远远不够;也有可能是因为数据本身的采样有偏,事实上水质监测数据确实不是完全随机时刻和取点的,会受到采样点设置偏好的影响。这里只是凭借大数定理和Q-Q图,定性地认为服从正态分布。
用Q-Q图辅助判断,处理后的数据点更好地分布在对角线上。
1 2 3 4 5 fig, ax = plt.subplots(1 ,2 ,figsize=(12 ,5 )) stats.probplot(cod, dist="norm" , plot=ax[0 ]) stats.probplot(cod2, dist="norm" , plot=ax[1 ]) ax[0 ].set_title('Original Data' ) ax[1 ].set_title('Logarithmic Data' )
image-20220424022527148
方差分析
获取海区和省份的数据(定类数据),和对数化的COD一起合成data frame。
1 2 3 4 5 anova_data = pd.DataFrame(columns=['海区' ,'省份' ,'logCOD' ]) for i in cod2.index: sea = data['海区' ][i] province = data['省份' ][i] anova_data.loc[i] = [sea, province, cod2[i]]
先用箱型图看一下四大海区的分布差异。
1 2 fig, ax = plt.subplots(figsize=(7 ,5 )) sns.boxplot(x='海区' ,y='logCOD' ,data = anova_data)
单因素方差分析。
1 2 3 model = ols('logCOD ~ 海区' ,data=anova_data).fit() anova_table = anova_lm(model, typ = 2 ) print (anova_table)
1 2 3 sum_sq df F PR(>F) 海区 73.816704 3.0 390.048152 1.941560e-235 Residual 473.062501 7499.0 NaN NaN
省份分布差异同理。
1 2 fig, ax = plt.subplots(figsize=(10 ,5 )) sns.boxplot(x='省份' ,y='logCOD' ,data = anova_data)
1 2 3 model = ols('logCOD ~ 省份' ,data=anova_data).fit() anova_table = anova_lm(model, typ = 2 ) print (anova_table)
1 2 3 sum_sq df F PR(>F) 省份 102.311302 11.0 156.722959 0.0 Residual 444.567902 7491.0 NaN NaN
单因素方差分析P值均低于0.01,表明海区和省份因素都会使得COD数据产生显著差异。
Pearson相关分析
上面的分析是针对COD(定序变量)和海区/省份(定类变量)之间关系的分析。接下来选取“无机氮(mg/L)”数据,与COD数据进行定序变量的相关性分析。由于Pearson相关性分析要求两个变量均服从正态分布,因此对无机氮数据也要先进行数据清理和转换,取以10为底的对数。Q-Q图依然可以反映出处理前后的正态性变化。
1 2 3 4 5 ni = data['无机氮(mg/L)' ] ni = pd.to_numeric(ni) ni2 = ni.copy(deep=True ) for i in ni.index: ni2[i] = np.log10(np.sqrt(ni[i]))
1 2 3 4 5 fig, ax = plt.subplots(1 ,2 ,figsize=(12 ,5 )) stats.probplot(ni, dist="norm" , plot=ax[0 ]) stats.probplot(ni2, dist="norm" , plot=ax[1 ]) ax[0 ].set_title('Original Data' ) ax[1 ].set_title('Logarithmic Data' )
image-20220424023434570
由于有些监测点只有化学需氧量数据,没有无机氮数据,因此需要进行数据清理,将这些监测点删除。
1 2 3 for i in range (len (data)): if i not in cod2.index: del ni2[i]
进行Pearson检验。
1 2 r,p = stats.pearsonr(ni2,cod2) print ('Pearson检验,相关系数 = %f, P值 = %f' %(r,p))
1 Pearson检验,相关系数 = 0.446809, P值 = 0.000000
\(r\in(0.4,0.6)\) ,认为\(\lg(\rm COD)\) 和\(\lg(\rm N)\) 之间为中等程度正相关。通过散点图分布情况也可以感性判断出这一规律。
线性回归
1 2 3 4 5 6 7 model = LinearRegression() model.fit(ni.values.reshape((-1 , 1 )), cod.values) predict_y = model.predict(ni2.values.reshape((-1 , 1 ))) print ('线性回归斜率:' ,model.coef_, '\n截距:' ,model.intercept_, '\nR方:' ,model.score(ni.values.reshape((-1 , 1 )),cod.values))
1 2 3 线性回归斜率: [1.10361078] 截距: 0.7039567080504857 R方: 0.24820547860780384
1 2 3 4 5 fig, ax = plt.subplots(figsize=(5 ,5 )) plt.scatter(ni2,cod2,s=1 ) plt.plot(ni2,predict_y,c='k' ) plt.xlabel('logN' ) plt.ylabel('logCOD' )
考虑到海水地理位置对相关性的影响,再分海区绘制散点图和回归曲线。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 sl_data = pd.DataFrame(columns=['海区' ,'logN' ,'logCOD' ]) for i in cod2.index: sea = data['海区' ][i] sl_data.loc[i] = [sea, ni2[i], cod2[i]] seas = ['黄海' ,'渤海' ,'东海' ,'南海' ] predicts = [] for i in range (4 ): model = LinearRegression() model.fit(sl_data[sl_data['海区' ]==seas[i]]['logN' ].values.reshape((-1 , 1 )), sl_data[sl_data['海区' ]==seas[i]]['logCOD' ].values) predict_y = model.predict(sl_data[sl_data['海区' ]==seas[i]]['logN' ].values.reshape((-1 , 1 ))) predicts.append(predict_y) print (seas[i], '\n线性回归斜率:' ,model.coef_, '\n截距:' ,model.intercept_, '\nR方:' ,model.score(sl_data[sl_data['海区' ]==seas[i]]['logN' ].values.reshape((-1 , 1 )), sl_data[sl_data['海区' ]==seas[i]]['logCOD' ].values) )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 黄海 线性回归斜率: [0.26539399] 截距: 0.081372330354296 R方: 0.14307643170710227 渤海 线性回归斜率: [0.27080498] 截距: 0.20731753294507865 R方: 0.1352474744941149 东海 线性回归斜率: [0.63405663] 截距: 0.0898058817788494 R方: 0.3550328969220925 南海 线性回归斜率: [0.36912331] 截距: 0.012211050912901922 R方: 0.1838150602146984
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 fig, ax = plt.subplots(2 ,2 ,figsize=(10 ,10 )) ax[0 ,0 ].scatter(sl_data[sl_data['海区' ]=='黄海' ]['logN' ], sl_data[sl_data['海区' ]=='黄海' ]['logCOD' ], s=3 , c='g' ) ax[0 ,0 ].plot(sl_data[sl_data['海区' ]=='黄海' ]['logN' ], predicts[0 ], c='k' ) ax[0 ,0 ].set_title('黄 海' ) ax[0 ,0 ].set_xlabel('logN' ) ax[0 ,0 ].set_ylabel('logCOD' ) ax[0 ,0 ].set_xlim(-1.52 , 0.5 ) ax[0 ,0 ].set_ylim(-1.2 ,1.3 ) ax[0 ,1 ].scatter(sl_data[sl_data['海区' ]=='渤海' ]['logN' ], sl_data[sl_data['海区' ]=='渤海' ]['logCOD' ], s=3 , c='y' ) ax[0 ,1 ].plot(sl_data[sl_data['海区' ]=='渤海' ]['logN' ], predicts[1 ], c='k' ) ax[0 ,1 ].set_title('渤 海' ) ax[0 ,1 ].set_xlabel('logN' ) ax[0 ,1 ].set_ylabel('logCOD' ) ax[0 ,1 ].set_xlim(-1.52 , 0.5 ) ax[0 ,1 ].set_ylim(-1.2 ,1.3 ) ax[1 ,0 ].scatter(sl_data[sl_data['海区' ]=='东海' ]['logN' ], sl_data[sl_data['海区' ]=='东海' ]['logCOD' ], s=3 , c='purple' ) ax[1 ,0 ].plot(sl_data[sl_data['海区' ]=='东海' ]['logN' ], predicts[2 ], c='k' ) ax[1 ,0 ].set_title('东 海' ) ax[1 ,0 ].set_xlabel('logN' ) ax[1 ,0 ].set_ylabel('logCOD' ) ax[1 ,0 ].set_xlim(-1.52 , 0.5 ) ax[1 ,0 ].set_ylim(-1.2 ,1.3 ) ax[1 ,1 ].scatter(sl_data[sl_data['海区' ]=='南海' ]['logN' ], sl_data[sl_data['海区' ]=='南海' ]['logCOD' ], s=3 , c='brown' ) ax[1 ,1 ].plot(sl_data[sl_data['海区' ]=='南海' ]['logN' ], predicts[3 ], c='k' ) ax[1 ,1 ].set_title('南 海' ) ax[1 ,1 ].set_xlabel('logN' ) ax[1 ,1 ].set_ylabel('logCOD' ) ax[1 ,1 ].set_xlim(-1.52 , 0.5 ) ax[1 ,1 ].set_ylim(-1.2 ,1.3 )
image-20220424024129532
机器学习
为了从多个污染指标的层面上对不同海区的差异进行分析,以“化学需氧量(mg/L)”、“无机氮(mg/L)”、“活性磷酸盐(mg/L)”、“石油类(mg/L)”这四个指标作为自变量,将“海区”作为因变量,尝试用5种不同的模型进行机器学习,并找出预测准确度最高的模型进行进一步分析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import pandas as pdimport matplotlib.pyplot as pltimport numpy as npimport seaborn as snsplt.rcParams['font.sans-serif' ]=['SimHei' ] plt.rcParams['axes.unicode_minus' ]=False from sklearn import model_selectionfrom sklearn.preprocessing import StandardScalerfrom sklearn.tree import DecisionTreeClassifierfrom sklearn.neighbors import KNeighborsClassifierfrom sklearn.linear_model import LogisticRegressionfrom sklearn.svm import SVCfrom sklearn.ensemble import RandomForestClassifierfrom sklearn.model_selection import cross_val_scorefrom sklearn.metrics import classification_report, confusion_matrix, f1_score, roc_curve, roc_auc_score, precision_score, accuracy_score, recall_score, explained_variance_score, mean_absolute_error, mean_squared_error, r2_score
数据清理
首先把数据转换为数值类型。将标记为“未检出”、“<0.01”、“<0.1”的数据转换为0。将因变量(海区)数值化,标记成0~3。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 for i in range (len (data)): if data['海区' ][i]=='黄海' : data['海区' ][i] = 0 elif data['海区' ][i]=='渤海' : data['海区' ][i] = 1 elif data['海区' ][i]=='东海' : data['海区' ][i] = 2 else : data['海区' ][i] = 3 for item in ['化学需氧量(mg/L)' ,'无机氮(mg/L)' ,'活性磷酸盐(mg/L)' ,'石油类(mg/L)' ]: if type (data[item][i])==str and (data[item][i] == '未检出' or data[item][i][0 ] == '<' ): data[item][i] = '0' D = data.loc[:,['海区' ,'化学需氧量(mg/L)' ,'无机氮(mg/L)' ,'活性磷酸盐(mg/L)' ,'石油类(mg/L)' ]] for item in D.columns: D[item] = pd.to_numeric(D[item])
其中任何一项数据缺失的监测点都删除,最后把清理后的因变量保存为一维数组Y
,自变量保存为四维数组X
。
1 2 3 4 5 6 nanlist = [] for i in range (len (D.values)): if True in np.isnan(D.values[i]): nanlist.append(i) X = np.delete(D.iloc[:,1 :5 ].values, nanlist, axis=0 ) Y = np.delete(D.iloc[:,0 ].values, nanlist, axis=0 )
多模型比较
划分训练集和测试机,特征缩放。
1 2 3 4 x_train,x_test, y_train,y_test = model_selection.train_test_split(X, Y, test_size=0.2 , random_state=1 ) sc = StandardScaler() x_train = sc.fit_transform(x_train) x_test = sc.transform(x_test)
用于对比的5种模型分别是:Decision Tree决策树、K-Nearest Neighbors K-最近邻(KNN)、Logistic Regression 逻辑回归、SVM支持向量机 (SVM)、Random Forest Tree随机森林。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 tree_model = DecisionTreeClassifier(max_depth = 10 , criterion = 'entropy' ) tree_model.fit(x_train, y_train) tree_yhat = tree_model.predict(x_test) n = 20 knn = KNeighborsClassifier(n_neighbors = n) knn.fit(x_train, y_train) knn_yhat = knn.predict(x_test) lr = LogisticRegression() lr.fit(x_train, y_train) lr_yhat = lr.predict(x_test) svm = SVC(probability=True ) svm.fit(x_train, y_train) svm_yhat = svm.predict(x_test) rf = RandomForestClassifier(max_depth = 10 ) rf.fit(x_train, y_train) rf_yhat = rf.predict(x_test)
分别用accuracy score和F1-score来衡量不同模型的效果。
1 2 3 4 5 6 7 8 9 10 11 12 pd.DataFrame({ 'Model' : ['Decision Tree model' , 'KNN model' , 'Logistic Regression model' , 'SVM model' , 'Random Forest Tree model' ], 'Accuracy score' :[accuracy_score(y_test, tree_yhat), accuracy_score(y_test, knn_yhat), accuracy_score(y_test, lr_yhat), accuracy_score(y_test, svm_yhat), accuracy_score(y_test, rf_yhat)] })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 pd.DataFrame({ 'Model' : ['Decision Tree model' , 'KNN model' , 'Logistic Regression model' , 'SVM model' , 'Random Forest Tree model' ], '黄海(0)' :[f1_score(y_test, tree_yhat, average=None )[0 ], f1_score(y_test, knn_yhat, average=None )[0 ], f1_score(y_test, lr_yhat, average=None )[0 ], f1_score(y_test, svm_yhat,average=None )[0 ], f1_score(y_test, rf_yhat, average=None )[0 ]], '渤海(1)' : [f1_score(y_test, tree_yhat, average=None )[1 ], f1_score(y_test, knn_yhat, average=None )[1 ], f1_score(y_test, lr_yhat, average=None )[1 ], f1_score(y_test, svm_yhat,average=None )[1 ], f1_score(y_test, rf_yhat, average=None )[1 ]], '东海(2)' :[f1_score(y_test, tree_yhat, average=None )[2 ], f1_score(y_test, knn_yhat, average=None )[2 ], f1_score(y_test, lr_yhat, average=None )[2 ], f1_score(y_test, svm_yhat,average=None )[2 ], f1_score(y_test, rf_yhat, average=None )[2 ]], '南海(3)' : [f1_score(y_test, tree_yhat, average=None )[3 ], f1_score(y_test, knn_yhat, average=None )[3 ], f1_score(y_test, lr_yhat, average=None )[3 ], f1_score(y_test, svm_yhat,average=None )[3 ], f1_score(y_test, rf_yhat, average=None )[3 ]], })
通过混淆矩阵也可以判断不同模型的预测能力,并且比较不同海区的数据被正确判断或误判为其他海区的可能性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 tree_matrix = confusion_matrix(y_test, tree_yhat, labels = [0 , 1 , 2 , 3 ]) knn_matrix = confusion_matrix(y_test, knn_yhat, labels = [0 , 1 , 2 , 3 ]) lr_matrix = confusion_matrix(y_test, lr_yhat, labels = [0 , 1 , 2 , 3 ]) svm_matrix = confusion_matrix(y_test, svm_yhat, labels = [0 , 1 , 2 , 3 ]) rf_matrix = confusion_matrix(y_test, rf_yhat, labels = [0 , 1 , 2 , 3 ]) def plot_confusion_matrix (ax, cm, classes, title ): plot = ax.imshow(cm, interpolation='nearest' , cmap=plt.cm.Blues) ax.set_title(title) ax.set_xticks([0 ,1 ,2 ,3 ]) ax.set_xticklabels(classes) ax.set_yticks([0 ,1 ,2 ,3 ]) ax.set_yticklabels(classes) ax.set_ylabel('真实海区' ) ax.set_xlabel('预测海区' ) return plot classes = ['黄海(0)' ,'渤海(1)' ,'东海(2)' ,'南海(3)' ] fig, ax = plt.subplots(2 ,3 ,figsize=(16 ,8 )) a = plot_confusion_matrix(ax[0 ][0 ], tree_matrix, classes=classes, title='Decision Tree' ) b = plot_confusion_matrix(ax[0 ][1 ], knn_matrix, classes=classes, title='KNN' ) c = plot_confusion_matrix(ax[0 ][2 ], lr_matrix, classes=classes, title='Logistic Regression' ) d = plot_confusion_matrix(ax[1 ][0 ], svm_matrix, classes=classes, title='SVM' ) e = plot_confusion_matrix(ax[1 ][1 ], rf_matrix, classes=classes, title='Random Forest Tree' ) fig.colorbar(a, ax = ax[0 ][0 ]) fig.colorbar(b, ax = ax[0 ][1 ]) fig.colorbar(c, ax = ax[0 ][2 ]) fig.colorbar(d, ax = ax[1 ][0 ]) fig.colorbar(e, ax = ax[1 ][1 ]) fig.delaxes(ax[1 ][2 ]) plt.tight_layout() plt.show()
image-20220424025728647
KNN模型
综合以上,认为在当前的参数设置下KNN模型具有最好的解释力。输出KNN模型的分类报告。
1 print (classification_report(y_test, knn_yhat))
1 2 3 4 5 6 7 8 9 10 precision recall f1-score support 0 0.52 0.62 0.57 370 1 0.56 0.61 0.59 295 2 0.71 0.70 0.70 446 3 0.59 0.46 0.52 398 accuracy 0.60 1509 macro avg 0.60 0.60 0.59 1509 weighted avg 0.60 0.60 0.60 1509
本模型采用的是多分类模型,但是传统的ROC曲线和AUC只能用于描述不同于“0-1”分类模型。首先需要将因变量的测试集转换成类似于0-1分类的多维矩阵。
1 2 y_test0 = np.array([[ 1 if y_test[i]==j else 0 for j in range(4) ] for i in range(len(y_test))], dtype=float) y_test0
1 2 3 4 5 6 7 array([[0., 0., 1., 0.], [0., 0., 1., 0.], [0., 1., 0., 0.], ..., [1., 0., 0., 0.], [0., 1., 0., 0.], [0., 0., 1., 0.]])
将转换后的因变量测试集和自变量测试集都按行展开,例如
1 array([0., 0., 1., ..., 0., 1., 0.])
采用macro方法对多分类数据进行处理,然后再计算ROC曲线和AUC值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 y_pred=knn.predict_proba(x_test)[:,:] roc_score = roc_auc_score(y_test, y_pred, multi_class='ovo' , average='macro' ) y_test0 = np.array([[ 1 if y_test[i]==j else 0 for j in range (4 ) ] for i in range (len (y_test))], dtype=float ) fpr,tpr, thresholds = roc_curve(y_test0.ravel(), y_pred.ravel(), pos_label=True ) fig, ax = plt.subplots(1 ,1 ,figsize=(8 ,6 )) ax.plot(fpr,tpr,"r" ,linewidth = 3 ) ax.set_title("多分类ROC曲线" ) ax.plot([0 , 1 ], [0 , 1 ], 'k--' ) ax.grid() ax.set_xlabel("假正率" ) ax.set_ylabel("真正率" ) ax.set_xlim(0 , 1 ) ax.set_ylim(0 , 1 ) ax.text(0.15 ,0.9 ,"AUC = " +str (round (roc_score,4 ))) plt.show()
限制本模型预测能力的可能原因有:分类精细度和依据不合适;没有找到最关键的自变量;数据采集有偏;模型训练迭代次数不足。