实现思路
-
数据加载与合并:
- 使用
pandas
读取训练集和测试集数据,然后合并,这便于对整体数据执行一致的预处理和分析。
- 使用
-
数据探索与可视化:
- 利用
pandas
进行基础数据探索(如检查缺失值、数据类型、基本统计描述)。 - 使用
matplotlib
和seaborn
进行数据可视化,探索不同特征与目标变量(生存率)之间的关系。
- 利用
-
特征工程:
- 基于探索分析的结果,进行特征选择和转换,例如转换类别数据、处理缺失值、创建新的交互特征等。
-
模型构建与评估:
- 选择多种机器学习模型进行训练,比如随机森林、支持向量机、逻辑回归等。
- 对模型进行训练并评估其性能,使用如交叉验证等方法进行模型选择。
-
结果预测与提交:
- 对测试数据集使用最优模型进行预测。
- 将预测结果保存并准备提交格式代码
代码解释
import warnings
warnings.filterwarnings("ignore") #忽略警告信息
# 数据处理清洗包
import pandas as pd
import numpy as np
import random as rnd
# 可视化包
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
# 机器学习算法相关包
from sklearn.linear_model import LogisticRegression, Perceptron, SGDClassifier
from sklearn.svm import SVC, LinearSVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
SVC()
%matplotlib inline
: 是一个魔法命令,它可以在IPython或Jupyter Notebook中内嵌绘图,并且可以省略掉plt.show()
。- Scikit-learn (
sklearn
): 是Python中使用最广泛的机器学习库之一,包含了从数据预处理到训练模型的各种工具。
LogisticRegression
、Perceptron
、SGDClassifier
: 线性模型,用于分类任务。SVC
、LinearSVC
: 支持向量机,用于分类和回归分析。KNeighborsClassifier
: 最近邻算法,用于分类和回归。GaussianNB
: 朴素贝叶斯分类器,适用于大量特征的数据。DecisionTreeClassifier
: 决策树分类器,用于分类和回归。RandomForestClassifier
: 随机森林是一个包含多个决策树的分类器,用于分类和回归,能有效处理过拟合。SVC()
是创建一个支持向量机分类器的实例,通常用于比较不同模型
train_df = pd.read_csv('./titanic/train.csv')
test_df = pd.read_csv('./titanic/test.csv')
combine = [train_df, test_df] # 合并数据
合并数据便于对整体数据执行一致的预处理和分析。
# 获取所有特征名
print(train_df.columns.values)
# 预览前五个数据
train_df.head()
# 预览后五个数据
train_df.tail()
print(train_df.isnull().sum()) #找出不同特征的缺失值数量
print('_'*40) #分割线
test_df.isnull().sum() #最后一个单元格表达式自动显示
test_df.isnull().sum() #在Jupyter Notebook中,单元格中的最后一个表达式的结果会被自动展示,这使得在一些情况下可以不必显式地使用print()函数。这种方式可以让代码看起来更简洁,尤其是在进行探索性数据分析时,可以快速查看数据而不需要过多的print()调用
train_df.info()
print('_'*40)
test_df.info()
info()方法详细列出了DataFrame中的每列,包括其名称、非空值的数量以及每列的数据类型。此外,还提供了DataFrame使用的内存量。
- 在交互式环境中:如Jupyter Notebook,
info()
的输出自动显示,不需要print()
。- 在非交互式环境中(如直接在Python脚本中运行):
info()
的输出仍然会显示在控制台,因为它设计为直接输出到标凈输出流
round(train_df.describe(percentiles=[.5, .6, .7, .75, .8, .9, .99]),2)
train_df.describe()
:
describe()
是一个生成描述性统计数据的方法,常用于快速查看数据的分布情况。- 默认情况下,它提供 count(计数)、mean(均值)、std(标准差)、min(最小值)、25%(第一四分位数)、50%(中位数)、75%(第三四分位数)和 max(最大值)。
percentiles=[.5, .6, .7, .75, .8, .9, .99]
:
- 通过
percentiles
参数,可以指定需要计算的具体百分位数。- 在这个例子中,除了常规的 25%、50%、75% 外,还额外计算了 60%、70%、80%、90% 和 99% 的百分位数。
- 50%(0.5)其实就是中位数,这里显示了将其作为百分位数的一部分也是可以的。
round(..., 2)
:
round()
函数用于对describe()
方法的结果进行四舍五入。- 这里的
, 2
表示将结果四舍五入到小数点后两位。- 这通常用于改进数据的呈现,使其更易读,尤其是在处理包含很多小数的数据时。
运行结果
列的解释:
- PassengerId:乘客的ID号。
- Survived:乘客是否存活,其中1表示存活,0表示未存活。
- Pclass:乘客的舱位等级,一个分类变量,代表1等舱、2等舱和3等舱。
- Age:乘客的年龄。
- SibSp:乘客在船上的兄弟姐妹或配偶的数量。
- Parch:乘客在船上的父母或子女的数量。
- Fare:乘客为船票支付的费用。
统计量的解释:
- count:表示每个特征列的非空值数量。例如,Age列有714个非空值,说明有部分乘客的年龄数据缺失。
- mean:平均值。例如,Survived的平均值为0.38,表明大约38%的乘客存活。
- std:标准差,表示数据的离散程度。例如,Age的标准差为14.53,说明乘客年龄分布的波动较大。
- min:最小值。例如,Fare的最小值为0.00,表明有乘客没有为船票支付费用。
- 50% (中位数):数据的中间点。例如,Pclass的中位数为3,表明大多数乘客处于3等舱。
- 60%, 70%, 75%, 80%, 90%, 99%:这些都是不同的百分位数,提供了关于数据分布的更多细节。例如,99%的乘客年龄小于或等于65.87岁。
- max:最大值。例如,Age的最大值为80岁,表明船上最老的乘客是80岁。
分析例子:
Survived:
- 平均值为0.38,说明大约38%的乘客存活。
- 50% (中位数) 为0,说明超过一半的乘客未存活。
- 99%的百分位数为1,说明除了极少数之外,绝大多数乘客都未能存活。
Fare:
- 平均值为32.20,但中位数仅为14.45,表明船票费用分布极不均匀,少数高票价拉高了平均值。
- 最高票价为512.33,远高于大多数乘客的票价,表明可能有极少数的高级舱位。
describe函数的使用
describe()
是 pandas 库中的一个强大函数,用于生成关于 DataFrame 或 Series 对象中数据的描述性统计摘要。这通常包括均值、标准差、最小值、最大值以及各个百分位数。
describe()
函数可以通过几个参数来定制其行为:percentiles:一个介于0和1之间的数值列表,用于指定计算哪些百分位数。默认是
[0.25, 0.5, 0.75]
,分别对应25%,50%(中位数),75% 百分位数。df.describe(percentiles=[0.2, 0.4, 0.6, 0.8])
include:用于指定应该包括哪些数据类型的列在输出中。默认情况下,只描述数值型列。可以设置为 'all' 包括所有列,或者一个数据类型列表。
df.describe(include='all') # 描述所有列,包括非数值型 df.describe(include=[np.number]) # 仅描述数值型列 df.describe(include=[np.object]) # 仅描述对象类型列
exclude:与
include
相反,用于指定排除哪些数据类型的列。df.describe(exclude=[np.number]) # 排除所有数值型列
train_df.describe(include=['O'])
- 使用
include=['O']
会导致describe()
方法生成那些类型为对象的列的统计摘要,这包括:
- count:非空(非 NaN)值的数量。
- unique:不重复值的数量,展示该列中有多少个唯一的值。
- top:出现频率最高的值。
- freq:出现频率最高的值的频率(即该值出现了多少次)。
对部分特征与survived的相关性进行分析,初步判断有用特征量
# 针对Pclass和Survived进行分类汇总
train_df[['Pclass','Survived']].groupby(['Pclass'], as_index=False).mean().sort_values(by='Survived', ascending=False)
# pd.DataFrame(train_df.groupby('Pclass', as_index=False)['Survived'].mean()).sort_values(by='Survived', ascending=False)
train_df[['Sex','Survived']].groupby(['Sex'], as_index=False).mean().sort_values(by='Survived', ascending=False)
train_df[['SibSp','Survived']].groupby(['SibSp'], as_index=False).mean().sort_values(by='Survived',ascending=False)
train_df[['Parch','Survived']].groupby(['Parch'], as_index=False).mean().sort_values(by='Survived',ascending=False)
g = sns.FacetGrid(train_df, col='Survived') #FacetGrid(data, row, col, hue, height, aspect, palette, ...)
g.map(plt.hist, 'Age', bins=20)
sns.FacetGrid
是seaborn
库中用于创建一个网格,可以在这个网格中绘制多个图表,每个图表显示数据的一个子集。train_df
是包含数据的 DataFrame。col='Survived'
参数指定网格的列(col)基于Survived
列的不同值来分割。因为Survived
是一个二元变量(通常取值 0 或 1),所以这将生成两个面板(facet):一个为存活(Survived=1
),一个为未存活(Survived=0
)。g.map()
函数用于在每个分面上应用一个绘图函数。plt.hist
是matplotlib.pyplot
的函数,用于绘制直方图。'Age'
指定了直方图的数据列,这里是乘客的年龄。bins=20
指定将数据分成20个等宽的条形,这有助于更细致地观察年龄分布的形状。
#grid = sns.FacetGrid(train_df, col='Survived', row='Pclass', size=2.5, aspect=1.6)
grid = sns.FacetGrid(train_df, col='Pclass', hue='Survived')
#alpha是透明度
grid.map(plt.hist, 'Age', alpha=0.5, bins=20)
grid.add_legend();
grid = sns.FacetGrid(train_df, col='Embarked')
grid.map(sns.pointplot, 'Pclass', 'Survived', 'Sex', palette='deep')
#add_legend():这是 seaborn FacetGrid 对象的方法,用于在图形中添加图例。图例显示的是图形中代表的#不同类别(如果 FacetGrid 在创建时使用了 hue 参数指定了颜色区分的变量)。
#如果 FacetGrid 被定义时包含了 hue 参数(这个参数通常用于按某个变量的不同值对数据进行颜色编码),#那么 add_legend() 就会在图表中添加一个图例,标明不同颜色代表的数据类别。
grid.add_legend();
grid = sns.FacetGrid(train_df, col='Embarked', hue='Survived', palette={0: 'b', 1: 'r'})
grid.map(sns.barplot, 'Sex', 'Fare', alpha=.5, ci=None)
grid.add_legend()
输出
整理清洗数据
移除不需要的列
- 在代码中,使用
combine[0].shape
和combine[1].shape
来显示数据处理前后这两个 DataFrame 的形状,这有助于确认是否正确地执行了如删除列等操作。
print("Before", train_df.shape, test_df.shape, combine[0].shape, combine[1].shape)
train_df = train_df.drop(['Ticket', 'Cabin'], axis=1)
test_df = test_df.drop(['Ticket', 'Cabin'], axis=1)
combine = [train_df, test_df]
"After", train_df.shape, test_df.shape, combine[0].shape, combine[1].shape
提取新的特征(将乘客的称呼作为了一个新特征)
# 使用正则表达式提取Title特征
for dataset in combine:
dataset['Title'] = dataset.Name.str.extract('([A-Za-z]+)\.', expand=False)
pd.crosstab(train_df['Title'], train_df['Sex']).sort_values(by='female', ascending=False) # pd.crosstab列联表
循环遍历
combine
列表:combine
是一个包含两个 DataFrame (train_df
和test_df
) 的列表。这个循环确保对列表中的每个 DataFrame 执行相同的操作。使用
str.extract()
方法:这是 pandas 中的一个字符串方法,用来对字符串列应用正则表达式,并提取匹配的部分。这里的正则表达式([A-Za-z]+)\.
匹配任何连续的字母字符(至少一个),后跟一个点(.
)。这种模式匹配常见于诸如 "Mr.", "Mrs.", "Dr." 等称谓。
expand=False
:此参数设置为 False 表示返回的结果是一个 Series,而不是 DataFrame。
pd.crosstab
:这是 pandas 中的一个函数,用于计算两个(或更多)因子的简单交叉表。它非常适合于快速汇总信息,并查看两个变量之间的关系。参数:这里
train_df['Title']
作为行索引(index),train_df['Sex']
作为列索引(columns)。这将创建一个展示不同称谓和性别组合的频率分布表。
sort_values(by='female', ascending=False)
:这个方法用于对结果进行排序,基于 'female' 列的值降序排序。这样做可以快速查看哪些称谓最常与女性关联。
grid = sns.FacetGrid(train_df, col='Title', hue='Survived', col_wrap=3, size=2.5, aspect=1.6)
#grid = sns.FacetGrid(train_df, col='Title', hue='Survived')
grid.map(plt.hist, 'Age', alpha=0.5, bins=20)
grid.add_legend()
# 可以用更常见的名称替换许多标题或将它们归类为稀有
for dataset in combine:
dataset['Title'] = dataset['Title'].replace(['Lady', 'Countess','Capt', 'Col',
'Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona'], 'Rare')
dataset['Title'] = dataset['Title'].replace(['Mlle', 'Ms'], 'Miss')
dataset['Title'] = dataset['Title'].replace('Mme', 'Mrs')
train_df[['Title', 'Survived']].groupby(['Title'], as_index=False).mean()
# 将分类标题转换为序数
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_df.head()
# 转换分类特征Sex
for dataset in combine:
dataset['Sex'] = dataset['Sex'].map( {'female': 1, 'male': 0} ).astype(int) #男性赋值为0,女性赋值为1,并转换为整型数据
train_df.head()
# 绘制Age, Pclass, Sex复合直方图
#grid = sns.FacetGrid(train_df, row='Pclass', col='Sex', size=2.2, aspect=1.6)
grid = sns.FacetGrid(train_df, col='Pclass', hue='Sex')
grid.map(plt.hist, 'Age', alpha=.5, bins=20)
grid.add_legend()
# 创建空数组
guess_ages = np.zeros((2,3))
guess_ages
# 遍历 Sex (0 或 1) 和 Pclass (1, 2, 3) 来计算六种组合的 Age 猜测值
for dataset in combine:
# 第一个for循环计算每一个分组的Age预测值
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()
# 将随机年龄浮点数转换为最接近的 0.5 年龄(四舍五入)
guess_ages[i,j] = int( age_guess/0.5 + 0.5 ) * 0.5
# 第二个for循环对空值进行赋值
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)
train_df.head()
# 创建年龄段,并确定其与Survived的相关性
# 一般在建立分类模型时,需要对连续变量离散化,特征离散化后,模型会更稳定,降低了模型过拟合的风险
train_df['AgeBand'] = pd.cut(train_df['Age'], 5) # 将年龄分割为5段,等距分箱
train_df[['AgeBand', 'Survived']].groupby(['AgeBand'], as_index=False).mean().sort_values(by='AgeBand', ascending=True)
# 将这些年龄区间替换为序数
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_df.head()
train_df = train_df.drop(['AgeBand'], axis=1) # 删除训练集中的AgeBand特征
combine = [train_df, test_df]
train_df.head()
test_df
for dataset in combine:
dataset['FamilySize'] = dataset['SibSp'] + dataset['Parch'] + 1
train_df[['FamilySize', 'Survived']].groupby(['FamilySize'], as_index=False).mean().sort_values(by='Survived', ascending=False)
# 创建新特征IsAlone
for dataset in combine:
dataset['IsAlone'] = 0
dataset.loc[dataset['FamilySize'] == 1, 'IsAlone'] = 1
train_df[['IsAlone', 'Survived']].groupby(['IsAlone'], as_index=False).mean()
train_df = train_df.drop(['Parch', 'SibSp', 'FamilySize'], axis=1)
test_df = test_df.drop(['Parch', 'SibSp', 'FamilySize'], axis=1)
combine = [train_df, test_df]
train_df.head()
# 创建Age*Pclass特征以此用来结合Age和Pclass变量
for dataset in combine:
dataset['Age*Pclass'] = dataset.Age * dataset.Pclass
train_df.loc[:, ['Age*Pclass', 'Age', 'Pclass']].head(10)
train_df[['Age*Pclass', 'Survived']].groupby(['Age*Pclass'], as_index=False).mean()
freq_port = train_df.Embarked.dropna().mode()[0]
for dataset in combine:
dataset['Embarked'] = dataset['Embarked'].fillna(freq_port)
train_df[['Embarked', 'Survived']].groupby(['Embarked'], as_index=False).mean().sort_values(by='Survived', ascending=False)
# 同样转换分类特征为序数
for dataset in combine:
dataset['Embarked'] = dataset['Embarked'].map( {'S': 0, 'C': 1, 'Q': 2} ).astype(int)
train_df.head()
# 测试集中Fare有一个缺失值,用中位数进行填补
test_df['Fare'].fillna(test_df['Fare'].dropna().median(), inplace=True)
test_df.head()
plt.hist(train_df['Fare'])
train_df['FareBand'] = pd.qcut(train_df['Fare'], 4) # 根据样本分位数进行分箱,等频分箱
train_df[['FareBand', 'Survived']].groupby(['FareBand'], as_index=False).mean().sort_values(by='FareBand', ascending=True)
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_df = train_df.drop(['FareBand'], axis=1)
combine = [train_df, test_df]
train_df.head(10)
test_df.head(10)
构建模型
X_train = train_df.drop("Survived", axis=1)
Y_train = train_df["Survived"]
X_test = test_df.drop("PassengerId", axis=1).copy()
X_train.shape, Y_train.shape, X_test.shape
# 逻辑回归模型
logreg = LogisticRegression()
logreg.fit(X_train, Y_train)
Y_pred = logreg.predict(X_test) # logreg.predict_proba(X_test)[:,1]
acc_log = round(logreg.score(X_train, Y_train) * 100, 2)
acc_log
coeff_df = pd.DataFrame(train_df.columns.delete(0))
coeff_df.columns = ['Feature']
coeff_df["Correlation"] = pd.Series(logreg.coef_[0])
coeff_df.sort_values(by='Correlation', ascending=False)
# 支持向量机模型
svc = 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
# KNN
knn = 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
# 朴素贝叶斯分类器
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
# 感知机
perceptron = 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
# 线性SVC
linear_svc = 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
# SDG随机梯度下降
sgd = 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
# 决策树
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
# 决策树可视化
from sklearn import tree
import graphviz
import pydotplus
from IPython.display import Image
dot_data = tree.export_graphviz(decision_tree, out_file=None,
feature_names=X_train.columns,
class_names=['0','1'],
max_depth=3,
filled=True, rounded=True,
special_characters=True)
graph = pydotplus.graph_from_dot_data(dot_data)
Image(graph.create_png())
dot_data = tree.export_graphviz(decision_tree, out_file=None,
feature_names=X_train.columns,
#class_names=['0','1'],
filled=True, rounded=True,
special_characters=True)
graph = pydotplus.graph_from_dot_data(dot_data)
graph.write_pdf("DTtree.pdf")
train_df[(train_df['Age*Pclass']>2.5) & (train_df['Fare']<=1.5)&(train_df['Fare']>0.5)&(train_df['IsAlone']>0.5)&(train_df['Age']>0.5)& \
(train_df['Embarked']<=1.5) &(train_df['Embarked']>0.5)&(train_df['Fare']<=2.5)&(train_df['Age']<=1.5)&(train_df['Pclass']>1.5)& \
(train_df['Title']<=1.5)]
# 随机森林
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
模型评估和提交结果
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_df["PassengerId"],
"Survived": Y_pred
})
submission.to_csv('./submission.csv', index=False)