數據探索,數據預處理,數據可視化
觀察數據分佈,處理異常值,缺失值
# coding: utf-8
"""
dist-所在區
roomnum-室的數量
halls-廳的數量
AREA-房屋面積
floor-樓層
subway-是否臨近地鐵
school-是否學區房
price-平米單價
"""
import pandas as pd
import numpy as np
import math
import matplotlib.pyplot as plt
import matplotlib
import seaborn as sns
import statsmodels.api as sm
from numpy import corrcoef,array
#from IPython.display import HTML, display
from statsmodels.formula.api import ols
import os
matplotlib.rcParams['axes.unicode_minus']=False#解決保存圖像時負號'-'顯示爲方塊的問題
plt.rcParams['font.sans-serif'] = ['SimHei']#指定默認字體
os.chdir(r"H:\2019-2-3新華書店筆記以及資料\資料\第四講作業-二手房房價影響因素分析")
datall=pd.read_csv("sndHsPr.csv")
print(datall.shape) #樣本量
(16210, 8)
dat0.drop_duplicates(inplace=True)
dat0=datall.copy()
dat0.describe(include="all").T #查看數據基本描述
count | unique | top | freq | mean | std | min | 25% | 50% | 75% | max | |
---|---|---|---|---|---|---|---|---|---|---|---|
dist | 16210 | 6 | fengtai | 2947 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
roomnum | 16210 | NaN | NaN | NaN | 2.16619 | 0.809907 | 1 | 2 | 2 | 3 | 5 |
halls | 16210 | NaN | NaN | NaN | 1.22141 | 0.532048 | 0 | 1 | 1 | 2 | 3 |
AREA | 16210 | NaN | NaN | NaN | 91.7466 | 44.0008 | 30.06 | 60 | 78.83 | 110.517 | 299 |
floor | 16210 | 3 | middle | 5580 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
subway | 16210 | NaN | NaN | NaN | 0.827822 | 0.377546 | 0 | 1 | 1 | 1 | 1 |
school | 16210 | NaN | NaN | NaN | 0.303085 | 0.459606 | 0 | 0 | 0 | 1 | 1 |
price | 16210 | NaN | NaN | NaN | 61151.8 | 22293.4 | 18348 | 42812.2 | 57473 | 76099.8 | 149871 |
得出此數據並無缺失值(若缺失值,可以用均值,衆數,平均數替換,具體情況看分佈),稍後只需對異常值進行調整
dat0.price=dat0.price/10000 #價格單位轉換成萬元
#將城區的水平由拼音改成中文,以便作圖輸出美觀
dict1 = {
u'chaoyang' : "朝陽",
u'dongcheng' : "東城",
u'fengtai' : "豐臺",
u'haidian' : "海淀",
u'shijingshan' : "石景山",
u'xicheng': "西城"
}
dat0.dist = dat0.dist.apply(lambda x : dict1[x])
dat0.head()
dist | roomnum | halls | AREA | floor | subway | school | price | |
---|---|---|---|---|---|---|---|---|
0 | 朝陽 | 1 | 0 | 46.06 | middle | 1 | 0 | 4.8850 |
1 | 朝陽 | 1 | 1 | 59.09 | middle | 1 | 0 | 4.6540 |
2 | 海淀 | 5 | 2 | 278.95 | high | 1 | 1 | 7.1662 |
3 | 海淀 | 3 | 2 | 207.00 | high | 1 | 1 | 5.7972 |
4 | 豐臺 | 2 | 1 | 53.32 | low | 1 | 1 | 7.1268 |
#頻次統計
dat0.dist.value_counts().plot(kind = 'pie') #繪製柱柱形圖
dat0.dist.agg(['value_counts'])
#dat0.dist.value_counts()
value_counts | |
---|---|
豐臺 | 2947 |
海淀 | 2919 |
朝陽 | 2864 |
東城 | 2783 |
西城 | 2750 |
石景山 | 1947 |
dat0.price.groupby(dat0.dist).mean().sort_values(ascending= True).plot(kind = 'barh') #不同城區的單位房價面積均值情況
dat1=dat0[['dist','price']]
dat1.dist=dat1.dist.astype("category")# 區域變成分類變量
dat1.dist.cat.set_categories(["石景山","豐臺","朝陽","海淀","東城","西城"],inplace=True)#分類變量的順序
#dat1.sort_values(by=['dist'],inplace=True)
sns.boxplot(x='dist',y='price',data=dat1)
#dat1.boxplot(by='dist',patch_artist=True)
plt.ylabel("單位面積房價(萬元/平方米)")
plt.xlabel("城區")
plt.title("城區對房價的分組箱線圖")
#不同臥室數的單位面積房價差異不大
dat4=dat0[['roomnum','price']]
dat4.price.groupby(dat4.roomnum).mean().plot(kind='bar')
dat4.boxplot(by='roomnum',patch_artist=True)
#廳數對單位面積房價有輕微影響
dat5=dat0[['halls','price']]
dat5.price.groupby(dat5.halls).mean().plot(kind='bar')
dat5.boxplot(by='halls',patch_artist=True)
#不同樓層的單位面積房價差異不明顯
dat6=dat0[['floor','price']]
dat6.price.groupby(dat5.halls).mean().plot(kind='bar')
dat6.boxplot(by='floor',patch_artist=True)
#地鐵和學區房的數量以及比例關係
print(pd.crosstab(dat0.subway,dat0.school))
sub_sch=pd.crosstab(dat0.subway,dat0.school)
sub_sch = sub_sch.div(sub_sch.sum(1),axis = 0)
sub_sch
school 0 1 subway 0 2378 413 1 8919 4500
school | 0 | 1 |
---|---|---|
subway | ||
0 | 0.852024 | 0.147976 |
1 | 0.664655 | 0.335345 |
def stack2dim(raw, i, j, rotation = 0, location = 'upper left'):
'''
此函數是爲了畫兩個維度標準化的堆積柱狀圖
要求是目標變量j是二分類的
raw爲pandas的DataFrame數據框
i、j爲兩個分類變量的變量名稱,要求帶引號,比如"school"
rotation:水平標籤旋轉角度,默認水平方向,如標籤過長,可設置一定角度,比如設置rotation = 40
location:分類標籤的位置,如果被主體圖形擋住,可更改爲'upper left'
'''
import math
data_raw = pd.crosstab(raw[i], raw[j])
data = data_raw.div(data_raw.sum(1), axis=0) # 交叉錶轉換成比率,爲得到標準化堆積柱狀圖
# 計算x座標,及bar寬度
createVar = locals()
x = [0] #每個bar的中心x軸座標
width = [] #bar的寬度
k = 0
for n in range(len(data)):
# 根據頻數計算每一列bar的寬度
createVar['width' + str(n)] = data_raw.sum(axis=1)[n] / sum(data_raw.sum(axis=1))
width.append(createVar['width' + str(n)])
if n == 0:
continue
else:
k += createVar['width' + str(n - 1)] / 2 + createVar['width' + str(n)] / 2 + 0.05
x.append(k)
# 以下是通過頻率交叉表矩陣生成一列對應堆積圖每一塊位置數據的數組,再把數組轉化爲矩陣
y_mat = []
n = 0
for p in range(data.shape[0]):
for q in range(data.shape[1]):
n += 1
y_mat.append(data.iloc[p, q])
if n == data.shape[0] * 2:
break
elif n % 2 == 1:
y_mat.extend([0] * (len(data) - 1))
elif n % 2 == 0:
y_mat.extend([0] * len(data))
y_mat = np.array(y_mat).reshape(len(data) * 2, len(data))
y_mat = pd.DataFrame(y_mat) # bar圖中的y變量矩陣,每一行是一個y變量
# 通過x,y_mat中的每一行y,依次繪製每一塊堆積圖中的每一塊圖
createVar = locals()
for row in range(len(y_mat)):
createVar['a' + str(row)] = y_mat.iloc[row, :]
if row % 2 == 0:
if math.floor(row / 2) == 0:
label = data.columns.name + ': ' + str(data.columns[row])
plt.bar(x, createVar['a' + str(row)],
width=width[math.floor(row / 2)], label='0', color='#5F9EA0')
else:
plt.bar(x, createVar['a' + str(row)],
width=width[math.floor(row / 2)], color='#5F9EA0')
elif row % 2 == 1:
if math.floor(row / 2) == 0:
label = data.columns.name + ': ' + str(data.columns[row])
plt.bar(x, createVar['a' + str(row)], bottom=createVar['a' + str(row - 1)],
width=width[math.floor(row / 2)], label='1', color='#8FBC8F')
else:
plt.bar(x, createVar['a' + str(row)], bottom=createVar['a' + str(row - 1)],
width=width[math.floor(row / 2)], color='#8FBC8F')
plt.title(j + ' vs ' + i)
group_labels = [data.index.name + ': ' + str(name) for name in data.index]
plt.xticks(x, group_labels, rotation = rotation)
plt.ylabel(j)
plt.legend(shadow=True, loc=location)
plt.show()
stack2dim(dat0, i="subway", j="school")
#地鐵、學區的分組箱線圖
dat2=dat0[['subway','price']]
dat3=dat0[['school','price']]
dat2.boxplot(by='subway',patch_artist=True)
dat3.boxplot(by='school',patch_artist=True)
#面積和房價的相關關係
datA=dat0[['AREA','price']]
plt.scatter(datA.AREA,datA.price,marker='.')
#求AREA和price的相關係數矩陣
data1=array(datA['price'])
data2=array(datA['AREA'])
datB=array([data1,data2])
corrcoef(datB)
array([[ 1. , -0.07395475], [-0.07395475, 1. ]])
#看到從左至右逐漸稀疏的散點圖,第一反應是對Y取對數(還可以根號,倒數等)
#房屋面積和單位面積房價(取對數後)的散點圖
datA['price_ln'] = np.log(datA['price']) #對price取對數
plt.figure(figsize=(8,8))
plt.scatter(datA.AREA,datA.price_ln,marker='.')
plt.ylabel("單位面積房價(取對數後)")
plt.xlabel("面積(平方米)")
#求AREA和price_ln的相關係數矩陣
data1=array(datA['price_ln'])
data2=array(datA['AREA'])
datB=array([data1,data2])
corrcoef(datB)
array([[ 1. , -0.05811827], [-0.05811827, 1. ]])
相關係數較低,之後可以交給模型來判斷相關程度
數據建模
#建模之前,數據去重,將特徵轉換,將地區,樓層轉換爲特徵
dat0.dist.unique()
dat0.floor=dat0.floor.apply(lambda x:1 if x=='low' else (2 if x=='middle' else(3)))
dist_dumm=pd.get_dummies(dat0.dist)
dat0=pd.concat([dat0,dist_dumm],axis=1)
dat0.drop(['dist'],axis=1,inplace=True)
features=dat0.iloc[:,:-1].copy()
prices=dat0.price.copy()
f_train,f_test,p_train,p_test=train_test_split(features, prices,test_size=0.2, random_state=0)
先使用較爲簡單的線性迴歸,使用預測值和測試集的值比較,以標準差來判斷模型優劣
#線性迴歸
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
lineR = LinearRegression()
lineR.fit(f_train,p_train)
y_pred = lineR.predict(f_test)
print(lineR.coef_, lineR.intercept_)#打印出模型的參數
[[-1.01571866e-16 -1.40658969e-15 -2.85145171e-17 -1.36369393e-17 7.15942502e-16 5.95711390e-16 -2.41642525e-16 -1.42857143e-01 -1.42857143e-01 -1.42857143e-01 -1.42857143e-01 -1.42857143e-01 7.14285714e-01 -1.42857143e-01 -1.42857143e-01 -1.42857143e-01 -1.42857143e-01 -1.42857143e-01]] [0.28571429]
#評價
#(1) 評價測度
# 對於分類問題,評價測度是準確率,但這種方法不適用於迴歸問題。我們使用針對連續數值的評價測度(evaluation metrics)。
# 這裏介紹3種常用的針對線性迴歸的測度。
# 1)平均絕對誤差(Mean Absolute Error, MAE)
# (2)均方誤差(Mean Squared Error, MSE)
# (3)均方根誤差(Root Mean Squared Error, RMSE)
# 這裏我使用RMSE。
sum_mean=0
for i in range(len(p_test)):
sum_mean+=(y_pred[i]-p_test.values[i])**2
sum_erro=np.sqrt(sum_mean/len(p_test))
# calculate RMSE by hand
print ("RMSE by hand:",sum_erro)
RMSE by hand: [6.28699327]
#多項式迴歸
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(degree=3)
x_poly = poly.fit_transform(f_train)
lrp = LinearRegression()
lrp.fit(x_poly, p_train)
y_poly_pred = lrp.predict(x_poly)
sum_mean=0
for i in range(len(p_test)):
sum_mean+=(y_poly_pred[i]-p_test.values[i])**2
sum_erro=np.sqrt(sum_mean/len(p_test))
# calculate RMSE by hand
print ("RMSE by hand:",sum_erro)
RMSE by hand: 3.156191349597294
明顯看到多項式迴歸得到的均方根誤差減小了
#嶺迴歸
from sklearn.linear_model import Ridge #加載嶺迴歸方法
clf = Ridge(alpha=1.0, fit_intercept=True) #創建嶺迴歸實例
clf.fit(f_train, p_train) #調用fit函數使用訓練集訓練迴歸器
score = clf.score(f_test, p_test)
c_test=clf.predict(f_test)
sum_mean=0
for i in range(len(p_test)):
sum_mean+=(c_test[i]-p_test.values[i])**2
sum_erro=np.sqrt(sum_mean/len(p_test))
# calculate RMSE by hand
print ("RMSE by hand:",sum_erro)
RMSE by hand: 5.313616011775014e-05
嶺迴歸得到的均方根誤差比上面2種模型的更小,得出最優模型