背景
在前几篇文章中,我们探讨了Python的基础知识,包括列表、元组、字典、集合、函数、类与异常处理、NumPy、Pandas和Matplotlib。本系列的第九篇将聚焦于一个实际案例,通过回归分析来预测预期寿命。
案例研究 - 回归分析
本文将探讨一个回归问题,目标是预测预期寿命。数据集中的特征如下:
- 国家
- 年份
- 状态:发达或发展中
- 预期寿命:年龄
- 成人死亡率:15至60岁每1000人的死亡率
- 婴儿死亡数:每1000人口中的婴儿死亡数
- 酒精:人均(15岁以上)酒精消费量(纯酒精升数)
- 百分比支出:按人均国内生产总值(%)计算的健康支出
- 乙肝:1岁儿童乙肝疫苗接种率(%)
- 麻疹:每1000人口中的报告病例数
- BMI:全人口的平均身体质量指数
- 五岁以下死亡数:每1000人口中的五岁以下儿童死亡数
- 脊髓灰质炎:1岁儿童脊髓灰质炎疫苗接种率(%)
- 总支出:政府总健康支出占总政府支出的百分比(%)
- 白喉:1岁儿童白喉疫苗接种率(%)
- 艾滋病死亡数:每1000活产婴儿中的艾滋病死亡数(0-4岁)
- GDP:人均国内生产总值(美元)
- 人口:国家人口
- 青少年瘦弱率(1-19岁):10至19岁儿童和青少年的瘦弱率(%)
- 儿童瘦弱率(5-9岁):5至9岁儿童的瘦弱率(%)
- 资源收入构成:按收入构成计算的人类发展指数(指数范围为0到1)
- 学校教育年限:受教育年限(年)
导入库
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')
import matplotlib
np.__version__, pd.__version__, sns.__version__, matplotlib.__version__
1. 加载数据
df = pd.read_csv('data/Life_Expectancy_Data.csv')
df.head()
df.shape
df.describe()
df.info()
df.columns
2. 探索性数据分析(EDA)
探索性数据分析是检查数据的一个重要步骤,以便更好地了解给定数据的性质。
重命名列名
df.rename(columns = {
'Country':'country',
'Year':'year',
'Status':'status',
'Life expectancy ':'life-exp',
'Adult Mortality':'adult-mort',
'infant deaths':'infant-deaths',
'Alcohol':'alcohol',
'percentage expenditure':'per-exp',
'Hepatitis B':'hepa',
'Measles ':'measles',
' BMI ':'bmi',
'under-five deaths ':'under-five-deaths',
'Polio':'polio',
'Total expenditure':'total-exp',
'Diphtheria ':'dip',
' HIV/AIDS':'hiv',
'GDP':'gdp',
'Population':'pop',
' thinness 1-19 years':'thin1-19',
' thinness 5-9 years':'thin5-9',
'Income composition of resources':'income',
'Schooling':'school'
}, inplace = True)
df.columns
单变量分析
sns.countplot(data = df, x = 'status')
sns.displot(data = df, x = 'life-exp')
多变量分析
sns.boxplot(x = df["status"], y = df["life-exp"]);
plt.ylabel("Life Expectancy")
plt.xlabel("Status")
sns.scatterplot(x = df['income'], y = df['life-exp'], hue=df['status'])
plt.figure(figsize = (15,8))
sns.heatmap(df.corr(), annot=True, cmap="coolwarm")
标签编码
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
df["status"] = le.fit_transform(df["status"])
df["status"].unique()
le.classes_
le.transform(["Developed", "Developing"])
plt.figure(figsize = (15,8))
sns.heatmap(df.corr(), annot=True, cmap="coolwarm")
预测力得分
import ppscore as pps
dfcopy = df.copy()
dfcopy.drop(['country', 'year'], axis='columns', inplace=True)
matrix_df = pps.matrix(dfcopy)[['x', 'y', 'ppscore']].pivot(columns='x', index='y', values='ppscore')
plt.figure(figsize = (15,8))
sns.heatmap(matrix_df, vmin=0, vmax=1, cmap="Blues", linewidths=0.5, annot=True)
3. 特征工程
本文将跳过特征工程,但我们可以尝试结合一些列来创建新特征。
4. 特征选择
X = df[['income', 'adult-mort']]
y = df["life-exp"]
训练集和测试集拆分
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 42)
5. 数据预处理
检查缺失值
X_train[['income', 'adult-mort']].isna().sum()
X_test[['income', 'adult-mort']].isna().sum()
y_train.isna().sum()
y_test.isna().sum()
填充缺失值
X_train['income'].fillna(X_train['income'].median(), inplace=True)
X_train['adult-mort'].fillna(X_train['adult-mort'].median(), inplace=True)
X_test['income'].fillna(X_train['income'].median(), inplace=True)
X_test['adult-mort'].fillna(X_train['adult-mort'].median(), inplace=True)
y_train.fillna(y_train.median(), inplace=True)
y_test.fillna(y_train.median(), inplace=True)
检查离群值
plt.figure(figsize=(20,30))
for variable,i in {'adult-mort':1,'income':2}.items():
plt.subplot(5,4,i)
plt.boxplot(X_train[variable])
plt.title(variable)
plt.show()
标准化
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
6. 建模
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
lr = LinearRegression()
lr.fit(X_train, y_train)
yhat = lr.predict(X_test)
print("MSE: ", mean_squared_error(y_test, yhat))
print("r2: ", r2_score(y_test, yhat))
交叉验证与网格搜索
from sklearn.model_selection import KFold, cross_val_score
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestRegressor
algorithms = [LinearRegression(), SVR(), KNeighborsRegressor(), DecisionTreeRegressor(random_state = 0),
RandomForestRegressor(n_estimators = 100, random_state = 0)]
algorithm_names = ["Linear Regression", "SVR", "KNeighbors Regressor", "Decision-Tree Regressor", "Random-Forest Regressor"]
kfold = KFold(n_splits=5, shuffle=True)
for i, model in enumerate(algorithms):
scores = cross_val_score(model, X_train, y_train, cv=kfold, scoring='neg_mean_squared_error')
print(f"{algorithm_names[i]} - Score: {scores}; Mean: {scores.mean()}")
param_grid = {'bootstrap': [True], 'max_depth': [5, 10, None], 'n_estimators': [5, 10, 15]}
rf = RandomForestRegressor(random_state = 1)
grid = GridSearchCV(estimator = rf, param_grid = param_grid, cv = kfold, n_jobs = -1, return_train_score=True, refit=True, scoring='neg_mean_squared_error')
grid.fit(X_train, y_train)
grid.best_params_
best_mse = grid.best_score_
print(best_mse)
7. 测试
yhat = grid.predict(X_test)
print(mean_squared_error(y_test, yhat))
8. 分析:特征重要性
算法方法
rf = grid.best_estimator_
plt.barh(X.columns, rf.feature_importances_)
置换方法
from sklearn.inspection import permutation_importance
perm_importance = permutation_importance(rf, X_test, y_test)
plt.barh(X.columns[perm_importance.importances_mean.argsort()], perm_importance.importances_mean[perm_importance.importances_mean.argsort()])
plt.xlabel("Random Forest Feature Importance")
Shap方法
import shap
explainer = shap.TreeExplainer(rf)
shap_values = explainer.shap_values(X_test)
shap.summary_plot(shap_values, X_test, plot_type="bar", feature_names = X.columns)
9. 推论
import pickle
filename = 'model/life-expectancy.model'
pickle.dump(grid, open(filename, 'wb'))
loaded_model = pickle.load(open(filename, 'rb'))
sample = np.array([[0.476, 10.000, 271.000]])
predicted_life_exp = loaded_model.predict(sample)
print(predicted_life_exp)
使用Scikit-Learn进行回归分析
在本篇博客中,我们重点介绍了如何使用 Scikit-Learn(sklearn)进行回归分析。以下是一些关键步骤:
-
导入库:首先,我们导入了必要的库,包括
numpy
、pandas
、seaborn
、matplotlib
和sklearn
。 -
加载数据:我们使用
pandas
库读取了包含生命期望值的数据集,并对数据集进行了初步的探索性数据分析(EDA)。 -
数据预处理:在进行建模之前,我们对数据进行了清洗,包括处理缺失值、检查数据类型、重命名列名等。我们还进行了标签编码,将分类变量转换为数值型变量。
-
特征选择和数据分割:我们选择了两个强相关的特征
income
和adult-mort
作为自变量X
,将life-exp
作为因变量y
。然后使用train_test_split
将数据集分为训练集和测试集。 -
标准化:使用
StandardScaler
对特征数据进行了标准化,以加速模型的收敛。 -
建模与评估:我们使用多种回归算法(线性回归、支持向量回归、K近邻回归、决策树回归和随机森林回归)进行了交叉验证,并通过网格搜索优化了最优模型参数。
-
特征重要性分析:通过多种方法(算法方法、置换重要性方法和 SHAP 方法)分析了特征对预测结果的重要性。
-
模型保存与加载:最后,我们使用
pickle
库保存和加载了训练好的模型,以便进行推断和部署。
讨论问题及答案
-
为什么这个数据集是一个回归问题?
- 因为预期寿命(我们的标签)是连续的。
- 无论我们的特征是连续的还是离散的,这都是一个回归问题。
-
我们有多少样本数据?有多少特征?有多少标签?
- 2938个样本 | 21个特征 | 1个标签
-
注意到“status”是一个对象类型。什么是“对象”类型?
- 基本上是“字符串”或“混合”类型。
-
注意到“float64”。64是什么意思?
- 数值类型的小数 - 64位 - 用多少个交替的0和1来表示一个数 - 位数越高,能表示的精度越高。
-
创建另一个计数图(Countplot)。
- 使用Seaborn的
countplot
函数,展示不同状态(Developed和Developing)国家的数量。
- 使用Seaborn的
-
创建另一个分布图(Displot)。
- 使用Seaborn的
displot
函数,展示预期寿命的分布情况。
- 使用Seaborn的
-
创建另一个箱线图(Boxplot)。
- 使用Seaborn的
boxplot
函数,比较不同状态国家的预期寿命分布。
- 使用Seaborn的
-
创建另一个散点图(Scatterplot)。
- 使用Seaborn的
scatterplot
函数,展示收入与预期寿命的关系,并用颜色区分不同状态的国家。
- 使用Seaborn的
-
我们使用了“标签编码”,将类别转为0、1、2…… 另一种技术叫做独热编码。它们有什么区别,各有什么优缺点?
- 标签编码将类别编码为0到k(k是类别数量减一)的数字。
- 标签编码无意中创建了一个顺序关系,例如3 > 2 > 1 > 0。
- 独热编码为每个类别创建一个列。
- 如果有50个类别,就会有50个额外的列,这样很不方便。
-
什么是
random_state
= 42?- 你可以使用任何数字,它只是让你在每次运行代码时都能复现结果。
- 以一种可复现的方式将数据分为训练集和测试集。
-
我们如何知道是用均值还是中位数替换缺失值?
- 使用
sns.displot
查看分布情况;如果均值代表大多数情况,则使用均值,否则可能使用中位数。
- 使用
-
有多少种数据缩放方式,什么时候使用? 尝试不缩放,看看MSE是否变化。 为什么?
- 两种方式 - 标准化(当均值是一个好的集中测量时使用)。
- 归一化(当最大值和最小值是更好的集中测量时使用,例如音频、信号、图像)。
- 缩放有助于加快训练过程的收敛速度,但不会提升模型性能。
-
为什么我们应该在拆分数据后才进行预处理?
- 防止数据泄漏。
-
尝试改变其他
X
并尝试找到最好的三个特征,它们可以获得最好的MSE。- 看起来所有三个特征都很重要。
-
MSE或$R^2$多少被认为是好或坏的?
- MSE: [0, ∞) | 0是最好的
- $R^2$: (-∞, 1] | 1是最好的 | 0意味着不比均值好 | 负值意味着比均值差
-
我怎么知道使用哪种算法? 有那么多回归算法。
- 使用交叉验证。
-
我们使用了k折交叉验证。 尝试找到其他交叉验证的方法。 提示:在sklearn网站内搜索。
-
在交叉验证和网格搜索中,我们是否触碰了测试集?
- 仅使用了训练集。
-
为什么在交叉验证之后需要进行网格搜索?
- 以找到最佳算法的最佳配置。
-
什么是特征重要性?
- 分析哪些特征对预测结果贡献最大。
-
在我得到模型后,如何创建一个网站/移动应用程序? 详细解释。
- 保存模型 | 使用streamlit、dash、flask | 加载模型 | 将数据输入模型 | 调用
model.predict([np.array])
- 保存模型 | 使用streamlit、dash、flask | 加载模型 | 将数据输入模型 | 调用
如果你觉得这篇博文对你有帮助,请点赞、收藏、关注我,并且可以打赏支持我!
欢迎关注我的后续博文,我将分享更多关于人工智能、自然语言处理和计算机视觉的精彩内容。
谢谢大家的支持!