系列文章目錄
一、Python二手房價格預(yù)測(一)——數(shù)據(jù)獲取
二、Python二手房價格預(yù)測(二)——數(shù)據(jù)處理及數(shù)據(jù)可視化
前言
? ? ? ? 在上次分享中我們對數(shù)據(jù)進行了部分預(yù)處理和數(shù)據(jù)可視化,接下來將對數(shù)據(jù)完全處理,并且使用幾種基線模型對二手房的價格進行預(yù)測。
一、數(shù)據(jù)處理
? ? ? ? 上次分享中我們將部分數(shù)據(jù)處理成了數(shù)值型數(shù)據(jù),還有部分Object類型數(shù)據(jù)沒有進行處理,先對這些數(shù)據(jù)進行一個處理。
def xiaoquInfo(df, flag):
xiaoqu = json.loads(df['小區(qū)簡介'].replace("'", '"'))
if flag == 1:
return int(xiaoqu['小區(qū)建造年份'][:-1])
elif flag == 2:
return int(xiaoqu['樓棟總數(shù)'][:-1])
else:
return xiaoqu['小區(qū)均價']
data['小區(qū)建造年份'] = data.apply(lambda x:xiaoquInfo(x, 1), axis=1)
data['樓棟總數(shù)'] = data.apply(lambda x:xiaoquInfo(x, 2), axis=1)
data['小區(qū)均價'] = data.apply(lambda x:xiaoquInfo(x, 3), axis=1)
# 剔除一些無用列,其中“戶型分間”列還可以提取一些東西,這里為了節(jié)約時間我就沒做,有能力的同學(xué)可以試著提取一些有效數(shù)據(jù)
data = data[['總價', '單位價格', '樓房信息', '所屬區(qū)縣', '所在樓層', '建筑面積', '戶型結(jié)構(gòu)', '套內(nèi)面積', '建筑類型', '房屋朝向', '建筑結(jié)構(gòu)', '裝修情況', '供暖方式', '配備電梯', '掛牌時間', '交易權(quán)屬', '上次交易', '房屋用途', '房屋年限', '產(chǎn)權(quán)所屬', '抵押信息', '房屋戶型_室', '房屋戶型_廳', '房屋戶型_廚', '房屋戶型_衛(wèi)', '梯戶比例_梯', '梯戶比例_戶', '梯戶比例_比例', '小區(qū)建造年份', '樓棟總數(shù)', '小區(qū)均價']]
# 將列中只含兩種類型的列進行0-1轉(zhuǎn)換,“抵押信息”列中,可以理解為只含“無抵押”和“有抵押”
def diyaInfo(df):
if df['抵押信息'] == '無抵押':
return 0
else:
return 1
data['抵押信息_01'] = data.apply(lambda x:diyaInfo(x), axis=1)
data['產(chǎn)權(quán)所屬'] = data['產(chǎn)權(quán)所屬'].replace("非共有", 0).replace("共有", 1)
# 將數(shù)據(jù)獲取時間定義
data['數(shù)據(jù)獲取日期'] = '2022-04-24'
# 定義一下處理日期型數(shù)據(jù)的函數(shù),將日期轉(zhuǎn)化為時間差值,天為單位
import datetime
def calDate(df, c):
if df[c] == '暫無數(shù)據(jù)':
return np.nan
d1=datetime.datetime.strptime('2022-04-24',"%Y-%m-%d")
d2=datetime.datetime.strptime(df[c],"%Y-%m-%d")
diff_days=d1-d2
return diff_days.days
for c in ['掛牌時間', '上次交易']:
data[c+'差(天)'] = data.apply(lambda x:calDate(x, c), axis=1)
# 有空缺值按均值填充了
for c in ['掛牌時間差(天)', '上次交易差(天)']:
data[c].fillna(data[c].mean(), inplace=True)
# 生成房齡數(shù)據(jù)
data['房齡'] = 2022 - data['樓房信息']
# 將某些列做獨熱編碼處理,房屋朝向這一列含有的類別過多,做獨熱編碼時會使數(shù)據(jù)稀疏,因此,將一些少量類別的數(shù)據(jù)進行合并為“其他朝向”
def calChaoxiang(df):
chaoxiangCol = ['南 北', '南', '東南', '西南', '北 南', '東 南 北', '西北', '西', '東', '南 西 北', '東北', '北', '東 西', '南 北 西', '東 北', '南 北 東']
if df['房屋朝向'] not in chaoxiangCol:
return "其他朝向"
else:
return df['房屋朝向']
data['房屋朝向'] = data.apply(lambda x:calChaoxiang(x), axis=1)
one_hot_col_names = ['所屬區(qū)縣',
'所在樓層',
'戶型結(jié)構(gòu)',
'建筑類型',
'房屋朝向',
'建筑結(jié)構(gòu)',
'裝修情況',
'供暖方式',
'配備電梯',
'交易權(quán)屬',
'房屋用途',
'房屋年限']
# 刪除已處理過的時間列
data.drop(['掛牌時間', '上次交易', '數(shù)據(jù)獲取日期'], axis=1, inplace=True)
one_hot_data = pd.get_dummies(data[one_hot_col_names])
data = pd.concat([data,one_hot_data],axis = 1)
data.drop(one_hot_col_names, axis=1, inplace=True)
# 將處理過的數(shù)據(jù)導(dǎo)出(注意,這一部分代碼要續(xù)上之前分享過的代碼,上次數(shù)據(jù)處理不完全)
data.to_excel("二手房數(shù)據(jù)(處理后).xlsx", index=False)
二、模型訓(xùn)練
1.引入庫
代碼如下:
# 寫在前面,大家可以關(guān)注一下微信公眾號:吉吉的機器學(xué)習(xí)樂園
# 可以通過后臺獲取數(shù)據(jù),不定期分享Python,Java、機器學(xué)習(xí)等相關(guān)內(nèi)容
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.pyplot import MultipleLocator
import sys
import seaborn as sns
import warnings
import math
warnings.filterwarnings("ignore")
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用來正常顯示中文標簽
plt.rcParams['axes.unicode_minus'] = False # 用來正常顯示負號
pd.set_option('display.max_rows', 100,'display.max_columns', 1000,"display.max_colwidth",1000,'display.width',1000)
from sklearn.metrics import *
from sklearn.linear_model import *
from sklearn.neighbors import *
from sklearn.svm import *
from sklearn.neural_network import *
from sklearn.tree import *
from sklearn.ensemble import *
from xgboost import *
import lightgbm as lgb
import tensorflow as tf
from tensorflow.keras import layers
from sklearn.preprocessing import *
from sklearn.ensemble import RandomForestRegressor as RFR
from sklearn.model_selection import *
2.讀入數(shù)據(jù)
代碼如下:
data = pd.read_excel("二手房數(shù)據(jù)(處理后).xlsx", na_values=np.nan)
# 將數(shù)據(jù)劃分輸入和結(jié)果集
X = data[ data.columns[1:] ]
y_reg = data[ data.columns[0] ]
# 切分訓(xùn)練集和測試集, random_state是切分數(shù)據(jù)集的隨機種子,要想復(fù)現(xiàn)本文的結(jié)果,隨機種子應(yīng)該一致
x_train, x_test, y_train, y_test = train_test_split(X, y_reg, test_size=0.3, random_state=42)
3.評價指標
- 平均絕對誤差(MAE)
? ? ? ? 平均絕對誤差的英文全稱為 Mean Absolute Error,也稱之為 L1 范數(shù)損失。是通過計算預(yù)測值和真實值之間的距離的絕對值的均值,來衡量預(yù)測值與真實值之間的距離。計算公式如下:
- 均方誤差(MSE)
? ? ? ? 均方誤差英文全稱為 Mean Squared Error,也稱之為 L2 范數(shù)損失。通過計算真實值與預(yù)測值的差值的平方和的均值來衡量距離。計算公式如下:
- 均方根誤差(RMSE)
? ? ? ? 均方根誤差的英文全稱為 Root Mean Squared Error,代表的是預(yù)測值與真實值差值的樣本標準差。計算公式如下:
- 決定系數(shù)(R2)
? ? ? ? 決定系數(shù)評估的是預(yù)測模型相對于基準模型(真實值的平均值作為預(yù)測值)的好壞程度。計算公式如下:
-
最好的模型預(yù)測的 R2 的值為 1,表示預(yù)測值與真實值之間是沒有偏差的;
-
但是最差的模型,得到的 R2 的值并不是 0,而是會得到負值;
-
當模型的 R2 值為負值,表示模型預(yù)測結(jié)果比基準模型(均值模型)表現(xiàn)要差;
-
當模型的 R2 值大于 0,表示模型的預(yù)測結(jié)果比使用均值預(yù)測得到的結(jié)果要好。
定義評價指標函數(shù):
# 評價指標函數(shù)定義,其中R2的指標可以由模型自身得出,后面的score即為R2
def evaluation(model):
ypred = model.predict(x_test)
mae = mean_absolute_error(y_test, ypred)
mse = mean_squared_error(y_test, ypred)
rmse = math.sqrt(mse)
print("MAE: %.2f" % mae)
print("MSE: %.2f" % mse)
print("RMSE: %.2f" % rmse)
return ypred
4.線性回歸
代碼如下:
model_LR = LinearRegression()
model_LR.fit(x_train, y_train)
print("params: ", model_LR.get_params())
print("train score: ", model_LR.score(x_train, y_train))
print("test score: ", model_LR.score(x_test, y_test))
predict_y = evaluation(model_LR)
輸出結(jié)果:
params: {'copy_X': True, 'fit_intercept': True, 'n_jobs': None, 'normalize': False}
train score: 0.9393799595620992
test score: 0.9369392050137573
MAE: 12.11
MSE: 370.69
RMSE: 19.25
我們可以將 y_test
轉(zhuǎn)換為 numpy
的 array
形式,方便后面的繪圖。
test_y = np.array(y_test)
由于數(shù)據(jù)量較多,我們?nèi)☆A(yù)測結(jié)果和真實結(jié)果的前50個數(shù)據(jù)進行繪圖。
plt.figure(figsize=(10,10))
plt.title('線性回歸-真實值預(yù)測值對比')
plt.plot(predict_y[:50], 'ro-', label='預(yù)測值')
plt.plot(test_y[:50], 'go-', label='真實值')
plt.legend()
plt.show()
上圖中,綠色點為真實值,紅色點為預(yù)測值,當兩點幾乎重合時,說明模型的預(yù)測結(jié)果十分接近。
5.K近鄰
代碼如下:
model_knn = KNeighborsRegressor()
model_knn.fit(x_train, y_train)
print("params: ", model_knn.get_params())
print("train score: ", model_knn.score(x_train, y_train))
print("test score: ", model_knn.score(x_test, y_test))
predict_y = evaluation(model_knn)
輸出結(jié)果:
params: {'algorithm': 'auto', 'leaf_size': 30, 'metric': 'minkowski', 'metric_params': None, 'n_jobs': None, 'n_neighbors': 5, 'p': 2, 'weights': 'uniform'}
train score: 0.8055745690649098
test score: 0.7144393809856229
MAE: 27.67
MSE: 1678.59
RMSE: 40.97
繪圖:
plt.figure(figsize=(10,10))
plt.title('KNN-真實值預(yù)測值對比')
plt.plot(predict_y[:50], 'ro-', label='預(yù)測值')
plt.plot(test_y[:50], 'go-', label='真實值')
plt.legend()
plt.show()
6.決策樹回歸
代碼如下:
model_dtr = DecisionTreeRegressor(max_depth = 5, random_state=30)
model_dtr.fit(x_train, y_train)
print("params: ", model_dtr.get_params())
print("train score: ", model_dtr.score(x_train, y_train))
print("test score: ", model_dtr.score(x_test, y_test))
predict_y = evaluation(model_dtr)
? ? ? ? 在這里,決策樹學(xué)習(xí)器加入了一個 max_depth
的參數(shù),這個參數(shù)限定了樹的最大深度,設(shè)置這個參數(shù)的主要原因是為了防止模型過擬合
輸出結(jié)果:
params: {'criterion': 'mse', 'max_depth': 5, 'max_features': None, 'max_leaf_nodes': None, 'min_impurity_decrease': 0.0, 'min_impurity_split': None, 'min_samples_leaf': 1, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'presort': False, 'random_state': 30, 'splitter': 'best'}
train score: 0.9624891829996234
test score: 0.9457849042838976
MAE: 12.19
MSE: 318.69
RMSE: 17.85
如果不限定樹的最大深度會發(fā)生什么呢?
model_dtr = DecisionTreeRegressor(random_state=30)
model_dtr.fit(x_train, y_train)
print("params: ", model_dtr.get_params())
print("train score: ", model_dtr.score(x_train, y_train))
print("test score: ", model_dtr.score(x_test, y_test))
predict_y = evaluation(model_dtr)
輸出結(jié)果:
params: {'criterion': 'mse', 'max_depth': None, 'max_features': None, 'max_leaf_nodes': None, 'min_impurity_decrease': 0.0, 'min_impurity_split': None, 'min_samples_leaf': 1, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'presort': False, 'random_state': 30, 'splitter': 'best'}
train score: 1.0
test score: 0.9819886071850025
MAE: 4.36
MSE: 105.88
RMSE: 10.29
獲得樹的最大深度:
model_dtr.get_depth()
輸出結(jié)果:
17
? ? ? ? 我們發(fā)現(xiàn),在不限定樹的最大深度時,決策樹模型的訓(xùn)練得分(R2)為:1.0
,但測試得分為:0.9819886071850025
。
? ? ? ? 這就是模型過擬合,在訓(xùn)練數(shù)據(jù)上的表現(xiàn)非常良好,當用未訓(xùn)練過的測試數(shù)據(jù)進行預(yù)測時,模型的泛化能力不足,導(dǎo)致測試結(jié)果不理想。
? ? ? ? 感興趣的同學(xué)可以自行查閱關(guān)于決策樹剪枝的過程。
繪圖:
plt.figure(figsize=(10,10))
plt.title('決策樹回歸-真實值預(yù)測值對比')
plt.plot(predict_y[:50], 'ro-', label='預(yù)測值')
plt.plot(test_y[:50], 'go-', label='真實值')
plt.legend()
plt.show()
7.隨機森林
代碼如下:
model_rfr = RandomForestRegressor(random_state=30)
model_rfr.fit(x_train, y_train)
print("params: ", model_rfr.get_params())
print("train score: ", model_rfr.score(x_train, y_train))
print("test score: ", model_rfr.score(x_test, y_test))
predict_y = evaluation(model_rfr)
輸出結(jié)果:
params: {'bootstrap': True, 'criterion': 'mse', 'max_depth': None, 'max_features': 'auto', 'max_leaf_nodes': None, 'min_impurity_decrease': 0.0, 'min_impurity_split': None, 'min_samples_leaf': 1, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'n_estimators': 10, 'n_jobs': None, 'oob_score': False, 'random_state': 30, 'verbose': 0, 'warm_start': False}
train score: 0.9950702190060036
test score: 0.9891553887607397
MAE: 3.07
MSE: 63.75
RMSE: 7.98
繪圖:
plt.figure(figsize=(10,10))
plt.title('隨機森林-真實值預(yù)測值對比')
plt.plot(predict_y[:50], 'ro-', label='預(yù)測值')
plt.plot(test_y[:50], 'go-', label='真實值')
plt.legend()
plt.show()
8.各模型結(jié)果
重新定義一個評估函數(shù)
def evaluation2(model):
ypred = model.predict(x_test)
mae = mean_absolute_error(y_test, ypred)
mse = mean_squared_error(y_test, ypred)
rmse = math.sqrt(mse)
return ypred, mae, mse, rmse
# 記錄各個模型的誤差及R2得分
maeList = []
mseList = []
rmseList = []
trainr2List = []
testr2List = []
for model in [model_LR, model_knn , model_dtr, model_rfr]:
ypred, mae, mse, rmse = evaluation2(model)
trainr2 = model.score(x_train, y_train)
testr2 = model.score(x_test, y_test)
maeList.append(mae)
mseList.append(mse)
rmseList.append(rmse)
trainr2List.append(trainr2)
testr2List.append(testr2)
# 用折線圖可視化一下
plt.figure(figsize=(8,6))
# plt.subplot(1,3,1)
plt.plot(['線性回歸', 'KNN', '決策樹', '隨機森林'], maeList, '--^', label="MAE")
plt.plot(['線性回歸', 'KNN', '決策樹', '隨機森林'], rmseList, '--*', label="RMSE")
plt.legend()
plt.show()
plt.figure(figsize=(8,6))
# plt.subplot(1,3,2)
plt.plot(['線性回歸', 'KNN', '決策樹', '隨機森林'], mseList, '--o', label="MAE")
plt.legend()
plt.show()
plt.figure(figsize=(8,6))
# plt.subplot(1,3,3)
plt.plot(['線性回歸', 'KNN', '決策樹', '隨機森林'], trainr2List, '--x', label="Train R2")
plt.plot(['線性回歸', 'KNN', '決策樹', '隨機森林'], testr2List, '--v', label="Test R2")
plt.legend()
plt.show()
? ? ? ? 從上面的圖中可以看出,隨機森林模型的整體表現(xiàn)最好(隨機森林yyds)。當然,這不代表最優(yōu)結(jié)果,因為上述幾種模型都沒有進行調(diào)參優(yōu)化,經(jīng)過調(diào)參后的各個模型效果還有可能提升。
三、重要特征篩選
? ? ? ? 為了提升模型效果也可以對數(shù)據(jù)做特征篩選,這里可以通過相關(guān)系數(shù)分析法,將影響二手房售價的特征與售價做相關(guān)系數(shù)計算,pandas包很方便的集成了算法,并且可以通過seaborn、matplotlib等繪圖包將相關(guān)系數(shù)用熱力圖的方式可視化。
# 這里的列取data中,除0-1和獨熱編碼形式的數(shù)據(jù)
corr_cols = list(data.columns[:20])
test_data = data[corr_cols]
test_data_corr = test_data.corr()
price_corr = dict(test_data_corr.iloc[0])
price_corr = sorted(price_corr.items(), key=lambda x: abs(x[1]), reverse=True)
# 輸出按絕對值排序后的相關(guān)系數(shù)
price_corr
輸出結(jié)果,除"總價"外,共有19個特征:
[('總價', 1.0),
('單位價格', 0.8222981290702119),
('小區(qū)均價', 0.7687546742386121),
('建筑面積', 0.7251709920087407),
('套內(nèi)面積', 0.7204559353359032),
('房屋戶型_衛(wèi)', 0.6312862646749),
('房屋戶型_室', 0.5902144343661491),
('房屋戶型_廳', 0.39760899920810316),
('梯戶比例_比例', -0.3133978158118177),
('小區(qū)建造年份', 0.2796377353906572),
('梯戶比例_戶', -0.27384128491689264),
('房齡', -0.22880814058196702),
('樓房信息', 0.2288081405819666),
('上次交易差(天)', -0.1499606404095348),
('抵押信息_01', 0.0854414739678885),
('掛牌時間差(天)', 0.07980048870698007),
('梯戶比例_梯', -0.06639533403491238),
('產(chǎn)權(quán)所屬', 0.05344184918071662),
('房屋戶型_廚', 0.04939490040354267),
('樓棟總數(shù)', -0.04560798084636813)]
繪制熱力圖:
price_corr_cols = [ r[0] for r in price_corr ]
price_data = test_data_corr[price_corr_cols].loc[price_corr_cols]
plt.figure(figsize=(15, 10))
plt.title("相關(guān)系數(shù)熱力圖")
ax = sns.heatmap(price_data, linewidths=0.5, cmap='OrRd', cbar=True)
plt.show()
? ? ? ? 為了驗證相關(guān)系數(shù)分析出來的重要特征是否對模型有效,我們將模型效果最好的隨機森林模型的前19個重要特征輸出。
feature_important = sorted(
zip(x_train.columns, map(lambda x:round(x,4), model_rfr.feature_importances_)),
key=lambda x: x[1],reverse=True)
for i in range(19):
print(feature_important[i])
輸出結(jié)果:
('單位價格', 0.6123)
('建筑面積', 0.2775)
('套內(nèi)面積', 0.0853)
('小區(qū)均價', 0.0121)
('掛牌時間差(天)', 0.0025)
('所屬區(qū)縣_和平', 0.0022)
('房屋戶型_衛(wèi)', 0.001)
('樓棟總數(shù)', 0.001)
('樓房信息', 0.0008)
('梯戶比例_梯', 0.0008)
('小區(qū)建造年份', 0.0006)
('房齡', 0.0006)
('上次交易差(天)', 0.0005)
('戶型結(jié)構(gòu)_暫無數(shù)據(jù)', 0.0004)
('房屋戶型_室', 0.0002)
('梯戶比例_比例', 0.0002)
('抵押信息_01', 0.0002)
('配備電梯_有', 0.0002)
('房屋用途_別墅', 0.0002)
? ? ? ? 將相關(guān)系數(shù)分析出來的重要特征集合與隨機森林的前19個重要特征取交集,并輸出有多少個重復(fù)的特征。
f1_list = []
f2_list = []
for i in range(19):
f1_list.append(feature_important[i][0])
for i in range(1, 20):
f2_list.append(price_corr[i][0])
cnt = 0
for i in range(19):
if f1_list[i] in f2_list:
print(f1_list[i])
cnt += 1
print("共有"+str(cnt)+"個重復(fù)特征!")
輸出結(jié)果:
單位價格
建筑面積
套內(nèi)面積
小區(qū)均價
掛牌時間差(天)
房屋戶型_衛(wèi)
樓棟總數(shù)
樓房信息
梯戶比例_梯
小區(qū)建造年份
房齡
上次交易差(天)
房屋戶型_室
梯戶比例_比例
抵押信息_01
共有15個重復(fù)特征!
? ? ? ?文章來源:http://www.zghlxwxcb.cn/news/detail-806626.html
結(jié)語
? ? ? ? 回歸預(yù)測模型的baseline總是相似的,掌握好套路就可以輕松得到一個baseline結(jié)果,一般都是先將數(shù)據(jù)進行清洗,將非數(shù)值型數(shù)據(jù)通過一些規(guī)則轉(zhuǎn)化成數(shù)值型數(shù)據(jù),再進行多特征的模型訓(xùn)練,一通百通。希望能給大家拋磚引玉,同學(xué)們可以根據(jù)自己的需求處理數(shù)據(jù),不同的處理方法,模型的效果可能也不相同?;貧w問題的預(yù)測寫了有兩個系列的博客了,接下來應(yīng)該不會再更新這方面的內(nèi)容了,嘗試一些新的知識,比如NLP、圖像識別、推薦系統(tǒng)等等。我喜歡根據(jù)問題提出實際的解決方法,從0到1的去實現(xiàn)(掉包除外,hhh,有機會再從頭好好學(xué)學(xué)原理,寫各個模型的源碼才有意思),因此可能就某些問題寫一些實戰(zhàn)。即將畢業(yè),馬上步入工作崗位了,希望能堅持下來寫博客分享的習(xí)慣~文章來源地址http://www.zghlxwxcb.cn/news/detail-806626.html
到了這里,關(guān)于Python二手房價格預(yù)測(三)——二手房價格預(yù)測模型baseline的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!