import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
# 导入数据
weather = pd.read_csv(r"your_path\weather.csv",index_col=0)
# 观察前五行数据
print(weather.head(5))
#将特征矩阵和标签Y分开
X = weather.iloc[:,:-1]
Y = weather.iloc[:,-1]
通过简单的观察数据,我们发现有很多需要我们要作的事情,例如Nan值、字符型变量的处理,这些都是特征工程中的难点。
#探索数据类型
X.info()
#探索缺失值
X.isnull().mean() #缺失值所占总值的比例
#我们要有不同的缺失值填补策略{均值,众数,中位数,....}
探索标签的缺失情况
Y.isnull().sum()
# result
0
#分训练集和测试集
Xtrain, Xtest, Ytrain, Ytest = train_test_split(X,Y,test_size=0.3,random_state=420) #随机抽样
Xtrain.head()
#恢复索引
for i in [Xtrain, Xtest, Ytrain, Ytest]:
i.index = range(i.shape[0])
#是否有样本不平衡问题?
print(Ytrain.value_counts())
print(Ytest.value_counts())
#将标签编码
from sklearn.preprocessing import LabelEncoder #标签专用
encorder = LabelEncoder().fit(Ytrain) #允许一维数据的输入的
#认得了:有两类,YES和NO,YES是1,NO是0
#使用训练集进行训练,然后在训练集和测试集上分别进行transform
Ytrain = pd.DataFrame(encorder.transform(Ytrain))
Ytest = pd.DataFrame(encorder.transform(Ytest))
#如果我们的测试集中,出现了训练集中没有出现过的标签类别
#比如说,测试集中有YES, NO, UNKNOWN
#而我们的训练集中只有YES和NO
#描述性统计
Xtrain.describe([0.01,0.05,0.1,0.25,0.5,0.75,0.9,0.99]).T
#先查看原始的数据结构
Xtrain.shape
Xtest.shape
#观察异常值是大量存在,还是少数存在
Xtrain.loc[Xtrain.loc[:,"Cloud9am"] == 9,"Cloud9am"]
Xtest.loc[Xtest.loc[:,"Cloud9am"] == 9,"Cloud9am"]
Xtest.loc[Xtest.loc[:,"Cloud3pm"] == 9,"Cloud3pm"]
#少数存在,于是采取删除的策略 #注意如果删除特征矩阵,则必须连对应的标签一起删除,特征矩阵的行和标签的行必须要一一对应
Xtrain = Xtrain.drop(index = 71737)
Ytrain = Ytrain.drop(index = 71737)
#删除完毕之后,观察原始的数据结构,确认删除正确 Xtrain.shape
Xtest = Xtest.drop(index = [19646,29632])
Ytest = Ytest.drop(index = [19646,29632])
Xtest.shape
#进行任何行删除之后,千万记得要恢复索引 for i in [Xtrain, Xtest, Ytrain, Ytest]: i.index = range(i.shape[0])
Xtrain.head() Xtest.head()
Xtrainc = Xtrain.copy()
Xtrainc.sort_values(by="Location")
Xtrain.iloc[:,0].value_counts()
#首先,日期不是独一无二的,日期有重复
#其次,在我们分训练集和测试集之后,日期也不是连续的,而是分散的
#某一年的某一天倾向于会下雨?或者倾向于不会下雨吗?
#不是日期影响了下雨与否,反而更多的是这一天的日照时间,湿度,温度等等这些因素影响了是否会下雨
#光看日期,其实感觉它对我们的判断并无直接影响 #如果我们把它当作连续型变量处理,那算法会人为它是一系列1~3000左右的数字,不会意识到这是日期
Xtrain.iloc[:,0].value_counts().count()
#如果我们把它当作分类型变量处理,类别太多,有2141类,如果换成数值型,会被直接当成连续型变量,如果做成哑 变量,我们特征的维度会爆炸
Xtrain["Rainfall"].head(20)
Xtrain.loc[Xtrain["Rainfall"] >= 1,"RainToday"] = "Yes" Xtrain.loc[Xtrain["Rainfall"] < 1,"RainToday"] = "No" Xtrain.loc[Xtrain["Rainfall"] == np.nan,"RainToday"] = np.nan
Xtest.loc[Xtest["Rainfall"] >= 1,"RainToday"] = "Yes" Xtest.loc[Xtest["Rainfall"] < 1,"RainToday"] = "No" Xtest.loc[Xtest["Rainfall"] == np.nan,"RainToday"] = np.nan
Xtrain.head()
Xtest.head()
int(Xtrain.loc[0,"Date"].split("-")[1]) #提取出月份
Xtrain["Date"] = Xtrain["Date"].apply(lambda x:int(x.split("-")[1])) #替换完毕后,我们需要修改列的名称 #rename是比较少有的,可以用来修改单个列名的函数 #我们通常都直接使用 df.columns = 某个列表 这样的形式来一次修改所有的列名 #但rename允许我们只修改某个单独的列 Xtrain = Xtrain.rename(columns={"Date":"Month"})
Xtrain.head()
Xtest["Date"] = Xtest["Date"].apply(lambda x:int(x.split("-")[1])) Xtest = Xtest.rename(columns={"Date":"Month"})
Xtest.head()
对地点特征的分析过程:
常识上来说,我们认为地点肯定是对明天是否会下雨存在影响的。比如说,如 果其他信息都不给出,我们只猜测,“伦敦明天是否会下雨”和”北京明天是否会下雨“,我一定会猜测伦敦会下雨, 而北京不会,因为伦敦是常年下雨的城市,而北京的气候非常干燥。对澳大利亚这样面积巨大的国家来说,必然存 在着不同的城市有着不同的下雨倾向的情况。但尴尬的是,和时间一样,我们输入地点的名字对于算法来说,就是 一串字符,"London"和"Beijing"对算法来说,和0,1没有区别。同样,我们的样本中含有49个不同地点,如果做 成分类型变量,算法就无法辨别它究竟是否是分类变量。也就是说,我们需要让算法意识到,不同的地点因为气候 不同,所以对“明天是否会下雨”有着不同的影响。如果我们能够将地点转换为这个地方的气候的话,我们就可以将 不同城市打包到同一个气候中,而同一个气候下反应的降雨情况应该是相似的
基于气象局和ABCB的数据,我制作了澳大利亚主要城市所对应的 气候类型数据,并保存在csv文件city_climate.csv当中。然后,我使用以下代码,在google上进行爬虫,爬出了每 个城市所对应的经纬度,并保存在数据cityll.csv当中,代码网上都有,这里不在展示,可以直接参考“写在前面“得连接查看源码
**为什么我们会需要城市的经纬度呢?**我曾经尝试过直接使用样本中的城市来爬取城市本身的气候,然而由于样本中 的地点名称,其实是气候站的名称,而不是城市本身的名称,因此不是每一个样本都能够直接获取到城市的气候。 比如说,如果我们搜索“海淀区气候”,搜索引擎返回的可能是海淀区现在的气温,而不是整个北京的气候类型。因 此,我们需要澳大利亚气象局的数据,来找到这些气候站所对应的城市。
我们有了澳大利亚全国主要城市的气候,也有了澳大利亚主要城市的经纬度(地点),我们就可以通过计算我们样 本中的每个气候站到各个主要城市的地理距离,来找出一个离这个气象站近的主要城市,而这个主要城市的气候 就是我们样本点所在的地点的气候。
接下来,我们如果想要计算距离,我们就会需要所有样本数据中的城市。我们认为,只有出现在训练集中的地点才 会出现在测试集中,基于这样的假设,我们来爬取训练集中所有的地点所对应的经纬度,并且保存在一个csv文件 samplecity.csv中
接下来 我们要开始计算我们样本上的地点到每个澳大利亚主要城市的距离,而离我们的样本地点近的那个澳大利亚主要 城市的气候,就是我们样本点的气候。
有了每个样本城市所对应的气候,我们接下来就使用气候来替掉原本的城市,原本的气象站的名称。
到这里,地点就处理完毕了。其实,我们还没有将这个特征转化为数字,即还没有对它进行编码。我们稍后和其他 的分类型变量一起来编码。
总结一下地点的处理:
1.获取主要城市的气候数据
2.获取主要城市的经纬度
3.获取样本城市(气象局)的经纬度,利用最近邻原则获取样本城市对应的气候数据
4.将样本的地点数据-------->气候数据
#首先找出,分类型特征都有哪些
cate = Xtrain.columns[Xtrain.dtypes == "object"].tolist()
#除了特征类型为"object"的特征们,还有虽然用数字表示,但是本质为分类型特征的云层遮蔽程度
cloud = ["Cloud9am","Cloud3pm"]
cate = cate + cloud
print(cate)
#对于分类型特征,我们使用众数来进行填补
from sklearn.impute import SimpleImputer #0.20, conda, pip
si = SimpleImputer(missing_values=np.nan,strategy="most_frequent")
#我们使用训练集数据来训练我们的填补器,本质是在生成训练集中的众数
si.fit(Xtrain.loc[:,cate])
#然后我们用训练集中的众数来同时填补训练集和测试集
Xtrain.loc[:,cate] = si.transform(Xtrain.loc[:,cate])
Xtest.loc[:,cate] = si.transform(Xtest.loc[:,cate])
#查看分类型特征是否依然存在缺失值
Xtrain.loc[:,cate].isnull().mean()
#将所有的分类型变量编码为数字,一个类别是一个数字
from sklearn.preprocessing import OrdinalEncoder #只允许二维以上的数据进行输入
oe = OrdinalEncoder()
#利用训练集进行fit
oe = oe.fit(Xtrain.loc[:,cate])
#用训练集的编码结果来编码训练和测试特征矩阵
#在这里如果测试特征矩阵报错,就说明测试集中出现了训练集中从未见过的类别
Xtrain.loc[:,cate] = oe.transform(Xtrain.loc[:,cate])
Xtest.loc[:,cate] = oe.transform(Xtest.loc[:,cate])
print(Xtrain.loc[:,cate].head())
print(Xtest.loc[:,cate].head())
#实例化模型,填补策略为"mean"表示均值
impmean = SimpleImputer(missing_values=np.nan,strategy = "mean")
#用训练集来fit模型
impmean = impmean.fit(Xtrain.loc[:,col])
#分别在训练集和测试集上进行均值填补
Xtrain.loc[:,col] = impmean.transform(Xtrain.loc[:,col])
Xtest.loc[:,col] = impmean.transform(Xtest.loc[:,col])
print(Xtrain.isnull().mean())
from sklearn.preprocessing import StandardScaler #数据转换为均值为0,方差为1的数据
#标准化不改变数据的分布,不会把数据变成正态分布的
ss = StandardScaler()
ss = ss.fit(Xtrain.loc[:,col])
Xtrain.loc[:,col] = ss.transform(Xtrain.loc[:,col])
Xtest.loc[:,col] = ss.transform(Xtest.loc[:,col])
print(Xtrain.head())
至此,我们终于完成了特征工程。
#建模选择自然是我们的支持向量机SVC,首先用核函数的学习曲线来选择核函数
#同时观察,精确性,recall以及AUC分数
times = time() #因为SVM是计算量很大的模型,所以我们需要时刻监控我们的模型运行时间
for kernel in ["linear","poly","rbf","sigmoid"]:
clf = SVC(kernel = kernel
,gamma="auto"
,degree = 1
,cache_size = 5000
).fit(Xtrain, Ytrain)
result = clf.predict(Xtest)
score = clf.score(Xtest,Ytest)
recall = recall_score(Ytest, result)
auc = roc_auc_score(Ytest,clf.decision_function(Xtest))
print("%s 's testing accuracy %f, recall is %f', auc is %f" % (kernel,score,recall,auc))
print(datetime.datetime.fromtimestamp(time()-times).strftime("%M:%S:%f"))
注意到,模型的准确率和auc曲线都还可以,recall可太低了,这代表我们能把雨天预测对的概率很小,我们需要着重考虑一下recall的优化
times = time()
for kernel in ["linear","poly","rbf","sigmoid"]:
clf = SVC(kernel = kernel
,gamma="auto"
,degree = 1
,cache_size = 5000
,class_weight = "balanced"
).fit(Xtrain, Ytrain)
result = clf.predict(Xtest)
score = clf.score(Xtest,Ytest)
recall = recall_score(Ytest, result)
auc = roc_auc_score(Ytest,clf.decision_function(Xtest))
print("%s 's testing accuracy %f, recall is %f', auc is %f" % (kernel,score,recall,auc))
print(datetime.datetime.fromtimestamp(time()-times).strftime("%M:%S:%f"))
from sklearn.linear_model import LogisticRegression as LR
logclf = LR(solver="liblinear").fit(Xtrain, Ytrain)
logclf.score(Xtest,Ytest)
# result
0.8486666666666667,比SVM稍微好一点
C_range = np.linspace(5,10,10)
for C in C_range:
logclf = LR(solver="liblinear",C=C).fit(Xtrain, Ytrain)
print(C,logclf.score(Xtest,Ytest))
# 随机森林
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
superpa = []
for i in range(200):
rfc = RandomForestClassifier(n_estimators=i+1)
rfc.fit(Xtrain,Ytrain)
rfc_s = rfc.score(Xtest,Ytest)
superpa.append(rfc_s)
print("最大的准确率为:{}".format(max(superpa)),"对应的树的数量为:{}".format(superpa.index(max(superpa))))
我们追寻的是Recall和假正率差值最大的时候的两个值。因为,随着Recall增加,我们捕捉少数类的能力越来越高,但同时也会将多数类判成少数类,我们希望在尽量捕捉到少数类的同时减少多数类判成少数类也就是FPR。换成数学语言就是,Recall越大越好,FPR越小越好。
from sklearn.metrics import roc_curve as ROC
import matplotlib.pyplot as plt
FPR, Recall, thresholds = ROC(Ytest,clf.decision_function(Xtest),pos_label=1)
area = roc_auc_score(Ytest,clf.decision_function(Xtest))
plt.figure()
plt.plot(FPR, Recall, color='red',
label='ROC curve (area = %0.2f)' % area)
plt.plot([0, 1], [0, 1], color='black', linestyle='--')
plt.xlim([-0.05, 1.05])
plt.ylim([-0.05, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('Recall')
plt.title('Receiver operating characteristic example')
plt.legend(loc="lower right")
plt.show()
from sklearn.metrics import accuracy_score as AC
maxindex = (Recall - FPR).tolist().index(max(Recall - FPR))
thresholds[maxindex]
clf = SVC(kernel = "linear",C=3.1663157894736838,cache_size = 5000
,class_weight = "balanced"
).fit(Xtrain, Ytrain)
prob = pd.DataFrame(clf.decision_function(Xtest))
prob.loc[prob.iloc[:,0] >= thresholds[maxindex],"y_pred"]=1
prob.loc[prob.iloc[:,0] < thresholds[maxindex],"y_pred"]=0
times = time()
score = AC(Ytest,prob.loc[:,"y_pred"].values)
recall = recall_score(Ytest, prob.loc[:,"y_pred"])
print("testing accuracy %f,recall is %f" % (score,recall))
print(datetime.datetime.fromtimestamp(time()-times).strftime("%M:%S:%f"))
SVM的最佳准确率为84.40%;
LR的最佳准确率为84.93%
RF的最佳准确率为85.84%
最终我们选择RF模型作为RF