1. 程式人生 > >《投資買房策略》專案分析報告

《投資買房策略》專案分析報告

專案工作思路

整體專案的工作思路包括觀察資料、清洗&轉換資料、建立模型&預測這三大模組。
觀察資料、清洗及轉換資料是實施專案的大前提,主要包括以下操作:

  • 觀察屬性特徵

資料中除了房價之外的屬性一共21項,包括具有地理位置屬性的district、name、address、circle等,也有與房子建築相關的building_type、floor_type、building_structure,還有小區內部相關的的property_fee、greening_rate、first_hand、plot_area等,還有與時間相關的date、age,部分也可以根據常識判斷是否會對房價產生影響。

  • 刪除極端值和無用屬性

房價price的數值特徵如上顯示,最小為2100元 ,最大為239887元,平均5.5萬/平米,沒有出現負值。再分析房價與房齡的散點圖,發現一些偏離的極端值,刪除掉;
floor_type、building_structure、tags屬性的型別混亂,選擇刪除屬性。

這裡寫圖片描述

  • 空缺值&數值變形處理

空缺值:
除了city 、name 、first_hand這三個屬性,其它的屬性都有數值缺失。
price資料的完整度為98.03%,date完整度為99.99%,空缺資料並不多,所以可以直接刪掉空缺的數值;
age 、plot_area等的空缺值使用均值填充;

數值變形:

building_type需要合併型別,由原來的19種類型合併為5種類型,同時進行啞編碼改造,將其每個屬性值轉化為一個二元屬性維度,值對應為0或1;
有些屬性的資料是偏態分佈的,這種資料不均衡會影響演算法準確性,所以需要對偏度較大資料做log變換;
date屬性從字元型改為int型,值更改為距今的月數,以便做相關性分析;
為了實現在統一資料範圍內的考量,需要對資料進行歸一化處理;

  • 建立模型和預測

問題分析與程式碼實現

經過以上資料預處理,綜合回答以下的問題:

1、資料中其餘資訊是否與房價相關?相關性如何?
2、空餘的資訊是否可以通過房價進行預測補全?比如物業費

資料中除了房價之外的屬性一共21項,包括具有地理位置屬性的district、name、address、circle,與房子建築相關的building_type、floor_type、building_structure,還有小區內部相關的的property_fee、greening_rate、first_hand、plot_area等,還有與時間相關的date、age,以上這些屬性根據常識判斷都與房價息息相關。

  • 相關係數分析

資料進行前處理之後,將他們與price做correlation matrix 分析,選擇正相關的屬性,根據相關係數圖表判斷相關程度。
操作結果如下:
old number of features: 17
drop columns: [‘households’, ‘users’, ‘greening_rate’, ‘date’, ‘hot’, ‘building_type_BL’, ‘building_type_TL’, ‘building_type_others’, ‘first_hand_true’]
New number of features : 8
這裡寫圖片描述
從相關性係數計算結果和作圖分析:在原來17個屬性中,刪除了9個非正相關屬性,包括’households’, ‘users’, ‘greening_rate’, ‘date’, ‘hot’, ‘building_type_BL’, ‘building_type_TL’, ‘building_type_others’, ‘first_hand_true’。經過選取的屬性中,與price相關性較大的是for_rent、deal、age、property_fee、plot_area等屬性。
經過預處理得到的相關係數結果看出來,property_fee、or_rent、deal、age、plot_area等這些屬性都與房價有一定的相關性,適合用房價來補全。

  • 房價和地理位置

資料中前幾個屬性district、name、address、circle,都含有地理位置資訊,但是district 和 circle欄位的內部分類不統一,既有區縣(如:朝陽、海淀),也有商區(如:西單、新街口)和其它型別等,name(樓盤)雖然沒有缺失值,但是樓盤名稱並非唯一,不同的城市可能具有同樣名稱的樓盤,想要定位到真實的點位會有偏差。最終,選擇具有唯一性的address屬性,刪除掉18.94%的空缺值,探討地理位置與房價的關係。

想要探討北京房價與地理位置的關係,我們先排除時間變化的影響,即探討在同一時期的房源裡,房價與地理位置的關係。將資料按照date分為11組,提取其中三組做對比分析,選擇2016年1月1日、2016年8月1日和2017年6月1日這三個時間點資料的address和price屬性,藉助百度地圖的api介面,在地圖上呈現房價熱力圖如下(熱力圖引數設定為統一標準):

這裡寫圖片描述
圖1–2016年1月1日房價熱力圖(左圖:北京全景,右圖:北京五環內)

這裡寫圖片描述
圖1–2016年8月1日房價熱力圖(左圖:北京全景,右圖:北京五環內)

這裡寫圖片描述
圖3–2017年6月1日房價熱力圖(左圖:北京全景,右圖:北京五環內)

從以上房價熱力圖可以觀察到:三個時間點下的房價空間變化特徵相似,高房價主要集中在四環內。其中,以西城區、海淀區、朝陽區、東城區為代表,西城區的房價最高,以北海公園、西四和金融街附近的房價為代表,海淀學區整體房價較高,朝陽區東三環附近的國貿等CBD區域也是高房價熱點。望京、國家體育場、北京南站附近房價也相對較高。
結合北京房價的空間特徵來看,金融中心、旅遊景區、學區房、火車站等所代表的經濟、教育、交通等資源因素是影響北京房價最重要的因素。

  • 程式碼部分

資料前處理:

import pandas as pd
import numpy as np
from sklearn.model_selection import cross_val_score, train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, make_scorer
from scipy.stats import norm, skew
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats.stats import pearsonr

import math
def rmsle(y, y_pred):
    return  np.sqrt(mean_squared_error(y,y_pred))

%config InlineBackend.figure_format = 'retina' #set 'png' here when working on notebook
%matplotlib inline

pd.set_option('display.float_format', lambda x: '%.3f' % x)
data=pd.read_csv('D://fangjia.tsv',sep='\t')
#print (data)


#尋找並去除極端值
plt.scatter(data['age'],data['price'])
plt.xlabel("age")
plt.ylabel("price")
plt.show()

data = data[data.price<220000]
age_drop_index=data[data.age>100].index
data.drop(age_drop_index,axis=0, inplace=True)

data.price = np.log1p(data.price)

#data.describe()
#刪除部分屬性
data.drop(['city', 'district', 'name', 'address', 'circle', 'floor_type',
            'building_structure', 'tags'], axis=1, inplace=True)

#空缺資料
data=data[data['price'].notnull()]
data=data[data['date'].notnull()]
'users', 'greening_rate', 'date', 'hot',
data.loc[:, "age"] = data.loc[:, "age"].fillna(data['age'].mean())
data.loc[:, "plot_area"] = data.loc[:, "plot_area"].fillna(data['plot_area'].mean())
data.loc[:, "households"] = data.loc[:, "age"].fillna(data['households'].mean())
data.loc[:, "users"] = data.loc[:, "age"].fillna(data['users'].mean())
data.loc[:, "greening_rate"] = data.loc[:, "age"].fillna(data['greening_rate'].mean())
data.loc[:, "hot"] = data.loc[:, "age"].fillna(data['hot'].mean())
data.loc[:, "for_sale"] = data.loc[:, "age"].fillna(data['for_sale'].mean())
data.loc[:, "for_rent"] = data.loc[:, "age"].fillna(data['for_rent'].mean())
data.loc[:, "deal"] = data.loc[:, "age"].fillna(data['deal'].mean())




#資料變形
data = data.replace({"building_type": {'塔樓|板樓': "BTJH", '板樓|板塔結合': "BTJH", '塔樓|板樓|板塔結合': "BTJH", '塔樓|板塔結合': "BTJH",
                                      '聯排|獨棟': "others", "雙拼|聯排|獨棟": "others", '聯排|疊拼': "others", '雙拼|獨棟': "others",'板樓|磚樓':'磚樓',
                                       '雙拼|聯排|獨棟|疊拼': "others", '聯排|獨棟|疊拼': "others", '雙拼|聯排|疊拼': "others",
                                      '雙拼|疊拼': "others", '獨棟|疊拼': "others",'板樓|磚樓':'ZL','雙拼|聯排': "others",'板樓':'BL','塔樓':'TL','板塔結合':'BTJH',}})

data.loc[:, "first_hand"] = data.loc[:, "first_hand"].astype('int')
data = data.replace({"first_hand": {0:'false',1:'true'}})
#data.building_type.value_counts()

#date處理
import datetime
starttime = datetime.datetime.now()
data.loc[:, 'date'] = pd.to_datetime(data['date'],format='%Y-%m')
from dateutil import rrule
import datetime
starts = data['date']
end = datetime.datetime.now()
l1=[]

for s in starts: #計算賣房時間距今的時間差
    months = rrule.rrule(rrule.MONTHLY, dtstart=s, until=end).count()
    l1.append(months)

data['date']=l1

#將資料劃分為數值型與字元型
categorical_features = data.select_dtypes(include=["object"]).columns
numerical_features = data.select_dtypes(exclude=["object"]).columns
y = data[numerical_features].price
numerical_features = numerical_features.drop("price")
data_num = data[numerical_features]
data_cat = data[categorical_features]

#計算數值型屬性偏度,選擇偏度過大的屬性做取對數處理
skewness = data_num.apply(lambda x: skew(x))
skewness = skewness[abs(skewness) > 0.5]
print(str(skewness.shape[0]) + " skewed numerical features to log transform")
skewed_features = skewness.index
data_num[skewed_features] = np.log1p(data_num[skewed_features])
#print(data_num[skewed_features])

#對字元型資料用啞編碼的方式轉換為定量特徵
data_cat = pd.get_dummies(data_cat)


data = pd.concat([data_num, data_cat], axis=1)

print(data)
#資料標準化處理
stdSc = StandardScaler()
data.loc[:, numerical_features] = stdSc.fit_transform(data.loc[:, numerical_features])
#選擇相關性較大的屬性
corr = pd.concat([data,y],axis=1).corr()
drop_columns = list(corr['price'].loc[corr['price'] < 0,].index)
print('old number of features:',str(data.shape[1]))
print('drop columns:',drop_columns)
data.drop(drop_columns,axis=1, inplace=True)
print("New number of features : " + str(data.shape[1]))
#畫出相關係數圖
corr_new = pd.concat([data,y],axis=1).corr()
f,ax=plt.subplots(figsize=(12,9))
sns.heatmap(corr_new, vmax=0.9, square=True)

#X_train, X_test, y_train, y_test = train_test_split(data, y, test_size=0.3, random_state=0)

房價熱力圖:提取資料的address和price屬性,藉助百度地圖的api介面,在百度地圖上呈現房價熱力圖:

import json
from urllib.request import urlopen, quote
import requests,csv

#根據不同時間點生成某月的房價資料 
l1=data['date'].value_counts().index.sort_values(ascending=True)

for i in l1:
    data=data[data['date']==i]
    data.to_csv('D://'+str(i)[:7]+'.csv',index=False,columns=['address','price'])


#定義獲取經緯度資料的函式
def getlnglat(address):
    url = 'http://api.map.baidu.com/geocoder/v2/'
    output = 'json'
    ak = 'DD279b2a90afdf0ae7a3796787a0742e'
    add = quote(address) #為防止地址中的中文亂碼
    uri = url + '?' + 'address=' + add  + '&output=' + output + '&ak=' + ak
    req = urlopen(uri)
    res = req.read().decode() #將其他編碼的字串解碼成unicode
    temp = json.loads(res) 
    return temp

#提取json檔案中的經緯度資料
file = open('D:\\point1.json','w')
with open('D:\\2016-01.csv', 'r',encoding='gbk') as csvfile: #開啟csv
    reader = csv.reader(csvfile)
    for line in reader: 
        # 忽略第一行屬性標籤
        if reader.line_num == 1: 
            continue
            # line是個list,取得所有需要的值
        b = line[0].strip() 
        c = line[1].strip()
        lng_lat = getlnglat(b) #採用構造的函式來獲取經度
        lng2=lng_lat.keys()
        if 'result' in lng2: #當是有錯誤資料時,就沒有“result”屬性,程式會跳出報錯
            lng = lng_lat['result']['location']['lng']
            lat = lng_lat['result']['location']['lat']
        else:
            continue

        str_temp = '{"lat":' + str(lat) + ',"lng":' + str(lng) + ',"count":' + str(c) +'},'
        print(str_temp)#把資料copy到百度熱力地圖api的相應位置上
        file.write(str_temp) #寫入文件
file.close() #儲存

3、房價是否可以通過房齡、綠化率、物業費等進行預測?

  • 程式碼部分
scorer = make_scorer(mean_squared_error, greater_is_better=False)
#定義交叉驗證模式下的模型均方根誤差函式
def rmse_cv(model, X, Y):
    rmse = np.sqrt(-cross_val_score(model, X, Y, scoring=scorer, cv=10))
    return (rmse)

#定義線性迴歸函式
def linear_regression():
    lr = LinearRegression()
    lr.fit(X_train, y_train)
    print("RMSE on Training set :", rmse_cv(lr, data, y).mean())
    y_train_pred = lr.predict(data)
    print('rmsle calculate by self:', rmsle(list(np.exp(y) - 1), list(np.exp(y_train_pred) - 1)))
    plt.scatter(y_train_pred, y_train_pred - y, c="blue", marker="s", label="Training data")
    plt.title("Linear regression")
    plt.xlabel("Predicted values")
    plt.ylabel("Residuals")
    plt.legend(loc="upper left")
    plt.hlines(y=0, xmin=10.5, xmax=13.5, color="red")
    plt.show()
    # Plot predictions
    plt.scatter(y_train_pred, y, c="blue", marker="s", label="Training data")
    plt.title("Linear regression")
    plt.xlabel("Predicted values")
    plt.ylabel("Real values")
    plt.legend(loc="upper left")
    plt.plot([10.5, 13.5], [10.5, 13.5], c="red")
    plt.show()
    return lr

linear_regression()

RMSE on Training set : 0.345447043724
rmsle calculate by self: 21945.3834665

這裡寫圖片描述

這裡寫圖片描述

分析:從均方根誤差RMSE和預測值-真實值的散點圖分佈來看,根據提取的屬性所建立的線性房價評估模型有一定程度的誤差,用來預測房價不是非常的精準,還需要對比其它型別的模型來考量,有可能是作者的資料前處理的方法與屬性選取還有待改進。