1. 程式人生 > >泰坦尼克號資料探勘專案實戰——Task2 特徵工程

泰坦尼克號資料探勘專案實戰——Task2 特徵工程

參考:【1】https://www.cnblogs.com/wxquare/p/5484636.html

           【2】https://blog.csdn.net/weishiym/article/details/79629329

           【3】https://blog.csdn.net/zhouhong0284/article/details/79489479

           【4】https://blog.csdn.net/kt513226724/article/details/79843073

           【5】https://blog.csdn.net/kt513226724/article/details/79844950

在task1中,我們簡單對資料進行了分析,選擇出了Age,Embarked,Fare, Parch, Pclass, Sex, SibSp 共7種特徵,由於這裡使用了one-hot 編碼,所以實際得到的特徵維度包括:Age, Embarked=C, Embarked=Q, Embarked=S, Fare, Parch, Pclass, Sex=female, Sex=male, SibSp 共10個,經過對缺失值的填充,得到的訓練集特徵矩陣為891*10,測試集的特徵矩陣為418*10.

   那麼,這些特徵都對分類起到積極作用嗎?我們需要對特徵進行挑選或者重新組合,形成新的特徵。即,需要進行特徵工程。

    “資料決定了機器學習的上限,而演算法只是儘可能逼近這個上限”,這裡的資料指的就是經過特徵工程得到的資料。特徵工程指的是把原始資料轉變為模型的訓練資料的過程,它的目的就是獲取更好的訓練資料特徵,使得機器學習模型逼近這個上限。特徵工程能使得模型的效能得到提升,有時甚至在簡單的模型上也能取得不錯的效果。特徵工程在機器學習中佔有非常重要的作用,一般認為括特徵構建、特徵提取、特徵選擇三個部分。特徵構建比較麻煩,需要一定的經驗。 特徵提取與特徵選擇都是為了從原始特徵中找出最有效的特徵。它們之間的區別是特徵提取強調通過特徵轉換

的方式得到一組具有明顯物理或統計意義的特徵;而特徵選擇是從特徵集合中挑選一組具有明顯物理或統計意義的特徵子集。兩者都能幫助減少特徵的維度、資料冗餘,特徵提取有時能發現更有意義的特徵屬性,特徵選擇的過程經常能表示出每個特徵的重要性對於模型構建的重要性【1】。

   特徵工程包括特徵清洗和預處理,task1 中已經進行了缺失值的填補,完成了資料清洗的部分,接下來要進行預處理部分。
   感謝002同學對task1的點評,我決定採用視覺化探索特徵,主要參考【4】。

#忽略警告提示
import warnings
warnings.filterwarnings('ignore')
#資料處理
import pandas as pd
import numpy as np
import random
#視覺化
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

對特徵與是否被救求相關係數,並畫heatmap 得到較為直觀的分析。

## 資料讀入
path='C:/Users/Titanic/'
p1=open(path+'train.csv')
p2=open(path+'test.csv')
train=pd.read_csv(p1)
test=pd.read_csv(p2)

## 資料概覽
train.head(3)
train.info()
train.describe()

## 求相關係數
train.drop('PassengerId',axis=1).corr()

## heatmap 作圖
sns.set(context='paper',font='monospace')
sns.set(style='white')
f,ax=plt.subplots(figsize=(10,6))
train_corr=train.drop('PassengerId',axis=1).corr()
sns.heatmap(train_corr,ax=ax,vmax=.9,square=True,cmap=plt.cm.get_cmap('RdYlBu'))
ax.set_xticklabels(train_corr.index,size=15)
ax.set_yticklabels(train_corr.columns,size=15)
ax.set_title('train feature corr',fontsize=20)
 

    由上圖可以看出,Pclass與獲救情況負相關,Fare和獲救情況正相關。

  我們單獨看一下Pclass與獲救情況。

Pclass 與獲救情況:

y_dead=train[train.Survived==0].groupby('Pclass')['Survived'].count()
y_alive=train[train.Survived==1].groupby('Pclass')['Survived'].count()
pos=[1,2,3]
ax=plt.figure(figsize=(8,4)).add_subplot(111)
ax.bar(pos,y_dead,color='r',alpha=.6,label='dead')
ax.bar(pos,y_alive,color='g',bottom=y_dead,alpha=.6,label='alive')
ax.legend(fontsize=16,loc='best')
ax.set_xticks(pos)
ax.set_xticklabels(['Pclass%d'%(i) for i in range(1,4)],size=15)
ax.set_title('Pclass Survived count',size=20)
 

從總數來看,三等艙存活人數最多,從存活比例來看,頭等艙存活人數最多,三等艙死亡率最高。

再對三個船艙和年齡一起來看:

pos=range(0,6)
age_list=[]
for Pclass_ in range(1,4):
    for Survived_ in range(0,2):
        age_list.append(train[(train.Pclass==Pclass_)&(train.Survived==Survived_)].Age.values)

fig,axes=plt.subplots(3,1,figsize=(10,6))

i_Pclass=1
for ax in axes:
    sns.distplot(age_list[i_Pclass*2-2],hist=False,ax=ax,label='Pclass:%d ,survived:0'%(i_Pclass),color='r')
    sns.distplot(age_list[i_Pclass*2-1],hist=False,ax=ax,label='Pclass:%d ,survived:1'%(i_Pclass),color='g')
    i_Pclass +=1
    ax.set_xlabel('age',size=15)
    ax.legend(fontsize=15)
 

可以看出,一等、二等艙的小孩被保護較好,而三等艙有部分小孩也死亡了。

以上是簡單的做了一個視覺化分析,接下來繼續對資料處理。
task1 中對資料的填充採用了最簡單的方法,這裡將採用更加合理一些的方法,結合乘客的性別、艙位、頭銜。
 

#將資料按照性別,艙位,頭銜分組,並計算中位數
grouped_train=combined.head(891).groupby(['Sex','Pclass','Title'])
grouped_median_train=grouped_train.median()
grouped_test=combined.iloc[891:].groupby(['Sex','Pclass','Title'])
grouped_median_test=grouped_test.median()12345

grouped_median_train

grouped_median_test

通過觀察,認為可以通過乘客性別、稱謂、艙等級的不同來對年齡進行填充。

def fillAges(row,grouped_median):
    if row['Sex']=='female' and row['Pclass']==1:
        if row['Title']=='Miss':
            return grouped_median.loc['female',1,'Miss']['Age']
        elif row['Title']=='Mrs':
            return grouped_median.loc['female',1,'Mrs']['Age']
        elif row['Title'] == 'Officer':
                return grouped_median.loc['female', 1, 'Officer']['Age']
        elif row['Title'] == 'Royalty':
                return grouped_median.loc['female', 1, 'Royalty']['Age']

    elif row['Sex']=='female' and row['Pclass'] == 2:
        if row['Title'] == 'Miss':
            return grouped_median.loc['female', 2, 'Miss']['Age']
        elif row['Title'] == 'Mrs':
            return grouped_median.loc['female', 2, 'Mrs']['Age']

    elif row['Sex']=='female' and row['Pclass'] == 3:
        if row['Title'] == 'Miss':
            return grouped_median.loc['female', 3, 'Miss']['Age']
        elif row['Title'] == 'Mrs':
            return grouped_median.loc['female', 3, 'Mrs']['Age']

    elif row['Sex']=='male' and row['Pclass'] == 1:
        if row['Title'] == 'Master':
            return grouped_median.loc['male', 1, 'Master']['Age']
        elif row['Title'] == 'Mr':
            return grouped_median.loc['male', 1, 'Mr']['Age']
        elif row['Title'] == 'Officer':
            return grouped_median.loc['male', 1, 'Officer']['Age']
        elif row['Title'] == 'Royalty':
            return grouped_median.loc['male', 1, 'Royalty']['Age']

    elif row['Sex']=='male' and row['Pclass'] == 2:
        if row['Title'] == 'Master':
            return grouped_median.loc['male', 2, 'Master']['Age']
        elif row['Title'] == 'Mr':
            return grouped_median.loc['male', 2, 'Mr']['Age']
        elif row['Title'] == 'Officer':
            return grouped_median.loc['male', 2, 'Officer']['Age']

    elif row['Sex']=='male' and row['Pclass'] == 3:
        if row['Title'] == 'Master':
            return grouped_median.loc['male', 3, 'Master']['Age']
        elif row['Title'] == 'Mr':
            return grouped_median.loc['male', 3, 'Mr']['Age']

combined.head(891).Age=combined.head(891).apply(lambda r: fillAges(r,grouped_median_train) if 
                                               np.isnan(r['Age']) else r['Age'],axis=1)
combined.iloc[891:].Age=combined.iloc[891:].apply(lambda r: fillAges(r,grouped_median_test) if
                                                 np.isnan(r['Age']) else r['Age'],axis=1)
combined.info()    

combined.head(891).Fare.fillna(combined.head(891).Fare.mean(),inplace=True)
combined.iloc[891:].Fare.fillna(combined.iloc[891:].Fare.mean(),inplace=True)

填充缺失Embarked為登船地點最多的S

combined.head(891).Embarked.fillna('S', inplace=True)
combined.iloc[891:].Embarked.fillna('S', inplace=True)

填補Cabin

combined.Cabin.fillna('U', inplace=True)
combined['Cabin'] = combined['Cabin'].map(lambda c : c[0])
combined.info()    

#title虛擬變數編碼
titleDf=pd.get_dummies(combined['Title'],prefix='Title')
combined=pd.concat([combined,titleDf],axis=1)

 

 

把家人數目和兄弟姐妹數目組合起來,建立family

# Parch&SibSp ,建立Familysize
familyDf=pd.DataFrame()
familyDf['FamilySize']=combined['Parch']+combined['SibSp']+1
familyDf[ 'Family_Single' ] = familyDf[ 'FamilySize' ].map( lambda s : 1 if s == 1 else 0 )
familyDf[ 'Family_Small' ]  = familyDf[ 'FamilySize' ].map( lambda s : 1 if 2 <= s <= 4 else 0 )
familyDf[ 'Family_Large' ]  = familyDf[ 'FamilySize' ].map( lambda s : 1 if 5 <= s else 0 )
combined=pd.concat([combined,familyDf],axis=1)
 

embarked

embarkedDf=pd.get_dummies(combined['Embarked'],prefix='Embarked')
combined=pd.concat([combined,embarkedDf],axis=1)

Sex

sex_mapDict={'male':1,
            'female':0}
#map函式:對Series每個資料應用自定義的函式計算
combined['Sex']=combined['Sex'].map(sex_mapDict)

Cabin

cabinDf=pd.get_dummies(combined['Cabin'],prefix='Cabin')
combined=pd.concat([combined,cabinDf],axis=1)

Pclass

pclassDf=pd.get_dummies(combined['Pclass'],prefix='Pclass')
combined=pd.concat([combined,pclassDf],axis=1)

Ticket

#提取票價字首,如果沒有字首,即票價為數字返回XXX
def cleanTicket(ticket):
    ticket=ticket.replace('.','')
    ticket=ticket.replace('/','')
    ticket=ticket.split()
    #ticket=map(lambda t: t.strip(),ticket)
    #flag=filter(lambda t: not t.isdigit(),ticket)
    if ticket[0].isdigit():
        return 'XXX'
    else:
        return ticket[0]

combined['Ticket']=combined['Ticket'].map(cleanTicket)
ticketsDf=pd.get_dummies(combined['Ticket'],prefix='Ticket')
combined=pd.concat([combined,ticketsDf],axis=1)
 

接下來看下目前的特徵維度:

combined.head(3)

列數為75。

#將其餘無關特徵刪除
combined.drop(['PassengerId','Cabin','Embarked','Name','Pclass','Ticket','Title'], inplace=True, axis=1)
combined.head(3)

現在列數為68。

接下來就是如何對特徵挑選了,特徵挑選前,需要把之前train和test分開。

#得到訓練/測試資料
train_X=combined.iloc[:891,:].drop(['Survived'],axis=1)
target_Y=combined.iloc[:891,:]['Survived']
test_X=combined.iloc[891:,:].drop(['Survived'],axis=1)
print('訓練集特徵:',train_X.shape,
     '訓練集標籤:',target_Y.shape,
     '測試集特徵:',test_X.shape)
 
#訓練集特徵: (891, 67) 訓練集標籤: (891,) 測試集特徵: (418, 67)

#匯入庫
from sklearn.pipeline import make_pipeline
from sklearn.ensemble import RandomForestClassifier
from sklearn.feature_selection import SelectKBest
from sklearn.cross_validation import StratifiedKFold
from sklearn.grid_search import GridSearchCV
from sklearn.ensemble.gradient_boosting import GradientBoostingClassifier
from sklearn.cross_validation import cross_val_score
 
#定義評價函式
def compute_score(clf,X,y,scoring='accuracy'):
    xval=cross_val_score(clf,X,y,cv=5,scoring=scoring)#K折交叉分類,cv資料分成的數量
    return np.mean(xval)
 

 

 

接下來是特徵挑選,一個好的特徵選擇可以: 1.減少資料之間的冗餘  2.加速訓練過程  3.防止過擬合。

這裡採用隨機森林來挑選特徵。

#採用隨機森林來計算特徵輸入
from sklearn.ensemble import RandomForestClassifier
from sklearn.feature_selection import SelectFromModel
#n_estimators構造的決策樹數量,max_features不超過的最大特徵數量
clf=RandomForestClassifier(n_estimators=50,max_features='sqrt')
clf=clf.fit(train_X,target_Y)
 
features=pd.DataFrame()
features['feature']=train_X.columns
features['importance']=clf.feature_importances_ #係數大小反應特徵重要性
features.sort_values(by=['importance'],ascending=True,inplace=True)
features.set_index('feature',inplace=True)
 
features.plot(kind='barh',figsize=(20,20))

#選取合適的特徵
model=SelectFromModel(clf,prefit=True)
train_reduced=model.transform(train_X)
train_reduced.shape

test_reduced=model.transform(test_X)
test_reduced.shape

由此,通過隨機森林,我們得到了12個特徵。