Python 数据分析学习(3)逻辑回归预测实例
文章目录
前言
带有什么特征的人在Titanic号事件中有更高的生存率?我们构建一个模型来预测不同特征的人的生存情况。本文是学习知乎的文章https://zhuanlan.zhihu.com/p/53176091/的一些个人感受。
1.提出问题
带有什么特征的人在Titanic号事件中有更高的生存率?我们构建一个模型来预测不同特征的人的生存情况。
2.理解数据
2.1 基本概念
先整理下概念。
1.训练集:可以简单理解为是上课学的知识,用来训练模型得到参数。
2.验证集可以理解成课后习题,可以纠正和强化知识点,用来评估不同超参数训练出的模型效果,从而优化模型。
3.测试集可以理解为期末考试,用来评估最终模型的效果。
对同一个 任务,测试集要和训练集严格分开,测试集只能进行测试,不能参与训练。
4.超参数:在训练前需要设定的很多其它参数,就是超参数,必须预先选定哪个函数空间作为模型框架,需要选择哪种损失函数来衡量输出与标签之间的差异,需要哪种优化算法等等这些问题涉及到的参数,炼丹师和根据不同的机器学习任务来选择相对适配的超参数。但是有时候参数太多,很难通过经验来选择最优的模型,这时候就可以借助验证集,去找最优的模型。这时候就将训练集再划分为训练集和验证集。
在训练集训练出模型后用验证集评估模型,找到一组最优的超参数,然后将超参数固定再拿到整个训练集上重新训练模型,最终由测试评估性能。常规划分是训练集、测试集验证集分别占比60%,20%,20%。数据集数量比较庞大的话 100:1:1也可。
2.2 下载数据。
https://www.kaggle.com/c/titanic
其中
gender_submission.csv 文件记录真实的存活情况,1表示存活,0表示不幸遇难;
test.csv 为测试数据集;
train.csv 为训练数据集。
其中
gender_submission.csv 文件记录真实的存活情况,1表示存活,0表示不幸遇难;
test.csv 为测试数据集;
train.csv 为训练数据集。
各字段含义如下:
Age 年龄
Cabin 船舱号
Embarked 登船港口
Fare 票价
Name 乘客姓名
Parch 不同代直系亲属人数
SibSp 同代直系亲属人数
PassengerId 乘客ID
Pclass 客舱等级
Sex 性别
Ticket 船票编号
Survived 存活情况
2.3 导入数据
#导入包
import numpy as np
import pandas as pd
#导入数据
train = pd.read_csv("./train.csv")
test = pd.read_csv("./test.csv")
#查看文件大小
print('训练数据集大小:',train.shape)
print('测试数据集大小:',test.shape)
训练数据集大小: (891, 12)
测试数据集大小: (418, 11)
合并两个表的的代码的一般为
data = pd.concat([train,test],axis=0)
axis的意思是“轴”,决定了两个数据集是纵向的合并,还是横向的合并。
axis=0是纵向的合并,把很多行合并在一起,由于默认是纵向的合并,axis=0写不写都可以。
axis=1是横向的合并,把很多列合并在一起。
2.4 查看字段
train.head()
#查看描述统计和信息
train.describe()
#根据描述统计没有观察到异常值
train.info()
训练数据集大小: (891, 12)
测试数据集大小: (418, 11)
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 PassengerId 891 non-null int64
1 Survived 891 non-null int64
2 Pclass 891 non-null int64
3 Name 891 non-null object
4 Sex 891 non-null object
5 Age 714 non-null float64
6 SibSp 891 non-null int64
7 Parch 891 non-null int64
8 Ticket 891 non-null object
9 Fare 891 non-null float64
10 Cabin 204 non-null object
11 Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
通过info可以看出总共有891条数据,其中Age,Cabin(船舱号),Embarked(登船港口),Fare(票价),Survived(存活情况)均有不同程度的数据缺失,包含数值型数据(Age,Fare和Survived)和字符型数值(Cabin和Embarked),这就需要我们根据不同的数据类型进行相应的数据预处理。
3.数据清洗(Data Preparaion)
这一步我们主要对缺失数据进行补充,并对数据进行特征工程。
3.1数据预处理
对于数值型数据(Age,Fare)缺失值,我们采用平均值来填充; 对于分类数据(Cabin,Embarked),我们采用众数填充(众数填充是一种处理缺失数据的方法,它使用变量中出现次数最多的值来替换缺失值。这种方法适用于类别变量,尤其是当数据集的分布偏斜或者具有很多类别时,使用众数填充可以是一个有效的策略。);
from pandas import DataFrame
train['Age'] = train['Age'].fillna(train['Age'].mean())
#年龄因为不可从其他途径得知,采用平均年龄填充,此步骤对最终预测结果有影响。
full.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 PassengerId 891 non-null int64
1 Survived 891 non-null int64
2 Pclass 891 non-null int64
3 Name 891 non-null object
4 Sex 891 non-null object
5 Age 714 non-null float64
6 SibSp 891 non-null int64
7 Parch 891 non-null int64
8 Ticket 891 non-null object
9 Fare 891 non-null float64
10 Cabin 204 non-null object
11 Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
fillna() 方法用于填充 DataFrame 中的 NaN 值。这个方法提供了多种填充方式,包括使用常数值填充、前向填充、后向填充等。使用 fillna() 可以很方便地处理数据中的缺失值,提高数据的质量。
它的格式如下full[‘Age’] (要被补充的列为Age) = full[‘Age’].fillna(full[‘Age’].mean())
fillna()的括号里填写要被替换的值比如 fillna(0)那就是Age的值都被替换成0
使用均值填充缺失值:import pandas as pd
data = pd.read_csv(‘data.csv’)
data.fillna(data.mean(), inplace=True)
使用中位数填充缺失值:
import pandas as pd
data = pd.read_csv('data.csv')
data.fillna(data.median(), inplace=True)
使用众数填充缺失值
import pandas as pd
data = pd.read_csv('data.csv')
data.fillna(data.mode(), inplace=True)
使用前向填充或后向填充:
import pandas as pd
data = pd.read_csv('data.csv')
data.fillna(method='ffill', inplace=True) # 使用前向填充
data.fillna(method='bfill', inplace=True) # 使用后向填充
在python中 inplace=True 这个语法。运行后,后面的运行就在前一次数据的基础上了,所以一般不用。
但是有些有时候确实某些数据确定没用的时候,可以用这个永久删掉,比如其实这个训练集姓名是没有用的,可以永久的删掉。
df_train.drop(columns=["Name”],inplace=True)
df_train.headO
train['Fare'] = train['Fare'].fillna(train['Fare'].mean())#船票价格用平均值填充
train.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 PassengerId 891 non-null int64
1 Survived 891 non-null int64
2 Pclass 891 non-null int64
3 Name 891 non-null object
4 Sex 891 non-null object
5 Age 891 non-null float64
6 SibSp 891 non-null int64
7 Parch 891 non-null int64
8 Ticket 891 non-null object
9 Fare 891 non-null float64
10 Cabin 204 non-null object
11 Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
其实港口和船舱号不是很重要,可以直接删掉,下面只是展示下填充
train['Embarked'].value_counts()#查看登船港口的分布
S 914
C 270
Q 123
Name: Embarked, dtype: int64
train['Embarked'] = train['Embarked'].fillna('S')#S港登船的人最多,对空值使用S港填充
train['Cabin'].head()#查看船舱号,head函数是返回数组的前几行,如果不加参数默认是5行
0 NaN
1 C85
2 NaN
3 C123
4 NaN
Name: Cabin, dtype: object
train['Cabin']=train['Cabin'].fillna('U')#船舱号Cabin的缺失值比较多,我们对于空值填充U(Unknow)
train.head(10)
.head函数在数据缺失值处理的时候查看缺失值比较有用,可以轻松查看列表的样子
3.2 特征提取
特征的选择和提取的好坏很大程度上决定了模型的上限,算法做到的只能是尽量逼近这个上限。如何提取尽可能多的特征并选择优质的特征是很重要的一个过程。
3.2.1整理数据
新建了一个叫做 Familynum 的变量,他表示乘客同城的家庭成员数就等于同城的伴侣或同胞数,加上同城的父母或孩子数,这样做是因为本质上像伴侣、同胞、父母、孩子都属于家庭成员。我这里是把它简化成了同一个因素,那后续也进行了一些数据的探索.
train.info()
train[‘FamilyNum’]=train[‘SibSp’]+train[‘Parch’]
train.head(20)
3.2.2探索数据
在着手逻辑回归分析之前,我们可以先借助数据可视化,探索数值变量的分布,以及与乘客是否幸存存在相关性的变量,为后续的进一步分析提供方向。这里可以用一些可视化操作,安装可视化的库,Seaborn 是一个用于数据可视化的 Python 库,它建立在 Matplotlib 之上,并提供了许多方便的函数来创建高质量的统计图形。matplotlib.pyplot 是 Matplotlib 库的一个模块,它提供了一个类似于 MATLAB 的绘图接口,使得使用 Matplotlib 进行绘图变得更加直观和易于使用。安装库使用
pip install seaborn
#设置图表色盘为”pastet
sns.set_palette("pastel")
#设置图表尺寸
plt.rcParams["figure.figsize"] =[7.00,3.50]
plt.rcParams["figure.autolayout"] =True
幸存比例
# 计算'Survived'列中每个值的计数
survived_count = train['Survived'].value_counts()
# 提取'Survived'列中每个值的标签
survived_label = survived_count.index
# 使用Matplotlib的pie函数创建饼图
# 参数survived_count表示饼图的各个部分的大小
# 参数labels表示饼图中的标签,这里就是survived_label
# 参数autopct表示自动显示每个部分的百分比,格式为'%.1f%%',即保留一位小数
plt.pie(survived_count, labels=survived_label, autopct='%.1f%%')
# 添加标题
plt.title('Survival Rate Distribution')
# 显示饼图
plt.show()
从以上饼图来看,泰坦尼克号遇难乘客多于幸存乘客,比例约为3:2。
乘客年龄
# 创建一个包含两个子图的图形
figure, axes = plt.subplots(1, 2)
# 创建第一个子图,绘制'Age'列的直方图
# 参数train表示数据集
# 参数x='Age'表示绘制'Age'列的直方图
# 参数ax=axes[0]表示将直方图绘制在第一个子图上
sns.histplot(train, x='Age', ax=axes[0])
# 创建第二个子图,绘制'Age'列的箱线图
# 参数train表示数据集
# 参数y='Age'表示绘制'Age'列的箱线图
# 参数ax=axes[1]表示将箱线图绘制在第二个子图上
sns.boxplot(train, y='Age', ax=axes[1])
# 显示图形
plt.show()
大多数乘客年龄位于20岁到40岁之间,但有不少老年乘客以及婴儿。
乘客年龄与是否幸存
# 使用Seaborn的histplot函数创建直方图
# 参数cleaned_titanic_train表示数据集
# 参数x='Age'表示直方图的x轴是'Age'列
# 参数hue='Survived'表示根据'Survived'列的值进行分组
# 参数alpha=0.4表示透明度,即直方图的填充颜色透明度为0.4
sns.histplot(cleaned_titanic_train, x='Age', hue='Survived', alpha=0.4)
# 显示图形
plt.show()
从乘客年龄直方图来看,只有婴儿群体幸存比例较高,绝大部分其余年龄段都是遇难人数多于幸存人数。
家庭成员数与是否幸存的关系
#家庭成员数与是否幸存的关系
sns.countplot(data=train,x='FamilyNum',hue='Survived')
从是否幸存与乘客家庭成员之间的柱状图来看,独身的乘客中遇难的多于幸存的。从有携带家庭成员的乘客来看,家庭成员在1~3位之间的幸存人数超过遇难人数,但同乘家庭成员超过3位后,遇难的更多。
3.2.2分析数据
移除大概率不影响乘客幸存概率的变量
数据里面包含了一些和生还概率可能无关的变量,比如说乘客的姓名,像这个肯定是没啥关系的。因为我总不能还去分析这个英文名的风水吧?然后像船票的号、船舱的号以及登船的这个港口,还有他的ID,就是乘客的 ID 也是大概率不会有什么影响的,所以说我们可以把他们都给移除掉
train:这是一个Pandas DataFrame对象,它包含了原始的titanic_train数据集。
.drop():这是一个Pandas方法,用于从DataFrame中删除列。
['PassengerId', 'Name', 'Ticket', 'Cabin', 'Embarked']:这是一个列表,包含了train DataFrame中要删除的列的名称。
axis=1:这是一个参数,指定沿着DataFrame的哪一轴(轴0是行,轴1是列)来删除列。在这个例子中,axis=1意味着我们要删除列。
train =train.drop(['PassengerId',‘Name','Ticket',‘Cabin','Embarked'],axis=1)
train.head()
那下一步是我们要给分类变量引入虚拟变量,也就是分别用 0 和1表示是否属于该类别,那一看到这个 survive 它本身就是用 0和1表示的,所以我们不需要再动它了。然后另外两个分类变量,它们分别是 pclass 船舱等级以及 sex 性别。
分类变量onehot化
onehot_columns =["Pclass","Sex"]#把需要onehot编码的列通通扔进去
train= pd.get_dummies(
train, #这行传入整个train
columns=onehot_columns ,#传入上面的列"Pclass","Sex"#要转码的列
prefix=onehot_columns,# 生成的列名的前缀意思就是sex列编程sex_male 和sex_fmale,方便查看
#把空值也做编码
drop_first=True) # 删掉第一列 ,防止出现问题
train.head()
划分自变量和因变量
因变量是Survived变量,因为我们进行逻辑回归的目的,是根据其它可能对乘客生还概率有影响的变量,来预测幸存情况
y=train['Survived']
我们可以把除Survived之外的先纳入自变量,但需要查看它们之间的相关性,如果其中有些变量之间相关性很高,会导致共线性。
X=train.drop(['Survived'],axis=1)
X.corr()
我们认为,当相关系数的绝对值大于0.8的时候,可能导致严重共线性,所以我们检查的时候,找绝对值大于0.8的值即可。
X.corr().abs()>0.8
单元格里面每一个数字它的绝对值都会去拿去和 0.8 进行比较。
那可以看到的这个 Sibsp 它们之间的相关系数是大于 0.8 的。那这个其实是符合预期的,因为咱们说到底 family num 它就是 sibsp和这个 parch 它们俩加一起得到的,那为了解决这个问题,我们可以把 Sibsp 这个变量给它移除掉。后另外这个 partch 它和 family num 之间也必然是强相关。因为这个 是一个道理,我们可以回去看一下他们之间相关系数,那你看到果然是比较大的一个数字,0.78,四舍五入也有 0.8 了。那为了避免算法无法收敛,我们要对这俩进行移除。
X = X.drop(['Parch', 'SibSp'], axis=1)
4.模型训练
4.1 划分训练集测试集
y=df_coded.pop('Survived')# pop删除并返回该列的值,这里就是删除Survived列
x=df_coded
x.head()
x没有Survived那一列了
from sklearn.model_selection import train_test_split#划分训练集和测试集
划分出训练集和测试集再进行下一步。
train_X, test_X, train_y, test_y = train_test_split(x,y,
train_size=.8)
X:这是您特征数据集,通常是一个二维数组或者pandas DataFrame。
y:这是目标变量(标签)数据集,通常是一个一维数组或者pandas Series。
train_size=0.8:这个参数指定了训练集应该占总数据集的80%,剩下的20%将用作测试集。
print ('原始数据集特征:',source_X.shape,
'训练数据集特征:',train_X.shape ,
'测试数据集特征:',test_X.shape)
原始数据集特征: (1309, 29)
训练数据集特征: (1047, 29)
测试数据集特征: (262, 29)
也可以用随机种子分开
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=0)
4.2引入逻辑回归模型
from sklearn.linear_model import LogisticRegression#导入逻辑回归模块
logreg=LogisticRegression(solver='liblinear')#使用solver求解器
model.score(X,y)
LogisticRegression:这是scikit-learn库中的一个类,用于实现逻辑回归模型。逻辑回归是一种广泛用于二分类问题的统计方法,尽管它也可以用于多分类问题。
solver:这个参数用于指定用于优化问题的求解器。不同的求解器适用于不同类型的问题和数据大小。
‘liblinear’:这是一个适用于小数据集的线性求解器。它适用于标准的逻辑回归(不带正则化项或带有L1和L2正则化项的逻辑回归)。对于多类问题,它默认使用一对多(one-vs-rest)的方法。
以下是LogisticRegression类中solver参数的一些常见选项:
‘liblinear’:适用于小数据集。
‘newton-cg’、‘sag’、‘saga’ 和 ‘lbfgs’:这些是适用于大数据集的优化求解器。其中,'sag’和’saga’适用于大规模稀疏数据集。
4.3用测试集来预测
把次数集和训练集对齐
test['Age'] = test['Age'].fillna(test['Age'].mean())
test['Fare'] = test['Fare'].fillna(test['Fare'].mean())
test.info()
test['FamilyNum'] = test['SibSp'] + test['Parch']
test = test.drop(['PassengerId', 'Name', 'Ticket', 'Cabin', 'Embarked', 'SibSp','Parch'],axis=1)
test = pd.get_dummies(test, columns=onehot_columns, prefix=onehot_columns, drop_first=True, dtype=int)
显示预测结果
predictions = model.predict(test)
predictions
4.4完整代码
import pandas as pd
train = pd.read_csv("./train.csv")
test = pd.read_csv("./test.csv")
print('训练数据集大小:', train.shape)
print('测试数据集大小:', test.shape)
train.head()
train.describe()
train.info()
train['Age'] = train['Age'].fillna(train['Age'].mean())
train.info()
train['FamilyNum'] = train['SibSp'] + train['Parch']
train.head(20)
import seaborn as sns
import matplotlib.pyplot as plt
sns.set_palette("pastel")
plt.rcParams["figure.figsize"] = [7.00, 3.50]
plt.rcParams["figure.autolayout"] = True
survived_count = train['Survived'].value_counts()
survived_label = survived_count.index
plt.pie(survived_count, labels=survived_label, autopct='%.1f%%')
plt.show()
figure, axes = plt.subplots(1, 2)
sns.histplot(train, x='Age', ax=axes[0])
sns.boxplot(train, y='Age', ax=axes[1])
plt.show()
sns.histplot(train, x='Age', hue='Survived', alpha=0.4)
plt.show()
train = train.drop(['PassengerId', 'Name', 'Ticket', 'Cabin', 'Embarked'], axis=1)
onehot_columns = ["Pclass", "Sex"]
train = pd.get_dummies(train, columns=onehot_columns, prefix=onehot_columns, drop_first=True, dtype=int)
train.head()
y = train['Survived']
X = train.drop(['Survived'], axis=1)
X.corr()
X = X.drop(['Parch', 'SibSp'], axis=1)
X.head()
from sklearn.linear_model import LogisticRegression
model = LogisticRegression()
model.fit(X, y)
score = model.score(X, y)
print("模型得分:", score)
test['Age'] = test['Age'].fillna(test['Age'].mean())
test['Fare'] = test['Fare'].fillna(test['Fare'].mean())
test.info()
test['FamilyNum'] = test['SibSp'] + test['Parch']
test = test.drop(['PassengerId', 'Name', 'Ticket', 'Cabin', 'Embarked', 'SibSp','Parch'],axis=1)
test = pd.get_dummies(test, columns=onehot_columns, prefix=onehot_columns, drop_first=True, dtype=int)
predictions = model.predict(test)
predictions
总结
利用一个例子学习逻辑回归的模型