Python租房价格分析及预测(xgb+catboost+rf)

         

目录

一、数据介绍

二、统计分析

三、数据预处理

四、回归模型

五、划重点


        早年爬取过我爱我家上北京的部分租房信息,现在重新拿来分析和建模,以往文章大多偏二分类、这次来个数据分析+回归模型的文章。

一、数据介绍

        数据内容包括房子地址、周边情况、交通情况、户型朝向、小区房源情况、看房情况等等信息。数据获取见文末

Python租房价格分析及预测(xgb+catboost+rf)_第1张图片

Python租房价格分析及预测(xgb+catboost+rf)_第2张图片

Python租房价格分析及预测(xgb+catboost+rf)_第3张图片

Python租房价格分析及预测(xgb+catboost+rf)_第4张图片

二、统计分析

        样本量共4w+,包括各地区房源,按照所在区分组、平均租房价格进行倒序排序结果如下,排序基本符合预期

Python租房价格分析及预测(xgb+catboost+rf)_第5张图片

        由于房源的户型面积不一样,所以计算单位面积的月租金(元/月/平方米)查看,排序结果与预期基本一致、越往郊区越便宜

Python租房价格分析及预测(xgb+catboost+rf)_第6张图片

三、数据预处理

        1、序列编码:对于楼层-高低、装修程度、出租方式、有无电梯等有序类别变量和二值变量进行序列编码;

        2、数值提取:对于看房次数提取数值,地铁距离、小区在租/在售房子数量通过正则表达式匹配出相应的数值;

        3、单位面积价格:对于同小区已租房源计算单位面积租金,作为衡量小区价格水平信息的变量;

        4、文本特征:房源特色描述是房东、中介根据房子自身特点填写的文本内容,这里进行简单加工。由于房子优点越多、可能相应的描述会越长,所以提取文本长度;查看内容可以发现,按照空格、逗号、句号、顿号进行断句,每一句对应一个房子特点/优势,所以按照空格、逗号、句号、顿号进行断句,计算句子数量作为特征。

        5、类别型变量编码:以上数据处理后均为数值型变量,还剩所在区, 朝向,楼型, 供暖, 户型结构等类别型变量,这里使用目标编码、按照每一类值对应的房租价格均值进行编码

        6、缺失值处理:本文使用模型之一的rf训练数据不能包含空值,为方便直接使用均值填充

Python租房价格分析及预测(xgb+catboost+rf)_第7张图片

数据预处理代码

import re
def get_subway_distance(string):
    try:
        return re.findall('([1-9]*)米',string)[0]
    except:
        return np.nan
​
def get_build_year(string):
    try:
        return re.findall('年代([1-9]*)年',string)[0]
    except:
        return np.nan
​
def get_community_house_cnt(string):
    try:
        return re.findall('房源([1-9]*)套',string)[0]
    except:
        return np.nan
​
def data_pred(data):
    df=data.copy()
    df['楼层-高低']=df['楼层-高低'].map({'底层':1, '低楼层':2, '中楼层':3, '高楼层':4, '顶层':5})
    df['装修']=df['装修'].str.replace('装修:','').map({'毛坯':1, '简装':2, '精装':3, '豪装':4})
    df['出租方式']=df['出租方式'].str.replace('出租方式:','').map({'合租':1, '整租':2})
    df['电梯']=df['电梯'].str.replace('电梯:','').str.replace('电梯','').map({'无':0,'有':1})
    df['近7天看房次数']=df['近7天看房次数'].str.replace('次','').astype(float)
    df['近30天看房次数']=df['近30天看房次数'].str.replace('次','').astype(float)
​
    df['地铁距离']=df['地铁'].apply(get_subway_distance).replace('',np.nan).astype(float)
    df['建筑年代']=df['建筑年代'].apply(get_build_year).astype(float)
    df['小区在售房源']=df['小区在售房源'].apply(get_community_house_cnt).astype(float)
    df['小区在租房源']=df['小区在租房源'].apply(get_community_house_cnt).astype(float)
​
    df['已租房源1单价']=df['已租房源1_价格'].str.replace('元','').astype(float)/df['已租房源1_面积'].str.replace('m²','').astype(float)
    df['已租房源2单价']=df['已租房源2_价格'].str.replace('元','').astype(float)/df['已租房源2_面积'].str.replace('m²','').astype(float)
    df['已租房源3单价']=df['已租房源3_价格'].str.replace('元','').astype(float)/df['已租房源3_面积'].str.replace('m²','').astype(float)
​
    for i in range(1,6):
        df['特色{}_len'.format(i)]=df['特色{}'.format(i)].str.len()
        df['特色{}_sentence_len'.format(i)]=df['特色{}'.format(i)].str.split('[,;。、;,. ]').str.len()
​
    for col in ['朝向','楼型', '供暖', '户型结构']:
        df[col]=df[col].str.replace(col+':','')
​
    return df
​
df_copy=df.pipe(data_pred)
df_copy.head()
​
y='租金(元/月)'
float_fea=[col for col in df_copy.select_dtypes(include=['int',float]).columns if col!=y]
cate_fea=['所在区', '朝向','楼型', '供暖', '户型结构']
​
def target_encode(df,col):
    return df.groupby([col])[y].mean().to_dict()
​
def data_pred2(data):
    df=data.copy()
    for col in cate_fea:
        df[col]=df[col].map(target_encode(df,col))
    return df
​
def data_pred3(data):
    df=data.copy()
    df[float_fea+cate_fea]=df[float_fea+cate_fea].fillna(df[float_fea+cate_fea].mean())
    return df
​
df_copy2=data_pred2(df_copy)
df_copy3=data_pred3(df_copy2)
print(df_copy3.shape)
df_copy3.head()

四、回归模型

        划分训练集、测试集,分别构建rf、xgboost、catboost回归模型,使用r方、mape进行评估,结果如下

Python租房价格分析及预测(xgb+catboost+rf)_第8张图片

建模代码

def mape_value(y_true,df,model):
    y_pred=model.predict(df)
    r2=r2_score(y_true, y_pred)
    mape=np.mean(np.abs((y_true - y_pred) / y_true))
    return r2,mape
​
def init_params_rf():
    params_rf={
        'n_jobs':4,
        'max_samples':0.8,
        'n_estimators':500,
        'max_depth':4,
        'min_samples_leaf':1,
        'max_features':'auto',
        'min_impurity_decrease':0,
        'bootstrap':True,
        'oob_score':True,
        'random_state':1,
        'verbose':1
    }
    return params_rf
​
def model_train_sklearn_rf(df,y_name,model_fea):
​
    params=init_params_rf()
    model=RandomForestRegressor(**params)
    x_train,x_test, y_train, y_test =train_test_split(df[model_fea],df[y_name],test_size=0.3, random_state=2)
    model.fit(x_train,y_train)
​
    train_r2,train_mape=mape_value(y_train,x_train,model)
    test_r2,test_mape=mape_value(y_test,x_test,model)
​
    model_result={
        'train':y_train.count(),
        'test':y_test.count(),
        'all':df.shape[0],
        'train_r2':train_r2,'train_mape':train_mape,
        'test_r2':test_r2,'test_mape':test_mape
    }
    return model_result,model
​
rf_model_result,rf_model=model_train_sklearn_rf(df_copy3,y,float_fea+cate_fea)
rf_model_result
​
def init_params_xgb():
    params_xgb={
        'objective':'reg:squarederror',
        'eval_metric':'rmse',
        'nthread':4,
        'n_estimators':1400,
        'eta':0.1,
        'max_depth':3,
        'min_child_weight':10,
        'scale_pos_weight':1,
        'gamma':0,
        'reg_alpha':2,
        'reg_lambda':0,
        'subsample':0.8,
        'colsample_bytree':0.9,
        'seed':123
    }
    return params_xgb
​
def xgb_regression(df,y_name,model_fea):
​
    params=init_params_xgb()
    x_train,x_test, y_train, y_test =train_test_split(df[model_fea],df[y_name],test_size=0.3,random_state=2)
​
    model=XGBRegressor(**params)
    model.fit(x_train,y_train,eval_set=[(x_train, y_train),(x_test, y_test)],verbose=False)
    train_r2,train_mape=mape_value(y_train,x_train,model)
    test_r2,test_mape=mape_value(y_test,x_test,model)
​
    model_result={
        'train':y_train.count(),
        'test':y_test.count(),
        'all':df.shape[0],
        'train_r2':train_r2,'train_mape':train_mape,
        'test_r2':test_r2,'test_mape':test_mape
    }
    return model_result,model
​
xgb_model_result,xgb_model=xgb_regression(df_copy2,y,float_fea+cate_fea)
xgb_model_result
​
from catboost import CatBoostRegressor
def init_params_catboost():
    params={
        'loss_function': 'RMSE', # 损失函数,取值RMSE, Logloss, MAE, CrossEntropy, Quantile, LogLinQuantile, Multiclass, MultiClassOneVsAll, MAPE, Poisson。默认Logloss。
    #     'custom_loss': 'F1', # 训练过程中计算显示的损失函数,取值Logloss、CrossEntropy、Precision、Recall、F、F1、BalancedAccuracy、AUC等等
        'eval_metric': 'RMSE', # 用于过度拟合检测和最佳模型选择的指标,取值范围同custom_loss
        'iterations': 1300, # 最大迭代次数,默认500. 别名:num_boost_round, n_estimators, num_trees
        'learning_rate': 0.15, # 学习速率,默认0.03 别名:eta
        'random_seed': 123, # 训练的随机种子,别名:random_state
        'l2_leaf_reg': 0, # l2正则项,别名:reg_lambda
        'bootstrap_type': 'Bernoulli', # 确定抽样时的样本权重,取值Bayesian、Bernoulli(伯努利实验)、MVS(仅支持cpu)、Poisson(仅支持gpu)、No(取值为No时,每棵树为简单随机抽样);默认值GPU下为Bayesian、CPU下为MVS
    #     'bagging_temperature': 0,  # bootstrap_type=Bayesian时使用,取值为1时采样权重服从指数分布;取值为0时所有采样权重均等于1。取值范围[0,inf),值越大、bagging就越激进
        'subsample': 0.6, # 样本采样比率(行采样)
        'sampling_frequency': 'PerTree', # 采样频率,取值PerTree(在构建每棵新树之前采样)、PerTreeLevel(默认值,在子树的每次分裂之前采样);仅支持CPU
        'use_best_model': True, # 让模型使用效果最优的子树棵树/迭代次数,使用验证集的最优效果对应的迭代次数(eval_metric:评估指标,eval_set:验证集数据),布尔类型可取值0,1(取1时要求设置验证集数据)
        'best_model_min_trees': 2000, # 最少子树棵树,和use_best_model一起使用
        'depth': 6, # 树深,默认值6
        'grow_policy': 'SymmetricTree', # 子树生长策略,取值SymmetricTree(默认值,对称树)、Depthwise(整层生长,同xgb)、Lossguide(叶子结点生长,同lgb)
        'min_data_in_leaf': 10, # 叶子结点最小样本量
#         'max_leaves': 12, # 最大叶子结点数量
        'one_hot_max_size': 4, # 对唯一值数量

五、划重点

        关注公众号 Python风控模型与数据分析,回复 房租价格预测 获取本篇数据及代码

你可能感兴趣的:(机器学习,数据分析,数学建模,pandas,机器学习)