分析需求:根据2015年某地二手房成交量前15的小区,建立房产估价模型;通过房产位置、面积、朝向、楼层等特征,进行合理估价。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
一、数据预处理-数据和特征选取
# 读取数据
data_list = []
for i in range(1, 8):
try:
data = pd.read_csv('lianjia_dataset/lianjia{}.csv'.format(i), encoding='gbk')
except:
data = pd.read_csv('lianjia_dataset/lianjia{}.csv'.format(i))
finally:
data_list.append(data)
data = pd.concat(data_list)
# 去NaN值
data = data.dropna()
# 查看数据
data.head(3)
1-特征:小区名称、厅室、面积、朝向、楼层,目标:成交单价
# 筛选2015年的数据(布尔过滤)
data = data[data.cjshijian.str.contains('2015-')]
# 特征提取(先提取再处理)
data = data[['cjdanjia', 'cjxiaoqu', 'cjlouceng']]
# 查看提取后的数据
data.head()
# 查看数据类型
data.info()
2-目标列(成交单价)数据类型转换:
# 处理成交单价数据类型
data['cjdanjia'] = data.cjdanjia.str.replace('元/平', '').astype(np.float32).map(lambda x:round(x/10000, 2))
3-判断成交小区形式是否统一,将成交小区解析为小区、户型、面积(分列)并删除原成交小区列:
# 判断成交小区形式是否统一
(data.cjxiaoqu.str.split().map(len)!=3).sum() # 有3个不满足这种形式
# out:3
# 取出满足统一形式的数据(布尔取值)
data = data[data.cjxiaoqu.str.split().map(len)==3]
(data.cjxiaoqu.str.split().map(len)!=3).sum()
# out:0
# 分列
data = data.assign(xiaoqu = data.cjxiaoqu.map(lambda x:x.split()[0]))
data = data.assign(huxing = data.cjxiaoqu.map(lambda x:x.split()[1]))
data = data.assign(mianji = data.cjxiaoqu.map(lambda x:x.split()[2]))
# 删除无价值列
del data['cjxiaoqu']
data.head(3)
4-判断成交小区形式是否统一,将成交楼层解析为朝向、楼层(仅使用此两项特征)并删除原成交楼层列:
# 判断成交楼层形式是否统一
(data.cjlouceng.str.split('/').map(len)!=3).sum() # 结果为统一形式
# out:0
# 分列
data = data.assign(chaoxiang = data.cjlouceng.map(lambda x:x.split('/')[0]))
data = data.assign(louceng = data.cjlouceng.map(lambda x:x.split('/')[1]))
# 删除原列
del data['cjlouceng']
data.head(3)
5-取出成交量前15的小区(成员关系判断):
# 统计,切片,取索引
data.xiaoqu.value_counts()[:15].index
# 成员关系判断,布尔取值
top15 = data.xiaoqu.value_counts()[:15].index
data = data[data.xiaoqu.isin(top15)]
通过以上预处理,获得建模的初始数据。
二、数据预处理-独热编码(特征数值化)
1-异常值处理:
# 1-面积数值化
data.mianji = data.mianji.str.replace('平','').astype(np.float32)
# 2-查看小区异常值
data.xiaoqu.unique()
# 3-查看朝向异常值
data.chaoxiang.unique() # 存在“暂无数据”异常值
# 筛选出朝向不为暂无数据的数据
data = data[data.chaoxiang != '暂无数据']
# 4-查看楼层异常值
data.louceng.unique()
# 5-常看户型异常值
data.huxing.unique()
2-各特征值数值化(独热码one-hot code)
独热码one-hot code:直观来说就是有多少个状态就有多少比特,而且只有一个比特为1,其他全为0的一种码制。
若引入自然数对string类型数据进行编码(如:'低楼层:1', '中楼层:2', '高楼层:3', '地下室:4'),计算机会认为2个中楼层=1个地下室,极大得影响机器学习的准确率,所以应避免引入线性关系。
Pandas提供了独热编码的方法:pd.get_dummies()
# 对单列(Series)独热编码
pd.get_dummies(data.louceng).head()
# 对多列(DataFrame)独热编码
pd.get_dummies(data[['louceng','huxing','xiaoqu']]).head(3)
# 将编码后的数据快速合并到data
data = data.join(pd.get_dummies(data[['louceng','huxing','xiaoqu']]))
data.head(3)
# 查看有多少朝向
len(data.chaoxiang.unique())
# out:27
由于有27个朝向,编码会产生27列数据影响计算效率;再则收集的数据不规范性也造成了麻烦,27种朝向并不包含所有朝向,比如说预测特征中有北南,而现有数据中没有北南则无法预测;
为了增加裸曝性,八个朝向,把所有朝向编码为8个方向:
data['dong'] = (data.chaoxiang.map(lambda x: '东' in x.split())).astype(np.int32)
data['xi'] = (data.chaoxiang.map(lambda x: '西' in x.split())).astype(np.int32)
data['nan'] = (data.chaoxiang.map(lambda x: '南' in x.split())).astype(np.int32)
data['bei'] = (data.chaoxiang.map(lambda x: '北' in x.split())).astype(np.int32)
data['dongnan'] = (data.chaoxiang.map(lambda x: '东南' in x.split())).astype(np.int32)
data['xinan'] = (data.chaoxiang.map(lambda x: '西南' in x.split())).astype(np.int32)
data['dongbei'] = (data.chaoxiang.map(lambda x: '东北' in x.split())).astype(np.int32)
data['xibei'] = (data.chaoxiang.map(lambda x: '西北' in x.split())).astype(np.int32)
# 删除不需要的列
data.drop(data.columns[[1,2,4,5]], axis=1, inplace=True)
data.shape
# out:(1852, 46)
data.head()最终形成1852rows、46columns数据,包含目标列(cjdanjia)以及45列特征
三、多元回归模型搭建
# 准备特征数据
Y = data.cjdanjia
X = data[[x for x in data.columns if x != 'cjdanjia']] # 这里采用双中括号,返回DataFrame结构,是对scikit-learn非常友好的数据形式
# 引入数据划分接口,划分训练集、测试集
from sklearn.model_selection import train_test_split
X_train,X_test,Y_train,Y_test = train_test_split(X, Y)
# 查看划分比例(3:1)
print('X_train:', np.shape(X_train.values))
print('X_test:', np.shape(X_test.values))
# out:X_train: (1389, 45)
# out:X_test: (463, 45)
# 引入线性回归模块
from sklearn.linear_model import LinearRegression
# 初始化模型
model = LinearRegression()
# 训练
model.fit(X_train, Y_train)
# out:LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
# 模型各特征权重(θ1~θ45)
model.coef_
# 模型偏执量
model.intercept_
四、模型评价与模型使用
1-评价模型:
# 导入评价模块
from sklearn import metrics
metrics.mean_squared_error(Y_, Y_test)
# out:0.2677309047184549
mean_squared_error值越小越好,但单纯看它是没有意义的,目的是作为对模型改进的参考,最显而易见的改进方向就是增加或减少特征值。
2-使用模型估价:
思路:采用np.zeros()生成全零的交互特征,根据需求补全相应特征值为1,构造独热码进行输入。
# 构造全零输入
data_test = pd.Series(np.zeros(len([x for x in data.columns if x!='cjdanjia'])), index=[x for x in data.columns if x!='cjdanjia'])
# 根据填表数据构造独热码输入
data_test.mianji = 80
data_test.xiaoqu_天通西苑三区 = 1
data_test.nan = 1
data_test.bei = 1
data_test.huxing_2室2厅 = 1
data_test.louceng_中楼层 = 1
# 预测估价
model.predict(pd.DataFrame(data_test).T)
# out:array([2.72911885])
即,天通西苑三区,面积80平,南北朝向,2室2厅,中楼层的二手房在当年估价为2.7万元/平。