1.前言

目的:
本文通过多元线性回归与随机森林算法预测笔记本新品的发售价

工具:
语言:Python 3.8
软件:Jupyter Notebook
库:pandas、numpy、matplotlib、statsmodels、sklearn等

2.数据分析

2.1 数据来源

数据爬取自中关村(链接),为2021年上半年笔记本部分品牌新品,经过初步清洗之后,共包含:型号、品牌、价格、屏幕尺寸、分辨率、CPU型号、CPU主频、显卡、内存容量、硬盘容量、重量、操作系统共12个特征。

下载地址见文末。

2.2 探索性分析

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.style.use('fivethirtyeight')
import warnings
warnings.filterwarnings("ignore")

数据加载及预览

# 数据加载
data = pd.read_excel('2021年上半年笔记本新品清单20210622v2.xlsx',index_col=0)
data.head()


查看基本统计信息

data.info()

  • 共586行,12列
  • 部分存在缺失值

描述统计

# 查看连续型变量描述统计
data.describe().round(2)

# 定义一个绘制柱状图函数
def draw_bar(df, title):fig, ax = plt.subplots(figsize=(15, 6))ax.bar(df.index, df.values)ax.set_title(title)for i,j in enumerate(df.values):ax.text(i,j,j,va='bottom',ha='center')plt.xticks(rotation=60)plt.show()

看看各品牌上半年发布的各款式不同配置的数量

df_count = data['品牌'].value_counts()
draw_bar(df_count, '2021年上半年各品牌笔记本新品款式配置数量对比图')

  • 大厂果然是大厂,产品线果然丰富

查看价格分布

plt.figure(figsize=(12,6))
data['价格'].hist(bins=20)
plt.show()

  • 价格呈现右偏分布,中位数价格为8199;
  • 售价最高的可达53000,
  • 售价最低的只有3299

看看5万3的笔记本到底是啥配置

data.query("价格 == 53000")

  • 果然不出意料,是壕无性价比可言的外星人。不过都卖5万多了,内存居然不是64g的,外星人可真有你的哦。

看看各品牌的价格中位数

df_price = data.groupby(['品牌'])['价格'].median().sort_values(ascending=False).astype("int")
draw_bar(df_price, '2021年上半年各品牌新笔记本品中位数价格对比图')

  • 从价格中位数初步判断:

    • 外星人超过25000,毕竟灯大灯亮灯会闪,手动狗头
    • 而ROG在近年推出了魔霸等系列后,竟然有了一丝丝性价比的感觉,价格还不及雷蛇
    • 国产游戏本机械革命、机械师、神舟、雷神价格相差无几,主打性价比
    • 普通家用办公领域的笔记本价格相对游戏本较低不少,VAIO真信仰,价格比同为中高端办公本的微软、ThinkPad还要高一些
    • 几大巨头华硕、戴尔、联想、惠普与近年来新入局的华为、小米 价格相差无几,华硕、戴尔稍高一些
    • 红米、荣耀的价格则竞争他俩大哥的下一级低端市场

屏幕尺寸

df_screen = data['屏幕尺寸'].value_counts().sort_index()
df_screen.index = df_screen.index.astype("str")
draw_bar(df_screen , '2021年上半年各品牌笔记本新品屏幕尺寸分布'

  • 笔记本屏幕尺寸主流大小为14与15.6

分辨率

df_resolution_ratio = data['分辨率'].value_counts()
draw_bar(df_resolution_ratio , '2021年上半年各品牌笔记本新品屏幕分辨率分布')

  • 目前的新品,1080P还是主流,2k、3k、4k也仅限于部分高端机型中。

等等,居然还有1366*768的新品,这都2021年了!看看是哪家还在用如此辣眼睛的分辨率。

data.query("分辨率 == '1366x768'")

  • 366*768居然还卖六千多,人傻钱多戴果然名不虚传

CPU型号

# 筛选出各cpu所属品牌
data['CPU品牌'] = data['CPU型号'].str.split(" ", expand=True)[0]
df_cpu_brand = data['CPU品牌'].value_counts()
draw_bar(df_cpu_brand, '2021年上半年各品牌笔记本新品CPU品牌分布')

  • Intel份额占据四分之三左右,AMD占据四分之一左右
  • 海思和骁龙等移动端cpu的使用数量还是属于极个别

CPU主频

df_cpu_freq = data['CPU主频'].value_counts().sort_index()
df_cpu_freq.index = df_cpu_freq.index.astype("str")
draw_bar(df_cpu_freq, '2021年上半年各品牌笔记本新品CPU主频分布')

  • 2.4GHz占据主流
  • 最高可达3.7GHz
  • 最低仅1.6GHz

来看看1.6GHz是啥处理器并且还有哪些机型再用

data.query("CPU主频 == 1.6")

  • 原来是i5-10210U,荣耀还在用英特尔十代cpu有点不厚道了

顺便看看3.5、3.7GHz的处理器

data.query("CPU主频 >= 3.5")

  • 未来人类yyds
  • 话说作为英特尔旗舰系列,新一代i9-11900K与上一代i9-10900K相比,降主频,还少了两个核心,这波操作是没看懂,难道是觉得十代的牙膏挤多了吸回来嘛

显卡

df_graphics_card = data['显卡'].value_counts()
draw_bar(df_graphics_card, '2021年上半年各品牌笔记本新品显卡分布')

  • 集显数量最多
  • 游戏显卡中,性价比较高的3060数量较多
  • 部分产品还在使用上一代的显卡mx350,2060,甚至上上代的gtx 1650,让我们看看是哪些厂家在帮老黄清库存
data.query("显卡.str.contains('MX 350|GTX|RTX 2')", engine='python')


内存

df_RAM = data['内存容量'].value_counts().sort_index()
df_RAM.index = df_RAM.index.astype("str")
draw_bar(df_RAM, '2021年上半年各品牌笔记本新品内存分布')

  • 主流内存为16G
  • 好家伙,这年头居然还有4G内存的笔记本,手机都不止4G了
data.query("内存容量 == 4")

  • 赛扬配4G,这配置我直呼内行

硬盘容量

df_hard_drive= data['硬盘容量'].value_counts()
draw_bar(df_hard_drive, '2021年上半年各品牌笔记本新品硬盘分布')

  • 硬盘容量512GB SSD为主流,1T SSD也占据了一定的数量
  • 绝大多数新品均采用SSD固态硬盘,没想到在笔记本领域,机械硬盘快被淘汰了

重量

plt.figure(figsize=(12,6))
data['重量'].hist(bins=20)
plt.show()

  • 笔记本重量频数图有两个波峰,说明多数超极本的重量控制在1.5kg左右,家用笔记本与游戏本的重量在2-2.5kg

看看裸机重量超过4kg的笔记本

data.query("重量 >= 4")


果然是GX8的新新新款,这就是习武之人的笔记本新宠嘛,爱了爱了

操作系统

df_system= data['操作系统'].value_counts()
draw_bar(df_system, '2021年上半年各品牌笔记本新品操作系统分布')

  • win10家庭版占据了绝大多数,少部分使用win10专业版,仅有一款笔记本使用国产的统信UOS操作系统
data.query("操作系统 == 'UOS 20'")

  • 在当前的环境下能有采用完全自主的CPU和系统的笔记本,确属不易,希望以后的国产笔记本自主化程度越来越高

2.3 特征工程

2.3.1 缺失值处理

查看缺失值数量与比例

# 查看缺失值数量与比例
(
pd.DataFrame({"NaN_num": round(data.isnull().sum(),2),"NaN_percent":(data.isnull().sum()/data.shape[0]).apply(lambda x:str(round(x*100,2))+'%') ,}).sort_values('NaN_num', ascending=False)
)

  • 存在少量缺失值,保存只有价格缺失的数据,用于后续预测
  • 删除所有缺失值
# 保存只有价格缺失的数据
pred_data = data.loc[(data['价格'].isnull()) & (~data[data.columns.drop('价格')].isna().any(1))]
# 删除缺失值
data.dropna(inplace=True)

2.3.2 相关性检验

查看相关系数图

plt.figure(figsize=(12,10))
sns.heatmap(data.corr(), annot=True, fmt='.2f')
plt.show()

  • CPU主频与价格基本没有相关性,将其删除
  • 屏幕尺寸、重量与价格呈现弱相关性
  • 内存与价格呈现中等强度的相关性
  • 屏幕尺寸与重量呈现强共线性
  • 屏幕尺寸和内存容量虽然是连续型变量,但可将其离散化作为分类变量处理
data.drop('CPU主频', axis=1, inplace=True)

2.3.3 数据转换

由探索性分析可知,价格呈现右偏分布。通常的做法是将其取对数处理,这样做之后因变量就变成了价格增长率,同样具有经济学上的意义。

data['price_ln'] = np.log(data['价格'])
# 查看偏度
print('价格对数转换前偏度:',data['价格'].skew())
# 查看转换后的偏度
print('价格对数转换后偏度:',data['price_ln'].skew())
  • 价格对数转换前偏度: 2.241435349699024
  • 价格对数转换后偏度: 0.6876367722224298

查看转换前后的直方图与QQ图

from scipy import stats
from scipy.stats import norm, skew def norm_test(data):fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(14, 5))sns.distplot(data, fit=norm, ax=ax[0])stats.probplot(data, plot=ax[1])plt.show()
# 转换前
norm_test(data.价格)

转换后

norm_test(data.price_ln)

  • 价格相较于原来更符合正态分布
# 查看重量偏度
data['重量'].skew()
  • 0.6686619530205637
  • 偏度较小,所以就不做对数转换了

2.3.4 异常值处理

价格
数据在符合正态分布时,3倍标准差范围内的数为99.7%,超过这个范围可以认为是小概率事件。

# 3sigma
data[~data['price_ln'].apply(lambda x: np.abs(x - data['price_ln'].mean()) / data['price_ln'].std() <= 3)]

  • 可以看到检测出的两款分别是玩家国度和外星人,除了价格外,各项配置基本拉满。由前面得探索性分析可知,玩家国度和外星人的价格整体偏高,不删除这两项。

重量

# 绘制价格与重量分布散点图
plt.scatter(data['重量'], data['价格'])
plt.xlabel("重量")
plt.ylabel("价格")
plt.show()

  • 存在四个重量大于等于4的强影响点
  • 删除强影响点
data = data.query("重量 < 4")

2.3.5 分箱

查看各分类变量的种类数量

cat_list = ['品牌',  '屏幕尺寸', '分辨率', 'CPU型号', '显卡', '内存容量', '硬盘容量', '操作系统', 'CPU品牌']
data[cat_list].nunique()

  • 可以看到各特征种类数量较多,所以接下来做分箱处理。

定义一个箱线图函数

# 定义一个箱线图函数
def draw_boxplot(feature):fig, ax = plt.subplots(figsize=(15, 6))sns.boxplot(x=feature, y='价格', data=data, ax=ax, linewidth=2)plt.xticks(rotation=60)plt.show()
品牌
draw_boxplot('品牌')

考虑到不同品牌的定位与价格,将电脑种类分为4类

brand_dict = {'game1':['外星人', '雷蛇', '微星', '技嘉', 'ROG'],'game2':['未来人类', '机械革命', '机械师', '神舟', '雷神'],'work1':['VAIO', '微软', 'ThinkPad'],'work2':['联想', '惠普', '华硕', '戴尔', '小米', '华为','宏碁', '荣耀', '红米']}def brand_func(x):for key,values in brand_dict.items():if x in values:res = keyreturn resdata['brand_level'] = data['品牌'].apply(lambda x : brand_func(x))
data['brand_level'].value_counts()

分箱后的数量

屏幕尺寸
draw_boxplot('屏幕尺寸')

  • 不同的屏幕大小代表笔记本的不同定位,一般来说14寸以下的为超极本,性能一般,讲究便携续航
  • 14 为正常家用的笔记本
  • 15.6 及以上的为游戏本居多
  • 所以根据定位与价格,我们将屏幕分为4类
def func(x):if x <= 13.9:res = 's'elif 13.9 < x <15 :res = 'm'elif 15 <= x < 16.2 :res = 'l'else:res = 'xl'return resdata['screen_level'] = data['屏幕尺寸'].apply(lambda x: func(x))
data['screen_level'].value_counts()

分辨率
draw_boxplot('分辨率')

  • 一般来说,按照主流16:9的长宽比,1920×1080为1080p屏,2560×1440为2k屏,3200×1800为3k屏,3840×2160为4k屏。
  • 由于不同机型的屏幕长宽比(4:3、16:9、16:10等)不一样,导致分辨率的品种繁多,厂家的命名也充满噱头,什么2k+,2.5k之类的。
  • 按照价格将其分类
def resolution_func(x):if x in ['2560x1600', '1920x1080', '2160x1440', '2880x1800', '3200x2000', '2240x1400', '2520x1680', '2160x1350','1366x768']:res = '1'elif x in ['2560x1440', '1920x1200', '2256x1504', '3000x2000', '2736x1824', '2496x1664']:res = '2'else:res = '3'return resdata['resolution_level'] = data['分辨率'].apply(lambda x : resolution_func(x))
data['resolution_level'].value_counts()

CPU型号
# 提取处理器类别与最后的字母
tmp = data['CPU型号'].str.extract("\s(.*?\d)\s\d+(\w.?)")
data['CPU分类'] = tmp[0] + ' '+tmp[1]
# 像骁龙、海思处理器上面的正则表达式无法提取,结果为缺失值,所以将其赋值
data['CPU分类'].loc[data['CPU分类'].isnull()] = data['CPU型号']
draw_boxplot('CPU分类')

def cpu_func(x):if x in ['酷睿i7 H', 'Ryzen 9 HX', '酷睿i7 G7','酷睿i9 H', '酷睿i9 HK', 'Ryzen 9 HS',  'Snapdragon 8cx 5G']:res = '1'else:res = '2'return res
data['cpu_level'] = data['CPU分类'].apply(lambda x : cpu_func(x))
data['cpu_level'].value_counts()

显卡
draw_boxplot('显卡')

def card_func(x):if x in ['RTX 3050Ti', 'RTX 3050', 'GTX 1650', '集显', 'MX450', 'MX350', 'GTX 1650Ti']:res = '1'elif x in ['RTX 3060', 'RTX 3070', 'RTX 3060MQ', 'RTX 3070MQ']:res = '2'else:res = '3'return res
data['card_level'] = data['显卡'].apply(lambda x : card_func(x))
data['card_level'].value_counts()

内存
draw_boxplot('内存容量')

  • 4G与64G的数量太少,分别将其归入8G与32G中
def RAM_func(x):if x in [4, 8]:res = '8G及以下'elif x == 16:res = '16G'else:res = '32G及以上'return res
data['RAM_level'] = data['内存容量'].apply(lambda x : RAM_func(x))
data['RAM_level'].value_counts()

硬盘容量
draw_boxplot('硬盘容量')

data = data.query("硬盘容量 != '1TB  HDD机械硬盘'")  # 删除
def hard_disk_func(x):if x in ['512GB  SSD固态硬盘', '256GB  SSD固态硬盘', '512GB+1TB  混合硬盘',  '128GB  SSD固态硬盘']:res = '1'elif x in ['1TB  SSD固态硬盘', '1TB+2TB  混合硬盘', '512GB+1TB  混合硬盘', '512GB+1TB  SSD固态硬盘']:res = '2'else:res = '3'return res
data['hard_disk_level'] = data['硬盘容量'].apply(lambda x : hard_disk_func(x))
data['hard_disk_level'].value_counts()

操作系统
draw_boxplot('操作系统')

  • 删去UOS 20
data = data.query("操作系统 != 'UOS 20' ")
data['system'] = data['操作系统'].str.replace(" ", "_")

查看所有分完类的不同特征的价格是否存在差异

tmp_list = [['RAM_level','brand_level','screen_level','resolution_level'],[ 'system','cpu_level','card_level','hard_disk_level']]fig, ax = plt.subplots(ncols=2, nrows=4, figsize=(16, 20))
for i,j in enumerate(tmp_list):for k, l in enumerate(j): sns.boxplot(x= l, y='价格', data=data, ax=ax[k][i], linewidth=2)

  • 特征内的分类基本都存在差异

2.4 建模

2.4.1 划分测试集验证集

from sklearn.model_selection import train_test_split
train, test = train_test_split(data, test_size=0.2, random_state=42, )

2.4.2 变量筛选

#定义向前逐步回归函数
from statsmodels.formula.api import olsdef forward_select(data, target):variate=set(data.columns)  #将字段名转换成字典类型variate.remove(target)  #去掉因变量的字段名selected=[]current_score,best_new_score=float('inf'),float('inf')  #目前的分数和最好分数初始值都为无穷大(因为AIC越小越好)#循环筛选变量while variate:aic_with_variate=[]for candidate in variate:  #逐个遍历自变量formula="{}~{}".format(target,"+".join(selected+[candidate]))  #将自变量名连接起来aic=ols(formula=formula,data=data).fit().aic  #利用ols训练模型得出aic值aic_with_variate.append((aic,candidate))  #将第每一次的aic值放进空列表aic_with_variate.sort(reverse=True)  #降序排序aic值best_new_score,best_candidate=aic_with_variate.pop()  #最好的aic值等于删除列表的最后一个值,以及最好的自变量等于列表最后一个自变量if current_score>best_new_score:  #如果目前的aic值大于最好的aic值variate.remove(best_candidate)  #移除加进来的变量名,即第二次循环时,不考虑此自变量了selected.append(best_candidate)  #将此自变量作为加进模型中的自变量current_score=best_new_score  #最新的分数等于最好的分数print("aic is {},continuing!".format(current_score))  #输出最小的aic值else:print("for selection over!")breakformula="{}~{}".format(target,"+".join(selected))  #最终的模型式子print("final formula is {}".format(formula))delete_var = [i for i in variate if i not in selected]print("剔除的变量为{}".format(delete_var))  # 打印删除的变量model=ols(formula=formula,data=data).fit()return model
variables = ['重量', 'brand_level', 'screen_level', 'resolution_level', 'cpu_level', 'card_level', 'RAM_level', 'hard_disk_level','system', 'price_ln']
ols_model = forward_select(train[variables], 'price_ln')

  • 向前法剔除了屏幕大小

2.4.3 建模评估

ols_model.summary()

2.4.4 模型解释

默认检验的显著性水平为0.05
1.Prob (F-statistic)远小于0.05,证明模型是线性显著的,回归方程有意义。
2.R2:0.777,表示笔记本价格的77.7%能被其与多个自变量之间的线性关系解释。
3.回归系数coef:

  • 截距系数为8.9582,p值小于0.001,显著,具有统计学意义
  • 品牌类别game2相较game1价格低40.5%,p值小于0.001,差异显著
  • 品牌类别work1相较于game1价格低8.04%,p值为0.220,差异不显著
  • 品牌类别work2相较game1价格低37.55%,p值小于0.001,差异显著
  • 显卡类别2相较类别1价格高21.19%,p值小于0.001,差异显著
  • 显卡类别3相较类别1价格高51.81%,p值小于0.001,差异显著
  • 硬盘类别2相较类别1价格高23.16%,p值小于0.001,差异显著
  • 硬盘类别3相较类别1价格高25.81%,p值小于0.001,差异显著
  • 分辨率类别2相较类别1价格高20.85%,p值小于0.001,差异显著
  • 分辨率类别3相较类别1价格高35.72%,p值小于0.001,差异显著
  • 内存类别32G及以上相较于16G价格高11.03%,p值为0.027,差异显著
  • 内存类别8G及以下相较于16G价格低21.18%,p值小于0.001,差异显著
  • 操作系统专业版比家庭版价格高27.95%,p值小于0.001,差异显著
  • cpu类别2相较于类别1价格低14%,p值为0.01,差异显著
  • 重量系数为0.1522,表示重量每增加1kg,价格上涨15.22%,p值小于0.001,显著,具有统计学意义

4.通过输出ols_model.params得到各特征系数,可构建回归方程

2.4.5 模型诊断

回归分析的基本假定:

  • 1.自变量与因变量线性相关

  • 2.自变量与误差项独立

  • 3.自变量间相互独立。

  • 4.误差项间不存在自相关。

  • 5.误差项的方差齐性。

  • 6.误差项应呈正态分布。

随机误差项是反应总体的误差,残差是反应样本的误差,由于随机误差项是不可观测的,所以一般用残差来估计随机误差项。

由之前的可知连续变量重量与价格有相关性。

自变量与误差项独立即要求没有其他的因素影响因变量,显然笔记本的价格还有其他因素影响,但是我收集到的数据就这些啦,┓( ´∀` )┏摊手。

自变量间相互独立,即要求不存在多重共线性,查看上述的summary可知,模型没有提示存在多重共线性

查看summary中的DW值,为1.994接近2,所以认为模型残差不存在自相关

残差齐性检验
常见的残差分布图如下:

图a为方差齐性的残差分布图

# 残差齐性检验
train_pred = ols_model.predict(train)
train_resid = ols_model.resid
plt.scatter(train_pred, train_resid)
plt.show()

  • 残差图没有明显的趋势,可以认为残差是齐性的

残差正态性检验

# 残差正态性检验---直方图与QQ图
norm_test(train_resid)

  • 残差近似符合正态分布

2.4.6 模型效果评估

from sklearn.metrics import mean_squared_error, r2_score, explained_variance_score
pred_ols_model = ols_model.predict(test)
print('r2_score:', r2_score(test.price_ln, pred_ols_model))
test['pred_price'] = np.power(np.e, pred_ols_model)
print('RMSE:', mean_squared_error(test['价格'], test['pred_price'])** 0.5)
  • r2_score: 0.8224419373780608
  • RMSE: 3499.9002210560184
fig, ax = plt.subplots(figsize=(8,8))
ax.scatter(test['价格'], test['pred_price'])
ax.axline(xy1=(0, 0), xy2=(55000, 55000), linestyle='--', color='red')
ax.set_xlabel('实际价格')
ax.set_ylabel('预测价格')
plt.show()

  • 如果预测完全正确,所有的点应该分布在直线上。
  • 试了几个变量交互项,效果并没有好多少,或许特征分类的时候还可以把品牌等更细分优化下

2.4.7 预测

爬取数据的时候联想拯救者Y900K新款还没公布价格,刚刚去翻了下居然公布价格了,来看看我们的模型预测价格与正式价格差多少。

pred_data.head(1)

pred_df = test.head(1).copy()pred_df['重量'] = 2.5
pred_df['brand_level'] = 'work2'
pred_df['screen_level'] =  'l'
pred_df['cpu_level'] = '1'
pred_df['card_level'] = '2'
pred_df['RAM_level'] = '2'
pred_df['RAM_level'] = '16G'
pred_df['hard_disk_level'] = "2"
pred_df['system'] = "Windows_10_Home"pred_df_value  =  ols_model.predict(pred_df)
print(pred_data.head(1)['型号'].values[0], '的预测价格为:', np.power(np.e, pred_df_value.values[0]))
  • 联想拯救者Y9000K 2021(i7 11800H/16GB/1TB/RTX3060) 的预测价格为: 12169.965728061405
  • 预测值与实际值居然只有170元的差距,狂喜!!!

2.5 随机森林

2.5.1 特征转换

树模型是对一个特征中的多个值或连续值做切分,并找到符合目标最好的切分临界值。这种操作方式,如果在数值型索引下,可直接做切分找最佳临界值;而如果做OneHotEncode后,原有的特征将会“稀疏化”。所以一般树模型特征无需进行OneHotEncode处理。
在本例中,之所以生成哑变量,是因为特征数量不多,哑变量处理后随机森林的效果好于OrdinalEncoder。
哑变量 vs OrdinalEncoder 处理后的R2: 0.8839090140860729 vs 0.8822343994588445
哑变量 vs OrdinalEncoder 处理后的RMSE: 2840.291594098016 vs 2914.578436639172

cat_list = [ 'RAM_level','brand_level', 'screen_level','resolution_level', 'system', 'cpu_level', 'card_level','hard_disk_level']
df = pd.get_dummies(data, columns=cat_list)
# 划分训练集测试集
train_X, train_y = df.loc[train.index,][df.columns[14:].tolist() + ['重量']], df.loc[train.index,].price_ln
test_X, test_y = df.loc[test.index,][df.columns[14:].tolist() + ['重量']], df.loc[test.index,].price_ln

2.5.2 默认参数效果

from sklearn.ensemble import RandomForestRegressor
RFR_model = RandomForestRegressor(random_state=43).fit(train_X, train_y)
RFR_preds = RFR_model.predict(test_X)
print('模型默认参数验证集R2:', r2_score(test_y, RFR_preds))
test['pred_price_RFR'] = np.power(np.e, RFR_preds)
print('模型默认参数验证集RMSE:',mean_squared_error(test['价格'], test['pred_price_RFR'])** 0.5)
  • 模型默认参数验证集R2: 0.8839090140860729
  • 模型默认参数验证集RMSE: 2840.291594098016
  • 比线性回归效果提高了不少

2.5.3 网格搜索法调参

先设定一个步长较大的范围,通过RandomizedSearchCV来初步知道最优的参数的范围,再根据此结果选取邻近的范围,进行GridSearchCV。这样可以提升训练速度。

%%time
from sklearn.model_selection import RandomizedSearchCVRFR = RandomForestRegressor()
# 设置范围
n_estimators = np.arange(1, 1001, 100)
min_samples_split = [2, 7, 12, 17]
min_samples_leaf = [1, 4, 7, 10]
max_depth = [4, 7, 10, 13]
max_features = ['auto','sqrt']
bootstrap = [True,False]
# 需要调整的参数
random_params_group = {'n_estimators': n_estimators,'min_samples_split': min_samples_split,'min_samples_leaf': min_samples_leaf,'max_depth': max_depth,'max_features': max_features,'bootstrap': bootstrap}
# 建立RandomizedSearchCV模型
random_search_model = RandomizedSearchCV(RFR, param_distributions = random_params_group, n_iter = 100,scoring = 'neg_mean_squared_error' ,n_jobs = -1, cv = 3, random_state = 44)
# 训练数据
random_search_model.fit(train_X, train_y)
# 打印最佳参数
random_search_model.best_params_


精确查找

%%time
from sklearn.model_selection import GridSearchCV# 网格搜索调参n_estimators
param_grid = {'n_estimators': [150, 200, 250],'min_samples_split': [2, 3, 4],'min_samples_leaf': [1, 2, 3],'max_depth': [9, 10, 11]}RFR = RandomForestRegressor(random_state = 45)
grid_search_model = GridSearchCV(estimator=RFR, param_grid=param_grid,scoring='neg_mean_squared_error', n_jobs = -1, cv=3)
grid_search_model.fit(train_X, train_y)
# 打印最佳参数
grid_search_model.best_params_

best_model = grid_search_model.best_estimator_
RFR_gs_preds = best_model.predict(test_X)
print('模型默认参数验证集R2:', r2_score(test_y, RFR_gs_preds))
test['pred_price_RFR_gs'] = np.power(np.e, RFR_gs_preds)
print('模型默认参数验证集RMSE:',mean_squared_error(test['价格'], test['pred_price_RFR_gs'])** 0.5)
  • 模型默认参数验证集R2: 0.8898229302217273
  • 模型默认参数验证集RMSE: 2736.917344601958
  • 相比默认参数有所提升,但有限

查看特征重要性

# 创建特征重要性df
feature_importance= (pd.DataFrame({'feature': train_X.columns,'feature_importance': RFR_model.feature_importances_}).sort_values('feature_importance', ascending=False).round(4))# 绘制barh图查看特征重要性排序
plt.figure(figsize=(10, 15))
sns.barplot(x='feature_importance', y='feature', data=feature_importance)
plt.show()

  • 由上图可以看到各个特征对于模型的重要性,可以舍弃不重要的特征再次进行建模

2.5.4 预测

同样对联想拯救者价格预测

RFR_pred_data  = [[1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 2.5]]
RFR_pred_value = best_model.predict(RFR_pred_data)[0]
print(pred_data.head(1)['型号'].values[0], '的随机森林回归预测价格为:', np.power(np.e, RFR_pred_value))
  • 联想拯救者Y9000K 2021(i7 11800H/16GB/1TB/RTX3060) 的随机森林回归预测价格为: 10924.143394391758
  • 个案误差比线性回归要大

3. 总结

本文分别使用线性回归与随机森林算法进行价格预测,线性回归R方0.82、RMSE3499.90,随机森林回归R方0.89、RMSE2736.92,后者效果较好。

数据集下载:
链接:https://pan.baidu.com/s/1H9i59tJ_ezHCxzFpUBUkmA
提取码:m229

码字不易,若对您有所帮助,望能收藏点赞哈!

Python数据分析案例-利用多元线性回归与随机森林回归算法预测笔记本新品价格相关推荐

  1. 随机森林 html5,利用随机森林回归算法预测总有机碳含量

    受美国"页岩气革命"的影响,页岩气勘探受到了大量的关注( 现有利用测井曲线预测TOC的方法有4类:①利用单曲线或多曲线建立回归关系的预测方法,如利用密度曲线(DEN)预测TOC.利 ...

  2. 随机森林回归算法讲解

    随机森林(Random Forest)是一种基于集成学习的机器学习算法,被广泛用于回归问题.它通过使用多个决策树对数据进行建模,并将它们的预测结果进行集成,从而提高了模型的性能和稳定性.在本教程中,我 ...

  3. 基于蜣螂算法改进的随机森林回归算法 - 附代码

    基于蜣螂算法改进的随机森林回归算法 - 附代码 文章目录 基于蜣螂算法改进的随机森林回归算法 - 附代码 1.数据集 2.RF模型 3.基于蜣螂算法优化的RF 4.测试结果 5.Matlab代码 6. ...

  4. 为葡萄酒数据集构造SVM分类器和使用随机森林回归模型预测葡萄酒质量

    目录 前言 一.实验目的 二.实验环境 三.实验内容与结果 1.SVM(support vector Machine)是什么? 2.SVM能干什么? 3.SVM如何实现? 4.独热编码:独热编码(On ...

  5. Scikit-learn_回归算法_随机森林回归算法

    一.描述 随机森林回归模型能够通过组合不同的决策树降低方差,但有时会略微增加偏差.在实际应用中,方差降低通常比偏差增加更加显著,所以随机森林回归模型能够取得更好的效果. 二.用法和参数 n_estim ...

  6. python数据分析案例-利用生存分析Kaplan-Meier法与COX比例风险回归模型进行客户流失分析与剩余价值预测

    目录 1. 概述 1.1 背景 1.2 目的 1.3 数据说明 2. 相关概念 2.1 事件 2.2 生存时间 2.3 删失 2.4 生存概率 2.5 中位生存时间 2.6 风险概率 3. 数据处理 ...

  7. Python数据分析案例-利用AB test分析转化率是否存在差异

    1. AB test简介 AB测试是为Web或App界面或流程制作两个(A/B)或多个(A/B/n)版本,在同一时间维度,分别让组成成分相同(相似)的访客群组(目标人群)随机的访问这些版本,收集各群组 ...

  8. 大数据分析案例-基于多元线性回归算法构建广告投放收益模型

  9. 大数据分析案例-基于多元线性回归算法构建用户信用评分模型

最新文章

  1. 根据总用量计算每种包装规格的购买量和总价
  2. 实现Java集合迭代的高性能
  3. 提高Java开发效率,Idea必装的几款插件
  4. Matplotlib 中文用户指南 8.1 屏幕截图
  5. 深入浅出C/C++中的正则表达式库(一)--GNU Regex Library
  6. Cisco交换机密码忘记重置
  7. 32G内存oracle内核设置,浅谈安装ORACLE时在Linux上设置内核参数的含义
  8. Oracle SqlLoader使用
  9. 武汉大学计算机学院2010情景剧,武汉大学2010届毕业生生源httpwww.xsjy.whu.edu.cn.doc...
  10. 网络安全等级保护细则
  11. Java ist reverse_GKCTF 2020 Reverse Writeup
  12. python爬虫获取下一页_python爬虫获取下一页
  13. KV杀毒软件创始人离世
  14. xmlserializer_更改XmlSerializer输出临时程序集的位置
  15. Chinese-LLaMA-Alpaca:优秀的开源中文语言模型预训练工具
  16. 单片机输入和输出模式简要说明
  17. 初学者古琴入门知识——【唐畅古琴】
  18. 1.mysql字段类型该怎么选择?
  19. (解决)虚拟机黑屏,界面显示:/dev/sda1: clean, xxxxx/yyyyyy files, aaaaaa/bbbbbbb blocks....
  20. 8.编写一个除法计算器,程序具有try-catch-finally结构,程序要求用户输入除数和被除数,在出现除数为零(异常2)和除数、被除数中有一个不是数字(异常1)的情况时进行相应的处理。当调用存放

热门文章

  1. 编程练习:头条校招题
  2. 相忘江湖不如相濡以沫(Ⅰ)
  3. 腾讯企业邮箱不能发送短信认证
  4. node.js基于JavaScript网上商城毕业设计源码261620
  5. adams怎么做往复运动_Adams设置运动函数的具体方法
  6. AutoLine开源平台发布
  7. SpringMVC源码分析迷你书
  8. 谷歌浏览器翻译插件使用不了,替代品 AnyTranslation
  9. 插入摄像头时,系统右下角提示:无法识别的USB设备:跟这台计算机连接的一个USB设备运行不正常...
  10. 【linux】软件管理