背景

在前几篇文章中,我们探讨了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)进行回归分析。以下是一些关键步骤:

  1. 导入库:首先,我们导入了必要的库,包括 numpypandasseabornmatplotlibsklearn

  2. 加载数据:我们使用 pandas 库读取了包含生命期望值的数据集,并对数据集进行了初步的探索性数据分析(EDA)。

  3. 数据预处理:在进行建模之前,我们对数据进行了清洗,包括处理缺失值、检查数据类型、重命名列名等。我们还进行了标签编码,将分类变量转换为数值型变量。

  4. 特征选择和数据分割:我们选择了两个强相关的特征 incomeadult-mort 作为自变量 X,将 life-exp 作为因变量 y。然后使用 train_test_split 将数据集分为训练集和测试集。

  5. 标准化:使用 StandardScaler 对特征数据进行了标准化,以加速模型的收敛。

  6. 建模与评估:我们使用多种回归算法(线性回归、支持向量回归、K近邻回归、决策树回归和随机森林回归)进行了交叉验证,并通过网格搜索优化了最优模型参数。

  7. 特征重要性分析:通过多种方法(算法方法、置换重要性方法和 SHAP 方法)分析了特征对预测结果的重要性。

  8. 模型保存与加载:最后,我们使用 pickle 库保存和加载了训练好的模型,以便进行推断和部署。

讨论问题及答案
  1. 为什么这个数据集是一个回归问题?

    • 因为预期寿命(我们的标签)是连续的。
    • 无论我们的特征是连续的还是离散的,这都是一个回归问题。
  2. 我们有多少样本数据?有多少特征?有多少标签?

    • 2938个样本 | 21个特征 | 1个标签
  3. 注意到“status”是一个对象类型。什么是“对象”类型?

    • 基本上是“字符串”或“混合”类型。
  4. 注意到“float64”。64是什么意思?

    • 数值类型的小数 - 64位 - 用多少个交替的0和1来表示一个数 - 位数越高,能表示的精度越高。
  5. 创建另一个计数图(Countplot)。

    • 使用Seaborn的countplot函数,展示不同状态(Developed和Developing)国家的数量。
  6. 创建另一个分布图(Displot)。

    • 使用Seaborn的displot函数,展示预期寿命的分布情况。
  7. 创建另一个箱线图(Boxplot)。

    • 使用Seaborn的boxplot函数,比较不同状态国家的预期寿命分布。
  8. 创建另一个散点图(Scatterplot)。

    • 使用Seaborn的scatterplot函数,展示收入与预期寿命的关系,并用颜色区分不同状态的国家。
  9. 我们使用了“标签编码”,将类别转为0、1、2…… 另一种技术叫做独热编码。它们有什么区别,各有什么优缺点?

    • 标签编码将类别编码为0到k(k是类别数量减一)的数字。
    • 标签编码无意中创建了一个顺序关系,例如3 > 2 > 1 > 0。
    • 独热编码为每个类别创建一个列。
    • 如果有50个类别,就会有50个额外的列,这样很不方便。
  10. 什么是 random_state = 42?

    • 你可以使用任何数字,它只是让你在每次运行代码时都能复现结果。
    • 以一种可复现的方式将数据分为训练集和测试集。
  11. 我们如何知道是用均值还是中位数替换缺失值?

    • 使用sns.displot查看分布情况;如果均值代表大多数情况,则使用均值,否则可能使用中位数。
  12. 有多少种数据缩放方式,什么时候使用? 尝试不缩放,看看MSE是否变化。 为什么?

    • 两种方式 - 标准化(当均值是一个好的集中测量时使用)。
    • 归一化(当最大值和最小值是更好的集中测量时使用,例如音频、信号、图像)。
    • 缩放有助于加快训练过程的收敛速度,但不会提升模型性能。
  13. 为什么我们应该在拆分数据后才进行预处理?

    • 防止数据泄漏。
  14. 尝试改变其他X并尝试找到最好的三个特征,它们可以获得最好的MSE。

    • 看起来所有三个特征都很重要。
  15. MSE或$R^2$多少被认为是好或坏的?

    • MSE: [0, ∞) | 0是最好的
    • $R^2$: (-∞, 1] | 1是最好的 | 0意味着不比均值好 | 负值意味着比均值差
  16. 我怎么知道使用哪种算法? 有那么多回归算法。

    • 使用交叉验证。
  17. 我们使用了k折交叉验证。 尝试找到其他交叉验证的方法。 提示:在sklearn网站内搜索。

  18. 在交叉验证和网格搜索中,我们是否触碰了测试集?

    • 仅使用了训练集。
  19. 为什么在交叉验证之后需要进行网格搜索?

    • 以找到最佳算法的最佳配置。
  20. 什么是特征重要性?

    • 分析哪些特征对预测结果贡献最大。
  21. 在我得到模型后,如何创建一个网站/移动应用程序? 详细解释。

    • 保存模型 | 使用streamlit、dash、flask | 加载模型 | 将数据输入模型 | 调用model.predict([np.array])

如果你觉得这篇博文对你有帮助,请点赞、收藏、关注我,并且可以打赏支持我!

欢迎关注我的后续博文,我将分享更多关于人工智能、自然语言处理和计算机视觉的精彩内容。

谢谢大家的支持!

08-12 05:00