引言
随着人工智能技术的飞速发展,机器学习已成为数据分析和预测领域的重要工具。在众多机器学习算法中,线性回归和多项式回归因其模型简单、易于理解和实现而受到广泛应用。本文旨在探讨如何利用这两种回归模型对人均收入进行预测。线性回归模型以其简洁性在预测问题中占据一席之地,它假设输入特征和输出变量之间存在线性关系。然而,在现实世界中,许多关系并非线性,这时多项式回归模型便显示出其优势。多项式回归通过引入高次项,能够更好地拟合数据中的非线性趋势,从而提高预测的准确性。
数据集
加拿大人均收入数据集全面记录了加拿大多年来的人均收入。主要目的是提供对加拿大人口的经济福祉和财务增长的见解。它是经济学家、研究人员、政策制定者和分析师研究收入分配趋势、衡量经济政策影响和识别财富差距的宝贵资源。
数据集由两列组成:“年”和“收入”。每行代表一个特定的年份及其相应的人均收入值。
导入数据
# 导入pandas库,用于数据处理和分析
import pandas as pd
# 导入numpy库,用于数值计算
import numpy as np
# 导入matplotlib.pyplot库,用于绘图
import matplotlib.pyplot as plt
# 导入seaborn库,用于数据可视化,它基于matplotlib
import seaborn as sns
# 从sklearn.model_selection中导入train_test_split函数,用于将数据集分割为训练集和测试集
from sklearn.model_selection import train_test_split
# 从sklearn.linear_model中导入LinearRegression类,用于线性回归模型的训练和预测
from sklearn.linear_model import LinearRegression
# 从sklearn.metrics中导入mean_squared_error和r2_score函数,用于评估回归模型的性能
from sklearn.metrics import mean_squared_error, r2_score
# 从statsmodels.tsa.seasonal中导入seasonal_decompose函数,用于时间序列的季节性分解
from statsmodels.tsa.seasonal import seasonal_decompose
# 从statsmodels.tsa.holtwinters中导入ExponentialSmoothing类,用于指数平滑法预测时间序列
from statsmodels.tsa.holtwinters import ExponentialSmoothing
# 从sklearn.preprocessing中导入PolynomialFeatures类,用于创建多项式特征
from sklearn.preprocessing import PolynomialFeatures
# 从sklearn.model_selection中导入cross_val_score函数,用于交叉验证模型的性能
from sklearn.model_selection import cross_val_score
# 导入warnings模块,并设置filterwarnings方法以忽略所有警告信息
import warnings
warnings.filterwarnings('ignore')
plt.rcParams ['font.sans-serif'] ='SimHei' #显示中文
plt.rcParams ['axes.unicode_minus']=False #显示负号
df = pd.read_csv('Canada_per_capita_income.csv')
df.head()
展示前五行数据,数据很简单,两列数据。
描述性统计加眼神交流
探索性分析
# 创建一个图形窗口,并设置其大小为 10 英寸宽和 6 英寸高
plt.figure(figsize=(10, 6))
# 在图形窗口中绘制数据,其中 df["year"] 是 x 轴数据,df["income"] 是 y 轴数据,marker='o' 表示数据点用圆圈表示
plt.plot(df["year"], df["per capita income (US$)"], marker='o')
# 设置图形的标题为 "Canada Per Capita Income Over the Years"
plt.title("Canada Per Capita Income Over the Years")
# 设置 x 轴的标签为 "Year"
plt.xlabel("Year")
# 设置 y 轴的标签为 "Per Capita Income"
plt.ylabel("Per Capita Income")
# 在图形上添加网格线
plt.grid(True)
# 显示图形窗口
plt.show()
人均收入随时间增长的区势图,可以看出接近2010年时候出现了第一次拐点。
机器学习
划分训练集和测试集:
# 将 DataFrame 'df' 中的 'year' 列作为特征变量 X,注意这里使用双中括号是因为我们想要一个 DataFrame 而不是 Series
X = df[["year"]]
# 将 DataFrame 'df' 中的 'income' 列作为目标变量 y
y = df["per capita income (US$)"]
# 将数据分割为训练集和测试集
# train_test_split 函数从 sklearn.model_selection 导入,用于将数据集划分为训练集和测试集
# X_train, X_test 分别是特征变量的训练集和测试集
# y_train, y_test 分别是目标变量的训练集和测试集
# test_size=0.2 表示测试集占总数据的 20%
# random_state=42 是随机数种子,用于确保每次分割的结果都相同(可复现性)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
构建双模型训练数据:
# 执行线性回归
# 创建一个 LinearRegression 对象,这个对象代表了一个线性回归模型
linear_model = LinearRegression()
# 使用训练数据 X_train 和 y_train 来拟合(训练)线性回归模型
linear_model.fit(X_train, y_train)
# 执行多项式回归
# 设置多项式的阶数,这里设为3,你可以根据需要调整这个值
degree = 3 # Adjust the polynomial degree as needed
# 创建一个 PolynomialFeatures 对象,这个对象用于生成多项式特征
# 参数 degree 指定了多项式的阶数
poly_features = PolynomialFeatures(degree=degree)
# 使用 PolynomialFeatures 对象的 fit_transform 方法来转换(添加多项式特征)训练数据 X_train
# 这将返回一个包含原始特征和多项式特征的新数据集
X_poly_train = poly_features.fit_transform(X_train)
# 使用 PolynomialFeatures 对象的 transform 方法来转换(添加多项式特征)测试数据 X_test
# 注意这里我们使用 transform 而不是 fit_transform,因为我们已经在 fit_transform(X_train) 中拟合了特征
X_poly_test = poly_features.transform(X_test)
# 创建一个新的 LinearRegression 对象,这个对象将用于拟合多项式回归模型
poly_model = LinearRegression()
# 使用添加了多项式特征的训练数据 X_poly_train 和原始的目标变量 y_train 来拟合(训练)多项式回归模型
poly_model.fit(X_poly_train, y_train)
双模型预测与评价:
# 评估线性回归模型
# 使用线性回归模型对测试集进行预测
y_pred_linear = linear_model.predict(X_test)
# 计算线性回归模型预测结果的均方误差(Mean Squared Error, MSE)
# MSE 衡量了预测值与真实值之间的平均平方差
linear_mse = mean_squared_error(y_test, y_pred_linear)
# 计算线性回归模型的 R-squared 分数
# R-squared 分数表示模型对数据的拟合程度,值越接近1表示模型拟合得越好
linear_r2 = r2_score(y_test, y_pred_linear)
# 评估多项式回归模型
# 使用多项式回归模型对测试集进行预测
y_pred_poly = poly_model.predict(X_poly_test)
# 计算多项式回归模型预测结果的均方误差(MSE)
poly_mse = mean_squared_error(y_test, y_pred_poly)
# 计算多项式回归模型的 R-squared 分数
poly_r2 = r2_score(y_test, y_pred_poly)
# 打印线性回归模型的评估结果
print("\n线性回归评价:")
print("均方误差:", linear_mse)
print("R平方(决定系数):", linear_r2)
# 打印多项式回归模型的评估结果
print("\n多项式回归评价:")
print("均方误差:", poly_mse)
print("R平方(决定系数):", poly_r2)
这里要跟大家详细讲解一下模型评价:
不急下结论,接着看下面的验证。
交叉验证
# 对线性回归模型进行交叉验证
# 使用 cross_val_score 函数对线性回归模型进行交叉验证
# linear_model 是前面训练好的线性回归模型
# X, y 是原始的特征和目标变量
# scoring='neg_mean_squared_error' 表示使用均方误差作为评分指标,但返回的是负值
# cv=5 表示将数据集分成5份进行5折交叉验证
linear_scores = cross_val_score(linear_model, X, y, scoring='neg_mean_squared_error', cv=5)
# 由于 cross_val_score 返回的是负均方误差,所以需要取反并开方得到 RMSE(均方根误差)
linear_rmse_scores = np.sqrt(-linear_scores)
# 打印线性回归模型的交叉验证结果
print("\n线性回归模型交叉验证结果:")
print("RMSE评分:", linear_rmse_scores) # 打印每一折的 RMSE 分数
print("RMSE平均值:", linear_rmse_scores.mean()) # 打印 RMSE 的平均值
print("RMSE标准差:", linear_rmse_scores.std()) # 打印 RMSE 的标准差,衡量分数的波动情况
# 对多项式回归模型进行交叉验证
# 使用 cross_val_score 函数对多项式回归模型进行交叉验证
# poly_model 是前面训练好的多项式回归模型
# X_poly_train, y_train 是添加了多项式特征的训练集和对应的目标变量
# 其他参数与线性回归的交叉验证相同
poly_scores = cross_val_score(poly_model, X_poly_train, y_train, scoring='neg_mean_squared_error', cv=5)
# 同样的,由于返回的是负均方误差,所以需要取反并开方得到 RMSE
poly_rmse_scores = np.sqrt(-poly_scores)
# 打印多项式回归模型的交叉验证结果
print("\n多项式回归模型交叉验证结果:")
print("RMSE评分:", poly_rmse_scores) # 打印每一折的 RMSE 分数
print("RMSE平均值:", poly_rmse_scores.mean()) # 打印 RMSE 的平均值
print("RMSE标准差:", poly_rmse_scores.std()) # 打印 RMSE 的标准差
上面分别对两个模型进行5折交叉验证,就是将数据分为5份来验证,大家可能看不懂交叉验证结果,我们来讲解一下:
大家能看懂上面的交叉验证结果了吗?均方根误差越小当然模型越好了。
绘图来查看:
# 创建一个新的图形窗口,并设置其大小(宽度为8英寸,高度为5英寸)
plt.figure(figsize=(8, 5))
# 创建一个列表,其中包含线性回归和多项式回归的均方误差值
mse_values = [linear_mse, poly_mse]
# 创建一个列表,其中包含模型的名称。对于多项式回归,假设 'degree' 变量已经在前面被定义,表示多项式的度数
models = ['线性回归模型', f'多项式回归模型 (Degree {degree})']
# 绘制一个条形图,其中 x 轴表示模型名称,y 轴表示均方误差值
# 'color' 参数用于指定每个条形图的颜色
plt.bar(models, mse_values, color=['blue', 'green'])
# 设置图形的标题
plt.title("模型性能比较")
# 设置 x 轴的标签
plt.xlabel("模型")
# 设置 y 轴的标签
plt.ylabel("均方误差")
# 显示图形
plt.show()
绘制模型残差对比图:
# 创建一个新的图形窗口,并设置其大小为12英寸宽和6英寸高
plt.figure(figsize=(12, 6))
# 绘制线性回归的残差图
# 创建一个子图,在1行2列的布局中占据第1个位置
plt.subplot(1, 2, 1)
# 计算线性回归的残差,即测试集的实际值y_test减去线性回归模型的预测值y_pred_linear
residuals_linear = y_test - y_pred_linear
# 绘制散点图,x轴为测试集的特征X_test,y轴为残差residuals_linear
# 散点颜色设置为蓝色
plt.scatter(X_test, residuals_linear, color='blue')
# 在图中添加一条红色的虚线,y轴值为0,表示残差为0的参考线
plt.axhline(y=0, color='red', linestyle='dashed')
# 设置图的标题为“Residual Plot - Linear Regression”
plt.title("残差图 - 线性回归")
# 设置x轴的标签为“Year”
plt.xlabel("Year")
# 设置y轴的标签为“Residuals”
plt.ylabel("残差")
# 绘制多项式回归的残差图
# 在1行2列的布局中占据第2个位置
plt.subplot(1, 2, 2)
# 计算多项式回归的残差,即测试集的实际值y_test减去多项式回归模型的预测值y_pred_poly
residuals_poly = y_test - y_pred_poly
# 绘制散点图,x轴为测试集的特征X_test,y轴为残差residuals_poly
# 散点颜色设置为绿色
plt.scatter(X_test, residuals_poly, color='green')
# 同样地,在图中添加一条红色的虚线,y轴值为0,表示残差为0的参考线
plt.axhline(y=0, color='red', linestyle='dashed')
# 设置图的标题为“Residual Plot - Polynomial Regression”
plt.title("残差图-多项式回归")
# 设置x轴的标签为“Year”
plt.xlabel("Year")
# 注意:这里y轴的标签与左侧的图相同,但为了保持代码的清晰性,通常我们会再次设置
plt.ylabel("残差")
# 调整子图之间的参数,使布局更紧凑,避免重叠
plt.tight_layout()
# 显示图形
plt.show()
有点麻烦,机器学习最大的缺点就是过于强大,强大到解释一个概念的时候会延伸另一个概念,或延伸另一本书其中的几十页内容,导致解释性很差。
先说一下残差的概念:
残差在数理统计和回归分析中是一个非常重要的概念。以下是关于残差的详细解释:
定义
- 残差:在数理统计和回归分析中,残差是指实际观察值与模型估计值(或称为拟合值)之间的差。残差蕴含了关于模型基本假设的重要信息,如果回归模型正确,残差可以被视为误差的观测值。
性质
- 总体期望:在包含截距的情况下,残差的总体期望(或均值)应该接近0。
- 误差性质:残差应符合模型的假设条件,通常假设残差是独立同分布的,且服从正态分布N(0, σ²)。
- 与回归变量关系:残差与回归变量(自变量)的乘积之和通常接近于0。
分类
- 普通残差:在线性回归模型中,残差为实际观察值减去拟合值。
- 内学生化残差:也被称为标准化残差,是通过某些数学变换(如使用帽子矩阵)得到的残差形式,其近似服从标准正态分布。
- 外学生化残差:在计算时排除了某个样本点的数据,基于剩余样本点重新估计回归系数,然后计算得到的残差。
计算公式
- 在线性回归中,设Y为实际观察值,为拟合值,则残差 。
残差图
- 定义:残差图是以残差为纵坐标,以其他适宜的量为横坐标(如拟合值、自变量值、观察时间或序号等)的散点图。
- 用途:残差图可以帮助我们诊断模型拟合是否准确,并突出模型中的不足。通过观察残差图的分布趋势,我们可以判断模型是否满足有关假设(如残差是否近似正态分布、是否方差齐次等)。
残差分析
- 残差分析是通过残差所提供的信息来考察模型假设的合理性和数据的可靠性的一种方法。
- 在回归分析中,如果发现残差图呈现出某种特定的形状或模式(如漏斗形状、曲线形状等),则可能需要对模型进行修改或调整,以更好地拟合数据。
总结
残差是实际观察值与模型估计值之间的差,它蕴含了关于模型假设的重要信息。通过残差分析,我们可以评估模型的拟合效果和预测精度,并发现模型中可能存在的问题。残差图是进行残差分析的重要工具之一,通过观察残差图的分布趋势和模式,我们可以对模型进行诊断和改进。
针对残差图再着重介绍一下:
残差图是一种用于分析模型拟合效果的重要工具,其观看方法主要包括以下几个方面:
理解残差图的基本结构:
- 残差图是以某种残差为纵坐标,以其他适宜的量为横坐标的散点图。
- 横坐标的选择有多种,常见的包括因变量的拟合值、某自变量的观察值、观察时间或观察序号等。
观察残差的分布情况:
- 在标准化残差图中,横轴通常是观测值的序号或者回归值,纵轴则是标准化残差。
- 标准化残差应该随机分布在横轴周围,不应呈现出明显的趋势或规律性。
- 如果标准化残差图呈现出一定的规律性(如上升或下降趋势),则可能说明回归模型存在问题,需要进一步分析和修正。
检查是否满足模型假设:
- 通过观察残差图,可以检查模型是否满足线性假设、误差项是否满足正态分布、是否存在异方差性等问题。
- 例如,如果残差图呈现水平带状,说明误差是等方差的假设合适;若呈现漏斗形状,则说明存在异方差性。
分析异常值:
- 残差图还可以用于检查回归线的异常点。通过观察散点的分布,可以识别出那些远离整体趋势的点,这些点可能对应着异常值或异常观测。
解读不同情况:
- 如果大部分点都落在中间部分,只有少数几个点落在外边,则这些点对应的样本可能有异常值存在。
- 如果图形呈现某种特定的形状(如图1(b)或图1©所示),则可能需要在模型中添加高次项或对自变量进行变换。
结合其他统计量进行判断:
- 除了观察残差图的分布情况外,还可以结合一些统计量来判断回归模型的合理性。例如,计算标准化残差的均值和标准差,以检验残差是否符合标准正态分布。
总结:
残差图是进行模型诊断的重要工具,通过观察其分布情况、是否满足模型假设、异常值的存在以及结合其他统计量进行综合判断,可以全面评估回归模型的合理性和准确性。在实际应用中,应充分利用残差图提供的信息,及时发现和解决模型存在的问题,提高模型的拟合效果和预测准确性。
两种模型比较好像都差不多,没有出现明显的规律和趋势,好像没毛病。
可视化两种模型的预测结果:
# 创建一个图形窗口,其中包含1行2列的子图,并设置整个图形的大小为14英寸宽和6英寸高
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(14, 6))
# 可视化线性回归模型的预测结果
# 在第一个子图上绘制测试集的实际数据点,颜色为蓝色,标签为'Actual Data'
axes[0].scatter(X_test, y_test, color='blue', label='实际数据')
# 在第一个子图上绘制线性回归模型对测试集的预测结果,颜色为红色,线宽为2,标签为'Linear Regression'
axes[0].plot(X_test, y_pred_linear, color='red', linewidth=2, label='线性回归')
# 设置第一个子图的标题为“Linear Regression: Actual vs. Predicted Per Capita Income”
axes[0].set_title("线性回归:实际与预测人均收入")
# 设置第一个子图的x轴标签为“Year”
axes[0].set_xlabel("Year")
# 设置第一个子图的y轴标签为“Per Capita Income”
axes[0].set_ylabel("Per Capita Income")
# 在第一个子图上显示图例
axes[0].legend()
# 可视化多项式回归模型的预测结果
# 在第二个子图上绘制测试集的实际数据点,颜色为蓝色,标签为'Actual Data'(注意这里标签是重复的,但通常没问题,因为每个子图都有自己的图例)
axes[1].scatter(X_test, y_test, color='blue', label='实际数据')
# 在第二个子图上绘制多项式回归模型对测试集的预测结果,颜色为红色,线宽为2,标签为'Polynomial Regression'
axes[1].plot(X_test, y_pred_poly, color='red', linewidth=2, label='多项式回归')
# 设置第二个子图的标题为“Polynomial Regression: Actual vs. Predicted Per Capita Income”
axes[1].set_title("多项式回归:实际与预测人均收入")
# 设置第二个子图的x轴标签为“Year”(这里和第一个子图的x轴标签一样,但通常对于对比图是有意义的)
axes[1].set_xlabel("Year")
# 设置第二个子图的y轴标签为“Per Capita Income”(这里和第一个子图的y轴标签一样)
axes[1].set_ylabel("Per Capita Income")
# 在第二个子图上显示图例
axes[1].legend()
# 调整子图之间的参数,使布局更紧凑,避免重叠
plt.tight_layout()
# 显示图形
plt.show()
很明显,多项式回归红线穿数据点穿的多,说明拟合的好。
# 未来预测(多项式回归)
# 创建一个从1970年到2024年的年份数组,并重塑为二维数组(每行一个年份)
future_years = np.arange(1970, 2025).reshape(-1, 1)
# 使用之前定义的多项式特征转换器(假设为poly_features)来转换未来的年份
# 这样它们可以与多项式模型(假设为poly_model)兼容
future_years_poly = poly_features.transform(future_years)
# 使用多项式模型预测未来的年收入
future_predictions_poly = poly_model.predict(future_years_poly)
# 绘制多项式回归的预测值图
plt.figure(figsize=(14, 6)) # 创建一个新的图形窗口,并设置其大小
# 创建一个子图,在1行2列的布局中占据第1个位置
plt.subplot(1, 2, 1)
# 在子图上绘制原始数据的散点图
plt.scatter(df["year"], df["per capita income (US$)"], color='blue', label='实际数据')
# 在子图上绘制多项式回归的预测结果,用红色线条表示
plt.plot(future_years, future_predictions_poly, color='red', label='预测数据(多项式回归)')
# 设置图的标题
plt.title("用多项式回归预测人均收入")
# 设置x轴和y轴的标签
plt.xlabel("Year")
plt.ylabel("Per Capita Income")
# 显示图例
plt.legend()
# 添加网格线
plt.grid(True)
# 未来预测(线性回归)
# 使用线性模型(假设为linear_model)预测未来的年收入
future_predictions_linear = linear_model.predict(future_years)
# 创建一个子图,在1行2列的布局中占据第2个位置
plt.subplot(1, 2, 2)
# 再次绘制原始数据的散点图(这里可以选择只绘制一次原始数据,但出于对比的目的,这里重复了)
plt.scatter(df["year"], df["per capita income (US$)"], color='blue', label='实际数据')
# 在子图上绘制线性回归的预测结果,用绿色线条表示
plt.plot(future_years, future_predictions_linear, color='green', label='预测数据(线性回归)')
# 设置图的标题
plt.title("用线性回归预测人均收入")
# 设置x轴和y轴的标签(注意这里y轴的标签与左侧的图相同)
plt.xlabel("Year")
plt.ylabel("Per Capita Income")
# 显示图例
plt.legend()
# 添加网格线
plt.grid(True)
# 调整子图之间的参数,使布局更紧凑,避免重叠
plt.tight_layout()
# 显示图形
plt.show()
多项式回归模型明显比线性回归模型的泛化能力更强。
# 创建一个包含2022, 2023, 2024年的数组,并重塑为二维数组(每行一个年份)
future_years = np.array([2022, 2023, 2024]).reshape(-1, 1)
# 使用之前定义的多项式特征转换器(假设为poly_features)来转换未来的年份
# 这样它们可以与多项式模型(假设为poly_model)兼容
future_years_poly = poly_features.transform(future_years)
# 使用多项式模型(假设为poly_model)预测未来的年收入
future_predictions_poly = poly_model.predict(future_years_poly)
# 打印预测结果的标题,包括多项式回归的阶数(degree 需要在之前定义)
print("\n预测 (多项式回归 - 阶数", degree, "):")
# 将预测结果转化为pandas DataFrame,包括年份和预测的收入
future_predictions_df = pd.DataFrame({'year': future_years.flatten(), 'income': future_predictions_poly})
# 打印预测结果的DataFrame
print(future_predictions_df)
# 使用线性模型(假设为linear_model)预测未来的年收入
# 注意这里不需要转换年份,因为线性模型直接接受原始年份作为输入
future_predictions_linear = linear_model.predict(future_years)
# 打印预测结果的标题
print("\n预测 (线性回归):")
# 将预测结果转化为pandas DataFrame,包括年份和预测的收入
future_predictions_df_linear = pd.DataFrame({'year': future_years.flatten(), 'income': future_predictions_linear})
# 打印预测结果的DataFrame
print(future_predictions_df_linear)
### 小结
虽然没有数据能够证明哪个回归模型更准,但通过上面的各种验证,可以肯定的是多项式回归在这类数据上是技高一筹的。
点赞、评论、收藏是我创作的动力。