分析北京链家房价数据并对北京房价进行预测
本次课程设计主题为数据分析与机器学习,选择分析北京链家房价数据,通过数据分析,了解整个数据情况,然后使用机器学习算法Lasso选择特征,GBDT回归算法建立模型,对北京房价进行预测。

图1 数据部分展示
整个数据建模的过程为:1、读取数据,了解数据;2、数据预处理;3、使用Lasso回归选择属性;4、建立GBDT回归模型;5、使用新数据进行预测。
- 读取数据
北京房价存储在new.csv文件中,通过pandas库进行数据的读取,过程如图2所示,接着查看数据前五行,并对数据信息进行查看,过程如图3和图4所示,然后查看数据类型dtype等于0的列及其信息,过程如图5所示,删除Cid,id,url等不需要的数据列,查看数据缺失值百分比,过程如图6所示,然后进行数据类型的转换,接着删除缺失值,过程如图2所示。

图2 读取数据

图3 数据的描述统计

图4 查看数据信息

图5 输出dtype等于0的列及其信息

图6 数据缺失值的百分比
绘制DOM列的箱线图和分布图,结果如图7 和图8所示,并用中位数填充DOM列的空值,显示DOM列的百分位数,结果如图9所示。

图7 DOM列箱线图

图8 DOM列分布图

图9 DOM列百分位数
- 数据预处理
接着合并数据,并展示数据,结果如图10和图11所示,输出数据的维度和属性个数,结果如图12所示,对livingRoom、DOM等列转换数据类型,转换为指定的数据类型,然后进行特征编码,对房屋类型、装修、是否有电梯等数据进行转换,然后重新设置索引,过程如图13所示,显示转换的 数据类型,结果如图14所示,显示数据的唯一值,结果如图15所示。然后对totalPrice列进行分析,按区域划分,计算每个区域totalPrice的最大值、最小值、均值等,过程如图16所示,然后删除Lat、Lng列,并将清洗后的数据导出到csv文件中,展示清洗的数据,过程如图18所示,然后显示编码后的数据形状,过程如19所示。

图10 合并之后的数据

图11 显示数据信息

图12 数据的唯一值

图13转换类型

图14 转换后的数据

图15 数据的唯一值

图16 每个区域总价的统计信息

图17 显示数据信息

图18 清洗之后的数据并展示
- 使用Lasso回归选择属性
在进行建立模型之前,还需要选择和totalPrice最相关的属性,这里通过Lasso回归选择和总价最相关的属性,选择Lasso回归系数绝对值大于0的属性。在选择之前,需要先对特征进行编码,因为文本类型的数据无法参与建模,因此先使用独热编码对特征进行编码,编码后的数据形状如图19所示,然后拆分数据集为训练集和测试集,结果如图20所示,再使用Lasso回归建立模型,输出回归模型得分,如图21和图22所示所示,选择Lasso回归系数的绝对值大于0的属性,并输出选择的变量名,结果如图23所示,然后统计选择的变量个数,结果如图24所示。

图19 编码之后的数据形状

图20 拆分数据之后特征和标签的形状

图21 Lasso回归选择得分

图22 Lasso回归得分

图23 Lasso回归选择的变量名

图24 选择的变量个数与维度
- 建立GBDT模型
在选择完属性之后,然后就可以建立GBDT模型,对北京房价进行拟合了,GBDT模型已经集成在了sklearn库当中,因此直接调用相关函数就可以建立相关模型,在建模之前,也要先对特征进行编码,编码之后拆分数据集为训练集和测试集,结果如图25所示,建立模型之后,直接输出模型的score,过程如图26所示,然后保存模型,使用pickle模块,将模型等信息保存到sav文件中。

图25 编码之后数据的形状和拆分后的形状

图26 GBDT模型得分
- 对新的数据进行预测
在保存模型之后,可以对模型等信息进行读取,然后对新的数据进行预测,过程如图27所示。

图27 GBDT模型预测值
#!/usr/bin/env python # coding: utf-8 import sys print(sys.executable) ## 导入库 import numpy as np import pandas as pd import re import datetime as dt import pickle import warnings import matplotlib.pyplot as plt import seaborn as sns ## 导入sklearn模型 from sklearn.linear_model import Ridge, Lasso from sklearn import metrics from sklearn.model_selection import train_test_split,cross_val_score from sklearn.preprocessing import LabelEncoder, scale, OneHotEncoder from sklearn.ensemble import GradientBoostingRegressor ## 设置 warnings.filterwarnings('ignore') plt.style.use(['seaborn-pastel']) # 读取数据 beijing = pd.read_csv("new.csv", parse_dates=["tradeTime"], encoding='gb2312') print(beijing.columns.values) beijing.head() # 数据描述信息 beijing.describe() # 删除不需要的数据列 beijing.drop("Cid", axis = 1, inplace=True) beijing.drop("id", axis = 1, inplace=True) beijing.drop("url", axis = 1, inplace=True) beijing.drop("price", axis = 1, inplace=True) # 显示数据信息 beijing.info() # ## 数据清洗 # 整理变量 for row in range(beijing.shape[1]): if(beijing.iloc[:,row].dtype=="O"): print("{}: {}\n".format(beijing.columns[row],beijing.iloc[:,row].unique())) #提取第11列中的数字 beijing.iloc[:,11] = np.array(beijing.iloc[:,11].str.extract("([0-9]+)")).reshape(-1,1) #列表 sel = [7,8,10,11,13] #遍历列表,把7,8,10,11,13列的数据转换成数值型数据 for s in sel: beijing.iloc[:,s] = pd.to_numeric(beijing.iloc[:,s], errors='coerce') # 删除缺失值 beijing.isna().mean()*100 # 对DOM列进行数据可视化 plt.clf() sns.boxplot(y = beijing["DOM"]) plt.xlabel("DOM") plt.ylabel("distribution") plt.show() plt.clf() sns.kdeplot(data = beijing.loc[:,"DOM"]) plt.show() beijing["DOM"].quantile([0.25,0.5,0.75,1]) # DOM列的缺失值使用DOM列的中位数来填充 beijing["DOM"] = beijing["DOM"].fillna(beijing["DOM"].median()) # 删除有缺失值的行 beijing.dropna(axis = 0, how = "any", inplace = True) # 转换列的类型 beijing = beijing.astype({"DOM":"int64","livingRoom":"int64","drawingRoom":"int64","bathRoom":"int64","floor":"int64", "buildingType":"O","constructionTime":"int64","renovationCondition":"O","buildingStructure":"O", "elevator":"O", "fiveYearsProperty":"O","subway":"O","district":"O"}, errors = 'ignore') # 显示数据信息 beijing.info() # 删除totalPrice列 feature_df = beijing.drop("totalPrice", axis = 1) # 取出totalPrice列 response_df = beijing["totalPrice"] #与feature_df合并totalPrice列 beijing = feature_df.merge(response_df, left_index = True, right_index = True) #显示数据前五行 beijing.head() # 显示数据信息 beijing.info() #输出数据信息 print("Shape of dataframe: {}".format(beijing.shape)) print(beijing.apply(lambda x: x.unique())) # 转换数据类型 beijing = beijing.apply(lambda x: x.astype("int64") if(x.dtype =="O") else x) # 数据编码 # 房屋类型 buildingType = { 1:"Tower", 2:"Bunglow", 3:"Plate/Tower", 4:"Plate" } #装修类型 renovationCondition = { 1:"Other", 2:"Rough", 3:"Simplicity", 4:"Hardcover" } #装修结构 buildingStructure = { 1:"Unavailable", 2:"Mixed", 3:"Brick/Wood", 4:"Brick/Concrete", 5:"Steel", 6:"Steel/Concrete" } #电梯 elevator = { 1:"Present", 0:"Absent" } #地铁 subway = { 1:"Nearby", 0:"Far" } #产权 fiveYearProperty = { 1:"Ownership<5y", 0:"Ownership>5y" } #行政区域 district = { 1 : "DongCheng", 2 : "FengTai", 3 : "DaXing", 4 : "FaXing", 5 : "FangShang", 6 : "ChangPing", 7 : "ChaoYang", 8 : "HaiDian", 9 : "ShiJingShan", 10 : "XiCheng", 11 : "TongZhou", 12 : "ShunYi", 13 : "MenTouGou" } #更改标签 correct_label = { 11:buildingType, 13:renovationCondition, 14:buildingStructure, 16:elevator, 17:fiveYearProperty, 18:subway, 19:district } #遍历更改的标签,并输出信息 for key,val in correct_label.items(): print(key,val) # 把装修类型、行政区域等列的数据更改标签 for key,val in correct_label.items(): beijing.iloc[:,key] = beijing.iloc[:,key].replace(val) #输出数据信息 print(beijing.shape) print(beijing.apply(lambda x: x.unique())) # 更改数据标签,把livingRoom变成0,drawingRoom变成0.. beijing = beijing.astype({"livingRoom":"O","drawingRoom":"O","bathRoom":"O","kitchen":"O"}, errors = 'ignore') # 重新设置索引 beijing.reset_index(inplace=True, drop = True) # 理解数据 names = beijing.columns#列名 #区分数据类型 cat_idx = []#新建空列表 num_idx = []#新建空列表 for i in range(20):#i的范围从0到19 column = beijing.iloc[:,i]#使用iloc定位第i列 if(column.dtype=="O"):#如果列的类型为0 cat_idx.append(i)#把i存入cat_idx列表 print("{}\nUnique values:\n{}\n".format(names[i],column.unique()))#输出信息 elif(column.dtype!='<M8[ns]'):#如果列的类型为<M8[ns] num_idx.append(i)#把i存入num_idx列表 beijing.iloc[:,num_idx[2:]].describe()#提取num_idx从第三列开始的数据,显示统计信息 beijing.iloc[:,cat_idx].describe()#提取cat_idx的数据,显示统计信息 # 按照行政区域划分,求房屋总价的均值、最小值、最大值、中位数 temp = beijing["totalPrice"].groupby(beijing["district"]).agg([np.mean,np.min,np.max,np.median]) temp # 删除Lat列 beijing.drop("Lat", axis = 1, inplace=True) #删除Lng列 beijing.drop("Lng", axis = 1, inplace=True) #显示数据信息 beijing.info() #把dtype数据导出为字典 dataClasses = beijing.dtypes.to_dict() #导出清洗之后的数据 beijing.to_csv('cleaned_beijing.csv', encoding='utf-8') # 数据预处理 #读取清洗之后的数据 beijing = pd.read_csv("./cleaned_beijing.csv", parse_dates=["tradeTime"], index_col = 0, encoding='utf-8') beijing.head() #转换数据 beijing = beijing.astype(dataClasses, errors = 'ignore') #数据dtype等于object的列 cat_mask = beijing.dtypes == object #提取数据dtype等于object的列 cat_cols = beijing.columns[cat_mask].tolist() #独热编码 l_enc = OneHotEncoder(handle_unknown='ignore', sparse = False) l_enc.fit(beijing[cat_cols]) def encode_df(df, enc, cat_cols):#对数据列进行编码 encoded_array = enc.transform(df[cat_cols])#对cat_cols列进行独热编码 #转换为DataFrame列 encoded_df = pd.DataFrame(encoded_array, columns = enc.get_feature_names(input_features = cat_cols)) #删除totalPrice列 feature_df = df.drop("totalPrice", axis = 1) response_df = df["totalPrice"]#取出totalPrice列 #合并feature_df和独热编码之后的数据 feature_df = feature_df.merge(encoded_df, left_index = True, right_index = True).drop(columns = cat_cols, axis=1) #合并feature_df和totalPrice列 df = feature_df.merge(response_df, left_index = True, right_index = True) df['tradeTime'] = df['tradeTime'].astype('datetime64[ns]')#转换数据 df['tradeTime'] = df['tradeTime'].map(dt.datetime.toordinal)#转换数据 return(df)#返回数据 #调用encode_df函数 l_beijing = encode_df(beijing, l_enc, cat_cols) # 数据形状 l_beijing.shape # 数据建模 def split_df(df):#拆分数据 X = df.drop("totalPrice", axis = 1).values#删除totalPrice列,当成x y = df["totalPrice"].values.reshape(-1,1)#取出totalPrice,当成Y return(X,y)#返回数据 X,y = split_df(l_beijing)#调用split_df函数 print(X.shape,y.shape)#输出数据形状 # 拆分数据集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=193) # 使用Lasso回归进行特征选择 #建立Lasso模型 lasso = Lasso(alpha = 0.005, normalize = True) #拟合数据 lasso.fit(X_train, y_train) #对数据预测 lasso_pred = lasso.predict(X_test) #输出得分 score = lasso.score(X_test, y_test) print("Lasso特征选择模型得分:" + str(score)) # 拟合数据 lasso_coef = lasso.fit(X, y).coef_ # 取出0到67的数据属性 names_coef = [x for x in l_beijing.columns[0:68]] #绘制图像 plt.clf() plt.figure(figsize=(20,10)) plt.plot(range(len(names_coef)), lasso_coef) plt.xticks(range(len(names_coef)), names_coef, rotation = 90) for i in range(0,68): plt.axvline(x = i, color = "gray", linestyle='--', alpha = 0.4) plt.axhline(y = 0, color = "skyblue") plt.ylabel("Coefficients") plt.show() # 选择系数绝对值大于0的变量 s_vars = np.where(abs(lasso_coef)>0)[0].tolist() # 输出选择的变量 for i in s_vars: print("{} ".format(names_coef[i],lasso_coef[i])) # 选择的变量名 s_var_names = ['tradeTime', 'square','communityAverage', 'livingRoom', 'drawingRoom', 'kitchen','bathRoom', 'buildingType', 'renovationCondition', 'buildingStructure', 'elevator','fiveYearsProperty', 'subway','district'] len(s_var_names) #定位选择的变量名 beijing_subset = beijing.loc[:,s_var_names+['totalPrice']] print(beijing_subset.shape) #选择dtype等于object的数据 cat_mask = beijing_subset.dtypes == object #选择列名 cat_cols = beijing_subset.columns[cat_mask].tolist() #独热编码 r_enc = OneHotEncoder(handle_unknown='ignore', sparse = False) r_enc.fit(beijing_subset[cat_cols]) r_beijing = encode_df(beijing_subset, r_enc, cat_cols) print("Encoded data:") print(r_beijing.shape) # 拆分数据 X, y = split_df(r_beijing) print(X.shape,y.shape) #拆分数据集为训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=193) # 使用正则化Ridge回归建模 model_parameters = { 'n_estimators': 500, 'max_depth': 6, 'min_samples_split': 5, 'learning_rate': 0.01, 'loss': 'ls' } #GBDT回归 gbReg = GradientBoostingRegressor(**model_parameters) #拟合模型 gbReg.fit(X_train, y_train) score= gbReg.score(X_test, y_test) print("gbdt回归模型得分:" + str(score)) #存储模型 filename = 'sklearn_model.sav' encoder_name = 'one_hot_encoder.sav' pickle.dump(gbReg, open(filename, 'wb')) pickle.dump(r_enc, open(encoder_name, 'wb')) # 加载模型 mdl = pickle.load(open(filename, 'rb')) enc = pickle.load(open(encoder_name, 'rb')) mdl.score(X_test, y_test) beijing.loc[1,s_var_names+['totalPrice']] df_dict = { 'tradeTime':'2023-07-28', 'square':132.38, 'communityAverage':71539, 'livingRoom':'2', 'drawingRoom':'2', 'kitchen':'1', 'bathRoom':'2', 'buildingType':'Tower', 'renovationCondition':'Hardcover', 'buildingStructure':'Steel/Concrete', 'elevator': 'Present', 'fiveYearsProperty':'Ownership<5y', 'subway': 'Far', 'district':'ChaoYang', 'totalPrice': 530 } #创建数据 df = pd.DataFrame(df_dict, index = [0]) #编码 df_enc = encode_df(df, enc, cat_cols) #拆分数据 X, y = split_df(df_enc) print(X.shape,y.shape) print("Predicted value : {}\nActual value : {}".format(mdl.predict(X)[0], y[0][0])) # EDA df = beijing.loc[:,s_var_names+['totalPrice']] print(np.quantile(df["totalPrice"], [0.25,0.5,0.75]))
浙公网安备 33010602011771号