分析需求:根据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万元/平。