目录
3.2.1 盒形图的离群点分析,需要根据四分位距去掉部分数据点。
与单元线性回归的模型相比,误差减小了,下面继续看看残差分布。
线性回归是一种基本的机器学习算法,用于基于一个或多个自变量预测数字因变量,而因变量(Y)应该是连续的。这里将描述如何在Julia中构建线性回归,构建模型后如何诊断。
1、线性回归概述
线性回归是一种最基本的机器学习算法,用于基于一个或多个自变量预测因变量。因变量(Y)应该是连续的。线性回归找到最能描述Y变量作为X变量(特征)函数的数学方程。方程一旦形成,就可以用来预测只有X已知时Y的值。 这一数学方程可概括如下:
𝑌=𝛽1+𝛽2𝑋+𝜖
其中,𝛽1为截距,𝛽2为斜率。如果只有一个X变量,则称为“简单线性回归”。如果涉及多个预测因子(X),则称为“多元线性回归”。不管怎样,它们的构建过程基本是相同。 𝛽i 称为回归系数,𝜖为误差项 且是 回归模型最不好解释的部分。
2、数据准备
这将使用预期寿命的数据来做回归分析。目标是根据不同的特征和人口统计数据预测各国人民的预期寿命。
数据集下载地址:预期寿命数据。
using DataFrames
using CSV
using Plots
using Lathe
using GLM
using Statistics
using StatsPlots
using MLBase
ENV["COLUMNS"] = 1000
df = DataFrame(CSV.File("D:/Life-Expectancy-Data.csv"))
3、数据探索
在建模之前,首先要分析下数据,看下数据的矩阵大小,检查下是否有missing 值或者异常值。
3.1简单的数据清洗
去掉missing值,如果是线性回归,使用中值或者均值替代missing,会影响分析结果。
#数据框的规格检查
println(size(df))
#(2938, 22)
#规范化列名,很多列名会带有空格或者其他特殊字符
colnames = Symbol[]
for i in string.(names(df))
push!(colnames,Symbol(replace(replace(replace(strip(i)," " => "_"),"-" => "_"), "/" => "_")))
end
rename!(df, colnames);
#数据概要统计分析,比如mean,min,max,median,
#当然还有几个非常重要的信息 ,nunique:w唯一值统计数,nmissing,missing值统计数
ds = describe(df)
names(df)
gd = groupby(df, [:Life_expectancy,],sort= false,skipmissing=true)
fh = DataFrame(gd)
# dropmissing!(fh)
数据概要分析
│ Row │ variable │ mean │ min │ median │ max │ nunique │ nmissing │ eltype │
│ │ Symbol │ Union… │ Any │ Union… │ Any │ Union… │ Union… │ Type │
├─────┼─────────────────────────────────┼───────────┼─────────────┼───────────┼────────────┼─────────┼──────────┼─────────────────────────┤
│ 1 │ Country │ │ Afghanistan │ │ Zimbabwe │ 183 │ │ String │
│ 2 │ Year │ 2007.52 │ 2000 │ 2008.0 │ 2015 │ │ │ Int64 │
│ 3 │ Status │ │ Developed │ │ Developing │ 2 │ │ String │
│ 4 │ Life_expectancy │ 69.319 │ 44.3 │ 72.1 │ 89.0 │ │ 0 │ Union{Missing, Float64} │
│ 5 │ Adult_Mortality │ 163.921 │ 1 │ 144.0 │ 723 │ │ 0 │ Union{Missing, Int64} │
│ 6 │ infant_deaths │ 30.3879 │ 0 │ 3.0 │ 1800 │ │ │ Int64 │
│ 7 │ Alcohol │ 4.62009 │ 0.01 │ 3.77 │ 17.87 │ │ 193 │ Union{Missing, Float64} │
│ 8 │ percentage_expenditure │ 742.76 │ 0.0 │ 66.5539 │ 19479.9 │ │ │ Float64 │
│ 9 │ Hepatitis_B │ 80.968 │ 1 │ 92.0 │ 99 │ │ 544 │ Union{Missing, Int64} │
│ 10 │ Measles │ 2423.73 │ 0 │ 17.0 │ 212183 │ │ │ Int64 │
│ 11 │ BMI │ 38.3002 │ 1.0 │ 43.5 │ 77.6 │ │ 32 │ Union{Missing, Float64} │
│ 12 │ under_five_deaths │ 42.1162 │ 0 │ 4.0 │ 2500 │ │ │ Int64 │
│ 13 │ Polio │ 82.6002 │ 3 │ 93.0 │ 99 │ │ 19 │ Union{Missing, Int64} │
│ 14 │ Total_expenditure │ 5.91715 │ 0.37 │ 5.73 │ 17.6 │ │ 226 │ Union{Missing, Float64} │
│ 15 │ Diphtheria │ 82.4039 │ 2 │ 93.0 │ 99 │ │ 19 │ Union{Missing, Int64} │
│ 16 │ HIV_AIDS │ 1.71779 │ 0.1 │ 0.1 │ 50.6 │ │ │ Float64 │
│ 17 │ GDP │ 7523.47 │ 1.68135 │ 1779.5 │ 119173.0 │ │ 443 │ Union{Missing, Float64} │
│ 18 │ Population │ 1.27937e7 │ 34.0 │ 1.37887e6 │ 1.29386e9 │ │ 644 │ Union{Missing, Float64} │
│ 19 │ thinness__1_19_years │ 4.84456 │ 0.1 │ 3.3 │ 27.7 │ │ 32 │ Union{Missing, Float64} │
│ 20 │ thinness_5_9_years │ 4.8755 │ 0.1 │ 3.3 │ 28.6 │ │ 32 │ Union{Missing, Float64} │
│ 21 │ Income_composition_of_resources │ 0.628373 │ 0.0 │ 0.678 │ 0.948 │ │ 160 │ Union{Missing, Float64} │
│ 22 │ Schooling │ 12.012 │ 0.0 │ 12.4 │ 20.7 │ │ 160 │ Union{Missing, Float64} │
3.2 图形分析数据
3.2.1 盒形图的离群点分析,需要根据四分位距去掉部分数据点。
想来看下概念解析:
四分位距(interquartile range, IQR),又称四分差,四分位距通常是用来构建箱形图,以及对概率分布的简要图表概述。是描述统计学中的一种方法,以确定第三四分位数和第一四分位数的区别。与方差、标准差一样,表示统计资料中各变量分散情形,但四分差更多为一种稳健统计(robust statistic)。
分位数是将总体的全部数据按大小顺序排列后,处于各等分位置的变量值。如果将全部数据分成相等的两部分,它就是中位数;如果分成四等分,就是四分位数;八等分就是八分位数等。四分位数也称为四分位点,它是将全部数据分成相等的四部分,其中每部分包括25%的数据,处在各分位点的数值就是四分位数。四分位数有三个,第一个四分位数就是通常所说的四分位数,称为下四分位数,第二个四分位数就是中位数,第三个四分位数称为上四分位数,分别用Q1、Q2、Q3表示 [2] 。
第一四分位数 (Q1),又称“较小四分位数”,等于该样本中所有数值由小到大排列后第25%的数字。
第二四分位数 (Q2),又称“中位数”,等于该样本中所有数值由小到大排列后第50%的数字。
第三四分位数 (Q3),又称“较大四分位数”,等于该样本中所有数值由小到大排列后第75%的数字。
第三四分位数与第一四分位数的差距又称四分位距(InterQuartile Range,IQR)。公式:IQR = Q3 − Q1,
#####基于盒形图的离群点分析
# Box Plot
boxplot(fh.Life_expectancy, title = "Box Plot - Life Expectancy", ylabel = "Life Expectancy (years)", legend = false)
#很明显的看到有一些 异常值,需要删除这些数据点。
#第25百分位数又称第一个四分位数
first_percentile = percentile(fh.Life_expectancy, 25)
#公式:IQR = Q3 − Q1,四分位距(interquartile range, IQR),又称四分差,四分位距通常是用来构建箱形图,以及对概率分布的简要图表概述。是描述统计学中的一种方法,以确定第三四分位数和第一四分位数的区别。与方差、标准差一样,表示统计资料中各变量分散情形,但四分差更多为一种稳健统计(robust statistic)。
iqr_value = iqr(fh.Life_expectancy)
df = fh[fh.Life_expectancy .> (first_percentile - 1.5*iqr_value),:];
3.2.2 密度图进行分布分析
density(df.Life_expectancy , title = "Density Plot - Life Expectancy", ylabel = "Frequency", xlabel = "Life Expectancy", legend = false)
当y变量为正态分布或接近正态分布时,线性回归效果良好。下面是y变量的分布,即预期寿命。分布确实有几个转折点,这说明分布是混合的。然而,总体分布确实像正太分布的曲线, 为了更好的理解,继续看下一个分析。
3.2.3 散点图相关分析
当y变量与x变量线性相关时,线性回归效果比较好。下面有几个概念需要先理解下,然后再看相关系数和散点图,
VAR(方差) 、COR(相关系数) 、COV(协方差)
方差:体现的是一组数据的波动情况,值越小波动越小。详细参阅:方差
协方差:两种不同数据的方差,体现两组数据的变化趋势如何,正值变化趋势一致,负值变化趋势相反,0不相关。详细参阅:协方差
相关系数:从概率论的角度,分析两组不同数据的相关程度,取值范围[-1,1],越接近与0越不相关,0时却不意味着两组数据独立,相关系数是两个变量之间的线性关联的一个度量,不一定有因果关系的含义。详细参阅:相关系数
println("Correlation of Life Expectancy with Adult Mortality Rate is ", cor(df.Adult_Mortality,df.Life_expectancy), "\n\n")
# Scatter plot
train_plot = scatter(df.Adult_Mortality,df.Life_expectancy, title = "Scatter Plot Life Expectancy vs Adult Mortality Rate", ylabel = "Life Expectancy", xlabel = "Adult Mortality Rate",legend = false)
这两个特征看起来是线性相关的,但仍然有一些点分布在其他位置。在后面我们构建具有多个特性的回归模型时,也许其他一些特征能解释这种关系。
4、数据预处理
数据预处理是建模的重要步骤之一,在前面的步骤中,已经处理了部分异常数据。这里主要针对带有分类的列进行分析。
4.1、独热编码(One Hot Encoding)
独热编码是将类别变量转换为多个数字列的过程,因为有类别。这样,变量就可以被输入到ML算法中,从而更好地进行预测。对于每一个特征,如果它有m个可能值,那么经过独热编码后,就变成了m个二元特征(如成绩这个特征有好,中,差变成one-hot就是100, 010, 001)。数据集中存在两个分类列需要处理。Country列有183个独特的值,分类太多了,只能放弃。相反,对Status列进行独热编码。
scaled_feature = Lathe.preprocess.OneHotEncode(df,:Status)
select!(df, Not([:Status,:Country]))
first(df,5)
4.2、按照比例分割为“测试集”和“训练集”
#安装比例拆分为“测试集”和“训练集”
using Lathe.preprocess: TrainTestSplit
train, test = TrainTestSplit(df,.75)
5、建立模型
这里使用GLM软件包建立线性回归模型,它与R中的“GLM”包非常相似。先从单个变量开始。要使用GLM训练线性回归模型,需要使用lm()函数,它接受公式对象作为第一个参数,使用@formula创建所需的公式对象。
fm = @formula(Life_expectancy ~ Adult_Mortality)
linearRegressor = lm(fm, train)
# StatsModels.TableRegressionModel{LinearModel{GLM.LmResp{Array{Float64,1}},GLM.DensePredChol{Float64,LinearAlgebra.Cholesky{Float64,Array{Float64,2}}}},Array{Float64,2}}
#
# Life_expectancy ~ 1 + Adult_Mortality
#
# Coefficients: 一些评价指标,重点看P值,也就是Pr(>|t|)
# ──────────────────────────────────────────────────────────────────────────────────
# Estimate Std. Error t value Pr(>|t|) Lower 95% Upper 95%
# ──────────────────────────────────────────────────────────────────────────────────
# (Intercept) 78.1938 0.245709 318.237 <1e-99 77.7119 78.6757
# Adult_Mortality -0.053974 0.00119445 -45.1874 <1e-99 -0.0563164 -0.0516316
# ──────────────────────────────────────────────────────────────────────────────────
6 检验标准(模型诊断)
这里给出一些概念:
P值 ,P值越小,表明结果越显著,详细参考:P值
T 值:是t检验bai的统计量值,t检验,亦称student t检验(duStudent's t test),主要用于样本含量较小(例如n < 30),总体标准差σ未知的正态分布。 t检验是用t分布理论来推论差异发生的概率,从而比较两个平均数的差异是否显著。t值是通过将β系数(X变量的权重)除以其标准误差来计算的统计度量。这里的标准误差是对β系数偏差的估计。t值越大说明β系数等于零的可能性越大,所以,t值越高越好。 参考:T值
R平方:
R平方是一种统计测度,它告诉我们在这个模型中,因变量(y)的变化比例是由不同的特征(自变量)解释的。R平方值越高越好,最大值为1。 参考:R平方
# R平方值为,
r2(linearRegressor)
#> 0.4548557168286279
可以看到上面的拟合出的模型已经给出了一些评价指标的值:
# “estimate”是指已获得相关的数据,用某个确定的样本值而得到的一个确切地估计值。
#Std. Error 标准误差,均方根误差是用来衡量观测值同真值之间的偏差,当样本容量n足够大时,标准差趋于稳定。标准误差随着n的增大而减小,甚至趋近于0。
# t value,t值,指的是T检验,主要用于样本含量较小(例如n<30),总体标准差σ未知的正态分布资料。T检验是用t分布理论来推论差异发生的概率,从而比较两个平均数的差异是否显著。
# Pr(>|t|) P值,双侧检验p值
# Lower 95% Upper 95% 置信区间
额外给出一些其他的检验标准:
STATISTIC |
CRITERION |
R-Squared |
Higher the better |
Adj R-Squared |
Higher the better |
F-Statistic |
Higher the better |
Std. Error |
Closer to zero the better |
t-statistic |
Should be greater 1.96 for p-value to be less than 0.05 |
AIC |
Lower the better |
BIC |
Lower the better |
Mallows cp |
Should be close to the number of predictors in model |
MAPE (Mean absolute percentage error) |
Lower the better |
MSE (Mean squared error) |
Lower the better |
Min_Max Accuracy => mean(min(actual, predicted)/max(actual, predicted)) |
Higher the better |
7、 模型预测、评价、交叉验证
涉及到的概念: 参考: 常用的评价误差指标有以下几种,RMSE、MSE、MAE、MAPE、SMAPE
(1)平均误差(MAE)
(2)平均绝对百分比误差(MAPE)
(3)均方根误差(RMSE)
(4)均方误差(MSE)
(5)误差分布,是否符合正太分布
在获取性能度量数据时,将研究训练集数据和测试集数据的评价指标。现在模型已经准备好了,让我们对训练集数据和测试数据以及cmopute平方误差进行预测。
7.1 模型的预测与评价
7.1.1预测
训练集和测试集都要预测
# 预测 Prediction
ypredicted_test = predict(linearRegressor, test)
ypredicted_train = predict(linearRegressor, train)
7.1.2 计算误差
先是平方误差
# 测试数据集评估(计算平方误差)
performance_testdf = DataFrame(y_actual = test[!,:Life_expectancy], y_predicted = ypredicted_test)
performance_testdf.error = performance_testdf[!,:y_actual] - performance_testdf[!,:y_predicted]
performance_testdf.error_sq = performance_testdf.error.*performance_testdf.error
# 训练数据集(计算平方误差)
performance_traindf = DataFrame(y_actual = train[!,:Life_expectancy], y_predicted = ypredicted_train)
performance_traindf.error = performance_traindf[!,:y_actual] - performance_traindf[!,:y_predicted]
performance_traindf.error_sq = performance_traindf.error.*performance_traindf.error ;
误差函数
#MAPE
function mape(performance_df)
mape = mean(abs.(performance_df.error./performance_df.y_actual))
return mape
end
# RMSE
function rmse(performance_df)
rmse = sqrt(mean(performance_df.error.*performance_df.error))
return rmse
end
根据上面的误差函数 计算误差
# 测试数据集 误差
println("Mean Absolute test error: ",mean(abs.(performance_testdf.error)), "\n")
println("Mean Aboslute Percentage test error: ",mape(performance_testdf), "\n")
println("Root mean square test error: ",rmse(performance_testdf), "\n")
println("Mean square test error: ",mean(performance_testdf.error_sq), "\n")
# Mean Absolute test error: 4.871609770224901
# Mean Aboslute Percentage test error: 0.07455633832726753
# Root mean square test error: 6.89521671872878
# Mean square test error: 47.54401359823688
# 训练数据集 误差
println("Mean train error: ",mean(abs.(performance_traindf.error)), "\n")
println("Mean Absolute Percentage train error: ",mape(performance_traindf), "\n")
println("Root mean square train error: ",rmse(performance_traindf), "\n")
println("Mean square train error: ",mean(performance_traindf.error_sq), "\n")
# Mean train error: 4.726124200320006
# Mean Absolute Percentage train error: 0.07258363410210457
# Root mean square train error: 6.716975968260544
# Mean square train error: 45.117766158189674
误差分布
错误不应该有任何模式,它应该遵循正态分布。这里使用直方图对训练和测试数据集进行残差分析
# 误差柱状图,看它是否在测试数据集上正态分布, Histogram of error to see if it's normally distributed on test dataset
histogram(performance_testdf.error, bins = 50, title = "Test Error Analysis", ylabel = "Frequency", xlabel = "Error",legend = false)
# 误差柱状图,看它是否在训练数据集上正态分布, Histogram of error to see if it's normally distributed on train dataset
histogram(performance_traindf.error, bins = 50, title = "Training Error Analysis", ylabel = "Frequency", xlabel = "Error",legend = false)
通过上面的柱状图 可以看到误差是正态分布的,但仍然有一些异常值。下面使用散点图查看实际值和预测值。分析误差是没有特定的形式的
# 测试数据集
test_plot = scatter(performance_testdf[!,:y_actual],performance_testdf[!,:y_predicted], title = "Predicted value vs Actual value on Test Data", ylabel = "Predicted value", xlabel = "Actual value", legend = false)
# 训练数据集
train_plot = scatter(performance_traindf[!,:y_actual],performance_traindf[!,:y_predicted], title = "Predicted value vs Actual value on Train Data", ylabel = "Predicted value", xlabel = "Actual value",legend = false)
可以看到有一些奇怪的点,这里很难数据来解释上面的问题,稍后将在下一节稍后解释这一点。
7.2 交叉验证 Cross Validation
如果拟合出的模型在25%的分割率(测试数据)时表现良好,但并不足以说明模型在任何时候都会表现得同样出色。
这是什么意思呢?一个好的模型在新数据(X vars)上的表现应该和在训练数据上的表现一样好。如果在新数据集上(测试数据和任何未来数据)的预测出的数据和实际数据差异过大,则表明该模型可能过度拟合。也就是说,它对训练数据的解释过于复杂,不够笼统。
因此,尽可能严格地交叉验证模型的准确性是非常重要的。 进行这种严格测试的一种方法是在不同的互斥数据块上进行训练和测试,并观察模型方程的准确性是否一样好。 这就是交叉验证的目的。下面是具体的实现。
# 交叉验证函数,可以使用下面函数 RMSE、MSE、MAE、MAPE、SMAPE
function cross_validation(train,k, fm = @formula(Life_expectancy ~ Adult_Mortality))
a = collect(Kfold(size(train)[1], k))
for i in 1:k
row = a[i]
temp_train = train[row,:]
temp_test = train[setdiff(1:end, row),:]
linearRegressor = lm(fm, temp_train)
performance_testdf = DataFrame(y_actual = temp_test[!,:Life_expectancy], y_predicted = predict(linearRegressor, temp_test))
performance_testdf.error = performance_testdf[!,:y_actual] - performance_testdf[!,:y_predicted]
println("Mean Aboslute Percentage test error: ",mape(performance_testdf), "\n")
end
end
cross_validation(train,10)
验证结果如下:
# Mean Aboslute Percentage test error: 0.07138211558037776
# Mean Aboslute Percentage test error: 0.07010161078533023
# Mean Aboslute Percentage test error: 0.08265067731693981
# Mean Aboslute Percentage test error: 0.06696041707392626
# Mean Aboslute Percentage test error: 0.08010228310894138
# Mean Aboslute Percentage test error: 0.06245662468846822
8、Julia多元线性回归
到目前为止,只使用一个自变量来实现线性回归。但在实际情况下,会有多个自变量,无论是单变量还是多变量,它们的核心概念是一样的,下面是具体的实现。这里使用7个特性来构建回归模型。
8.1 、去掉missing 值,并重新划分数据集
#数据清洗
gd = groupby(df, [:Life_expectancy,:Adult_Mortality,:infant_deaths,:Developing,:BMI,:Total_expenditure,:HIV_AIDS,:Income_composition_of_resources],sort= false,skipmissing=true)
fh = DataFrame(gd)
fh = fh[:,[:Life_expectancy,:Adult_Mortality,:infant_deaths,:Developing,:BMI,:Total_expenditure,:HIV_AIDS,:Income_composition_of_resources]]
disallowmissing(fh)
train, test = TrainTestSplit(fh,.75)
# 回归模型
fm2 = @formula(Life_expectancy ~ Adult_Mortality + infant_deaths + Developing + BMI + Total_expenditure + HIV_AIDS + Income_composition_of_resources)
linearRegressor = lm(fm2, train)
#R 平方
r2(linearRegressor)
与单元线性回归的模型相比,误差减小了,下面继续看看残差分布。
8.2、模型诊断
计算误差
function predict2(linearRegressor::RegressionModel,test::DataFrame,train::DataFrame)
# 诊断
ypredicted_test = predict(linearRegressor, test)
ypredicted_train = predict(linearRegressor, train)
# 测试数据集
performance_testdf = DataFrame(y_actual = test[!,:Life_expectancy], y_predicted = ypredicted_test)
performance_testdf.error = performance_testdf[!,:y_actual] - performance_testdf[!,:y_predicted]
performance_testdf.error_sq = performance_testdf.error.*performance_testdf.error
# 训练数据集
performance_traindf = DataFrame(y_actual = train[!,:Life_expectancy], y_predicted = ypredicted_train)
performance_traindf.error = performance_traindf[!,:y_actual] - performance_traindf[!,:y_predicted]
performance_traindf.error_sq = performance_traindf.error.*performance_traindf.error ;
# 测试数据集误差
println("Mean Absolute test error: ",mean(abs.(performance_testdf.error)), "\n")
println("Mean Aboslute Percentage test error: ",mape(performance_testdf), "\n")
println("Root mean square test error: ",rmse(performance_testdf), "\n")
println("Mean square test error: ",mean(performance_testdf.error_sq), "\n")
# 训练数据集误差
println("Mean train error: ",mean(abs.(performance_traindf.error)), "\n")
println("Mean Aboslute Percentage train error: ",mape(performance_traindf), "\n")
println("Root mean square train error: ",rmse(performance_traindf), "\n")
println("Mean square train error: ",mean(performance_traindf.error_sq), "\n")
end
predict2(linearRegressor,test,train)
# Mean Absolute test error: 3.19108586769138
# Mean Aboslute Percentage test error: 0.04889948491362247
# Root mean square test error: 4.474131472086835
# Mean square test error: 20.01785242951791
# Mean train error: 3.265500683664497
# Mean Aboslute Percentage train error: 0.050029246118041305
# Root mean square train error: 4.4325406761905635
# Mean square train error: 19.647416846083896
所有特征的p值、t值和R平方值均具有统计学意义。从诊断学的角度来看,这是一个很好的模型。比前一个好。下面看看这个模型在预测中的准确性。与单元线性回归的模型相比,误差减小了,下面继续看看残差分布。
残差分布
# 测试数据集残差分布
histogram(performance_testdf.error, bins = 50, title = "Test Error Analysis", ylabel = "Frequency", xlabel = "Error",legend = false)
# 训练数据集的残差分布
histogram(performance_traindf.error, bins = 50, title = "Training Error Analysis", ylabel = "Frequency", xlabel = "Error",legend = false)
这几乎是正态分布的,说明误差分布也比以前的模型好。下面用散点图看看预测值
预测值与实际值非常接近。之前看到的一些怪异的数据点 现在也能通过自变量解释了。
8.3、交叉验证
cross_validation(train,10, fm)
# Mean Aboslute Percentage test error: 0.052744242203262945
# Mean Aboslute Percentage test error: 0.04686934760263142
# Mean Aboslute Percentage test error: 0.04728274263246261
# Mean Aboslute Percentage test error: 0.052746085003455706
# Mean Aboslute Percentage test error: 0.047812645635019635
# Mean Aboslute Percentage test error: 0.051726285166176805
# Mean Aboslute Percentage test error: 0.048795829636545066
# Mean Aboslute Percentage test error: 0.0491212004243017
# Mean Aboslute Percentage test error: 0.049672209036192565
# Mean Aboslute Percentage test error: 0.05783432064238129
明显要比单变量的线性回归误差小很多。
。。。。。待续