參照kaggle來學習Python資料分析的思路和方法:https://www.kaggle.com/startupsci/titanic-data-science-solutions
簡書上這個寫的也蠻有趣可以看一下https://www.jianshu.com/p/9a5bce0de13f
完成整個項目後對如何認識資料、清洗資料有了更具體的認識。資料處理過程中有兩點習慣需要養成::盡量備份原資料,在對資料進行操作處理後及時輸出檢視資料是否得到想要的結果。
資料認識
導入需要的包
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import os
import warnings
warnings.filterwarnings('ignore')
plt.rcParams["patch.force_edgecolor"] = True#邊緣線
導入資料,讀取head來看一下資料的格式
#讀取資料
os.chdir('D:\\資料分析\\微專業\\kaggle\\titanic\\')
train_data=pd.read_csv('train.csv')
test_data=pd.read_csv('test.csv')
print(train_data.columns.values)
head=train_data.head()
觀察資料的格式
categorical:某些資料可以将樣本資料分類,進而選擇後續合适的可視化圖。可以發現Survived, Sex, Embarked、Pclass都是代表分類的變量。
Numerical:是否存在數值類的資料,如離散、連續、時間序列等。連續資料 Age, Fare. 離散資料SibSp(
Number of Siblings/Spouses Aboard,兄弟姐妹/配偶登船), Parch(Number of Parents/Children Aboard 父母/子女登船)
mixed data types:Ticke、Cabint是字母+數字的格式
train_data.info()
一共891條訓練資料
Age\Cabin\Embarked資料存在資料缺失
train_d=train_data.describe()
* Passengerid作為唯一辨別,共891條資料
* 因Survived僅有0辨別去世,1辨別存活,mean均值0.38說明38%的存活率
* Age中上到80下至嬰兒0.42,均值為29.7,Age-75%說明有75%的乘客小于38歲。
* Parch %75=0 超過75%的樣本沒有和父母/子女登船
* SibSp %50=0 %75=1 超過%50的樣本沒有兄弟姐妹/配偶登船(
Nearly 30% of the passengers had siblings and/or spouse aboard. 原文可能是估計?)
* 原文這兩條不知道是怎麼從describe中解讀的
Fares varied significantly with few passengers (<1%) paying as high as $512.
Few elderly passengers (<1%) within age range 65-80.
train_d2=train_data.describe(include='O')
describe預設是隻計算數值型特征的統計量,輸入參數include=['O'],describe可以計算離散型變量的統計特征,得到總數、唯一值個數、出現最多的資料和出現頻數。
* 姓名是唯一變量
* 男性多于女性,男性占比577/891=65%
* Cabin房間号是重複使用的,多個人共享一個房間
* Ticket不是唯一編号,存在多人同一Ticket的情況
* Embarked登陸港口共3個,S最多
基于資料分析的假設
分析各資料和存活的關聯
可能無分析意義的資料:
* Ticket資料重複率過高,不作為特征
* Cabin缺失過度,不作為特征
* Passengerid作為唯一辨別沒有作為分類的意義
* Name因為格式不标準可能無關不作為分析特征(看到過部落格提取title如Mr,Ms作為分析)
資料處理:
* 填充Age,Embarked特征
* 基于Parch和SibSp建立新的資料Family标志船上所有家庭成員數目
* 姓名中提取title作為新的特征
* 可以将Age參數進行分類,轉換為多個類别
* 建立Fare特征,可能有助于分析
假設:
* Sex中female女性可能存活率更高
* 兒童(需要設定Age的範圍)可能存活率更高
* 一等艙(Pclass=1)可能存活率更高
粗略判斷分類特征Pclass\Sex\SibSp and Parch和survived的關系
Pclass和sex明顯和存活率相關
資料可視化
#複制了一個新的資料來用均值填充一下Age, 檢視Age的分布
#真實分布應該去除空值而非填充train_data_age=train_data[train_data['Age'].notnull()]
train_data_age=train_data
train_data_age['Age'].fillna(train_data['Age'].mean()).astype(np.int)
#年齡分布
fig,axes=plt.subplots(1,2,figsize=(18,8))
train_data_age['Age'].hist(bins=80,ax=axes[0])
train_data_age.boxplot(column='Age',showfliers=False,ax=axes[1])
年齡和存活情況的關系
觀察:
年齡較小存活較高
年級最大80歲存活
15-25歲中的大部分未存活
年齡分布在15-35歲乘客較多
結論:
在訓練模型中考慮Age特征
完整Age特征
設定Age特征分組
pclass_survived=sns.FacetGrid(train_data,col='Survived',row='Pclass')
pclass_survived.map(plt.hist,'Age')
Age, Pclass, Survived
觀測:
Age pclass和存活
Pclass=3乘客最多但幸存者并不多,Pclass和Survive相關,驗證假設1
Pclass=2和Pclass=3中,年齡較小的乘客存活較多,驗證假設2
各年齡乘客分布在不同的Pclass
結論:訓練模型中應考慮Pclass
sns.pointplot(x='Pclass',y='Survived',data=train_data,hue='Sex',
dodge=True,join=True,markers=['o','x'],linestyle=['--','-'])#??linestyle顯示不出
觀察到不同Pclass中女性明顯比男性存活率高,性别是分類的有效特征
grid=sns.FacetGrid(train_data,col='Embarked',size=3,aspect=1.5)
grid.map(sns.pointplot,'Pclass','Survived','Sex')
#plt.legend()
#grid.add_legend() 多圖示注圖例
for i in range(0,3):
grid.axes[0][i].legend()
關聯特征Embarked Pclass Sex
Observations.
女性明顯比男性存活率高
觀察到S和Q的Embarked中,女性生存率高于男性,Embarked=C男性比女性存活率高,可能是Embarked和的關聯pclass轉而影響survived,而非直接關聯
Embarked=C Embarked=Q中,Pclass=3的男性存活率高于Pcalss=2
Embarked不同的Pclass=3的男性存活率存在較大差異
Decisions.
增加性别特征
完善并增加embarked特征
grid=sns.FacetGrid(train_data,row='Embarked',col='Survived',aspect=1.6)
grid.map(sns.barplot,'Sex','Fare',ci=None)#barplot預設用平均值估計 ci置信區間
grid.add_legend()
correlating Embarked (Categorical non-numeric), Sex (Categorical non-numeric), Fare (Numeric continuous), with Survived (Categorical numeric)
barplot按照分類,用estimator方法(預設平均值)計算相應的值。
觀察:
對比左右兩列,Embarked=S/C中,存活的人船票平均值較高
Embarked=Q的票價都較低,可能關聯存活率也較低
Embarked=C幸存者的票價明顯高于其他。
結論:
考慮劃分船票價格區間
Wrangle data(資料清洗)
去除無用資訊
去除無用的ticket cabin的資訊
#去除ticket cabin這兩列
print("Before", train_data.shape, test_data.shape)
train_data = train_data.drop(['Ticket', 'Cabin'], axis=1)
test_data = test_data.drop(['Ticket', 'Cabin'], axis=1)
combine = [train_data, test_data]
print("After", train_data.shape, test_data.shape, combine[0].shape, combine[1].shape)
Name中提取Title(社會地位)
對Name進行特征提取,從中提取出頭銜
#從name中提取title 如Mr. Mrs.等
for dataset in combine:
dataset['Title'] = dataset.Name.str.extract(' ([A-Za-z]+)\.', expand=False)
#crosstab交叉表 按照Title分組,統計各分組中'Sex'的頻數
pd.crosstab(train_data['Title'], train_data['Sex'])
crosstab交叉表得到的結果
可以發現Master、Miss、Mr、Mrs死者較多,其他較少,是以将較少的稱謂替換成Rare,将同義詞進行替換如Mlle替換成Miss。
#将同義的不同寫法進行替換,将較少及未知意義替換成Rare
for dataset in combine:
dataset['Title'].replace(['Capt','Col','Countess','Don','Dr','Master','Jonkheer','Lady','Major','Rev',
'Sir'],'Rare',inplace=True)
dataset['Title'].replace('Mlle', 'Miss',inplace=True)
dataset['Title'].replace('Ms', 'Miss',inplace=True)
dataset['Title'].replace('Mme', 'Mrs',inplace=True)
train_data[['Survived','Title']].groupby('Title',as_index=False).mean()
可以發現不同稱謂的存活率相差較大,特别是Miss、Mrs比Mr明顯要高,佐證了性别對生存率的影響。
由于文本無法用作訓練特征,将文本通過map映射到數字,以數字作為訓練特征
#将文字map投影到數字,便于作為特征訓練 空值投影為0
title_mapping={'Mr':1,'Miss':2,'Mrs':3,'Master':4,'Rare':5}
for dataset in combine:
dataset['Title']=dataset['Title'].map(title_mapping)
dataset['Title']=dataset['Title'].fillna(0)
train_data[['Survived','Title']].groupby('Title',as_index=False).mean()
#删除id和name 如果不重新指向,就inplace=True
train_data=train_data.drop(['PassengerId','Name'],axis=1)
test_data.drop(['Name'],axis=1,inplace=True)#Id作為測試集送出需使用
Age填充(連續數字屬性離散化)
方法一:在均值和标準差的範圍中生成随機數(最簡單)
方法二:根據關聯特征填補缺失值,Age Gender Pclass三者相關,根據Pclass和Gender的分類,用均值填充
方法三:基于Pclass和Gender,用均值和标準差範圍内的随機數進行填充
方法一和三使用随機數會引入随機噪聲,采用方法二
grid=sns.FacetGrid(train_data,col='Sex',row='Pclass',aspect=1.5)
grid.map(plt.hist,'Age',bins=20)
grid.add_legend()
#按照Sex Pclass分組求各組中位數
guess_ages=np.zeros((2,3))#注意是兩個括号
for dataset in combine:#第一個dataset是train的df,第二個是test的df
for i in range(0, 2):
for j in range(0, 3):
guess_df = dataset[(dataset['Sex'] == i) & \
(dataset['Pclass'] == j+1)]['Age'].dropna()
# age_mean = guess_df.mean()
# age_std = guess_df.std()
# age_guess = rnd.uniform(age_mean - age_std, age_mean + age_std)
#age_guess = guess_df.median()
# Convert random age float to nearest .5 age
#guess_ages[i,j] = int( age_guess/0.5 + 0.5 ) * 0.5
guess_ages[i,j]=guess_df.median()
#按篩選條件填充空值
for i in range(0, 2):
for j in range(0, 3):
dataset.loc[ (dataset.Age.isnull()) & (dataset.Sex == i) & (dataset.Pclass == j+1),\
'Age'] = guess_ages[i,j]
dataset['Age'] = dataset['Age'].astype(int)
#利用cut連續屬性離散化,增加輔助列AgeBand,将Age範圍均勻分成5份
train_data['AgeBand']=pd.cut(train_data['Age'],5)
train_data[['AgeBand','Survived']].groupby('AgeBand').mean().sort_values(by='AgeBand',ascending=True)
train_data['AgeBand'].value_counts()
#按照cut的分類對Age進行離散化
for dataset in combine:
dataset.loc[ dataset['Age'] <= 16, 'Age'] = 0
dataset.loc[(dataset['Age'] > 16) & (dataset['Age'] <= 32), 'Age'] = 1
dataset.loc[(dataset['Age'] > 32) & (dataset['Age'] <= 48), 'Age'] = 2
dataset.loc[(dataset['Age'] > 48) & (dataset['Age'] <= 64), 'Age'] = 3
dataset.loc[ dataset['Age'] > 64, 'Age']=4
#移除輔助列
train_data = train_data.drop(['AgeBand'], axis=1)
combine = [train_data, test_data]#如果沒有這條語句,combine不能被更新?
train_data.head()
可以看出低齡組存活率比其他年齡大,這個地方我不太懂combine和train_data,test_data的關系,combine中的元素指向兩個df的位址?是以更改combine就可以直接更新兩個df?但為什麼drop AgeBand以後,如果不重新指派則combine中的traindata沒有變化。【要補課!!!】
'''
Age*Class
假設越年輕生存率越高+船艙等級越高生存率越高
創造聯合特征(個人認為這隻能說明都高很難活下來,但年齡最低劃分為0無法反映不同Pclass)
純屬按照kernel走一下流程
'''
for dataset in combine:
dataset['Age*Class']=dataset['Age']*dataset['Pclass']
sns.barplot(x='Age*Class',y='Survived',data=train_data,ci=False)
IsAlone參數:SibSp Parch FamilySize
'''
SibSp Parch
a:有無兄弟姐妹/父母子女對存活率的影響
b:親戚多少與存活率
'''
#篩選有無的資料
sibsp_df=train_data[train_data['SibSp']!=0]
no_sibsp_df=train_data[train_data['SibSp']==0]
parch_df=train_data[train_data['Parch']!=0]
no_parch_df=train_data[train_data['Parch']==0]
plt.figure(figsize=(12,3))
plt.subplot(141)
plt.axis('equal')
plt.title('sibsp')
sibsp_df['Survived'].value_counts().plot.pie(labels=['No Survived','Survived'],
autopct='%1.1f%%',colormap='Blues')
plt.subplot(142)
plt.axis('equal')
plt.title('no_sibsp')
no_sibsp_df['Survived'].value_counts().plot.pie(labels=['No Survived','Survived'],
autopct='%1.1f%%',colormap='Blues')
plt.subplot(143)
plt.axis('equal')
plt.title('parch')
parch_df['Survived'].value_counts().plot.pie(labels=['No Survived','Survived'],
autopct='%1.1f%%',colormap='Reds')
plt.subplot(144)
plt.axis('equal')
plt.title('no_parch')
no_parch_df['Survived'].value_counts().plot.pie(labels=['No Survived','Survived'],
autopct='%1.1f%%',colormap='Reds')
#Parch和SibSp人數多少和存活率的關系
fig,ax=plt.subplots(1,2,figsize=(13,4))
train_data[['Parch','Survived']].groupby('Parch').mean().plot.bar(ax=ax[0],rot=0)
train_data[['SibSp','Survived']].groupby('SibSp').mean().plot.bar(ax=ax[1],rot=0)
#總數和存活率的關系
#利用Parch和SibSp創造新的參數FamilySize +1是因為計算本人
for dataset in combine:
dataset['FamilySize']=dataset['Parch']+dataset['SibSp']+1
train_data[['FamilySize','Survived']].groupby('FamilySize').mean().plot.bar(rot=0)
整體呈現先增後減的趨勢
#根據FamilySize建立新的列——IsAlone判斷是否一個人
for dataset in combine:
dataset['IsAlone']=0
dataset.loc[dataset['FamilySize']==1,'IsAlone']=1#将總數為1的定義為IsAlone=1
sns.barplot(x='IsAlone',y='Survived',data=train_data,ci=False)
#使用IsAlone代替FamilySize,删除無用的列
train_data=train_data.drop(['Parch','SibSp','FamilySize'],axis=1)
test_data=test_data.drop(['Parch','SibSp','FamilySize'],axis=1)
combine=[train_data,test_data]
IsAlone=1意味着獨身一人上傳,存活率明顯較低。
Embarked參數
'''
Embarked 用衆數mode填充
'''
print(train_data['Embarked'].unique())
print(train_data['Embarked'].value_counts())
mode_embarked=train_data['Embarked'].mode()
for dataset in combine:
#mode得到的是list,用索引取value
dataset['Embarked'].fillna(mode_embarked[0],inplace=True)
#sns.barplot(x='Embarked',y='Survived',hue='Pclass',data=train_data,ci=False)
train_data[['Embarked','Survived']].groupby('Embarked').mean().plot.bar(rot=0)
#标簽特征離散化
#注意下面是錯誤的寫法,要注意指派啊!
#for dataset in combine:
# dataset['Embarked'].map({'S': 0, 'C': 1, 'Q': 2}).astype(int)
for dataset in combine:
dataset['Embarked'] = dataset['Embarked'].map( {'S': 0, 'C': 1, 'Q': 2} ).astype(int)
train_data.head()
猜測Embarked口岸不同,可能位置不同進而影響生存率,是以填充很重要,選擇用衆數進行填充。
Fare填充,連續數字屬性離散化
'''
Fare用中位數填充
'''
#中位數填充
for dataset in combine:
dataset['Fare'].fillna(dataset['Fare'].median(),inplace=True)
train_data['FareBand']=pd.qcut(train_data['Fare'],4)#等頻劃分,盡量每個部分頻數相同
train_data[['FareBand','Survived']].groupby(['FareBand'],as_index=False).mean().sort_values(by='FareBand')
#連續數字屬性離散化
for dataset in combine:
dataset.loc[ dataset['Fare'] <= 7.91, 'Fare'] = 0
dataset.loc[(dataset['Fare'] > 7.91) & (dataset['Fare'] <= 14.454), 'Fare'] = 1
dataset.loc[(dataset['Fare'] > 14.454) & (dataset['Fare'] <= 31), 'Fare'] = 2
dataset.loc[ dataset['Fare'] > 31, 'Fare'] = 3
dataset['Fare'] = dataset['Fare'].astype(int)
train_data = train_data.drop(['FareBand'], axis=1)
combine = [train_data, test_data]
和Age的處理相似,這裡用到qcut是按照等頻數劃分區間(四分位數),而age的cut是按照等寬劃分。
突然發現對test的劃分處理是按照train的資料劃分進行的,是以test中沒有輔助列也不需要删除輔助列。
訓練和測試
目标是一個分類和回歸的問題,想要得到Survived和其他變量之間的關系。
已有資料是有标簽的,是以是監督學習。
适用于:(居然每個名字都知道是什麼但隻會最簡單的幾個= =)
Logistic Regression
KNN or k-Nearest Neighbors
Support Vector Machines
Naive Bayes classifier
Decision Tree
Random Forrest
Perceptron
Artificial neural network
RVM or Relevance Vector Machine
#準備資料 将特征和标簽分開
X_train=train_data.drop('Survived',axis=1)
Y_train=train_data['Survived']
X_test=test_data.drop('PassengerId',axis=1).copy()
#Logistic Regression邏輯回歸
from sklearn.linear_model.logistic import LogisticRegression
logreg = LogisticRegression()
logreg=LogisticRegression()
logreg.fit(X_train,Y_train)#資料和标簽
Y_pred=logreg.predict(X_test)
acc_log= round(logreg.score(X_train,Y_train)*100,2)
print('LogisticRegression',acc_log)#LogisticRegression 81.26
#Logistic Regression看各特征貢獻率
coeff_df = pd.DataFrame(train_data.columns.delete(0))#delete(0)去除Survived标簽
coeff_df.columns=['Feature']#自定義列名
coeff_df['Correlation']=pd.Series(logreg.coef_[0])#讀取系數
coeff_df.sort_values(by='Correlation',ascending=False)
Positive coefficients increase the log-odds of the response (and thus increase the probability), and negative coefficients decrease the log-odds of the response (and thus decrease the probability).
Sex(male: 0 to female: 1)是最大的正數,Sex增加(即=1 female)最可能增加Survived=1的可能性。Title第二大的正數(這種情況是否在做離散化的時候,指派也應該具有邏輯性?)
Pclass是最大的負數,Pclass越大,Survived=1的可能性降低。Age*Class作者結果中是第二大的負數,不知道為什麼這個地方有比較大的差别。
#SVM
from sklearn import svm
svc=svm.SVC()
svc.fit(X_train,Y_train)
Y_pred=svc.predict(X_test)
acc_svc=round(svc.score(X_train,Y_train)*100,2)
acc_svc#83.5
# Linear SVC
linear_svc = svm.LinearSVC()
linear_svc.fit(X_train, Y_train)
Y_pred = linear_svc.predict(X_test)
acc_linear_svc = round(linear_svc.score(X_train, Y_train) * 100, 2)
acc_linear_svc#79.46
#KNN
from sklearn import neighbors
knn=neighbors.KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train,Y_train)
Y_pred=knn.predict(X_test)
acc_knn=round(knn.score(X_train,Y_train)*100,2)
acc_knn# 83.73
# Gaussian Naive Bayes
from sklearn.naive_bayes import GaussianNB
gaussian =GaussianNB()
gaussian.fit(X_train, Y_train)
Y_pred = gaussian.predict(X_test)
acc_gaussian = round(gaussian.score(X_train, Y_train) * 100, 2)
acc_gaussian#76.88
# Perceptron
from sklearn import linear_model
perceptron = linear_model.Perceptron()
perceptron.fit(X_train, Y_train)
Y_pred = perceptron.predict(X_test)
acc_perceptron = round(perceptron.score(X_train, Y_train) * 100, 2)
acc_perceptron#76.66
# Stochastic Gradient Descent
sgd = linear_model.SGDClassifier()
sgd.fit(X_train, Y_train)
Y_pred = sgd.predict(X_test)
acc_sgd = round(sgd.score(X_train, Y_train) * 100, 2)
acc_sgd#77,33
# Decision Tree
from sklearn.tree import DecisionTreeClassifier
decision_tree = DecisionTreeClassifier()
decision_tree.fit(X_train, Y_train)
Y_pred = decision_tree.predict(X_test)
acc_decision_tree = round(decision_tree.score(X_train, Y_train) * 100, 2)
acc_decision_tree#86.64
# Random Forest
from sklearn.ensemble import RandomForestClassifier
random_forest = RandomForestClassifier(n_estimators=100)
random_forest.fit(X_train, Y_train)
Y_pred = random_forest.predict(X_test)
random_forest.score(X_train, Y_train)
acc_random_forest = round(random_forest.score(X_train, Y_train) * 100, 2)
acc_random_forest#86.64
#對不同算法的性能進行排序
models = pd.DataFrame({
'Model': ['Support Vector Machines', 'KNN', 'Logistic Regression',
'Random Forest', 'Naive Bayes', 'Perceptron',
'Stochastic Gradient Decent', 'Linear SVC',
'Decision Tree'],
'Score': [acc_svc, acc_knn, acc_log,
acc_random_forest, acc_gaussian, acc_perceptron,
acc_sgd, acc_linear_svc, acc_decision_tree]})
models.sort_values(by='Score', ascending=False)
#按照格式導出送出文檔
submission = pd.DataFrame({
"PassengerId": test_data["PassengerId"],
"Survived": Y_pred
})
submission.to_csv('submission.csv', index=False)