做者:xiaoyupython
微信公衆號:Python數據科學算法
知乎:python數據分析師微信
上一篇是數據挖掘的前戲,主要目的是認識數據特徵、判斷特徵重要性、觀察數據異常,掌握數據間聯繫。本篇將繼續上一篇分析進行數據挖掘建模部分。app
上篇數據分析的連接:
【Kaggle入門級競賽top5%排名經驗分享】— 分析篇dom
數據預處理涉及的內容不少,也包括特徵工程,是任務量最大的一部分。爲了讓你們更清晰的閱讀,如下先列出處理部分大體要用到的一些方法。學習
one-hot
和 label coding
;分析部分咱們看到,存在缺失值的特徵有4個:Age,Cabin,Embarked,Fare。關於缺失值處理部分博主以前介紹過一些方法:【Python數據分析基礎】: 數據缺失值處理測試
下面開始對缺失值分別處理。優化
Fare缺失值處理編碼
首先查看一下Fare特徵缺失:spa
df[df['Fare'].isnull()]
發現只有一個缺失值,其實能夠直接刪除,可是好多乘客都是以一個家庭來的,這其中會有很強的聯繫,並會給咱們很好的線索,所以選擇不刪除。
繼續觀察一下這個缺失值乘客有什麼特色?如何利用咱們以前的分析來處理?
這時咱們可使用類似特徵替換方法來填補缺失值,下面來找一下與缺失值具備類似特徵的其它樣本數據:
df.loc[(df['Pclass']==3)&(df['Age']>60)&(df['Sex']=='male')]
找到了與之相匹配的幾位其它乘客,咱們就用這幾位乘客的Fare平均值來填補。
# 提取出Name中的Surname信息 df['surname'] = df["Name"].apply(lambda x: x.split(',')[0].lower()) fare_mean_estimated = df.loc[(df['Pclass']==3)&(df['Age']>60)&(df['Sex']=='male')].Fare.mean() df.loc[df['surname']=='storey','Fare'] = fare_mean_estimated
Embarked特徵缺失值
一樣,觀察Embarked
的缺失值狀況:
# Embarked缺失值處理 df[df['Embarked'].isnull()]
發現兩位都是女性
。上篇可視化分析過,pclass1
且爲女性的狀況下,Q港口
幾乎爲0,而C港口
最多,其次S港口
,下圖爲分析篇的可視化結果。
這裏採用出現最多的港口,也就是衆數C港口進行填補。
df['Embarked'] = df['Embarked'].fillna('C')
Cabin特徵缺失值
Cain特徵有70%
的缺失值,較爲嚴重,若是進行大量的填補會引入更多噪聲。由於缺失值也是一種值,這裏將Cabin缺失值視爲一種特殊的值來處理,並根據Cabin首個字符衍生一個新的特徵CabinCat
。
df['CabinCat'] = pd.Categorical.from_array(df.Cabin.fillna('0').apply(lambda x: x[0])).codes
pandas的 Categorical.from_array()
用法。代碼含義是用「0」
替換Cabin缺失值
,並將非缺失Cabin特徵值提取出第一個值以進行分類,好比A114就是A,C345就是C,以下:
[0, C, 0, C, 0, ..., 0, C, 0, 0, 0] Length: 1309 Categories (9, object): [0, A, B, C, ..., E, F, G, T]
用Categorical.from_array()
將Cabin分紅了9組,最後經過codes量化爲數字,經過可視化觀察一下分組離散化後的結果:
fig, ax = plt.subplots(figsize=(10,5)) sns.countplot(x='CabinCat', hue='Survived',data=df) plt.show()
以上可視化看到:Cabin缺失的乘客中,遇難人數是獲救人數2倍以上,而其它有Cabin信息的乘客中,獲救人數都相對較多。所以說明Cabin缺失與否關係到了生還的機率。
Age特徵缺失值
Age有20%缺失值,缺失值較多,大量刪除會減小樣本信息,因爲它與Cabin不一樣,這裏將利用其它特徵進行預測填補Age,也就是擬合未知Age特徵值,會在後續進行處理。
數據一致性分析
當咱們拿到數據後,咱們要謹記一個道理:不要徹底相信數據。即便不是異常值,也有多是錯誤的信息,那就是檢查數據的一致性。
本例中,咱們經過兩個錯誤的修正來理解一下。
錯誤1:SibSp和Parch特徵存在不一致
df.loc[df['surname']=='abbott',['Name','Sex','Age','SibSp','Parch']]
爲了方便閱讀,下面用序號來代替名字。
首先尋找到了船上姓 abbott
的全部人,即一家人。發現:392 乘客只有13歲,確有兩個孩子Parch=2
(理論上不太可能),而279乘客35歲,有一個孩子,還有一個兄弟姐妹,746有一個家長和一個兄弟姐妹。很明顯,信息是錯誤的,279與392乘客的信息寫反了。正確的信息是一位母親帶着兩個孩子,因此改成:279乘客爲SibSp=0,Parh=2
,392歲的乘客是:SibSp=1, Parh=1
。下面是修改代碼:
df.loc[(df['surname']=='abbott')&(df['Age']==35),'SibSp'] = 0 df.loc[(df['surname']=='abbott')&(df['Age']==35),'Parch'] = 2 df.loc[(df['surname']=='abbott')&(df['Age']==13),'SibSp'] = 1 df.loc[(df['surname']=='abbott')&(df['Age']==13),'Parch'] = 1
錯誤2:SibSp和Parch特徵存在不一致
df.loc[df['surname']=='ford',['Name','Sex','Age','SibSp','Parch']]
同理,ford
一家人也出現了一致性錯誤的問題,具體你們可自行分析。正確的是:一位母親帶着三個孩子,而最後一位乘客爲測試集裏的樣本,推測極可能是父親。下面是修改代碼:
df.loc[(df['surname']=='ford')&(df['Age']==16),'SibSp'] = 3 df.loc[(df['surname']=='ford')&(df['Age']==16),'Parch'] = 1 df.loc[(df['surname']=='ford')&(df['Age']==9),'SibSp'] = 3 df.loc[(df['surname']=='ford')&(df['Age']==9),'Parch'] = 1 df.loc[(df['surname']=='ford')&(df['Age']==21),'SibSp'] = 3 df.loc[(df['surname']=='ford')&(df['Age']==21),'Parch'] = 1 df.loc[(df['surname']=='ford')&(df['Age']==48),'SibSp'] = 0 df.loc[(df['surname']=='ford')&(df['Age']==48),'Parch'] = 4 df.loc[(df['surname']=='ford')&(df['Age']==18),'SibSp'] = 3 df.loc[(df['surname']=='ford')&(df['Age']==18),'Parch'] = 1
衍生變量
分析部分沒說起到Name
特徵,由於每一個人的名字都不同。可是一些人多是羣體行動,好比一家人一塊兒,而一家人的surname
是同樣的,所以這時候就能夠經過surname找到一個家庭羣體。家庭羣體有什麼用?咱們後面會提到。
實際上,若是咱們深刻分析,Name特徵是很是重要的。試想一下乘客有沒有多是和其餘人一塊兒上船的?是一家人?情侶?仍是獨自一人?而這一羣人生還的機率應該是存在共性的,好比:有一個5人之家,有4人死亡,能夠推測第5我的極有可能死亡。
下面是對全部特徵進行衍生的新特徵變量。
# 從Name中提取Title信息,由於同爲男性,Mr.和 Master.的生還率是不同的 df["Title"] = df["Name"].apply(lambda x: re.search(' ([A-Za-z]+)\.',x).group(1)) title_mapping = {"Mr": 1, "Miss": 2, "Mrs": 3, "Master": 4, "Dr": 5, "Rev": 6, "Major": 7, "Col": 7, "Mlle": 2, "Mme": 3,"Don": 9,"Dona": 9, "Lady": 10, "Countess": 10, "Jonkheer": 10, "Sir": 9, "Capt": 7, "Ms": 2} # 量化Title信息 df["TitleCat"] = df.loc[:,'Title'].map(title_mapping) # SibSp和Parch特徵進行組合 df["FamilySize"] = df["SibSp"] + df["Parch"] + 1 # 根據FamilySize分佈進行分箱 df["FamilySize"] = pd.cut(df["FamilySize"], bins=[0,1,4,20], labels=[0,1,2]) # 從Name特徵衍生出Name的長度 df["NameLength"] = df["Name"].apply(lambda x: len(x)) # 量化Embarked特徵 df["Embarked"] = pd.Categorical.from_array(df.Embarked).codes # 對Sex特徵進行獨熱編碼分組 df = pd.concat([df,pd.get_dummies(df['Sex'])],axis=1)
下面衍生特徵變量的說明:
高級衍生變量
【1】人物衍生特徵
因爲兒童的生還率較高,所以將全部乘客兒童單獨提取出來(這裏設置爲18歲)。而對於成年人女性生還機率比較高,因此又非爲成年女性和成年男性。代碼以下:
# 婦女/兒童 男士標籤 child_age = 18 def get_person(passenger): age, sex = passenger if (age < child_age): return 'child' elif (sex == 'female'): return 'female_adult' else: return 'male_adult' df = pd.concat([df, pd.DataFrame(df[['Age', 'Sex']].apply(get_person, axis=1), columns=['person'])],axis=1) df = pd.concat([df,pd.get_dummies(df['person'])],axis=1)
【2】Ticket衍生特徵
下面基於Ticket
衍生出了幾個高級特徵變量,其含義:若是幾我的擁有相同的Ticket號碼,那麼意味着他門是一個小羣體(一家人或情侶等),而又由於男性女性還機率本省存在差別,所以將分別衍生出幾我的物標籤特徵,即分羣體狀況下的男女生還特徵。如下是代碼實現:
table_ticket = pd.DataFrame(df["Ticket"].value_counts()) table_ticket.rename(columns={'Ticket':'Ticket_Numbers'}, inplace=True) table_ticket['Ticket_dead_women'] = df.Ticket[(df.female_adult == 1.0) & (df.Survived == 0.0) & ((df.Parch > 0) | (df.SibSp > 0))].value_counts() table_ticket['Ticket_dead_women'] = table_ticket['Ticket_dead_women'].fillna(0) table_ticket['Ticket_dead_women'][table_ticket['Ticket_dead_women'] > 0] = 1.0 table_ticket['Ticket_surviving_men'] = df.Ticket[(df.male_adult == 1.0) & (df.Survived == 1.0) & ((df.Parch > 0) | (df.SibSp > 0))].value_counts() table_ticket['Ticket_surviving_men'] = table_ticket['Ticket_surviving_men'].fillna(0) table_ticket['Ticket_surviving_men'][table_ticket['Ticket_surviving_men'] > 0] = 1.0 # Ticket特徵量化 table_ticket["Ticket_Id"] = pd.Categorical.from_array(table_ticket.index).codes table_ticket["Ticket_Id"][table_ticket["Ticket_Numbers"] < 3 ] = -1 # Ticket數量分箱 table_ticket["Ticket_Numbers"] = pd.cut(table_ticket["Ticket_Numbers"], bins=[0,1,4,20], labels=[0,1,2]) df = pd.merge(df, table_ticket, left_on="Ticket",right_index=True, how='left', sort=False)
同理,基於衍生變量Surname
也能夠衍生出高級特徵變量,以及Cabin的奇偶性衍生特徵
。
Age缺失值處理
前面說了將採用擬合的方法來填補Age
缺失值,那爲何必定要在後面處理呢?
緣由以下:
由於上面已經將所提問題解決,所以能夠開始擬合Age缺失值
。這部分使用了隨機森林的ExtraTreesRegressor
模型進行擬合,代碼以下:
from sklearn.ensemble import RandomForestClassifier, ExtraTreesRegressor classers = ['Fare','Parch','Pclass','SibSp','TitleCat', 'CabinCat','female','male', 'Embarked', 'FamilySize', 'NameLength','Ticket_Numbers','Ticket_Id'] etr = ExtraTreesRegressor(n_estimators=200,random_state=0) X_train = df[classers][df['Age'].notnull()] Y_train = df['Age'][df['Age'].notnull()] X_test = df[classers][df['Age'].isnull()] etr.fit(X_train.as_matrix(),np.ravel(Y_train)) age_preds = etr.predict(X_test.as_matrix()) df['Age'][df['Age'].isnull()] = age_preds
想繼續看一下擬合的結果是怎麼樣,能夠經過可視化來觀察:
# Age缺失值填補後的狀況 X_test['Age'] = pd.Series(age_preds) f,ax=plt.subplots(figsize=(10,5)) sns.swarmplot(x='Pclass',y='Age',data=X_test) plt.show()
觀察:經過擬合獲得的Age缺失值的可視化展現,整體上看效果還能夠,具體須要進一步排查。
過濾法—方差分析
這裏特徵採用 ANOVA
方差分析的 F值
來對各個特徵變量打分,打分的意義是:各個特徵變量對目標變量的影響權重。代碼以下,使用了sklearn的feature_selection
:
from sklearn.feature_selection import SelectKBest, f_classif,chi2 target = data_train["Survived"].values features= ['female','male','Age','male_adult','female_adult', 'child','TitleCat', 'Pclass','Ticket_Id','NameLength','CabinType','CabinCat', 'SibSp', 'Parch', 'Fare','Embarked','Surname_Numbers','Ticket_Numbers','FamilySize', 'Ticket_dead_women','Ticket_surviving_men', 'Surname_dead_women','Surname_surviving_men'] train = df[0:891].copy() test = df[891:].copy() selector = SelectKBest(f_classif, k=len(features)) selector.fit(train[features], target) scores = -np.log10(selector.pvalues_) indices = np.argsort(scores)[::-1] print("Features importance :") for f in range(len(scores)): print("%0.2f %s" % (scores[indices[f]],features[indices[f]]))
此部分將以前訓練和測試合併的數據集分開,由於最後咱們要對測試集進行預測。特徵選擇權重結果以下(能夠經過可視化的方法展現出來):
這裏分數越高表明特徵權重越大,固然咱們能夠規定相應的閾值來選擇權重大的特徵。
特徵相關性分析
除了對特徵權重選擇外,咱們也要分析特徵相關性來篩選特徵。相關性大的特徵容易形成過擬合現象,所以須要進行剔除。最好的狀況就是:全部特徵相關性很低,各自的方差或者說信息量很高。
使用了seaborn的heatmap展現相關性,代碼以下:
features_selected = features # data_corr df_corr = df[features_selected].copy() colormap = plt.cm.RdBu plt.figure(figsize=(20,20)) sns.heatmap(df_corr.corr(),linewidths=0.1,vmax=1.0, square=True, cmap=colormap, linecolor='white', annot=True) plt.show()
這是個明顯的監督分類問題,所以可選擇的模型算法不少,或者模型融合等來提升準確度。這裏採用了集成學習的隨機森林RandomForest
模型。代碼以下:
from sklearn import cross_validation rfc = RandomForestClassifier(n_estimators=3000, min_samples_split=4, class_weight={0:0.745,1:0.255}) # rfc = AdaBoostClassifier(n_estimators=3000, learning_rate=0.1, random_state=1) # 交叉驗證,建模隨機森林 kf = cross_validation.KFold(train.shape[0], n_folds=3, random_state=1) scores = cross_validation.cross_val_score(rfc, train[features_selected], target, cv=kf) print("Accuracy: %0.3f (+/- %0.2f) [%s]" % (scores.mean()*100, scores.std()*100, 'RFC Cross Validation')) rfc.fit(train[features_selected], target) score = rfc.score(train[features_selected], target) print("Accuracy: %0.3f [%s]" % (score*100, 'RFC full test')) importances = rfc.feature_importances_ indices = np.argsort(importances)[::-1] for f in range(len(features_selected)): print("%d. feature %d (%f) %s" % (f + 1, indices[f]+1, importances[indices[f]]*100, features_selected[indices[f]]))
爲防止過擬合,採用了K折交叉驗證
進行採樣。集成學習等高級模型有自帶的特徵打分方法,訓練數據後,咱們能夠經過feature_importances
獲得特徵權重分數(當特徵特別多時,也能夠做爲初始的特徵篩選方法)。固然這也能夠經過可視化的方法展現出來。
輸入結果以下:
# 預測目標值 rfc.fit(train[features_selected], target) predictions = rfc.predict(test[features_selected])
# 輸出文件 PassengerId =np.array(test["PassengerId"]).astype(int) my_prediction = pd.DataFrame(predictions, PassengerId, columns = ["Survived"]) my_prediction.to_csv("my_prediction.csv", index_label = ["PassengerId"])
最後,將預測結果輸出到excel表中。若是你到Kaggle
將輸出的數據提交,你應該獲得的分數是:0.8188,也就是說你的準確率是0.8188。這個分數能夠達到500/11000
的排名(top5%)。
本篇分析了數據預處理以及建模的部分,完成了最後的生還者預測,有幾下幾點還須要提升的地方:
以上就是本次項目的所有內容,後續會繼續分享新數據分析挖掘項目,敬請期待。
參考: https://www.kaggle.com/franck...
關注微信公衆號:Python數據科學,發現更多精彩內容。