思路:影响二手汽车售价的特征很多,比如汽车品牌,汽车性能,上牌年限等,同样的零部件参数,不一样的品牌,对应的售价是不一样的,而车的品牌众多,不在参数范围之内,所以本文将售价与新车价的比例作为目标进行分析预测。正文提供了三种求解方法:K近邻,线性回归,随机森林,以均方根误差为评判标准。
一:数据来源
瓜子二手车北京地区的二手车售卖信息。
本数据只包含了基本信息,其它参数未进行爬取。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
df = pd.read_excel("北京二手车.xlsx")
二:数据清洗
1.我们将上牌时间与2018年8月的时间差记为使用时间的长短,并将其他字段的字符中提取有用的信息
df['time'] = '2018-08'
df['使用时间'] = (pd.to_datetime(df.time)-pd.to_datetime(df.上牌时间)).map(lambda x :x.days)#单位是天
df['里程'] = df['里程数'].str[:-3]
df['排标'] = df.排放标准.str[:2]
df['过户次数'] = df.过户次数.str[0].astype(int)
df['售价'] = df.保价.str.strip('万').str[1:].astype(float)
df['原价'] = df.新车价.str.strip('新车指导价').str.strip('万(含税)').astype(float)
df.drop(['上牌时间','time','里程数','排放标准','保价','新车价','车名','时间急迫度'],axis=1,inplace = True)
得到初步清洗的数据
接下来将文字数字化
label_mapping = {"自动": 1, "手动": 0}
df['变速箱'] = df.变速箱.map(label_mapping)
label_mapping = {"国四": 4, "国五": 5,"国三": 3, "国二": 2}
df['排标'] = df.排标.map(label_mapping)
得到如下数据:
因为在日常中进行时间估计不会说我的车开了多少天,基本都是一年半,两年之类的大概说法,想将使用时间按年(365天)换算。一样的指标不一样的品牌也会导致原价不一样,售价不一样,故此我们以售价与原价的占比为目标进行估计。
注意到数据中含有空值,且“原价”字段中空值最多,但数量比较少,故删去。
df.dropna(inplace = True)
df['占比'] = round(df.售价/df.原价,2) #保留2位小数
df['时间'] = round(df.时间/365,1) #保留1一位小数
df.drop(['售价','原价'],axis=1,inplace = True)
得到最终清洗好的数据集干净的数据集
三:特征选取
虽然特征不多,但是也要进行特征工程,特征共线性对于一些模型(比如线性回归)影响很大,也去掉一些无用特征,使得模型的拟合能力更强。
先来看一下各个特征对于目标的分布:
mpl.rcParams['font.sans-serif'] = 'FangSong' # 中文字体设置-仿宋
mpl.rcParams['axes.unicode_minus'] = False
for i in df.columns[:-1]:
fig,ax = plt.subplots(1,1,figsize=(18,4))
df_1=df[['占比',i]].groupby([i],as_index=False).mean()
ax.set_xlabel(i,fontsize = 45)
ax.set_ylabel('占比',fontsize = 45)
sns.barplot(x=i,y='占比',data=df_1)
从上图图看汽车排量对于售价的影响不大,后续建模可以考虑将其删去
上图可知,虽不明显,但是随着过户次数增多,汽车售价降低
由上图发现,自动挡的车比手动挡车更值钱,平均售价更高
很明显,用车年限对于车的售价起着至关重要的作用
行驶的里程数越高,售价越低,对于目标的影响明显
汽车排放标准越高越值钱,排放标准对于售价影响很大
接着来看各个特诊之间是否具有线性关系:
Correlation = pd.DataFrame(df[['排量','过户次数','变速箱','使用时间','里程','排标']])
colormap = plt.cm.viridis
plt.figure(figsize=(14,12))
plt.title('Pearson Correaltion of Feature',y=1.05,size=15)
sns.heatmap(Correlation.astype(float).corr(),linewidths=0.1,vmax=1.0,square=True,cmap=colormap,linecolor='white',annot=True,xticklabels = True)
发现使用年限和里程数具有很大的相关性,后续建模可以考虑去掉里程数这一特征。
四:建模预测
1.KNN模型:
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsRegressor
from sklearn.metrics import mean_squared_error
features = ['排量','过户次数','变速箱','使用时间','里程','排标']
df1 = df.copy()
df1[features] = StandardScaler().fit_transform(df1[features])
df1_train = df1.iloc[:3000]
df1_test = df1.iloc[3000:]
def k():
knn = KNeighborsRegressor(5)
knn.fit(df1_train[features], df1_train['占比'])
predictions = pd.DataFrame(knn.predict(df1_test[features]))
predictions = round(predictions,2)
mse = mean_squared_error(df1_test['占比'], predictions)
rmse = mse ** (1/2)
score = knn.score(df1_test[features], df1_test['占比'])
print(features)
print("rmse",rmse)
print("score",score)
print("-"*40)
可以看到当特征为'过户次数','变速箱','使用时间','里程','排标'时,模型rmse最低
2.sklearn线性回归预测
#线性回归模型
from sklearn import linear_model
features = ['排量','过户次数','变速箱','使用时间','里程','排标']
df2=df.copy()
df2[features] = StandardScaler().fit_transform(df2[features])
df2_train = df2.iloc[:3000]
df2_test = df2.iloc[3000:]
def L():
model = linear_model.LinearRegression()
model.fit(df2_train[features], df2_train['占比'])
result = pd.DataFrame(model.predict(df2_test[features]))
result = round(result,2)
mse = mean_squared_error(df2_test['占比'], result)
rmse = mse ** (1/2)
score = model.score(df2_test[features], df2_test['占比'])
print(features)
print("rmse",rmse)
print("score",score)
print("theta",model.coef_ )
print("截距",model.intercept_)
print("-"*40)
theta值为负,意味着负相关,即过户次数、里程数、使用时间三特征与售价呈负相关,按照我们的常识,当然三手比二手便宜,开的时间和里程越久汽车的价值越低。
3.随机森林
from sklearn.ensemble import RandomForestRegressor
features = ['排量','过户次数','变速箱','使用时间','里程','排标']
df4 = df.copy()
df4[features] = StandardScaler().fit_transform(df4[features])
df4_train = df4.iloc[:3000]
df4_test = df4.iloc[3000:]
rfr = RandomForestRegressor(n_estimators = 20,max_features = 3)
rfr.fit(df4_train[features],df4_train['占比'])
result3 = pd.DataFrame(rfr.predict(df4_test[features]))
mse = mean_squared_error(df4_test['占比'], y_pred)
rmse = mse ** (1/2)
随机森林暂不做调参,本文仅供参考
3.融合以上三个模型:将三个模型的预测值求平均
比较两个模型的rmse值,以['过户次数', '变速箱', '使用时间', '里程', '排标']为特征比较好。
#融合两个模型,以['过户次数', '变速箱', '使用时间', '里程', '排标']为特征
features = ['过户次数','变速箱','使用时间','里程','排标']
df6=df.copy()
df6[features] = StandardScaler().fit_transform(df2[features])
df6_train = df6.iloc[:3000]
df6_test = df6.iloc[3000:]
model = linear_model.LinearRegression()
model.fit(df6_train[features], df6_train['占比'])
result1 = pd.DataFrame(model.predict(df6_test[features]))
knn = KNeighborsRegressor(5)
knn.fit(df6_train[features], df6_train['占比'])
result2 = pd.DataFrame(knn.predict(df6_test[features]))
result = (result1 + result2 + result3)/3
mse = mean_squared_error(df6_test['占比'], result)
rmse = mse ** (1/2)
print(rmse)
rmse 降低至
可见多个模型预测可降低标准差,模型得到优化。
四 总结:
二手车的售价与里程数、已使用年限、过户次数负相关。与排放标准、排量、变速箱正相关,即排量越大(印象里面豪车都是大排量)、自动、排放标准越大售价定的比例更高(排放标准越高代表废气排放越少)。多模型的融合可降低均方根误差。
当然线性方程也可采用梯度下降求解:(仅当做求解参考,可忽视。)
#建立目标函数模型
def model(X, theta):
return np.dot(X, theta.T)
#定义X,y(target)
df3 = df.copy()
df3.insert(0, 'Ones', 1)
df3_train = df3.iloc[:3000]
df3_test = df3.iloc[3000:]
train = df3_train.as_matrix()
test = df3_test.as_matrix()
cols = train.shape[1]
X = train[:,0:cols-1]
y = train[:,cols-1:cols]
#对于theta赋予初始值,这里赋值为0
theta = np.zeros([1, 7]) #一共7个参数
#定义损失函数
def cost(X, y, theta):
errors =(model(X, theta)-y)**2
return np.sum(errors)/len(X)
#计算梯度
def gradient(X, y, theta):
grad = np.zeros(theta.shape)#每一个参数对应一个下降梯度
error = (model(X, theta)- y).ravel()
for j in range(len(theta.ravel())): #for each parmeter
term = np.multiply(error, X[:,j])
grad[0, j] = np.sum(term) / len(X)
return grad
#梯度下降求解
#iters为梯度下降计算次数,以迭代次数为终止策略,当然也能以损失值小于多少为终止策略或以时间限度为终止策略
#alpha为学习率
import time
def descent(X,y,theta,iters,alpha):
i = 0 #迭代次数
init_time = time.time()
grad = np.zeros(theta.shape) # 计算的梯度
costs = [cost(X, y, theta)] # 损失值
while True:
grad = gradient(X, y, theta) #计算梯度
theta = theta - alpha*grad #更新参数theta
costs.append(cost(X, y, theta)) # 计算新的损失
i += 1
if i >iters: #迭代终止
break
return theta, costs, i-1,grad, time.time() - init_time
#梯度下降可视化(损失与迭代次数)
from matplotlib import pyplot as plt
iters = 5000
theta,costs,n,grad,times = descent(X,y,theta,iters,0.0001)
fig, ax = plt.subplots(figsize=(12,4))
ax.plot(np.arange(n+2), costs, 'r')
ax.set_xlabel('Iterations')
ax.set_ylabel('Cost')
ax.set_title( 'Error vs. Iteration (spent %.2f s)'%times)
可以看到学习率偏大,下降的很快,迭代五千次基本收敛于0.01左右。
将alpha 降为 0.00001,得到如下曲线
计算rsme:
#当学习率是0.00001时,theta=array([[0.00750955, 0.01538768, 0.00186182, 0.00707161, 0.0132343 ,0.01844291, 0.03455897]])
X_test = test[:,0:cols-1]
y_test = test[:,cols-1:cols]
mse = mean_squared_error(y_test, model(X_test,theta))
rmse = mse ** (1/2)
#rmse = 0.3092584574922207
对比rsme,梯度下降的值比前两者较大,说明梯度下降还有待优化,考虑到篇幅过长,但此处不再分析优化。
或者直接用线性方程求解:
直接给出theta的求解方程
from numpy.linalg import inv
X = train[:,0:cols-1]
y = train[:,cols-1:cols]
#计算rsme
theta = np.dot(np.dot(inv(np.dot(X.T, X)), X.T), y)
#theta = array([[ 0.56614127],[ 0.00770775],[-0.006578 ],[ 0.03765713],[-0.04862077],[-0.00399016],[ 0.02989723]]
X_test = test[:,0:cols-1]
y_test = test[:,cols-1:cols]
mse = mean_squared_error(y_test, model(X_test,theta.T))
rmse = mse ** (1/2)
#rmse = 0.0839764322871155
线性方程直接求出来的结果和sk-learn算出来的结果十分相似,当线性相关的时候直接用方程求解是十分便利的。但是有一个缺陷:有时候未必有解。
文章可转载,请注明出处!