参加了Kaggle的Getting Started Competition体验一下参赛流程。在此记录一下赛题思路和Python实现代码。
赛题原址:Titanic: Machine Learning from Disaster
Kaggle-Getting Started Prediction Competition
Titanic: Machine Learning from Disaster
It is your job to predict if a passenger survived the sinking of the Titanic or not.
For each in the test set, you must predict a 0 or 1 value for the variable.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
csv_data = pd.read_csv("C:\\Users\\rinnki\\Desktop\\train.csv")
df_train=pd.DataFrame(csv_data)#转换成dataframe格式
print(df_train.head)
#瞧瞧训练集里存活和死亡比例再看看缺失值情况
df_train_pclass = df_train.groupby('Pclass')
countF1Sum = df_train_pclass['Survived'].count()
countF1T = pd.DataFrame(countF1Sum)
print(df_train.isnull().sum())
缺失值情况如下:
[891 rows x 12 columns]>
PassengerId 0
Survived 0
Pclass 0
Name 0
Sex 0
Age 177
SibSp 0
Parch 0
Ticket 0
Fare 0
Cabin 687
Embarked 2
dtype: int64
可知Age内有部分缺失,可稍后考虑填充方式。Cabin缺失值较多,可以考虑舍弃该特征或者从其中提取信息使用。Embarked缺失值很少,可以考虑使用众数填充。
接下来再来看看数据中各特征对应的存活情况,写个出图的函数:
def quickBarh(string_name):
df_train1 = df_train.groupby(string_name)
countF1E = df_train1['Survived'].apply(pd.value_counts)
countF1S = countF1E.unstack() #unstack方法救命
#print(countF1S)
countF1S.plot.barh(stacked=True, alpha=0.5) #为了方便直接用pandas的barh方法了
查看一下无需复杂缺失值处理和特征提取的数据分布,如‘Pclass’、‘Sex’、‘SibSp’、‘Parch’和‘Embarked’。
quickBarh('Pclass')
quickBarh('Sex')
quickBarh('SibSp')
quickBarh('Parch')
quickBarh('Embarked')
舱位应该和泰坦尼克号乘客存活有很大关系,可以看到训练集中各舱乘客数3>1>2,但存活比例1>2>3,与电影情节一致,舱位高的客人可能有优先权逃离并且较早获得沉船的消息。
性别应该也和乘客存活有很大关系,虽然训练集中男性乘客较多,但女性存活比例大,有可能是男性遵循传统让女士先上救生艇或是带孩子的女士优先逃离。
看起来是兄弟姐妹越少存活比例越大。
看起来是亲族越少存活比例越高。
登舱人数S>C>Q,存活人数S>C>Q,存活比例C>S>Q。
接下来处理一些需要提取特征或者填充缺失值的特征来观看它们的数据分布。
Name:
姓名乍一看上去对结果没什么影响,但其实里边蕴含着乘客的身份信息,提取这些信息也是feature engineering的一步,就像Kaggle官方给的hint一样:‘We can at least extract the title from the name,reduce them all to Mrs,Miss, Mr and Master.’
def substrings_in_string(big_string, substrings):
for substring in substrings:
if big_string.find(substring) != -1:
return substring
print(big_string)
return np.nan #没有头衔的乘客就返回nan
title_list=['Mrs', 'Mr', 'Master', 'Miss', 'Major', 'Rev',
'Dr', 'Ms', 'Mlle','Col', 'Capt', 'Mme', 'Countess',
'Don', 'Jonkheer'] #这些个就是我们要提取的身份信息了
#新建一列显示乘客头衔身份的特征
df_train['Title']=df_train['Name'].map(lambda x: substrings_in_string(x, title_list))
#replacing all titles with mr, mrs, miss, master--统一拼写/等级同化
def replace_titles(x):
title=x['Title']
if title in ['Don', 'Major', 'Capt', 'Jonkheer', 'Rev', 'Col']:
return 'Mr'
elif title in ['Countess', 'Mme']:
return 'Mrs'
elif title in ['Mlle', 'Ms']:
return 'Miss'
elif title =='Dr':
if x['Sex']=='Male':
return 'Mr'
else:
return 'Mrs'
else:
return title
df_train['Title'] = df_train.apply(replace_titles, axis=1) #更新头衔列
quickBarh('Title')
头衔为Mr.的乘客人数最多,但可以看出Mrs和Miss的存活比例明显高于其他头衔。
年龄:
Age这个特征有较多缺失值,考虑填补年龄的方法会比较好。接下来以如下填补原则进行缺失值填充:统计title为Mrs, Miss, Mr, Master, nan,而Age不为空的乘客的平均年龄,以对应title将年龄补上。
进行完缺失值填充之后,将其分段作图初步查看年龄分布及存活情况。
def replaceNanAge(x):
title = x['Title']
Age = x['Age']
df_train1 = df_train.dropna(subset=['Age'])
#print(df_train1.isnull().sum())
df_train_titleM = df_train1.groupby('Title')['Age'].mean()
#print(df_train_titleM)
df_AgeUse = pd.DataFrame(df_train_titleM)
#print(df_AgeUse)
if Age == np.nan:
if title == 'Mr':
return df_AgeUse['Age'][2]
elif title == 'Mrs':
return df_AgeUse['Age'][3]
elif title == 'Miss':
return df_AgeUse['Age'][1]
elif title =='Master':
return df_AgeUse['Age'][0]
else:
return df_AgeUse['Age']['nan'] #但大家都有头衔
else:
return Age
df_train['Age'] = df_train.apply(replaceNanAge,axis=1)
age_groups = pd.cut(df_train['Age'], bins=5)
df_trainA = df_train.groupby(age_groups)
countF1A = df_trainA['Survived'].apply(pd.value_counts)
countF1AA = countF1A.unstack()
countF1AA.plot.barh(stacked=True, alpha=0.5)
随意分了下组,可以看出青壮年的存活率高,年迈的老者存活率极低,有可能是老人身体不灵活无法登上救生艇,或者老人的身体无法承受冰冷的海水等不到救援船的到来。而儿童或少年的存活率并没有非常高,可以预想:尽管儿童优先登上救生艇,但儿童身体较弱且仅由保姆陪伴登船的幼儿依旧可能被遗弃。
费用:
在此先将费用分段粗略查看分布
fare_groups = pd.cut(df_train['Fare'], bins=[0,20,50,100,np.inf])
df_trainB = df_train.groupby(fare_groups)
countF1B = df_trainB['Survived'].apply(pd.value_counts)
countF1BB = countF1B.unstack()
countF1BB.plot.barh(stacked=True, alpha=0.5)
但考虑到存在团体票和家庭票的情况,仅利用已知的费用数据分组并不能获得很好的分布,稍后再特征工程的处理中可以考虑进行团体票和家庭票的判断,并将船费分摊到个人身上,得到新特征“每个人的船费”。
舱号:
缺失值有687个,不可能修补了,且舱号乱需信息提取。拥有Cabin号的里边有很少的2、3等,基本都是1等舱。本文在此放弃此特征。
提取信息代码如下:
#Turning cabin number into Deck
cabin_list = ['A', 'B', 'C', 'D', 'E', 'F', 'T', 'G', 'Unknown']
df_train['Deck']=df_train['Cabin'].map(lambda x: substrings_in_string(x, cabin_list))
subCabin = df_train.dropna(subset=['Cabin'])
先将官方给的两个文件分别导入,一个是训练集,一个是待预测的预测集。
#读入文件
csv_data1 = pd.read_csv("C:\\Users\\rinnki\\Desktop\\train.csv")
csv_data2 = pd.read_csv("C:\\Users\\rinnki\\Desktop\\test.csv")
df_train=pd.DataFrame(csv_data1)#转换成dataframe格式
df_test=pd.DataFrame(csv_data2)
df_combined = df_train.append(df_test)
PassengerId = df_test['PassengerId']
print(df_train.isnull().sum())
print(df_test.isnull().sum())
#在数据格式转换的时候将训练集和预测集放在一起处理,但是特征选取和训练的时候就分开训练集来。
接下来开始数据的转换与处理,Scikit-learn要求数据均为numeric型,所以需要将非数字型的原始数据转换为数字型numeric。而变量有如下两种:
(1)Qualitative Variable——定性变量,观测的个体只能归属于几种互不相容类别中的一种时,这样的观测数据称为定性变量。
处理方式有dummies()和factorize()。虚拟变量 ( Dummy Variables) 又称虚设变量,用以反映质的属性的一个人工变量,是量化了的质变量,通常取值为0或1,dummies()方法会将定性变量转换为多个虚拟变量,每个类别都用0、1表示。有多个变量出现时,可以使用factorize( )创建一些数字来表示类别变量,这种映射最后只生成一个特征,不像dummies那样生成多个特征。
(2)Quantitative Variable——定量变量:也就是通常所说的连续量,是由测量或计数、统计所得到的量,这些变量具有数值特征,称为定量变量。
处理方式有scaling和binning。scaling即归一化,把数据变成(0,1)或者(1,1)之间的小数,把数据映射到0~1范围之内处理,更加便捷快速,这是因为归一化/标准化后可以加快梯度下降的求解速度,即提升模型的收敛速度。binning即分箱,就是将连续变量离散化。将数据分成几块(几类)进行处理,在将数据bining化后,将数据factorize或者做dummies处理。
在处理数据时,测试集需要与训练集进行相同处理。这里重开了一个文件所以特征处理的部分函数需要再重新定义一下,可能与前文有重复部分,代码如下:
#Part2 特征工程 Feature Engineering
#接下来开始数据预处理和数据形式转换
"""
feature1:pClass(2)
原数据形式为int这里给它label化再dummy就好,毕竟代表类别不能直接用实数。
"""
# 建立PClass Fare Category,分出舱内高低票价
def pclass_fare_category(df, pclass1_mean_fare, pclass2_mean_fare, pclass3_mean_fare):
if df['Pclass'] == 1:
if df['Fare'] <= pclass1_mean_fare:
return 'Pclass1_Low'
else:
return 'Pclass1_High'
elif df['Pclass'] == 2:
if df['Fare'] <= pclass2_mean_fare:
return 'Pclass2_Low'
else:
return 'Pclass2_High'
elif df['Pclass'] == 3:
if df['Fare'] <= pclass3_mean_fare:
return 'Pclass3_Low'
else:
return 'Pclass3_High'
Pclass_mean_fare = df_combined['Fare'].groupby(by=df_combined['Pclass']).mean()#这里的Pclass是int64型的不能unstack()
Pclass1_MF = Pclass_mean_fare.get([1]).values[0]
Pclass2_MF = Pclass_mean_fare.get([2]).values[0]
Pclass3_MF = Pclass_mean_fare.get([3]).values[0]
df_combined['Pclass_Fare_Category'] = df_combined.apply(pclass_fare_category, args=(Pclass1_MF, Pclass2_MF, Pclass3_MF), axis=1)
pclassQ = pd.get_dummies(df_combined['Pclass_Fare_Category'])
df_combined['Pclass1_Low'],df_combined['Pclass1_High'] = pclassQ['Pclass1_Low'],pclassQ['Pclass1_High']
df_combined['Pclass2_Low'],df_combined['Pclass2_High'] = pclassQ['Pclass2_Low'],pclassQ['Pclass2_High']
df_combined['Pclass3_Low'],df_combined['Pclass3_High'] = pclassQ['Pclass3_Low'],pclassQ['Pclass3_High']
df_combined['Pclass'] = pd.factorize(df_combined['Pclass'])[0]
"""
feature2:Sex
"""
sexQ= pd.get_dummies(df_combined['Sex'])
df_combined['Female'],df_combined['Male'] = sexQ['female'],sexQ['male']
df_combined['Sex'] = pd.factorize(df_combined['Sex'])[0]
"""
feature3:Name/Title(1)
dummy处理
"""
def substrings_in_string(big_string, substrings):
for substring in substrings:
if big_string.find(substring) != -1:
return substring
print(big_string)
return np.nan #没有头衔的乘客就返回nan
title_list=['Mrs', 'Mr', 'Master', 'Miss', 'Major', 'Rev',
'Dr', 'Ms', 'Mlle','Col', 'Capt', 'Mme', 'Countess',
'Don', 'Jonkheer'] #这些个就是我们要提取的身份信息了
#新建一列显示乘客头衔身份的特征
df_combined['Title']=df_combined['Name'].map(lambda x: substrings_in_string(x, title_list))
#replacing all titles with mr, mrs, miss, master--统一拼写/等级同化
def replace_titles(x):
title=x['Title']
if title in ['Don', 'Major', 'Capt', 'Jonkheer', 'Rev', 'Col']:
return 'Mr'
elif title in ['Countess', 'Mme']:
return 'Mrs'
elif title in ['Mlle', 'Ms']:
return 'Miss'
elif title =='Dr':
if x['Sex']=='Male':
return 'Mr'
else:
return 'Mrs'
else:
return title
df_combined['Title'] = df_combined.apply(replace_titles, axis=1) #更新头衔列
TitleQ = pd.get_dummies(df_combined['Title'])
df_combined['Mrs'], df_combined['Mr'], df_combined['Miss'], df_combined['Master'] = TitleQ['Mrs'],TitleQ['Mr'],TitleQ['Miss'],TitleQ['Master']
"""
feature4:Age
填补原则:统计title为Mrs, Miss, Mr, Master, nan,而Age不为空的乘客的平均年龄,以对应title将年龄补上。
做归一化处理
"""
def replaceNanAge(x):
title = x['Title']
Age = x['Age']
df_train1 = df_combined.dropna(subset=['Age'])
#print(df_train1.isnull().sum())
df_train_titleM = df_train1.groupby('Title')['Age'].mean()
#print(df_train_titleM)
df_AgeUse = pd.DataFrame(df_train_titleM)
#print(df_AgeUse)
if pd.isnull(Age): #不知道为什么Age.isnull()就不行但是pd就行
if title == 'Mr':
return df_AgeUse['Age'][2]
elif title == 'Mrs':
return df_AgeUse['Age'][3]
elif title == 'Miss':
return df_AgeUse['Age'][1]
elif title =='Master':
return df_AgeUse['Age'][0]
else:
return df_AgeUse['Age']['nan'] #但大家都有头衔
else:
return Age
df_combined['Age'] = df_combined.apply(replaceNanAge,axis=1)
from sklearn import preprocessing
assert np.size(df_combined['Age']) == 1309
# StandardScaler will subtract the mean from each value then scale to the unit variance
scaler = preprocessing.StandardScaler()
df_combined['AgeS'] = scaler.fit_transform(df_combined['Age'].values.reshape(-1, 1))
"""
feature5:FamilySize=SibSp+Parch
"""
#Creating new family_size column
df_combined['Family_Size']=df_combined['SibSp']+df_combined['Parch']
df_combined.drop(['SibSp','Parch'], axis=1,inplace=True)
"""
feature6:Ticket
看看有没有票号一样的,那就是团体票或者家庭票的顾客啦。
----1列
"""
ticket_count = pd.DataFrame(df_combined.groupby('Ticket').count())
#print(ticket_count.columns.values)
def groupCheck(x):
ticket = x['Ticket']
return ticket_count['Age'][ticket]
df_combined['Group_Size'] = df_combined.apply(groupCheck,axis=1)
print(df_combined.head())
print(df_combined.groupby('Group_Size').count())
"""
feature8:Fare
对Fare进行Binning分块离散化处理
"""
#缺失值处理
df_combined['Fare'] = df_combined[['Fare']].fillna(df_combined.groupby('Pclass').transform(np.mean))
def SplitFare(x):
fare = x['Fare']
Family_Size = x['Family_Size']
Group_Size = x['Group_Size']
if Family_Size == 0:
if Group_Size != 0:
return fare/(Group_Size)
else:
return fare #独行者是这样的
else:
return fare/(Family_Size+1)
df_combined['Fare_Per_Person']=df_combined.apply(SplitFare,axis=1)
# Divide all fares into quartiles
df_combined['FareB'] = pd.qcut(df_combined['Fare_Per_Person'], 5) #出现频率,即所有点5等分,画出区间
df_combined['FareBid'] = pd.factorize(df_combined['FareB'])[0] #factorize的第二个元素是数据类型,取第一个元素为类别
fareBQ = pd.get_dummies(df_combined['FareBid']).rename(columns=lambda x: 'Fare_' + str(x))
#print(fareBQ.columns.values)
df_combined['Fare0'],df_combined['Fare1'] = fareBQ['Fare_0'],fareBQ['Fare_1']
df_combined['Fare2'],df_combined['Fare3'],df_combined['Fare4'] = fareBQ['Fare_2'],fareBQ['Fare_3'],fareBQ['Fare_4']
df_combined.drop(['FareB','Fare','FareBid','Fare_Per_Person'], axis=1, inplace=True)
"""
feature10:Embarked
"""
df_combined['Embarked'].fillna(df_combined['Embarked'].mode().iloc[0], inplace=True)
embarkedQ = pd.get_dummies(df_combined['Embarked'])
df_combined['S'], df_combined['C'],df_combined['Q'] = embarkedQ['S'],embarkedQ['C'],embarkedQ['Q']
df_combined.drop(['Embarked'], axis=1,inplace=True)
print(df_combined.head())
至此为止训练用和预测用的Dataframe转换建立完毕。
#整理并备份一下当前df
df_combined_backup = df_combined
df_test0 = df_combined[891:]
PassengerId = df_test0['PassengerId'].values
df_combined.drop(['PassengerId', 'Embarked', 'Sex', 'Name', 'Title', 'FareBid','FareB', 'Pclass_Fare_Category',
'Cabin', 'Age','Ticket','Fare_Per_Person'],axis=1,inplace=True)
#将数据集分为训练集和测试集
df_train1 = df_combined[:891]
df_test1 = df_combined[891:]
trainX = df_train1.drop(['Survived'],axis=1)
trainY = df_train['Survived']
testX = df_test1.drop(['Survived'],axis=1)
x_train = trainX.values # Creates an array of the train data
x_test = testX.values # Creats an array of the test data
y_train = trainY.values
from sklearn.ensemble import RandomForestClassifier
random_forest = RandomForestClassifier(oob_score=True, n_estimators=1000)
random_forest.fit(x_train, y_train)
predictions = random_forest.predict(x_test)
Submission = pd.DataFrame({'PassengerId': PassengerId, 'Survived': predictions})
Submission.to_csv('C:\\Users\\rinnki\\Desktop\\Submission.csv',index=False,sep=',')
这里先非常简单地拿随机森林训练+预测了一下,出了个结果文件,提交到Kaggle看看预测结果,当前得分76.076%。
这个结果很一般,可以看到榜上有许多预测准确率很高的选手,甚至有很多得分为100%的,但本题特征提取和模型建立都十分自由,预测结果的受随机因素的影响也很大。模型还有许多地方可以优化和修改,以后有时间的话会将相关内容补充至本文。
[1]Kaggle_Titanic生存预测_Koala_Tree
[2]机器学习入门——逻辑回归之kaggle泰坦尼克号竞赛
[3]机器学习实战之Kaggle_Titanic预测